feat(transcode): ein HandBrake-Preset je Disc-Typ statt eines fuer alles
Ampel / ampel (push) Successful in 29s

Rueckfrage des Commanders beim ersten echten UHD-Rip: "merkt Rippy, dass es
eine UHD ist, und nimmt direkt das 4K-Preset?" Antwort war nein. Die
Kompression fragte den Disc-Typ ueberhaupt nicht:

    preset = einstellungen.get("transcodePreset") or DEFAULT_HB_PRESET

Live eingestellt war "HQ 1080p30 Surround". Der gerade laufende Akira-Rip
waere also verlustfrei in 4K gerippt und danach auf 1080p heruntergerechnet
worden - und mit keepOriginal=False waere der 4K-Rohschnitt danach geloescht
worden. Umgekehrt wurde eine DVD auf 1080p hochskaliert, was nichts bringt.

- preset_fuer(disc_type, einstellungen) in ripping.py, pure und getestet.
  Reihenfolge: Preset des Disc-Typs -> allgemeines transcodePreset ->
  DEFAULT_HB_PRESET. Bestandsinstallationen aendern ihr Verhalten NICHT,
  solange die neuen Felder nicht gespeichert sind.
- Drei Einstellungen: transcodePresetDvd / transcodePresetBluray /
  transcodePresetUhd. transcodePreset bleibt als Rueckfall bestehen.
- transcode_files holt den Disc-Typ aus dem Job-Datensatz und schreibt ihn
  mit ins Log ("Disc-Typ 'uhd', Preset '...'").
- UI: drei Auswahlfelder statt einem, mit Klartext dazu, warum eine 4K-UHD
  auf ein 2160p-Preset gehoert.

Preset-Namen stammen aus "HandBrakeCLI --preset-list" im Worker-Image
(HandBrake 1.6.1) - nicht aus dem Kopf (AGENTS Regel D):
H.265 MKV 2160p60 4K, HQ 2160p60 4K HEVC Surround,
Super HQ 2160p60 4K HEVC Surround, H.265 MKV 1080p30, HQ 1080p30 Surround,
Super HQ 1080p30 Surround, H.265 MKV 576p25, H.265 MKV 480p30,
HQ 576p25 Surround.

Sofortmassnahme am laufenden Job (auf Ansage des Commanders): keepOriginal
auf True gesetzt - nur dieses eine Feld, gegengeprueft dass kein anderer
Schluessel veraendert wurde. Damit ueberlebt der 4K-Rohschnitt die
Kompression.

ACHTUNG - Deploy bewusst NICHT ausgefuehrt: docker compose up -d --build
wuerde den Worker-Container neu erstellen und den laufenden Akira-Rip
abbrechen. Erst nach Abschluss des Jobs deployen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-25 11:38:52 +02:00
parent f449c4ee34
commit e84afc1718
7 changed files with 203 additions and 15 deletions
+7 -1
View File
@@ -63,7 +63,13 @@ NICHT als emuliertes CD-ROM (`media=cdrom`), das kann keine SCSI-Kommandos.
per Laufzeitabgleich (TMDB) automatisch zu „Serie S01E02.mkv" benannt
(nur bei eindeutiger Zuordnung — sonst bleiben die Namen, mit Log).
3. **Kompression** (HandBrake, eigener Job auf eigener Queue): x265/x264,
Preset im UI wählbar; Rohdatei wird erst nach Erfolg gelöscht
**je Disc-Typ ein eigenes Preset** (Einstellungen → Verarbeitung) —
Rippy kennt den Typ und wählt selbst. Wichtig, weil ein 1080p-Preset
eine 4K-UHD stillschweigend herunterrechnet und ein 1080p-Preset eine
DVD sinnlos hochskaliert. Sind die drei Felder nie gespeichert worden,
gilt weiter das alte allgemeine Preset — Bestandsinstallationen ändern
ihr Verhalten also nicht von selbst.
Rohdatei wird erst nach Erfolg gelöscht
(„Original behalten" als Option). Fehlgeschlagene Kompressionen lassen
sich ohne Neu-Rip neu anstoßen.
4. **Media-Server-Ablage**: Unter Einstellungen → Ripping (oder im
+29
View File
@@ -541,6 +541,35 @@ completed", `TCOUNT:5`, fünf Titel, identisch zum Windows-Ergebnis.
---
## Etappe 19 (25.07.2026): Preset je Disc-Typ
**Quelle:** Rückfrage des Commanders beim ersten echten UHD-Rip („merkt
Rippy eigentlich, wenn es eine UHD-Disc ist, und wendet direkt das
4K-Preset an?"). Antwort war: nein. Die Kompression fragte den Disc-Typ
gar nicht — ein globales `transcodePreset` galt für alles, live eingestellt
`HQ 1080p30 Surround`. Der laufende 4K-Rip wäre danach auf 1080p
heruntergerechnet und der Rohschnitt gelöscht worden (`keepOriginal: False`).
**Gebaut:**
- [x] **`preset_fuer(disc_type, einstellungen)`** in `ripping.py` — pure
Funktion, drei Tests. Reihenfolge: Preset des Disc-Typs → allgemeines
`transcodePreset``DEFAULT_HB_PRESET`. Bestandsinstallationen
ändern ihr Verhalten NICHT, solange die neuen Felder ungespeichert sind.
- [x] **`transcode_files`** holt den Disc-Typ aus dem Job-Datensatz und
schreibt ihn mit ins Log („Disc-Typ 'uhd', Preset '…'").
- [x] **UI**: drei Auswahlfelder (DVD / Blu-ray / 4K-UHD) statt einem, mit
Erklärung, warum 4K auf ein 2160p-Preset gehört. Preset-Namen aus
`HandBrakeCLI --preset-list` im Worker-Image belegt, nicht geraten.
- [x] **Sofortmaßnahme am laufenden Job**: `keepOriginal` auf `True`, damit
der 4K-Rohschnitt die Kompression überlebt.
**Offen aus dieser Runde:**
- [ ] **Deploy steht aus** — er würde den laufenden Akira-Rip abbrechen.
Erst nach Abschluss des Jobs deployen, dann bei Bedarf „Neu
komprimieren" mit dem 4K-Preset.
---
## Ideen-Katalog (Rest) — bewusst offen
1. **Design 2.0** — an Gemini übergeben (24.07.2026). Vollständiges
+36 -1
View File
@@ -1,6 +1,41 @@
# SAVEPOINT — Rippy
## Aktueller Stand: v3.114K-UHD GELÖST: Akira geht auf (25.07.2026)
## Aktueller Stand: v3.12Preset je Disc-Typ (25.07.2026)
- **Der Fund:** Beim ersten echten UHD-Rip aufgefallen — die Kompression
fragte den Disc-Typ **gar nicht**: `preset = einstellungen.get(
"transcodePreset")`, ein globales Preset für alles. Live eingestellt war
`HQ 1080p30 Surround`. Der laufende Akira-Rip wäre also verlustfrei in 4K
gerippt und danach **auf 1080p heruntergerechnet** worden — und mit
`keepOriginal: False` wäre der 4K-Rohschnitt anschließend gelöscht worden.
Aufgefallen ist es nur, weil der Commander gefragt hat, ob Rippy das
UHD-Preset automatisch nimmt.
- **Sofortmaßnahme am laufenden Job:** `keepOriginal` auf `True` gesetzt
(nur dieses eine Feld, gegengeprüft: kein anderer Schlüssel verändert).
Damit überlebt der 4K-Rohschnitt die Kompression auf jeden Fall.
- **Gebaut:** `preset_fuer(disc_type, einstellungen)` in `ripping.py` (pure,
getestet) plus drei Einstellungen `transcodePresetDvd` / `…Bluray` /
`…Uhd`. Reihenfolge: Preset des Disc-Typs → allgemeines
`transcodePreset``DEFAULT_HB_PRESET`. **Bestandsinstallationen ändern
ihr Verhalten nicht**, solange die neuen Felder nicht gespeichert sind.
`transcode_files` holt den Disc-Typ aus dem Job-Datensatz und schreibt ihn
mit ins Log.
- **UI (Einstellungen → Verarbeitung):** drei Auswahlfelder statt einem, mit
Klartext dazu, warum 4K auf ein 2160p-Preset gehört. Alle Preset-Namen
stammen aus `HandBrakeCLI --preset-list` im Worker-Image (1.6.1) — nicht
geraten (AGENTS Regel D).
- **⚠️ Deploy bewusst zurückgehalten:** `docker compose up -d --build`
würde den Worker-Container neu erstellen und den **laufenden Akira-Rip
abbrechen**. Erst deployen, wenn der Job durch ist. Der 4K-Rohschnitt ist
durch `keepOriginal` geschützt; danach reicht „Neu komprimieren" im UI,
um mit dem richtigen Preset in 4K zu komprimieren.
- **Nebenbefund:** `ps` gibt es im Worker-Image nicht (python-slim). Frühere
Prüfungen auf laufende Rips per `ps | grep` lieferten deshalb still
„nichts aktiv" — richtig geht es über `/proc`.
---
## Vorheriger Stand: v3.11 — 4K-UHD GELÖST: Akira geht auf (25.07.2026)
- **🎉 Der Durchbruch:** Nach Übernahme des Schlüsselspeichers öffnet
`makemkvcon` auf der VM die Akira-UHD: „Operation successfully
+56 -10
View File
@@ -25,7 +25,13 @@ interface SettingsState {
autoRipStart: boolean
notificationWebhook: string
transcodeEnabled: boolean
// transcodePreset bleibt als Rückfall für Bestandsinstallationen bestehen —
// solange die drei Felder darunter nicht gespeichert sind, gilt weiter dieser
// Wert (siehe preset_fuer() in docker/worker/ripping.py).
transcodePreset: string
transcodePresetDvd: string
transcodePresetBluray: string
transcodePresetUhd: string
keepOriginal: boolean
mediaServer: string
jellyfinUrl: string
@@ -49,6 +55,9 @@ const defaultSettings: SettingsState = {
notificationWebhook: '',
transcodeEnabled: true,
transcodePreset: 'H.265 MKV 1080p30',
transcodePresetDvd: 'H.265 MKV 576p25',
transcodePresetBluray: 'H.265 MKV 1080p30',
transcodePresetUhd: 'H.265 MKV 2160p60 4K',
keepOriginal: false,
mediaServer: 'none',
jellyfinUrl: '',
@@ -547,16 +556,53 @@ export default function SettingsPage() {
/>
</div>
<Select
label="HandBrake-Preset"
value={settings.transcodePreset}
onChange={(e) => handleChange('transcodePreset', e.target.value)}
>
<option value="H.265 MKV 1080p30">H.265 MKV 1080p30 klein &amp; modern (Standard)</option>
<option value="H.265 MKV 2160p60 4K">H.265 MKV 2160p60 4K für UHD-Quellen</option>
<option value="HQ 1080p30 Surround">HQ 1080p30 Surround H.264, maximale Kompatibilität</option>
<option value="Super HQ 1080p30 Surround">Super HQ 1080p30 Surround größer, beste Qualität</option>
</Select>
{/*
Ein Preset JE DISC-TYP (25.07.2026). Vorher lief alles durch
dasselbe Preset — eine 4K-UHD wurde damit auf 1080p
heruntergerechnet und eine DVD auf 1080p hochskaliert.
Preset-Namen stammen aus `HandBrakeCLI --preset-list` im
Worker-Image (HandBrake 1.6.1), nicht aus dem Kopf.
*/}
<div className="space-y-3">
<p className="text-xs text-slate-500 dark:text-slate-400">
Ein Preset je Quelle Rippy erkennt den Disc-Typ selbst und wählt passend aus.
<strong className="text-slate-700 dark:text-slate-300"> Eine 4K-UHD gehört auf ein
2160p-Preset</strong>, sonst rechnet HandBrake sie auf 1080p herunter und die
4K-Auflösung ist weg.
</p>
<Select
label="Preset für 4K-UHD"
value={settings.transcodePresetUhd}
onChange={(e) => handleChange('transcodePresetUhd', e.target.value)}
>
<option value="H.265 MKV 2160p60 4K">H.265 MKV 2160p60 4K 4K erhalten (empfohlen)</option>
<option value="HQ 2160p60 4K HEVC Surround">HQ 2160p60 4K HEVC Surround 4K, mehr Qualität</option>
<option value="Super HQ 2160p60 4K HEVC Surround">Super HQ 2160p60 4K HEVC Surround 4K, größer</option>
<option value="H.265 MKV 1080p30">H.265 MKV 1080p30 bewusst auf 1080p verkleinern</option>
</Select>
<Select
label="Preset für Blu-ray"
value={settings.transcodePresetBluray}
onChange={(e) => handleChange('transcodePresetBluray', e.target.value)}
>
<option value="H.265 MKV 1080p30">H.265 MKV 1080p30 klein &amp; modern (empfohlen)</option>
<option value="HQ 1080p30 Surround">HQ 1080p30 Surround H.264, maximale Kompatibilität</option>
<option value="Super HQ 1080p30 Surround">Super HQ 1080p30 Surround größer, beste Qualität</option>
</Select>
<Select
label="Preset für DVD"
value={settings.transcodePresetDvd}
onChange={(e) => handleChange('transcodePresetDvd', e.target.value)}
>
<option value="H.265 MKV 576p25">H.265 MKV 576p25 PAL-DVD, native Auflösung (empfohlen)</option>
<option value="H.265 MKV 480p30">H.265 MKV 480p30 NTSC-DVD, native Auflösung</option>
<option value="HQ 576p25 Surround">HQ 576p25 Surround H.264, maximale Kompatibilität</option>
<option value="H.265 MKV 1080p30">H.265 MKV 1080p30 hochskalieren (bringt nichts)</option>
</Select>
</div>
<div className="p-4 rounded-xl border border-slate-200/80 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-950/80">
<Toggle
+27
View File
@@ -225,6 +225,33 @@ def check_handbrake_installed() -> bool:
DEFAULT_HB_PRESET = "H.265 MKV 1080p30"
# Ein Preset je Disc-Typ (Befund 25.07.2026). Vorher galt EIN Preset für
# alles: eine 4K-UHD wurde damit auf 1080p heruntergerechnet und eine DVD auf
# 1080p hochskaliert — beides Unsinn, und beim ersten UHD-Rip wäre die 4K-
# Auflösung still verlorengegangen. Den Disc-Typ kennt Rippy ohnehin.
PRESET_SCHLUESSEL = {
"dvd": "transcodePresetDvd",
"bluray": "transcodePresetBluray",
"uhd": "transcodePresetUhd",
}
def preset_fuer(disc_type: str, einstellungen: dict) -> str:
"""Welches HandBrake-Preset gilt für diesen Disc-Typ? (pure, testbar)
Reihenfolge: Preset des Disc-Typs allgemeines transcodePreset
DEFAULT_HB_PRESET. Diese Kette hält Bestandsinstallationen am Leben, in
denen nur `transcodePreset` gesetzt ist: solange der Nutzer die neuen
Felder nicht speichert, ändert sich sein Verhalten nicht.
"""
einstellungen = einstellungen or {}
schluessel = PRESET_SCHLUESSEL.get(disc_type or "")
if schluessel:
eigen = (einstellungen.get(schluessel) or "").strip()
if eigen:
return eigen
return (einstellungen.get("transcodePreset") or "").strip() or DEFAULT_HB_PRESET
def build_handbrake_cmd(input_path: str, output_path: str, preset: str = DEFAULT_HB_PRESET) -> list:
"""Baut das HandBrake-Kommando für die Kompressions-Stufe (pure Funktion).
+8 -3
View File
@@ -35,11 +35,11 @@ except ImportError: # Windows: kein fcntl
disc_size_bytes = None
from ripping import (
DEFAULT_HB_PRESET,
RIP_OUTPUT_DIR,
RipAbbruch,
lies_datei_dauer,
lies_titel_info,
preset_fuer,
rip_cd,
rip_video,
run_handbrake,
@@ -565,14 +565,19 @@ def transcode_files(self, job_id: str, raw_dir: str, final_dir: str):
return ergebnis
einstellungen = db.get_settings()
preset = einstellungen.get("transcodePreset") or DEFAULT_HB_PRESET
# Preset nach Disc-Typ (Befund 25.07.2026): vorher lief JEDE Quelle durch
# dasselbe Preset — eine 4K-UHD wurde damit auf 1080p heruntergerechnet.
job = db.get_job(job_id) or {}
disc_type = job.get("disc_type") or ""
preset = preset_fuer(disc_type, einstellungen)
original_behalten = einstellungen.get("keepOriginal", False)
os.makedirs(final_dir, exist_ok=True)
db.update_job(job_id, status="transcoding", progress=0, error=None)
db.add_log(
"info", "worker",
f"Job {job_id}: Kompression gestartet ({len(quellen)} Datei(en), Preset '{preset}')",
f"Job {job_id}: Kompression gestartet ({len(quellen)} Datei(en), "
f"Disc-Typ '{disc_type or 'unbekannt'}', Preset '{preset}')",
)
anzahl = len(quellen)
+40
View File
@@ -138,3 +138,43 @@ def test_abcde_config_enthaelt_zielverzeichnis():
assert "INTERACTIVE=n" in inhalt
finally:
os.unlink(pfad)
def test_preset_fuer_nimmt_das_preset_des_disc_typs():
# Befund 25.07.2026: vorher galt EIN Preset fuer alles — eine 4K-UHD wurde
# damit auf 1080p heruntergerechnet, und beim ersten echten UHD-Rip waere
# die 4K-Aufloesung still verlorengegangen.
from ripping import preset_fuer
einstellungen = {
"transcodePreset": "HQ 1080p30 Surround",
"transcodePresetDvd": "H.265 MKV 576p25",
"transcodePresetBluray": "H.265 MKV 1080p30",
"transcodePresetUhd": "H.265 MKV 2160p60 4K",
}
assert preset_fuer("uhd", einstellungen) == "H.265 MKV 2160p60 4K"
assert preset_fuer("bluray", einstellungen) == "H.265 MKV 1080p30"
assert preset_fuer("dvd", einstellungen) == "H.265 MKV 576p25"
def test_preset_fuer_faellt_auf_das_allgemeine_preset_zurueck():
# Bestandsinstallationen kennen die drei neuen Felder nicht. Solange der
# Nutzer sie nicht speichert, MUSS sich sein Verhalten nicht aendern.
from ripping import preset_fuer
alt = {"transcodePreset": "HQ 1080p30 Surround"}
assert preset_fuer("uhd", alt) == "HQ 1080p30 Surround"
assert preset_fuer("dvd", alt) == "HQ 1080p30 Surround"
# Leere Zeichenkette zaehlt als "nicht gesetzt" (leeres Select-Feld im UI)
assert preset_fuer("uhd", {"transcodePresetUhd": " ", "transcodePreset": "X"}) == "X"
def test_preset_fuer_ohne_einstellungen_nimmt_den_eingebauten_standard():
# Unbekannter Disc-Typ, leere oder fehlende Einstellungen: nie None, nie
# Absturz — sonst stirbt die Kompression an einem leeren --preset-Argument.
from ripping import DEFAULT_HB_PRESET, preset_fuer
assert preset_fuer("uhd", {}) == DEFAULT_HB_PRESET
assert preset_fuer("", None) == DEFAULT_HB_PRESET
assert preset_fuer(None, {}) == DEFAULT_HB_PRESET
assert preset_fuer("cd", {"transcodePresetUhd": "egal"}) == DEFAULT_HB_PRESET