getYtmVideoUrls wertete jedes /browse/UC in der Ausgabe als "yt-dlp hat einen Artist-Channel aufgeklappt" und gab [] zurueck — samt der bereits gesammelten Song-URLs. Bei "DAT ADAM UFO" stand der Kanal auf Platz 9 und riss sieben gute Treffer mit, darunter den offiziellen Art Track auf Platz 1; uebrig blieb der YouTube-Fallback mit einem Live-Mitschnitt. An 8 echten Suchen gemessen feuerte das in 7 Faellen, Schritt 1 der Kaskade war also praktisch tot. Mit --flat-playlist kann gar nichts aufgeklappt werden, ein Kanal ist EINE Zeile. pickYtmVideoUrls() behaelt daher alle watch?v=-Zeilen und ueberspringt browse/UC (Kanal) wie browse/MPREb_ (Album). Ueber 20k Zufallsausgaben geprueft: die neue Funktion ist eine Obermenge der alten, kann also nur Kandidaten hinzufuegen. Drei echte Downloads liefen end-to-end sauber durch (Dauer sekundengenau auf Deezer, korrekte Tags), alle aus Schritt 1 statt aus dem Fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
5.1 KiB
JavaScript
110 lines
5.1 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { buildTitleKeywords } = require('../lib/ytdlp.js');
|
|
|
|
// The keywords become `title ~= (?i)<kw>` match-filters in yt-dlp, i.e. every keyword must match
|
|
// the candidate title case-insensitively. This mirrors that check.
|
|
const accepts = (title, candidate) =>
|
|
buildTitleKeywords(title).every(kw => new RegExp(kw, 'i').test(candidate));
|
|
|
|
test('a number in the title is always required, however short', () => {
|
|
const kws = buildTitleKeywords('700 Main Street');
|
|
assert.ok(kws.some(k => k.includes('700')), `700 fehlt in ${JSON.stringify(kws)}`);
|
|
});
|
|
|
|
test('rejects a same-shaped title with a different number', () => {
|
|
// The real regression: "777 Main Street" (Release - Topic) was downloaded for DAT ADAM's
|
|
// "700 Main Street", because only the two longest words (Street, Main) were required.
|
|
assert.equal(accepts('700 Main Street', '777 Main Street'), false);
|
|
assert.equal(accepts('700 Main Street', '700 Main Street'), true);
|
|
});
|
|
|
|
test('accepts the abbreviated spelling uploaders use', () => {
|
|
assert.equal(accepts('700 Main Street', '700 Main st (cr-Version)'), true);
|
|
assert.equal(accepts('700 Main Street', 'DAT ADAM - 700 Main St [FULL SONG]'), true);
|
|
});
|
|
|
|
test('the abbreviation does not match inside another word', () => {
|
|
assert.equal(accepts('700 Main Street', '700 Main Stuff'), false);
|
|
});
|
|
|
|
test('titles without numbers keep the previous two-longest-words behaviour', () => {
|
|
assert.deepEqual(buildTitleKeywords('Bohemian Rhapsody'), ['Bohemian', 'Rhapsody']);
|
|
assert.equal(accepts('Bohemian Rhapsody', 'Queen - Bohemian Rhapsody (Official Video)'), true);
|
|
assert.equal(accepts('Bohemian Rhapsody', 'Queen - Another One Bites The Dust'), false);
|
|
});
|
|
|
|
test('a remaster year is not required — YouTube titles never carry it', () => {
|
|
// "… - 2020 Remaster" steht auf der Veröffentlichung, der Upload heißt schlicht
|
|
// "Take Me Home, Country Roads". Vor dieser Ausnahme hätte die 2020 ihn abgelehnt.
|
|
assert.equal(accepts('Take Me Home, Country Roads - 2020 Remaster',
|
|
'John Denver - Take Me Home, Country Roads'), true);
|
|
});
|
|
|
|
test('a year that IS the title stays required', () => {
|
|
assert.equal(accepts('1999', 'Prince - 1999 (Official Music Video)'), true);
|
|
assert.equal(accepts('1999', 'Prince - 1984 (Official Music Video)'), false);
|
|
});
|
|
|
|
const { buildVariantFilter } = require('../lib/ytdlp.js');
|
|
|
|
// Der Variantenfilter ist eine yt-dlp-Ausdruckskette "title !~= (?i)\bx\b & …".
|
|
// Diese Hilfsfunktion wertet sie so aus, wie yt-dlp es tut.
|
|
const variantAccepts = (searchTitle, candidate) => {
|
|
const f = buildVariantFilter(searchTitle);
|
|
if (!f) return true;
|
|
return f.split(' & ').every(part => {
|
|
const m = /^title !~= \(\?i\)(.+)$/.exec(part);
|
|
return !new RegExp(m[1], 'i').test(candidate);
|
|
});
|
|
};
|
|
|
|
test('rejects a live recording — is_live only covers running streams', () => {
|
|
// Der echte Fall: auf dem Kuenstlerkanal, Dauer im Fenster, Titel passend.
|
|
assert.equal(variantAccepts('UFO', 'Dat Adam - Ufo Video Days 2015 Live'), false);
|
|
assert.equal(variantAccepts('UFO', 'DAT ADAM - 700 Main St | ROCK AM RING 2017 [LIVE]'), false);
|
|
assert.equal(variantAccepts('UFO', 'UFO'), true);
|
|
});
|
|
|
|
test('a deliberately live title keeps its live results', () => {
|
|
assert.equal(variantAccepts('Live Forever', 'Oasis - Live Forever'), true);
|
|
assert.equal(variantAccepts('Unplugged in New York (Live)', 'Nirvana - All Apologies (Live)'), true);
|
|
});
|
|
|
|
test('"live" inside another word does not disable the rule', () => {
|
|
// "deliver" enthaelt "live" als Teilstring — die Regel muss trotzdem greifen.
|
|
assert.equal(variantAccepts('Deliver', 'Artist - Deliver (Live at Wembley)'), false);
|
|
assert.equal(variantAccepts('Deliver', 'Artist - Deliver'), true);
|
|
});
|
|
|
|
const { pickYtmVideoUrls } = require('../lib/ytdlp.js');
|
|
|
|
// Echte Ausgabe der YTM-Suche "DAT ADAM UFO": Songs, ein Album (MPREb_) und der
|
|
// Kuenstlerkanal (UC…) auf Platz 9.
|
|
const YTM_OUT = [
|
|
'https://music.youtube.com/watch?v=ZG4XhPn3Yb4',
|
|
'https://music.youtube.com/watch?v=2KVU6aikZF0',
|
|
'https://music.youtube.com/browse/MPREb_IhxJPixC1QT',
|
|
'https://music.youtube.com/watch?v=phLOTtJaqZk',
|
|
'https://music.youtube.com/watch?v=p935nH6aj8Y',
|
|
'https://music.youtube.com/watch?v=Pb2YcDBm5fA',
|
|
'https://music.youtube.com/browse/MPREb_S7MMKVMduma',
|
|
'https://music.youtube.com/watch?v=mNfsX2RbZ2M',
|
|
'https://music.youtube.com/browse/UC5hilNPCUHEPf4VpG5_YpYA',
|
|
'https://music.youtube.com/watch?v=O034JUlf1J4',
|
|
].join('\n');
|
|
|
|
test('a channel hit no longer discards the whole result list', () => {
|
|
const { urls, skipped } = pickYtmVideoUrls(YTM_OUT);
|
|
assert.equal(urls.length, 7);
|
|
assert.equal(skipped, 3); // 2 Alben + 1 Kanal
|
|
assert.match(urls[0], /ZG4XhPn3Yb4/); // der offizielle Art Track
|
|
assert.ok(!urls.some(u => u.includes('/browse/')));
|
|
});
|
|
|
|
test('nothing but list entries yields no candidates', () => {
|
|
const only = 'https://music.youtube.com/browse/UC5hilNPCUHEPf4VpG5_YpYA';
|
|
assert.deepEqual(pickYtmVideoUrls(only).urls, []);
|
|
assert.deepEqual(pickYtmVideoUrls('').urls, []);
|
|
});
|