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:
1 parent
16f76a4236
commit
6d084a7b7a
6 files changed
+200
-9
No files matched your search
@@ -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), {});
|
||||
});
|
||||
Reference in new issue
Block a user