fix: playlist matching for romaji-stored Japanese tracks + punctuation cleanup

- Clean Japanese punctuation (、。・) in romajiForStorage so it doesn't pass through
- Add POST /api/romaji-convert endpoint for batch title conversion
- Add romajiConvert() to api.js
- Add third fallback in _createNdPlaylist: convert Japanese title to romaji and
  search Navidrome with it (for songs already downloaded with romaji tags)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-25 01:20:37 +02:00
1 parent c7bebd5691
commit f1eabf7099
4 files changed
+48 -5

No files matched your search

+19 -4
View File
@@ -1,6 +1,6 @@
import { S } from './state.js';
import { esc, formatDur, toast } from './utils.js';
import { api, coverUrl, ndAuth, fetchRomajiIndex } from './api.js';
import { api, coverUrl, ndAuth, fetchRomajiIndex, romajiConvert } from './api.js';
import { THEMES, applyTheme, getCurrentThemeId } from './themes.js';
// Circular imports — safe (only used inside function bodies)
import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt } from './player.js';
@@ -565,7 +565,12 @@ async function _createNdPlaylist(tracks, name) {
// doesn't handle Japanese text, so we need the index as a fallback.
const hasJp = tracks.some(t => JP_RE.test(t.trackName) || JP_RE.test(t.artistName));
const romajiIdx = hasJp ? await fetchRomajiIndex().catch(() => null) : null;
for (const t of tracks) {
// Pre-convert all Japanese titles to romaji in one batch — needed for songs that were
// already downloaded with romaji tags (they're no longer in the romaji index).
const jpTitles = tracks.map(t => (hasJp && JP_RE.test(t.trackName)) ? t.trackName : '');
const romajiTitles = hasJp ? await romajiConvert(jpTitles).catch(() => jpTitles) : jpTitles;
const normR = s => (s || '').toLowerCase().replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim();
for (const [idx, t] of tracks.entries()) {
if (!t.trackName) continue;
const missingLabel = t.artistName ? t.artistName + ' – ' + t.trackName : t.trackName;
try {
@@ -586,12 +591,22 @@ async function _createNdPlaylist(tracks, name) {
const artistMatch = s => firstWord(s.artist) === firstWord(firstArtist || t.artistName);
let match = songs.find(s => titleMatch(s) && artistMatch(s))
|| songs.find(s => titleMatch(s));
// Fallback: search romaji index by original Japanese title
// (Navidrome search3 doesn't index Japanese text properly)
// Fallback 1: search romaji index by original Japanese title
// (for old songs that still have Japanese in Navidrome)
if (!match && romajiIdx && JP_RE.test(t.trackName)) {
const jpEntry = romajiIdx.find(e => norm(e.title) === norm(t.trackName));
if (jpEntry) match = { id: jpEntry.id };
}
// Fallback 2: search Navidrome with romaji-converted title
// (for songs downloaded after romaji changes — stored with romaji tags)
if (!match && JP_RE.test(t.trackName) && romajiTitles[idx]) {
const rTitle = romajiTitles[idx];
try {
const r3 = await api('search3', '&query=' + encodeURIComponent(rTitle) + '&songCount=20&albumCount=0&artistCount=0');
const s3 = r3.searchResult3?.song || [];
match = s3.find(s => normR(s.title) === normR(rTitle)) || null;
} catch { /* ignore */ }
}
if (match) songIds.push(match.id);
else missing.push(missingLabel);
} catch { missing.push(missingLabel); }