Say what a file's audio actually is, on the page built to repair it - #58
Merged
Conversation
The Metadata page reviews a folder of files and repairs what their tags say. It never said anything about the audio underneath those tags, so a 96 kbps download and a lossless rip presented identically on the one page where a whole library passes under review — the place where noticing would cost nothing, because the user is already looking at every row. The obvious way to answer this is the wrong size for the question. A recorder in the same space does it by decoding the samples and running an FFT over them, hunting the brick-wall low-pass a lossy encoder leaves behind, and it earns that: a spectral cut-off is the only thing that catches a lossy file rewrapped as FLAC, where the container is lying. That is a real feature and a much larger one — a DSP pass per file, a confidence model, and a verdict that can be wrong. What is wanted here is the smaller, honest half: what does the file itself claim. TagLib# already has it. The scan opens every file to read its tags, and the same File object carries Properties — bitrate, sample rate, bit depth — so the answer costs one more open per row and no new package, no ffprobe, and no decoding. AudioQuality holds those three numbers and nothing else, and the tier falls out of them. Lossless is decided by bit depth rather than by codec name: TagLib# populates BitsPerSample only for codecs that have a fixed one, which is exactly the lossless set, and reports zero for MP3, AAC, Opus and Vorbis, none of which have one. That is a free and reliable discriminator, and it beats matching a list of codec strings that has to stay right about formats nobody here has seen. The reader is its own seam rather than an extra return value on ILibraryTagStore. That interface is heavily covered and exists so a scan can be tested without a real audio file for every case; widening Read to return a pair would have rewritten those tests to carry a value none of them care about. A second interface costs one more TagLib# open per file during a scan the user asked for, on a folder measured in thousands, and buys leaving that seam alone. LibraryTrack takes the quality as an optional third argument for the same reason — every existing two-argument construction, nearly all of it in tests, keeps compiling untouched. Only the low tier is coloured. A row that announces itself in amber for being 320 kbps is a warning about nothing, and a page where most rows are coloured has taught the user to stop reading the colour. Lossy under 128 kbps is the one reading worth a second look, so it is the only one that gets the caution brush; the rest is stated and left alone. The label goes under the file name in the existing hint style rather than into a pill beside the "Will change" badge, because it describes the file the row came from, not something Save is going to do about it. The .m4a label is the one place the extension cannot answer on its own — Offstream only ever writes AAC into it, but a file from elsewhere can be ALAC. The tier already knows, so a lossless .m4a is labelled ALAC rather than guessed wrong. TagLibAudioQualityReaderTests encodes with BitrateMode.Constant rather than the averaging default, which is not incidental. The shared fixture's source is a two-second 440 Hz sine — trivially compressible — and ABR allocates on spectral demand, so a request for 320 kbps came back under 128 and the tier assertion failed on encoder behaviour that was entirely correct. The plan already records the neighbouring version of this trap, that pink noise is the wrong thing to measure ABR on. CBR pins every frame to the request and makes the reading deterministic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Metadata page reviews a folder of files and repairs what their tags say. It never said anything about the audio underneath those tags, so a 96 kbps download and a lossless rip presented identically on the one page where a whole library passes under review — the place where noticing would cost nothing, because the user is already looking at every row.
Each row now carries a line under its file name:
MP3 · 320 kbps,FLAC · Lossless.Why not the spectral approach
The obvious way to answer this is the wrong size for the question. A recorder in the same space does it by decoding the samples and running an FFT over them, hunting the brick-wall low-pass a lossy encoder leaves behind, and it earns that: a spectral cut-off is the only thing that catches a lossy file rewrapped as FLAC, where the container is lying. That is a real feature and a much larger one — a DSP pass per file, a confidence model, and a verdict that can be wrong.
This is the smaller, honest half: what does the file itself claim. No decoding, no ffprobe, no new package.
How
TagLib#already has it. The scan opens every file to read its tags, and the sameFileobject carriesProperties— bitrate, sample rate, bit depth — so the answer costs one more open per row.Lossless is decided by bit depth, not codec name. TagLib# populates
BitsPerSampleonly for codecs that have a fixed one, which is exactly the lossless set, and reports zero for MP3, AAC, Opus and Vorbis, none of which have one. That beats matching a list of codec strings that has to stay right about formats nobody here has seen.Tiers:
Lossless(bit depth > 0), then by bitrate —>=256High,128–255Medium,<128Low.Decisions worth flagging in review
ILibraryTagStore. That interface is heavily covered and exists so a scan can be tested without a real audio file for every case; wideningReadto return a pair would have rewritten those tests to carry a value none of them care about. The cost is one more TagLib# open per file during a user-initiated scan, on a folder measured in thousands.LibraryTracktakes quality as an optional third argument, so every existing two-argument construction — nearly all of it in tests — keeps compiling untouched..m4ais labelled ALAC, not AAC. The extension cannot answer that on its own; the tier already knows.TagLibAudioQualityReaderTestsencodes withBitrateMode.Constant, and that is not incidental. The shared fixture's source is a two-second 440 Hz sine — trivially compressible — and ABR allocates on spectral demand, so a request for 320 kbps came back under 128 and the tier assertion failed on encoder behaviour that was entirely correct. The plan already records the neighbouring version of this trap (pink noise being the wrong thing to measure ABR on). CBR makes the reading deterministic.Testing
AudioQualityTests(tier thresholds and boundaries),TagLibAudioQualityReaderTests(Ffmpeg-tagged, against real encoded files).LibraryScannerTests(quality reaches the row),LibraryTrackViewModelTests(label text, caution flag, ALAC-vs-AAC naming, absent-when-unread).🤖 Generated with Claude Code