Skip to content

fix(slskd): cover-art false positives, variant-recording matches, path-component variant judging - #77

Merged
chodeus merged 5 commits into
mainfrom
fix/artwork-false-positives-and-variant-tracks
Aug 7, 2026
Merged

fix(slskd): cover-art false positives, variant-recording matches, path-component variant judging#77
chodeus merged 5 commits into
mainfrom
fix/artwork-false-positives-and-variant-tracks

Conversation

@chodeus

@chodeus chodeus commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Three matching/quality fixes from the GLXY audit, each traced to root cause and verified against the real ffmpeg build (8.1.2) the scanner uses.

1. Malformed cover art deletes healthy files

The scanner decodes with -err_detect explode, which sets AV_EF_EXPLODE. libavformat then aborts input open when a FLAC picture block has an unreadable MIME field (flac_picture.c returns AVERROR_INVALIDDATA only under EXPLODE) — before -map 0:a can limit the scan to audio. The file is deleted, blocklisted and re-searched despite intact audio.

Reproduced with a hand-built FLAC carrying one malformed picture block:

invocation exit result
scanner args (-err_detect explode) 183 Error opening inputdeclared corrupt
same file, no explode 0 one warning line, decodes fine
decoded audio MD5, clean vs bad-picture byte-identical (4c0c7e80…)

The reproduced stderr matches the production strike text exactly. GLXY "Love Lost" was destroyed this way from two different peers, which is why that album never imported.

Fix — re-verify, never just ignore. The audio was never judged on the failing pass, so passing on that basis would be unsafe. When (and only when) the artwork markers appear, a second decode runs without explode so the demuxer skips the picture. That pass is the authority and stays fail-closed on all three axes: timeout, non-zero exit, or any error output left after benign metadata noise is stripped (needed because without explode ffmpeg exits 0 on recoverable decoder errors). Failures without the markers keep the unchanged path.

End-to-end against the real binary:

clean.flac            -> CLEAN (first pass)
badpic.flac           -> CLEAN (artwork malformed, audio fine)
badpic+badaudio.flac  -> CORRUPT (real damage past the artwork)
truncated.flac        -> CORRUPT (first pass)

2. Variant recordings satisfy plain wanted tracks

A wanted title is a substring of its own variant, so MatchWantedTrackFiles counted Proposition (Radio Edit) as covering Proposition. A radio-edit-only source looked like full coverage, won the grab, and was rejected at import on track length — burning a grab plus a 24 h failed-skip window each time. 22 albums in the last three weeks failed import holding variant-qualified files.

Matching now rejects a file whose qualifier conflicts with the wanted title, reusing the folder-level RemixSignaturesConflict so the rules live in one place. Only pairs where one side is decorated pay for the check, and MusicBrainz secondary types still forgive the box-set case (release marked Live, track titles plain).

3. Path components judged as one candidate

The folder check tested leaf and parent separately and rejected if either conflicted — wrong in both directions:

  • A generic parent vetoed an album whose own title carries the qualifier: "Live at Wembley" under Music\ conflicted because the parent lacks the word. Live-titled albums were unmatchable in the commonest layout there is.
  • The "Album (Live)\FLAC" rescue the old comment promised never worked — the leaf's own missing-qualifier conflict rejected the folder before the parent was ever consulted.

The qualifier may sit in any component, so the candidate profile is now the union over leaf and parent, judged once. The union is deliberately one-way — it can only ADD a qualifier, never cancel one a sibling carries — so an unwanted (Live) is still rejected whichever component holds it, and a plain sibling cannot excuse it. Concatenating the components would not work: trailing-word detection is position-sensitive, and "One More Light Live" + "FLAC" stops reading as live.

Verification

415 tests pass (28 new). Each fix was driven from live evidence and confirmed empirically before coding, and the new tests were checked against the old code to prove they fail on it:

  • Reverting fix 3 fails exactly the two bug-targeting tests while the four protection tests still pass — the guard against unwanted variants is provably intact.
  • Reverting fix 2's plumbing fails the box-set regression test.

Summary by CodeRabbit

  • Bug Fixes

    • Improved audio validation when malformed embedded cover art causes decoding failures.
    • Prevented artwork-only errors from incorrectly marking otherwise valid audio as corrupt.
    • Improved matching for live, acoustic, demo, extended, mono/stereo, and remix variants.
    • Prevented mismatched variants, such as radio edits versus standard tracks, from being treated as equivalent.
    • Improved matching of live recordings across album, single, and folder naming variations.
  • Tests

    • Added coverage for artwork error handling and variant-aware track matching.

chodeus added 3 commits August 7, 2026 08:24
The corruption scanner runs ffmpeg with -err_detect explode. That sets
AV_EF_EXPLODE, which makes libavformat abort INPUT OPEN when a FLAC picture
block has an unreadable MIME field (flac_picture.c) — before -map 0:a can
limit the scan to audio. The file is then deleted, blocklisted and
re-searched even though its audio is intact.

Verified against ffmpeg 8.1.2 with a hand-built FLAC carrying one malformed
picture block: the scanner args exit 183 "Error opening input", while the
decoded audio MD5 is byte-identical to the clean source. GLXY "Love Lost"
was lost this way from two different peers, which is why it never imported.

A failure carrying the artwork markers now re-verifies with a second decode
without explode, so the demuxer skips the picture and the audio is judged on
its own. That pass is the authority and stays fail-closed: timeout, non-zero
exit, or any error output left after benign metadata noise is stripped all
report corruption (the pass exits 0 on recoverable decoder errors, hence the
stderr check). Failures without the markers — truncation, header damage —
take the unchanged path.
A wanted title is a substring of its own variant, so MatchWantedTrackFiles
counted "Proposition (Radio Edit)" as covering "Proposition". A radio-edit-only
source therefore looked like complete coverage, won the grab, and was rejected
at import on track length — burning a grab and a 24h failed-skip window each
time. Live 2026-08-06: GLXY "Proposition / Mind Less" grabbed exactly that
source; 22 albums across the last three weeks failed import holding
variant-qualified files.

Track matching now rejects a file whose variant qualifier conflicts with the
wanted title, reusing the folder-level RemixSignaturesConflict so the rules
stay in one place. Only pairs where one side is decorated pay for the check —
plain-vs-plain can never conflict — and the target's MusicBrainz secondary
types still forgive the box-set case where the release is marked Live but its
track titles are plain.
Artwork: the live Love Lost stderr is recognised, artwork lines strip as
benign, real decoder errors survive the strip (fail-closed), and truncation
never reaches the re-verify path.

Variants: radio-edit-only sources stop matching plain titles, plain and
matching-variant sources still match, a mixed source counts as partial, and
a Live-typed release with plain track titles still matches its live files.
One test documents the pre-existing parent-folder conflict false negative.
@chodeus chodeus added the release:patch Merge to main → patch release label Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 94aeaadd-c1d9-4c5b-bffc-abc181222da9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds FFmpeg recovery for attached-picture decode failures. It also adds qualifier-aware Soulseek track and folder matching, target variant propagation, and regression tests.

Changes

FFmpeg artwork recovery

Layer / File(s) Summary
Attached-picture re-verification
src/Sleezer/Core/PostProcessing/CorruptionScanner.cs, src/Sleezer/Core/PostProcessing/FfmpegErrorFormatter.cs, tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs
The scanner retries attached-picture failures without -err_detect explode. It accepts files only when significant decoder errors are absent. Tests cover artwork-only, decoder, unrelated, and empty stderr cases.

Soulseek variant matching

Layer / File(s) Summary
Variant qualifier detection
src/Sleezer/Indexers/Soulseek/SlskdTextProcessor.cs, tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs
Variant profiles detect supported qualifiers and union qualifiers across candidate path components.
Variant-aware track matching
src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs, tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs
Album creation passes target variants into matching. Matching rejects conflicting qualified titles while preserving audio-file filtering, one-to-one matching, and coverage counts.
Folder qualifier reconciliation
src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs, tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs
Album validation evaluates leaf and parent folder qualifiers together. Tests cover generic parents, qualified folders, unwanted qualifiers, and plain folders.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

FFmpeg artwork recovery

sequenceDiagram
  participant CorruptionScanner
  participant FfmpegErrorFormatter
  participant FFmpeg
  CorruptionScanner->>FfmpegErrorFormatter: classify stderr
  FfmpegErrorFormatter-->>CorruptionScanner: attached-picture failure
  CorruptionScanner->>FFmpeg: retry decode without -err_detect explode
  FFmpeg-->>CorruptionScanner: exit status and stderr
Loading

Soulseek variant matching

sequenceDiagram
  participant AlbumCreation
  participant SlskdItemsParser
  participant SlskdTextProcessor
  AlbumCreation->>SlskdItemsParser: pass target variant types
  SlskdItemsParser->>SlskdTextProcessor: compare title and path qualifiers
  SlskdTextProcessor-->>SlskdItemsParser: qualifier conflict result
  SlskdItemsParser-->>AlbumCreation: matched audio files and coverage
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's cover-art handling, variant matching, and path-component qualifier changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/artwork-false-positives-and-variant-tracks

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs (1)

740-744: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce the added comment to a two-line gotcha.

Keep the reason for the qualifier check. Remove the detailed import-history explanation.

Proposed change
-                    // A wanted "Proposition" is CONTAINED in "Proposition (Radio
-                    // Edit)" — a different recording that the import gate later
-                    // rejects on track length. Only pairs where one side is
-                    // decorated pay for the check; MB secondary types forgive a
-                    // live/remix album whose track titles are plain.
+                    // Do not let a qualified recording satisfy its base title.
+                    // Target secondary types permit valid live/remix candidates.

As per path instructions, comments must be navigational or gotcha comments of 1-2 lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs` around lines 740 - 744,
Shorten the comment above the qualifier check to two lines, retaining only the
gotcha that a wanted title can be contained in a decorated variant and that the
check should apply only when one side is decorated. Remove the detailed
discussion of import rejection, track length, and MusicBrainz secondary types.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs`:
- Around line 164-177: Update
A_live_album_with_plain_track_titles_still_matches_live_files to add a separate
live target using AlbumType "Single" and RequireCoherentSingleSource enabled,
with both plain wanted track titles. Assert that this target requires coverage
of both live files, ensuring the test exercises MatchWantedTrackFiles rather
than relying on the matching folder.

---

Nitpick comments:
In `@src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs`:
- Around line 740-744: Shorten the comment above the qualifier check to two
lines, retaining only the gotcha that a wanted title can be contained in a
decorated variant and that the check should apply only when one side is
decorated. Remove the detailed discussion of import rejection, track length, and
MusicBrainz secondary types.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79e5caa2-c6c5-4b2b-a1b6-365853d1b580

📥 Commits

Reviewing files that changed from the base of the PR and between bf99a1c and 1a09379.

📒 Files selected for processing (5)
  • src/Sleezer/Core/PostProcessing/CorruptionScanner.cs
  • src/Sleezer/Core/PostProcessing/FfmpegErrorFormatter.cs
  • src/Sleezer/Indexers/Soulseek/SlskdItemsParser.cs
  • src/Sleezer/Indexers/Soulseek/SlskdTextProcessor.cs
  • tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs

Comment thread tests/Sleezer.Tests/SlskdVariantAndArtworkTests.cs
chodeus added 2 commits August 7, 2026 08:29
Adds the live album as a coherence-gated single so wanted-track coverage is
the only route to a match, alongside the existing album-type case.
The variant check tested the leaf and the parent separately and rejected on
either conflicting. That is wrong in both directions.

A generic parent vetoed an album whose own title carries the qualifier:
"Live at Wembley" under "Music\" conflicted because the parent lacks the word,
so live-titled albums were unmatchable in the commonest layout there is. And
the "Album (Live)\FLAC" rescue the comment promised never worked — the leaf's
own missing-qualifier conflict rejected the folder before the parent was
consulted.

The qualifier may sit in any component, so the candidate profile is now the
UNION over leaf and parent, judged once. The union is deliberately one-way: it
can only ADD a qualifier, never cancel one a sibling carries, so an unwanted
"(Live)" is still rejected whichever component holds it — a plain sibling can
no longer excuse it. Concatenating the components would not work; trailing-word
detection is position-sensitive and "One More Light Live" + "FLAC" stops
reading as live.
@chodeus chodeus changed the title fix(slskd): cover-art false positives and variant-recording track matches fix(slskd): cover-art false positives, variant-recording matches, path-component variant judging Aug 7, 2026
@chodeus

chodeus commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chodeus
chodeus merged commit 5e07b1b into main Aug 7, 2026
3 checks passed
@chodeus
chodeus deleted the fix/artwork-false-positives-and-variant-tracks branch August 7, 2026 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release:patch Merge to main → patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant