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 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-23 21:19:42 +02:00
1 parent 62fefbb5e7
commit d785a4c09a
3 files changed
+28 -16

No files matched your search

+1 -1
View File
@@ -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)}`);
+22 -9
View File
@@ -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);
+5 -6
View File
@@ -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() {