fix(download): stop wrong album/cover from same-titled tracks; add MusicBrainz fallback

Root cause: deezer pickTrackMatch fell back to a blind title-only match, grabbing a
same-titled song by a completely different artist (e.g. 'Memories' by Rab Noakes for
'☆NoAki☆') and applying its album/cover/year. The download's clean fallback existed
but was never reached because the lookup almost always returned *something*.

Stufe A — deezer.js: the title-only fallback now REQUIRES an artist correspondence
(equal, or one name contained in the other after normalization). No match → {} →
the existing clean fallback finally runs (album_artist = song artist, album =
'Singles', cover = source thumbnail, year/genre untouched). Test that encoded the old
blind behaviour replaced; NoAki + loose-match regression tests added.

Stufe B — musicbrainz.js: a rate-limited (~1/s, User-Agent) MusicBrainz recording
lookup with the SAME artist discipline + Cover Art Archive cover. trackmeta.js is the
single resolver (Deezer → MusicBrainz → clean fallback); the three single-track call
sites use it (album batch stays Deezer-only to avoid MB rate-limit slowdowns).

Unit-tested (17/17) + live-verified: NoAki→clean fallback, real track→Deezer, MB finds
real albums when Deezer misses. Backend-only; no frontend/Android change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-08-12 01:51:29 +02:00
1 parent 16f76a4236
commit 6d084a7b7a
6 files changed
+200 -9

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
const { test } = require('node:test');
const assert = require('node:assert/strict');
const mb = require('../lib/musicbrainz');
const rec = (title, artist, releases = []) => ({
id: 'rec-' + title, title, length: 200000,
'artist-credit': [{ name: artist, artist: { id: 'a', name: artist } }],
releases,
});
const albumRel = { id: 'rel-1', title: 'Real Album', date: '2021-05-01', 'release-group': { 'primary-type': 'Album' } };
const singleRel = { id: 'rel-2', title: 'Some Single', date: '2020-01-01', 'release-group': { 'primary-type': 'Single' } };
test('mbPickMatch: artist-verified match prefers a recording with a release', () => {
const recs = [rec('Memories', 'NoAki'), rec('Memories', 'NoAki', [albumRel])];
assert.equal(mb.mbPickMatch(recs, 'NoAki', 'Memories'), recs[1]);
});
test('mbPickMatch: completely different artist → null (no wrong album)', () => {
const recs = [rec('Memories', 'Rab Noakes', [albumRel]), rec('Memories', 'Maroon 5', [albumRel])];
assert.equal(mb.mbPickMatch(recs, '☆NoAki☆', 'Memories'), null);
});
test('mbPickMatch: loose artist match (one contained in the other)', () => {
const recs = [rec('Flower Dance', 'DJ Okawari', [albumRel])];
assert.equal(mb.mbPickMatch(recs, 'Okawari', 'Flower Dance'), recs[0]);
});
test('mapMbRecording: maps album/year/cover/duration and prefers the Album release', () => {
const m = mb.mapMbRecording(rec('Memories', 'NoAki', [singleRel, albumRel]));
assert.equal(m.album, 'Real Album');
assert.equal(m.albumArtist, 'NoAki');
assert.equal(m.year, 2021);
assert.equal(m.trackName, 'Memories');
assert.equal(m.durationMs, 200000);
assert.equal(m.artworkUrl, 'https://coverartarchive.org/release/rel-1/front-500');
assert.equal(m._source, 'musicbrainz');
});
test('mapMbRecording: no release → no album, no artwork (clean fallback territory)', () => {
const m = mb.mapMbRecording(rec('Orphan Song', 'NoAki', []));
assert.equal(m.album, undefined);
assert.equal(m.artworkUrl, '');
assert.equal(m.metaArtistName, 'NoAki');
});
test('mapMbRecording: null in → {} out', () => {
assert.deepEqual(mb.mapMbRecording(null), {});
});