A track marked missing whose replacement is already on disk under another
row — on the operator's library 355 of 451 missing tracks, nearly all Lidarr
mp3 -> flac upgrades — is folded into the replacement: likes, plays, playlist
entries and tags move across and the missing row goes. Move adoption could
not catch these: the replacements were re-encodes (no shared audio hash) with
no recording MBID at import.
Pairs (ListMissingTrackPairs): the same recording MBID within the album
group, or the same album row and title ignoring case. Each side must have
exactly one candidate; conflicting MBIDs refuse a pair. No duration or track
position gate: on the 142 pairs known to be one recording, 18% differed by
over 2s and the poorly tagged set is where numbering is broken (spike #5274).
Each pair folds in its own transaction after locking both rows and checking
the pair still holds. Runs automatically (operator, 2026-10-07) after a full
scan, after a watcher batch that added or updated tracks, and after an
AcoustID pass that matched any track. The first scan after deploy repairs the
existing rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration 0069 adds tracks.mbid_source (tag | acoustid), a lookup state per
track (matched | ambiguous | no_match | failed) and the acoustid_settings
row (off, no key, min score 0.85).
The file's tag outranks a lookup (D4). UpsertTrack keeps a looked-up id
through a re-read that finds no tag id and replaces it as soon as one
appears. SetTrackMbidFromAcoustID refuses to write over a tag id.
The worker fingerprints each untagged track with fpcalc's compressed
print, looks it up and writes an id only when D5 settles it: one
recording at or above the threshold, or one left after matching title and
length. Ambiguous and no-match results write nothing. A key AcoustID
refuses, or the service being unreachable, stops the pass and is reported
in the worker's status. It never counts as a verdict on a track.
A changed file drops its lookup in the scan. The re-lookup takes back an
id that no longer matches.
Admin API: GET /api/admin/library/acoustid (settings, status, coverage by
source), PUT …/acoustid-settings (write-only key), POST …/acoustid/run,
GET …/acoustid/unsettled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>