Die Disc gab keine Titel her.
)}+ Leer heißt: Rippy ordnet die Folgen über die TMDb-Laufzeiten zu, und nur, wenn es eindeutig ist. + Weißt du es besser („auf Disc 2 sind die Folgen 3 und 4"), trag es hier ein — dann zählt Rippy + fortlaufend in Disc-Reihenfolge. + {info !== undefined && info.discNr !== null && ` Die Disc nennt sich selbst Disc ${info.discNr}.`} +
+LANDET IN
+{vorschau[0]}
+{vorschau[1]}
+- {aktiv.size} von {alle.length} gewählt · zusammen {gb(gesamtBytes)} + {aktiv.size} von {alle.length} gewählt · zusammen {gb(gesamtBytes)} verlustfrei + {regel.length > 0 ? ` · ${regel}` : ''}
- {alle.some((t) => aktiv.has(t.nr) && rolleVon(t) === 'folge') - ? 'Folgen landen unter Serien/… /Season NN. Die Episoden-Nummern setzt Rippy nur, wenn die Laufzeiten eindeutig passen — sonst behalten die Dateien ihre Namen.' + {alsSerie + ? 'Folgen landen unter Serien/… /Season NN. Ohne Vorgabe setzt Rippy die Episoden-Nummern nur, wenn die Laufzeiten eindeutig passen — sonst behalten die Dateien ihre Namen.' : 'Der Hauptfilm landet in seinem eigenen Ordner, Extras daneben im Unterordner „extras" — so lesen Jellyfin, Emby und Kodi sie als Zugaben.'} + {' '}Das Laufwerk ist frei, sobald die Roh-Dateien liegen — die Kompression läuft danach im Hintergrund.
> )} @@ -555,9 +768,18 @@ function TitelWahlDialog({ onClick={() => { ripStarten( id, - alle - .filter((t) => aktiv.has(t.nr)) - .map((t) => ({ nr: t.nr, rolle: rolleVon(t), dauerS: t.dauerS })), + gewaehlteTitel.map((t) => { + const wahl = sprachenVon(t) + return { + nr: t.nr, + rolle: rolleVon(t), + dauerS: t.dauerS, + groesseBytes: t.groesseBytes, + audioSprachen: wahl.audio, + untertitelSprachen: wahl.untertitel, + } + }), + { staffel: alsSerie ? staffelZahl : null, ersteFolge: alsSerie ? folgeZahl : null }, ) schliessen() }} @@ -576,29 +798,51 @@ function LaufwerkKachel({ geraet, rip, info, - vorschlaege, ripStartMs, protokoll, verbindung, - titelStand, + bericht, + vorgang, + kompression, + wartePlatz, + aufRippen, + aufAendern, }: { geraet: GeraeteInfo rip: RipStatus | undefined info: DiscInfoStand | undefined - vorschlaege: MetaVorschlag[] ripStartMs: number | undefined protokoll: string[] verbindung: Verbindung - titelStand: TitelStand | undefined + /** 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 + /** 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 [aendernOffen, setAendernOffen] = useState(false) - const [titelOffen, setTitelOffen] = useState(false) 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 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-white/12 bg-samt/60' return ({info.tagline}
+ )} + {erkannt && metaZeile(info).length > 0 && ( +{metaZeile(info)}
+ )} {erkannt && info.beschreibung.length > 0 && ({info.beschreibung}
)} @@ -725,9 +987,35 @@ function LaufwerkKachel({+ {h.includes('NICHT') ? '⚠ ' : h.includes('bestätigt') ? '✓ ' : '· '} + {h} +
+ ))} +{geraet.grund}
}+ + {bericht.urteil === 'ruhig' + ? 'LAUFWERK RUHIG' + : bericht.urteil === 'disc' + ? 'VERDACHT: DIE DISC' + : bericht.urteil === 'hardware' + ? 'VERDACHT: DIE HARDWARE' + : 'PROTOKOLL'} + {' '} + — {bericht.text} +
+ ++ {z} +
+ ))} ++ KOMPRESSION LÄUFT{kompression.prozent >= 0 ? ` — ${kompression.prozent} %` : ''} +
++ > {kompression.text} +
+ > + ) : wartePlatz > 0 ? ( ++ Kompression wartet — Platz {wartePlatz} in der Warteschlange. +
+ ) : vorgang.phase === 'fertig' ? ( ++ ✓ {vorgang.komprimiert ? 'Komprimiert und abgelegt' : 'Verlustfrei abgelegt'}: {vorgang.zielOrdner} + {vorgang.fertigBytes > 0 && ` (${gb(vorgang.fertigBytes)})`} +
+ ) : vorgang.phase === 'fehler' || vorgang.phase === 'abgebrochen' ? ( +⚠ {vorgang.fehler.length > 0 ? vorgang.fehler : `Kompression ${vorgang.phase}`}
+ ) : ( +Kompression: {vorgang.phase}
+ )} +{l.titel}
++ > {l.text} +
+Belegung wird gemessen …
+ ) : ( + <> ++ Belegung jetzt:{' '} + {gb(v.rohGesamtBytes)} in{' '} + {liste.filter((e) => !e.rohGeloescht).length + v.fremde.length} Roh-Ordnern unter {werte['ablage'] ?? ''}\roh +
+{v.regel}
++ Unkomprimierte Rips (z. B. 4K verlustfrei) liegen im Zielordner, nicht hier — sie sind nie betroffen. +
+ > + )} +Noch kein Vorgang — der erste Rip legt hier einen Eintrag an.
+ )} ++ Roh:{' '} + {e.rohGeloescht ? 'gelöscht' : gb(e.rohBytes)} + {e.faelligAm.length > 0 && ` · fällig ${datumKurz(e.faelligAm)}`} + {' · '} + Fertig:{' '} + {e.phase === 'fertig' ? ( + + {e.fertigBytes > 0 ? `${gb(e.fertigBytes)} · ` : ''} + {e.zielOrdner} + {!e.komprimiert && ' (verlustfrei, ohne Kompression)'} + + ) : ( + '—' + )} +
+ {e.fehler.length > 0 &&⚠ {e.fehler}
} +ORDNER OHNE VORGANG
++ Aus der Zeit vor 5.4.0 oder von Hand hineingelegt — Rippy kennt sie nicht und löscht sie deshalb NIE von selbst. +
+├─ Filme\Akira (1988)\ — Film, Poster und Infodatei beieinander
├─ Musik\ — Audio-CDs als FLAC, benannt nach MusicBrainz
├─ ISO\ — unerkannte Discs als 1:1-Abbild
-└─ roh\ — Arbeitsstand während des Rips, räumt sich selbst auf
+├─ Serien\Spartacus\Season 01\ — Folgen, wie Jellyfin sie erwartet
+└─ roh\ — verlustfreies Roh-Material, aufgeräumt nach der Regel unter „Roh-Dateien"
+Updates warten, solange ein Rip läuft — nie mitten im Vorgang.
++ Datenbank: {verbindung.kern !== null && verbindung.kern.datenbank.ok ? verbindung.kern.datenbank.pfad : '—'} +
+Updates warten, solange ein Rip oder eine Kompression läuft — nie mitten im Vorgang.