feat(v5): 5.6.0 — Vorgangs-Leiste, mehrere Filme je Disc, Filmnamen für Ordner und Dateien, Untertitel-Vorwahl je Extra, Restzeit, Hell-Modus

Die sechs Punkte des Commanders vom 02.09.2026 abends und seine Entscheide:

* Restzeit der Kompression aus HandBrakes eigener ETA-Klammer (Formatstring
  aus dem Binary 1.11.2 gelesen), KompressionStand.restS.
* Kachel zeigt nach dem Rip nur noch eine Zeile — Balken, Prozent, Ziel in
  der Kompressions-Karte und im Verlauf ("nur in der Kompressions-Karte").
* Kopfzeile bleibt beim Scrollen stehen (sticky), Sektionswahl darunter.
* Untertitel-Vorwahl je Extra ("NUR für Extras, IMMER OPTIONAL"): welche
  Untertitel-Spur beim Abspielen an ist — weiche Spur, nie eingebrannt;
  RipAuftragTitel/RohDatei.untertitelVorwahl, sprachwahl.
* Mehrere Filme je Disc: "anderer Film …" je Titel mit TMDb-Suche;
  filme.ts filmGruppen trennt am Rip-Ende in eigene Roh-Ordner, Vorgänge
  und Kompressions-Aufträge mit voller Zuordnung (erkennung.zuordnungAus).
* Roh-Ordner und Dateien tragen den Filmnamen (filme.ts rohOrdnerName,
  ablage/dateinamen.ts: Akira (1988).mkv, - Fassung n, - Extra nn).
* Hell-Modus: Einstellung design (dunkel/hell/system), data-design auf
  <html>, Token --color-tinte statt white/-Alpha-Klassen.
* Vorgangs-Leiste (Commander: "Phase 1–4 plus Komprimierung — zu einem
  machen"): gemeinsam/vorgangsleiste.ts + uebersicht/VorgangsLeiste.tsx,
  vier Stationen (Rettung als Zwischenstation), Segment-Balken in der
  aktiven Station; Kachel, Kompressions-Karte, Verlauf. PHASE-x/4-Texte weg.
* Automatik-Kasten leert sich, sobald ein Rip startet.

Beweise: 446 Tests (neu: filme, dateinamen, vorgangsleiste; erweitert:
auftrag, handbrake, automatik, ablegen), drei Typprüfungen, Klick-Beweis
33 Schritte inkl. Vorwahl, anderer Film, Vorgangs-Leiste, Restzeit,
Hell-Modus (Bilder beweise/klick/01…11).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-03 19:17:37 +02:00
co-authored by Claude Fable 5.1
parent 76ed3b337a
commit f5dab5d1c1
59 changed files with 1284 additions and 182 deletions
@@ -66,7 +66,7 @@ export function AblageKarte({ verbindung }: { verbindung: Verbindung }) {
{gbKurz(speicher.arbeit.freiBytes)} frei von {gbKurz(speicher.arbeit.gesamtBytes)} eine Blu-ray braucht rund 50 GB.
</p>
)}
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<p className="text-dunst">So legt Rippy ab:</p>
<p>
Filme\Akira (1988)\ <span className="text-nebel/70"> Film, Poster und Infodatei beieinander</span>
@@ -238,7 +238,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
</button>
</div>
</div>
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
{v === null ? (
<p>Belegung wird gemessen </p>
) : (
@@ -258,7 +258,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
{sichtbar.map((e) => {
const ruhig = e.phase === 'fertig' || e.phase === 'fehler' || e.phase === 'abgebrochen'
return (
<div key={e.id} className="rounded-xl border border-white/8 bg-buehne/60 p-3">
<div key={e.id} className="rounded-xl border border-tinte/8 bg-buehne/60 p-3">
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<span className="font-display text-[14px] font-bold text-schnee">
{e.titel}
@@ -293,7 +293,7 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
<select
value={presetWahl[e.id] ?? ''}
onChange={(ev) => setPresetWahl({ ...presetWahl, [e.id]: ev.target.value })}
className="rounded-lg border border-white/15 bg-samt/70 px-2 py-1 text-[11px] text-dunst"
className="rounded-lg border border-tinte/15 bg-samt/70 px-2 py-1 text-[11px] text-dunst"
title="Preset für die Wiederholung — leer: wie eingestellt"
>
<option value="">Preset wie eingestellt</option>
@@ -318,12 +318,12 @@ export function RohDateienKarte({ verbindung, aktivFuer }: { verbindung: Verbind
</button>
)}
{e.phase === 'fertig' && e.zielOrdner.length > 0 && (
<button onClick={() => void window.rippy.ordnerOeffnen(e.zielOrdner)} className="rounded-lg border border-white/15 px-3 py-1 text-[11px] text-dunst hover:text-schnee">
<button onClick={() => void window.rippy.ordnerOeffnen(e.zielOrdner)} className="rounded-lg border border-tinte/15 px-3 py-1 text-[11px] text-dunst hover:text-schnee">
Ordner öffnen
</button>
)}
{(e.phase === 'wartet' || e.phase === 'komprimiert' || e.phase === 'legt-ab') && (
<button onClick={() => kompressionAbbrechen(e.id)} className="rounded-lg border border-white/15 px-3 py-1 text-[11px] text-dunst hover:text-rot">
<button onClick={() => kompressionAbbrechen(e.id)} className="rounded-lg border border-tinte/15 px-3 py-1 text-[11px] text-dunst hover:text-rot">
Kompression abbrechen
</button>
)}
@@ -401,7 +401,7 @@ export function MakemkvKarte({ verbindung }: { verbindung: Verbindung }) {
titel="MAKEMKV"
intro="MakeMKV liest die Discs. Rippy holt den frei veröffentlichten Beta-Key automatisch aus dem Hersteller-Forum, hinterlegt ihn und fragt MakeMKV selbst, ob er angenommen wird täglich."
>
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3">
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3">
{s === null ? (
<p className="font-mono text-xs text-nebel">Schlüssel wird geprüft …</p>
) : (
@@ -437,10 +437,10 @@ export function MakemkvKarte({ verbindung }: { verbindung: Verbindung }) {
</button>
</div>
{b !== null && (
<div className="rounded-lg border border-white/10 bg-buehne/70 p-2.5">
<div className="rounded-lg border border-tinte/10 bg-buehne/70 p-2.5">
<p className={`font-mono text-[11px] leading-5 ${b.fehler ? 'text-rot' : b.laeuft ? 'text-gold-hell' : 'text-gruen'}`}>{b.text}</p>
{b.laeuft && b.anteil !== null && (
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-white/8">
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-tinte/8">
<div className="h-full rounded-full bg-gradient-to-r from-gold-tief to-gold-hell transition-all" style={{ width: `${Math.round(b.anteil * 100)}%` }} />
</div>
)}
@@ -519,6 +519,14 @@ export function AllgemeinKarte({ verbindung, haupt }: { verbindung: Verbindung;
return (
<SektionsKarte id="allgemein" titel="ALLGEMEIN" intro="Rippy lebt im Infobereich neben der Uhr: Fenster zu heißt weiterlaufen, beendet wird über das Tray-Menü.">
<Schalter an={haupt?.autostart === true} aufWechsel={(an) => void window.rippy.autostartSetzen(an)} label="Rippy mit Windows starten (Autostart im Benutzerkonto kein Dienst, keine Adminrechte)" />
<label className="block max-w-sm">
<span className="text-xs font-semibold text-dunst">Erscheinungsbild</span>
<select value={werte['design'] ?? 'dunkel'} onChange={(e) => setzen('design', e.target.value)} className={FELD_KLASSE}>
<option value="dunkel">Dunkel — der Kinosaal (Vorgabe)</option>
<option value="hell">Hell — Papier und Gold</option>
<option value="system">Wie Windows eingestellt</option>
</select>
</label>
<div className="flex flex-wrap gap-2">
<button onClick={() => void window.rippy.protokollOeffnen()} className={KNOPF_LEISE} title="Eine Datei je Tag, 14 Tage aufgehoben Haupt und Kern schreiben hinein">
Protokoll öffnen
@@ -532,7 +540,7 @@ export function AllgemeinKarte({ verbindung, haupt }: { verbindung: Verbindung;
</button>
)}
</div>
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<p>
Rippy v{haupt?.version ?? '…'}
{kern !== null && ` · Kern: Node ${kern.nodeVersion} (PID ${kern.pid})`}
@@ -559,7 +567,7 @@ export function UpdateKarte({ verbindung, haupt }: { verbindung: Verbindung; hau
ihn in der Datenbank, und § 6.8 prüft ihn wie zuvor (nur
https://). */}
{haupt !== null && (
<div className="rounded-xl border border-white/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<div className="rounded-xl border border-tinte/8 bg-buehne/60 p-3 font-mono text-[11px] leading-5 text-nebel">
<p>
<span className="text-dunst">Stand:</span> {haupt.update.text}
</p>
@@ -597,7 +605,7 @@ function WerkzeugZeile({
wert: string
}) {
return (
<div className="rounded-xl border border-white/8 bg-samt/50 p-3">
<div className="rounded-xl border border-tinte/8 bg-samt/50 p-3">
<p className={`break-all font-mono text-xs ${pfad === null ? 'text-nebel' : pfad.length > 0 ? 'text-gruen' : pflicht ? 'text-rot' : 'text-warn'}`}>
{name}: {pfad === null ? 'wird gesucht …' : pfad.length > 0 ? pfad : fehlt}
</p>