Files
rippy/rippy-windows/test/messung.spuren.test.ts
HitonabiandClaude Opus 5 e7e33e9021 feat(v5): jede Spur einzeln anhaken — Codec und Kanäle je Tonspur, Untertitel genauso
WAS: Der Auswahl-Dialog zeigt je Titel jede Ton- und Untertitelspur, die
der Info-Lauf gemessen hat („German · DTS-HD MA · Surround 5.1", „German ·
DD · Stereo (Kommentar)", „German · PGS (nur erzwungene)"), vorbelegt wie
bisher (je Wunschsprache die erste Tonspur, alle Untertitel der Wunsch-
sprachen) — angehakt wird einzeln. Die Untertitel-Vorwahl je Extra ist
jetzt eine Spur. Der Parser liest dazu SINFO-Attribute 2 (Anordnung),
22 (Flags) und markiert abgeleitete Spuren (Bit 0x800, an der Kizuna-
Disc gemessen): DTS-Kerne werden nicht angeboten, „nur erzwungene"-
Untertitel schon. Die Ablage scannt die Roh-Datei und findet die
gewählten MakeMKV-Spuren gestuft wieder (Sprache + Codec-Familie +
Kanäle → … → Sprache), HandBrake bekommt `--audio 1,3 --subtitle 2,1
--subtitle-default=1`; Fehlendes wird gemeldet, scheitert der Scan,
gelten die Sprachen der gewählten Spuren als Liste. MakeMKVs Auswahl-
regel lässt Stereo-Doppel jetzt stehen (nur -sel:havecore); Rippys alte
Regel wird in der Registry ersetzt, eine fremde bleibt.
Dazu F31 (Durchsicht): `--aencoder copy` ohne `--audio-copy-mask`
verengt HandBrakeCLI 1.11.2 auf „copy:aac" — AC3/DTS/TrueHD wurden seit
5.0 in AAC umgerechnet. Beide Wege setzen die Maske jetzt ausdrücklich
(gemessen 12.09.2026: AC3 bleibt AC3, Passthru-Zeile nennt alle Codecs).
WARUM: Commander-Entscheid 12.09.2026 („Jede Spur einzeln anhaken") —
bisher ließ sich je Sprache nur „alles oder nichts" wählen, und der
Kommentar-Ton fehlte in der Roh-Datei (havemulti). Gemessen am
mitgelieferten HandBrakeCLI mit ffmpeg-gebauter Quelle
(test/messung.spuren.test.ts, RIPPY_MESSUNG=1): --audio 1,3 → jpn und
deu-Kommentar, --subtitle 2,1 --subtitle-default=1 → eng (default) vor
deu, --subtitle none → keine Spur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 22:11:27 +02:00

101 lines
5.8 KiB
TypeScript

// 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'])
})
})