From c3d1df42b0704ebf8c89b4841057ac97518e7e29 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Thu, 5 Mar 2026 11:21:47 +0000 Subject: [PATCH] Docs --- docs/api/pipeline.md | 266 ++++++++++++----------- docs/api/websocket.md | 241 +++++++++----------- docs/architecture/backend.md | 59 ++--- docs/architecture/frontend.md | 28 +-- docs/architecture/index.md | 2 +- docs/architecture/overview.md | 17 +- docs/configuration/settings-reference.md | 2 +- docs/getting-started/configuration.md | 2 +- docs/getting-started/quickstart.md | 93 ++++---- docs/index.md | 17 +- docs/pipeline/encoding.md | 4 +- docs/pipeline/post-encode-scripts.md | 23 +- docs/pipeline/workflow.md | 154 +++++++------ 13 files changed, 461 insertions(+), 447 deletions(-) diff --git a/docs/api/pipeline.md b/docs/api/pipeline.md index f4458b9..9f7e989 100644 --- a/docs/api/pipeline.md +++ b/docs/api/pipeline.md @@ -1,29 +1,34 @@ # Pipeline API -Alle Endpunkte zur Steuerung des Ripping-Workflows. +Alle Endpunkte zur Steuerung des Ripster-Workflows. --- ## GET /api/pipeline/state -Gibt den aktuellen Pipeline-Zustand zurück. +Liefert den aktuellen Pipeline-Snapshot. **Response:** ```json { - "state": "ENCODING", - "jobId": 42, - "job": { - "id": 42, - "title": "Inception", - "status": "ENCODING", - "imdb_id": "tt1375666", - "omdb_year": "2010" - }, - "progress": 73.5, - "eta": "00:12:34", - "updatedAt": "2024-01-15T14:30:00.000Z" + "pipeline": { + "state": "READY_TO_ENCODE", + "activeJobId": 42, + "progress": 0, + "eta": null, + "statusText": "Mediainfo geladen - bitte bestätigen", + "context": { + "jobId": 42 + }, + "queue": { + "maxParallelJobs": 1, + "runningCount": 0, + "queuedCount": 0, + "runningJobs": [], + "queuedJobs": [] + } + } } ``` @@ -31,84 +36,73 @@ Gibt den aktuellen Pipeline-Zustand zurück. | Wert | Beschreibung | |------|-------------| -| `IDLE` | Wartet auf Disc | -| `DISC_DETECTED` | Disc erkannt, wartet auf Benutzer | -| `METADATA_SELECTION` | Disc-Scan läuft / Metadaten-Dialog | -| `WAITING_FOR_USER_DECISION` | Mehrere Playlist-Kandidaten – manuelle Auswahl | -| `READY_TO_START` | Bereit zum Starten | -| `RIPPING` | MakeMKV-Ripping läuft | -| `MEDIAINFO_CHECK` | HandBrake-Scan & Encode-Plan-Erstellung | -| `READY_TO_ENCODE` | Wartet auf Encode-Bestätigung | -| `ENCODING` | HandBrake encodiert | -| `POST_ENCODE_SCRIPTS` | Post-Encode-Skripte laufen | +| `IDLE` | Wartet auf Medium | +| `DISC_DETECTED` | Medium erkannt, wartet auf Analyse-Start | +| `METADATA_SELECTION` | Metadaten-Dialog aktiv | +| `WAITING_FOR_USER_DECISION` | Manuelle Playlist-Auswahl erforderlich | +| `READY_TO_START` | Übergang/Fallback vor Start | +| `RIPPING` | MakeMKV läuft | +| `MEDIAINFO_CHECK` | HandBrake-Scan + Plan-Erstellung | +| `READY_TO_ENCODE` | Review bereit | +| `ENCODING` | HandBrake-Encoding läuft (inkl. Post-Skripte) | | `FINISHED` | Abgeschlossen | +| `CANCELLED` | Vom Benutzer abgebrochen | | `ERROR` | Fehler | -**Kontext-Felder (state-abhängig):** +--- -Beim Zustand `WAITING_FOR_USER_DECISION` enthält die Response zusätzlich: +## POST /api/pipeline/analyze + +Startet die Analyse für die aktuell erkannte Disc. + +**Request:** kein Body + +**Response:** ```json { - "state": "WAITING_FOR_USER_DECISION", - "context": { - "playlistAnalysis": { - "evaluatedCandidates": [...], - "recommendation": { "playlistId": "00800", "score": 18 }, - "manualDecisionRequired": true, - "manualDecisionReason": "multiple_candidates_after_min_length" - }, - "playlistCandidates": ["00800", "00801", "00900"] + "result": { + "jobId": 42, + "detectedTitle": "INCEPTION", + "omdbCandidates": [] } } ``` --- -## POST /api/pipeline/analyze - -Startet eine manuelle Disc-Analyse. - -**Request:** Kein Body - -**Response:** - -```json -{ "ok": true, "message": "Analyse gestartet" } -``` - -**Fehlerfälle:** -- `409` – Pipeline bereits aktiv - ---- - ## POST /api/pipeline/rescan-disc -Erzwingt eine erneute Disc-Erkennung. +Erzwingt eine erneute Laufwerksprüfung. -**Response:** `{ "ok": true }` +**Response (Beispiel):** + +```json +{ + "result": { + "emitted": "discInserted" + } +} +``` --- -## GET /api/pipeline/omdb/search +## GET /api/pipeline/omdb/search?q= -Sucht in der OMDb-API nach einem Filmtitel. - -**Query-Parameter:** - -| Parameter | Typ | Beschreibung | -|----------|-----|-------------| -| `q` | string | Suchbegriff | -| `type` | string | `movie` oder `series` (optional) | - -**Beispiel:** `GET /api/pipeline/omdb/search?q=Inception&type=movie` +Sucht OMDb-Titel. **Response:** ```json { "results": [ - { "imdbId": "tt1375666", "title": "Inception", "year": "2010", "type": "movie", "poster": "https://..." } + { + "imdbId": "tt1375666", + "title": "Inception", + "year": "2010", + "type": "movie", + "poster": "https://..." + } ] } ``` @@ -117,54 +111,52 @@ Sucht in der OMDb-API nach einem Filmtitel. ## POST /api/pipeline/select-metadata -Bestätigt Metadaten und optionale Playlist-Auswahl. +Setzt Metadaten (und optional Playlist-Entscheidung). **Request:** ```json { "jobId": 42, - "omdb": { - "imdbId": "tt1375666", - "title": "Inception", - "year": "2010", - "type": "movie", - "poster": "https://..." - }, + "title": "Inception", + "year": 2010, + "imdbId": "tt1375666", + "poster": "https://...", + "fromOmdb": true, "selectedPlaylist": "00800" } ``` -!!! info "Playlist-Felder" - `selectedPlaylist` ist optional. Wird es beim ersten Aufruf weggelassen (kein Obfuskierungsverdacht), wird die Empfehlung automatisch übernommen. +**Response:** `{ "job": { ... } }` - Beim zweiten Aufruf aus dem `WAITING_FOR_USER_DECISION`-Dialog reicht es, nur `jobId` + `selectedPlaylist` zu schicken – `omdb` kann dann weggelassen werden. - -**Response:** `{ "ok": true }` +!!! note "Startlogik" + Nach Metadaten-Bestätigung wird der nächste Schritt automatisch ausgelöst (`startPreparedJob`). + Der Job startet direkt oder wird in die Queue eingereiht. --- ## POST /api/pipeline/start/:jobId -Startet den Ripping-Prozess. +Startet einen vorbereiteten Job manuell (z. B. Fallback/Queue-Szenario). -**URL-Parameter:** `jobId` +**Response (Beispiel):** -**Response:** `{ "ok": true, "message": "Ripping gestartet" }` +```json +{ + "result": { + "started": true, + "stage": "RIPPING" + } +} +``` -**Sonderfall:** Falls für den Job bereits eine Raw-Datei vorhanden ist, wird das Ripping übersprungen und direkt der HandBrake-Scan gestartet. - -**Fehlerfälle:** -- `404` – Job nicht gefunden -- `409` – Job nicht im Status `READY_TO_START` +Mögliche `stage`-Werte sind u. a. `RIPPING`, `MEDIAINFO_CHECK`, `ENCODING`. --- ## POST /api/pipeline/confirm-encode/:jobId -Bestätigt die Encode-Konfiguration mit Track-Auswahl und Post-Encode-Skripten. - -**URL-Parameter:** `jobId` +Bestätigt Review-Auswahl (Titel/Tracks/Post-Skripte). **Request:** @@ -174,72 +166,102 @@ Bestätigt die Encode-Konfiguration mit Track-Auswahl und Post-Encode-Skripten. "selectedTrackSelection": { "1": { "audioTrackIds": [1, 2], - "subtitleTrackIds": [1] + "subtitleTrackIds": [3] } }, - "selectedPostEncodeScriptIds": ["script-abc123", "script-def456"] + "selectedPostEncodeScriptIds": [2, 7], + "skipPipelineStateUpdate": false } ``` -| Feld | Typ | Beschreibung | -|------|-----|-------------| -| `selectedEncodeTitleId` | number | HandBrake-Titel-ID (aus dem Encode-Plan) | -| `selectedTrackSelection` | object | Pro Titel: Audio- und Untertitel-Track-IDs | -| `selectedPostEncodeScriptIds` | string[] | Skript-IDs in Ausführungsreihenfolge (optional) | - -!!! note "Track-IDs" - Die Track-IDs entsprechen den `id`-Feldern aus dem Encode-Plan (`encode_plan_json`), nicht den rohen HandBrake-Track-Nummern. - -**Response:** `{ "ok": true, "message": "Encoding gestartet" }` +**Response:** `{ "job": { ... } }` --- ## POST /api/pipeline/cancel -Bricht den aktiven Pipeline-Prozess ab. +Bricht laufenden Job ab oder entfernt einen Queue-Eintrag. -**Response:** `{ "ok": true, "message": "Pipeline abgebrochen" }` +**Request (optional):** -SIGINT → graceful exit (10 s Timeout) → SIGKILL. +```json +{ + "jobId": 42 +} +``` + +**Response (Beispiel):** + +```json +{ + "result": { + "cancelled": true, + "queuedOnly": false, + "jobId": 42 + } +} +``` --- ## POST /api/pipeline/retry/:jobId -Wiederholt einen fehlgeschlagenen Job. +Startet einen Job aus `ERROR`/`CANCELLED` erneut (oder reiht ihn in die Queue ein). -**Response:** `{ "ok": true, "message": "Job wird wiederholt" }` - -**Fehlerfälle:** -- `404` – Job nicht gefunden -- `409` – Job nicht im Status `ERROR` +**Response:** `{ "result": { ... } }` --- ## POST /api/pipeline/resume-ready/:jobId -Reaktiviert einen Job im Status `READY_TO_ENCODE` in die aktive Pipeline (z. B. nach Neustart). +Lädt einen `READY_TO_ENCODE`-Job nach Neustart wieder in die aktive Session. -**Response:** `{ "ok": true }` +**Response:** `{ "job": { ... } }` --- ## POST /api/pipeline/reencode/:jobId -Encodiert eine abgeschlossene Raw-MKV erneut – ohne Ripping. +Startet Re-Encode aus bestehendem RAW. + +**Response:** `{ "result": { ... } }` + +--- + +## POST /api/pipeline/restart-review/:jobId + +Berechnet die Review aus vorhandenem RAW neu. + +**Response:** `{ "result": { ... } }` + +--- + +## POST /api/pipeline/restart-encode/:jobId + +Startet Encoding mit der zuletzt bestätigten Auswahl neu. + +**Response:** `{ "result": { ... } }` + +--- + +## Queue-Endpunkte + +### GET /api/pipeline/queue + +Liefert den aktuellen Queue-Status. + +**Response:** `{ "queue": { ... } }` + +### POST /api/pipeline/queue/reorder + +Sortiert Queue-Einträge neu. **Request:** ```json { - "selectedEncodeTitleId": 1, - "selectedTrackSelection": { - "1": { "audioTrackIds": [1, 2], "subtitleTrackIds": [1] } - }, - "selectedPostEncodeScriptIds": ["script-abc123"] + "orderedJobIds": [42, 43, 41] } ``` -Gleiche Struktur wie `confirm-encode` – ermöglicht andere Track-Auswahl und Skripte als beim ersten Encoding. - -**Response:** `{ "ok": true, "message": "Re-Encoding gestartet" }` +**Response:** `{ "queue": { ... } }` diff --git a/docs/api/websocket.md b/docs/api/websocket.md index e24e0e9..4daf488 100644 --- a/docs/api/websocket.md +++ b/docs/api/websocket.md @@ -1,6 +1,6 @@ # WebSocket Events -Ripster verwendet WebSockets für Echtzeit-Updates. Der Endpunkt ist `/ws`. +Ripster sendet Echtzeit-Updates über WebSocket unter `/ws`. --- @@ -11,20 +11,21 @@ const ws = new WebSocket('ws://localhost:3001/ws'); ws.onmessage = (event) => { const message = JSON.parse(event.data); - console.log(message.type, message.data); + console.log(message.type, message.payload); }; ``` --- -## Nachrichten-Format +## Nachrichtenformat -Alle Nachrichten folgen diesem Schema: +Alle Broadcasts haben dieses Schema: ```json { "type": "EVENT_TYPE", - "data": { ... } + "payload": { }, + "timestamp": "2026-03-05T10:00:00.000Z" } ``` @@ -32,194 +33,154 @@ Alle Nachrichten folgen diesem Schema: ## Event-Typen -### PIPELINE_STATE_CHANGE +### WS_CONNECTED -Wird gesendet, wenn der Pipeline-Zustand wechselt. +Wird direkt nach Verbindungsaufbau gesendet. ```json { - "type": "PIPELINE_STATE_CHANGE", - "data": { + "type": "WS_CONNECTED", + "payload": { + "connectedAt": "2026-03-05T10:00:00.000Z" + } +} +``` + +### PIPELINE_STATE_CHANGED + +Snapshot bei Zustandswechsel. + +```json +{ + "type": "PIPELINE_STATE_CHANGED", + "payload": { "state": "ENCODING", - "jobId": 42, - "job": { - "id": 42, - "title": "Inception", - "status": "ENCODING" + "activeJobId": 42, + "progress": 73.5, + "eta": "00:12:34", + "statusText": "Encoding mit HandBrake", + "context": {}, + "queue": { + "maxParallelJobs": 1, + "runningCount": 1, + "queuedCount": 0 } } } ``` ---- +### PIPELINE_PROGRESS -### PROGRESS_UPDATE - -Wird während aktiver Prozesse (Ripping/Encoding) regelmäßig gesendet. +Laufende Fortschrittsupdates während aktiver Phasen. ```json { - "type": "PROGRESS_UPDATE", - "data": { + "type": "PIPELINE_PROGRESS", + "payload": { + "state": "ENCODING", + "activeJobId": 42, "progress": 73.5, "eta": "00:12:34", - "speed": "45.2 fps", - "phase": "ENCODING" + "statusText": "ENCODING 73.50% - task 1 of 1" } } ``` -**Felder:** +### PIPELINE_QUEUE_CHANGED -| Feld | Typ | Beschreibung | -|-----|-----|-------------| -| `progress` | number | Fortschritt 0–100 | -| `eta` | string | Geschätzte Restzeit (`HH:MM:SS`) | -| `speed` | string | Encoding-Geschwindigkeit (nur beim Encoding) | -| `phase` | string | Aktuelle Phase (`RIPPING` oder `ENCODING`) | +Aktualisierung der Job-Queue. ---- +```json +{ + "type": "PIPELINE_QUEUE_CHANGED", + "payload": { + "maxParallelJobs": 1, + "runningCount": 1, + "queuedCount": 2, + "runningJobs": [], + "queuedJobs": [] + } +} +``` ### DISC_DETECTED -Wird gesendet, wenn eine Disc erkannt wird. +Disc erkannt. ```json { "type": "DISC_DETECTED", - "data": { - "device": "/dev/sr0" + "payload": { + "device": { + "path": "/dev/sr0", + "discLabel": "INCEPTION" + } } } ``` ---- - ### DISC_REMOVED -Wird gesendet, wenn eine Disc ausgeworfen wird. +Disc entfernt. ```json { "type": "DISC_REMOVED", - "data": { - "device": "/dev/sr0" - } -} -``` - ---- - -### JOB_COMPLETE - -Wird gesendet, wenn ein Job erfolgreich abgeschlossen wurde. - -```json -{ - "type": "JOB_COMPLETE", - "data": { - "jobId": 42, - "title": "Inception", - "outputPath": "/mnt/nas/movies/Inception (2010).mkv" - } -} -``` - ---- - -### ERROR - -Wird gesendet, wenn ein Fehler aufgetreten ist. - -```json -{ - "type": "ERROR", - "data": { - "jobId": 42, - "message": "HandBrake ist abgestürzt", - "details": "Exit code: 1\nStderr: ..." - } -} -``` - ---- - -### METADATA_REQUIRED - -Wird gesendet, wenn Benutzer-Eingabe für Metadaten benötigt wird. - -```json -{ - "type": "METADATA_REQUIRED", - "data": { - "jobId": 42, - "makemkvData": { ... }, - "playlistAnalysis": { ... } - } -} -``` - ---- - -### ENCODE_REVIEW_REQUIRED - -Wird gesendet, wenn der Benutzer den Encode-Plan bestätigen soll. - -```json -{ - "type": "ENCODE_REVIEW_REQUIRED", - "data": { - "jobId": 42, - "encodePlan": { - "audioTracks": [ ... ], - "subtitleTracks": [ ... ] + "payload": { + "device": { + "path": "/dev/sr0" } } } ``` +### PIPELINE_ERROR + +Fehler bei Pipeline-Disc-Events im Backend. + +```json +{ + "type": "PIPELINE_ERROR", + "payload": { + "message": "..." + } +} +``` + +### DISK_DETECTION_ERROR + +Fehler im Laufwerkserkennungsdienst. + +```json +{ + "type": "DISK_DETECTION_ERROR", + "payload": { + "message": "..." + } +} +``` + --- ## Reconnect-Verhalten -Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect: +`useWebSocket.js` versucht bei Verbindungsabbruch automatisch erneut zu verbinden. -``` -Verbindung verloren - ↓ -Warte 1s → Reconnect-Versuch - ↓ (Fehlschlag) -Warte 2s → Reconnect-Versuch - ↓ (Fehlschlag) -Warte 4s → ... - ↓ -Max. 30s Wartezeit -``` +- fester Retry-Intervall: `1500ms` +- erneuter Versuch bis zum Unmount der Komponente --- -## Beispiel: React-Hook +## React-Beispiel ```js -import { useEffect, useState } from 'react'; +import { useWebSocket } from './hooks/useWebSocket'; -function usePipelineState() { - const [state, setState] = useState({ state: 'IDLE' }); - - useEffect(() => { - const ws = new WebSocket(import.meta.env.VITE_WS_URL + '/ws'); - - ws.onmessage = (event) => { - const msg = JSON.parse(event.data); - - if (msg.type === 'PIPELINE_STATE_CHANGE') { - setState(msg.data); - } - }; - - return () => ws.close(); - }, []); - - return state; -} +useWebSocket({ + onMessage: (msg) => { + if (msg.type === 'PIPELINE_STATE_CHANGED') { + setPipeline(msg.payload); + } + } +}); ``` diff --git a/docs/architecture/backend.md b/docs/architecture/backend.md index 1c28ecd..8ac6259 100644 --- a/docs/architecture/backend.md +++ b/docs/architecture/backend.md @@ -19,13 +19,16 @@ Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, d | Methode | Beschreibung | |---------|-------------| -| `analyzeDisc()` | Startet MakeMKV-Analyse der eingelegten Disc | -| `selectMetadata(jobId, omdbData, playlist)` | Setzt Metadaten und Playlist für einen Job | -| `startJob(jobId)` | Startet den Ripping-Prozess | -| `confirmEncode(jobId, trackSelection)` | Bestätigt Encode mit Track-Auswahl | -| `cancelPipeline()` | Bricht aktiven Prozess ab | -| `retryJob(jobId)` | Wiederholt fehlgeschlagenen Job | -| `reencodeJob(jobId)` | Encodiert bestehende Raw-MKV neu | +| `analyzeDisc()` | Legt Job an und öffnet Metadaten-Auswahl | +| `selectMetadata({...})` | Setzt Metadaten/Playlist und triggert Auto-Start | +| `startPreparedJob(jobId)` | Startet vorbereiteten Job (oder Queue) | +| `confirmEncodeReview(jobId, options)` | Bestätigt Review inkl. Track/Skript-Auswahl | +| `cancel(jobId)` | Bricht laufenden Job ab oder entfernt Queue-Eintrag | +| `retry(jobId)` | Startet fehlgeschlagenen/abgebrochenen Job neu | +| `reencodeFromRaw(jobId)` | Encodiert aus vorhandenem RAW neu | +| `restartReviewFromRaw(jobId)` | Berechnet Review aus RAW neu | +| `restartEncodeWithLastSettings(jobId)` | Neustart mit letzter bestätigter Auswahl | +| `resumeReadyToEncodeJob(jobId)` | Lädt READY_TO_ENCODE nach Neustart in die Session | ### Zustandsübergänge @@ -34,20 +37,26 @@ Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, d ```mermaid flowchart LR START(( )) --> IDLE - IDLE -->|analyzeDisc()| ANALYZING[ANALYZING] - ANALYZING -->|MakeMKV fertig| META[METADATA\nSELECTION] + IDLE -->|analyzeDisc()| META[METADATA\nSELECTION] META -->|selectMetadata()| RTS[READY_TO\nSTART] - RTS -->|startJob()| RIP[RIPPING] + RTS -->|Auto-Start/Queue| RIP[RIPPING] + RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK] RIP -->|MKV erstellt| MIC[MEDIAINFO\nCHECK] + MIC -->|Playlist offen| WUD[WAITING_FOR\nUSER_DECISION] + WUD -->|selectMetadata(selectedPlaylist)| MIC MIC -->|Tracks analysiert| RTE[READY_TO\nENCODE] - RTE -->|confirmEncode()| ENC[ENCODING] - ENC -->|HandBrake fertig| FIN([FINISHED]) + RTE -->|confirmEncodeReview() + startPreparedJob()| ENC[ENCODING] + ENC -->|HandBrake + Post-Skripte fertig| FIN([FINISHED]) + ENC -->|Abbruch| CAN([CANCELLED]) ENC -->|Fehler| ERR([ERROR]) RIP -->|Fehler| ERR - ERR -->|retryJob() / cancel| IDLE + RIP -->|Abbruch| CAN + ERR -->|retry() / cancel()| IDLE + CAN -->|retry() / analyzeDisc()| IDLE FIN -->|cancel / neue Disc| IDLE style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 + style CAN fill:#fff3e0,stroke:#fb8c00,color:#e65100 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0 @@ -71,12 +80,12 @@ flowchart LR ### Polling -Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 5000ms) und emittiert Events: +Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 4000ms) und emittiert Events: ```js // Ereignisse -emit('disc-detected', { device: '/dev/sr0' }) -emit('disc-removed', { device: '/dev/sr0' }) +emit('discInserted', { path: '/dev/sr0' }) +emit('discRemoved', { path: '/dev/sr0' }) ``` --- @@ -120,8 +129,9 @@ WebSocket-Server für Echtzeit-Client-Kommunikation. ### API ```js -broadcast({ type: 'PIPELINE_STATE_CHANGE', data: { state, jobId } }); -broadcast({ type: 'PROGRESS_UPDATE', data: { progress, eta } }); +broadcast('PIPELINE_STATE_CHANGED', { state, activeJobId }); +broadcast('PIPELINE_PROGRESS', { state, progress, eta, statusText }); +broadcast('PIPELINE_QUEUE_CHANGED', queueSnapshot); ``` --- @@ -168,13 +178,12 @@ Verwaltet alle Anwendungseinstellungen. | Kategorie | Einstellungen | |-----------|--------------| -| `paths` | `raw_dir`, `movie_dir`, `log_dir` | -| `tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command` | -| `encoding` | `handbrake_preset`, `handbrake_extra_args`, `output_extension`, `filename_template` | -| `drive` | `drive_mode`, `drive_device`, `disc_poll_interval_ms` | -| `makemkv` | `makemkv_min_length_minutes`, `makemkv_backup_mode` | -| `omdb` | `omdb_api_key`, `omdb_default_type` | -| `notifications` | `pushover_user_key`, `pushover_api_token` | +| `Pfade` | `raw_dir`, `movie_dir`, `log_dir` | +| `Laufwerk` | `drive_mode`, `drive_device`, `disc_poll_interval_ms`, `makemkv_source_index` | +| `Monitoring` | `hardware_monitoring_enabled`, `hardware_monitoring_interval_ms` | +| `Tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command`, `pipeline_max_parallel_jobs` | +| `Metadaten` | `omdb_api_key`, `omdb_default_type` | +| `Benachrichtigungen` | `pushover_user_key`, `pushover_api_token` | --- diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index f26b96c..de20935 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -11,18 +11,18 @@ Das Frontend ist mit **React 18** und **PrimeReact** gebaut und kommuniziert üb Die Hauptseite von Ripster – zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen. **Funktionen:** -- Anzeige des aktuellen Pipeline-Zustands (IDLE, ANALYZING, RIPPING, ENCODING, ...) +- Anzeige des aktuellen Pipeline-Zustands (IDLE, DISC_DETECTED, METADATA_SELECTION, RIPPING, MEDIAINFO_CHECK, READY_TO_ENCODE, ENCODING, ...) - Live-Fortschrittsbalken mit ETA - Trigger für Metadaten-Dialog - Playlist-Entscheidungs-UI (bei Blu-ray Obfuskierung) - Encode-Review mit Track-Auswahl -- Job-Steuerung (Start, Abbruch, Retry) +- Job-Steuerung (Start, Abbruch, Retry, Queue-Interaktion) **Zugehörige Komponenten:** - `PipelineStatusCard` – Status-Widget - `MetadataSelectionDialog` – OMDb-Suche und Playlist-Auswahl - `MediaInfoReviewPanel` – Track-Auswahl vor dem Encoding -- `DiscDetectedDialog` – Benachrichtigung bei Disc-Erkennung +- Queue- und Job-Karten-UI direkt in `DashboardPage` ### SettingsPage.jsx @@ -34,9 +34,9 @@ Konfigurationsoberfläche für alle Ripster-Einstellungen. - PushOver-Verbindungstest - Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen -### HistoryPage.jsx +### DatabasePage.jsx (`/history`) -Job-Historie mit vollständigem Audit-Trail. +Job-Historie und Datenbankansicht mit vollständigem Audit-Trail. **Funktionen:** - Sortier- und filterbares Job-Verzeichnis @@ -88,7 +88,7 @@ Track-Auswahl-Panel vor dem Encoding. │ ☑ Track 1: Deutsch │ │ ☐ Track 2: English │ ├─────────────────────────────────────┤ -│ [Encodierung starten] │ +│ [Encoding starten] │ └─────────────────────────────────────┘ ``` @@ -123,10 +123,10 @@ Vollständiger Job-Detail-Dialog mit Logs-Viewer. Zentraler Custom-Hook für die WebSocket-Verbindung. ```js -const { status, lastMessage } = useWebSocket({ +useWebSocket({ onMessage: (msg) => { - if (msg.type === 'PIPELINE_STATE_CHANGE') { - setPipelineState(msg.data); + if (msg.type === 'PIPELINE_STATE_CHANGED') { + setPipelineState(msg.payload); } } }); @@ -134,9 +134,8 @@ const { status, lastMessage } = useWebSocket({ **Features:** - Automatische Verbindung zu `/ws` -- Reconnect mit exponential backoff +- Reconnect mit festem Intervall (`1500ms`) - Message-Parsing (JSON) -- Status-Tracking (connecting, connected, disconnected) --- @@ -148,8 +147,11 @@ Zentraler HTTP-Client für alle Backend-Anfragen. // Beispiel-Aufrufe const state = await api.getPipelineState(); const results = await api.searchOmdb('Inception'); -await api.selectMetadata(jobId, omdbData, playlist); -await api.confirmEncode(jobId, { audioTracks: [0, 1], subtitleTracks: [0] }); +await api.selectMetadata({ jobId, title, year, imdbId, selectedPlaylist }); +await api.confirmEncodeReview(jobId, { + selectedEncodeTitleId: 1, + selectedTrackSelection: { 1: { audioTrackIds: [1], subtitleTrackIds: [3] } } +}); ``` **Features:** diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 739f459..7b06375 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -82,7 +82,7 @@ App.jsx (React Router) ├── Pages │ ├── DashboardPage.jsx ← Haupt-Interface │ ├── SettingsPage.jsx -│ └── HistoryPage.jsx +│ └── DatabasePage.jsx ← Historie/DB-Ansicht ├── Components │ ├── PipelineStatusCard.jsx │ ├── MetadataSelectionDialog.jsx diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index d8829bb..3c000a7 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -34,10 +34,10 @@ Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON ü ```json { - "type": "PIPELINE_STATE_CHANGE", - "data": { + "type": "PIPELINE_STATE_CHANGED", + "payload": { "state": "ENCODING", - "jobId": 42, + "activeJobId": 42, "progress": 73.5, "eta": "00:12:34" } @@ -48,16 +48,17 @@ Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON ü | Typ | Beschreibung | |----|-------------| -| `PIPELINE_STATE_CHANGE` | Pipeline-Zustand hat gewechselt | -| `PROGRESS_UPDATE` | Fortschritt (% und ETA) | +| `PIPELINE_STATE_CHANGED` | Pipeline-Zustand hat gewechselt | +| `PIPELINE_PROGRESS` | Fortschritt (% und ETA) | +| `PIPELINE_QUEUE_CHANGED` | Queue-Status geändert | | `DISC_DETECTED` | Disc wurde erkannt | | `DISC_REMOVED` | Disc wurde entfernt | -| `ERROR` | Fehler aufgetreten | -| `JOB_COMPLETE` | Job abgeschlossen | +| `PIPELINE_ERROR` | Pipeline-Fehler aufgetreten | +| `DISK_DETECTION_ERROR` | Laufwerkserkennung-Fehler | ### Reconnect-Logik -Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit exponential backoff bei Verbindungsabbrüchen. +Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit festem Intervall von 1500ms bei Verbindungsabbrüchen. --- diff --git a/docs/configuration/settings-reference.md b/docs/configuration/settings-reference.md index 54b446d..318ac23 100644 --- a/docs/configuration/settings-reference.md +++ b/docs/configuration/settings-reference.md @@ -82,7 +82,7 @@ Häufig verwendete Presets: |---------|-----|---------|---------|-------------| | `drive_mode` | select | `auto` | `auto`, `explicit` | Laufwerk-Erkennungsmodus | | `drive_device` | string | `/dev/sr0` | — | Geräte-Pfad (nur bei `explicit`) | -| `disc_poll_interval_ms` | number | `5000` | 1000–60000 | Polling-Intervall in Millisekunden | +| `disc_poll_interval_ms` | number | `4000` | 1000–60000 | Polling-Intervall in Millisekunden | **`drive_mode` Optionen:** diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index c57da04..4a7fe4d 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -84,7 +84,7 @@ Das Template unterstützt folgende Platzhalter: |------------|---------|-------------| | `drive_mode` | `auto` | `auto` (automatisch erkennen) oder `explicit` (festes Gerät) | | `drive_device` | `/dev/sr0` | Geräte-Pfad (nur bei `explicit`) | -| `disc_poll_interval_ms` | `5000` | Polling-Intervall in Millisekunden | +| `disc_poll_interval_ms` | `4000` | Polling-Intervall in Millisekunden | --- diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index ccaaca9..d7ead5a 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -4,7 +4,7 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f --- -## Übersicht: Pipeline-Zustände +## Übersicht: Pipeline-Ablauf
@@ -20,7 +20,7 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
2
METADATA_SELECTION
-
Scan & Metadaten
+
OMDb & Dialog
@@ -53,9 +53,9 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
HandBrake
-
8
-
POST_ENCODE_SCRIPTS
-
Skripte
(optional)
+
8*
+
POST-ENCODE
+
Skripte
(innerhalb ENCODING)
@@ -77,31 +77,29 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f IDLE -->|Disc erkannt| DD[DISC_DETECTED] DD -->|Analyse starten| META[METADATA\nSELECTION] - META -->|1 Kandidat| RTS[READY_TO\nSTART] - META -->|mehrere Kandidaten| WUD[WAITING_FOR\nUSER_DECISION] - WUD -->|Playlist bestätigt| RTS + META -->|Metadaten übernommen| RTS[READY_TO\nSTART] + META -->|vorhandenes RAW +\nPlaylist offen| WUD[WAITING_FOR\nUSER_DECISION] + RTS -->|Auto-Start| RIP[RIPPING] + RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK] - RTS -->|Raw vorhanden| MIC[MEDIAINFO\nCHECK] - RTS -->|Ripping starten| RIP[RIPPING] RIP -->|MKV fertig| MIC RIP -->|Fehler| ERR + MIC -->|Playlist offen (Backup)| WUD + WUD -->|Playlist bestätigt| MIC + WUD -->|Playlist bestätigt,\nnoch kein RAW| RTS + MIC --> RTE[READY_TO\nENCODE] - RTE -->|bestätigt| ENC[ENCODING] + RTE -->|Encoding starten| ENC[ENCODING] - ENC -->|mit Skripten| PES[POST_ENCODE\nSCRIPTS] - ENC -->|ohne Skripte| FIN([FINISHED]) + ENC -->|inkl. Post-Skripte| FIN([FINISHED]) ENC -->|Fehler| ERR - PES -->|Erfolg| FIN - PES -->|Fehler| ERR - ERR([ERROR]) -->|Retry / Cancel| IDLE style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 style WUD fill:#fff8e1,stroke:#ffa726,color:#e65100 - style PES fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a ``` @@ -122,13 +120,14 @@ cd ripster ## Schritt 2 – Disc einlegen → `DISC_DETECTED` -Lege eine DVD oder Blu-ray ein. Der `diskDetectionService` pollt das Laufwerk alle `disc_poll_interval_ms` Millisekunden (Standard: 5 Sekunden). +Lege eine DVD oder Blu-ray ein. Der `diskDetectionService` pollt das Laufwerk alle `disc_poll_interval_ms` Millisekunden (Standard: 4 Sekunden). **Was passiert im Code:** -- `diskDetectionService` emittiert `disc:inserted` mit Geräteinformationen +- `diskDetectionService` emittiert `discInserted` mit Geräteinformationen - `pipelineService.onDiscInserted()` wird aufgerufen -- Dashboard zeigt Badge **"Neue Disc erkannt"** +- Dashboard-Status-Badge zeigt **"Medium erkannt"** +- Status-Text zeigt **"Neue Disk erkannt"** - Der **"Analyse starten"**-Button wird aktiv !!! tip "Manuelle Auslösung" @@ -141,7 +140,7 @@ Lege eine DVD oder Blu-ray ein. Der `diskDetectionService` pollt das Laufwerk al ## Schritt 3 – Analyse starten → `METADATA_SELECTION` -Klicke auf **"Analyse starten"** oder warte auf automatischen Start. +Klicke auf **"Analyse starten"**. **Was passiert im Code:** @@ -183,21 +182,18 @@ Falls kein passendes Ergebnis gefunden wird: **Was passiert nach Bestätigung:** -Ripster ruft `pipelineService.selectMetadata()` auf und führt sofort eine **Playlist-Analyse** durch: +Ripster ruft `pipelineService.selectMetadata()` auf und startet den nächsten Schritt automatisch: -- MakeMKV wird im Info-Modus gestartet -- Alle Titel und deren Segment-Reihenfolgen werden analysiert -- Das Ergebnis entscheidet über den nächsten Zustand (→ Schritt 5) +- Job wird auf `READY_TO_START` gesetzt (kurzer Übergangszustand) +- Falls bereits RAW vorhanden: direkter Sprung zu `MEDIAINFO_CHECK` +- Falls kein RAW vorhanden: automatischer Start von `RIPPING` +- Wenn bereits andere Jobs laufen, landet der Start stattdessen in der Queue --- -## Schritt 5 – Playlist-Situation (zwei Wege) +## Schritt 5 – Optional: Playlist-Auswahl → `WAITING_FOR_USER_DECISION` -### 5a) Keine Obfuskierung → `READY_TO_START` - -Der Dialog schließt sich automatisch. Die empfohlene Playlist wird still übernommen. Weiter zu **Schritt 6**. - -### 5b) Obfuskierung erkannt → `WAITING_FOR_USER_DECISION` +Dieser Zustand erscheint nur bei mehrdeutigen Blu-ray-Playlists (typisch nach RAW-Analyse im Backup-Modus). Der **Playlist-Auswahl-Dialog** erscheint **zusätzlich** (nach dem Metadaten-Dialog): @@ -219,26 +215,29 @@ Der **Playlist-Auswahl-Dialog** erscheint **zusätzlich** (nach dem Metadaten-Di └───────────┴──────────┴────────┴──────────────────────────────┘ 847 Playlists insgesamt · 3 relevante Kandidaten (≥ 15 min) Empfehlung: 00800 (vorausgewählt) - [Playlist bestätigen] + [Playlist übernehmen] ``` -- Die empfohlene Playlist ist **vorausgewählt** (Radio-Button) +- Die empfohlene Playlist ist **vorausgewählt** (Checkbox) - Score und Bewertungslabel helfen bei der Entscheidung -- Nach Bestätigung: Pipeline wechselt zu `READY_TO_START` +- Nach **"Playlist übernehmen"** setzt Ripster automatisch fort: + - mit vorhandenem RAW in `MEDIAINFO_CHECK` + - ohne RAW über `READY_TO_START` weiter Richtung `RIPPING` !!! info "Scoring-Details" Wie die Scores berechnet werden, erklärt die [Playlist-Analyse](../pipeline/playlist-analysis.md)-Seite. --- -## Schritt 6 – Ripping starten → `RIPPING` +## Schritt 6 – Ripping → `RIPPING` **Vorher prüft Ripster:** Existiert bereits eine Raw-Datei für diesen Job? - **Ja, Raw-Datei vorhanden** → Direkt zu Schritt 7 (Track-Review), kein erneutes Ripping - **Nein** → MakeMKV-Ripping startet -Klicke auf **"Starten"** im Dashboard. +Im Standardfall startet Ripster diesen Schritt automatisch nach der Metadaten-Auswahl. +Der Button **"Job starten"** ist hauptsächlich für Sonderfälle sichtbar (z. B. Fallback/Queue). **Was MakeMKV ausführt (MKV-Modus):** @@ -269,7 +268,7 @@ PRGT:5011,0,"Sichern..." → Aktueller Task-Name ## Schritt 7 – Track-Review → `READY_TO_ENCODE` -Nach dem Ripping (oder direkt bei vorhandener Raw-Datei) startet der **HandBrake-Scan**: +Nach dem Ripping, nach Playlist-Übernahme oder direkt bei vorhandenem RAW startet der **HandBrake-Scan**: ```bash HandBrakeCLI --scan -i -t 0 @@ -297,7 +296,7 @@ Danach öffnet sich das **Encode-Review-Panel** (`READY_TO_ENCODE`): │ ☑ │ Track 1: Deutsch │ Einbr.☐ │Forc.☐│Default☑ │ │ ☐ │ Track 2: English │ Einbr.☐ │Forc.☐│Default☐ │ ├──────┴─────────────────────────────┴────────┴──────┴──────────┤ -│ [Encode bestätigen] │ +│ [Encoding starten] │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -324,7 +323,8 @@ Danach öffnet sich das **Encode-Review-Panel** (`READY_TO_ENCODE`): Die Tracks mit `☑` wurden nach der Regel aus den Einstellungen automatisch vorausgewählt (`selectedByRule: true`). Die Auswahl kann frei geändert werden. -Klicke **"Encode bestätigen"** um fortzufahren. +Klicke **"Encoding starten"** (bzw. im Pre-Rip-Modus **"Backup + Encoding starten"**), um fortzufahren. +Falls die Auswahl noch nicht bestätigt wurde, übernimmt das Frontend die Bestätigung automatisch beim Start. --- @@ -401,12 +401,11 @@ Das Dashboard zeigt: |--|--------|---------------|----------------| | 1 | `IDLE` | Disc einlegen | Disc-Polling erkennt Disc | | 2 | `DISC_DETECTED` | "Analyse starten" klicken | Job anlegen, OMDb vorsuchen | -| 3 | `METADATA_SELECTION` | Film im Dialog auswählen | Playlist-Analyse durchführen | -| 4a | `READY_TO_START` | — | Empfehlung automatisch übernommen | -| 4b | `WAITING_FOR_USER_DECISION` | Playlist manuell wählen | Auf Bestätigung warten | -| 5 | `READY_TO_START` | "Starten" klicken | MakeMKV-Ripping starten | -| 6 | `RIPPING` | Warten | MakeMKV rippt, Fortschritt streamen | -| 7 | `MEDIAINFO_CHECK` | Warten | HandBrake-Scan, Encode-Plan bauen | -| 8 | `READY_TO_ENCODE` | Tracks prüfen + bestätigen | Auswahl in Plan übernehmen | -| 9 | `ENCODING` | Warten | HandBrake encodiert, Fortschritt streamen | +| 3 | `METADATA_SELECTION` | Film im Dialog auswählen | Start automatisch einplanen/auslösen | +| 4 | `READY_TO_START` | meist keine | Übergangszustand vor Auto-Start | +| 5 | `RIPPING` | Warten | MakeMKV rippt, Fortschritt streamen | +| 6 | `MEDIAINFO_CHECK` | Warten | HandBrake-Scan, Encode-Plan bauen | +| 7 | `WAITING_FOR_USER_DECISION` (optional) | Playlist manuell wählen | Auf Bestätigung warten | +| 8 | `READY_TO_ENCODE` | Tracks prüfen + "Encoding starten" | Auswahl übernehmen, Start auslösen | +| 9 | `ENCODING` | Warten | HandBrake encodiert, inkl. Post-Skripte | | 10 | `FINISHED` | — | Datei fertig, Benachrichtigung senden | diff --git a/docs/index.md b/docs/index.md index 7a5b7fd..fc42eb6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -118,25 +118,24 @@ open http://localhost:5173 flowchart LR IDLE --> DD[DISC_DETECTED] DD --> META[METADATA\nSELECTION] - META -->|1 Kandidat| RTS[READY_TO\nSTART] - META -->|Obfuskierung| WUD[WAITING_FOR\nUSER_DECISION] - WUD --> RTS - RTS --> RIP[RIPPING] - RTS -->|Raw vorhanden| MIC + META --> RTS[READY_TO\nSTART] + RTS -->|Auto-Start| RIP[RIPPING] + RTS -->|Auto-Start mit RAW| MIC RIP --> MIC[MEDIAINFO\nCHECK] + MIC -->|Playlist offen (Backup)| WUD[WAITING_FOR\nUSER_DECISION] + WUD --> MIC MIC --> RTE[READY_TO\nENCODE] RTE --> ENC[ENCODING] - ENC --> PES[POST_ENCODE\nSCRIPTS] - ENC -->|keine Skripte| FIN([FINISHED]) - PES --> FIN + ENC -->|inkl. Post-Skripte| FIN([FINISHED]) ENC --> ERR([ERROR]) RIP --> ERR style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 style WUD fill:#fff8e1,stroke:#ffa726,color:#e65100 - style PES fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a ```
+ +`READY_TO_START` ist in der Praxis meist ein kurzer Übergangszustand: der Job wird nach Metadaten-Auswahl automatisch gestartet oder in die Queue eingeplant. diff --git a/docs/pipeline/encoding.md b/docs/pipeline/encoding.md index a182ebc..9a8fa8f 100644 --- a/docs/pipeline/encoding.md +++ b/docs/pipeline/encoding.md @@ -243,7 +243,7 @@ Das Review-Panel zeigt: │ [✓] │ Track 1: Deutsch │Einbr.[ ]│Forced[ ]│Default[✓]│ │ [ ] │ Track 2: English │Einbr.[ ]│Forced[ ]│Default[ ]│ ├──────┴──────────────────────────┴────────┴────────┴────────────┤ -│ [Encode bestätigen] │ +│ [Encoding starten] │ └─────────────────────────────────────────────────────────────────┘ ``` @@ -256,7 +256,7 @@ Der Benutzer kann: ## Phase 7: Benutzer-Auswahl anwenden (`applyManualTrackSelectionToPlan`) -Nach "Encode bestätigen" wird die Benutzer-Auswahl auf den Plan angewendet: +Im Frontend wird die Benutzer-Auswahl beim Klick auf **"Encoding starten"** (ggf. automatisch) bestätigt und dann auf den Plan angewendet: ```json Payload: { diff --git a/docs/pipeline/post-encode-scripts.md b/docs/pipeline/post-encode-scripts.md index 348bc7e..77e80b6 100644 --- a/docs/pipeline/post-encode-scripts.md +++ b/docs/pipeline/post-encode-scripts.md @@ -21,7 +21,8 @@ FINISHED ``` !!! warning "Abbruch bei Fehler" - Schlägt ein Skript fehl (Exit-Code ≠ 0), werden alle nachfolgenden Skripte **nicht mehr ausgeführt**. Der Job wechselt in den Status `ERROR`. + Schlägt ein Skript fehl (Exit-Code ≠ 0), werden alle nachfolgenden Skripte **nicht mehr ausgeführt**. + Der Job bleibt im Abschlusszustand `FINISHED`; der Fehler wird in Log/Status-Text und im `postEncodeScripts`-Summary festgehalten. --- @@ -120,26 +121,21 @@ Das Ergebnis der Skript-Ausführung wird im Job-Datensatz gespeichert und in der { "postEncodeScripts": { "configured": 2, + "attempted": 2, "succeeded": 2, "failed": 0, "skipped": 0, "aborted": false, "results": [ { - "scriptId": "script-1", - "name": "Zu Plex verschieben", - "exitCode": 0, - "stdout": "Verschoben: Inception nach /mnt/media/movies", - "stderr": "", - "durationMs": 342 + "scriptId": 1, + "scriptName": "Zu Plex verschieben", + "status": "SUCCESS" }, { - "scriptId": "script-2", - "name": "Webhook auslösen", - "exitCode": 0, - "stdout": "", - "stderr": "", - "durationMs": 128 + "scriptId": 2, + "scriptName": "Webhook auslösen", + "status": "SUCCESS" } ] } @@ -149,6 +145,7 @@ Das Ergebnis der Skript-Ausführung wird im Job-Datensatz gespeichert und in der | Feld | Beschreibung | |------|-------------| | `configured` | Anzahl ausgewählter Skripte | +| `attempted` | Anzahl tatsächlich gestarteter Skripte | | `succeeded` | Erfolgreich ausgeführt (Exit-Code 0) | | `failed` | Fehlgeschlagen | | `skipped` | Nicht ausgeführt (wegen vorherigem Fehler) | diff --git a/docs/pipeline/workflow.md b/docs/pipeline/workflow.md index abcacd1..7899b35 100644 --- a/docs/pipeline/workflow.md +++ b/docs/pipeline/workflow.md @@ -15,32 +15,33 @@ flowchart LR IDLE -->|Disc erkannt| DD[DISC_DETECTED] DD -->|Analyse starten| META[METADATA\nSELECTION] - META -->|1 Kandidat| RTS[READY_TO\nSTART] - META -->|mehrere Kandidaten| WUD[WAITING_FOR\nUSER_DECISION] - WUD -->|Playlist bestätigt| RTS + META -->|Metadaten übernommen| RTS[READY_TO\nSTART] + META -->|vorhandenes RAW +\nPlaylist offen| WUD[WAITING_FOR\nUSER_DECISION] - RTS -->|Raw vorhanden| MIC[MEDIAINFO\nCHECK] - RTS -->|Ripping starten| RIP[RIPPING] + RTS -->|Auto-Start| RIP[RIPPING] + RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK] RIP -->|MKV fertig| MIC RIP -->|Fehler| ERR + RIP -->|Abbruch| CAN([CANCELLED]) + MIC -->|Playlist offen (Backup)| WUD + WUD -->|Playlist bestätigt| MIC + WUD -->|Playlist bestätigt,\nnoch kein RAW| RTS MIC --> RTE[READY_TO\nENCODE] - RTE -->|Tracks + Skripte\nbestätigt| ENC[ENCODING] + RTE -->|Encoding starten\n(bestätigt bei Bedarf automatisch)| ENC[ENCODING] - ENC -->|mit Skripten| PES[POST_ENCODE\nSCRIPTS] - ENC -->|ohne Skripte| FIN([FINISHED]) + ENC -->|inkl. Post-Skripte| FIN([FINISHED]) ENC -->|Fehler| ERR - - PES -->|Erfolg| FIN - PES -->|Fehler| ERR + ENC -->|Abbruch| CAN ERR([ERROR]) -->|Retry / Cancel| IDLE + CAN -->|Retry / Neu-Analyse| IDLE FIN -->|Neue Disc| IDLE style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 + style CAN fill:#fff3e0,stroke:#fb8c00,color:#e65100 style WUD fill:#fff8e1,stroke:#ffa726,color:#e65100 - style PES fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0 style MIC fill:#e3f2fd,stroke:#42a5f5,color:#1565c0 @@ -50,6 +51,28 @@ flowchart LR --- +## UI-Badge-Bezeichnungen + +Die Status-Badges im Dashboard verwenden diese Labels: + +| State | Badge-Label | +|------|-------------| +| `IDLE` | `Bereit` | +| `DISC_DETECTED` | `Medium erkannt` | +| `METADATA_SELECTION` | `Metadatenauswahl` | +| `WAITING_FOR_USER_DECISION` | `Warte auf Auswahl` | +| `READY_TO_START` | `Startbereit` | +| `RIPPING` | `Rippen` | +| `MEDIAINFO_CHECK` | `Mediainfo-Pruefung` | +| `READY_TO_ENCODE` | `Bereit zum Encodieren` | +| `ENCODING` | `Encodieren` | +| `FINISHED` | `Fertig` | +| `CANCELLED` | `Abgebrochen` | +| `ERROR` | `Fehler` | +| Queue (kein eigener State) | `In der Queue` | + +--- + ## Zustandsbeschreibungen ### IDLE @@ -66,7 +89,8 @@ flowchart LR **Disc erkannt, wartet auf Benutzeraktion.** -- Dashboard zeigt **"Neue Disc erkannt"**-Badge +- Dashboard-Badge: **"Medium erkannt"** +- Status-Text: **"Neue Disk erkannt"** - **"Analyse starten"**-Button wird aktiv - Kein Prozess läuft noch @@ -76,20 +100,21 @@ flowchart LR ### METADATA_SELECTION -**Disc-Info-Scan läuft, danach Benutzer-Eingabe.** +**Metadaten-Auswahl läuft.** -1. MakeMKV wird im Info-Modus gestartet und liest alle Titel/Playlists +1. Job wird erstellt (`status = METADATA_SELECTION`) 2. OMDb-Vorsuche mit erkanntem Disc-Label 3. `MetadataSelectionDialog` öffnet sich mit vorgeladenen Ergebnissen 4. Benutzer wählt Filmtitel (oder gibt manuell ein) -5. Nach Bestätigung: **Playlist-Analyse** läuft sofort durch +5. Nach Bestätigung wird der Job automatisch für Start/Queue vorbereitet (`selectMetadata` + `startPreparedJob`) -**Übergang (automatisch nach Playlist-Analyse):** +**Übergang (automatisch nach Metadaten-Bestätigung):** -| Ergebnis der Analyse | Nächster Zustand | +| Ergebnis | Nächster Zustand | |--------------------|-----------------| -| Nur ein Kandidat nach Mindestlänge | `READY_TO_START` | -| Mehrere Kandidaten nach Mindestlänge | `WAITING_FOR_USER_DECISION` | +| Kein verwertbares RAW vorhanden | `READY_TO_START` → automatisch `RIPPING` (oder Queue) | +| Verwertbares RAW vorhanden | `READY_TO_START` → automatisch `MEDIAINFO_CHECK` (oder Queue) | +| Vorhandenes RAW + offene Playlist-Entscheidung | `WAITING_FOR_USER_DECISION` | --- @@ -104,6 +129,7 @@ flowchart LR - Alle Kandidaten mit Score, Laufzeit und Bewertungslabel - Empfohlene Playlist ist vorausgewählt - Benutzer bestätigt mit **"Playlist übernehmen"** +- Tritt häufig nach `MEDIAINFO_CHECK` auf (Backup-Analyse), seltener direkt nach `METADATA_SELECTION` bei vorhandenem RAW **Darstellung im Dashboard:** @@ -121,7 +147,10 @@ flowchart LR [Playlist übernehmen] ``` -**Übergang:** `selectMetadata(jobId, { selectedPlaylist })` → `READY_TO_START` +**Übergang:** `selectMetadata(jobId, { selectedPlaylist })` setzt die Pipeline automatisch fort: + +- mit vorhandenem RAW nach `MEDIAINFO_CHECK` +- ohne RAW über `READY_TO_START` weiter Richtung `RIPPING` Mehr Details: [Playlist-Analyse](playlist-analysis.md) @@ -129,15 +158,17 @@ Mehr Details: [Playlist-Analyse](playlist-analysis.md) ### READY_TO_START -**Metadaten und Playlist bestätigt, bereit zum Starten.** +**Übergangs-/Fallback-Zustand vor dem eigentlichen Start.** -- Job-Datensatz in Datenbank aktualisiert -- **"Starten"**-Button im Dashboard aktiv +- Wird nach Metadaten-Bestätigung kurz gesetzt +- `startPreparedJob()` wird danach automatisch ausgeführt +- Wenn Parallel-Limit erreicht ist, wird der Start stattdessen in die Queue eingereiht +- **"Job starten"** ist primär für Sonderfälle/Fallback sichtbar -**Sonderfall – Raw-Datei bereits vorhanden:** -Wenn für diesen Job bereits eine geri rippte Raw-Datei im `raw_dir` existiert (Pfad-Match über Metadaten-Basis), überspringt Ripster den Ripping-Schritt und springt direkt zum HandBrake-Scan. +**Sonderfall – RAW-Datei bereits vorhanden:** +Wenn für diesen Job bereits ein verwertbares RAW unter `raw_dir` existiert, wird Ripping übersprungen und direkt `MEDIAINFO_CHECK` gestartet. -**Übergang:** `startJob(jobId)` → `RIPPING` oder direkt `MEDIAINFO_CHECK` +**Übergang:** `startPreparedJob(jobId)` → `RIPPING` oder direkt `MEDIAINFO_CHECK` --- @@ -176,20 +207,24 @@ PRGT:5011,0,"..." → Aktueller Task-Name **HandBrake-Scan und Encode-Plan-Erstellung.** -Dieser Zustand umfasst zwei Phasen: +Dieser Zustand umfasst je nach Quelle mehrere Phasen: -1. **HandBrake-Scan** (`HandBrakeCLI --scan`) auf Disc oder Raw-Datei -2. **Encode-Plan-Erstellung** mit automatischer Track-Vorauswahl +1. Optional: Playlist-Auflösung bei Blu-ray-Backup (inkl. MakeMKV/HandBrake-Zuordnung) +2. **HandBrake-Scan** (`HandBrakeCLI --scan`) auf RAW-Input +3. **Encode-Plan-Erstellung** mit automatischer Track-Vorauswahl Kein Benutzereingriff – läuft automatisch durch. -**Übergang:** → `READY_TO_ENCODE` +**Übergänge:** + +- Eindeutige Quelle/Titelwahl möglich → `READY_TO_ENCODE` +- Mehrdeutige Playlist erkannt → `WAITING_FOR_USER_DECISION` --- ### READY_TO_ENCODE -**Encode-Plan bereit, wartet auf Benutzer-Bestätigung.** +**Encode-Plan bereit.** Das `MediaInfoReviewPanel` zeigt: @@ -198,7 +233,10 @@ Das `MediaInfoReviewPanel` zeigt: - **Untertitel-Tracks** mit Flags (Einbrennen, Forced, Default) - **Post-Encode-Skripte** – Auswahl und Reihenfolge der auszuführenden Skripte -**Übergang:** `confirmEncodeReview(jobId, { tracks, scripts })` → `ENCODING` +Im Frontend startet **"Encoding starten"** (bzw. **"Backup + Encoding starten"** im Pre-Rip-Modus) den nächsten Schritt. +Falls die Review noch nicht bestätigt wurde, wird `confirmEncodeReview(...)` automatisch vor dem Start aufgerufen. + +**Übergang:** `startPreparedJob(jobId)` → `ENCODING` (oder im Pre-Rip-Fall zuerst `RIPPING`) --- @@ -221,34 +259,10 @@ HandBrakeCLI \ Encoding: task 1 of 1, 73.50 % (45.23 fps, avg 44.12 fps, ETA 00h12m34s) ``` ---- +Post-Encode-Skripte werden innerhalb dieses Zustands sequenziell ausgeführt (kein separater Pipeline-State). -### POST_ENCODE_SCRIPTS - -**Post-Encode-Skripte werden ausgeführt.** - -!!! info "Neu seit „Skript Integration + UI Anpassungen"" - Post-Encode-Skripte ermöglichen es, nach erfolgreichem Encoding automatisch Aktionen auszuführen. - -- Skripte werden **sequenziell** in der konfigurierten Reihenfolge ausgeführt -- Bei Fehler eines Skripts: restliche Skripte werden **abgebrochen** -- Ergebnis-Zusammenfassung wird im Job-Datensatz gespeichert: - -```json -{ - "configured": 2, - "succeeded": 2, - "failed": 0, - "skipped": 0, - "aborted": false -} -``` - -Dieser Zustand wird nur erreicht, wenn im Encode-Review mindestens ein Skript ausgewählt wurde. - -**Übergang:** → `FINISHED` (alle Skripte erfolgreich) oder `ERROR` (Skript-Fehler) - -Details: [Post-Encode-Skripte](post-encode-scripts.md) +!!! note "Skriptfehler" + Skriptfehler führen zum Abbruch der Skriptkette, der Job bleibt jedoch im Abschlusszustand `FINISHED` mit entsprechendem Hinweis im Status-Text/Log. --- @@ -259,7 +273,17 @@ Details: [Post-Encode-Skripte](post-encode-scripts.md) - Ausgabedatei liegt im konfigurierten `movie_dir` - Job-Status in Datenbank: `FINISHED` - PushOver-Benachrichtigung (falls konfiguriert) -- WebSocket-Event: `JOB_COMPLETE` +- WebSocket-Event: `PIPELINE_STATE_CHANGED` (State `FINISHED`) + +--- + +### CANCELLED + +**Job wurde vom Benutzer abgebrochen.** + +- Entsteht bei aktivem Abbruch (`/api/pipeline/cancel`) während laufender Phase +- Job-Status in Datenbank: `CANCELLED` +- Im Dashboard stehen danach u. a. `Retry Rippen`, `Review neu starten` oder `Encode neu starten` (kontextabhängig) zur Verfügung --- @@ -270,7 +294,7 @@ Details: [Post-Encode-Skripte](post-encode-scripts.md) - Fehlerdetails im Job-Datensatz gespeichert - Fehler-Logs in History abrufbar - **Retry**: Neustart vom Fehlerzustand -- **Abbrechen**: Pipeline zurück zu IDLE +- **Neu analysieren**: Disc erneut als neuer Job starten --- @@ -283,7 +307,7 @@ POST /api/pipeline/cancel ``` - SIGINT → graceful exit (Timeout: 10 s) → SIGKILL -- Pipeline zurück zu IDLE +- Laufender Job landet in `CANCELLED` (oder Queue-Eintrag wird entfernt, falls noch nicht gestartet) ### Job wiederholen @@ -291,8 +315,8 @@ POST /api/pipeline/cancel POST /api/pipeline/retry/:jobId ``` -- Setzt Job zurück auf `READY_TO_START` -- Metadaten und Playlist-Auswahl bleiben erhalten +- Startet den Job neu in `RIPPING` (oder reiht den Retry in die Queue ein) +- Metadaten bleiben erhalten; Encode-/Scan-Daten werden neu erzeugt ### Re-Encode