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:
+91
-1
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user