soulsync/webui/static
Broque Thomas 9cc09118bf AcoustID scanner: multi-candidate match + duration guard + multi-value retag
Closes #587. Three coordinated fixes per codex's diagnosis. AcoustID
verification gate left intact — these fixes target the upstream
scanner false-positive surface plus a separate retag-path gap.

Bug 1 — scanner used recordings[0] as authoritative

`core/repair_jobs/acoustid_scanner.py:_scan_file` only checked the
top fingerprint match's metadata. AcoustID often returns multiple
recordings per fingerprint (sample collisions, multi-MB-record
cases) and the wrong-credited recording can outrank the right-
credited one. Foxxify case 2 (Nana / Nana): top match credited the
wrong artist while a lower-ranked candidate matched the user's
expected metadata exactly.

Lifted the verifier's all-candidates check to a shared pure helper
`core/matching/acoustid_candidates.py:find_matching_recording`. Both
verifier and scanner can now ask "given these candidates, does ANY
of them match expected (title, artist)?" with the same contract.
Scanner suppresses the finding when any candidate matches.

Bug 2 — no duration check guards against fingerprint hash collisions

Foxxify case 3: 17-minute mashup edit fingerprinted to a 5-minute
late-70s Japanese hiphop track (different songs, fingerprint hash
collision on a sampled section). Scanner had no signal to detect
this and would have recommended retagging the 17-min file as the
5-min track.

`duration_mismatches_strongly` in the same helper module flags drifts
beyond max(60s, 35%). Scanner now skips findings when the candidate's
duration disagrees strongly with the file's expected duration. Loaded
duration via the existing tracks SQL (added `t.duration` to the
SELECT). Returns False when either side is unknown — no behavior
change for older rows without duration data.

Bug 3 — scanner retag bypassed multi-value ARTISTS tag setting

`core/repair_worker.py:_fix_wrong_song` called `write_tags_to_file`
with single-string artist updates. The writer only wrote TPE1
(single string) and never read the user's
`metadata_enhancement.tags.write_multi_artist` config. Multi-value
ARTISTS tags got stripped on every retag, contradicting the
post-download enrichment pipeline's behavior.

Per codex's pick (option B over routing through enhance_file_metadata),
extended `write_tags_to_file` with an optional `artists_list`
parameter. Each format-specific writer respects the config flag the
same way enrichment.py does:
- ID3: TPE1 stays as joined display string + TXXX:Artists multi-value
- Vorbis/Opus/FLAC: `artist` display string + `artists` multi-value key
- MP4: \xa9ART as list when on, single string when off

Scanner retag derives the per-artist list by splitting AcoustID's
credit through the existing `split_artist_credit` helper (same
separators the matching layer already uses).

Backward compatible: callers that don't pass `artists_list` get the
exact same single-string write as before. No regression for the
write_artist_image button or any other tag_writer caller.

15 tests on the candidate helper + duration guard.
13 tests on the tag_writer multi-value path (write/skip/single/
no-list cases for FLAC + the config-gate helper).
4 new scanner regression tests pinning lower-ranked candidate
suppression, no-suppression when no candidate matches, duration
mismatch skip, no-skip when duration matches.

Existing scanner tests updated for the new 11-column SQL select
(added duration column to fake schema + test row tuples).

Full suite: 3097 passed. Ruff clean.
2026-05-14 14:09:38 -07:00
..
docs update docs page 2026-03-13 08:35:28 -07:00
vendor Add WebSocket real-time updates with automatic HTTP polling fallback 2026-03-03 18:26:29 -08:00
api-monitor.js Stop watchlist countdown refetch loop 2026-05-02 22:02:00 +03:00
artists.png add static images 2026-03-09 17:35:46 -07:00
automation.png add static images 2026-03-09 17:35:46 -07:00
beatport-ui.js Add "All Libraries (combined)" mode to PlexClient 2026-05-06 15:39:19 -07:00
core.js Keep Issues and artist detail history stable 2026-05-13 22:26:24 +03:00
dashboard.png add static images 2026-03-09 17:35:46 -07:00
discover-section-controller.js Discover controller — Cin pre-review polish 2026-05-07 20:35:10 -07:00
discover.js Your Albums: selectable wishlist modal + Tidal album resolution 2026-05-11 12:36:16 -07:00
discover.png add static images 2026-03-09 17:35:46 -07:00
docs-images-needed.md Service Badges, Page Headers, Docs Page, and Bug Fixe 2026-03-10 14:47:11 -07:00
docs.js Clean up stale Artists-page references in helper.js + docs.js 2026-04-22 20:29:32 -07:00
downloads.js Quarantine management — list, approve, delete, recover 2026-05-14 08:06:19 -07:00
enrichment.js Repair job card badge — show pending count, not last-scan count 2026-05-07 08:28:17 -07:00
explorer.png Create explorer.png 2026-03-30 17:23:31 -07:00
favicon.png enlarge favicon 2026-02-13 16:11:32 -08:00
help.png Add media server setup, processing settings, text import, automation history, and streaming details 2026-03-10 15:35:18 -07:00
helper.js AcoustID scanner: multi-candidate match + duration guard + multi-value retag 2026-05-14 14:09:38 -07:00
hydrabase.png Add Hydrabase dev UI and WebSocket support 2026-02-21 00:32:04 -08:00
import.png add static images 2026-03-09 17:35:46 -07:00
init.js Move shared shell chrome into bridge 2026-05-13 22:26:26 +03:00
library.js Merge pull request #530 from dlynas/feat/explicit-badges 2026-05-13 15:04:14 -07:00
library.png add static images 2026-03-09 17:35:46 -07:00
manifest.json Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
media-player.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
mobile.css Remove legacy issues shell code 2026-05-13 22:26:22 +03:00
pages-extra.js Persist Find & Add selections as permanent server-playlist match overrides 2026-05-14 09:39:24 -07:00
particles.js Add particle background toggle & optimize accent color caching 2026-03-13 16:05:22 -07:00
placeholder-album.png Add missing placeholder-album.png to stop 404 spam in console 2026-03-23 18:05:11 -07:00
pwa-icon-192.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
pwa-icon-512.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
search.js Keep Issues and artist detail history stable 2026-05-13 22:26:24 +03:00
search.png add static images 2026-03-09 17:35:46 -07:00
settings.js Configurable duration tolerance for downloaded-file integrity check 2026-05-14 06:53:36 -07:00
settings.png add static images 2026-03-09 17:35:46 -07:00
setup-wizard.css Add informative help text and tips to every setup wizard step 2026-04-11 23:26:01 -07:00
setup-wizard.js Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import 2026-04-18 17:35:20 -07:00
shared-helpers.js Fix search source picker defaulting to Spotify regardless of config 2026-05-13 15:36:48 -07:00
shell-bridge.js Move shared shell chrome into bridge 2026-05-13 22:26:26 +03:00
stats-automations.js Remove legacy issues shell code 2026-05-13 22:26:22 +03:00
style.css Polish Download Missing modal tracklist 2026-05-13 18:38:02 -07:00
sw.js Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
sync-services.js Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
sync-spotify.js Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
sync.png add static images 2026-03-09 17:35:46 -07:00
trans.png Add SoulID worker with API-based debut year disambiguation 2026-03-21 10:21:06 -07:00
trans2.png Create trans2.png 2026-03-21 10:23:36 -07:00
whisoul.png Add library repair worker and UI 2026-02-21 15:00:23 -08:00
wishlist-tools.js Polish quarantine UI — fold into Library History modal as third tab 2026-05-14 08:50:24 -07:00
worker-orbs.js Add Discogs to worker orbs animation and fix button styling 2026-04-02 17:18:31 -07:00