Deezer enrichment fills in artwork and artist data, but there is no way to say "this album's tags are wrong, fetch them and let me approve the result".
Depends on #598 (safe file writing). This writes across a whole album at once; doing that before writes are robust turns one bad moment into a folder in an unknown state.
Shape
Match the album, then match the tracks inside it. Album matching is the easy half. Track matching is where these features fail, and the approach worth copying is specific:
- Three weighted signals — title 0.60, duration 0.25, track number 0.15. Not equal weights: a title carries most of the identity, a duration confirms it, a track number is corroboration that is wrong often enough to trust least.
- Missing data scores 0.5, not 0. A track with no number is not evidence against a match; scoring it zero would push every untagged file below threshold and make the feature useless on exactly the libraries that need it.
- Assign greedily and globally, consuming each remote track once. Per-track best-match lets a generic title — "Intro", "Interlude" — win against several local tracks at once and capture one that belonged to another. One pass over all candidates, each consumed as it is taken.
- Two thresholds, not one: confident above 0.85, doubtful above 0.55, no match below. The middle band is the point — it is what the review screen exists to resolve.
A review screen, per track, with the file's current values facing the fetched ones, expandable field by field. Three grains of acceptance: one value, one track, or one field across the whole selection — because "take all the years but none of the titles" is the common case.
Nothing is written directly. Everything lands in pending edits and is applied deliberately.
Our side
Port the matching onto waveflow_core::metadata::name_match rather than writing a third normaliser. Ours handles NFD combining marks properly, which a latin-1 transliteration table does not — and accented titles are not an edge case in a music library.
The Deezer client, its error handling and its rate limiting already exist in waveflow_core.
Traps
- Deezer does not carry everything. No composer, no fine-grained genre, and disc numbers on box sets are unreliable. The review screen should not offer to overwrite a field the source cannot fill.
- Deezer returns errors as HTTP 200 with an
error object in the body — a quota exhaustion deserialises as an empty result and looks exactly like an album that does not exist. Already fixed once; keep it fixed here.
- Multi-artist credits are
"; " joined and rebuilt from track_artist (invariant). Writing a fetched artist string back has to go through that shape, not around it.
- New strings go to all 17 locales.
Deezer enrichment fills in artwork and artist data, but there is no way to say "this album's tags are wrong, fetch them and let me approve the result".
Depends on #598 (safe file writing). This writes across a whole album at once; doing that before writes are robust turns one bad moment into a folder in an unknown state.
Shape
Match the album, then match the tracks inside it. Album matching is the easy half. Track matching is where these features fail, and the approach worth copying is specific:
A review screen, per track, with the file's current values facing the fetched ones, expandable field by field. Three grains of acceptance: one value, one track, or one field across the whole selection — because "take all the years but none of the titles" is the common case.
Nothing is written directly. Everything lands in pending edits and is applied deliberately.
Our side
Port the matching onto
waveflow_core::metadata::name_matchrather than writing a third normaliser. Ours handles NFD combining marks properly, which a latin-1 transliteration table does not — and accented titles are not an edge case in a music library.The Deezer client, its error handling and its rate limiting already exist in
waveflow_core.Traps
errorobject in the body — a quota exhaustion deserialises as an empty result and looks exactly like an album that does not exist. Already fixed once; keep it fixed here."; "joined and rebuilt fromtrack_artist(invariant). Writing a fetched artist string back has to go through that shape, not around it.