// Die Bühne: Backdrop als Panorama, Poster mit Goldkante, Plakat-Titel — // und beim Arbeiten die Aussteuerungsanzeige samt Mono-Protokoll. // 5.4.0: Kino-Banner v2 (Logo, Tagline, Meta-Zeile, Darsteller), Laufwerks- // Gesundheit, Stand der Kompression nach dem Rip. 5.5.0: Automatik-Countdown // und das Rettungs-Abbild — nur, wenn Rippy Lesefehler gesehen hat. import { useState } from 'react' import type { AutomatikStand, DiscInfoStand, GeraeteInfo, KompressionStand, LaufwerkBericht, RipStatus, VorgangEintrag, } from '../../gemeinsam/nachrichten' import { DEFAULT_HB_PRESET, PRESET_KEINE, empfehlungText, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung' import { Badge, IconDisc, IconEject, IconPlay, IconStift, IconStop, InfoKachel, LAUFENDE_PHASEN, PHASEN_MONO, TYP_NAMEN, backdropUrl, datumKurz, discHerkunft, gb, logoUrl, metaZeile, posterUrlGross, profilUrl, restzeitText, sicherheitsWort, } from '../bausteine' import { automatikJetzt, automatikStopp, auswerfen, laufwerkPruefen, rettungStarten, ripAbbrechen, ripUeberspringen, type Verbindung, } from '../kernverbindung' import { leisteAusRip } from '../../gemeinsam/vorgangsleiste' import { VorgangsLeiste } from './VorgangsLeiste' /** Welches Preset diese Disc bekäme — dieselbe Kette wie im Kern * (eigene Wahl → Empfehlung → Default). */ export function presetAnzeige(typ: GeraeteInfo['typ'], verbindung: Verbindung): string { if (typ === 'cd') return 'FLAC (verlustfrei)' if (typ === 'unknown') return 'ISO-Abbild' const werte = verbindung.einstellungen if (werte['transcodeEnabled'] === 'false') return 'ohne Kompression' const schluessel = typ === 'dvd' ? 'transcodePresetDvd' : typ === 'uhd' ? 'transcodePresetUhd' : 'transcodePresetBluray' const eigene = werte[schluessel] ?? '' if (eigene === PRESET_KEINE) return 'ohne Kompression' if (eigene.length > 0) return eigene const empfehlung = presetEmpfehlung(typ, verbindung.werkzeuge?.backends ?? [], verbindung.werkzeuge?.presets ?? []) if (empfehlung === PRESET_KEINE) return typ === 'uhd' ? 'verlustfrei (nur CPU-Encoder)' : 'ohne Kompression' return empfehlungText(typ, empfehlung.length > 0 ? empfehlung : DEFAULT_HB_PRESET) } export function LaufwerkKachel({ geraet, rip, info, ripStartMs, protokoll, verbindung, bericht, vorgang, kompression, wartePlatz, automatik, aufRippen, aufAendern, }: { geraet: GeraeteInfo rip: RipStatus | undefined info: DiscInfoStand | undefined ripStartMs: number | undefined protokoll: string[] verbindung: Verbindung /** Was das Windows-Protokoll zu diesem Laufwerk sagt (5.4.0). */ bericht: LaufwerkBericht | undefined /** Der Vorgang des letzten Rips — für den Stand der Kompression (5.4.0). */ vorgang: VorgangEintrag | undefined kompression: KompressionStand | null wartePlatz: number /** Der Automatik-Countdown (5.5.0). */ automatik: AutomatikStand | undefined /** Die Dialoge leben im App-Zustand (5.4.0): Ein leerer Takt der Wache * räumte sonst Kachel UND Dialog ab. */ aufRippen: () => void aufAendern: () => void }) { const [protokollOffen, setProtokollOffen] = useState(false) const [berichtOffen, setBerichtOffen] = useState(false) const ripLaeuft = rip !== undefined && LAUFENDE_PHASEN.includes(rip.phase) const discDa = geraet.status === 'ready' const backdrop = discDa && info !== undefined ? backdropUrl(info.backdropPfad) : '' const poster = discDa && info !== undefined ? posterUrlGross(info.posterPfad) : '' const erkannt = discDa && info !== undefined && info.titel.length > 0 const logo = erkannt ? logoUrl(info.logoPfad) : '' const rettungMoeglich = discDa && !ripLaeuft && rip !== undefined && rip.rettungMoeglich === true const berichtTon = bericht === undefined ? '' : bericht.urteil === 'ruhig' ? 'text-gruen border-gruen/40 bg-gruen/10' : bericht.urteil === 'disc' ? 'text-warn border-warn/45 bg-warn/10' : bericht.urteil === 'hardware' ? 'text-rot border-rot/45 bg-rot/10' : 'text-dunst border-tinte/12 bg-samt/60' return (
{geraet.status === 'empty' ? 'Keine Disc eingelegt — Disc rein genügt, Rippy meldet sich.' : geraet.grund}
{info.tagline}
} {erkannt && metaZeile(info).length > 0 &&{metaZeile(info)}
} {erkannt && info.beschreibung.length > 0 &&{info.beschreibung}
} {!erkannt && ({geraet.typ === 'cd' ? 'Titel kommen beim Rippen von MusicBrainz.' : 'Noch keine Zuordnung — „Ändern …" hilft nach.'}
)} {/* Zwei Kacheln nebeneinander im schmalen Fenster, vier im breiten — vorher immer vier, also im schmalen gequetscht. */}{h.includes('NICHT') ? '⚠ ' : h.includes('bestätigt') ? '✓ ' : '· '} {h}
))}{geraet.grund}
}{automatik.text}
{automatik.aktiv && ({bericht.urteil === 'ruhig' ? 'LAUFWERK RUHIG' : bericht.urteil === 'disc' ? 'VERDACHT: DIE DISC' : bericht.urteil === 'hardware' ? 'VERDACHT: DIE HARDWARE' : 'PROTOKOLL'} {' '} — {bericht.text}
{z}
))}Titel {rip.titelNr} von {rip.titelGesamt}
)} {rip.rettung !== undefined && (Rettungs-Abbild: {rip.rettung.unlesbareSektoren} unlesbare Sektoren an {rip.rettung.stellen} Stellen mit Nullen gefüllt
)} {/* Der Defekt-Waechter (§ 6.2): Dieselbe Stelle wieder und wieder ist kein Fortschritt, sondern ein physischer Defekt. Vorher lief MakeMKV dort stundenlang weiter, waehrend identische Zeilen durchs Protokoll rauschten. */} {rip.haengt != null && (Die Disc hat an einer Stelle einen Defekt.
MakeMKV versucht es seit {rip.haengt.wiederholungen} Meldungen immer wieder an derselben Stelle (Byte {rip.haengt.offset}). Das wird nicht besser — der Rest der Disc ist davon nicht betroffen. Nach dem Abbruch bietet Rippy das Rettungs-Abbild an.
> {rip.text}
{rip.dateien.map((datei) => ({datei}
))} {/* Nach dem Rip (5.6.0, Commander: „Nur in der Kompressions-Karte"): hier nur noch EINE Zeile, wo es weitergeht — Balken, Prozent, Restzeit und Ziel stehen in der Kompressions-Karte, danach im Verlauf. Fehler bleiben sichtbar (R4). */} {rip.phase === 'fertig' && vorgang !== undefined && ({kompression !== null ? 'Kompression läuft — Stand in der Kompressions-Karte unten.' : wartePlatz > 0 ? `Kompression wartet — Platz ${wartePlatz} in der Warteschlange (Karte unten).` : vorgang.phase === 'fertig' ? 'Kompression fertig — das Ergebnis steht im Verlauf.' : vorgang.phase === 'fehler' || vorgang.phase === 'abgebrochen' ? `⚠ ${vorgang.fehler.length > 0 ? vorgang.fehler : `Kompression ${vorgang.phase}`}` : `Kompression: ${vorgang.phase}`}
)}> {zeile}
))}