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:
+128
-9
@@ -6,26 +6,133 @@ Remote-GPU-Worker meldet sich hier genauso wie der eingebaute CPU-Worker.
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
def erkenne_encoder() -> list:
|
||||
"""Liste der verfügbaren Encoder-Backends auf dieser Maschine."""
|
||||
gefunden = ["cpu-x264", "cpu-x265"] # HandBrake-Software-Encoder, immer dabei
|
||||
HB_ENCODER_KOPF = re.compile(r"^-e,\s*--encoder\b")
|
||||
|
||||
# VAAPI: AMD (VCN) und Intel (QuickSync) melden sich über /dev/dri
|
||||
if os.path.exists("/dev/dri/renderD128"):
|
||||
# Reihenfolge = Anzeige-Reihenfolge im UI.
|
||||
SIMD_STUFEN = ("avx512f", "avx2", "avx", "sse4_2")
|
||||
|
||||
|
||||
def parse_encoder_liste(hilfe_text: str) -> list:
|
||||
"""Video-Encoder, die DIESES HandBrake wirklich anbietet.
|
||||
|
||||
Quelle: `HandBrakeCLI --help`, Abschnitt `-e, --encoder <string>`
|
||||
(AGENTS Regel D — im Worker-Image am 25.07.2026 gemessen, nicht geraten):
|
||||
|
||||
-e, --encoder <string> Select video encoder:
|
||||
svt_av1
|
||||
x264
|
||||
x265
|
||||
...
|
||||
--encoder-preset <string>
|
||||
|
||||
Der Block endet an der nächsten Option — also an der ersten Zeile, deren
|
||||
erstes nicht-leeres Zeichen ein Bindestrich ist.
|
||||
"""
|
||||
encoder = []
|
||||
im_block = False
|
||||
for zeile in (hilfe_text or "").splitlines():
|
||||
inhalt = zeile.strip()
|
||||
if not im_block:
|
||||
if HB_ENCODER_KOPF.match(inhalt):
|
||||
im_block = True
|
||||
continue
|
||||
if not inhalt:
|
||||
continue
|
||||
if inhalt.startswith("-"):
|
||||
break
|
||||
encoder.append(inhalt.split()[0])
|
||||
return encoder
|
||||
|
||||
|
||||
def leite_backends_ab(hb_encoder, dri_da: bool, nvidia_da: bool) -> list:
|
||||
"""Übersetzt die HandBrake-Encoder-Namen in die Backends, die das UI zeigt.
|
||||
|
||||
Zwei Ehrlichkeits-Regeln, die vorher fehlten (Befund 25.07.2026):
|
||||
* Ohne HandBrake gibt es KEINE Encoder. Vorher standen `cpu-x264` und
|
||||
`cpu-x265` fest verdrahtet in der Liste („immer dabei") — ein reiner
|
||||
Rip-Worker ohne HandBrake behauptete damit, komprimieren zu können.
|
||||
* Hardware zählt nur, wenn das Gerät da ist UND HandBrake den Encoder
|
||||
überhaupt kennt. Vorher genügte `/dev/dri` — das Worker-Image kann
|
||||
laut eigener Auskunft aber gar keinen Hardware-Encoder.
|
||||
"""
|
||||
namen = {e.lower() for e in (hb_encoder or [])}
|
||||
gefunden = []
|
||||
if any(n.startswith("x264") for n in namen):
|
||||
gefunden.append("cpu-x264")
|
||||
if any(n.startswith("x265") for n in namen):
|
||||
gefunden.append("cpu-x265")
|
||||
if any(n.startswith("svt_av1") for n in namen):
|
||||
gefunden.append("cpu-av1")
|
||||
if dri_da and any(("qsv" in n) or ("vce" in n) or ("vaapi" in n) for n in namen):
|
||||
gefunden.append("vaapi")
|
||||
|
||||
# NVENC: NVIDIA-Treiber im Container sichtbar
|
||||
if shutil.which("nvidia-smi") or os.path.exists("/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.1"):
|
||||
if nvidia_da and any("nvenc" in n for n in namen):
|
||||
gefunden.append("nvenc")
|
||||
|
||||
return gefunden
|
||||
|
||||
|
||||
def beste_simd(flags_text: str) -> str:
|
||||
"""Höchste Vektor-Befehlsstufe der CPU — entscheidend fürs Software-Encodieren.
|
||||
|
||||
Befund 25.07.2026: Die Rippy-VM läuft auf dem generischen QEMU-CPU-Modell
|
||||
(„QEMU Virtual CPU version 2.5+") und hat deshalb KEIN AVX2, nur sse4_2.
|
||||
x265 lebt von diesen Befehlen; ein 4K-Encode brauchte dort gemessene
|
||||
28-55 Stunden. Sichtbar war das nirgends — genau diese Lücke schließt die
|
||||
Anzeige. Abhilfe bei Proxmox/KVM: CPU-Typ der VM auf `host` stellen.
|
||||
"""
|
||||
vorhanden = set((flags_text or "").split())
|
||||
for stufe in SIMD_STUFEN:
|
||||
if stufe in vorhanden:
|
||||
return stufe
|
||||
return "unbekannt"
|
||||
|
||||
|
||||
def _lies_cpuinfo() -> tuple:
|
||||
"""(Modellname, Flags-Text, Kernzahl) aus /proc/cpuinfo — leer auf Windows."""
|
||||
modell, flags, kerne = "", "", 0
|
||||
try:
|
||||
with open("/proc/cpuinfo", encoding="utf-8", errors="replace") as f:
|
||||
for zeile in f:
|
||||
if zeile.startswith("processor"):
|
||||
kerne += 1
|
||||
elif not modell and zeile.startswith("model name"):
|
||||
modell = zeile.split(":", 1)[-1].strip()
|
||||
elif not flags and zeile.startswith("flags"):
|
||||
flags = zeile.split(":", 1)[-1].strip()
|
||||
except OSError:
|
||||
pass
|
||||
return modell, flags, kerne
|
||||
|
||||
|
||||
def hole_handbrake_hilfe() -> str:
|
||||
"""`HandBrakeCLI --help` einmal abrufen (leer, wenn nicht installiert)."""
|
||||
if not shutil.which("HandBrakeCLI"):
|
||||
return ""
|
||||
try:
|
||||
aus = subprocess.run(
|
||||
["HandBrakeCLI", "--help"], capture_output=True, text=True, timeout=30
|
||||
)
|
||||
return (aus.stdout or "") + (aus.stderr or "")
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return ""
|
||||
|
||||
|
||||
def erkenne_encoder() -> list:
|
||||
"""Encoder-Backends, die auf DIESER Maschine wirklich nutzbar sind."""
|
||||
hb_encoder = parse_encoder_liste(hole_handbrake_hilfe())
|
||||
dri_da = os.path.exists("/dev/dri/renderD128")
|
||||
nvidia_da = bool(
|
||||
shutil.which("nvidia-smi")
|
||||
or os.path.exists("/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.1")
|
||||
)
|
||||
return leite_backends_ab(hb_encoder, dri_da, nvidia_da)
|
||||
|
||||
|
||||
def erkenne_ip() -> str:
|
||||
"""Beste erratbare eigene IP (UDP-Route-Trick, KEIN echter Traffic).
|
||||
|
||||
@@ -56,11 +163,23 @@ def werkzeug_versionen() -> dict:
|
||||
"""
|
||||
import socket
|
||||
|
||||
modell, flags, kerne = _lies_cpuinfo()
|
||||
info = {
|
||||
# Zuordnung im UI: hostname (für den Celery-Online-Abgleich) + IP
|
||||
"hostname": socket.gethostname(),
|
||||
"ip": erkenne_ip(),
|
||||
# Rechenleistung fürs Software-Encodieren. Ohne diese drei Angaben war
|
||||
# am 25.07.2026 nicht zu sehen, dass ein 4K-Encode auf dieser Maschine
|
||||
# Tage statt Stunden braucht (kein AVX2, 4 Kerne).
|
||||
"cpu_modell": modell or (platform.processor() or "unbekannt"),
|
||||
"cpu_kerne": str(kerne or os.cpu_count() or 0),
|
||||
"cpu_simd": beste_simd(flags),
|
||||
}
|
||||
hb_encoder = parse_encoder_liste(hole_handbrake_hilfe())
|
||||
if hb_encoder:
|
||||
# Die ungefilterte Auskunft von HandBrake — damit im UI nachprüfbar ist,
|
||||
# woher die Backend-Liste kommt.
|
||||
info["handbrake_encoder"] = ",".join(hb_encoder)
|
||||
if shutil.which("makemkvcon"):
|
||||
info["makemkv"] = os.getenv("MAKEMKV_VERSION") or "installiert"
|
||||
if shutil.which("HandBrakeCLI"):
|
||||
|
||||
Reference in New Issue
Block a user