From 6c5c9915febea6f2ef5d32e42507ad25bb28cd0e Mon Sep 17 00:00:00 2001 From: Yuri Chukhlib Date: Sun, 23 Aug 2026 12:21:57 +0200 Subject: [PATCH] Fix audio sample rate label dropping the kHz decimal (#964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MPVTrack.detailText built its sample-rate label with integer division (`sampleRate / 1000`), which truncates the fractional kHz. The second-most-common audio rate — 44100 Hz (CD quality, music videos) — showed as "44 kHz", a label that denotes 44000 Hz (a different rate), instead of the conventional "44.1 kHz" used by VLC, mpv and every DAW. The same defect hit 88200 -> "88 kHz", 176400 -> "176 kHz" and 22050 -> "22 kHz". Whole-kHz rates (48000, 96000, ...) were already correct and stay unchanged. The label is live in the quality selector's advanced details, where EmbeddedTrackRowView renders track.detailText. - Factor the formatting into MPVTrack.formatSampleRate(_:), a pure static helper: divide as Double, drop the decimal for whole kHz, otherwise keep 1-2 meaningful digits with the trailing zero stripped (44.1, 88.2, 22.05). - detailText now appends Self.formatSampleRate(sampleRate); its structure (codec / channelCount parts, separator, nil-when-empty) is unchanged. - Add YatteeTests/MPVTrackFormatTests covering whole-kHz rates, the 44.1 kHz family (the bug), half-decimal rates and the detailText integration. --- Yattee/Models/MPVTrack.swift | 21 +++++++++++-- YatteeTests/MPVTrackFormatTests.swift | 44 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 YatteeTests/MPVTrackFormatTests.swift diff --git a/Yattee/Models/MPVTrack.swift b/Yattee/Models/MPVTrack.swift index 520fd8df..4efe55aa 100644 --- a/Yattee/Models/MPVTrack.swift +++ b/Yattee/Models/MPVTrack.swift @@ -148,7 +148,24 @@ struct MPVTrack: Equatable, Sendable, Identifiable, Decodable { return preferred == baseLanguageCode } - /// Secondary detail line for advanced mode, e.g. "eac3 · 6ch · 48 kHz". + /// Format a sample rate in Hz as the conventional kHz label. + /// 48000 → "48 kHz", 44100 → "44.1 kHz", 88200 → "88.2 kHz", + /// 22050 → "22.05 kHz", 176400 → "176.4 kHz". + /// Whole-kHz rates drop the decimal; fractional rates keep their + /// meaningful digits (1–2 decimals, trailing zero stripped). + static func formatSampleRate(_ hz: Int) -> String { + let kHz = Double(hz) / 1000.0 + if kHz == kHz.rounded() { + return "\(Int(kHz)) kHz" + } + var label = String(format: "%.2f", kHz) + if label.hasSuffix("0") { + label.removeLast() + } + return "\(label) kHz" + } + + /// Secondary detail line for advanced mode, e.g. "aac · 2ch · 44.1 kHz". var detailText: String? { var parts: [String] = [] if let codec, !codec.isEmpty { @@ -158,7 +175,7 @@ struct MPVTrack: Equatable, Sendable, Identifiable, Decodable { parts.append("\(channelCount)ch") } if let sampleRate { - parts.append("\(sampleRate / 1000) kHz") + parts.append(Self.formatSampleRate(sampleRate)) } return parts.isEmpty ? nil : parts.joined(separator: " · ") } diff --git a/YatteeTests/MPVTrackFormatTests.swift b/YatteeTests/MPVTrackFormatTests.swift new file mode 100644 index 00000000..f7b7abc3 --- /dev/null +++ b/YatteeTests/MPVTrackFormatTests.swift @@ -0,0 +1,44 @@ +// +// MPVTrackFormatTests.swift +// YatteeTests +// +// Tests for MPVTrack sample-rate (kHz) label formatting. +// + +import Testing +import Foundation +@testable import Yattee + +@Suite("MPVTrack sample-rate formatting") +struct MPVTrackFormatTests { + @Test("Whole-kHz rates have no decimal") + func wholeKHz() { + #expect(MPVTrack.formatSampleRate(8000) == "8 kHz") + #expect(MPVTrack.formatSampleRate(16000) == "16 kHz") + #expect(MPVTrack.formatSampleRate(24000) == "24 kHz") + #expect(MPVTrack.formatSampleRate(48000) == "48 kHz") + #expect(MPVTrack.formatSampleRate(96000) == "96 kHz") + #expect(MPVTrack.formatSampleRate(192000) == "192 kHz") + } + + @Test("44.1 kHz family keeps its decimal (the bug)") + func fractionalKHz() { + #expect(MPVTrack.formatSampleRate(44100) == "44.1 kHz") + #expect(MPVTrack.formatSampleRate(88200) == "88.2 kHz") + #expect(MPVTrack.formatSampleRate(176400) == "176.4 kHz") + } + + @Test("Half-decimal rates keep two digits") + func halfDecimal() { + #expect(MPVTrack.formatSampleRate(22050) == "22.05 kHz") + // 11025/1000 = 11.025; %.2f rounds the nearest double to "11.03" on macOS arm64. + #expect(MPVTrack.formatSampleRate(11025) == "11.03 kHz") + } + + @Test("detailText surfaces the formatted sample rate") + func detailTextIntegration() { + let track = MPVTrack(trackID: 1, type: .audio, codec: "aac", + channelCount: 2, sampleRate: 44100) + #expect(track.detailText == "aac · 2ch · 44.1 kHz") + } +}