From d785a4c09aa40d2cd1d98a5b4f4fcf33adfba384 Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 23 May 2026 21:19:42 +0200 Subject: [PATCH] fix: SC metadata fields, albumartist & separator, importer clear, folder rename on album edit - SC tracks: use artworkUrl/album/genre fields (not iTunes artworkUrl100/collectionName/primaryGenreName) - Playlist handler: same SC field fallbacks for artwork, album, genre, year - ytdlp.js: convert & to , in album_artist tag so Navidrome albumartist.Split works - Importer: clear both previews on any URL input change, not just on clear - edit-metadata: rename album folder on disk when album name changes Co-Authored-By: Claude Sonnet 4.6 --- backend/lib/ytdlp.js | 2 +- backend/novasonic-server.js | 31 ++++++++++++++++++++++--------- src/spotify.js | 11 +++++------ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/backend/lib/ytdlp.js b/backend/lib/ytdlp.js index 8af4c6e..e5dd85a 100644 --- a/backend/lib/ytdlp.js +++ b/backend/lib/ytdlp.js @@ -312,7 +312,7 @@ async function embedMetadata({ jobId, audioFile, dir, trackPrefix, safeTitle, ar '-id3v2_version', '3', '-metadata', `title=${title}`, '-metadata', `artist=${navidromeArtist(artist)}`, - '-metadata', `album_artist=${mainArtist}`, + '-metadata', `album_artist=${mainArtist.replace(/\s+&\s+/g, ', ')}`, ); if (album) ffArgs.push('-metadata', `album=${album}`); if (year) ffArgs.push('-metadata', `date=${String(year)}`); diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 13858a9..d135e2e 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -17,7 +17,7 @@ const crypto = require('crypto'); const CONFIG = require('./lib/config'); const { jobs, updateJob, log } = require('./lib/jobs'); -const { lookupItunesTrackMeta, fetchItunesTracks, searchItunesAlbumTracks } = require('./lib/metadata'); +const { sanitize, lookupItunesTrackMeta, fetchItunesTracks, searchItunesAlbumTracks } = require('./lib/metadata'); const { downloadTrack, downloadAlbumTracks } = require('./lib/downloader'); const { buildRomajiIndex, clearRomajiCache, isTokenizerReady, fetchNdSongs } = require('./lib/romaji'); @@ -243,15 +243,16 @@ app.post('/api/download', async (req, res) => { try { if (type === 'track') { const sp = req.body.tracks?.[0] || {}; - const spAlbum = sp.collectionName || album || ''; - const spArt = sp.artworkUrl100 || artworkUrl || ''; - const spYear = sp.releaseDate ? new Date(sp.releaseDate).getFullYear() : undefined; + const spAlbum = sp.collectionName || sp.album || album || ''; + const spArt = sp.artworkUrl100 || sp.artworkUrl || artworkUrl || ''; + const spYear = sp.releaseDate ? new Date(sp.releaseDate).getFullYear() : (sp.year || undefined); let meta = { album: spAlbum, year: spYear, trackNum: sp.trackNumber || bodyTrackNum || undefined, totalTracks: sp.trackCount || bodyTotalTracks || undefined, discNum: sp.discNumber || bodyDiscNum || undefined, discTotal: sp.discCount || bodyDiscTotal || undefined, + genre: sp.primaryGenreName || sp.genre || undefined, artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit, durationMs: sp.durationMs || undefined, }; @@ -260,7 +261,7 @@ app.post('/api/download', async (req, res) => { log(jobId, 'πŸ“‹ Suche Album-Metadaten von iTunes…'); const extra = await lookupItunesTrackMeta(searchArtist, title); if (extra.album) log(jobId, `πŸ“‹ Album: "${extra.album}" (${extra.year || '?'})`); - meta = { ...meta, ...extra, artworkUrl: spArt || extra.artworkUrl || '' }; + meta = { ...meta, ...extra, artworkUrl: spArt || extra.artworkUrl || '', genre: meta.genre || extra.genre || undefined }; } else { log(jobId, `πŸ“‹ Album aus Spotify: "${meta.album}"`); } @@ -343,12 +344,12 @@ app.post('/api/download', async (req, res) => { artist: trackArtist, albumArtist: trackArtist, title: trackTitle, - album: itunes.album || track.collectionName || '', - year: itunes.year || (track.releaseDate ? new Date(track.releaseDate).getFullYear() : undefined), + album: itunes.album || track.collectionName || track.album || '', + year: itunes.year || (track.releaseDate ? new Date(track.releaseDate).getFullYear() : undefined) || track.year, trackNum: itunes.trackNum || track.trackNumber || undefined, totalTracks: itunes.totalTracks || undefined, - genre: itunes.genre || track.primaryGenreName || undefined, - artworkUrl: itunes.artworkUrl || track.artworkUrl100 || artworkUrl || '', + genre: itunes.genre || track.primaryGenreName || track.genre || undefined, + artworkUrl: itunes.artworkUrl || track.artworkUrl100 || track.artworkUrl || artworkUrl || '', explicit: track.trackExplicitness === 'explicit', durationMs: itunes.durationMs || track.durationMs || undefined, sourceUrl: track.sourceUrl || undefined, @@ -578,6 +579,18 @@ app.post('/api/edit-metadata', async (req, res) => { fs.renameSync(tmpFile, filePath); if (artFile) try { fs.unlinkSync(artFile); } catch {} + // Rename album folder if album changed and folder name matches old album dir + if (album) { + const dir = path.dirname(filePath); + const newDirName = sanitize(album); + if (path.basename(dir) !== newDirName) { + const newDir = path.join(path.dirname(dir), newDirName); + if (!fs.existsSync(newDir)) { + try { fs.renameSync(dir, newDir); } catch (e) { console.error('[edit-metadata] folder rename:', e.message); } + } + } + } + // Trigger Navidrome rescan try { const salt2 = Math.random().toString(36).slice(2, 10); diff --git a/src/spotify.js b/src/spotify.js index 8434b7d..8191730 100644 --- a/src/spotify.js +++ b/src/spotify.js @@ -7,6 +7,11 @@ import { openDlModal } from './ui.js'; // ─── UNIFIED IMPORTER INPUT ─── export function onImporterInput(val) { + // Always clear results when URL changes + document.getElementById('spotify-preview').style.display = 'none'; + document.getElementById('sc-preview').style.display = 'none'; + document.getElementById('importer-hint').style.display = val ? 'none' : ''; + const badge = document.getElementById('importer-badge'); const btn = document.getElementById('importer-btn'); if (!badge || !btn) return; @@ -32,12 +37,6 @@ export function onImporterInput(val) { btn.style.color = ''; btn.style.borderColor = ''; } - // Hide results when input is cleared - if (!val) { - document.getElementById('spotify-preview').style.display = 'none'; - document.getElementById('sc-preview').style.display = 'none'; - document.getElementById('importer-hint').style.display = ''; - } } export async function resolveImporterLink() {