Files
rippy/rippy-windows/test/lebenszeichen-571.test.ts
T
HitonabiandClaude Opus 5 eb54c59141
Ampel / ampel (push) Successful in 1m20s
fix(v5): 5.7.1 — Titel lesen wartet, solange MakeMKV arbeitet; Grenze gilt für Stille, Dialog zeigt Schritt, Prozent und Meldung
WAS: Der Info-Lauf verlangt --progress=-same und wertet die Ausgabe
zeilenweise aus (PRGC/PRGT/PRGV/MSG). titelInfoLesen beendet makemkvcon
nur noch, wenn er so lange STILL ist, wie die Einstellung erlaubt
(Vorgabe 15 min) — jede Fortschritts- oder Meldungszeile setzt die Uhr
zurück. Kündigt MakeMKV den VOB-Scan an („IFO-Datei … beschädigt, die
VOB-Datei muss gescannt werden"), gilt nur noch eine Obergrenze von 4 h.
Der Dialog zeigt während des Lesens Schritt, Prozent und letzte Meldung
(titelLaufZwischenstand), beim VOB-Scan mit Erklärung (20–60 min, im
MakeMKV-Programm genauso). Bricht Rippy ab, stehen die letzten acht
Zeilen von makemkvcon im Fehler und im Protokoll. Einstellungs-Text
„Titel lesen — ohne Lebenszeichen höchstens". Tests mit nachgebautem
makemkvcon (redselig über die Stille-Grenze hinaus, stumm, VOB-Scan mit
Obergrenze). Version 5.7.1, Änderungsnotizen.
WARUM: Commander 12.09.2026 spät: „Der Patriot" (DVD, IFO für VTS #1
beschädigt) scheiterte in 5.7.0 erneut nach 15 Minuten, das MakeMKV-
Programm las die Disc fertig — MakeMKV liest bei so einer Disc die ganze
VOB durch, das dauert so lange wie die Disc braucht. Eine feste
Gesamtgrenze ist dafür immer zu kurz; Stille ist das richtige Maß.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 01:04:08 +02:00

133 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 5.7.1 — Commander-Fund 12.09.2026 spät: „Der Patriot" (DVD, IFO-Datei für
// VTS #1 beschädigt, MakeMKV scannt die VOB-Datei). Rippy 5.7.0 brach nach
// 15 Minuten ab, das MakeMKV-Programm las die Disc fertig. Jetzt gilt die
// Grenze für STILLE: Solange makemkvcon Fortschritt oder Meldungen schickt,
// wartet Rippy — und der Dialog zeigt, was MakeMKV gerade tut.
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { titelLaufZwischenstand } from '../src/kern/dienste/erkennung'
import { buildInfoArgs, titelInfoLesen, VOB_SCAN_HOECHSTENS_MS, type Lebenszeichen } from '../src/kern/rip/makemkv'
import { aufgabeAusPrg, istVobScanMeldung } from '../src/kern/rip/parser'
let wegwerf: string[] = []
afterEach(() => {
for (const o of wegwerf.splice(0)) rmSync(o, { recursive: true, force: true })
})
describe('Fortschritts-Zeilen des Info-Laufs', () => {
it('der Info-Lauf verlangt --progress=-same', () => {
expect(buildInfoArgs('\\\\.\\G:')).toContain('--progress=-same')
})
it('PRGC ist der Schritt, PRGT die Aufgabe — mit Komma und Anführungszeichen im Namen', () => {
expect(aufgabeAusPrg('PRGC:5018,0,"Analysieren von seamless segments"')).toEqual({ art: 'schritt', name: 'Analysieren von seamless segments' })
expect(aufgabeAusPrg('PRGT:5017,0,"Öffnen der DVD-Disc"')).toEqual({ art: 'aufgabe', name: 'Öffnen der DVD-Disc' })
expect(aufgabeAusPrg('PRGC:1,2,"Lesen, Teil \\"A\\""')).toEqual({ art: 'schritt', name: 'Lesen, Teil "A"' })
expect(aufgabeAusPrg('PRGV:10,20,65536')).toBeNull()
expect(aufgabeAusPrg('MSG:1005,0,1,"x","x"')).toBeNull()
})
it('erkennt die VOB-Scan-Ankündigung auf Deutsch und Englisch', () => {
expect(istVobScanMeldung('IFO-Datei für VTS #1 ist beschädigt, die VOB-Datei muss gescannt werden. Das kann viel Zeit in Anspruch nehmen, bitte haben Sie Geduld.')).toBe(true)
expect(istVobScanMeldung('IFO file for VTS #1 is corrupt, VOB file needs to be scanned. This may take a long time, please be patient.')).toBe(true)
expect(istVobScanMeldung('Using LibreDrive mode (v02.1 id=F39BF9BC17BE)')).toBe(false)
expect(istVobScanMeldung('Titel #1 wurde hinzugefügt (31 cell(s), 2:47:40)')).toBe(false)
})
})
describe('Zwischenstand im Dialog', () => {
const still: Lebenszeichen = { prozent: -1, schritt: '', meldung: '', vobScan: false }
it('nennt Dauer, Schritt, Prozent, letzte Meldung und die Geduld', () => {
const text = titelLaufZwischenstand(17 * 60_000, 20_000, 15, { prozent: 43, schritt: 'Öffnen der DVD-Disc', meldung: 'Using LibreDrive mode', vobScan: false })
expect(text).toContain('MakeMKV liest seit 17 min')
expect(text).toContain('Öffnen der DVD-Disc 43 %')
expect(text).toContain('letzte Meldung: „Using LibreDrive mode"')
expect(text).toContain('ohne Lebenszeichen bricht es nach 15 min ab')
expect(text).toContain('„Abbrechen" beendet den Lauf')
expect(text).not.toContain('still seit')
})
it('sagt, wie lange MakeMKV schon still ist', () => {
expect(titelLaufZwischenstand(9 * 60_000, 3 * 60_000 + 5000, 15, still)).toContain('still seit 3 min')
})
it('beim VOB-Scan: die Erklärung statt der Stille-Grenze', () => {
const text = titelLaufZwischenstand(23 * 60_000, 0, 15, { prozent: 12, schritt: '', meldung: 'IFO-Datei für VTS #1 ist beschädigt, die VOB-Datei muss gescannt werden.', vobScan: true })
expect(text).toContain('Fortschritt 12 %')
expect(text).toContain('DVD-Struktur ist beschädigt')
expect(text).toContain('oft 2060 min')
expect(text).not.toContain('bricht es nach')
})
})
// Ein nachgebautes makemkvcon (Node-Skript, aufruf.ts startet es über die
// eigene Laufzeit): meldet erst die VOB-Ankündigung, dann alle 300 ms
// Fortschritt — länger als die Stille-Grenze von 1 s — und liefert am Ende
// einen Titel. Ein zweites schweigt nach der ersten Zeile.
function fakeMakemkv(inhalt: string): string {
const ordner = mkdtempSync(join(tmpdir(), 'rippy-lebens-'))
wegwerf.push(ordner)
const pfad = join(ordner, 'fake-makemkvcon.cjs')
writeFileSync(pfad, inhalt)
return pfad
}
const REDSELIG = `
const out = (z) => process.stdout.write(z + '\\n')
out('MSG:1005,0,1,"MakeMKV v1.18.4 win(x64-release) gestartet","%1 gestartet","MakeMKV v1.18.4 win(x64-release)"')
out('PRGT:5017,0,"Öffnen der DVD-Disc"')
out('MSG:3300,0,1,"IFO-Datei für VTS #1 ist beschädigt, die VOB-Datei muss gescannt werden. Das kann viel Zeit in Anspruch nehmen, bitte haben Sie Geduld.","x"')
let i = 0
const tick = () => {
i += 1
out('PRGV:' + i * 100 + ',' + i * 100 + ',1000')
if (i < 8) return setTimeout(tick, 300)
out('TCOUNT:1')
out('TINFO:0,8,0,"28"')
out('TINFO:0,9,0,"2:47:40"')
out('TINFO:0,11,0,"5800000000"')
out('SINFO:0,0,1,6201,"Video"')
process.exit(0)
}
setTimeout(tick, 300)
`
const STUMM = `
process.stdout.write('MSG:1005,0,1,"MakeMKV gestartet","x"\\n')
setTimeout(() => process.exit(0), 60_000)
`
describe('titelInfoLesen — Geduld nach Lebenszeichen', () => {
it('wartet über die Stille-Grenze hinaus, solange Fortschritt kommt; meldet Lebenszeichen', { timeout: 20_000 }, async () => {
const zeichen: Lebenszeichen[] = []
const start = Date.now()
const info = await titelInfoLesen(fakeMakemkv(REDSELIG), '\\\\.\\G:', { stillMs: 1000, aufLebenszeichen: (s) => zeichen.push(s) })
expect(Date.now() - start).toBeGreaterThan(2000)
expect(info.titel).toHaveLength(1)
expect(info.titel[0].dauerS).toBe(2 * 3600 + 47 * 60 + 40)
const letztes = zeichen.at(-1)!
expect(letztes.vobScan).toBe(true)
expect(letztes.meldung).toContain('VOB-Datei muss gescannt werden')
expect(zeichen.some((z) => z.prozent === 50)).toBe(true)
expect(zeichen.some((z) => z.schritt === '' && z.prozent === -1 && z.meldung.includes('gestartet'))).toBe(true)
})
it('beendet einen stummen makemkvcon nach der Stille-Grenze — mit klarem Grund', { timeout: 20_000 }, async () => {
const start = Date.now()
await expect(titelInfoLesen(fakeMakemkv(STUMM), '\\\\.\\G:', { stillMs: 1000 })).rejects.toThrow('kein Lebenszeichen seit 1 s')
expect(Date.now() - start).toBeLessThan(10_000)
// Die letzten Zeilen reisen im Fehler mit — fürs Protokoll.
await expect(titelInfoLesen(fakeMakemkv(STUMM), '\\\\.\\G:', { stillMs: 1000 })).rejects.toThrow('letzte Zeilen: MSG:1005')
})
it('beim VOB-Scan gilt nur die Obergrenze (4 h Vorgabe)', { timeout: 20_000 }, async () => {
expect(VOB_SCAN_HOECHSTENS_MS).toBe(4 * 60 * 60_000)
// Stille-Grenze 200 ms, aber nach der VOB-Ankündigung kommt 300 ms lang
// nichts — der Lauf darf trotzdem weiterleben und liefert den Titel.
const info = await titelInfoLesen(fakeMakemkv(REDSELIG), '\\\\.\\G:', { stillMs: 200 })
expect(info.titel).toHaveLength(1)
})
it('beim VOB-Scan beendet die Obergrenze den Lauf', { timeout: 20_000 }, async () => {
await expect(titelInfoLesen(fakeMakemkv(REDSELIG), '\\\\.\\G:', { stillMs: 60_000, vobScanHoechstensMs: 1200 })).rejects.toThrow('VOB-Scan nach 0 min nicht fertig')
})
})