fix(backend): Dublettenpruefung erkennt Akzente und Soundtrack-Ordner

Drei Luecken derselben Familie, die dazu fuehrten, dass dieselben Songs ein
zweites Mal geladen wurden:

- Kuenstlernamen wurden zeichengenau verglichen: "Cécile Corbel" und "Cecile
  Corbel" galten als zwei Personen. Welche Schreibweise ankommt, haengt an der
  Quelle (Deezer vs. Spotify). Der Vergleich normalisiert jetzt Akzente weg —
  nur der Vergleich, Tags und Dateinamen behalten das Original.
- Typografische Apostrophe: "Arrietty’s Song" (U+2019) und "Arrietty's Song"
  fielen auseinander. Fiel beim Testen auf, betrifft viele Soundtracks.
- findExistingTrack durchsucht nur den Ordner des uebergebenen Kuenstlers. Bei
  Soundtracks weicht der Album- vom Titel-Kuenstler ab, der Song landet je nach
  Ladeweg unter dem einen oder anderen — geprueft wurde jeweils der falsche.
  Weicht der Titel-Kuenstler ab, wird nun auch dessen Ordner geprueft (~0,6 ms
  bei 28.801 Dateien, gegenueber 3-9 s pro Download).

Sieben neue Tests zielen gezielt auf Fehlalarme: aehnliche Namen (Cecil Taylor
vs. Cécile Corbel), gleicher Titel bei verschiedenen Kuenstlern, Remix gegen
Basistitel, fremder Titel im Soundtrack-Ordner — alle bleiben getrennt.
Gesamte Suite: 42 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-05 08:40:02 +02:00
1 parent f62b248bb6
commit 3c99778567
4 files changed
+96 -3

No files matched your search

+9 -2
View File
@@ -61,14 +61,21 @@ const _VERSION_KW = /\b(?:remix|live|acoustic|instrumental|version|vip|flip|boot
// release suffixes and track prefixes. Handles DJ OKAWARI vs DJ Okawari, and
// "recognize - Single.mp3" vs "recognize.mp3" (drift between runs).
function findExistingTrack(musicRoot, firstArtist, cleanTitle) {
const norm = s => (s || '').toLowerCase()
const norm = s => (s || '')
// Akzente abtrennen und verwerfen: dieselbe Kuenstlerin kommt je nach Quelle als
// "Cécile Corbel" oder "Cecile Corbel" — ohne das galt sie als zwei Personen und derselbe
// Song wurde ein zweites Mal geladen. Betrifft nur den VERGLEICH, nicht Tags oder Dateinamen.
.normalize('NFD').replace(/\p{M}+/gu, '')
.toLowerCase()
// Strip bracketed noise (feat. X, Official Video, Prod. by …) but KEEP version markers
// (Remix/Live/Acoustic/…) so distinct recordings aren't collapsed onto the base track.
.replace(/[\[((]([^\]))]*)[\]))]/g, (m, inner) => _VERSION_KW.test(inner) ? m : '')
// Filesystem-sanitized chars (/ \ : * ? " < > |) become "_" on disk — map those AND "_"
// to a space so a title tag "Kwik Flip | YTPMV" matches the file "Kwik Flip _ YTPMV".
.replace(/[/\\:*?"<>|_]/g, ' ')
.replace(/[,.'!]/g, '')
// Auch die typografischen Apostrophe: Anbieter schreiben "Arrietty’s Song" (U+2019), eine
// spaetere Anfrage kommt mit geradem "'" — ohne das galten beide als verschiedene Titel.
.replace(/[,.!'\u2018\u2019\u02bc\u00b4`]/g, '')
.replace(/\s+/g, ' ')
.trim();
const normArtist = norm(firstArtist);
+11 -1
View File
@@ -144,7 +144,17 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb
const safeArtistForFile = sanitize((artist || '').replace(/;/g, ','));
const fileBase = `${trackPrefix}${safeArtistForFile} - ${safeTitle}`;
const existing = findExistingTrack(outputDir || CONFIG.musicPath, safeArtist, cleanTitle);
// Dublettenpruefung: findExistingTrack durchsucht NUR den Ordner des uebergebenen Kuenstlers.
// Bei Soundtracks und Compilations weicht der Album-Kuenstler vom Titel-Kuenstler ab, und je
// nachdem, ob der Song als Album oder einzeln geladen wird, landet er unter dem einen ODER dem
// anderen. Die Pruefung sah dann jeweils im falschen Ordner nach und lud ihn erneut. Deshalb wird
// bei abweichendem Titel-Kuenstler auch dessen Ordner geprueft — zweiter Durchlauf nur in genau
// diesem Fall, gemessen ~0,6 ms zusaetzlich bei knapp 29.000 Dateien.
const trackArtist = sanitize((artist || '').split(/[,;&]/)[0].trim());
const existing = findExistingTrack(outputDir || CONFIG.musicPath, safeArtist, cleanTitle)
|| (trackArtist && trackArtist !== safeArtist
? findExistingTrack(outputDir || CONFIG.musicPath, trackArtist, cleanTitle)
: null);
if (existing) {
log(jobId, `⏭ Bereits vorhanden: ${path.basename(existing)} — übersprungen`);
return { success: true, dir, skipped: true };