Ampel / ampel (push) Failing after 47s
WAS: rippy/tools/ mit katalog.py (finden + Version) und beschaffen.py
(herunterladen, installieren, Update-Stand). ripping.py und caps.py fragen
den Katalog statt shutil.which.
DER BEFUND, DER DAS NOETIG MACHT: Der Bestand suchte AUSSCHLIESSLICH mit
shutil.which(). Im Container stimmt das — dort liegen beide Werkzeuge in
/usr/local/bin. Unter Windows findet es NICHTS, auch wenn alles installiert
ist: Windows-Programme liegen in Program Files, nicht im PATH. Am
28.08.2026 auf dem Commander-PC nachgesehen — MakeMKV lag unter
"C:\Program Files (x86)\MakeMKV\makemkvcon64.exe", which sah es nicht.
Fuer einen Rippy, der unter Windows eigenstaendig arbeiten soll, ist das der
Unterschied zwischen "laeuft" und "laeuft nicht". Vorher/nachher gemessen:
vorher check_makemkv_installed() -> False (obwohl installiert)
nachher check_makemkv_installed() -> True
build_makemkv_cmd()[0] -> C:\Program Files (x86)\MakeMKV\makemkvcon64.exe
SUCHREIHENFOLGE, mit Begruendung im Modul: eingestellt > Rippys eigener
Werkzeug-Ordner > PATH > bekannte Installationsorte > Uninstall-Zweig der
Registry. Rippys eigener Ordner steht VOR dem System, damit eine selbst
gepflegte Fassung eine alte Systeminstallation schlaegt.
VERSION: HandBrakeCLI kann --version. makemkvcon KANN DAS NICHT (usage.txt
kennt keinen solchen Schalter) — die Version kommt aus dem Uninstall-Zweig.
Dort steht bei MakeMKV zwar eine Version, aber InstallLocation ist LEER
(nachgesehen); deshalb wird der Ordner ersatzweise aus DisplayIcon bzw.
UninstallString abgeleitet. Der Test dafuer hat sofort einen Fehler
gefunden: '"C:\...\uninstall.exe" /S' liess sich mit einem blossen
strip('"') nicht aufloesen.
BESCHAFFUNG, an echter Hardware gemessen:
HandBrake GitHub-Release-API -> 1.11.2, HandBrakeCLI-1.11.2-win-x86_64.zip
geladen, entpackt, gestartet: meldet sich als 1.11.2. 2,8 s.
MakeMKV makemkv.com antwortete mit HTTP 525 (Cloudflare) — auch mit
Browser-Kennung. Dieselbe Sperre, die im Projekt schon den
Docker-Bau lahmlegt. Deshalb: Abruf wird versucht, ein
Fehlschlag wird im Klartext gemeldet, und eine selbst geholte
Datei laesst sich weiterhin verwenden. Ein Update-Server, der
heute antwortet, darf keine Startbedingung fuer morgen sein.
DREI FALLEN, GEGEN DIE ES TESTS GIBT:
- Die .sig-Datei liegt im Release direkt neben dem ZIP und ist 566 Bytes
gross. Wer nur nach "win" filtert, laedt sie.
- "1.9.2" ist als Zeichenkette GROESSER als "1.11.2". Ein Update-Hinweis, der
ab Version zehn dauerhaft in die Irre fuehrt — und genau dort ist
HandBrake gerade.
- Eine Fehlerseite kommt oft mit HTTP 200. Sie darf keine funktionierende
Installation ersetzen: erst laden, pruefen, dann tauschen.
Und: Ist die neueste Version unbekannt (Quelle tot), steht NICHT "Update
verfuegbar" da. Eine Nichtauskunft ist keine Aussage.
GEMESSEN: ruff sauber, 489 Tests gruen + 15 uebersprungen (vorher 456).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
430 lines
17 KiB
Python
430 lines
17 KiB
Python
"""Encoder-Fähigkeiten dieses Workers — ehrlich erkannt, nicht behauptet.
|
|
|
|
Jeder Worker meldet beim Start, was auf SEINER Maschine wirklich verfügbar
|
|
ist (Commander-Anforderung 23.07.: das UI zeigt an, WAS da ist). Ein
|
|
Remote-GPU-Worker meldet sich hier genauso wie der eingebaute CPU-Worker.
|
|
"""
|
|
|
|
import os
|
|
import platform
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
from rippy.platform.winlauf import OHNE_FENSTER
|
|
from rippy.tools import katalog as werkzeuge
|
|
|
|
|
|
def _hb() -> str:
|
|
"""Pfad zu HandBrakeCLI — "" wenn es nicht da ist.
|
|
|
|
Bis V2-4 stand hier `shutil.which("HandBrakeCLI")`. Das findet unter
|
|
Windows nichts, auch wenn HandBrake installiert ist (Program Files statt
|
|
PATH). Die Folge waere gewesen: Rippy meldet "keine Encoder gefunden" auf
|
|
einem Rechner, auf dem alles da ist — und die Encoder-Auswahl im UI bliebe
|
|
leer, ohne dass jemand den Grund saehe.
|
|
"""
|
|
return werkzeuge.finden("handbrake")
|
|
|
|
|
|
HB_ENCODER_KOPF = re.compile(r"^-e,\s*--encoder\b")
|
|
|
|
# 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
|
|
|
|
|
|
# Hardware-Encoder-Familien, wie HandBrake sie benennt → Backend-Kennung.
|
|
# Reihenfolge = Anzeige-Reihenfolge; AV1 zuerst, weil es das modernste ist.
|
|
HW_FAMILIEN = (
|
|
("nvenc", "nvenc"), # NVIDIA
|
|
("qsv", "qsv"), # Intel QuickSync
|
|
("vce", "vce"), # AMD VCE/VCN
|
|
("vaapi", "vaapi"), # generisch (Linux)
|
|
)
|
|
|
|
|
|
def leite_backends_ab(hb_encoder) -> list:
|
|
"""Übersetzt die HandBrake-Encoder-Namen in die Backends, die das UI zeigt.
|
|
|
|
## HandBrakes Liste IST die Auskunft — keine zweite Meinung nötig
|
|
|
|
Frühere Fassung (25.07.2026) verlangte zusätzlich ein Gerät: `/dev/dri`
|
|
für VAAPI/QSV/VCE bzw. `nvidia-smi` für NVENC. Das war doppelt falsch:
|
|
|
|
1. **Unter Windows gibt es beides nicht.** Der Commander-PC (RX 9070 XT)
|
|
meldete deshalb nur CPU-Encoder, obwohl HandBrakes Windows-Build
|
|
`vce_*`, `nvenc_*` und `qsv_*` beherrscht. Ein Bug, der genau den
|
|
Anwendungsfall traf, für den externe Worker gedacht sind.
|
|
2. **Die Prüfung war überflüssig.** HandBrake probiert Hardware-Encoder
|
|
beim Start selbst an und listet nur, was es wirklich benutzen kann. Auf
|
|
der Rippy-VM belegt: `--version` meldet „qsv: not available on this
|
|
system" und „Cannot load libnvidia-encode.so.1" — und genau diese
|
|
Encoder fehlen dann in `--help`. Wer die Liste hat, hat die Antwort.
|
|
|
|
Bleibt die eine Regel, die weiter gilt: **ohne HandBrake gibt es keine
|
|
Encoder.** Vorher standen `cpu-x264`/`cpu-x265` fest verdrahtet drin, und
|
|
ein reiner Rip-Worker behauptete damit, komprimieren zu können.
|
|
|
|
Hardware wird jetzt nach Familie unterschieden (nvenc/qsv/vce/vaapi) statt
|
|
alles in „vaapi" zu werfen — sonst kann das UI nicht sagen, welches Preset
|
|
passt, und AMD-Karten liefen unter einem Intel-Namen.
|
|
"""
|
|
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")
|
|
for kennzeichen, backend in HW_FAMILIEN:
|
|
treffer = [n for n in namen if kennzeichen in n]
|
|
if not treffer:
|
|
continue
|
|
gefunden.append(backend)
|
|
# AV1 in Hardware ist die derzeit beste Kombination aus Tempo und
|
|
# Dateigröße — das muss sichtbar sein, nicht in „nvenc" verschwinden.
|
|
if any("av1" in n for n in treffer):
|
|
gefunden.append(f"{backend}-av1")
|
|
return gefunden
|
|
|
|
|
|
def parse_preset_liste(text: str) -> list:
|
|
"""Preset-NAMEN, die DIESES HandBrake wirklich kennt.
|
|
|
|
Quelle: `HandBrakeCLI --preset-list`, am 26.07.2026 im Worker-Image
|
|
gemessen (HandBrake 1.6.1) — AGENTS Regel D, nicht aus dem Kopf:
|
|
|
|
General/
|
|
Very Fast 2160p60 4K AV1
|
|
AV1 video (up to 2160p60) and AAC stereo audio, in an MP4
|
|
container.
|
|
Hardware/
|
|
H.265 VCN 2160p 4K
|
|
|
|
Die Struktur ist die Einrückung: Kategorien ohne Einrückung (mit „/" am
|
|
Ende), Preset-Namen mit GENAU vier Leerzeichen, Beschreibungen mit acht.
|
|
HandBrakes Logzeilen („[10:26:05] qsv: not available on this system") und
|
|
Meldungen wie „Cannot load libnvidia-encode.so.1" haben keine Einrückung
|
|
und fallen damit von selbst weg.
|
|
|
|
⚠️ Wichtige Richtigstellung zum SAVEPOINT v3.16: Dort steht, die Namen der
|
|
HARDWARE-Presets seien auf der Rippy-VM „nicht ermittelbar", weil deren
|
|
HandBrake keinen Hardware-Encoder hat. Gemessen ist das falsch — die
|
|
Kategorie `Hardware/` steht vollständig in der Liste (VCN, NVENC, QSV, MF).
|
|
HandBrake trennt beides: `--preset-list` nennt alle mitgelieferten Presets,
|
|
`--help` nur die Encoder, die diese Maschine wirklich benutzen kann. Für
|
|
„welches Preset gibt es" ist die Preset-Liste die Quelle, für „läuft das
|
|
hier" die Encoder-Liste. Deshalb werden beide gemeldet.
|
|
"""
|
|
presets = []
|
|
for zeile in (text or "").splitlines():
|
|
if not zeile.strip():
|
|
continue
|
|
einrueckung = len(zeile) - len(zeile.lstrip(" "))
|
|
if einrueckung == 4:
|
|
presets.append(zeile.strip())
|
|
return presets
|
|
|
|
|
|
def hole_handbrake_presets() -> list:
|
|
"""`HandBrakeCLI --preset-list` einmal abrufen (leer, wenn nicht installiert)."""
|
|
if not _hb():
|
|
return []
|
|
try:
|
|
aus = subprocess.run(
|
|
[_hb(), "--preset-list"],
|
|
capture_output=True, text=True, timeout=30,
|
|
creationflags=OHNE_FENSTER,
|
|
)
|
|
return parse_preset_liste((aus.stdout or "") + (aus.stderr or ""))
|
|
except (OSError, subprocess.TimeoutExpired):
|
|
return []
|
|
|
|
|
|
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
|
|
|
|
|
|
# Merkmals-Nummern für IsProcessorFeaturePresent (Win32, winnt.h).
|
|
# Absteigend nach Stärke — der erste Treffer ist die höchste Stufe.
|
|
WIN_MERKMALE = (
|
|
("avx512f", 41), # PF_AVX512F_INSTRUCTIONS_AVAILABLE
|
|
("avx2", 40), # PF_AVX2_INSTRUCTIONS_AVAILABLE
|
|
("avx", 39), # PF_AVX_INSTRUCTIONS_AVAILABLE
|
|
("sse4_2", 38), # PF_SSE4_2_INSTRUCTIONS_AVAILABLE
|
|
)
|
|
|
|
|
|
def _simd_windows() -> str:
|
|
"""Höchste Vektor-Befehlsstufe unter Windows.
|
|
|
|
Es gibt dort kein /proc/cpuinfo, deshalb meldete der Windows-Worker bis
|
|
25.07.2026 „Vektorbefehle unbekannt" — gerade auf der Maschine, die das
|
|
Encodieren übernehmen soll, also genau da, wo die Angabe zählt.
|
|
|
|
Gefragt wird `IsProcessorFeaturePresent` aus kernel32 (Win32-API, in
|
|
winnt.h dokumentiert). Kein Zusatzpaket, kein Registry-Gefummel, keine
|
|
Fremdprozesse.
|
|
"""
|
|
try:
|
|
import ctypes
|
|
|
|
kernel32 = ctypes.windll.kernel32
|
|
except (ImportError, AttributeError, OSError):
|
|
return "unbekannt"
|
|
for name, nummer in WIN_MERKMALE:
|
|
try:
|
|
if kernel32.IsProcessorFeaturePresent(nummer):
|
|
return name
|
|
except OSError:
|
|
return "unbekannt"
|
|
return "unbekannt"
|
|
|
|
|
|
def _modell_windows() -> str:
|
|
"""Lesbarer CPU-Name unter Windows.
|
|
|
|
platform.processor() liefert dort nur „AMD64 Family 26 Model 68 …" — die
|
|
Registry kennt den Namen, den auch der Aufkleber trägt.
|
|
"""
|
|
try:
|
|
import winreg
|
|
|
|
with winreg.OpenKey(
|
|
winreg.HKEY_LOCAL_MACHINE,
|
|
r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
|
|
) as schluessel:
|
|
name, _ = winreg.QueryValueEx(schluessel, "ProcessorNameString")
|
|
return " ".join(str(name).split())
|
|
except (ImportError, OSError):
|
|
return ""
|
|
|
|
|
|
def cpu_merkmale() -> tuple:
|
|
"""(Modell, SIMD-Stufe, Kernzahl) — plattformübergreifend, nie leer."""
|
|
modell, flags, kerne = _lies_cpuinfo()
|
|
simd = beste_simd(flags)
|
|
if simd == "unbekannt" and platform.system() == "Windows":
|
|
simd = _simd_windows()
|
|
if not modell:
|
|
modell = _modell_windows() or platform.processor() or "unbekannt"
|
|
return modell, simd, (kerne or os.cpu_count() or 0)
|
|
|
|
|
|
def hole_handbrake_hilfe() -> str:
|
|
"""`HandBrakeCLI --help` einmal abrufen (leer, wenn nicht installiert)."""
|
|
if not _hb():
|
|
return ""
|
|
try:
|
|
aus = subprocess.run(
|
|
[_hb(), "--help"], capture_output=True, text=True, timeout=30,
|
|
creationflags=OHNE_FENSTER
|
|
)
|
|
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.
|
|
|
|
Gefragt wird ausschließlich HandBrake selbst — es probiert Hardware beim
|
|
Start an und listet nur Nutzbares. Eine zusätzliche Geräteprüfung wäre
|
|
plattformabhängig (und war unter Windows falsch), siehe leite_backends_ab.
|
|
"""
|
|
return leite_backends_ab(parse_encoder_liste(hole_handbrake_hilfe()))
|
|
|
|
|
|
def erkenne_ip() -> str:
|
|
"""Beste erratbare eigene IP (UDP-Route-Trick, KEIN echter Traffic).
|
|
|
|
Ehrlicher Vorbehalt: im Docker-Bridge-Netz ist das die Container-IP —
|
|
für die Maschinen-Zuordnung ist deshalb WORKER_NAME (Anzeigename in
|
|
compose/.env) die verlässliche Größe; die IP ist Zusatz-Indiz.
|
|
"""
|
|
import socket
|
|
|
|
try:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
try:
|
|
s.connect(("192.0.2.1", 9)) # TEST-NET — nur Routing, kein Paket nötig
|
|
return s.getsockname()[0]
|
|
finally:
|
|
s.close()
|
|
except OSError:
|
|
return ""
|
|
|
|
|
|
def werkzeug_versionen() -> dict:
|
|
"""Kern-Werkzeuge dieses Workers — fürs UI (Einstellungen → System).
|
|
|
|
MakeMKV-Version kommt aus dem Build (ENV MAKEMKV_VERSION im Dockerfile) —
|
|
makemkvcon hat keinen dokumentierten --version-Schalter (usage.txt).
|
|
HandBrakeCLI --version ist dokumentiert (handbrake.fr/docs, CLI Options)
|
|
und gibt z.B. "HandBrake 1.6.1" aus.
|
|
"""
|
|
import socket
|
|
|
|
modell, simd, kerne = cpu_merkmale()
|
|
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,
|
|
"cpu_kerne": str(kerne),
|
|
"cpu_simd": simd,
|
|
# Läuft dieser Worker INNERHALB des Rippy-Containers oder auf einer
|
|
# fremden Maschine? Entscheidend, weil ein externer Worker die
|
|
# Container-Pfade (/app/media, /app/temp) nur über eine Freigabe plus
|
|
# RIPPY_PATH_MAP erreicht. Das UI kann damit VOR dem Rip warnen, statt
|
|
# den Nutzer eine Stunde rippen zu lassen (Vorfall 25.07.2026).
|
|
# /app ist im Rippy-Image immer vorhanden — kein Ratespiel.
|
|
"extern": "nein" if os.path.isdir("/app") else "ja",
|
|
# Ist die Pfad-Übersetzung gesetzt? Ohne sie kann ein externer Worker
|
|
# grundsätzlich nicht komprimieren.
|
|
"pfad_map": os.getenv("RIPPY_PATH_MAP", ""),
|
|
}
|
|
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)
|
|
# Die Preset-NAMEN dieses HandBrake. Damit hört das Raten auf: Das UI bietet
|
|
# genau an, was der jeweilige Worker kennt — die Namen unterscheiden sich
|
|
# zwischen HandBrake-Versionen, und ein erfundener Name lässt die Kompression
|
|
# scheitern (Commander-Anforderung „immer das Beste auswählen" braucht eine
|
|
# echte Liste, nicht eine geratene).
|
|
presets = hole_handbrake_presets()
|
|
if presets:
|
|
info["presets"] = presets
|
|
if werkzeuge.finden("makemkv"):
|
|
info["makemkv"] = os.getenv("MAKEMKV_VERSION") or "installiert"
|
|
if _hb():
|
|
try:
|
|
aus = subprocess.run(
|
|
[_hb(), "--version"],
|
|
capture_output=True, text=True, timeout=15,
|
|
creationflags=OHNE_FENSTER,
|
|
)
|
|
treffer = re.search(r"HandBrake\s+([\w.]+)", (aus.stdout or "") + (aus.stderr or ""))
|
|
info["handbrake"] = treffer.group(1) if treffer else "installiert"
|
|
except (OSError, subprocess.TimeoutExpired):
|
|
info["handbrake"] = "installiert"
|
|
# Woher kommt der MakeMKV-Key? UI-Setting schlägt Env — ehrlich anzeigen.
|
|
try:
|
|
from rippy import store as db
|
|
ui_key = (db.get_settings(bei_fehler_leer=True).get("makemkvAppKey") or "").strip()
|
|
except Exception:
|
|
ui_key = ""
|
|
if ui_key:
|
|
info["makemkv_key"] = "ui"
|
|
elif os.getenv("MAKEMKV_APP_KEY"):
|
|
info["makemkv_key"] = "env"
|
|
else:
|
|
info["makemkv_key"] = "keiner"
|
|
# Sieht DIESER Worker eine KEYDB.cfg? Die API zeigt ihren eigenen Mount —
|
|
# bei einem Remote-Worker kann das etwas ganz anderes sein, und nur das
|
|
# Verzeichnis des rippenden Workers zählt (Befund 25.07.2026).
|
|
#
|
|
# Gemeldet wird die Angabe NUR, wenn das Datenverzeichnis hier wirklich
|
|
# eingehängt ist (os.path.ismount). Ein Remote-Transcode-Worker benutzt
|
|
# dasselbe Image — makemkvcon ist dort also vorhanden und der entrypoint
|
|
# legt den Ordner an —, er bekommt den Mount aber nicht und rippt nie.
|
|
# Ohne diese Prüfung trüge er im UI dauerhaft die Warnung "keine
|
|
# KEYDB.cfg", obwohl ihn das gar nichts angeht.
|
|
try:
|
|
from rippy.rip import makemkv_daten
|
|
daten_dir = makemkv_daten.DATEN_DIR
|
|
except Exception:
|
|
daten_dir = ""
|
|
if shutil.which("makemkvcon") and daten_dir and os.path.ismount(daten_dir):
|
|
try:
|
|
info["keydb"] = "ja" if makemkv_daten.keydb_status().get("vorhanden") else "nein"
|
|
except Exception:
|
|
info["keydb"] = "unbekannt"
|
|
# Die entscheidende Zahl für 4K-UHD: wie viele Disc-Schlüssel kennt
|
|
# dieser Worker? 0 heißt, dass jede unbekannte UHD-Disc scheitert —
|
|
# makemkvcon holt sie unter Linux nie selbst (Befund 25.07.2026).
|
|
try:
|
|
info["schluessel"] = str(makemkv_daten.schluesselspeicher_status().get("schluessel", 0))
|
|
except Exception:
|
|
info["schluessel"] = "unbekannt"
|
|
|
|
# Version für den Windows-Worker (aus version.txt) oder Linux-Worker (aus Env)
|
|
try:
|
|
ver_pfad = os.path.join(os.path.dirname(__file__), "version.txt")
|
|
if os.path.isfile(ver_pfad):
|
|
with open(ver_pfad, "r", encoding="utf-8") as f:
|
|
info["rippy_version"] = f.read().strip()
|
|
else:
|
|
info["rippy_version"] = os.getenv("RIPPY_VERSION", "dev")
|
|
except Exception:
|
|
info["rippy_version"] = "unbekannt"
|
|
|
|
return info
|