fix(downloads): make import quality comparison profile-aware (#582)#752
Open
jaredvincent414 wants to merge 3 commits into
Open
fix(downloads): make import quality comparison profile-aware (#582)#752jaredvincent414 wants to merge 3 commits into
jaredvincent414 wants to merge 3 commits into
Conversation
…rrs#582) Import gating ran through ImportQualityEvaluator.IsAcceptable, which accepted a QualityProfile but never read it. The decision was a regex bitrate compare, so any non-numeric quality (an existing FLAC) failed to parse and fell through to "acceptable" — letting a lossy MP3 import on top of a lossless file and leaving the audiobook with both. Route the decision through the profile-aware QualityMatcher that the automatic-search path already uses. Both sides are projected into AudioQualityInput and ranked by the profile's rung priority. When there is no profile, an empty profile, or a side the profile cannot rank, fall back to codec/bitrate facts so lossy never lands on top of lossless; genuinely unknown quality still imports rather than being dropped. Import compares as "not worse" (allow-equal) rather than the strictly better rule used by search, so the parts of a multi-file audiobook that match the quality already on disk still import. Also drop the hardcoded MP3 bitrate buckets in DownloadImportService, which mislabeled every non-MP3 file as MP3, and fix the repro test's profile, whose priorities ranked MP3 above FLAC and so contradicted the behavior the test asserts.
Add the cases that fence in the new profile-aware gate: a numeric upgrade and a cross-codec (lossless-over-lossy) upgrade both import, equal quality imports, and unknown quality with no profile imports. The equal-quality case is the load-bearing one: it fails if the gate is ever changed to the strictly-better rule that automatic search uses, which would skip every part of a multi-file audiobook whose quality merely matches what is already on disk.
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.
Summary
When a completed download is imported for an audiobook that already has files, Listenarr is supposed to consult the audiobook's quality profile and skip the incoming file when it isn't an upgrade. That decision ran through
ImportQualityEvaluator.IsAcceptable, which accepted aQualityProfilebut never read it — it compared a regex-extracted numeric bitrate instead:Any non-numeric quality (an existing FLAC) fails that parse and falls through to "acceptable", so a lossy MP3 gets imported on top of a lossless file and the audiobook ends up with both.
A profile-aware, lossless-aware comparator already exists in the domain —
QualityMatcher— and the automatic-search path already uses it. Import was the only path still on the old regex evaluator. This PR routes import onto it.Fixes #582. Related: #549 (quality determination algorithm).
Changes
Added
QualityMatcher.IsLossless(AudioQualityInput)— public wrapper over the existing private lossless check, so callers outside the profile path can still honour lossless-vs-lossy.ImportQualityEvaluator.FromFile(...)/FromMetadata(...)— project anAudiobookFileand an incoming download'sAudioMetadatainto the sharedAudioQualityInputvocabulary.ImportQualityEvaluator.Describe(...)— readable quality label for the import skip log.Changed
ImportQualityEvaluator.IsAcceptablenow takesAudioQualityInputand ranks both sides through the profile's rung priority. When there is no profile, an empty profile, or a side the profile cannot rank, it falls back to codec/bitrate facts — lossy never lands on top of lossless, and genuinely unknown quality still imports rather than being silently dropped.DownloadImportServicepicks the hardest-to-beat existing file via the same comparator, and gates the candidate through it.Fixed
"MP3 320kbps"etc.), which mislabelled every non-MP3 file as MP3.Removed
TryParseBitratedecision helper.Testing
dotnet test— 1020 passed, 0 failed.cd fe && npm run test:unit— 389 passed;npm run type-checkclean.dotnet build— 0 warnings, 0 errors. Pre-commit (format, layering,async void) and pre-push (format, type-check, FE tests) hooks pass.The reproduction is
QualityGating_LosslessExisting_SkipsLossyDownload: an audiobook with an existing FLAC and a profile ranking FLAC above MP3 receives a lossy MP3 download. Before this change the MP3 was imported and the audiobook held two files; now it is skipped.Worth being straight about the other four tests: they would also pass against the old code (its regex fell through to "acceptable" for any non-numeric label, so it imported in all four cases). They exist to guard against over-correction, not to prove the fix. The load-bearing one is
QualityGating_EqualQuality_IsImported— it fails if the gate is ever tightened to the strictly-better rule search uses, which would skip every part of a multi-file audiobook whose quality merely matches what is already on disk.Notes
Import compares "not worse"; search compares "strictly better". That asymmetry is deliberate and now documented in the code. Import must accept equal quality, or multi-file audiobooks would have every part skipped.
The delete/replace half of #582 is deliberately not here. The issue also proposes removing existing files when the incoming quality is better. That is a separate, riskier change and — per @Paelsmoessan's comment on the issue — is blocked on #542 (multi-file size aggregation), #736 (recycling bin, so a wrong quality call is not data loss), and #737 (imported files have no stored codec/bitrate). Deferring it keeps this PR to one bug fix.
Behaviour change for users with no quality profile. The fallback means lossy-over-lossless is now blocked even when no profile is configured. This is intended — previously nothing gated those users — but it is a real behaviour change worth flagging.