Version 2.0.0 - update of everything
This commit is contained in:
85 files changed
+13065
-2107
No files matched your search
@@ -0,0 +1,155 @@
|
||||
// Test de bout en bout : vrai yt-dlp, vrai YouTube, sans proxy (WARP non démarré => route directe)
|
||||
// Usage : node stream.e2e.js [--no-update] [--no-cookies]
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execFileSync } = require('child_process');
|
||||
const { BACKEND, __glob, isolateData, check, done, req, chopinTmpDirs } = require('./_setup');
|
||||
const dataDir = isolateData('e2e');
|
||||
|
||||
const youtube = req('src/youtube');
|
||||
const { getStream, activeProcesses } = req('src/player/Method/Youtube.js');
|
||||
const warp = req('src/utils/WarpProxy');
|
||||
const { cookies, settings, ytdlp, diagnostics } = youtube;
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const TARGET = 200 * 1024;
|
||||
const LONG_URL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
|
||||
|
||||
function procCount(image) {
|
||||
if (process.platform !== 'win32') return null;
|
||||
try {
|
||||
const out = execFileSync('tasklist', ['/FI', `IMAGENAME eq ${image}`, '/NH'], { encoding: 'utf8', windowsHide: true });
|
||||
return out.split(/\r?\n/).filter(l => l.toLowerCase().startsWith(image.toLowerCase())).length;
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
/**
|
||||
* Lit ~200 Ko puis annule via AbortController
|
||||
*/
|
||||
async function readThenAbort(label, seek, { expectRaw = false, url } = {}) {
|
||||
console.log(`\n# ${label}`);
|
||||
const controller = new AbortController();
|
||||
const progress = [];
|
||||
const t0 = Date.now();
|
||||
const song = { id: 'e2e', title: 'e2e', url: url || settings.get().testUrl, type: 'youtube' };
|
||||
const stream = await getStream(song, seek, { signal: controller.signal, guildId: 'e2e-guild', onProgress: (p) => progress.push(p) });
|
||||
const tStream = Date.now() - t0;
|
||||
console.log(` getStream -> ${stream ? 'flux' : 'null'} en ${tStream} ms, progression : ${JSON.stringify(progress)}`);
|
||||
console.log(` activeProcesses pendant la lecture : ${JSON.stringify([...activeProcesses.entries()].map(([g, p]) => ({ g, route: p.route, identity: p.identity, pid: !!p.pid })))}`);
|
||||
if (!stream) {
|
||||
console.log(' diagnostics :', JSON.stringify(diagnostics.getRecent(4), null, 0));
|
||||
return { ok: false };
|
||||
}
|
||||
check(`${label} : streamType ${expectRaw ? '"raw"' : 'conteneur'}`, expectRaw ? stream.streamType === 'raw' : stream.streamType === undefined);
|
||||
|
||||
const chunks = [];
|
||||
let total = 0;
|
||||
await new Promise((resolve) => {
|
||||
const timer = setTimeout(resolve, 60000);
|
||||
stream.on('data', (c) => {
|
||||
if (total >= TARGET) return;
|
||||
chunks.push(c);
|
||||
total += c.length;
|
||||
if (total >= TARGET) { clearTimeout(timer); resolve(); }
|
||||
});
|
||||
stream.on('close', () => { clearTimeout(timer); resolve(); });
|
||||
stream.on('error', () => {});
|
||||
});
|
||||
const buf = Buffer.concat(chunks);
|
||||
console.log(` ${total} octets lus en ${Date.now() - t0} ms`);
|
||||
check(`${label} : ~200 Ko reçus`, total >= TARGET, total);
|
||||
|
||||
if (expectRaw) {
|
||||
// PCM s16le stéréo : vérifie qu'il y a du signal (pas que des zéros)
|
||||
let sumSq = 0, n = 0, nonZero = 0;
|
||||
for (let i = 0; i + 1 < buf.length; i += 2) {
|
||||
const v = buf.readInt16LE(i);
|
||||
sumSq += v * v; n++;
|
||||
if (v !== 0) nonZero++;
|
||||
}
|
||||
const rms = Math.sqrt(sumSq / Math.max(1, n));
|
||||
console.log(` PCM : ${n} échantillons, RMS ${rms.toFixed(1)}, ${(100 * nonZero / Math.max(1, n)).toFixed(1)} % non nuls, ~${(n / 2 / 48000).toFixed(2)} s d'audio`);
|
||||
check(`${label} : PCM non silencieux`, rms > 10 && nonZero > n / 10);
|
||||
} else {
|
||||
// WebM (EBML 1A45DFA3) ou MP4 (ftyp)
|
||||
const magic = buf.subarray(0, 4).toString('hex');
|
||||
const isWebm = magic === '1a45dfa3';
|
||||
const isMp4 = buf.subarray(4, 8).toString() === 'ftyp';
|
||||
console.log(` conteneur : ${isWebm ? 'WebM' : isMp4 ? 'MP4' : 'inconnu (' + magic + ')'}`);
|
||||
check(`${label} : conteneur audio reconnu`, isWebm || isMp4);
|
||||
}
|
||||
|
||||
controller.abort();
|
||||
// Processus tués et copies de cookies supprimées
|
||||
let waited = 0;
|
||||
while ((ytdlp.activeCount() > 0 || activeProcesses.size > 0 || cookies.liveCopyCount() > 0) && waited < 5000) {
|
||||
await sleep(100); waited += 100;
|
||||
}
|
||||
console.log(` après abort (${waited} ms) : processus suivis ${ytdlp.activeCount()}, activeProcesses ${activeProcesses.size}, copies de cookies ${cookies.liveCopyCount()}, stream détruit ${stream.destroyed}`);
|
||||
check(`${label} : tous les processus terminés`, ytdlp.activeCount() === 0 && activeProcesses.size === 0);
|
||||
check(`${label} : flux détruit`, stream.destroyed === true);
|
||||
check(`${label} : aucune copie chopin-ytc-* restante`, chopinTmpDirs().length === 0 && cookies.liveCopyCount() === 0, chopinTmpDirs());
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
console.log('routes WARP :', JSON.stringify(warp.getRoutes().map(r => r ? 'warp' : 'direct')));
|
||||
const bin = ytdlp.resolveBinary();
|
||||
const ff = ytdlp.resolveFfmpeg();
|
||||
console.log(`yt-dlp : ${bin.source} ${path.relative(BACKEND, bin.path) || bin.path}`);
|
||||
console.log(`ffmpeg : ${ff?.source} ${ff ? path.relative(BACKEND, ff.path) : '-'}`);
|
||||
console.log(`version : ${await ytdlp.getVersion({ force: true })}`);
|
||||
const before = { ytdlp: procCount('yt-dlp.exe'), ffmpeg: procCount('ffmpeg.exe') };
|
||||
console.log('processus système avant :', JSON.stringify(before));
|
||||
|
||||
if (!args.includes('--no-update')) {
|
||||
console.log('\n# Mise à jour (yt-dlp -U)');
|
||||
const up = await ytdlp.update({ reason: 'test e2e' });
|
||||
console.log(' ', JSON.stringify({ ok: up.ok, updatable: up.updatable, before: up.before, after: up.after, message: up.message }));
|
||||
console.log(' sortie :', up.output.split('\n').slice(-4).join(' | '));
|
||||
check('update : version connue après', Boolean(up.after));
|
||||
}
|
||||
|
||||
await readThenAbort('Lecture sans seek (anonyme, directe)', 0);
|
||||
// testUrl (Me at the zoo) ne dure que 19 s : seek 30 s sur une vidéo plus longue
|
||||
await readThenAbort('Seek 30 s (natif : yt-dlp -> ffmpeg -> PCM brut)', 30, { expectRaw: true, url: LONG_URL });
|
||||
|
||||
console.log('\n# Seek au-delà de la fin (testUrl, 19 s) : un seul essai, sans clients de repli');
|
||||
const n0 = diagnostics.getRecent(100).length;
|
||||
const past = await getStream({ id: 'e2e', title: 'e2e', url: settings.get().testUrl, type: 'youtube' }, 30, { guildId: 'e2e-guild' });
|
||||
const tries = diagnostics.getRecent(100).length - n0;
|
||||
check('seek après la fin : null en 1 tentative (final)', past === null && tries === 1 && diagnostics.getRecent(1)[0].cls === 'EMPTY_DOWNLOAD', { tries, last: diagnostics.getRecent(1)[0] });
|
||||
await sleep(2500);
|
||||
check('seek après la fin : processus terminés', ytdlp.activeCount() === 0, ytdlp.activeCount());
|
||||
|
||||
if (!args.includes('--no-cookies')) {
|
||||
// Anciens cookies (fichiers réels en lecture seule) importés dans le dossier isolé, mode "always"
|
||||
__glob.COOKIES = path.join(BACKEND, 'data', 'cookies.txt');
|
||||
__glob.COOKIES_JSON = path.join(BACKEND, 'data', 'cookies.json');
|
||||
const mig = cookies.migrateLegacy();
|
||||
console.log('\n# Cookies : migration', JSON.stringify(mig), '-> valides :', cookies.isValid());
|
||||
if (cookies.isValid()) {
|
||||
settings.update({ cookiesMode: 'always' }, { username: 'e2e' });
|
||||
await readThenAbort('Lecture avec compte (mode always, copie temporaire des cookies)', 0);
|
||||
const st = cookies.getStatus();
|
||||
console.log(' statut cookies après lecture :', JSON.stringify({ valid: st.valid, invalidatedAt: !!st.invalidatedAt, invalidReason: st.invalidReason }));
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n# Diagnostics');
|
||||
for (const e of diagnostics.getRecent(20).reverse()) {
|
||||
console.log(` ${e.outcome.padEnd(7)} ${e.route}/${e.identity}${e.clients ? '/' + e.clients : ''}${e.seek ? '/' + e.seek : ''} ${e.ms} ms ${e.cls || ''} ${e.message || ''}`);
|
||||
}
|
||||
const s = diagnostics.getSummary();
|
||||
console.log(' résumé :', JSON.stringify({ ok: s.ok, failed: s.failed, aborted: s.aborted, byClass: s.byClass, consecutiveBlocks: s.consecutiveBlocks }));
|
||||
await sleep(1000);
|
||||
console.log(' getStatus().activeProcesses :', youtube.getStatus().activeProcesses);
|
||||
const after = { ytdlp: procCount('yt-dlp.exe'), ffmpeg: procCount('ffmpeg.exe') };
|
||||
console.log('processus système après :', JSON.stringify(after));
|
||||
check('aucun yt-dlp.exe / ffmpeg.exe orphelin', after.ytdlp === null || (after.ytdlp <= before.ytdlp && after.ffmpeg <= before.ffmpeg), { before, after });
|
||||
check('aucune copie chopin-ytc-* (fin)', chopinTmpDirs().length === 0, chopinTmpDirs());
|
||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
||||
done();
|
||||
// Timers unref : le process se termine seul
|
||||
})().catch((e) => { console.error(e); process.exitCode = 1; });
|
||||
Reference in new issue
Block a user