fix: fetch YTM URLs once and reuse across cascade steps
Each tryYtmSearch() call made a fresh HTTP request, so YouTube could return different result orderings between steps. The correct video (right title, wrong channel name) appeared in step 1 but was gone by step 3 — a worse video with matching keywords slipped through instead. Now fetch 10 YTM URLs once upfront and filter the same list with progressively relaxed rules. Also saves 2-4 extra YTM requests per download. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c037ee623d
commit
7bec106350
1 file changed
+24
-23
+24
-23
@@ -145,15 +145,6 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
proc.on('close', () => resolve(channelExpanded ? [] : urls));
|
||||
});
|
||||
|
||||
// Search YTM for video URLs (pre-filtered), then pass them all at once to yt-dlp.
|
||||
// --max-downloads 1 makes yt-dlp stop after the first video that passes mfArgs.
|
||||
// Filtered/skipped videos do NOT count toward --max-downloads, so the cascade works.
|
||||
const tryYtmSearch = async (maxItems, mfArgs) => {
|
||||
const urls = await getYtmVideoUrls(maxItems);
|
||||
if (!urls.length) return;
|
||||
await spawnYtdlp([...urls, '--max-downloads', '1', ...mfArgs, ...baseArgs]);
|
||||
};
|
||||
|
||||
const variantFilter = buildVariantFilter(title);
|
||||
const titleKws = buildTitleKeywords(title);
|
||||
const variantPart = variantFilter ? ` & ${variantFilter}` : '';
|
||||
@@ -177,6 +168,16 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
const PI_FILTERED_10 = ['--playlist-items', '1-10', '--max-downloads', '1'];
|
||||
const PI_UNFILTERED = ['--playlist-items', '1'];
|
||||
|
||||
// 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 tryYtm = async (n, mfArgs) => {
|
||||
const urls = n ? ytmUrls.slice(0, n) : ytmUrls;
|
||||
if (!urls.length) return;
|
||||
await spawnYtdlp([...urls, '--max-downloads', '1', ...mfArgs, ...baseArgs]);
|
||||
};
|
||||
|
||||
if (durationMs) {
|
||||
const durationSec = Math.round(durationMs / 1000);
|
||||
const margin = Math.max(15, Math.round(durationSec * 0.15));
|
||||
@@ -184,34 +185,34 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
|
||||
log(jobId, `🔍 Suche YT Music (~${durationSec}s ±${margin}s): "${query}"`);
|
||||
// 1. YTM top-5 — strict: duration + title + variant + channel check
|
||||
await tryYtmSearch(5, mf(dur, true, true));
|
||||
await tryYtm(5, mf(dur, true, true));
|
||||
if (!hasAudio()) {
|
||||
// 2. 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 top-10 — no channel check (label channels, etc.)
|
||||
// 3. YTM all-10 — no channel check (label channels, etc.)
|
||||
log(jobId, '⚠️ Erweitere Suche auf top 10 mit Dauerbeschränkung…');
|
||||
await tryYtmSearch(10, mf(dur, true));
|
||||
await tryYtm(0, mf(dur, true));
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 4. YT top-10 — no channel check
|
||||
await spawnYtdlp([`ytsearch10:${query}`, ...PI_FILTERED_10, ...mf(dur, true), ...baseArgs]);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 5. YTM top-5 — no duration check
|
||||
// 5. YTM all — no duration check
|
||||
log(jobId, '⚠️ Kein Treffer im Dauerbereich — suche ohne Dauerbeschränkung…');
|
||||
await tryYtmSearch(5, mf(null, true));
|
||||
await tryYtm(0, mf(null, true));
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 6. YT top-5 — no duration check
|
||||
await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, true), ...baseArgs]);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 7. YTM top-5 — title only (no variant filter)
|
||||
// 7. YTM all — title only (no variant filter)
|
||||
log(jobId, '⚠️ Letzter gefilterter Versuch…');
|
||||
await tryYtmSearch(5, mf(null, false));
|
||||
await tryYtm(0, mf(null, false));
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 8. YT top-5 — title only
|
||||
@@ -220,7 +221,7 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
if (!hasAudio()) {
|
||||
// 9. YTM — no filter at all, take first video result
|
||||
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
|
||||
await tryYtmSearch(3, []);
|
||||
await tryYtm(3, []);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 10. YT — no filter
|
||||
@@ -229,23 +230,23 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
} else {
|
||||
log(jobId, `🔍 Suche YT Music: "${query}"`);
|
||||
// 1. YTM top-5 — strict: title + variant + channel check
|
||||
await tryYtmSearch(5, mf(null, true, true));
|
||||
await tryYtm(5, mf(null, true, true));
|
||||
if (!hasAudio()) {
|
||||
// 2. YT top-5 — strict
|
||||
await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, true, true), ...baseArgs]);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 3. YTM top-5 — no channel check
|
||||
await tryYtmSearch(5, mf(null, true));
|
||||
// 3. YTM all-10 — no channel check (catches label/distributor channels)
|
||||
await tryYtm(0, mf(null, true));
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 4. YT top-5 — no channel check
|
||||
await spawnYtdlp([`ytsearch5:${query}`, ...PI_FILTERED, ...mf(null, true), ...baseArgs]);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 5. YTM top-5 — title only
|
||||
// 5. YTM all — title only
|
||||
log(jobId, '⚠️ Letzter gefilterter Versuch…');
|
||||
await tryYtmSearch(5, mf(null, false));
|
||||
await tryYtm(0, mf(null, false));
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 6. YT top-5 — title only
|
||||
@@ -254,7 +255,7 @@ async function downloadTrack({ jobId, artist, albumArtist, title, album, year, t
|
||||
if (!hasAudio()) {
|
||||
// 7. YTM — first video result, no filter
|
||||
log(jobId, '⚠️ Letzter Versuch: keine Filter…');
|
||||
await tryYtmSearch(3, []);
|
||||
await tryYtm(3, []);
|
||||
}
|
||||
if (!hasAudio()) {
|
||||
// 8. YT — no filter
|
||||
|
||||
Reference in new issue
Block a user