soulsync/core/repair_jobs
Broque Thomas 6e61890551 Stop watchlist re-downloading compilation tracks; catch slskd dedup orphans
Two related bugs reported on Discord by Mushy.

1. The watchlist re-downloaded the same OST track up to 7 times.

   ``is_track_missing_from_library`` compared Spotify's album name and
   the media-server scan's album name with a raw SequenceMatcher at a
   strict 0.85 threshold. Compilations and soundtracks routinely fail
   this — Spotify reports
   ``"Napoleon Dynamite (Music From The Motion Picture)"`` while the
   Plex / Navidrome / Jellyfin tag scan saves it as
   ``"Napoleon Dynamite OST"``. Raw similarity ≈ 0.49, so the scanner
   declared the track missing on every 30-minute scan and added it back
   to the wishlist. The wishlist then issued a fresh download. slskd
   appended ``_<19-digit-ns-timestamp>`` to each new copy because the
   target file already existed, and the user ended up with seven copies
   of one song in one folder.

   Fix: extract two pure helpers — ``_normalize_album_for_match``
   strips qualifier parentheticals (Music From X, OST, Deluxe Edition,
   Remastered, Anniversary, etc.) and trailing dash-clauses;
   ``_albums_likely_match`` checks equality after normalization,
   substring containment, and a relaxed 0.6 fuzzy ratio. A volume /
   part / disc / standalone-trailing-number guard rejects pairs like
   ``"Greatest Hits Vol. 1"`` vs ``"Greatest Hits Vol. 2"`` so the
   relaxed threshold doesn't introduce false positives on serialized
   releases. After this change the Napoleon Dynamite case collapses
   to ``"napoleon dynamite" == "napoleon dynamite"`` via the equality
   short-circuit and the redownload loop dies.

2. The duplicate detector found only one of the seven dupe files.

   The detector buckets tracks by the first 4 chars of their normalized
   tag title. Files written by slskd directly into a library folder
   often get inconsistent (or blank) tags from the media-server rescan,
   so the seven copies were bucketed apart by parsed title and never
   compared.

   Fix: refactor the per-bucket comparison into ``_scan_bucket``, then
   add a second pass — ``_build_filename_buckets`` re-buckets leftover
   tracks by canonical filename stem (slskd dedup tail stripped via
   ``_strip_slskd_dedup_suffix``, same regex the import-cleanup PR uses)
   plus extension. Filename agreement is itself strong evidence the
   files came from the same source download, so the second pass calls
   ``_scan_bucket`` with ``require_metadata_match=False`` to skip the
   title / artist / cross-album gates. The same-physical-file guard
   still runs so bind-mount duplicates aren't flagged.

72 new regression tests across two files cover the album-match
helpers (28 tests including the Napoleon Dynamite scenario, 7 volume
disagreements, 8 positive/negative pairs, 5 defensive cases) and the
new filename-bucket pass (16 tests across bucket construction, scan
integration, and existing title-pass behavior). Full pytest 1509
passed; ruff clean.

Reported by Mushy in Discord.
2026-05-01 12:57:50 -07:00
..
__init__.py Add Discography Backfill maintenance job 2026-04-20 22:04:38 -07:00
acoustid_scanner.py Fix null-pointer error in acoustid_scanner 2026-04-17 19:17:24 +03:00
album_completeness.py Fix Album Completeness job reporting zero findings for every album 2026-04-24 12:39:41 -07:00
album_tag_consistency.py Fix Album Tag Consistency job: add missing _get_settings method 2026-03-24 06:38:05 -07:00
base.py Improve graceful shutdown and rollback safety 2026-04-12 15:17:18 +03:00
cache_evictor.py Add metadata cache maintenance and health monitoring 2026-03-30 07:40:20 -07:00
dead_file_cleaner.py Fix dead file cleaner false positives from transfer path resolution failure 2026-03-17 09:51:31 -07:00
discography_backfill.py Rebuild Discography Backfill: auto-wishlist, Fix All, section UI 2026-04-21 18:44:43 -07:00
duplicate_detector.py Stop watchlist re-downloading compilation tracks; catch slskd dedup orphans 2026-05-01 12:57:50 -07:00
fake_lossless_detector.py Enrich repair findings with album art, artist images & live job progress 2026-03-15 14:08:26 -07:00
library_reorganize.py Add $cdnum template variable for multi-disc filenames 2026-04-21 22:55:37 -07:00
live_commentary_cleaner.py Fix watchlist content filters: live false positives + auto-scan bypass 2026-04-21 19:16:25 -07:00
lossy_converter.py Add independent Blasphemy Mode setting for lossy converter job 2026-03-22 08:08:47 -07:00
mbid_mismatch_detector.py Fix MBID Mismatch Detector comparing MB title against DB title (filename) instead of embedded tag 2026-04-14 12:19:41 -07:00
metadata_gap_filler.py Make metadata gap filler source-aware 2026-04-15 21:35:57 +03:00
missing_cover_art.py Respect source preference in cover art repair job 2026-04-15 21:28:12 +03:00
orphan_file_detector.py Fix dismissed findings reappearing and reduce false orphan detections 2026-04-10 17:42:39 -07:00
single_album_dedup.py Fix bulk Fix All ignoring Single/Album Dedup findings and expand version keywords 2026-03-19 08:53:14 -07:00
track_number_repair.py Clean up 286 ruff lint errors to unblock CI and fix 10 latent bugs 2026-04-21 13:30:52 -07:00
unknown_artist_fixer.py Fix unknown_artist_fixer crash on missing deezer_track_id column 2026-04-20 16:29:38 -07:00