feat(tools): Werkzeuge finden, holen und aktuell halten — Grundlage fuer Windows
Ampel / ampel (push) Failing after 47s
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
059183c651
commit
288f9eeb8d
+20
-7
@@ -12,6 +12,19 @@ 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")
|
||||
@@ -148,11 +161,11 @@ def parse_preset_liste(text: str) -> list:
|
||||
|
||||
def hole_handbrake_presets() -> list:
|
||||
"""`HandBrakeCLI --preset-list` einmal abrufen (leer, wenn nicht installiert)."""
|
||||
if not shutil.which("HandBrakeCLI"):
|
||||
if not _hb():
|
||||
return []
|
||||
try:
|
||||
aus = subprocess.run(
|
||||
["HandBrakeCLI", "--preset-list"],
|
||||
[_hb(), "--preset-list"],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
@@ -262,11 +275,11 @@ def cpu_merkmale() -> tuple:
|
||||
|
||||
def hole_handbrake_hilfe() -> str:
|
||||
"""`HandBrakeCLI --help` einmal abrufen (leer, wenn nicht installiert)."""
|
||||
if not shutil.which("HandBrakeCLI"):
|
||||
if not _hb():
|
||||
return ""
|
||||
try:
|
||||
aus = subprocess.run(
|
||||
["HandBrakeCLI", "--help"], capture_output=True, text=True, timeout=30,
|
||||
[_hb(), "--help"], capture_output=True, text=True, timeout=30,
|
||||
creationflags=OHNE_FENSTER
|
||||
)
|
||||
return (aus.stdout or "") + (aus.stderr or "")
|
||||
@@ -349,12 +362,12 @@ def werkzeug_versionen() -> dict:
|
||||
presets = hole_handbrake_presets()
|
||||
if presets:
|
||||
info["presets"] = presets
|
||||
if shutil.which("makemkvcon"):
|
||||
if werkzeuge.finden("makemkv"):
|
||||
info["makemkv"] = os.getenv("MAKEMKV_VERSION") or "installiert"
|
||||
if shutil.which("HandBrakeCLI"):
|
||||
if _hb():
|
||||
try:
|
||||
aus = subprocess.run(
|
||||
["HandBrakeCLI", "--version"],
|
||||
[_hb(), "--version"],
|
||||
capture_output=True, text=True, timeout=15,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
|
||||
@@ -37,6 +37,31 @@ from rippy.drives.linux import ( # noqa: F401
|
||||
)
|
||||
from rippy.drives.linux import auswerfen_versuchen as wirf_disc_aus # noqa: F401
|
||||
from rippy.platform.winlauf import OHNE_FENSTER
|
||||
from rippy.tools import katalog as werkzeuge
|
||||
|
||||
_NACKTER_NAME = {"makemkv": "makemkvcon", "handbrake": "HandBrakeCLI"}
|
||||
|
||||
|
||||
def werkzeug(name: str) -> str:
|
||||
"""Der Pfad zu einem Werkzeug — oder sein blosser Name als Rueckfall.
|
||||
|
||||
## Warum hier nicht mehr `shutil.which` steht
|
||||
|
||||
Bis V2-4 stand ueberall in dieser Datei der nackte Programmname
|
||||
("makemkvcon", "HandBrakeCLI"). Im Container stimmt das — dort liegen
|
||||
beide in /usr/local/bin. **Unter Windows findet es NICHTS**, auch wenn
|
||||
beide installiert sind: Windows-Programme liegen in Program Files und
|
||||
stehen nicht im PATH. Am 28.08.2026 auf dem Commander-PC nachgesehen —
|
||||
MakeMKV lag da, und `shutil.which` sah es nicht.
|
||||
|
||||
Fuer einen Rippy, der unter Windows eigenstaendig arbeiten soll, ist das
|
||||
der Unterschied zwischen "laeuft" und "laeuft nicht".
|
||||
|
||||
Der Rueckfall auf den blossen Namen ist Absicht: Im Container aendert sich
|
||||
damit NICHTS — subprocess findet das Programm dort ueber den PATH wie
|
||||
bisher.
|
||||
"""
|
||||
return werkzeuge.finden(name) or _NACKTER_NAME[name]
|
||||
|
||||
RIP_OUTPUT_DIR = os.getenv("RIP_OUTPUT_DIR", "/app/media")
|
||||
|
||||
@@ -48,7 +73,7 @@ class RipAbbruch(Exception):
|
||||
|
||||
def check_makemkv_installed() -> bool:
|
||||
"""Prüft, ob makemkvcon installiert ist."""
|
||||
return shutil.which("makemkvcon") is not None
|
||||
return bool(werkzeuge.finden("makemkv"))
|
||||
|
||||
|
||||
def check_abcde_installed() -> bool:
|
||||
@@ -72,7 +97,7 @@ def build_makemkv_cmd(device_path: str, output_dir: str, titel: str = "all") ->
|
||||
mkv dev:<pfad> <titel> <ziel> 'all' oder eine Titel-Nummer (Hauptfilm)
|
||||
"""
|
||||
return [
|
||||
"makemkvcon",
|
||||
werkzeug("makemkv"),
|
||||
"-r",
|
||||
"--noscan",
|
||||
"--progress=-same",
|
||||
@@ -232,7 +257,7 @@ def lies_titel_info(device_path: str, timeout: int = 300) -> tuple:
|
||||
wäre eine Minute Wartezeit für nichts.
|
||||
"""
|
||||
ergebnis = subprocess.run(
|
||||
["makemkvcon", "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
@@ -305,7 +330,7 @@ def lies_titel_dauern(device_path: str, timeout: int = 300) -> dict:
|
||||
"""Fragt die Titel-Laufzeiten der Disc ab (makemkvcon info, Robot-Mode)."""
|
||||
try:
|
||||
ergebnis = subprocess.run(
|
||||
["makemkvcon", "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
[werkzeug("makemkv"), "-r", "--noscan", "info", f"dev:{device_path}"],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
@@ -334,7 +359,7 @@ def lies_datei_dauer(pfad: str, timeout: int = 120) -> int:
|
||||
return 0
|
||||
try:
|
||||
ergebnis = subprocess.run(
|
||||
["HandBrakeCLI", "--scan", "-i", pfad],
|
||||
[werkzeug("handbrake"), "--scan", "-i", pfad],
|
||||
capture_output=True, text=True, timeout=timeout,
|
||||
creationflags=OHNE_FENSTER,
|
||||
)
|
||||
@@ -381,7 +406,7 @@ def get_progress_from_prgv(line: str) -> int:
|
||||
|
||||
def check_handbrake_installed() -> bool:
|
||||
"""Prüft, ob HandBrakeCLI installiert ist."""
|
||||
return shutil.which("HandBrakeCLI") is not None
|
||||
return bool(werkzeuge.finden("handbrake"))
|
||||
|
||||
|
||||
DEFAULT_HB_PRESET = "H.265 MKV 1080p30"
|
||||
@@ -478,7 +503,7 @@ def build_handbrake_cmd(input_path: str, output_path: str,
|
||||
diese dann verlustfrei durch, statt sie auf Stereo herunterzurechnen.
|
||||
"""
|
||||
befehl = [
|
||||
"HandBrakeCLI",
|
||||
werkzeug("handbrake"),
|
||||
"--input", input_path,
|
||||
"--output", output_path,
|
||||
"--preset", preset,
|
||||
|
||||
@@ -20,7 +20,13 @@ from ripping import (
|
||||
|
||||
def test_makemkv_cmd_vollstaendig():
|
||||
cmd = build_makemkv_cmd("/dev/sr0", "/app/media/dvd/x")
|
||||
assert cmd[0] == "makemkvcon"
|
||||
# Seit V2-4 steht hier der GEFUNDENE Pfad statt des blossen Namens: Unter
|
||||
# Windows liegt makemkvcon in Program Files und nicht im PATH — mit dem
|
||||
# nackten Namen faende `subprocess` es dort nie. Im Container bleibt es
|
||||
# der blosse Name (der Katalog findet nichts, der Rueckfall greift), auf
|
||||
# dem Entwicklungsrechner ist es der volle Pfad. Beides ist richtig;
|
||||
# gepruefet wird deshalb, WORAUF der Befehl zeigt.
|
||||
assert "makemkvcon" in cmd[0].lower()
|
||||
assert "-r" in cmd # Robot-Mode: maschinenlesbar
|
||||
assert "--noscan" in cmd # Scan hängt/crasht im Container (23.07.)
|
||||
assert "--progress=-same" in cmd # Fortschritt im selben Stream
|
||||
|
||||
Reference in New Issue
Block a user