diff --git a/backend/lib/ytdlp.js b/backend/lib/ytdlp.js index 2155b67..dc5dd58 100644 --- a/backend/lib/ytdlp.js +++ b/backend/lib/ytdlp.js @@ -247,7 +247,7 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb // Fetch YTM URLs once upfront so every cascade step filters the SAME result set. // Re-fetching produced different orderings, causing the correct video (found in step 1 // but failing the channel filter) to disappear by step 3 and a wrong one to slip through. - const ytmUrls = await getYtmVideoUrls(10); + const ytmUrls = await getYtmVideoUrls(20); const tryYtm = async (n, mfArgs) => { const urls = n ? ytmUrls.slice(0, n) : ytmUrls; if (!urls.length) return; @@ -260,47 +260,51 @@ async function downloadTrack({ jobId, artist, rawArtist, albumArtist, title, alb const dur = { min: Math.max(30, durationSec - margin), max: durationSec + margin }; log(jobId, `🔍 Suche YT Music (~${durationSec}s ±${margin}s): "${query}"`); - // 1. YTM top-5 — strict: duration + title + variant + channel check + // 1. YTM top-5 via manual URL — strict: duration + title + variant + channel check await tryYtm(5, mf(dur, true, true)); if (!hasAudio()) { - // 2. YT top-5 — strict: same filters including channel check + // 2. ytmsearch5 (yt-dlp native YTM) — different result ordering than manual URL + await spawnYtdlp([`ytmsearch5:${query}`, ...PI_FILTERED, ...mf(dur, true, true), ...baseArgs]); + } + if (!hasAudio()) { + // 3. YT top-5 — strict: same filters including channel check log(jobId, '⚠️ Kein YTM-Treffer — Fallback zu YouTube…'); await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(dur, true, true), ...baseArgs]); } if (!hasAudio()) { - // 3. YTM all-10 — no channel check (label channels, etc.) - log(jobId, '⚠️ Erweitere Suche auf top 10 mit Dauerbeschränkung…'); + // 4. YTM all-20 — no channel check (label channels, etc.) + log(jobId, '⚠️ Erweitere Suche auf top 20 mit Dauerbeschränkung…'); await tryYtm(0, mf(dur, true)); } if (!hasAudio()) { - // 4. YT top-10 — no channel check + // 5. YT top-10 — no channel check await spawnYtdlp([`ytsearch10:${query}`, ...PI_FILTERED_10, ...mf(dur, true), ...baseArgs]); } if (!hasAudio()) { - // 5. YTM all — no duration check + // 6. YTM all — no duration check log(jobId, '⚠️ Kein Treffer im Dauerbereich — suche ohne Dauerbeschränkung…'); await tryYtm(0, mf(null, true)); } if (!hasAudio()) { - // 6. YT top-5 — no duration check + // 7. YT top-5 — no duration check await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, true), ...baseArgs]); } if (!hasAudio()) { - // 7. YTM all — title only (no variant filter) + // 8. YTM all — title only (no variant filter) log(jobId, '⚠️ Letzter gefilterter Versuch…'); await tryYtm(0, mf(null, false)); } if (!hasAudio()) { - // 8. YT top-5 — title only + // 9. YT top-5 — title only await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, false), ...baseArgs]); } if (!hasAudio()) { - // 9. YTM — no filter at all, take first video result + // 10. YTM — no filter at all, take first video result log(jobId, '⚠️ Letzter Versuch: keine Filter…'); await tryYtm(3, []); } if (!hasAudio()) { - // 10. YT — no filter + // 11. YT — no filter await spawnYtdlp([`ytsearch1:${query}`, ...PI_UNFILTERED, ...baseArgs]); } } else {