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>
101 lines
5.8 KiB
TypeScript
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'])
|
|
})
|
|
})
|