// ── Erst-Einrichtung (§ 6.9): nach der Installation, im Programm ──────── // Zwei Regeln aus dem alten Setup: NUR ein Fehler blockiert (kein // Laufwerk ist eine WARNUNG — eine reine Komprimier-Maschine ist ein // vorgesehener Betriebsfall), und Erfolg wird am BESTAND gemessen. // 5.5.0: dieselben Bausteine wie die Einstellungen (Commander: „All diese // Dinge müssen auch in den FirstRun mit rein") — Ablage und Arbeitsordner, // Werkzeuge mit Durchsuchen, Kompression mit Sprach-Auswahl, TMDb, // Benachrichtigung, Automatik und Roh-Regel. Dazu der Poster-Hintergrund. import { useEffect, useState } from 'react' import { DEFAULT_HB_PRESET, presetEmpfehlung } from '../../gemeinsam/preset-empfehlung' import { Badge, FELD_KLASSE, Feld, KNOPF_GOLD, KNOPF_LEISE, PfadFeld, Schalter, gbKurz } from '../bausteine' import { SprachAuswahl } from '../einstellungen/SprachAuswahl' import { benachrichtigungTesten, einstellungSetzen, posterLaden, speicherLaden, type Verbindung } from '../kernverbindung' import { PosterHintergrund } from './PosterHintergrund' const SCHRITTE = ['Willkommen', 'Prüfung', 'Ordner', 'Qualität', 'Titel', 'Meldungen', 'Automatik', 'Fertig'] export function ErstEinrichtung({ verbindung }: { verbindung: Verbindung }) { const [schritt, setSchritt] = useState(0) const werte = verbindung.einstellungen const werkzeuge = verbindung.werkzeuge const speicher = verbindung.speicher const setzen = einstellungSetzen const makemkvFehlt = werkzeuge !== null && werkzeuge.makemkvcon.length === 0 const tmdbDa = (werte['tmdbApiKey'] ?? '').length > 0 // Poster, sobald ein TMDb-Schlüssel da ist — auch, wenn er gerade erst // eingetragen wurde. useEffect(() => { if (tmdbDa) posterLaden() }, [tmdbDa]) useEffect(() => { speicherLaden() }, [schritt]) const pruefzeilen: Array<{ ok: boolean | null; pflicht: boolean; text: string }> = [ { ok: werkzeuge === null ? null : werkzeuge.makemkvcon.length > 0, pflicht: true, text: werkzeuge === null ? 'MakeMKV wird gesucht …' : werkzeuge.makemkvcon.length > 0 ? `MakeMKV gefunden: ${werkzeuge.makemkvcon}` : 'MakeMKV FEHLT — ohne makemkvcon kann Rippy keine Video-Disc lesen. Von makemkv.com/download installieren, dann findet Rippy es von selbst — oder unten den Pfad wählen.', }, { ok: werkzeuge === null ? null : werkzeuge.handbrake.length > 0, pflicht: false, text: werkzeuge === null ? 'HandBrakeCLI wird gesucht …' : werkzeuge.handbrake.length > 0 ? `HandBrakeCLI dabei: ${werkzeuge.handbrake}` : 'HandBrakeCLI fehlt — Rippen geht, Komprimieren nicht.', }, { ok: werkzeuge === null ? null : werkzeuge.flac.length > 0, pflicht: false, text: werkzeuge === null ? 'FLAC wird gesucht …' : werkzeuge.flac.length > 0 ? `FLAC dabei: ${werkzeuge.flac}` : 'FLAC fehlt — nur für Audio-CDs nötig.', }, { ok: verbindung.laufwerke.length > 0 ? true : null, pflicht: false, text: verbindung.laufwerke.length > 0 ? `${verbindung.laufwerke.length} optisches Laufwerk gefunden (${verbindung.laufwerke.map((l) => l.id + ':').join(', ')})` : 'Kein optisches Laufwerk — nur eine Warnung: Eine reine Komprimier-Maschine ist ein vorgesehener Betriebsfall.', }, ] // Was Rippy an DIESER Maschine gemessen hat — die Grundlage der // Preset-Empfehlung (Commander-Fund 01.09.2026: sie war unsichtbar). const backends = werkzeuge?.backends ?? [] const presets = werkzeuge?.presets ?? [] const nurCpu = backends.length > 0 && backends.every((b) => b.startsWith('cpu')) const EMPFEHLUNGEN: ReadonlyArray = [ ['DVD', 'dvd'], ['Blu-ray', 'bluray'], ['4K-UHD', 'uhd'], ] return ( <>
{SCHRITTE.map((name, i) => ( {i + 1} {name} ))}
{schritt === 0 && (

Willkommen bei Rippy

Rippy sichert deine DVDs, Blu-rays und Audio-CDs — erst verlustfrei, dann komprimiert.

Disc rein genügt: Rippy erkennt, was drauf ist, und startet bei sicherer Erkennung von selbst.

Fertige Filme landen so abgelegt, dass Jellyfin, Emby, Kodi und Plex sie sofort erkennen.

Rippy prüft gleich seine Werkzeuge und fragt dich danach ein paar Dinge: wohin, wie schnell, woher die Titel kommen, wie es sich meldet. Dauert zwei Minuten — ändern kannst du alles später.

)} {schritt === 1 && (
{pruefzeilen.map((zeile, i) => (

{zeile.ok === true ? '✓' : zeile.ok === null ? '…' : zeile.pflicht ? '✗' : '⚠'} {zeile.text}

))}
{makemkvFehlt && ( setzen('werkzeug.makemkv', w)} /> )}
)} {schritt === 2 && (

Wohin sollen fertige Filme, Musik und Abbilder? Ein Netzlaufwerk geht — Rippy baut darin selbst die Struktur.

setzen('ablage', w)} /> {speicher !== null && speicher.ablage.ok &&

{gbKurz(speicher.ablage.freiBytes)} frei von {gbKurz(speicher.ablage.gesamtBytes)}

}

Liegt die Ablage auf einem Server, wähle einen Arbeitsordner auf einer lokalen Platte: Dort liegen die Roh-Dateien (30 GB und mehr je Blu-ray) und dort rechnet HandBrake. Nur das fertige Ergebnis wandert übers Netz.

setzen('arbeitsordner', w)} /> {speicher !== null && speicher.getrennt && speicher.arbeit.ok && (

{gbKurz(speicher.arbeit.freiBytes)} frei von {gbKurz(speicher.arbeit.gesamtBytes)} — eine Blu-ray braucht rund 50 GB.

)}
)} {schritt === 3 && (

Rippy sichert eine Disc immer erst verlustfrei. Danach kann es eine kleinere Fassung berechnen — die kostet Zeit, spart aber viel Platz.

setzen('transcodeEnabled', an ? 'true' : 'false')} label="Nach dem Rip automatisch komprimieren" />
{werkzeuge === null ? (

Encoder werden gemessen …

) : backends.length === 0 ? (

Keine Encoder gemessen — ohne HandBrakeCLI bleibt es beim verlustfreien Rip.

) : ( <>
AUF DIESER MASCHINE GEMESSEN: {backends.map((backend) => ( ))}

Danach wählt Rippy von selbst — Grafikkarte vor CPU, wo es eine gibt:

{EMPFEHLUNGEN.map(([name, typ]) => (

{name}

{presetEmpfehlung(typ, backends, presets) || DEFAULT_HB_PRESET}

))}
{nurCpu &&

Nur CPU-Encoder gefunden — das Komprimieren dauert dann deutlich länger. Der verlustfreie Rip ist davon nicht betroffen.

} )}
setzen('audioSprachen', w)} /> setzen('untertitelSprachen', w)} />

Je Disc-Typ ein eigenes Preset geht später unter „Einstellungen → Kompression" — deine Wahl schlägt die Empfehlung immer.

)} {schritt === 4 && (

Woher sollen Titel, Jahr, Poster und Beschreibung kommen? Dafür braucht Rippy einen kostenlosen TMDb-Schlüssel.

setzen('tmdbApiKey', w)} /> {tmdbDa && verbindung.poster.length > 0 &&

✓ Der Schlüssel funktioniert — die Poster im Hintergrund kommen von TMDb.

}

Ohne Schlüssel rippt Rippy trotzdem — die Filme heißen dann so, wie die Disc sich selbst nennt, und es gibt keine Poster. Nachtragen geht jederzeit unter „Einstellungen → Metadaten".

)} {schritt === 5 && (

Soll Rippy sich aufs Handy melden, wenn ein Rip fertig ist oder etwas schiefgeht? Windows-Meldungen kommen immer — das hier ist freiwillig.

setzen('discordWebhook', w)} />
setzen('telegramToken', w)} /> setzen('telegramChatId', w)} />
)} {schritt === 6 && (

Wie selbstständig soll Rippy sein?

setzen('automatik', an ? 'an' : 'aus')} label="Automatik: bei sicherer Erkennung (ab 90 %) nach einem Countdown von selbst rippen — mit Stopp-Knopf" /> setzen('autoAuswurf', an ? 'true' : 'false')} label="Disc nach dem Rip auswerfen — die nächste kann rein, während die Kompression läuft" />

Die Roh-Dateien sind der Grund, warum ein Preset-Wechsel kein Neulesen kostet — und gelöscht wird nur, wenn das fertige Ergebnis nachweislich da ist.

)} {schritt === 7 && (

Das war's. Disc einlegen — Rippy meldet sich.

Rippy lebt im Infobereich neben der Uhr: Fenster zu heißt weiterlaufen, beendet wird über das Tray-Menü.

{makemkvFehlt &&

Denk an MakeMKV: Ohne makemkvcon bleibt der Video-Weg zu (Einstellungen → Werkzeuge zeigt den Stand, sobald es installiert ist).

}
)}
{schritt < SCHRITTE.length - 1 ? ( ) : ( )}
) }