diff --git a/rippy-windows/src/fenster/dialoge/TitelWahlDialog.tsx b/rippy-windows/src/fenster/dialoge/TitelWahlDialog.tsx
index 1df051b..76624fd 100644
--- a/rippy-windows/src/fenster/dialoge/TitelWahlDialog.tsx
+++ b/rippy-windows/src/fenster/dialoge/TitelWahlDialog.tsx
@@ -23,7 +23,7 @@ import type {
TitelStand,
} from '../../gemeinsam/nachrichten'
import { DialogRahmen, IconDisc, KNOPF_GOLD, datumKurz, dauerText, gb, gbKurz, posterUrlKlein, sprachenVorbelegt } from '../bausteine'
-import { metadatenSuchen, ripStarten } from '../kernverbindung'
+import { metadatenSuchen, ripStarten, titelAbbrechen } from '../kernverbindung'
/** Sprach-Chips eines Titels — anklickbar, außerhalb des Auswahl-Labels
* (ein Knopf IM Label würde die Checkbox mit umschalten). */
@@ -181,9 +181,12 @@ export function TitelWahlDialog({
Was soll gerippt werden?
{stand === undefined || stand.laeuft ? (
-
- Rippy liest die Titel der Disc … das dauert bis zu zwei Minuten. Solange dreht das Laufwerk.
-
+
+
Rippy liest die Titel der Disc … meist dauert das ein bis zwei Minuten. Solange dreht das Laufwerk.
+ {/* 5.7.0 (Kandidat 2): Ab der zweiten Minute sagt der Kern, wie lange
+ es schon läuft und wo die Grenze liegt. */}
+ {stand !== undefined && stand.einordnung.length > 0 &&
{stand.einordnung}
}
+
) : stand.fehler.length > 0 ? (
✗ {stand.fehler}
) : (
@@ -463,7 +466,15 @@ export function TitelWahlDialog({
)}
-
+ {
+ // Läuft der Titel-Lauf noch, beendet „Abbrechen" ihn auch im Kern —
+ // sonst dreht das Laufwerk weiter, obwohl der Dialog zu ist.
+ if (stand !== undefined && stand.laeuft) titelAbbrechen(id)
+ schliessen()
+ }}
+ className="rounded-lg border border-tinte/15 px-4 py-1.5 text-xs text-dunst hover:text-schnee"
+ >
Abbrechen
void window.rippy.autostartSetzen(an)} label="Rippy mit Windows starten (Autostart im Benutzerkonto — kein Dienst, keine Adminrechte)" />
+
+ Titel lesen — höchstens
+ setzen('titelLesenMinuten', e.target.value)} className={FELD_KLASSE}>
+ 5 Minuten
+ 10 Minuten
+ 15 Minuten (Vorgabe)
+ 30 Minuten
+ 60 Minuten
+
+ So lange darf MakeMKV die Titel einer Disc lesen. Kopiergeschützte DVDs und Discs mit vielen Playlisten brauchen im MakeMKV-Programm oft länger als fünf Minuten — braucht eine Disc dort länger als hier eingestellt, erhöhe den Wert.
+
Erscheinungsbild
setzen('design', e.target.value)} className={FELD_KLASSE}>
diff --git a/rippy-windows/src/fenster/kernverbindung.ts b/rippy-windows/src/fenster/kernverbindung.ts
index f933502..7c107e2 100644
--- a/rippy-windows/src/fenster/kernverbindung.ts
+++ b/rippy-windows/src/fenster/kernverbindung.ts
@@ -347,6 +347,11 @@ export function titelLesen(id: string): void {
anKern({ art: 'titel-lesen', id }, `Titel lesen ${id}`)
}
+/** 5.7.0: den laufenden Titel-Lauf beenden — makemkvcon gibt das Laufwerk frei. */
+export function titelAbbrechen(id: string): void {
+ anKern({ art: 'titel-abbruch', id }, `Titel-Lauf abbrechen ${id}`)
+}
+
/** `auftrag` leer heißt: alles rippen (wie vor 5.2). */
export function ripStarten(id: string, auftrag: RipAuftragTitel[] = [], optionen: RipAuftragOptionen = {}): void {
anKern({ art: 'rip-start', id, auftrag, optionen }, `Rip ${id}`)
diff --git a/rippy-windows/src/gemeinsam/nachrichten.ts b/rippy-windows/src/gemeinsam/nachrichten.ts
index 02abd51..16096af 100644
--- a/rippy-windows/src/gemeinsam/nachrichten.ts
+++ b/rippy-windows/src/gemeinsam/nachrichten.ts
@@ -438,6 +438,8 @@ export type FensterNachricht =
* und nicht schon beim Einlegen (der 30.08.-Fund „das Erkennen dauert
* sehr sehr lange"). */
| { art: 'titel-lesen'; id: string }
+ /** 5.7.0: den laufenden Titel-Lauf abbrechen (Kandidat 2). */
+ | { art: 'titel-abbruch'; id: string }
/** `auftrag` leer heißt: alles rippen — wie vor 5.2. */
| { art: 'rip-start'; id: string; auftrag?: RipAuftragTitel[]; optionen?: RipAuftragOptionen }
| { art: 'rip-abbruch'; id: string }
@@ -705,6 +707,7 @@ export function istFensterNachricht(wert: unknown): wert is FensterNachricht {
if (
wert.art === 'auswerfen' ||
wert.art === 'titel-lesen' ||
+ wert.art === 'titel-abbruch' ||
wert.art === 'rip-abbruch' ||
wert.art === 'rip-ueberspringen' ||
wert.art === 'laufwerk-pruefen' ||
diff --git a/rippy-windows/src/kern/dienste/einstellungen.ts b/rippy-windows/src/kern/dienste/einstellungen.ts
index 10372c5..cf945d2 100644
--- a/rippy-windows/src/kern/dienste/einstellungen.ts
+++ b/rippy-windows/src/kern/dienste/einstellungen.ts
@@ -37,6 +37,8 @@ export const ERLAUBTE_EINSTELLUNGEN: ReadonlySet = 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 (5–60, 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
diff --git a/rippy-windows/src/kern/dienste/erkennung.ts b/rippy-windows/src/kern/dienste/erkennung.ts
index 2660b39..52ee922 100644
--- a/rippy-windows/src/kern/dienste/erkennung.ts
+++ b/rippy-windows/src/kern/dienste/erkennung.ts
@@ -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
diff --git a/rippy-windows/src/kern/index.ts b/rippy-windows/src/kern/index.ts
index 467f3a0..2b3abe8 100644
--- a/rippy-windows/src/kern/index.ts
+++ b/rippy-windows/src/kern/index.ts
@@ -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 ?? {})