From 72b612f5ffe10b6844eb03302b6a5828d35dc9cc Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 26 Jul 2026 22:54:15 +0200 Subject: [PATCH] fix(backend): don't tag Western artists with the JP store's katakana album-artist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iTunes lookup is JP-first (for correct Japanese titles/artwork), but the Japanese store returns katakana-localized names for Western artists — so DJ Snake's single got album_artist / folder "DJスネイク • アルーナジョージ" while the title/artist stayed English. - lookupItunesTrackMeta: take the NAMES (album, album-artist, track, artist) from the US store when the search isn't Japanese; keep JP names for actual Japanese songs. - album & artist download paths: if iTunes' collectionArtistName is Japanese but the request artist isn't, keep the request's English name for the tag and folder. Already-downloaded files aren't rewritten — re-download or fix via the editor. Co-Authored-By: Claude Opus 4.8 --- backend/lib/metadata.js | 15 ++++++++++----- backend/novasonic-server.js | 11 +++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/backend/lib/metadata.js b/backend/lib/metadata.js index 15cf927..72673dc 100644 --- a/backend/lib/metadata.js +++ b/backend/lib/metadata.js @@ -179,22 +179,27 @@ async function lookupItunesTrackMeta(artist, title) { let usMatch = strictMatch(usResults) || (!isJpSearch && titleOnlyMatch(usResults)); if (!match) match = usMatch; if (!match) return {}; + // For non-Japanese songs, take the NAMES from the US store: the JP store returns katakana- + // localized artist / album-artist names for Western artists (e.g. "DJスネイク" for DJ Snake), + // which would otherwise become the album_artist tag and the on-disk folder. Japanese songs keep + // the JP names (correct kanji). Numeric fields (year, track#, duration) are store-agnostic. + const naming = (!isJpSearch && usMatch) ? usMatch : match; // JP genre names are localized (e.g. イージーリスニング); prefer US genre, map JP→EN as fallback const rawGenre = (usMatch || match).primaryGenreName; const genre = rawGenre ? toEnglishGenre(rawGenre) : undefined; return { - album: match.collectionName || '', - albumArtist: match.collectionArtistName || undefined, - trackName: match.trackName || undefined, + album: naming.collectionName || '', + albumArtist: naming.collectionArtistName || undefined, + trackName: naming.trackName || undefined, year: match.releaseDate ? new Date(match.releaseDate).getFullYear() : undefined, trackNum: match.trackNumber || undefined, totalTracks: match.trackCount || undefined, discNum: match.discNumber || undefined, discTotal: match.discCount || undefined, genre: genre, - artworkUrl: (match.artworkUrl100 || '').replace('100x100bb', '1000x1000bb'), + artworkUrl: (naming.artworkUrl100 || match.artworkUrl100 || '').replace('100x100bb', '1000x1000bb'), durationMs: match.trackTimeMillis || undefined, - itunesArtistName: match.artistName || undefined, + itunesArtistName: naming.artistName || undefined, }; } catch { return {}; } } diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index c3152a5..925ea2d 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -627,7 +627,11 @@ app.post('/api/download', async (req, res) => { // over the request's `artist`, which can be a single track's performer — e.g. the lead single // "TAKEDOWN" is by TWICE, so `artist` arrives as "TWICE" and would tag the whole soundtrack's // album_artist as TWICE. Keeps album_artist consistent so Navidrome doesn't split the album. - const albumArtistName = tracks.map(t => t.collectionArtistName).find(Boolean) || artist; + let albumArtistName = tracks.map(t => t.collectionArtistName).find(Boolean) || artist; + // The JP store localizes Western artists to katakana ("DJスネイク"). If iTunes' album artist is + // Japanese but the request artist isn't, keep the request's English name (tag + folder). + const JP_RE = /[぀-ヿ㐀-䶿一-鿿]/; + if (albumArtistName && artist && JP_RE.test(albumArtistName) && !JP_RE.test(artist)) albumArtistName = artist; if (albumArtistName !== artist) log(jobId, `👥 Album-Künstler: "${albumArtistName}" (iTunes)`); const { done: dlDone, skipped: dlSkipped } = await downloadAlbumTracks({ jobId, artist: albumArtistName, albumTitle: album || title, tracks, artworkUrl }); const allSkipped = dlSkipped === tracks.length; @@ -655,7 +659,10 @@ app.post('/api/download', async (req, res) => { if (tracks.length) { // Same as the album path: album_artist from iTunes' album-level artist, so a collaboration // or soundtrack in the artist's discography isn't tagged with the searched artist. - const albArtist = tracks.map(t => t.collectionArtistName).find(Boolean) || alb.artistName || artist; + let albArtist = tracks.map(t => t.collectionArtistName).find(Boolean) || alb.artistName || artist; + // Don't let the JP store's katakana album-artist override a non-Japanese searched artist. + const JP_RE = /[぀-ヿ㐀-䶿一-鿿]/; + if (albArtist && artist && JP_RE.test(albArtist) && !JP_RE.test(artist)) albArtist = artist; await downloadAlbumTracks({ jobId, artist: albArtist, albumTitle: alb.collectionName, tracks, artworkUrl: alb.artworkUrl100 }); } done++;