fix: auto-lookup album metadata from iTunes when missing on track download

Songs from new releases (Apple RSS feed) have no collectionName, causing
the album tag to be omitted from embedded metadata -> Navidrome shows
[Unknown Album]. Backend now calls iTunes search to fill in album, year,
trackNum, totalTracks, discNum, genre before downloading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-26 18:57:25 +02:00
1 parent f5e0af6e90
commit 5987e1f303
1 file changed
+37 -1
+37 -1
View File
@@ -130,6 +130,35 @@ async function fetchLyrics(artist, title, album) {
} catch { return null; } } catch { return null; }
} }
// iTunes track lookup — fills in album, year, trackNum, totalTracks, genre when missing
async function lookupItunesTrackMeta(artist, title) {
try {
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(artist + ' ' + title)}&media=music&entity=musicTrack&limit=5&country=us&explicit=Yes`;
const results = await new Promise((resolve, reject) => {
https.get(url, res => {
let d = ''; res.on('data', c => d += c);
res.on('end', () => { try { resolve(JSON.parse(d).results || []); } catch { reject(new Error('parse')); } });
}).on('error', reject);
});
const norm = s => s?.toLowerCase().trim() || '';
const match = results.find(t =>
norm(t.trackName) === norm(title) &&
norm(t.artistName).split(' ')[0] === norm(artist).split(' ')[0]
) || results[0];
if (!match) return {};
return {
album: match.collectionName || '',
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: match.primaryGenreName || undefined,
artworkUrl: (match.artworkUrl100 || '').replace('100x100bb', '1000x1000bb'),
};
} catch { return {}; }
}
// Download a single track: yt-dlp audio + ffmpeg clean metadata + iTunes cover embedded // Download a single track: yt-dlp audio + ffmpeg clean metadata + iTunes cover embedded
async function downloadTrack({ jobId, artist, albumArtist, title, album, year, trackNum, totalTracks, discNum, discTotal, compilation, genre, artworkUrl, explicit, outputDir }) { async function downloadTrack({ jobId, artist, albumArtist, title, album, year, trackNum, totalTracks, discNum, discTotal, compilation, genre, artworkUrl, explicit, outputDir }) {
const mainArtist = albumArtist || artist; const mainArtist = albumArtist || artist;
@@ -380,7 +409,14 @@ app.post('/api/download', async (req, res) => {
updateJob(jobId, { status: 'running', progress: 5, message: 'Gestartet…' }); updateJob(jobId, { status: 'running', progress: 5, message: 'Gestartet…' });
try { try {
if (type === 'track') { if (type === 'track') {
const result = await downloadTrack({ jobId, artist, title, album, artworkUrl, explicit: !!explicit }); let meta = { album, year: undefined, trackNum: undefined, totalTracks: undefined, discNum: undefined, discTotal: undefined, genre: undefined };
if (!album) {
log(jobId, '📋 Suche Album-Metadaten von iTunes…');
const extra = await lookupItunesTrackMeta(artist, title);
if (extra.album) log(jobId, `📋 Album: "${extra.album}" (${extra.year || '?'})`);
meta = { ...extra, artworkUrl: artworkUrl || extra.artworkUrl || '' };
}
const result = await downloadTrack({ jobId, artist, title, artworkUrl: artworkUrl || meta.artworkUrl || '', explicit: !!explicit, ...meta });
if (result?.skipped) { if (result?.skipped) {
updateJob(jobId, { status: 'done', progress: 100, message: 'Bereits vorhanden – übersprungen', skipped: true }); updateJob(jobId, { status: 'done', progress: 100, message: 'Bereits vorhanden – übersprungen', skipped: true });
} else { } else {