Track-Auswahl-Tabelle, nativer Windows-Worker (via UI angeboten), Anleitungs-Tab
Ampel / ampel (push) Successful in 29s
Ampel / ampel (push) Successful in 29s
Volle Titel-Auswahl vor dem Rip (Etappe-12-Rest):
- worker.tasks.scan_tracks: makemkvcon info -> Titel-Liste (Dauer/Groesse/
Kapitel, apdefs-Attr 8/9/11) in die settings-Tabelle 'tracks:<device>';
API: POST /devices/{n}/scan-tracks + GET /devices/{n}/tracks (Polling).
- Rip-Dialog: 'Disc scannen' -> Tabelle mit Checkboxen, Vorauswahl ab
5 min, Groessen-Summe; gewaehlte Titel als meta.titles in den Job.
- rip_titel_auswahl: ein makemkvcon-Aufruf je Titel (mkv kann nur einen
Titel oder all), Fortschritt anteilig. Parser-Tests dabei.
Nativer Windows-Worker OHNE Docker (Commander-Wunsch):
- Die Rippy-Instanz versorgt sich selbst: GET /worker-setup/windows
liefert install.ps1, /worker-setup/paket den Worker-Code als Zip
(api-Dockerfile kopiert docker/worker/*.py als worker_dist/ ins Image).
- install.ps1: Python-Check, venv + requirements, HandBrakeCLI 1.9.2 vom
offiziellen GitHub-Release (URL per HEAD verifiziert), start-worker.bat
mit celery -Q transcode --pool=solo (Celery-Doku: Windows nur solo),
optional -Autostart via schtasks onlogon.
- tasks.py laedt jetzt auch ohne fcntl (Import-Guard) — rip_disc ist auf
solchen Workern hart verriegelt (Klartext-Fehler statt Crash).
- Einstellungen -> Worker: Varianten-Umschalter Linux(Docker)/Windows(nativ)
mit passenden Copy-Paste-Befehlen.
Anleitungs-Tab: Rippy erklaert sich selbst (Disc-Weg, Serien, NAS,
Media-Server, Benachrichtigungen, Key, Worker, Downloads, FAQ).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+51
-5
@@ -23,12 +23,22 @@ import db
|
||||
import medien
|
||||
import notify
|
||||
from celery_app import celery_app
|
||||
from detection import detect_disc_type, disc_size_bytes
|
||||
|
||||
# fcntl gibt es nur unter Linux — der NATIVE Windows-Transcode-Worker
|
||||
# (deploy/worker-windows) lädt dieses Modul auch, bedient aber nur die
|
||||
# transcode-Queue. Rippen ohne detection ist unten hart verriegelt.
|
||||
try:
|
||||
from detection import detect_disc_type, disc_size_bytes
|
||||
except ImportError: # Windows: kein fcntl
|
||||
detect_disc_type = None
|
||||
disc_size_bytes = None
|
||||
|
||||
from ripping import (
|
||||
DEFAULT_HB_PRESET,
|
||||
RIP_OUTPUT_DIR,
|
||||
RipAbbruch,
|
||||
lies_datei_dauer,
|
||||
lies_titel_info,
|
||||
rip_cd,
|
||||
rip_video,
|
||||
run_handbrake,
|
||||
@@ -252,6 +262,16 @@ def rip_disc(self, device_path: str, job_id: str, target_dir: str = None):
|
||||
"""
|
||||
db.init_db()
|
||||
|
||||
if detect_disc_type is None:
|
||||
ergebnis = {
|
||||
"status": "error",
|
||||
"error": "Dieser Worker kann nur komprimieren (nativer "
|
||||
"Transcode-Worker ohne Laufwerks-Zugriff) — gerippt "
|
||||
"wird auf der Rippy-Hauptmaschine.",
|
||||
}
|
||||
_job_abschliessen(job_id, ergebnis)
|
||||
return ergebnis
|
||||
|
||||
if _abbruch_angefordert(job_id):
|
||||
_job_abschliessen(job_id, {"status": "cancelled"})
|
||||
return {"status": "cancelled"}
|
||||
@@ -311,12 +331,14 @@ def rip_disc(self, device_path: str, job_id: str, target_dir: str = None):
|
||||
job.get("title") or "", meta.get("year"), job_id,
|
||||
)
|
||||
|
||||
# Nur Hauptfilm (längster Titel): pro Rip wählbar, sonst Setting;
|
||||
# bei Serien-Discs sinnlos (dort zählen ALLE Episoden-Titel).
|
||||
# Titel-Wahl: explizite Auswahl aus der Track-Tabelle schlägt alles;
|
||||
# sonst „Nur Hauptfilm" (pro Rip wählbar, Fallback: Setting);
|
||||
# bei Serien-Discs ohne Auswahl zählen ALLE Episoden-Titel.
|
||||
titel_liste = [int(t) for t in (meta.get("titles") or []) if str(t).isdigit()]
|
||||
nur_hauptfilm = meta.get("main_feature_only")
|
||||
if nur_hauptfilm is None:
|
||||
nur_hauptfilm = bool(einstellungen.get("mainFeatureOnly", False))
|
||||
if meta.get("series"):
|
||||
if meta.get("series") or titel_liste:
|
||||
nur_hauptfilm = False
|
||||
# Geplantes Ziel sofort sichtbar machen (UI-Detail + retry-transcode)
|
||||
db.update_job(job_id, output_path=final_dir)
|
||||
@@ -326,7 +348,7 @@ def rip_disc(self, device_path: str, job_id: str, target_dir: str = None):
|
||||
# Platz-Check VOR dem Rip: Disc-Größe ist per ioctl bekannt — eine volle
|
||||
# Platte nach 40 GB wäre der teuerste Fehlschlag (4K-UHD: bis 100 GB roh).
|
||||
try:
|
||||
disc_bytes = disc_size_bytes(device_path)
|
||||
disc_bytes = disc_size_bytes(device_path) if disc_size_bytes else 0
|
||||
except OSError:
|
||||
disc_bytes = 0
|
||||
marge = 1024**3 # 1 GB Sicherheitsabstand
|
||||
@@ -349,12 +371,14 @@ def rip_disc(self, device_path: str, job_id: str, target_dir: str = None):
|
||||
progress_cb=fortschritt,
|
||||
output_dir=raw_dir,
|
||||
nur_hauptfilm=nur_hauptfilm,
|
||||
titel_liste=titel_liste,
|
||||
)
|
||||
else:
|
||||
ergebnis = rip_video(
|
||||
device_path, job_id, disc_type,
|
||||
progress_cb=fortschritt, output_dir=final_dir,
|
||||
nur_hauptfilm=nur_hauptfilm,
|
||||
titel_liste=titel_liste,
|
||||
)
|
||||
|
||||
# UHD-Fehler in Klartext übersetzen — "Failed to open disc" allein hilft
|
||||
@@ -397,6 +421,28 @@ def rip_disc(self, device_path: str, job_id: str, target_dir: str = None):
|
||||
return ergebnis
|
||||
|
||||
|
||||
@celery_app.task(name="worker.tasks.scan_tracks")
|
||||
def scan_tracks(device_path: str):
|
||||
"""Titel-Liste der eingelegten Disc erfassen (für die Auswahl-Tabelle).
|
||||
|
||||
Ergebnis landet in der settings-Tabelle (key 'tracks:<device>') — die
|
||||
API pollt darauf. Läuft auf der Haupt-Queue (nur der Rip-Worker hat das
|
||||
Laufwerk). Ein Info-Lauf dauert je nach Disc 20–120 s.
|
||||
"""
|
||||
db.init_db()
|
||||
key = f"tracks:{device_path}"
|
||||
db.save_settings({"status": "running"}, key)
|
||||
try:
|
||||
titel = lies_titel_info(device_path)
|
||||
db.save_settings({"status": "done", "tracks": titel}, key)
|
||||
db.add_log("info", "worker", f"Titel-Scan {device_path}: {len(titel)} Titel gefunden")
|
||||
return {"status": "done", "anzahl": len(titel)}
|
||||
except Exception as e:
|
||||
db.save_settings({"status": "error", "error": str(e)[:300]}, key)
|
||||
db.add_log("error", "worker", f"Titel-Scan {device_path} fehlgeschlagen: {e}")
|
||||
return {"status": "error", "error": str(e)}
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Reference in New Issue
Block a user