From 03394cac6869e5f1e60b651899ee55937d81b800 Mon Sep 17 00:00:00 2001 From: Honey Date: Tue, 28 Apr 2026 18:55:01 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20correct=20yt-dlp=20duration=20filter=20?= =?UTF-8?q?=E2=80=94=20handle=20exit=20code=20101,=20remove=20--playlist-i?= =?UTF-8?q?tems=20from=20filtered=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --playlist-items 1 in baseArgs caused filter to only check result #1 (filter useless). --max-downloads 1 exits with code 101 which we treated as an error β€” ffmpeg never ran, leaving untagged files (β†’ Unknown Artist/Album in Navidrome). Now code 101 resolves successfully and filtered search uses commonArgs without --playlist-items. Co-Authored-By: Claude Sonnet 4.6 --- backend/novasonic-server.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 24e2710..9620270 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -182,16 +182,16 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t const query = `${mainArtist} - ${title} official audio`; // Step 1: yt-dlp β€” audio only, no thumbnail (we embed iTunes artwork ourselves) - const baseArgs = [ + const commonArgs = [ '--extract-audio', '--audio-format', CONFIG.audioFmt, '--audio-quality', CONFIG.audioQuality, '-o', outputTemplate, - '--no-playlist', - '--playlist-items', '1', '--no-warnings', '--newline', ]; + // --no-playlist + --playlist-items 1 only for single-result searches (prevents playlist bleed) + const singleArgs = [...commonArgs, '--no-playlist', '--playlist-items', '1']; const spawnYtdlp = (args) => new Promise((resolve, reject) => { const proc = spawn(CONFIG.ytdlpBin, args, { env: { ...process.env, PYTHONUNBUFFERED: '1' } }); @@ -210,8 +210,9 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t .forEach(l => log(jobId, l.trim())); }); proc.on('error', reject); + // code 101 = --max-downloads limit reached (success β€” file was downloaded) proc.on('close', code => - code === 0 ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`)) + (code === 0 || code === 101) ? resolve() : reject(new Error(`yt-dlp code ${code}: ${stderr.slice(-300)}`)) ); }); @@ -220,21 +221,22 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t const minDur = Math.max(30, Math.round(durationSec * 0.6)); const maxDur = Math.round(durationSec * 1.5); log(jobId, `πŸ” Suche (~${durationSec}s): "${query}"`); + // Search 5 results, pick first matching duration β€” no --playlist-items so filter sees all 5 await spawnYtdlp([ `ytsearch5:${query}`, '--match-filter', `duration > ${minDur} & duration < ${maxDur} & !is_live`, '--max-downloads', '1', - ...baseArgs, + ...commonArgs, ]); // Fallback if duration filter matched nothing const hasFile = fs.readdirSync(dir).some(f => AUDIO_EXTS.some(e => f.endsWith(`.${e}`))); if (!hasFile) { log(jobId, '⚠️ Kein Treffer im Dauerbereich β€” Suche ohne Filter…'); - await spawnYtdlp([`ytsearch1:${query}`, ...baseArgs]); + await spawnYtdlp([`ytsearch1:${query}`, ...singleArgs]); } } else { log(jobId, `πŸ” Suche: "${query}"`); - await spawnYtdlp([`ytsearch1:${query}`, ...baseArgs]); + await spawnYtdlp([`ytsearch1:${query}`, ...singleArgs]); } // Step 2: find the audio file yt-dlp wrote (newest audio file in dir)