fix(v5): 5.7.1 — Titel lesen wartet, solange MakeMKV arbeitet; Grenze gilt für Stille, Dialog zeigt Schritt, Prozent und Meldung
Ampel / ampel (push) Successful in 1m20s
Ampel / ampel (push) Successful in 1m20s
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0088948b4f
commit
eb54c59141
@@ -0,0 +1,132 @@
|
||||
// 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 20–60 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')
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,7 @@ import { buildInfoArgs, buildRipArgs, RipAuswertung, ZeilenLeser } from '../src/
|
||||
|
||||
describe('Kommandobau', () => {
|
||||
it('info: Robot-Mode ohne Geräte-Scan', () => {
|
||||
expect(buildInfoArgs('\\\\.\\G:')).toEqual(['-r', '--noscan', 'info', 'dev:G:'])
|
||||
expect(buildInfoArgs('\\\\.\\G:')).toEqual(['-r', '--noscan', '--progress=-same', 'info', 'dev:G:'])
|
||||
})
|
||||
it('mkv: Fortschritt in denselben Strom', () => {
|
||||
expect(buildRipArgs('\\\\.\\G:', 'C:\\ziel', 'all')).toEqual([
|
||||
|
||||
Reference in New Issue
Block a user