diff --git a/CHANGELOG.md b/CHANGELOG.md index e696b35..119d826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -237,8 +237,16 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d - Bleibt am Ende weder ein Titel- noch ein Künstlermerkmal übrig, wird **gar nicht gesucht**: ohne Merkmal landet sonst der erste Treffer passender Länge auf der Platte. Der Direktlink im Importer ist dafür der vorgesehene Weg. - - Fünf neue Prüfungen in `backend/test/ytdlp.test.js` (59 grün), darunter „anderes Stück - desselben Albums darf nicht passen". + - **Die Umschrift ist nur *eine* Schreibweise.** Der Tokenizer liefert „Ginpatsu No Shounen", + Uploader schreiben „Ginpatsu no Shonen", setzen Bindestriche oder lassen den + Katakana-Längungsstrich weg (der bei „テーゼ" sogar unübersetzt stehenbleibt: „TeーZe"). Das + Muster lässt diese Abweichungen zu — langes o/u optional, Längungsstrich weg, Trennzeichen + frei. Sonst wäre die Alternative so streng, dass sie praktisch nie greift. + - Dieselbe Hilfe für **Kanji-Künstlernamen**: Kanäle und Videotitel schreiben sie meist + lateinisch, also zählt die Umschrift jetzt auch als Künstlermerkmal. Vorher blieb dort nur das + `artist`-Feld in Originalschrift. + - Sieben neue Prüfungen in `backend/test/ytdlp.test.js` (61 grün), darunter „anderes Stück + desselben Albums darf nicht passen" und die Schreibvarianten der Umschrift. - **Das Mischen-Symbol zeigte nie an, dass es an ist (Android)** — und der Grund lag nicht in der Anzeige. `toggleShuffle` baute den Rest der Warteschlange als Schleife aus `removeMediaItem` + `addMediaItem` um; dazwischen hatte der Controller vorübergehend weniger Titel als unsere Liste. diff --git a/backend/lib/ytdlp.js b/backend/lib/ytdlp.js index cb80e61..477a0c6 100644 --- a/backend/lib/ytdlp.js +++ b/backend/lib/ytdlp.js @@ -29,7 +29,17 @@ function buildTitleKeywords(title, romaji = '') { const alternativen = [escRe(title.trim())]; const r = (romaji || '').trim(); if (r && r.toLowerCase() !== title.trim().toLowerCase() && !JP_RE.test(r)) { - alternativen.push(escRe(r).replace(/\s+/g, '[ -]?')); + // Die Umschrift des Tokenizers ist EINE Schreibweise von mehreren. Uploader schreiben + // "Shonen" statt "Shounen", setzen Bindestriche oder lassen den Katakana-Laengungsstrich + // weg. Das Muster laesst diese Abweichungen zu — sonst waere die Alternative so streng, + // dass sie praktisch nie greift. + alternativen.push( + escRe(r) + .replace(/ー/g, '') // kommt in lateinischen Titeln nie vor + .replace(/\s+/g, '[ -]?') + .replace(/ou/gi, '(?:ou|o|\u014d|oh)') + .replace(/uu/gi, '(?:uu|u|\u016b)') + ); } return [`(?:${alternativen.join('|')})`]; } @@ -212,8 +222,19 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb // Ausnahme hatte ein japanischer Kuenstler ueberhaupt keine Identitaetspruefung. const artistMetaPart = (artistFirstWord.length >= 3 || (JP_RE.test(artistFirstWord) && artistFirstWord.length >= 2)) ? ` & artist ~= (?i)${escRe(artistFirstWord)}` : ''; + // Kanji-Kuenstler: Kanaele und Videotitel schreiben den Namen meist lateinisch. Ohne diese + // Alternative bliebe fuer sie nur das artist-Feld in Originalschrift — und steht dort die + // lateinische Schreibweise, faellt der richtige Upload durch. + const artistRomajiWord = (JP_RE.test(artistFirstWord) && isTokenizerReady()) + ? ((romajiForStorage(artistFirstWord) || '').split(/\s+/) + .filter(w => w.length >= 3 && !JP_RE.test(w)) + .sort((a, b) => b.length - a.length)[0] || '') + : ''; + const artistRomajiParts = artistRomajiWord + ? [` & channel ~= (?i)${escRe(artistRomajiWord)}`, ` & artist ~= (?i)${escRe(artistRomajiWord)}`] + : []; // Alle Wege, den Kuenstler zu erkennen. yt-dlp verodert mehrere --match-filter. - const identitaet = [artistChannelPart, artistMetaPart].filter(Boolean); + const identitaet = [artistChannelPart, artistMetaPart, ...artistRomajiParts].filter(Boolean); // Use a space (not " - ") between artist and title: this matches how users type // in the YTM search box and ranks the exact official track first. A dash skews diff --git a/backend/test/ytdlp.test.js b/backend/test/ytdlp.test.js index 6e230ee..d6fe1f2 100644 --- a/backend/test/ytdlp.test.js +++ b/backend/test/ytdlp.test.js @@ -149,3 +149,19 @@ test('lateinische Titel bleiben unveraendert', () => { assert.deepEqual(buildTitleKeywords('Bohemian Rhapsody'), ['Bohemian', 'Rhapsody']); assert.deepEqual(buildTitleKeywords('Dearly Beloved'), ['Beloved', 'Dearly']); }); + +test('Umschrift toleriert die ueblichen Schreibvarianten', () => { + const re = new RegExp(buildTitleKeywords('銀髪の少年', 'Ginpatsu No Shounen')[0], 'i'); + assert.ok(re.test('Ginpatsu no Shounen'), 'wie der Tokenizer schreibt'); + assert.ok(re.test('Ginpatsu no Shonen'), 'ohne langes o — die haeufigste Variante'); + assert.ok(re.test('Ginpatsu-no-Shonen'), 'mit Bindestrichen'); + assert.ok(re.test('GINPATSU NO SHŌNEN'), 'mit Makron'); + assert.ok(!re.test('Ginpatsu no Otona'), 'anderer Titel faellt weiterhin durch'); +}); + +test('Katakana-Laengungsstrich faellt aus der Umschrift', () => { + // romajiForStorage laesst "ー" stehen ("TeーZe"); in einem lateinischen Titel steht es nie. + const re = new RegExp(buildTitleKeywords('残酷な天使のテーゼ', 'Zankoku Na Tenshi No TeーZe')[0], 'i'); + assert.ok(re.test('Zankoku na Tenshi no Teze'), 'ohne Strich geschrieben'); + assert.ok(re.test('残酷な天使のテーゼ'), 'Originaltitel weiterhin'); +});