feat(backend): Media-Server-Aufbereitung, echte Webhooks, SMB-Klartext, UHD-Arbeitsverzeichnis, MakeMKV-Key via UI
- jobs.meta (Migration in beiden db.py): Disc-Metadaten wandern in den Job —
Quelle fuer Job-Detail-Popup (GET /jobs/{id}/detail), Ordner-Benennung, NFO.
- medien.py (Worker, mit Tests): Zielordner 'Titel (Jahr)' statt Job-UUID;
movie.nfo/tvshow.nfo (Kodi-Schema, kodi.wiki/view/NFO_files) + poster.jpg
fuer jellyfin/emby/kodi; plex nur Benennung; Kollision -> Job-ID-Suffix.
- notify.py (identisch in API+Worker, mit Tests): Webhook bei Job-Ende —
Discord ({content}, discord.com/developers), Slack ({text},
api.slack.com/messaging/webhooks), ntfy (Rohtext + ?title=, docs.ntfy.sh),
generisches JSON. Vorher war das Setting ein Placebo: nichts sendete je.
POST /notifications/test beweist die Anbindung sofort.
- mounts.py: NT_STATUS-Fehler -> handelbarer Klartext (ACCESS_DENIED ohne
Credentials = Gast-Abfrage verweigert), Timeout-Meldung, mount-Hinweis
bei error(13). Mit Tests.
- tasks.py: Platz-Check per Disc-Groesse (ioctl BLKGETSIZE64) VOR dem Rip;
Arbeitsverzeichnis workDir (unter /app/media, z.B. NAS) statt fix
/app/temp/raw — 4K-UHD-Rohdaten (bis 100 GB) sprengen sonst die VM-Platte;
MakeMKV-Key aus UI-Settings (~/.MakeMKV/settings.conf, Format wie
entrypoint.sh) gilt ab dem naechsten Rip ohne Rebuild.
- caps.py: Werkzeug-Versionen (MakeMKV aus ENV MAKEMKV_VERSION im Image,
makemkvcon hat keinen --version-Schalter lt. usage.txt; HandBrakeCLI
--version lt. handbrake.fr/docs) + Key-Quelle; workers.info-Spalte.
- /browse liefert jetzt auch DATEIEN (Name + Groesse) — der 'leere'
bluray-Ordner war voll, der Browser zeigte nur Unterordner.
- GET /system/info: Versionen, Plattenplatz, Key-/Webhook-Status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+53
-3
@@ -49,6 +49,40 @@ def schreibtest(pfad: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def uebersetze_smb_fehler(fehler: str, mit_credentials: bool) -> str:
|
||||
"""Pure Funktion (testbar): NT_STATUS-Kauderwelsch → handelbarer Klartext.
|
||||
|
||||
Befund 24.07.: „session setup failed: NT_STATUS_ACCESS_DENIED" hieß in
|
||||
Wahrheit nur „Windows/NAS erlauben keine Gast-Abfrage" — ohne Übersetzung
|
||||
rät niemand, dass oben Benutzer/Passwort fehlen.
|
||||
"""
|
||||
if "NT_STATUS_ACCESS_DENIED" in fehler:
|
||||
if not mit_credentials:
|
||||
return (
|
||||
"Zugriff verweigert — dieser Rechner erlaubt keine Gast-Abfrage. "
|
||||
"Benutzername + Passwort eintragen (Windows: ein Konto mit "
|
||||
"Zugriff auf die Freigabe), dann erneut auflisten."
|
||||
)
|
||||
return (
|
||||
"Zugriff verweigert — Benutzername/Passwort stimmen nicht oder das "
|
||||
"Konto darf die Freigaben nicht auflisten."
|
||||
)
|
||||
if "NT_STATUS_LOGON_FAILURE" in fehler:
|
||||
return "Anmeldung fehlgeschlagen — Benutzername oder Passwort falsch."
|
||||
if (
|
||||
"NT_STATUS_HOST_UNREACHABLE" in fehler
|
||||
or "NT_STATUS_IO_TIMEOUT" in fehler
|
||||
or "NT_STATUS_UNSUCCESSFUL" in fehler
|
||||
or "NT_STATUS_CONNECTION_REFUSED" in fehler
|
||||
or "Connection to" in fehler
|
||||
):
|
||||
return (
|
||||
"Rechner nicht erreichbar — Name/IP prüfen; läuft dort ein "
|
||||
"SMB-Dienst (Port 445, Datei- und Druckerfreigabe aktiv)?"
|
||||
)
|
||||
return fehler[:200]
|
||||
|
||||
|
||||
def liste_smb_freigaben(host: str, username: str = "", passwort: str = "") -> list:
|
||||
"""Listet die SMB-Freigaben eines Rechners (smbclient -L) — damit man
|
||||
seinen PC/NAS per Klick wählt statt //host/share zu raten."""
|
||||
@@ -57,7 +91,13 @@ def liste_smb_freigaben(host: str, username: str = "", passwort: str = "") -> li
|
||||
cmd += ["-U", f"{username}%{passwort or ''}"]
|
||||
else:
|
||||
cmd += ["-N"]
|
||||
ergebnis = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
|
||||
try:
|
||||
ergebnis = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise RuntimeError(
|
||||
f"Freigaben-Abfrage fehlgeschlagen: {host} antwortet nicht "
|
||||
"(Timeout nach 20 s) — Name/IP und Netzwerk prüfen."
|
||||
)
|
||||
freigaben = []
|
||||
for zeile in (ergebnis.stdout or "").splitlines():
|
||||
# -g-Format: Disk|Freigabename|Kommentar
|
||||
@@ -66,7 +106,10 @@ def liste_smb_freigaben(host: str, username: str = "", passwort: str = "") -> li
|
||||
freigaben.append(teile[1])
|
||||
if not freigaben and ergebnis.returncode != 0:
|
||||
fehler = (ergebnis.stderr or ergebnis.stdout or "").strip()
|
||||
raise RuntimeError(f"Freigaben-Abfrage fehlgeschlagen: {fehler[:200]}")
|
||||
raise RuntimeError(
|
||||
"Freigaben-Abfrage fehlgeschlagen: "
|
||||
+ uebersetze_smb_fehler(fehler, bool(username))
|
||||
)
|
||||
return freigaben
|
||||
|
||||
|
||||
@@ -109,7 +152,14 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
|
||||
ergebnis = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
if ergebnis.returncode != 0:
|
||||
fehler = (ergebnis.stderr or ergebnis.stdout or "").strip()
|
||||
raise RuntimeError(f"mount schlug fehl: {fehler[:300]}")
|
||||
hinweis = ""
|
||||
if "error(13)" in fehler or "Permission denied" in fehler:
|
||||
hinweis = (
|
||||
" — Zugriff verweigert: Benutzer/Passwort prüfen; ohne "
|
||||
"Angaben versucht Rippy einen Gast-Zugriff, den Windows/"
|
||||
"NAS meist ablehnen."
|
||||
)
|
||||
raise RuntimeError(f"mount schlug fehl: {fehler[:300]}{hinweis}")
|
||||
return schreibtest(ziel)
|
||||
finally:
|
||||
if creds_datei:
|
||||
|
||||
Reference in New Issue
Block a user