fix(worker): Hardware-Encoder und CPU-Merkmale auf Windows - plus ehrliche Transcode-Fehler

Aus dem gescheiterten Test-Rip des Commanders (Job 95afdc89) und seiner
UI-Durchsicht. Der Reihe nach.

## 1. Der externe Encoder wurde SEHR WOHL gesehen (Diagnose-Korrektur)

Der Commander schloss aus der Meldung, das System sehe seinen PC nicht. Live
gemessen sagt das Gegenteil:

    meta.transcode_node = tobisnicerpc@TobisNicerPC
    VM-Worker-Log        = nur rip_disc, KEIN transcode_files
    22:19:41.271  Kompression eingereiht
    22:19:41.453  "Keine Roh-MKVs gefunden"   <- 182 ms spaeter

Das Routing hat funktioniert. Sein PC nahm die Aufgabe an und lehnte sie sofort
ab, weil er /app/media/rippy/<job> nicht finden kann - ein Pfad, der nur INNEN
im Container existiert. Der Rip war vollstaendig (79,6 GB auf der NAS).

## 2. Der Hardware-Encoder-Bug war meiner

leite_backends_ab() verlangte zusaetzlich ein Geraet: /dev/dri fuer
VAAPI/QSV/VCE bzw. nvidia-smi fuer 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, fuer den externe Worker gedacht sind.

Die Pruefung war ausserdem ueberfluessig: HandBrake probiert Hardware beim
Start selbst an und listet nur Nutzbares - auf der VM belegt durch
"qsv: not available on this system" bei gleichzeitig fehlendem qsv_* in --help.
Jetzt ist HandBrakes Liste die einzige Auskunft.

Dazu nach FAMILIE unterschieden (nvenc/qsv/vce/vaapi) statt alles in "vaapi" zu
werfen - eine AMD-Karte lief unter dem Intel-Namen. Hardware-AV1 bekommt eine
eigene Kennung, weil es die beste Kombination aus Tempo und Groesse ist und
sonst unsichtbar bliebe.

## 3. Vektorbefehle unter Windows - gemessen, nicht "unbekannt"

Es gibt dort kein /proc/cpuinfo, also meldete der Windows-Worker
"Vektorbefehle unbekannt" - gerade auf der Maschine, die encodieren soll.
Jetzt ueber IsProcessorFeaturePresent (kernel32, winnt.h dokumentiert) plus
CPU-Name aus der Registry. Auf dem Commander-PC gegengeprueft:

    vorher: AMD64 Family 26 Model 68 Stepping 0  /  Vektorbefehle unbekannt
    jetzt:  AMD Ryzen 7 9700X 8-Core Processor   /  avx512f  /  16 Kerne

Damit verschwindet die AVX2-Warnung fuer diesen Worker von selbst - genau das
hatte der Commander gefordert.

## 4. Ehrliche Fehlermeldung statt "Keine Roh-MKVs gefunden"

Neues _erreichbarkeit_pruefen() unterscheidet jetzt drei Faelle statt einem:
Pfad nicht erreichbar UND kein Mapping gesetzt (nennt RIPPY_PATH_MAP samt
Beispiel), Mapping gesetzt aber greift nicht, oder Pfad einfach nicht da. Und
es prueft BEIDE Pfade - im Fall des Commanders lag die Quelle auf der
erreichbaren NAS, das ZIEL aber auf der VM-Platte ohne Samba; das waere erst
beim Schreiben nach Stunden Rechenzeit aufgefallen.

Jede Meldung sagt jetzt ausdruecklich, dass die Rohdaten NICHT verloren sind
und "Neu komprimieren" genuegt.

## 5. Persoenliche Werte raus

Vier Platzhalter trugen die Umgebung des Commanders (192.168.178.20,
TOBIS-PC, seine Jellyfin- und Rippy-IP). In einem Repo, das weitergegeben
wird, hat das nichts verloren.

Nebenbei dreimal in die eigene Falle getappt: deutsche Anfuehrungszeichen in
DOPPELT gequoteten Python-Strings beenden den String vorzeitig. Der Bestand
nutzt dafuer einfach gequotete f-Strings - jetzt auch meine.

NOCH OFFEN und wichtig: RIPPY_PATH_MAP wird von niemandem GESETZT (Installer,
UI, Compose). Ein Windows-Worker kann damit grundsaetzlich nicht transcodieren.
Das ist der naechste Schritt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 11:51:47 +02:00
parent 61c38a0d14
commit fe12401e34
7 changed files with 271 additions and 50 deletions
+91 -1
View File
@@ -700,6 +700,80 @@ def scan_tracks(device_path: str):
return {"status": "error", "error": str(e)}
def _ist_uebersetzt(container_pfad: str, lokal_pfad: str) -> bool:
"""Hat RIPPY_PATH_MAP diesen Pfad wirklich angefasst? (pure, testbar)"""
return container_pfad != lokal_pfad
def _erreichbarkeit_pruefen(raw_container: str, raw_lokal: str,
final_container: str, final_lokal: str) -> str:
"""Kann DIESER Worker Quelle und Ziel überhaupt sehen? Klartext oder "".
Befund 25.07.2026 am Job 95afdc89, live: Das gezielte Routing an den
Windows-PC des Commanders funktionierte einwandfrei — der Worker nahm die
Aufgabe an und lehnte sie 182 ms später ab mit „Keine Roh-MKVs in
/app/media/rippy/… gefunden". Diese Meldung klang nach einem kaputten Rip,
obwohl der Rip vollständig war (79,6 GB lagen auf der NAS). Der Commander
schloss daraus, das System sähe den externen Encoder nicht — verständlich
und falsch.
Die eigentliche Ursache: `/app/media/...` sind CONTAINER-Pfade. Ein nativer
Worker sieht sie nur, wenn RIPPY_PATH_MAP sie auf eine Freigabe übersetzt —
und dieses Mapping wurde von NIEMANDEM gesetzt. Es war also nicht möglich,
dass diese Kombination je funktioniert.
Zweiter Stolperstein im selben Fall: Die Rohdaten lagen auf der NAS (für den
PC erreichbar), das ZIEL aber auf der VM-Platte (nicht erreichbar, dort
läuft kein Samba). Deshalb werden hier BEIDE Pfade geprüft, nicht nur die
Quelle — sonst scheitert es erst beim Schreiben, nach Stunden Rechenzeit.
"""
mapping = os.getenv("RIPPY_PATH_MAP", "")
fremder_worker = not os.path.isdir("/app")
for zweck, container, lokal in (
("Quelle (Rohdaten)", raw_container, raw_lokal),
("Ziel (fertige Datei)", final_container, final_lokal),
):
if os.path.isdir(lokal):
continue
# Ziel darf fehlen, solange sein Elternordner erreichbar ist — es wird
# gleich angelegt. Nur ein unerreichbarer ELTERN-Pfad ist ein Problem.
if zweck.startswith("Ziel") and os.path.isdir(os.path.dirname(lokal) or "."):
continue
text = [f"Dieser Worker erreicht die {zweck} nicht: {lokal}"]
if fremder_worker and not mapping:
text.append(
"Ursache: RIPPY_PATH_MAP ist auf dieser Maschine nicht gesetzt. "
f'{container}" ist ein Pfad INNERHALB des Rippy-Containers — ein '
'externer Worker sieht ihn nur, wenn er auf eine Netzwerk-Freigabe '
'übersetzt wird.'
)
text.append(
"Abhilfe: Freigabe auf diesem Rechner erreichbar machen und in "
"start-tray.bat setzen, z. B. "
"set RIPPY_PATH_MAP=/app/media=\\\\NAS\\rippy-media"
)
elif fremder_worker and mapping:
text.append(f"RIPPY_PATH_MAP ist gesetzt ({mapping}), deckt diesen Pfad aber nicht ab.")
if not _ist_uebersetzt(container, lokal):
text.append(
f'{container}" wurde von keinem Eintrag übersetzt — fehlt ein '
'Präfix, oder ist es ein anderes Verzeichnis als erwartet?'
)
else:
text.append(
"Der Pfad existiert nicht. Liegt das Arbeitsverzeichnis auf einer "
"Freigabe, die gerade nicht eingehängt ist?"
)
text.append(
'WICHTIG: Die Rohdaten sind NICHT verloren — nach der Korrektur '
'genügt „Neu komprimieren", ohne die Disc erneut zu rippen.'
)
return " ".join(text)
return ""
@celery_app.task(bind=True, name="worker.tasks.transcode_files")
def transcode_files(self, job_id: str, raw_dir: str, final_dir: str):
"""Stufe 2: HandBrake komprimiert die Roh-MKVs auf Arbeitsgröße.
@@ -711,11 +785,27 @@ def transcode_files(self, job_id: str, raw_dir: str, final_dir: str):
"""
db.init_db()
# Native Worker (Windows) übersetzen Container-Pfade aufs Netzlaufwerk
raw_container, final_container = raw_dir, final_dir
raw_dir = pfad_lokal(raw_dir)
final_dir = pfad_lokal(final_dir)
fehler = _erreichbarkeit_pruefen(raw_container, raw_dir, final_container, final_dir)
if fehler:
ergebnis = {"status": "error", "error": fehler}
_job_abschliessen(job_id, ergebnis)
return ergebnis
quellen = sorted(glob.glob(os.path.join(raw_dir, "*.mkv")))
if not quellen:
ergebnis = {"status": "error", "error": f"Keine Roh-MKVs in {raw_dir} gefunden"}
ergebnis = {
"status": "error",
"error": (
f"Das Verzeichnis {raw_dir} ist erreichbar, enthält aber keine "
"MKV-Datei. Der Rip hat also nichts abgelegt (oder jemand hat die "
"Datei entfernt). Die Kompression lässt sich nach einem neuen Rip "
"erneut anstoßen."
),
}
_job_abschliessen(job_id, ergebnis)
return ergebnis