feat(v5): Etappe W-2 — Rippen: Parser, makemkvcon-Lauf, Werkzeug-Katalog
Ampel / ampel (push) Failing after 1m22s

rip/parser.ts: die reine Textverarbeitung mit allen bezahlten Fallen aus
dem Docker-Zweig — quelle() (dev:G: statt dev:\.\G:, der 404-Verwandte),
textVon() (UTF-8 streng, sonst cp1252 — nie wieder 'Öffnen'), parseMsg
(Regex statt split — Meldungen mit Komma), PRGV (-1 vs. echtes 0 %),
TINFO/SINFO (Sprache aus Attr 3/4, NICHT 28/29), laengsterTitel mit
Play-All-Schutz, episodenTitel, KRITISCHE_CODES (sprachfeste NUMMERN).

rip/makemkv.ts: Kommandobau + Lauf — ZeilenLeser (binaer, halbe Zeilen,
je Zeile dekodiert), RipAuswertung (pur), rippen() mit AbortSignal,
Lesefehler-Flag AUCH bei Rueckgabewert 0. werkzeuge/katalog.ts: die
Suchreihenfolge samt %VAR%-Gross/Klein-Falle und Registry-Rueckfall.
ablauf/rippen.ts: ein Rip je Laufwerk, Status-Push, 'erst nachsehen,
dann rippen' (unbekannt heisst trotzdem rippen). Fenster: Rippen-Knopf,
Fortschrittsbalken, Abbrechen; Auswurf waehrend des Rips abgelehnt.

Gemessen am echten System (30.08.2026): Katalog findet
makemkvcon64 (Program Files x86), HandBrakeCLI und flac (beide im
rc11-Werkzeugordner LOCALAPPDATA\Rippy\tools). NEUER BEFUND: Nach dem
abgebrochenen rc11-Rip vom Mittag sieht MakeMKV GAR KEIN Laufwerk mehr
(MSG 5042, leere DRV-Liste, mit und ohne --noscan), waehrend die
Storage-IOCTLs sauber antworten — MakeMKV spricht SCSI direkt. 5042 ist
jetzt ein KRITISCHER_CODE mit Klartext-Abhilfe. Der volle Rip-Beweis
(messung.rip.test.ts liegt bereit) braucht deshalb erst eine Hand am
Geraet: Disc neu einlegen oder USB ab-/anstecken.

96 Tests gruen, Smoke gruen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-08-30 18:12:44 +02:00
co-authored by Claude Fable 5
parent a45069af5e
commit 844bca37ff
13 changed files with 1495 additions and 23 deletions
@@ -0,0 +1,57 @@
// MESSUNG an echter Hardware: makemkvcon finden und die eingelegte Disc
// befragen (Titel + Sprachen). Läuft nur auf Verlangen:
//
// RIPPY_MESSUNG=1 npx vitest run test/messung.makemkv.test.ts
//
// Der VOLLE Rip ist ein eigener Lauf (messung.rip.test.ts) — er dauert
// je Disc 3090 Minuten.
import { describe, expect, it } from 'vitest'
const messen = process.env.RIPPY_MESSUNG === '1' && process.platform === 'win32'
describe.skipIf(!messen)('makemkvcon am echten System', () => {
it('wird über den Katalog gefunden', async () => {
const { finden } = await import('../src/kern/werkzeuge/katalog')
const pfad = finden('makemkv')
console.log(`MESSUNG makemkvcon: ${pfad.length > 0 ? pfad : 'NICHT GEFUNDEN'}`)
const handbrake = finden('handbrake')
console.log(`MESSUNG HandBrakeCLI: ${handbrake.length > 0 ? handbrake : 'NICHT GEFUNDEN'}`)
const flac = finden('flac')
console.log(`MESSUNG flac: ${flac.length > 0 ? flac : 'NICHT GEFUNDEN'}`)
expect(pfad.length).toBeGreaterThan(0)
})
it('liest Titel und Sprachen der eingelegten Disc', { timeout: 360_000 }, async () => {
const { finden } = await import('../src/kern/werkzeuge/katalog')
const { titelInfoLesen } = await import('../src/kern/rip/makemkv')
const { win32Api } = await import('../src/kern/laufwerk/win32')
const { listOptischeLaufwerke } = await import('../src/kern/laufwerk/disc')
const programm = finden('makemkv')
expect(programm.length).toBeGreaterThan(0)
const geraete = listOptischeLaufwerke(win32Api())
expect(geraete.length).toBeGreaterThan(0)
const info = await titelInfoLesen(programm, geraete[0])
console.log(`MESSUNG Titel: ${info.titel.length}`)
for (const t of info.titel.slice(0, 12)) {
console.log(
`MESSUNG #${t.nr}: ${Math.floor(t.dauerS / 60)}min ${t.dauerS % 60}s · ` +
`${(t.groesseBytes / 1e9).toFixed(2)} GB · ${t.kapitel} Kapitel`,
)
}
console.log(
`MESSUNG Sprachen Ton: ${info.sprachen.audio.map((s) => `${s.lang}(${s.spuren})`).join(', ') || '—'}`,
)
console.log(
`MESSUNG Sprachen Untertitel: ${info.sprachen.untertitel.map((s) => `${s.lang}(${s.spuren})`).join(', ') || '—'}`,
)
expect(info.titel.length).toBeGreaterThan(0)
})
})
describe.skipIf(messen)('Messung übersprungen', () => {
it('läuft nur mit RIPPY_MESSUNG=1 unter Windows', () => {
expect(true).toBe(true)
})
})