feat(v5): Titel lesen bis 15 Minuten (einstellbar), Zwischenstand im Dialog, Abbrechen beendet den Lauf

WAS: Die Grenze fürs Titel-Lesen kommt aus der Einstellung
titelLesenMinuten (5–60, Vorgabe 15; Einstellungen → Allgemein). Ab der
zweiten Minute meldet der Kern jede Minute, wie lange MakeMKV schon liest
und wo die Grenze liegt; „Abbrechen" im Dialog beendet den Lauf auch im
Kern (titel-abbruch), damit das Laufwerk frei wird. Läuft die Zeit ab,
nennt die Meldung Kopierschutz und viele Playlisten als mögliche Gründe
statt nur „beschädigte Disc" — mit dem Rat, die Disc im MakeMKV-Programm
zu öffnen und die Zeit zu stoppen.
WARUM: Durchsicht 12.09.2026, Kandidat 2 (Commander-Entscheid: 15 Minuten
plus Regler) — fünf Minuten fest war für ältere DVDs mit Kopierschutz zu
kurz, das GUI wartet, Rippy nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-12 21:44:44 +02:00
co-authored by Claude Opus 5
parent ae82d8c0fd
commit 6b6e5c3436
7 changed files with 82 additions and 7 deletions
@@ -37,6 +37,8 @@ export const ERLAUBTE_EINSTELLUNGEN: ReadonlySet<string> = new Set([
'telegramToken',
'telegramChatId',
'design',
// 5.7.0: wie lange makemkvcon die Titel lesen darf (Kandidat 2 der Durchsicht)
'titelLesenMinuten',
])
/** Die Schlüssel, die Rip und Kompression lesen (Pipeline/Ablage). Das
@@ -56,6 +58,10 @@ const PIPELINE_EINSTELLUNGEN = [
] as const
export const AUTOMATIK_SEKUNDEN_VORGABE = 30
/** 5.7.0: Vorgabe 15 min (Commander-Entscheid 12.09.2026) — kopiergeschützte
* DVDs und Discs mit vielen Playlisten brauchen im MakeMKV-GUI regelmäßig
* länger als die alten fünf Minuten. */
export const TITEL_LESEN_MINUTEN_VORGABE = 15
export class EinstellungsDienst {
constructor(private readonly kontext: KernKontext) {}
@@ -101,6 +107,12 @@ export class EinstellungsDienst {
return this.lesen('automatik', 'an') !== 'aus'
}
/** Grenze fürs Titel-Lesen in Minuten (560, Vorgabe 15). */
titelLesenMinuten(): number {
const n = Number(this.lesen('titelLesenMinuten', String(TITEL_LESEN_MINUTEN_VORGABE)))
return Number.isInteger(n) && n >= 5 && n <= 60 ? n : TITEL_LESEN_MINUTEN_VORGABE
}
automatikSekunden(): number {
const n = Number(this.lesen('automatikSekunden', String(AUTOMATIK_SEKUNDEN_VORGABE)))
return Number.isInteger(n) && n >= 5 && n <= 600 ? n : AUTOMATIK_SEKUNDEN_VORGABE
+31 -2
View File
@@ -24,6 +24,8 @@ export interface ErkennungAbhaengigkeiten {
ripLaeuft(id: string): boolean
geraetInfoVon(id: string): GeraeteInfo | null
makemkvcon(): string
/** 5.7.0: wie lange makemkvcon die Titel lesen darf (Minuten). Fehlt: 15. */
titelLesenMinuten?(): number
}
/** Der Serien-Merker (5.5.0): je TMDb-Serie, was die vorige Disc hinterließ. */
@@ -311,8 +313,22 @@ export class ErkennungDienst {
senden({ ...leer, laeuft: true })
const start = Date.now()
// 5.7.0 (Kandidat 2): Die Grenze kommt aus den Einstellungen (Vorgabe
// 15 min statt fest fünf), und ab der zweiten Minute sagt der Dialog,
// dass es länger dauert — mit Abbrechen-Knopf, statt stumm zu drehen.
const minuten = this.abh.titelLesenMinuten?.() ?? 15
const zwischenstand = setInterval(() => {
if (!nochAktuell()) return
const vergangen = Math.round((Date.now() - start) / 60_000)
senden({
...leer,
laeuft: true,
einordnung: `MakeMKV liest seit ${vergangen} min. Kopiergeschützte DVDs und Discs mit vielen Playlisten brauchen länger — Rippy wartet bis ${minuten} min (Einstellungen → Allgemein), „Abbrechen" beendet den Lauf.`,
})
}, 60_000)
zwischenstand.unref()
try {
const info = await titelInfoLesen(programm, geraet.pfad, 300_000, abbruch.signal)
const info = await titelInfoLesen(programm, geraet.pfad, minuten * 60_000, abbruch.signal)
if (!nochAktuell()) return { ...leer, fehler: ABGELOEST }
const merkmale = this.merkmale.get(id)
const wahl = titelVorauswahl(info.titel, {
@@ -352,11 +368,12 @@ export class ErkennungDienst {
// R4: sonst laut scheitern. „Konnte nicht nachsehen" ist etwas anderes
// als „die Disc hat keine Titel" — beides zu vermischen erzeugt Unsinn.
const grund = String(fehler).includes('keine Antwort nach')
? 'MakeMKV hat die Titel nach fünf Minuten nicht gelesen. Bei einer beschädigten Disc kann das passieren — Rippy hat den Versuch beendet und das Laufwerk freigegeben.'
? `MakeMKV hat die Titel nach ${minuten} Minuten nicht gelesen — Rippy hat den Versuch beendet und das Laufwerk freigegeben. Das kann an einer beschädigten Disc liegen, aber auch an Kopierschutz oder vielen Playlisten, die einfach länger brauchen: Die Grenze steht unter Einstellungen → Allgemein. Zur Probe die Disc im MakeMKV-Programm öffnen und die Zeit stoppen.`
: `Titel lesen: ${String(fehler).slice(0, 200)}`
this.kontext.protokoll.schreiben(`Titel-Lauf ${id} gescheitert: ${grund}`)
return senden({ ...leer, fehler: grund })
} finally {
clearInterval(zwischenstand)
// Nur aufräumen, wenn ICH noch der aktuelle Lauf bin.
if (nochAktuell()) {
this.titelAbbrueche.delete(id)
@@ -365,6 +382,18 @@ export class ErkennungDienst {
}
}
/** Der Nutzer bricht das Titel-Lesen ab (5.7.0): Der Lauf endet sofort,
* makemkvcon gibt das Laufwerk frei, der Dialog zeigt „abgebrochen". */
titelAbbrechen(id: string): void {
const abbruch = this.titelAbbrueche.get(id)
if (abbruch === undefined) return
abbruch.abort()
this.titelAbbrueche.delete(id)
this.titelLaufNummern.delete(id)
this.kontext.protokoll.schreiben(`Titel-Lauf ${id} vom Nutzer abgebrochen`)
this.kontext.anFenster({ art: 'disc-titel', stand: { id, laeuft: false, titel: [], einordnung: '', unsicher: false, fehler: 'Abgebrochen — „Rippen" liest die Titel neu.' } })
}
/** Erkennung v2 (5.4.0): Erkannt wird beim EINLEGEN, vor jeder Messung.
* Nach dem Info-Lauf wird noch einmal gerechnet — Laufzeit gegen TMDb,
* Titel-Struktur gegen den Treffer-Typ. Aus „wahrscheinlich" wird
+4
View File
@@ -137,6 +137,7 @@ const erkennung = new ErkennungDienst(kontext, {
ripLaeuft: (id) => ripVerwaltung.laeuft(id),
geraetInfoVon: (id) => laufwerke.geraetInfoVon(id),
makemkvcon: () => werkzeuge.makemkvcon(),
titelLesenMinuten: () => einstellungen.titelLesenMinuten(),
})
const vorgaenge = new VorgangDienst(kontext, einstellungen, werkzeuge, erkennung, benachrichtigung, () => speicher.senden())
@@ -239,6 +240,9 @@ function fensterNachricht(frage: unknown): void {
automatik.stopp(frage.id, '')
void erkennung.titelLesen(frage.id)
return
case 'titel-abbruch':
erkennung.titelAbbrechen(frage.id)
return
case 'rip-start':
automatik.stopp(frage.id, '')
ripMitPlatz(frage.id, frage.auftrag ?? [], frage.optionen ?? {})