test(backend): Filterprobe fragt auch YT Music ab — erste Fassung meldete erfundene Fehler
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f87a12f08
commit
7879e37a7b
2 files changed
+50
-12
No files matched your search
+10
-3
@@ -231,9 +231,16 @@ Die Android-App zählt eigenständig weiter (mehrere APKs pro Release). Welche d
|
||||
`artist`-Feld den Song selbst benennen. Es fällt **kein** Schritt weg — was vorher gefunden
|
||||
wurde, wird weiter gefunden, gegebenenfalls eine Stufe später.
|
||||
- **Belegt statt behauptet:** `backend/scripts/filter-probe.mjs` holt für bekannte Fälle die
|
||||
echten Suchtreffer und spielt beide Reihenfolgen durch. Fünf Fälle, **ein** Unterschied (der
|
||||
gemeldete), **null** Regressionen — BTS auf dem Label-Kanal BANGTANTV, der japanische
|
||||
Soundtrack-Titel und Rising Insane kommen unverändert heraus, Letzterer nur eine Stufe später.
|
||||
echten Suchtreffer — **aus beiden Quellen, YT Music zuerst** — und spielt beide Reihenfolgen
|
||||
durch. Fünf Fälle, **ein** Unterschied (der gemeldete), **null** Regressionen: BTS auf dem
|
||||
Label-Kanal BANGTANTV, der japanische Soundtrack-Titel, Rising Insane und Daft Punk kommen
|
||||
unverändert heraus.
|
||||
- Die erste Fassung der Probe sah **nur** die gewöhnliche YouTube-Suche und meldete deshalb für
|
||||
„Get Lucky" einen Grammy-Mitschnitt und für TPR ein Tutorial-Video. Beides war erfunden: über
|
||||
YT Music steht dort an erster Stelle die offizielle Tonspur („Get Lucky (feat. Pharrell
|
||||
Williams and Nile Rodgers)", 370 s) beziehungsweise TPRs echtes „Dearly Beloved" (138 s) — und
|
||||
genau die lädt die Kaskade. Eine Probe, die eine Stufe auslässt, erfindet Fehler; deshalb fragt
|
||||
sie jetzt beide Quellen ab.
|
||||
- **Der Download-Filter konnte den falschen Titel speichern — zwei Löcher, ein Grund (Backend)** —
|
||||
gemeldet mit „銀髪の少年" (Yoshihisa Hirano, HUNTER×HUNTER) und „Dearly Beloved" (TPR): beide Male
|
||||
kam etwas anderes an.
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
// das `artist`-Feld der Art Tracks vorliegt) und spielt dann die Kaskadenstufen lokal durch —
|
||||
// in der ALTEN und der NEUEN Reihenfolge. Gemeldet wird, welcher Treffer jeweils gewinnt.
|
||||
//
|
||||
// WICHTIG: beide Quellen werden abgefragt, YT MUSIC ZUERST. Die erste Fassung dieser Probe sah nur
|
||||
// die gewoehnliche YouTube-Suche und meldete deshalb fuer "Get Lucky" einen Grammy-Mitschnitt als
|
||||
// Treffer — in Wahrheit liefert YT Music dort die offizielle Tonspur an erster Stelle, und genau
|
||||
// die laedt die Kaskade. Eine Probe, die eine Stufe auslaesst, erfindet Fehler.
|
||||
//
|
||||
// docker run --rm --network host --entrypoint node <backend-image> /app/scripts/filter-probe.mjs
|
||||
//
|
||||
// Die Filterbedingungen sind hier nachgebaut, nicht importiert: geprueft werden soll, was yt-dlp
|
||||
@@ -55,11 +60,35 @@ const FAELLE = [
|
||||
];
|
||||
|
||||
// ── Suchtreffer holen ─────────────────────────────────────────────────────────
|
||||
const YTM_SONGS_PARAM = 'EgWKAQIIAWoKEAkQBRAKEAMQBA%3D%3D';
|
||||
|
||||
/** YT-Music-Suche: erst die URLs der Songs-Leiste, dann deren Metadaten. */
|
||||
async function sucheYtm(query, n) {
|
||||
const url = `https://music.youtube.com/search?q=${encodeURIComponent(query)}¶ms=${YTM_SONGS_PARAM}`;
|
||||
const urls = await new Promise(resolve => {
|
||||
const p = spawn('yt-dlp', [
|
||||
url, '--flat-playlist', '--playlist-items', `1-${n}`, '--print', 'webpage_url',
|
||||
'--no-warnings', '--extractor-args', 'youtube:player_client=web_music,default',
|
||||
]);
|
||||
let aus = '';
|
||||
p.stdout.on('data', d => aus += d.toString());
|
||||
p.on('error', () => resolve([]));
|
||||
p.on('close', () => resolve(aus.split('\n').map(z => z.trim()).filter(z => z.includes('watch?v='))));
|
||||
});
|
||||
if (!urls.length) return [];
|
||||
return metadaten(urls);
|
||||
}
|
||||
|
||||
function suche(query, n) {
|
||||
return metadaten([`ytsearch${n}:${query}`]);
|
||||
}
|
||||
|
||||
/** Metadaten zu einer Liste von URLs (oder einer Suchangabe) holen. */
|
||||
function metadaten(ziele) {
|
||||
return new Promise(resolve => {
|
||||
const felder = '%(duration)s%(channel)s%(artist)s%(title)s';
|
||||
const p = spawn('yt-dlp', [
|
||||
`ytsearch${n}:${query}`, '--simulate', '--no-warnings', '--ignore-errors',
|
||||
...ziele, '--simulate', '--no-warnings', '--ignore-errors',
|
||||
'--print', felder,
|
||||
]);
|
||||
let aus = '';
|
||||
@@ -115,13 +144,14 @@ function pruefer(fall) {
|
||||
|
||||
const ersterTreffer = (kandidaten, pruefung) => kandidaten.find(pruefung) || null;
|
||||
|
||||
function kaskade(kandidaten, p, reihenfolge) {
|
||||
function kaskade(ytm, yt, p, reihenfolge) {
|
||||
const stufen = reihenfolge === 'neu'
|
||||
? [['1/2 streng', p.streng], ['3/4 ohne Dauer, mit Künstler', p.ohneDauer], ['5/6 mit Dauer, ohne Künstler', p.ohneKuenstler]]
|
||||
: [['1/2 streng', p.streng], ['3/4 mit Dauer, ohne Künstler', p.ohneKuenstler], ['5/6 ohne Dauer, mit Künstler', p.ohneDauer]];
|
||||
for (const [stufe, pruefung] of stufen) {
|
||||
const t = ersterTreffer(kandidaten, pruefung);
|
||||
if (t) return { stufe, treffer: t };
|
||||
// Jede Stufe fragt erst YT Music, dann die gewoehnliche YouTube-Suche — so laeuft die Kaskade.
|
||||
const t = ersterTreffer(ytm, pruefung) || ersterTreffer(yt, pruefung);
|
||||
if (t) return { stufe: `${stufe}${ytm.includes(t) ? ' · YTM' : ' · YT'}`, treffer: t };
|
||||
}
|
||||
return { stufe: 'kein Treffer', treffer: null };
|
||||
}
|
||||
@@ -133,14 +163,15 @@ const zeig = t => t ? `${String(t.dauer).padStart(4)}s | ${t.kanal || '?'} | ${t
|
||||
for (const fall of FAELLE) {
|
||||
const query = `${fall.artist} ${fall.titel}`;
|
||||
process.stdout.write(`\n▶ ${fall.name}\n Suche: "${query}" `);
|
||||
const kandidaten = await suche(query, 10);
|
||||
console.log(`(${kandidaten.length} Treffer)`);
|
||||
if (!kandidaten.length) { console.log(' ⚠️ keine Suchtreffer — übersprungen'); continue; }
|
||||
const ytm = await sucheYtm(query, 10);
|
||||
const yt = await suche(query, 10);
|
||||
console.log(`(YTM ${ytm.length}, YouTube ${yt.length})`);
|
||||
if (!ytm.length && !yt.length) { console.log(' ⚠️ keine Suchtreffer — übersprungen'); continue; }
|
||||
|
||||
const p = pruefer(fall);
|
||||
console.log(` Dauerfenster ${p.fenster.min}–${p.fenster.max}s`);
|
||||
const alt = kaskade(kandidaten, p, 'alt');
|
||||
const neu = kaskade(kandidaten, p, 'neu');
|
||||
const alt = kaskade(ytm, yt, p, 'alt');
|
||||
const neu = kaskade(ytm, yt, p, 'neu');
|
||||
console.log(` ALT [${alt.stufe}] ${zeig(alt.treffer)}`);
|
||||
console.log(` NEU [${neu.stufe}] ${zeig(neu.treffer)}`);
|
||||
|
||||
|
||||
Reference in new issue
Block a user