fix(caps,ui): Encoder werden gemessen statt behauptet

Der Modulkopf von caps.py verspricht "ehrlich erkannt, nicht behauptet" -
erkenne_encoder() tat aber drei Mal das Gegenteil:

1. cpu-x264 und cpu-x265 standen fest verdrahtet in der Liste ("immer
   dabei"). Ein Rip-Worker OHNE HandBrake behauptete damit, komprimieren zu
   koennen; jeder Transcode dort endete sofort mit "nicht installiert".
2. 'vaapi' wurde allein wegen /dev/dri gemeldet, ohne zu pruefen, ob
   HandBrake das ueberhaupt kann. Auf der VM gemessen: das Worker-Image
   kennt svt_av1/x264/x265/mpeg4/mpeg2/VP8/VP9/theora und KEINEN einzigen
   Hardware-Encoder. Rippy haette VAAPI versprochen und dann versagt.
3. Ueber die Rechenleistung sagte es gar nichts. Genau deshalb war nicht zu
   sehen, dass ein 4K-Encode auf dieser Maschine Tage braucht: CPU-Modell
   ist das generische "QEMU Virtual CPU version 2.5+", grep -c avx2
   /proc/cpuinfo = 0, nur bis sse4_2. x265 lebt von AVX2. Gemessen an der
   Leseposition des Quellstroms: 28-55 h fuer einen Film, 3,84 von 4 Kernen
   gesaettigt. Abhilfe waere der CPU-Typ 'host' in Proxmox.

Jetzt wird die Encoder-Liste aus `HandBrakeCLI --help` geparst (Abschnitt
-e/--encoder, Formatstrings im Worker-Image gemessen - AGENTS Regel D), und
Hardware zaehlt nur, wenn Geraet UND HandBrake-Unterstuetzung da sind. Neu
gemeldet: cpu-av1 (svt_av1 ist wirklich verfuegbar) sowie CPU-Modell,
Kernzahl und Vektorbefehlsstufe je Worker.

UI: Kerne/Vektorbefehle stehen bei jedem Worker (Worker-Tab und
Einstellungen -> System), dazu die ungefilterte HandBrake-Auskunft und eine
sichtbare Warnung, wenn AVX2 fehlt - inklusive des Hinweises auf den
VM-CPU-Typ. Der Warntext liegt in lib/encoder.ts, damit er nicht doppelt
im Code steht.

Mit im gleichen Zug (dieselbe Datei): der Schalter "Alle Tracks rippen" ist
entfallen. Er war ein Placebo - abcde bekommt keine Track-Auswahl und es gab
auch keine Oberflaeche, um eine Teilmenge zu waehlen. Eine Audio-CD wurde
also immer vollstaendig gerippt, egal wie der Schalter stand. An seiner
Stelle steht jetzt die Wahrheit.

7 Tests, Testdaten sind echte HandBrake-Ausgaben von der VM. Der Parser ist
zusaetzlich gegen die vollen 697 Zeilen der echten --help gegengeprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-25 21:04:42 +02:00
parent e0cb7b3ddc
commit 883c1c290b
8 changed files with 328 additions and 21 deletions
+115
View File
@@ -0,0 +1,115 @@
"""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([], dri_da=True, nvidia_da=True) == []
def test_backends_aus_echter_liste():
encoder = caps.parse_encoder_liste(HB_HILFE_ECHT)
# Kein /dev/dri, kein NVIDIA — und HandBrake kann hier ohnehin keine Hardware
assert caps.leite_backends_ab(encoder, dri_da=False, nvidia_da=False) == [
"cpu-x264", "cpu-x265", "cpu-av1",
]
def test_hardware_nur_wenn_geraet_UND_handbrake_es_kann():
"""Befund 25.07.2026: Vorher genügte /dev/dri, um 'vaapi' zu melden. Das
Worker-Image kann laut eigener Auskunft aber gar keinen Hardware-Encoder —
Rippy hätte VAAPI versprochen und wäre beim Encodieren gescheitert."""
ohne_hw = caps.parse_encoder_liste(HB_HILFE_ECHT)
assert "vaapi" not in caps.leite_backends_ab(ohne_hw, dri_da=True, nvidia_da=True)
assert "nvenc" not in caps.leite_backends_ab(ohne_hw, dri_da=True, nvidia_da=True)
mit_hw = caps.parse_encoder_liste(HB_HILFE_MIT_HARDWARE)
# Gerät fehlt → trotz Können kein Hardware-Backend
assert caps.leite_backends_ab(mit_hw, dri_da=False, nvidia_da=False) == [
"cpu-x264", "cpu-x265",
]
# Beides da → beides gemeldet
beides = caps.leite_backends_ab(mit_hw, dri_da=True, nvidia_da=True)
assert "vaapi" in beides # qsv_* vorhanden
assert "nvenc" in beides
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"