From b788fedec32f9404fcd628006489b7e604800f1d Mon Sep 17 00:00:00 2001 From: Honey Date: Wed, 12 Aug 2026 17:06:14 +0200 Subject: [PATCH] fix(download): dedup missed re-downloads when the title contains ' - ' (KH triple/quadruple) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit findExistingTrack split the filename at the first ' - ' assuming 'Artist - Title', so a title with internal dashes ('Kingdom Hearts 2 - Dearly Beloved - Main Theme') or a garbage/absent artist prefix ('01-01 - …') was mis-parsed → the duplicate went undetected and got re-downloaded (3-4x). Now match the query title against BOTH the full stem AND the part after the first ' - ', so titles with dashes and prefix-only filenames are recognized. Normal 'Artist - Title' files still match; no false positives; remix-vs-base distinction preserved. Unit-tested on the exact KH case. Co-Authored-By: Claude Opus 4.8 --- backend/lib/metadata.js | 15 ++++++++--- backend/test/metadata.test.js | 50 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 backend/test/metadata.test.js diff --git a/backend/lib/metadata.js b/backend/lib/metadata.js index 9b36f7a..82c504a 100644 --- a/backend/lib/metadata.js +++ b/backend/lib/metadata.js @@ -90,11 +90,18 @@ function findExistingTrack(musicRoot, firstArtist, cleanTitle) { if (!AUDIO_EXTS.includes(ext)) continue; // Strip extension + leading track prefix ("01 - ", "01-15 – " etc.) const stem = f.slice(0, -(ext.length + 1)).replace(/^\d[\d-]*\s*[-–—]\s*/, ''); - // Stem may be "Artist - Title" or just "Title" + // Candidate titles from the filename. The old code only used the part AFTER the first + // " - " (assuming "Artist - Title"), which mis-parses titles that themselves contain + // " - " (e.g. "Kingdom Hearts 2 - Dearly Beloved - Main Theme") or filenames with a + // garbage/absent artist prefix ("01-01 - …") → the duplicate went undetected and got + // re-downloaded. Now match if ANY reading equals the query title: + // 1) the FULL stem (title with internal dashes, or no clean artist prefix) + // 2) the part after the first " - " (normal "Artist - Title") const di = stem.indexOf(' - '); - const rawTitle = di >= 0 ? stem.slice(di + 3) : stem; - const normStemTitle = norm(rawTitle.replace(_RELEASE_SUFFIX_RE, '')); - if (titleVariants.has(normStemTitle)) return path.join(albumPath, f); + const candidates = di >= 0 ? [stem, stem.slice(di + 3)] : [stem]; + for (const cand of candidates) { + if (titleVariants.has(norm(cand.replace(_RELEASE_SUFFIX_RE, '')))) return path.join(albumPath, f); + } } } catch {} } diff --git a/backend/test/metadata.test.js b/backend/test/metadata.test.js new file mode 100644 index 0000000..dd37a78 --- /dev/null +++ b/backend/test/metadata.test.js @@ -0,0 +1,50 @@ +const { test, before, after } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { findExistingTrack } = require('../lib/metadata'); + +let ROOT; +const touch = (artist, album, file) => { + const dir = path.join(ROOT, artist, album); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, file), ''); +}; + +before(() => { + ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-dedup-')); + // KH single: normal "01 - " prefix, title contains multiple " - " + touch('Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme - Single', + '01 - Kingdom Hearts 2 - Dearly Beloved - Main Theme.mp3'); + // KH album variant: garbage "01-01" artist prefix, no track number + touch('Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme', + '01-01 - Kingdom Hearts 2 - Dearly Beloved - Main Theme.mp3'); + // Normal "Artist - Title" file + touch('Some Artist', 'Therapy - Single', '01 - Some Artist - Therapy.mp3'); +}); +after(() => { try { fs.rmSync(ROOT, { recursive: true, force: true }); } catch {} }); + +test('detects a re-download when the title itself contains " - " (KH bug)', () => { + assert.ok(findExistingTrack(ROOT, 'Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme')); +}); + +test('detects it even with a garbage "01-01 - " artist prefix', () => { + // Both KH files should resolve; at least one must be found → dedup skips the re-download. + const hit = findExistingTrack(ROOT, 'Geek Music', 'Kingdom Hearts 2 - Dearly Beloved - Main Theme'); + assert.ok(hit && hit.includes('Kingdom Hearts 2 - Dearly Beloved - Main Theme')); +}); + +test('normal "Artist - Title" filename still matches on title alone', () => { + assert.ok(findExistingTrack(ROOT, 'Some Artist', 'Therapy')); +}); + +test('no false positive: a different title is not matched', () => { + assert.equal(findExistingTrack(ROOT, 'Geek Music', 'Some Unrelated Song'), null); + assert.equal(findExistingTrack(ROOT, 'Some Artist', 'Kingdom Hearts 2'), null); +}); + +test('remix stays distinct from the base track (no regression from the version-marker fix)', () => { + assert.equal(findExistingTrack(ROOT, 'Some Artist', 'Therapy (Remix)'), null); + assert.ok(findExistingTrack(ROOT, 'Some Artist', 'Therapy')); // base still found +});