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
|
`artist`-Feld den Song selbst benennen. Es fällt **kein** Schritt weg — was vorher gefunden
|
||||||
wurde, wird weiter gefunden, gegebenenfalls eine Stufe später.
|
wurde, wird weiter gefunden, gegebenenfalls eine Stufe später.
|
||||||
- **Belegt statt behauptet:** `backend/scripts/filter-probe.mjs` holt für bekannte Fälle die
|
- **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
|
echten Suchtreffer — **aus beiden Quellen, YT Music zuerst** — und spielt beide Reihenfolgen
|
||||||
gemeldete), **null** Regressionen — BTS auf dem Label-Kanal BANGTANTV, der japanische
|
durch. Fünf Fälle, **ein** Unterschied (der gemeldete), **null** Regressionen: BTS auf dem
|
||||||
Soundtrack-Titel und Rising Insane kommen unverändert heraus, Letzterer nur eine Stufe später.
|
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)** —
|
- **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
|
gemeldet mit „銀髪の少年" (Yoshihisa Hirano, HUNTER×HUNTER) und „Dearly Beloved" (TPR): beide Male
|
||||||
kam etwas anderes an.
|
kam etwas anderes an.
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
// das `artist`-Feld der Art Tracks vorliegt) und spielt dann die Kaskadenstufen lokal durch —
|
// 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.
|
// 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
|
// 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
|
// Die Filterbedingungen sind hier nachgebaut, nicht importiert: geprueft werden soll, was yt-dlp
|
||||||
@@ -55,11 +60,35 @@ const FAELLE = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// ── Suchtreffer holen ─────────────────────────────────────────────────────────
|
// ── 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) {
|
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 => {
|
return new Promise(resolve => {
|
||||||
const felder = '%(duration)s%(channel)s%(artist)s%(title)s';
|
const felder = '%(duration)s%(channel)s%(artist)s%(title)s';
|
||||||
const p = spawn('yt-dlp', [
|
const p = spawn('yt-dlp', [
|
||||||
`ytsearch${n}:${query}`, '--simulate', '--no-warnings', '--ignore-errors',
|
...ziele, '--simulate', '--no-warnings', '--ignore-errors',
|
||||||
'--print', felder,
|
'--print', felder,
|
||||||
]);
|
]);
|
||||||
let aus = '';
|
let aus = '';
|
||||||
@@ -115,13 +144,14 @@ function pruefer(fall) {
|
|||||||
|
|
||||||
const ersterTreffer = (kandidaten, pruefung) => kandidaten.find(pruefung) || null;
|
const ersterTreffer = (kandidaten, pruefung) => kandidaten.find(pruefung) || null;
|
||||||
|
|
||||||
function kaskade(kandidaten, p, reihenfolge) {
|
function kaskade(ytm, yt, p, reihenfolge) {
|
||||||
const stufen = reihenfolge === 'neu'
|
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 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]];
|
: [['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) {
|
for (const [stufe, pruefung] of stufen) {
|
||||||
const t = ersterTreffer(kandidaten, pruefung);
|
// Jede Stufe fragt erst YT Music, dann die gewoehnliche YouTube-Suche — so laeuft die Kaskade.
|
||||||
if (t) return { stufe, treffer: t };
|
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 };
|
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) {
|
for (const fall of FAELLE) {
|
||||||
const query = `${fall.artist} ${fall.titel}`;
|
const query = `${fall.artist} ${fall.titel}`;
|
||||||
process.stdout.write(`\n▶ ${fall.name}\n Suche: "${query}" `);
|
process.stdout.write(`\n▶ ${fall.name}\n Suche: "${query}" `);
|
||||||
const kandidaten = await suche(query, 10);
|
const ytm = await sucheYtm(query, 10);
|
||||||
console.log(`(${kandidaten.length} Treffer)`);
|
const yt = await suche(query, 10);
|
||||||
if (!kandidaten.length) { console.log(' ⚠️ keine Suchtreffer — übersprungen'); continue; }
|
console.log(`(YTM ${ytm.length}, YouTube ${yt.length})`);
|
||||||
|
if (!ytm.length && !yt.length) { console.log(' ⚠️ keine Suchtreffer — übersprungen'); continue; }
|
||||||
|
|
||||||
const p = pruefer(fall);
|
const p = pruefer(fall);
|
||||||
console.log(` Dauerfenster ${p.fenster.min}–${p.fenster.max}s`);
|
console.log(` Dauerfenster ${p.fenster.min}–${p.fenster.max}s`);
|
||||||
const alt = kaskade(kandidaten, p, 'alt');
|
const alt = kaskade(ytm, yt, p, 'alt');
|
||||||
const neu = kaskade(kandidaten, p, 'neu');
|
const neu = kaskade(ytm, yt, p, 'neu');
|
||||||
console.log(` ALT [${alt.stufe}] ${zeig(alt.treffer)}`);
|
console.log(` ALT [${alt.stufe}] ${zeig(alt.treffer)}`);
|
||||||
console.log(` NEU [${neu.stufe}] ${zeig(neu.treffer)}`);
|
console.log(` NEU [${neu.stufe}] ${zeig(neu.treffer)}`);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user