Ampel / ampel (push) Successful in 1m27s
Vier Fragen des Commanders vom 29.08.2026, drei davon mit Codefolge.
## 1. „Was passiert wenn man die Setup.exe einfach drueber installiert?"
Bis hierher: nicht zuverlaessig. Rippy startet mit Windows, laeuft also fast
immer — dann ist `Rippy.exe` gesperrt, `shutil.copy2` warf PermissionError,
und die neue Fassung landete als `Rippy.exe.neu` daneben. Dazu die Meldung
„wird beim naechsten Start uebernommen".
**Diese Zusage hat niemand eingeloest.** `.neu` kam im ganzen Projekt genau
einmal vor: an der Stelle, die es schrieb. Wer drueberinstallierte, behielt
still die alte Fassung, und das Setup meldete Erfolg.
Jetzt wird der laufende Rippy vorher beendet (`dienst_beenden` gibt es seit
der Deinstallation und wartet auch die zwei Sekunden ab, die Windows fuer die
Dateihandles braucht). Eine von einer aelteren Setup-Fassung liegengelassene
`.neu` wird dabei uebernommen. Bleibt die Datei DANN noch gesperrt, gibt es
einen klaren Fehler statt einer Zusage — Rippy im Infobereich beenden und das
Setup erneut starten.
Der Tausch laeuft bewusst im SETUP und nicht beim Dienststart: Windows sperrt
eine laufende .exe, und `Rippy.exe` waere genau die zu ersetzende Datei.
## 3. Linux-Reste im Windows-Betrieb (Docker/Headless unveraendert)
**`caps.py`: `os.path.isdir("/app")`.** Damit hielt sich der eigenstaendige
Windows-Rippy fuer einen FREMDEN Worker — und das UI warnte vor fehlender
Pfad-Uebersetzung auf einer Maschine ohne Container und ohne Freigabe.
„Extern" heisst jetzt, was es meint: Rippy laeuft woanders als dieser Worker.
**`caps.py`: `shutil.which("makemkvcon")` + `os.path.ismount(daten_dir)`.**
Beide unter Windows immer falsch (Programme liegen nicht im PATH, ein
normaler Ordner ist kein Mount). Die Schluessel-Auskunft blieb dauerhaft
„unbekannt", obwohl MakeMKV samt Datenverzeichnis da war. Der Mount-Test
bleibt fuer den Container, wo er einen Zweck hat.
**`rohdaten.py`: `/app/temp/raw` und `/app/media` fest.** Dieses Modul findet
die Rohdaten eines Jobs wieder — fuer den Wiederholen-Dialog und fuer
„Rohdaten mitloeschen". Unter Windows fand es NIE etwas: Der Dialog meldete
„keine Rohdaten", das Aufraeumen loeschte nichts, und die Bruchstuecke eines
abgebrochenen Rips blieben liegen (bei 4K-UHD bis 100 GB).
Sieben Tests wurden dabei rot, und zwar zu Recht: Sie pruefen Container-Regeln,
liefen aber unter Windows. Die Wurzeln sind jetzt einspritzbar — beide
Betriebsfaelle auf jedem Rechner pruefbar statt vom laufenden abhaengig.
## 4. „Der Durchsuchen button fehlt. Wie es der Installer auch macht"
Neu: `OrdnerWaehler` — Pfadfeld plus „Durchsuchen …", benutzt fuer Ablage und
Arbeitsverzeichnis. Es waere der DRITTE fest eingebaute Ordner-Browser
geworden (RipTargetModal, StorageMounts); dieser hier ist wiederverwendbar.
`/browse` weiss seit dem 28.08. selbst, in welchem Betrieb es laeuft.
⚠️ Beim Einbau fiel der Import unter den Tisch. `vite` pruefte das NICHT — das
Buendel blieb byte-gleich gross, und zur Laufzeit waere es der naechste leere
Bildschirm gewesen. Aufgefallen nur, weil die erwartete Anzahl Ersetzungen
nicht stimmte. Im Browser gegengeprueft: alle sieben Laufwerke, Navigation in
D:\, keine Konsolenfehler.
843 Tests gruen, ruff sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
247 lines
9.9 KiB
Python
247 lines
9.9 KiB
Python
"""Tests der Fähigkeiten-Erkennung — reine Funktionen, keine Infrastruktur.
|
|
|
|
Die Testdaten sind ECHTE Ausgaben, am 25.07.2026 im Worker-Image der Rippy-VM
|
|
abgenommen (AGENTS Regel D). Erfundene Formate hätten hier keinen Wert: genau
|
|
daran ist die Erkennung vorher gescheitert.
|
|
"""
|
|
|
|
import caps
|
|
|
|
# Wörtlich aus `docker exec rippy-worker-1 HandBrakeCLI --help` (HandBrake im
|
|
# Worker-Image, 25.07.2026). Bemerkenswert: KEIN Hardware-Encoder dabei.
|
|
HB_HILFE_ECHT = """
|
|
-Z, --preset <string> Select preset by name
|
|
-e, --encoder <string> Select video encoder:
|
|
svt_av1
|
|
svt_av1_10bit
|
|
x264
|
|
x264_10bit
|
|
x265
|
|
x265_10bit
|
|
x265_12bit
|
|
mpeg4
|
|
mpeg2
|
|
VP8
|
|
VP9
|
|
VP9_10bit
|
|
theora
|
|
--encoder-preset <string>
|
|
Adjust video encoding settings
|
|
"""
|
|
|
|
# Wie dieselbe Stelle auf einer Maschine MIT Hardware-Encodern aussieht
|
|
# (HandBrake-Windows-Build; Struktur identisch, nur mehr Namen).
|
|
HB_HILFE_MIT_HARDWARE = """
|
|
-e, --encoder <string> Select video encoder:
|
|
x264
|
|
x265
|
|
nvenc_h264
|
|
nvenc_h265
|
|
qsv_h264
|
|
qsv_h265
|
|
--encoder-preset <string>
|
|
"""
|
|
|
|
|
|
def test_encoder_liste_wird_aus_echter_hilfe_gelesen():
|
|
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
|
|
assert encoder == [
|
|
"svt_av1", "svt_av1_10bit",
|
|
"x264", "x264_10bit",
|
|
"x265", "x265_10bit", "x265_12bit",
|
|
"mpeg4", "mpeg2", "VP8", "VP9", "VP9_10bit", "theora",
|
|
]
|
|
# Der Block endet an der nächsten Option — sie darf nicht mitgelesen werden
|
|
assert not any("encoder-preset" in e for e in encoder)
|
|
assert not any(e.startswith("-") for e in encoder)
|
|
|
|
|
|
def test_encoder_liste_ohne_handbrake_ist_leer():
|
|
assert caps.parse_encoder_liste("") == []
|
|
assert caps.parse_encoder_liste(None) == []
|
|
assert caps.parse_encoder_liste("irgendein Text ohne Encoder-Abschnitt") == []
|
|
|
|
|
|
def test_ohne_handbrake_keine_encoder():
|
|
"""Der wichtigste Fall: vorher standen cpu-x264/cpu-x265 fest verdrahtet in
|
|
der Liste. Ein Rip-Worker ohne HandBrake behauptete damit, komprimieren zu
|
|
können — und jeder Transcode dort endete sofort mit 'nicht installiert'."""
|
|
assert caps.leite_backends_ab([]) == []
|
|
assert caps.leite_backends_ab(None) == []
|
|
|
|
|
|
def test_backends_aus_echter_liste():
|
|
"""Das Worker-Image der VM kann laut eigener Auskunft keine Hardware —
|
|
entsprechend steht hier nur CPU."""
|
|
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
|
|
assert caps.leite_backends_ab(encoder) == ["cpu-x264", "cpu-x265", "cpu-av1"]
|
|
|
|
|
|
def test_handbrakes_liste_ist_die_auskunft_ohne_geraetepruefung():
|
|
"""Regel-Wechsel 25.07.2026, weil die alte Fassung einen Bug hatte.
|
|
|
|
Vorher wurde zusätzlich ein Gerät verlangt: /dev/dri für VAAPI/QSV/VCE bzw.
|
|
nvidia-smi für NVENC. Unter WINDOWS gibt es beides nicht — der PC des
|
|
Commanders (RX 9070 XT) meldete deshalb nur CPU-Encoder, obwohl HandBrakes
|
|
Windows-Build vce_*/nvenc_*/qsv_* beherrscht. Der Bug traf genau den
|
|
Anwendungsfall, für den externe Worker gedacht sind.
|
|
|
|
Die Prüfung war zudem überflüssig: HandBrake probiert Hardware beim Start
|
|
selbst an und listet nur Nutzbares (auf der VM belegt — 'qsv: not available
|
|
on this system', und qsv_* fehlt dann in --help). Wer die Liste hat, hat die
|
|
Antwort; eine zweite Meinung braucht es nicht.
|
|
"""
|
|
mit_hw = caps.parse_encoder_liste(HB_HILFE_MIT_HARDWARE)
|
|
backends = caps.leite_backends_ab(mit_hw)
|
|
assert "nvenc" in backends
|
|
assert "qsv" in backends
|
|
# Ohne Geräteprüfung — es gibt keine Parameter mehr, die das verhindern
|
|
assert caps.leite_backends_ab(mit_hw) == backends
|
|
|
|
|
|
def test_hardware_wird_nach_familie_unterschieden():
|
|
"""Vorher landete alles in „vaapi" — eine AMD-Karte lief damit unter dem
|
|
Intel-Namen, und das UI konnte nicht sagen, welches Preset passt."""
|
|
amd = caps.leite_backends_ab(["x265", "vce_h264", "vce_h265"])
|
|
assert "vce" in amd
|
|
assert "qsv" not in amd and "nvenc" not in amd
|
|
|
|
intel = caps.leite_backends_ab(["x265", "qsv_h265"])
|
|
assert "qsv" in intel and "vce" not in intel
|
|
|
|
|
|
def test_hardware_av1_bekommt_eigene_kennung():
|
|
"""AV1 in Hardware ist derzeit die beste Kombination aus Tempo und
|
|
Dateigröße — das darf nicht in „nvenc" verschwinden, sonst kann das UI es
|
|
nicht empfehlen."""
|
|
backends = caps.leite_backends_ab(["x265", "nvenc_h265", "nvenc_av1"])
|
|
assert "nvenc" in backends
|
|
assert "nvenc-av1" in backends
|
|
# Ohne AV1 in der Liste gibt es die Kennung auch nicht
|
|
assert "nvenc-av1" not in caps.leite_backends_ab(["nvenc_h265"])
|
|
|
|
|
|
def test_simd_stufe_erkennt_die_hoechste():
|
|
assert caps.beste_simd("fpu vme de sse4_2 avx avx2") == "avx2"
|
|
assert caps.beste_simd("sse4_2 avx avx2 avx512f") == "avx512f"
|
|
assert caps.beste_simd("fpu vme de pse tsc sse4_2") == "sse4_2"
|
|
assert caps.beste_simd("") == "unbekannt"
|
|
assert caps.beste_simd(None) == "unbekannt"
|
|
|
|
|
|
def test_simd_stufe_der_rippy_vm_ist_sse4_2():
|
|
"""Echt gemessen auf der Rippy-VM (25.07.2026): generisches QEMU-CPU-Modell,
|
|
`grep -c avx2 /proc/cpuinfo` = 0. Genau das macht Software-4K dort so lahm."""
|
|
flags_vm = (
|
|
"fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 "
|
|
"clflush mmx fxsr sse sse2 syscall nx lm rep_good nopl cpuid "
|
|
"pni cx16 sse4_1 sse4_2 x2apic popcnt aes hypervisor lahf_lm"
|
|
)
|
|
assert caps.beste_simd(flags_vm) == "sse4_2"
|
|
|
|
|
|
# --- Preset-Liste: die Namen, die HandBrake wirklich kennt -------------------
|
|
|
|
# Wörtlich aus `HandBrakeCLI --preset-list` im Worker-Image (26.07.2026,
|
|
# HandBrake 1.6.1) — gekürzt auf je zwei Presets pro Kategorie, Struktur und
|
|
# Einrückung unverändert. Die zwei Logzeilen oben stehen dort wirklich mit
|
|
# drin (HandBrake schreibt sie in denselben Strom) und müssen wegfallen.
|
|
HB_PRESETS_ECHT = """[10:26:05] Compile-time hardening features are enabled
|
|
[10:26:05] qsv: not available on this system
|
|
Cannot load libnvidia-encode.so.1
|
|
General/
|
|
Very Fast 2160p60 4K AV1
|
|
AV1 video (up to 2160p60) and AAC stereo audio, in an MP4
|
|
container.
|
|
Super HQ 1080p30 Surround
|
|
High quality H.264 video (up to 1080p30) and high bit rate AAC
|
|
surround audio, in an MP4 container.
|
|
Matroska/
|
|
H.265 MKV 2160p60 4K
|
|
H.265 video (up to 2160p60) and surround audio, in an MKV
|
|
container.
|
|
H.265 MKV 1080p30
|
|
H.265 video (up to 1080p30) and surround audio, in an MKV
|
|
container.
|
|
Hardware/
|
|
AV1 QSV 2160p 4K
|
|
AV1 video (up to 2160p) and surround audio, in an MP4 container.
|
|
H.265 VCN 2160p 4K
|
|
H.265 video (up to 2160p) and surround audio, in an MP4 container.
|
|
CLI Defaults/
|
|
CLI Default
|
|
"""
|
|
|
|
|
|
def test_preset_liste_kommt_aus_der_einrueckung():
|
|
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
|
|
assert presets == [
|
|
"Very Fast 2160p60 4K AV1",
|
|
"Super HQ 1080p30 Surround",
|
|
"H.265 MKV 2160p60 4K",
|
|
"H.265 MKV 1080p30",
|
|
"AV1 QSV 2160p 4K",
|
|
"H.265 VCN 2160p 4K",
|
|
"CLI Default",
|
|
]
|
|
|
|
|
|
def test_preset_liste_nimmt_keine_beschreibung_und_kein_log():
|
|
"""Beschreibungen (8 Leerzeichen), Kategorien (0) und HandBrakes eigene
|
|
Logzeilen (0) dürfen nicht als Preset-Namen durchgehen."""
|
|
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
|
|
assert not any(p.endswith("/") for p in presets)
|
|
assert not any("container" in p for p in presets)
|
|
assert not any(p.startswith("[") for p in presets)
|
|
assert "Cannot load libnvidia-encode.so.1" not in presets
|
|
|
|
|
|
def test_preset_liste_leer_bleibt_leer():
|
|
assert caps.parse_preset_liste("") == []
|
|
assert caps.parse_preset_liste(None) == []
|
|
|
|
|
|
def test_hardware_presets_stehen_auch_ohne_hardware_in_der_liste():
|
|
"""Richtigstellung zum SAVEPOINT v3.16: Dort galt es als unmöglich, die
|
|
Hardware-Preset-Namen auf der Rippy-VM zu ermitteln, weil dort kein
|
|
Hardware-Encoder läuft. Die Messung sagt das Gegenteil — `--preset-list`
|
|
nennt sie, `--help` nennt nur die nutzbaren Encoder. Zwei Fragen, zwei
|
|
Quellen."""
|
|
presets = caps.parse_preset_liste(HB_PRESETS_ECHT)
|
|
assert "H.265 VCN 2160p 4K" in presets # AMD, aus der VM-Liste
|
|
assert "AV1 QSV 2160p 4K" in presets # Intel, aus der VM-Liste
|
|
# ... und dieselbe Maschine meldet trotzdem KEINEN Hardware-Encoder:
|
|
assert caps.leite_backends_ab(caps.parse_encoder_liste(HB_HILFE_ECHT)) == [
|
|
"cpu-x264", "cpu-x265", "cpu-av1",
|
|
]
|
|
|
|
|
|
# ── „Extern" heisst: Rippy laeuft woanders (Befund 29.08.2026) ──────────
|
|
#
|
|
# Hier stand `os.path.isdir("/app")`. Im Image stimmt das. Auf einem
|
|
# Windows-PC gibt es `/app` nicht — und damit hielt sich der eigenstaendige
|
|
# Windows-Rippy fuer einen FREMDEN Worker. Folge: Das UI warnte vor fehlender
|
|
# Pfad-Uebersetzung auf einer Maschine, auf der es weder Container noch
|
|
# Freigabe gibt.
|
|
|
|
|
|
def test_im_container_ist_nichts_extern():
|
|
from caps import _extern
|
|
|
|
assert _extern({}, container=True) == "nein"
|
|
|
|
|
|
def test_die_eigenstaendige_windows_app_ist_NICHT_extern():
|
|
"""Dieser Prozess IST Rippy — die Frage ist dort gegenstandslos."""
|
|
from caps import _extern
|
|
|
|
assert _extern({"profil": "standalone"}, container=False) == "nein"
|
|
|
|
|
|
def test_ein_worker_bei_einem_docker_rippy_ist_extern():
|
|
"""Der Fall, fuer den die Auskunft gemacht ist: Er braucht RIPPY_PATH_MAP."""
|
|
from caps import _extern
|
|
|
|
assert _extern({"profil": "node", "queue": {"treiber": "celery"}},
|
|
container=False) == "ja"
|