fix: always write English genre for all download paths
1. JP genre mapping table in metadata.js — when only JP iTunes has the song (niche Japanese artists), map localized JP genre names (アニメ, ポップ, etc.) to English equivalents so files never get Japanese characters in genre tag. 2. Spotify playlist import (alreadyEnriched path) — was skipping iTunes lookup entirely, leaving genre empty. Now does a lightweight genre-only lookup. 3. Single track download with Spotify album — was skipping iTunes when album already known from Spotify. Now also fetches genre from iTunes when missing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ae439bfd2
commit
78024181f0
2 files changed
+27
-5
No files matched your search
+17
-3
@@ -88,6 +88,19 @@ function normalizeTitle(s) {
|
|||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iTunes JP store returns localized genre names — map them to English equivalents
|
||||||
|
const JP_GENRE_MAP = {
|
||||||
|
'アニメ': 'Anime', 'イージーリスニング': 'Easy Listening', 'ポップ': 'Pop',
|
||||||
|
'J-Pop': 'J-Pop', 'ロック': 'Rock', 'クラシック': 'Classical', 'ジャズ': 'Jazz',
|
||||||
|
'エレクトロニック': 'Electronic', 'R&B/ソウル': 'R&B/Soul', 'ヒップホップ/ラップ': 'Hip-Hop/Rap',
|
||||||
|
'オルタナティブ': 'Alternative', 'サウンドトラック': 'Soundtrack', 'ボーカル': 'Vocal',
|
||||||
|
'インストゥルメンタル': 'Instrumental', 'ワールド': 'World', 'フォーク': 'Folk',
|
||||||
|
'カントリー': 'Country', 'レゲエ': 'Reggae', 'ソウル': 'Soul', 'ブルース': 'Blues',
|
||||||
|
'メタル': 'Metal', 'パンク': 'Punk', 'ダンス': 'Dance', 'コメディ': 'Comedy',
|
||||||
|
'スピーチ': 'Speech', 'キッズ/ファミリー': 'Kids/Family',
|
||||||
|
};
|
||||||
|
function toEnglishGenre(g) { return JP_GENRE_MAP[g] || g; }
|
||||||
|
|
||||||
// iTunes track lookup — fills in album, year, trackNum, totalTracks, genre when missing
|
// iTunes track lookup — fills in album, year, trackNum, totalTracks, genre when missing
|
||||||
async function lookupItunesTrackMeta(artist, title) {
|
async function lookupItunesTrackMeta(artist, title) {
|
||||||
try {
|
try {
|
||||||
@@ -118,8 +131,9 @@ async function lookupItunesTrackMeta(artist, title) {
|
|||||||
let usMatch = strictMatch(usResults) || titleOnlyMatch(usResults);
|
let usMatch = strictMatch(usResults) || titleOnlyMatch(usResults);
|
||||||
if (!match) match = usMatch;
|
if (!match) match = usMatch;
|
||||||
if (!match) return {};
|
if (!match) return {};
|
||||||
// JP genre names are localized (e.g. イージーリスニング); always prefer US genre
|
// JP genre names are localized (e.g. イージーリスニング); prefer US genre, map JP→EN as fallback
|
||||||
const genre = (usMatch || match).primaryGenreName;
|
const rawGenre = (usMatch || match).primaryGenreName;
|
||||||
|
const genre = rawGenre ? toEnglishGenre(rawGenre) : undefined;
|
||||||
return {
|
return {
|
||||||
album: match.collectionName || '',
|
album: match.collectionName || '',
|
||||||
year: match.releaseDate ? new Date(match.releaseDate).getFullYear() : undefined,
|
year: match.releaseDate ? new Date(match.releaseDate).getFullYear() : undefined,
|
||||||
@@ -127,7 +141,7 @@ async function lookupItunesTrackMeta(artist, title) {
|
|||||||
totalTracks: match.trackCount || undefined,
|
totalTracks: match.trackCount || undefined,
|
||||||
discNum: match.discNumber || undefined,
|
discNum: match.discNumber || undefined,
|
||||||
discTotal: match.discCount || undefined,
|
discTotal: match.discCount || undefined,
|
||||||
genre: genre || undefined,
|
genre: genre,
|
||||||
artworkUrl: (match.artworkUrl100 || '').replace('100x100bb', '1000x1000bb'),
|
artworkUrl: (match.artworkUrl100 || '').replace('100x100bb', '1000x1000bb'),
|
||||||
durationMs: match.trackTimeMillis || undefined,
|
durationMs: match.trackTimeMillis || undefined,
|
||||||
itunesArtistName: match.artistName || undefined,
|
itunesArtistName: match.artistName || undefined,
|
||||||
|
|||||||
@@ -416,14 +416,19 @@ app.post('/api/download', async (req, res) => {
|
|||||||
artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit,
|
artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit,
|
||||||
durationMs: sp.durationMs || undefined,
|
durationMs: sp.durationMs || undefined,
|
||||||
};
|
};
|
||||||
if (!meta.album) {
|
|
||||||
const searchArtist = artist.split(',')[0].trim();
|
const searchArtist = artist.split(',')[0].trim();
|
||||||
|
if (!meta.album) {
|
||||||
log(jobId, '📋 Suche Album-Metadaten von iTunes…');
|
log(jobId, '📋 Suche Album-Metadaten von iTunes…');
|
||||||
const extra = await lookupItunesTrackMeta(searchArtist, title);
|
const extra = await lookupItunesTrackMeta(searchArtist, title);
|
||||||
if (extra.album) log(jobId, `📋 Album: "${extra.album}" (${extra.year || '?'})`);
|
if (extra.album) log(jobId, `📋 Album: "${extra.album}" (${extra.year || '?'})`);
|
||||||
meta = { ...meta, ...extra, artworkUrl: spArt || extra.artworkUrl || '', genre: meta.genre || extra.genre || undefined };
|
meta = { ...meta, ...extra, artworkUrl: spArt || extra.artworkUrl || '', genre: meta.genre || extra.genre || undefined };
|
||||||
} else {
|
} else {
|
||||||
log(jobId, `📋 Album aus Spotify: "${meta.album}"`);
|
log(jobId, `📋 Album aus Spotify: "${meta.album}"`);
|
||||||
|
// Spotify has no per-track genre — still look up genre from iTunes
|
||||||
|
if (!meta.genre) {
|
||||||
|
const genreMeta = await lookupItunesTrackMeta(searchArtist, title).catch(() => ({}));
|
||||||
|
if (genreMeta.genre) { meta.genre = genreMeta.genre; log(jobId, `📋 Genre: "${genreMeta.genre}"`); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const finalArtist = meta.itunesArtistName || artist;
|
const finalArtist = meta.itunesArtistName || artist;
|
||||||
const result = await downloadTrack({ jobId, artist: finalArtist, rawArtist: artist, title, ...meta, sourceUrl: sourceUrl || sp.sourceUrl || undefined });
|
const result = await downloadTrack({ jobId, artist: finalArtist, rawArtist: artist, title, ...meta, sourceUrl: sourceUrl || sp.sourceUrl || undefined });
|
||||||
@@ -492,11 +497,14 @@ app.post('/api/download', async (req, res) => {
|
|||||||
// trackNumber + durationMs all present), skip the re-lookup to avoid a different
|
// trackNumber + durationMs all present), skip the re-lookup to avoid a different
|
||||||
// iTunes result overriding the already-correct enriched metadata.
|
// iTunes result overriding the already-correct enriched metadata.
|
||||||
const alreadyEnriched = !!(track.collectionName && track.trackNumber && track.durationMs);
|
const alreadyEnriched = !!(track.collectionName && track.trackNumber && track.durationMs);
|
||||||
|
const searchArtist = trackArtist.split(',')[0].trim();
|
||||||
let itunes = {};
|
let itunes = {};
|
||||||
if (alreadyEnriched) {
|
if (alreadyEnriched) {
|
||||||
log(jobId, `📋 Frontend-Metadaten: "${trackArtist} – ${trackTitle}" (${track.collectionName})`);
|
log(jobId, `📋 Frontend-Metadaten: "${trackArtist} – ${trackTitle}" (${track.collectionName})`);
|
||||||
|
// Still look up genre — frontend enrichment doesn't include it and Spotify has no genre per track
|
||||||
|
const genreMeta = await lookupItunesTrackMeta(searchArtist, trackTitle).catch(() => ({}));
|
||||||
|
itunes = { genre: genreMeta.genre };
|
||||||
} else {
|
} else {
|
||||||
const searchArtist = trackArtist.split(',')[0].trim();
|
|
||||||
log(jobId, `📋 iTunes: "${trackArtist} – ${trackTitle}"`);
|
log(jobId, `📋 iTunes: "${trackArtist} – ${trackTitle}"`);
|
||||||
itunes = await lookupItunesTrackMeta(searchArtist, trackTitle);
|
itunes = await lookupItunesTrackMeta(searchArtist, trackTitle);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user