fix(backend): scan top 5 results per search instead of only item 1

--playlist-items 1-5 --max-downloads 1 on all filtered steps so yt-dlp
skips non-matching results (wrong artist, sped up, wrong duration) and
lands on the first result that actually passes all active filters.
Unfiltered last-resort fallbacks keep --playlist-items 1.
YouTube fallback uses ytsearch5: to match the 5-result window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-02 12:00:49 +02:00
1 parent cf8ba70af8
commit 5db9938f49
1 file changed
+19 -16
+19 -16
View File
@@ -218,10 +218,13 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
'--audio-quality', CONFIG.audioQuality,
'-o', outputTemplate,
'--no-playlist',
'--playlist-items', '1',
'--no-warnings',
'--newline',
];
// Filtered searches scan top 5 results and stop at first match.
// Unfiltered fallback only needs item 1 (no filter to skip past).
const PI_FILTERED = ['--playlist-items', '1-5', '--max-downloads', '1'];
const PI_UNFILTERED = ['--playlist-items', '1'];
const spawnYtdlp = (args) => new Promise((resolve, reject) => {
const proc = spawn(CONFIG.ytdlpBin, args, { env: { ...process.env, PYTHONUNBUFFERED: '1' } });
@@ -274,61 +277,61 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
const dur = { min: Math.max(30, durationSec - margin), max: durationSec + margin };
log(jobId, `🔍 Suche YT Music (~${durationSec}s ±${margin}s): "${query}"`);
// 1. YTM — Dauer + Artist + Varianten-Filter (streng)
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, true, true), ...baseArgs]);
// 1. YTM — Dauer + Artist + Varianten-Filter (Top 5, erster Treffer)
await tryYtdlp([ytmUrl, ...PI_FILTERED, ...mf(dur, true, true), ...baseArgs]);
if (!hasAudio()) {
// 2. YT — Dauer + Artist + Varianten-Filter
log(jobId, '⚠️ Kein YTM-Treffer — Fallback zu YouTube…');
await tryYtdlp([`ytsearch1:${query}`, ...mf(dur, true, true), ...baseArgs]);
await tryYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(dur, true, true), ...baseArgs]);
}
if (!hasAudio()) {
// 3. YTM — Dauer + Varianten-Filter (kein Artist)
log(jobId, '⚠️ Kein Treffer mit Artist-Filter — ohne Artist…');
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(dur, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_FILTERED, ...mf(dur, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 4. YT — Dauer + Varianten-Filter
await tryYtdlp([`ytsearch1:${query}`, ...mf(dur, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(dur, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 5. YTM — nur Varianten-Filter (kein Dauer-Check)
log(jobId, '⚠️ Kein Treffer im Dauerbereich — suche ohne Dauerbeschränkung…');
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_FILTERED, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 6. YT — nur Varianten-Filter
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
// 7+8. Letzter Ausweg: komplett ungefiltert
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_UNFILTERED, ...baseArgs]);
}
if (!hasAudio()) {
await tryYtdlp([`ytsearch1:${query}`, ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...PI_UNFILTERED, ...baseArgs]);
}
} else {
log(jobId, `🔍 Suche YT Music: "${query}"`);
// Ohne Dauer: Artist + Varianten → ohne Artist → ohne alles
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, true, true), ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_FILTERED, ...mf(null, true, true), ...baseArgs]);
if (!hasAudio()) {
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, true, true), ...baseArgs]);
await tryYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, true, true), ...baseArgs]);
}
if (!hasAudio()) {
log(jobId, '⚠️ Kein Treffer mit Artist — ohne Artist-Filter…');
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_FILTERED, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
await tryYtdlp([`ytsearch1:${query}`, ...mf(null, false, true), ...baseArgs]);
await tryYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, false, true), ...baseArgs]);
}
if (!hasAudio()) {
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
await tryYtdlp([ytmUrl, '--playlist-items', '1', ...baseArgs]);
await tryYtdlp([ytmUrl, ...PI_UNFILTERED, ...baseArgs]);
}
if (!hasAudio()) {
await tryYtdlp([`ytsearch1:${query}`, ...baseArgs]);
await tryYtdlp([`ytsearch1:${query}`, ...PI_UNFILTERED, ...baseArgs]);
}
}