revert: restore original yt-dlp search — remove broken ytmsearch/ytsearch5 changes

Reverts cc7fc80, 59562fe, 595fb2b, 626a755.
The new search logic caused wrong songs, [Unknown Album] folders and duplicate downloads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-28 18:27:01 +02:00
1 parent 626a755688
commit 8e9521f889
3 files changed
+26 -61

No files matched your search

+21 -54
View File
@@ -161,10 +161,9 @@ async function lookupItunesTrackMeta(artist, title) {
} }
// 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, durationMs, 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;
// use only the first artist for the folder path (e.g. "Artist1, Artist2" → "Artist1") const safeArtist = sanitize(mainArtist);
const safeArtist = sanitize(mainArtist.split(',')[0].trim());
const safeAlbum = sanitize(album || 'Singles'); const safeAlbum = sanitize(album || 'Singles');
const dir = path.join(outputDir || CONFIG.musicPath, safeArtist, safeAlbum); const dir = path.join(outputDir || CONFIG.musicPath, safeArtist, safeAlbum);
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
@@ -179,26 +178,19 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
} }
const outputTemplate = path.join(dir, `${trackPrefix}${safeTitle}.%(ext)s`); const outputTemplate = path.join(dir, `${trackPrefix}${safeTitle}.%(ext)s`);
const query = `${mainArtist} - ${title}`; const query = `${mainArtist} - ${title} official audio`;
log(jobId, `🔍 Suche: "${query}"`);
// Build match-filter: always exclude live streams; also constrain duration when known (±30s low, +45s high) // Step 1: yt-dlp — audio only, no thumbnail (we embed iTunes artwork ourselves)
const durationSec = durationMs ? Math.round(durationMs / 1000) : null; await new Promise((resolve, reject) => {
const matchFilter = [
'!is_live',
durationSec && `duration > ${Math.max(0, durationSec - 30)}`,
durationSec && `duration < ${durationSec + 45}`,
].filter(Boolean).join(' & ');
const runYtdlp = (searchExpr) => new Promise((resolve, reject) => {
const args = [ const args = [
searchExpr, `ytsearch1:${query}`,
'--extract-audio', '--extract-audio',
'--audio-format', CONFIG.audioFmt, '--audio-format', CONFIG.audioFmt,
'--audio-quality', CONFIG.audioQuality, '--audio-quality', CONFIG.audioQuality,
'-o', outputTemplate, '-o', outputTemplate,
'--no-playlist', '--no-playlist',
'--match-filter', matchFilter, '--playlist-items', '1',
'--max-downloads', '1',
'--no-warnings', '--no-warnings',
'--newline', '--newline',
]; ];
@@ -218,33 +210,11 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
.forEach(l => log(jobId, l.trim())); .forEach(l => log(jobId, l.trim()));
}); });
proc.on('error', reject); proc.on('error', reject);
// code 101 = --max-downloads limit reached (= success); 0 = completed normally
proc.on('close', code => proc.on('close', code =>
(code === 0 || code === 101) ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`)) code === 0 ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`))
); );
}); });
const audioFileCount = () =>
fs.readdirSync(dir).filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`))).length;
// Step 1: YouTube Music — music-specific index, far fewer false positives
// Falls silently if this yt-dlp version doesn't support ytmsearch (older Alpine package)
const countBefore = audioFileCount();
try {
log(jobId, `🎵 YouTube Music: "${query}"`);
await runYtdlp(`ytmsearch:${query}`);
} catch (e) {
if (!e.message.includes('Unsupported url scheme')) {
log(jobId, `⚠️ YouTube Music Fehler: ${e.message}`);
}
}
// Step 2: Fallback to regular YouTube if no file was produced
if (audioFileCount() === countBefore) {
log(jobId, `🔍 YouTube: "${query}"`);
await runYtdlp(`ytsearch5:${query}`);
}
// Step 2: find the audio file yt-dlp wrote (newest audio file in dir) // Step 2: find the audio file yt-dlp wrote (newest audio file in dir)
const audioFile = fs.readdirSync(dir) const audioFile = fs.readdirSync(dir)
.filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`))) .filter(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`)))
@@ -344,11 +314,10 @@ async function downloadAlbumTracks({ jobId, artist, albumTitle, tracks, artworkU
title: track.trackName || track.title, title: track.trackName || track.title,
album: albumTitle, album: albumTitle,
year: track.releaseDate ? new Date(track.releaseDate).getFullYear() : undefined, year: track.releaseDate ? new Date(track.releaseDate).getFullYear() : undefined,
trackNum: track.trackNumber || undefined, trackNum: track.trackNumber || undefined,
totalTracks: total, totalTracks: total,
discNum: track.discNumber || undefined, discNum: track.discNumber || undefined,
discTotal: track.discCount || undefined, discTotal: track.discCount || undefined,
durationMs: track.trackTimeMillis || undefined,
compilation: track.collectionType === 'Compilation' || undefined, compilation: track.collectionType === 'Compilation' || undefined,
genre: track.primaryGenreName || undefined, genre: track.primaryGenreName || undefined,
artworkUrl: artworkUrl || track.artworkUrl100, artworkUrl: artworkUrl || track.artworkUrl100,
@@ -596,8 +565,7 @@ app.post('/api/spotify/resolve', async (req, res) => {
app.post('/api/download', async (req, res) => { app.post('/api/download', async (req, res) => {
const { type, artist, title, album, artworkUrl, collectionId, jobId: clientJobId, explicit, const { type, artist, title, album, artworkUrl, collectionId, jobId: clientJobId, explicit,
trackNum: bodyTrackNum, totalTracks: bodyTotalTracks, discNum: bodyDiscNum, discTotal: bodyDiscTotal, trackNum: bodyTrackNum, totalTracks: bodyTotalTracks, discNum: bodyDiscNum, discTotal: bodyDiscTotal } = req.body;
durationMs: bodyDurationMs } = req.body;
if (!artist || !title) return res.status(400).json({ error: 'artist and title required' }); if (!artist || !title) return res.status(400).json({ error: 'artist and title required' });
const jobId = clientJobId || ('job_' + Date.now()); const jobId = clientJobId || ('job_' + Date.now());
@@ -618,11 +586,10 @@ app.post('/api/download', async (req, res) => {
const spArt = sp.artworkUrl100 || artworkUrl || ''; const spArt = sp.artworkUrl100 || artworkUrl || '';
const spYear = sp.releaseDate ? new Date(sp.releaseDate).getFullYear() : undefined; const spYear = sp.releaseDate ? new Date(sp.releaseDate).getFullYear() : undefined;
let meta = { album: spAlbum, year: spYear, let meta = { album: spAlbum, year: spYear,
trackNum: sp.trackNumber || bodyTrackNum || undefined, trackNum: sp.trackNumber || bodyTrackNum || undefined,
totalTracks: sp.trackCount || bodyTotalTracks || undefined, totalTracks: sp.trackCount || bodyTotalTracks || undefined,
discNum: sp.discNumber || bodyDiscNum || undefined, discNum: sp.discNumber || bodyDiscNum || undefined,
discTotal: sp.discCount || bodyDiscTotal || undefined, discTotal: sp.discCount || bodyDiscTotal || undefined,
durationMs: sp.trackTimeMillis || bodyDurationMs || undefined,
artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit }; artworkUrl: spArt, explicit: sp.trackExplicitness === 'explicit' || !!explicit };
if (!meta.album) { if (!meta.album) {
// Use first artist only for iTunes search (multi-artist strings like "Kløn, Renzyx" confuse iTunes) // Use first artist only for iTunes search (multi-artist strings like "Kløn, Renzyx" confuse iTunes)
@@ -634,7 +601,7 @@ app.post('/api/download', async (req, res) => {
} else { } else {
log(jobId, `📋 Album aus Spotify: "${meta.album}"`); log(jobId, `📋 Album aus Spotify: "${meta.album}"`);
} }
const result = await downloadTrack({ jobId, artist, albumArtist: artist.split(',')[0].trim(), title, ...meta }); const result = await downloadTrack({ jobId, artist, title, ...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 {
+1 -2
View File
@@ -30,8 +30,7 @@ services:
- MAX_PARALLEL=${MAX_PARALLEL:-2} - MAX_PARALLEL=${MAX_PARALLEL:-2}
command: > command: >
sh -c " sh -c "
apk add --no-cache ffmpeg python3 py3-pip && apk add --no-cache yt-dlp ffmpeg python3 &&
pip3 install -U yt-dlp --break-system-packages &&
npm install express cors && npm install express cors &&
node novasonic-server.js node novasonic-server.js
" "
+4 -5
View File
@@ -294,11 +294,10 @@ export async function executeDownload() {
itunesId: S.downloadTarget.trackId || S.downloadTarget.collectionId || null, itunesId: S.downloadTarget.trackId || S.downloadTarget.collectionId || null,
collectionId: S.downloadTarget.collectionId || null, collectionId: S.downloadTarget.collectionId || null,
explicit: S.downloadTarget.trackExplicitness === 'explicit', explicit: S.downloadTarget.trackExplicitness === 'explicit',
trackNum: S.downloadTarget.trackNumber || undefined, trackNum: S.downloadTarget.trackNumber || undefined,
totalTracks: S.downloadTarget.trackCount || undefined, totalTracks: S.downloadTarget.trackCount || undefined,
discNum: S.downloadTarget.discNumber || undefined, discNum: S.downloadTarget.discNumber || undefined,
discTotal: S.downloadTarget.discCount || undefined, discTotal: S.downloadTarget.discCount || undefined,
durationMs: S.downloadTarget.trackTimeMillis || undefined,
}; };
addJob(jobId, payload.title, payload.artist, payload.type); addJob(jobId, payload.title, payload.artist, payload.type);
try { try {