fix(v5): 5.6.1 — Untertitel-Spuren wieder erkannt: Stream-Typ per Code (6203), nicht per lokalisiertem Wort

Commander-Fund 03.09.2026 (Digimon Adventure: Last Evolution Kizuna): im
Auswahl-Dialog fehlten die UNTERTITEL-Chips. Gemessen mit makemkvcon64 -r
info auf diesem PC: MakeMKV schreibt den Stream-Typ in seiner Anzeigesprache
("Untertitel"), der Parser prüfte auf "Subtitles" (Messung vom 26.07. an
einem englischen MakeMKV). "Audio" heißt zufällig gleich, deshalb fiel nur
der Untertitel-Weg aus. Jetzt entscheidet der Typ-Code im vierten Feld
(6201 Video, 6202 Audio, 6203 Untertitel); das Wort bleibt Notnagel.

Test rip-parser-561 mit den gemessenen Zeilen (deu×2/jpn×2 Ton, deu×3
Untertitel, darunter "PGS German (nur erzwungene)"); die volle Messung
liegt unter beweise/messungen/2026-09-03-makemkvcon-info-kizuna.txt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-03 19:40:22 +02:00
co-authored by Claude Fable 5.1
parent 8f671ff160
commit 687173430c
17 changed files with 484 additions and 10 deletions
+12
View File
@@ -1,3 +1,15 @@
Version 5.6.1
Untertitel-Spuren werden wieder erkannt.
MakeMKV schreibt den Typ eines Streams in seiner Anzeigesprache - auf einem
deutsch eingestellten MakeMKV "Untertitel" statt "Subtitles". Rippy prueft
seit 5.4.0 auf das englische Wort und fand deshalb keine Untertitel mehr
(Ton heisst in beiden Sprachen "Audio", darum fiel es dort nicht auf).
Jetzt zaehlt der Typ-Code (6202 Ton, 6203 Untertitel), der ist
sprachunabhaengig. Gemessen an der Blu-ray "Digimon Adventure: Last
Evolution Kizuna": drei deutsche PGS-Spuren, darunter eine nur fuer
erzwungene Untertitel.
Version 5.6.0
Mehrere Filme auf einer Disc.
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "rippy",
"version": "5.6.0",
"version": "5.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rippy",
"version": "5.6.0",
"version": "5.6.1",
"license": "UNLICENSED",
"dependencies": {
"electron-updater": "^6.8.9",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "rippy",
"productName": "Rippy",
"version": "5.6.0",
"version": "5.6.1",
"description": "Rippy v5 — Disc-Ripping als eigenständiges Windows-Programm (KONZEPT-WINDOWS.md)",
"main": "out/haupt/index.js",
"author": "KrBrZ",
+24 -7
View File
@@ -114,9 +114,21 @@ export function parseTitelInfo(ausgabe: string): TitelInfo[] {
// ⚠️ Die Sprache steht in 3/4 — NICHT in 28/29: Die tragen auf JEDEM
// Stream „eng"/„English" (MakeMKVs Anzeigesprache). Wer 28 nimmt, hält
// jede Disc für englisch.
// ⚠️ 5.6.1 (Commander-Fund 03.09.2026, Digimon Last Evolution Kizuna): Auch
// der TEXT von Attribut 1 ist MakeMKVs Anzeigesprache — deutsch eingestellt
// schreibt es `SINFO:1,5,1,6203,"Untertitel"`, nicht „Subtitles". „Audio"
// heißt zufällig in beiden Sprachen gleich, deshalb fehlten NUR die
// Untertitel. Verlässlich ist der CODE im vierten Feld: 6201 Video,
// 6202 Audio, 6203 Untertitel (gemessen an makemkvcon64 auf diesem PC).
export const STREAM_CODE_VIDEO = 6201
export const STREAM_CODE_AUDIO = 6202
export const STREAM_CODE_UNTERTITEL = 6203
export interface StreamInfo {
nr: number
typ: string
/** Typ-Code aus Attribut 1 (6201/6202/6203), 0 wenn keiner dastand. */
typCode: number
lang: string
sprache: string
codec: string
@@ -129,7 +141,7 @@ export interface TitelStreams {
untertitel: StreamInfo[]
}
const SINFO_ZEILE = /^SINFO:(\d+),(\d+),(\d+),\d+,"(.*)"\s*$/
const SINFO_ZEILE = /^SINFO:(\d+),(\d+),(\d+),(\d+),"(.*)"\s*$/
export function parseStreamInfo(ausgabe: string): Map<number, TitelStreams> {
const titel = new Map<number, Map<number, StreamInfo>>()
@@ -139,7 +151,8 @@ export function parseStreamInfo(ausgabe: string): Map<number, TitelStreams> {
const tNr = Number(treffer[1])
const sNr = Number(treffer[2])
const attr = Number(treffer[3])
const wert = treffer[4]
const code = Number(treffer[4])
const wert = treffer[5]
let streams = titel.get(tNr)
if (streams === undefined) {
streams = new Map()
@@ -147,10 +160,13 @@ export function parseStreamInfo(ausgabe: string): Map<number, TitelStreams> {
}
let eintrag = streams.get(sNr)
if (eintrag === undefined) {
eintrag = { nr: sNr, typ: '', lang: '', sprache: '', codec: '', kanaele: '', beschreibung: '' }
eintrag = { nr: sNr, typ: '', typCode: 0, lang: '', sprache: '', codec: '', kanaele: '', beschreibung: '' }
streams.set(sNr, eintrag)
}
if (attr === 1) eintrag.typ = wert
if (attr === 1) {
eintrag.typ = wert
eintrag.typCode = code
}
else if (attr === 3) eintrag.lang = wert
else if (attr === 4) eintrag.sprache = wert
else if (attr === 6) eintrag.codec = wert
@@ -164,9 +180,10 @@ export function parseStreamInfo(ausgabe: string): Map<number, TitelStreams> {
const untertitel: StreamInfo[] = []
for (const sNr of [...streams.keys()].sort((a, b) => a - b)) {
const s = streams.get(sNr)!
// MakeMKV schreibt „Subtitles" (englisch, Plural) — gemessen.
if (s.typ === 'Audio') audio.push(s)
else if (s.typ.startsWith('Subtitle')) untertitel.push(s)
// Der Code entscheidet (5.6.1); das Wort nur als Notnagel für Ausgaben
// ohne Code („Audio", „Subtitles", „Untertitel").
if (s.typCode === STREAM_CODE_AUDIO || (s.typCode === 0 && s.typ === 'Audio')) audio.push(s)
else if (s.typCode === STREAM_CODE_UNTERTITEL || (s.typCode === 0 && (s.typ.startsWith('Subtitle') || s.typ.startsWith('Untertitel')))) untertitel.push(s)
}
ergebnis.set(tNr, { audio, untertitel })
}
+70
View File
@@ -0,0 +1,70 @@
// 5.6.1 — Commander-Fund vom 03.09.2026: Bei der Blu-ray „Digimon Adventure:
// Last Evolution Kizuna" fehlten im Auswahl-Dialog die Untertitel-Chips,
// obwohl die Disc drei deutsche PGS-Spuren hat. Gemessen mit
// makemkvcon64 -r info auf diesem PC: MakeMKV (deutsch eingestellt) schreibt
// den Stream-Typ als „Untertitel", der Parser prüfte auf „Subtitles". Der
// Typ-Code im vierten Feld (6203) ist sprachunabhängig.
import { describe, expect, it } from 'vitest'
import { STREAM_CODE_AUDIO, STREAM_CODE_UNTERTITEL, parseStreamInfo, sprachenFuerTitel } from '../src/kern/rip/parser'
// Wörtlich aus der Messung (gekürzt auf Typ, Sprache, Name, Beschreibung).
const KIZUNA = [
'SINFO:1,0,1,6201,"Video"',
'SINFO:1,0,6,0,"Mpeg4"',
'SINFO:1,1,1,6202,"Audio"',
'SINFO:1,1,3,0,"deu"',
'SINFO:1,1,4,0,"German"',
'SINFO:1,1,30,0,"DTS-HD MA Surround 5.1 German"',
'SINFO:1,2,1,6202,"Audio"',
'SINFO:1,2,3,0,"deu"',
'SINFO:1,2,4,0,"German"',
'SINFO:1,3,1,6202,"Audio"',
'SINFO:1,3,3,0,"jpn"',
'SINFO:1,3,4,0,"Japanese"',
'SINFO:1,4,1,6202,"Audio"',
'SINFO:1,4,3,0,"jpn"',
'SINFO:1,4,4,0,"Japanese"',
'SINFO:1,5,1,6203,"Untertitel"',
'SINFO:1,5,3,0,"deu"',
'SINFO:1,5,4,0,"German"',
'SINFO:1,5,30,0,"PGS German"',
'SINFO:1,6,1,6203,"Untertitel"',
'SINFO:1,6,3,0,"deu"',
'SINFO:1,6,4,0,"German"',
'SINFO:1,6,22,0,"6144"',
'SINFO:1,6,30,0,"PGS German (nur erzwungene)"',
'SINFO:1,7,1,6203,"Untertitel"',
'SINFO:1,7,3,0,"deu"',
'SINFO:1,7,4,0,"German"',
].join('\n')
describe('parseStreamInfo — Stream-Typ per Code, nicht per Wort (5.6.1)', () => {
it('deutsches MakeMKV schreibt „Untertitel": die drei PGS-Spuren werden erkannt', () => {
const streams = parseStreamInfo(KIZUNA).get(1)!
expect(streams.audio.map((s) => s.lang)).toEqual(['deu', 'deu', 'jpn', 'jpn'])
expect(streams.untertitel.map((s) => s.lang)).toEqual(['deu', 'deu', 'deu'])
expect(streams.untertitel[0].typCode).toBe(STREAM_CODE_UNTERTITEL)
expect(streams.audio[0].typCode).toBe(STREAM_CODE_AUDIO)
expect(streams.untertitel[1].beschreibung).toBe('PGS German (nur erzwungene)')
})
it('für den Dialog: deu×2 Ton, jpn×2 Ton, deu×3 Untertitel', () => {
const sprachen = sprachenFuerTitel(parseStreamInfo(KIZUNA).get(1))
expect(sprachen.audio).toEqual([
{ lang: 'deu', sprache: 'German', spuren: 2 },
{ lang: 'jpn', sprache: 'Japanese', spuren: 2 },
])
expect(sprachen.untertitel).toEqual([{ lang: 'deu', sprache: 'German', spuren: 3 }])
})
it('jede Anzeigesprache: der Code zählt — französisch, englisch, und ohne Code das Wort als Notnagel', () => {
expect(parseStreamInfo('SINFO:0,1,1,6203,"Sous-titres"\nSINFO:0,1,3,0,"fra"').get(0)!.untertitel).toHaveLength(1)
expect(parseStreamInfo('SINFO:0,1,1,6203,"Subtitles"\nSINFO:0,1,3,0,"eng"').get(0)!.untertitel).toHaveLength(1)
expect(parseStreamInfo('SINFO:0,1,1,0,"Subtitles"\nSINFO:0,1,3,0,"eng"').get(0)!.untertitel).toHaveLength(1)
expect(parseStreamInfo('SINFO:0,1,1,0,"Untertitel"\nSINFO:0,1,3,0,"deu"').get(0)!.untertitel).toHaveLength(1)
expect(parseStreamInfo('SINFO:0,1,1,0,"Audio"\nSINFO:0,1,3,0,"deu"').get(0)!.audio).toHaveLength(1)
})
it('Video bleibt draußen', () => {
const streams = parseStreamInfo('SINFO:0,0,1,6201,"Video"\nSINFO:0,0,3,0,"und"').get(0)!
expect(streams.audio).toHaveLength(0)
expect(streams.untertitel).toHaveLength(0)
})
})