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>
This commit is contained in:
Hitonabi
2026-09-12 22:11:27 +02:00
co-authored by Claude Opus 5
parent fc2d16ca38
commit e7e33e9021
18 changed files with 1098 additions and 60 deletions
+89 -1
View File
@@ -199,7 +199,7 @@ describe('komprimierenUndAblegen', () => {
const ergebnis = await komprimierenUndAblegen(
auftrag({ rohOrdner: roh, dateien }),
umgebung(ablage, {
spurenScannen: async () => ({ audio: [{ nr: 1, lang: 'deu', beschreibung: 'Deutsch' }], untertitel: [], dauerS: 3167 }),
spurenScannen: async () => ({ audio: [{ nr: 1, lang: 'deu', beschreibung: 'Deutsch', codec: 'dtshd', kanaele: 6 }], untertitel: [], dauerS: 3167 }),
}),
() => {},
KEIN_ABBRUCH,
@@ -395,3 +395,91 @@ describe('komprimierenUndAblegen — Durchsicht 5.7.0', () => {
expect(ergebnis.dateien.map((d) => d.split(/[\\/]/).pop())).toEqual(['title_t00 (2).mkv'])
})
})
// ── 5.7.0: Spuren einzeln ──────────────────────────────────────────────────
describe('Spuren einzeln (5.7.0)', () => {
const jpnHd = { nr: 1, lang: 'jpn', sprache: 'Japanese', codec: 'DTS-HD MA', kanaele: '6', layout: 'Surround 5.1', beschreibung: '', abgeleitet: false }
const deuKommentar = { nr: 4, lang: 'deu', sprache: 'German', codec: 'DD', kanaele: '2', layout: 'Stereo', beschreibung: 'DD Stereo German (Kommentar)', abgeleitet: false }
const deuPgs = { nr: 5, lang: 'deu', sprache: 'German', codec: 'PGS', kanaele: '', layout: '', beschreibung: 'PGS German', abgeleitet: false }
const engPgs = { nr: 7, lang: 'eng', sprache: 'English', codec: 'PGS', kanaele: '', layout: '', beschreibung: 'PGS English', abgeleitet: false }
const scan = {
audio: [
{ nr: 1, lang: 'jpn', beschreibung: '', codec: 'dtshd', kanaele: 6 },
{ nr: 2, lang: 'deu', beschreibung: '', codec: 'ac3', kanaele: 6 },
{ nr: 3, lang: 'deu', beschreibung: '', codec: 'ac3', kanaele: 2 },
],
untertitel: [
{ nr: 1, lang: 'deu', beschreibung: '', codec: 'PGS', kanaele: 0 },
{ nr: 2, lang: 'eng', beschreibung: '', codec: 'PGS', kanaele: 0 },
],
dauerS: 3167,
}
it('gewählte Spuren erreichen HandBrake als Nummern — japanischer Ton stellt die deutschen Untertitel vor und wählt sie vor', async () => {
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
dateien[0].audioSprachen = ['jpn', 'deu']
dateien[0].untertitelSprachen = ['eng', 'deu']
dateien[0].audioSpuren = [jpnHd, deuKommentar]
dateien[0].untertitelSpuren = [engPgs, deuPgs]
const ergebnis = await komprimierenUndAblegen(
auftrag({ rohOrdner: roh, dateien }),
umgebung(ablage, { spurenScannen: async () => scan }),
() => {},
KEIN_ABBRUCH,
)
expect(ergebnis.status).toBe('fertig')
expect(hb.aufrufe[0].audioNr).toEqual([1, 3])
expect(hb.aufrufe[0].untertitelNr).toEqual([1, 2])
expect(hb.aufrufe[0].untertitelStandardNr).toBe(1)
expect(ergebnis.hinweise.join(' ')).not.toContain('NICHT in der Roh-Datei')
})
it('eine gewählte Spur fehlt in der Roh-Datei → Hinweis, der Rest läuft', async () => {
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
const fra = { ...jpnHd, nr: 9, lang: 'fra', sprache: 'French' }
dateien[0].audioSpuren = [fra, deuKommentar]
dateien[0].untertitelSpuren = []
const ergebnis = await komprimierenUndAblegen(
auftrag({ rohOrdner: roh, dateien }),
umgebung(ablage, { spurenScannen: async () => scan }),
() => {},
KEIN_ABBRUCH,
)
expect(hb.aufrufe[0].audioNr).toEqual([3])
expect(hb.aufrufe[0].untertitelNr).toEqual([])
expect(ergebnis.hinweise.join(' ')).toContain('Tonspur French DTS-HD MA ist NICHT in der Roh-Datei')
})
it('Scan der Roh-Datei scheitert → Sprachen der gewählten Spuren als Liste (wie bisher)', async () => {
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
dateien[0].audioSpuren = [jpnHd]
dateien[0].untertitelSpuren = [deuPgs]
const ergebnis = await komprimierenUndAblegen(
auftrag({ rohOrdner: roh, dateien }),
umgebung(ablage, { spurenScannen: async () => null }),
() => {},
KEIN_ABBRUCH,
)
expect(hb.aufrufe[0].audioNr).toBeUndefined()
expect(hb.aufrufe[0].audioSprachen).toEqual(['jpn'])
expect(hb.aufrufe[0].untertitelSprachen).toEqual(['deu'])
expect(hb.aufrufe[0].untertitelStandard).toBe(true)
expect(ergebnis.hinweise.join(' ')).toContain('ließen sich nicht messen')
})
it('keine gewählte Tonspur findbar → Sprachlisten statt stummer Datei', async () => {
const { ablage, roh, dateien } = aufbau(['title_t00.mkv'])
const fra = { ...jpnHd, nr: 9, lang: 'fra', sprache: 'French' }
dateien[0].audioSpuren = [fra]
dateien[0].untertitelSpuren = [deuPgs]
const ergebnis = await komprimierenUndAblegen(
auftrag({ rohOrdner: roh, dateien }),
umgebung(ablage, { spurenScannen: async () => scan }),
() => {},
KEIN_ABBRUCH,
)
expect(hb.aufrufe[0].audioNr).toBeUndefined()
expect(hb.aufrufe[0].audioSprachen).toEqual(['fra'])
expect(ergebnis.hinweise.join(' ')).toContain('keine der gewählten Tonspuren gefunden')
})
})