Track-Auswahl-Tabelle, nativer Windows-Worker (via UI angeboten), Anleitungs-Tab
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:
Hitonabi
2026-07-24 15:18:08 +02:00
parent 325d95af6f
commit 3a4eb25392
15 changed files with 738 additions and 44 deletions
+6
View File
@@ -17,4 +17,10 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY docker/api/ .
# Worker-Selbstversorgung: die API liefert Installer + Worker-Code an
# native Worker aus (GET /worker-setup/windows bzw. /worker-setup/paket) —
# die Zielmaschine braucht weder git noch Docker.
COPY docker/worker/*.py docker/worker/requirements.txt worker_dist/
COPY deploy/worker-windows/install.ps1 worker_dist/
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app"]
+77
View File
@@ -337,6 +337,7 @@ class JobCreateRequest(BaseModel):
series: Optional[str] = None # Serien-Flow: Ablage <Serie>/Season NN
season: Optional[int] = None
main_feature_only: Optional[bool] = None # pro Rip; None = Setting gilt
titles: Optional[List[int]] = None # exakte Titel-Auswahl (Track-Tabelle)
MEDIA_ROOT = "/app/media"
@@ -390,6 +391,10 @@ async def create_job(request: JobCreateRequest):
meta_dict["season"] = max(1, int(request.season or 1))
if request.main_feature_only is not None:
meta_dict["main_feature_only"] = request.main_feature_only
if request.titles:
titel_liste = sorted({int(t) for t in request.titles if int(t) >= 0})[:200]
if titel_liste:
meta_dict["titles"] = titel_liste
meta_json = json.dumps(meta_dict) if meta_dict else None
job_id = str(uuid.uuid4())
@@ -542,6 +547,34 @@ async def eject_device(name: str):
return {"status": "ejected", "device": device_path}
@app.post("/devices/{name}/scan-tracks")
async def scan_tracks_starten(name: str):
"""Titel-Scan der eingelegten Disc anstoßen (Track-Auswahl-Tabelle).
Läuft als Worker-Task (nur der hat makemkvcon + Laufwerk); das UI pollt
GET /devices/{name}/tracks. Dauert je nach Disc 20120 s.
"""
device_path = f"/dev/{name}"
if device_path not in device_discovery.list_optical_devices():
raise HTTPException(status_code=404, detail=f"Laufwerk {device_path} nicht gefunden")
if await asyncio.to_thread(db.has_active_job, device_path):
raise HTTPException(status_code=409, detail="Auf diesem Laufwerk läuft gerade ein Job")
await asyncio.to_thread(db.save_settings, {"status": "running"}, f"tracks:{device_path}")
celery_client.send_task("worker.tasks.scan_tracks", args=[device_path])
return {"status": "scanning"}
@app.get("/devices/{name}/tracks")
async def scan_tracks_ergebnis(name: str):
"""Ergebnis des Titel-Scans (Polling-Ziel des UI)."""
device_path = f"/dev/{name}"
daten = await asyncio.to_thread(db.get_settings, f"tracks:{device_path}")
if not daten:
return {"status": "none"}
return daten
@app.post("/jobs/{job_id}/retry-transcode")
async def retry_transcode(job_id: str):
"""Stößt die Kompression eines Jobs neu an — OHNE die Disc neu zu rippen.
@@ -902,6 +935,50 @@ async def browse_mkdir(request: MkdirRequest):
return {"path": ziel}
@app.get("/worker-setup/windows")
async def worker_setup_windows():
"""Windows-Installer-Skript für den nativen Transcode-Worker.
Das UI (Einstellungen → Worker → Windows) zeigt den passenden
Zwei-Zeilen-Aufruf — alles kommt von dieser Rippy-Instanz selbst.
"""
pfad = "worker_dist/install.ps1"
if not os.path.isfile(pfad):
raise HTTPException(status_code=404, detail="Installer nicht im Image — API neu bauen")
return FileResponse(pfad, media_type="text/plain", filename="install-rippy-worker.ps1")
@app.get("/worker-setup/paket")
async def worker_setup_paket():
"""Worker-Quellcode als Zip — der Windows-Installer lädt ihn von hier.
Kein git, kein Docker auf der Zielmaschine nötig: die Rippy-Instanz
versorgt ihre Worker selbst (Dateien liegen via Dockerfile im Image).
"""
def baue():
import io
import zipfile
if not os.path.isdir("worker_dist"):
return None
puffer = io.BytesIO()
with zipfile.ZipFile(puffer, "w", zipfile.ZIP_DEFLATED) as z:
for name in sorted(os.listdir("worker_dist")):
if name.endswith(".py") and not name.startswith(("test_", "conftest")):
z.write(os.path.join("worker_dist", name), name)
elif name == "requirements.txt":
z.write(os.path.join("worker_dist", name), name)
return puffer.getvalue()
inhalt = await asyncio.to_thread(baue)
if not inhalt:
raise HTTPException(status_code=404, detail="Worker-Paket nicht im Image — API neu bauen")
return Response(
content=inhalt, media_type="application/zip",
headers={"Content-Disposition": 'attachment; filename="rippy-worker.zip"'},
)
@app.get("/system/info")
async def system_info():
"""System-Selbstauskunft (Einstellungen → System): Werkzeug-Versionen der