fix: normalize diacritics in playlist artist matching

normD strips accents/special chars before comparing artist first-word
so "Kløn" (iTunes) matches "Klon" (embedded tag) → "klon" === "klon"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-02 15:33:01 +02:00
1 parent 971cadd8fa
commit fed3df7e36
1 file changed
+9 -7
+9 -7
View File
@@ -371,7 +371,9 @@ async function _createNdPlaylist(tracks, name) {
toast('🎵 Erstelle Navidrome-Playlist…'); toast('🎵 Erstelle Navidrome-Playlist…');
const songIds = []; const songIds = [];
const missing = []; const missing = [];
// Normalize: lowercase, trim, strip diacritics (ø→o, ä→a, etc.) for fuzzy matching
const norm = s => s?.toLowerCase().trim() || ''; const norm = s => s?.toLowerCase().trim() || '';
const normD = s => norm(s).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^\w\s]/g, '');
for (const t of tracks) { for (const t of tracks) {
if (!t.trackName) continue; if (!t.trackName) continue;
const missingLabel = t.artistName ? `${t.artistName} – ${t.trackName}` : t.trackName; const missingLabel = t.artistName ? `${t.artistName} – ${t.trackName}` : t.trackName;
@@ -381,7 +383,7 @@ async function _createNdPlaylist(tracks, name) {
const artistQuery = firstArtist ? `${firstArtist} ${t.trackName}` : t.trackName; const artistQuery = firstArtist ? `${firstArtist} ${t.trackName}` : t.trackName;
const r = await api('search3', `&query=${encodeURIComponent(artistQuery)}&songCount=20&albumCount=0&artistCount=0`); const r = await api('search3', `&query=${encodeURIComponent(artistQuery)}&songCount=20&albumCount=0&artistCount=0`);
let songs = r.searchResult3?.song || []; let songs = r.searchResult3?.song || [];
// If the artist+title query returned no title match (e.g. special chars in artist like "Kløn"), // If artist+title query returned no title match (e.g. special chars in artist like "Kløn"),
// retry with title-only so the track can still be found // retry with title-only so the track can still be found
if (!songs.some(s => norm(s.title) === norm(t.trackName))) { if (!songs.some(s => norm(s.title) === norm(t.trackName))) {
try { try {
@@ -389,12 +391,12 @@ async function _createNdPlaylist(tracks, name) {
songs = r2.searchResult3?.song || []; songs = r2.searchResult3?.song || [];
} catch { /* keep original songs */ } } catch { /* keep original songs */ }
} }
// Strip trailing punctuation from first word so "Kløn," matches "Kløn" // firstWordD: first word of artist, diacritics stripped — "Kløn" and "Klon" both → "klon"
const firstWord = s => norm(s).split(/[\s,/•]+/)[0].replace(/\W+$/, ''); const firstWordD = s => normD(s).split(/\s+/)[0];
const match = songs.find(s => const titleMatch = s => norm(s.title) === norm(t.trackName);
norm(s.title) === norm(t.trackName) && const artistMatch = s => firstWordD(s.artist) === firstWordD(firstArtist || t.artistName);
firstWord(s.artist) === firstWord(firstArtist || t.artistName) const match = songs.find(s => titleMatch(s) && artistMatch(s))
) || songs.find(s => norm(s.title) === norm(t.trackName)); || songs.find(s => titleMatch(s));
if (match) songIds.push(match.id); if (match) songIds.push(match.id);
else missing.push(missingLabel); else missing.push(missingLabel);
} catch { missing.push(missingLabel); } } catch { missing.push(missingLabel); }