// MESSUNG an einer ECHTEN Roh-Datei: Welche Ton- und Untertitelspuren hat // MakeMKV wirklich in die MKV geschrieben? (5.4.0, Regel D) // // RIPPY_MESSUNG_MKV="E:\Rippy\roh\bluray-…\title_t00.mkv" npx vitest run test/messung.spuren.test.ts --reporter=verbose // // Der Anlass: Der Auswahl-Dialog zeigt die Sprachen, die der Info-Lauf an // der DISC gemessen hat. Ob sie alle in der Datei landen, entscheidet // MakeMKVs Auswahlregel (werkzeuge/spurauswahl.ts). Diese Messung fragt die // Datei selbst — über denselben HandBrake-Scan, den die Kompression benutzt // (komprimieren/scan.ts). Läuft nur auf Verlangen und nur unter Windows. import { describe, expect, it } from 'vitest' const datei = process.env.RIPPY_MESSUNG_MKV ?? '' const messen = datei.length > 0 && process.platform === 'win32' describe.skipIf(!messen)('Spuren einer echten Roh-Datei', () => { it('HandBrake-Scan liefert Ton- und Untertitelspuren mit Sprachcode', { timeout: 180_000 }, async () => { const { finden } = await import('../src/kern/werkzeuge/katalog') const { spurenScannen } = await import('../src/kern/komprimieren/scan') const handbrake = finden('handbrake', '') expect(handbrake.length, 'HandBrakeCLI nicht gefunden').toBeGreaterThan(0) const start = Date.now() const spuren = await spurenScannen(handbrake, datei) console.log(`MESSUNG Scan: ${((Date.now() - start) / 1000).toFixed(1)} s — ${datei}`) expect(spuren, 'Scan lieferte nichts').not.toBeNull() console.log(`MESSUNG Dauer: ${Math.round(spuren!.dauerS / 60)} min`) for (const a of spuren!.audio) console.log(`MESSUNG Ton ${a.nr}: ${a.lang} — ${a.beschreibung}`) for (const u of spuren!.untertitel) console.log(`MESSUNG Untertitel ${u.nr}: ${u.lang} — ${u.beschreibung}`) expect(spuren!.audio.length).toBeGreaterThan(0) }) }) // ── 5.7.0: Spur-Schalter am mitgelieferten HandBrakeCLI ──────────────────── // Baut sich die Quelle selbst (ffmpeg muss im PATH sein): jpn 5.1 / deu 2.0 / // deu 2.0 „Kommentar" als AC3, deu/eng SRT — und prüft die Ausgabe wieder // über HandBrakes eigenen Scan. Anlass: `--audio 1,3`, `--subtitle 2,1`, // `--subtitle-default=1` und die Kopiermaske (F31: ohne `--audio-copy-mask` // wurde AC3 zu AAC). Läuft nur mit RIPPY_MESSUNG=1 unter Windows. // // RIPPY_MESSUNG=1 npx vitest run test/messung.spuren.test.ts --reporter=verbose import { spawnSync } from 'node:child_process' import { mkdtempSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' const spurMessung = process.env.RIPPY_MESSUNG === '1' && process.platform === 'win32' describe.skipIf(!spurMessung)('Spur-Schalter am echten HandBrakeCLI (5.7.0)', () => { it('--audio 1,3 --subtitle 2,1 --subtitle-default=1 mit Kopiermaske: AC3 bleibt AC3, eng vor deu', { timeout: 10 * 60_000 }, async () => { const ffmpeg = spawnSync('ffmpeg', ['-version'], { encoding: 'utf8', windowsHide: true }) if (ffmpeg.status !== 0) { console.log('MESSUNG übersprungen: kein ffmpeg im PATH') return } const { finden } = await import('../src/kern/werkzeuge/katalog') const { buildHandbrakeArgs } = await import('../src/kern/komprimieren/handbrake') const { spurenScannen } = await import('../src/kern/komprimieren/scan') const handbrake = finden('handbrake', '') expect(handbrake.length, 'HandBrakeCLI nicht gefunden').toBeGreaterThan(0) const ordner = mkdtempSync(join(tmpdir(), 'rippy-spuren-')) const deu = join(ordner, 'deu.srt') const eng = join(ordner, 'eng.srt') writeFileSync(deu, '1\n00:00:00,500 --> 00:00:02,000\nHallo\n\n') writeFileSync(eng, '1\n00:00:00,500 --> 00:00:02,000\nHello\n\n') const quelle = join(ordner, 'quelle.mkv') const bau = spawnSync( 'ffmpeg', [ '-y', '-loglevel', 'error', '-f', 'lavfi', '-i', 'testsrc=size=320x240:rate=25', '-f', 'lavfi', '-i', 'sine=frequency=440:sample_rate=48000', '-f', 'lavfi', '-i', 'sine=frequency=660:sample_rate=48000', '-f', 'lavfi', '-i', 'sine=frequency=880:sample_rate=48000', '-i', deu, '-i', eng, '-t', '3', '-map', '0:v', '-map', '1:a', '-map', '2:a', '-map', '3:a', '-map', '4:s', '-map', '5:s', '-c:v', 'libx264', '-preset', 'ultrafast', '-c:a', 'ac3', '-ac:a:0', '6', '-ac:a:1', '2', '-ac:a:2', '2', '-c:s', 'srt', '-metadata:s:a:0', 'language=jpn', '-metadata:s:a:1', 'language=deu', '-metadata:s:a:2', 'language=deu', '-metadata:s:a:2', 'title=Kommentar', '-metadata:s:s:0', 'language=deu', '-metadata:s:s:1', 'language=eng', quelle, ], { encoding: 'utf8', windowsHide: true, timeout: 120_000 }, ) expect(bau.status, `ffmpeg: ${bau.stderr}`).toBe(0) const ausgabe = join(ordner, 'ausgabe.mkv') const args = buildHandbrakeArgs({ eingabe: quelle, ausgabe, preset: 'H.265 MKV 1080p30', audioNr: [1, 3], untertitelNr: [2, 1], untertitelStandardNr: 1 }) const lauf = spawnSync(handbrake, args, { encoding: 'utf8', windowsHide: true, timeout: 8 * 60_000 }) expect(lauf.status, `HandBrake: ${(lauf.stderr ?? '').slice(-600)}`).toBe(0) const passthru = /Auto Passthru: allowed codecs are ([^\n]*)/.exec(lauf.stderr ?? '') console.log(`MESSUNG Passthru: ${passthru?.[1] ?? '(Zeile fehlt)'}`) const spuren = await spurenScannen(handbrake, ausgabe) expect(spuren).not.toBeNull() for (const a of spuren!.audio) console.log(`MESSUNG Ton ${a.nr}: ${a.lang} ${a.codec} ${a.kanaele}ch — ${a.beschreibung}`) for (const u of spuren!.untertitel) console.log(`MESSUNG Untertitel ${u.nr}: ${u.lang} ${u.codec} — ${u.beschreibung}`) expect(spuren!.audio.map((a) => [a.lang, a.codec, a.kanaele])).toEqual([ ['jpn', 'ac3', 6], ['deu', 'ac3', 2], ]) expect(spuren!.untertitel.map((u) => u.lang)).toEqual(['eng', 'deu']) }) })