This commit is contained in:
2026-03-05 11:21:47 +00:00
parent e3d890c071
commit c3d1df42b0
13 changed files with 461 additions and 447 deletions

View File

@@ -1,29 +1,34 @@
# Pipeline API # Pipeline API
Alle Endpunkte zur Steuerung des Ripping-Workflows. Alle Endpunkte zur Steuerung des Ripster-Workflows.
--- ---
## GET /api/pipeline/state ## GET /api/pipeline/state
Gibt den aktuellen Pipeline-Zustand zurück. Liefert den aktuellen Pipeline-Snapshot.
**Response:** **Response:**
```json ```json
{ {
"state": "ENCODING", "pipeline": {
"jobId": 42, "state": "READY_TO_ENCODE",
"job": { "activeJobId": 42,
"id": 42, "progress": 0,
"title": "Inception", "eta": null,
"status": "ENCODING", "statusText": "Mediainfo geladen - bitte bestätigen",
"imdb_id": "tt1375666", "context": {
"omdb_year": "2010" "jobId": 42
}, },
"progress": 73.5, "queue": {
"eta": "00:12:34", "maxParallelJobs": 1,
"updatedAt": "2024-01-15T14:30:00.000Z" "runningCount": 0,
"queuedCount": 0,
"runningJobs": [],
"queuedJobs": []
}
}
} }
``` ```
@@ -31,84 +36,73 @@ Gibt den aktuellen Pipeline-Zustand zurück.
| Wert | Beschreibung | | Wert | Beschreibung |
|------|-------------| |------|-------------|
| `IDLE` | Wartet auf Disc | | `IDLE` | Wartet auf Medium |
| `DISC_DETECTED` | Disc erkannt, wartet auf Benutzer | | `DISC_DETECTED` | Medium erkannt, wartet auf Analyse-Start |
| `METADATA_SELECTION` | Disc-Scan läuft / Metadaten-Dialog | | `METADATA_SELECTION` | Metadaten-Dialog aktiv |
| `WAITING_FOR_USER_DECISION` | Mehrere Playlist-Kandidaten manuelle Auswahl | | `WAITING_FOR_USER_DECISION` | Manuelle Playlist-Auswahl erforderlich |
| `READY_TO_START` | Bereit zum Starten | | `READY_TO_START` | Übergang/Fallback vor Start |
| `RIPPING` | MakeMKV-Ripping läuft | | `RIPPING` | MakeMKV läuft |
| `MEDIAINFO_CHECK` | HandBrake-Scan & Encode-Plan-Erstellung | | `MEDIAINFO_CHECK` | HandBrake-Scan + Plan-Erstellung |
| `READY_TO_ENCODE` | Wartet auf Encode-Bestätigung | | `READY_TO_ENCODE` | Review bereit |
| `ENCODING` | HandBrake encodiert | | `ENCODING` | HandBrake-Encoding läuft (inkl. Post-Skripte) |
| `POST_ENCODE_SCRIPTS` | Post-Encode-Skripte laufen |
| `FINISHED` | Abgeschlossen | | `FINISHED` | Abgeschlossen |
| `CANCELLED` | Vom Benutzer abgebrochen |
| `ERROR` | Fehler | | `ERROR` | Fehler |
**Kontext-Felder (state-abhängig):**
Beim Zustand `WAITING_FOR_USER_DECISION` enthält die Response zusätzlich:
```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"]
}
}
```
--- ---
## POST /api/pipeline/analyze ## POST /api/pipeline/analyze
Startet eine manuelle Disc-Analyse. Startet die Analyse für die aktuell erkannte Disc.
**Request:** Kein Body **Request:** kein Body
**Response:** **Response:**
```json ```json
{ "ok": true, "message": "Analyse gestartet" } {
"result": {
"jobId": 42,
"detectedTitle": "INCEPTION",
"omdbCandidates": []
}
}
``` ```
**Fehlerfälle:**
- `409` Pipeline bereits aktiv
--- ---
## POST /api/pipeline/rescan-disc ## 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=<query>
Sucht in der OMDb-API nach einem Filmtitel. Sucht OMDb-Titel.
**Query-Parameter:**
| Parameter | Typ | Beschreibung |
|----------|-----|-------------|
| `q` | string | Suchbegriff |
| `type` | string | `movie` oder `series` (optional) |
**Beispiel:** `GET /api/pipeline/omdb/search?q=Inception&type=movie`
**Response:** **Response:**
```json ```json
{ {
"results": [ "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 ## POST /api/pipeline/select-metadata
Bestätigt Metadaten und optionale Playlist-Auswahl. Setzt Metadaten (und optional Playlist-Entscheidung).
**Request:** **Request:**
```json ```json
{ {
"jobId": 42, "jobId": 42,
"omdb": {
"imdbId": "tt1375666",
"title": "Inception", "title": "Inception",
"year": "2010", "year": 2010,
"type": "movie", "imdbId": "tt1375666",
"poster": "https://..." "poster": "https://...",
}, "fromOmdb": true,
"selectedPlaylist": "00800" "selectedPlaylist": "00800"
} }
``` ```
!!! info "Playlist-Felder" **Response:** `{ "job": { ... } }`
`selectedPlaylist` ist optional. Wird es beim ersten Aufruf weggelassen (kein Obfuskierungsverdacht), wird die Empfehlung automatisch übernommen.
Beim zweiten Aufruf aus dem `WAITING_FOR_USER_DECISION`-Dialog reicht es, nur `jobId` + `selectedPlaylist` zu schicken `omdb` kann dann weggelassen werden. !!! note "Startlogik"
Nach Metadaten-Bestätigung wird der nächste Schritt automatisch ausgelöst (`startPreparedJob`).
**Response:** `{ "ok": true }` Der Job startet direkt oder wird in die Queue eingereiht.
--- ---
## POST /api/pipeline/start/:jobId ## 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. Mögliche `stage`-Werte sind u. a. `RIPPING`, `MEDIAINFO_CHECK`, `ENCODING`.
**Fehlerfälle:**
- `404` Job nicht gefunden
- `409` Job nicht im Status `READY_TO_START`
--- ---
## POST /api/pipeline/confirm-encode/:jobId ## POST /api/pipeline/confirm-encode/:jobId
Bestätigt die Encode-Konfiguration mit Track-Auswahl und Post-Encode-Skripten. Bestätigt Review-Auswahl (Titel/Tracks/Post-Skripte).
**URL-Parameter:** `jobId`
**Request:** **Request:**
@@ -174,72 +166,102 @@ Bestätigt die Encode-Konfiguration mit Track-Auswahl und Post-Encode-Skripten.
"selectedTrackSelection": { "selectedTrackSelection": {
"1": { "1": {
"audioTrackIds": [1, 2], "audioTrackIds": [1, 2],
"subtitleTrackIds": [1] "subtitleTrackIds": [3]
} }
}, },
"selectedPostEncodeScriptIds": ["script-abc123", "script-def456"] "selectedPostEncodeScriptIds": [2, 7],
"skipPipelineStateUpdate": false
} }
``` ```
| Feld | Typ | Beschreibung | **Response:** `{ "job": { ... } }`
|------|-----|-------------|
| `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" }`
--- ---
## POST /api/pipeline/cancel ## 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 ## 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" }` **Response:** `{ "result": { ... } }`
**Fehlerfälle:**
- `404` Job nicht gefunden
- `409` Job nicht im Status `ERROR`
--- ---
## POST /api/pipeline/resume-ready/:jobId ## 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 ## 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:** **Request:**
```json ```json
{ {
"selectedEncodeTitleId": 1, "orderedJobIds": [42, 43, 41]
"selectedTrackSelection": {
"1": { "audioTrackIds": [1, 2], "subtitleTrackIds": [1] }
},
"selectedPostEncodeScriptIds": ["script-abc123"]
} }
``` ```
Gleiche Struktur wie `confirm-encode` ermöglicht andere Track-Auswahl und Skripte als beim ersten Encoding. **Response:** `{ "queue": { ... } }`
**Response:** `{ "ok": true, "message": "Re-Encoding gestartet" }`

View File

@@ -1,6 +1,6 @@
# WebSocket Events # 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) => { ws.onmessage = (event) => {
const message = JSON.parse(event.data); 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 ```json
{ {
"type": "EVENT_TYPE", "type": "EVENT_TYPE",
"data": { ... } "payload": { },
"timestamp": "2026-03-05T10:00:00.000Z"
} }
``` ```
@@ -32,148 +33,129 @@ Alle Nachrichten folgen diesem Schema:
## Event-Typen ## Event-Typen
### PIPELINE_STATE_CHANGE ### WS_CONNECTED
Wird gesendet, wenn der Pipeline-Zustand wechselt. Wird direkt nach Verbindungsaufbau gesendet.
```json ```json
{ {
"type": "PIPELINE_STATE_CHANGE", "type": "WS_CONNECTED",
"data": { "payload": {
"state": "ENCODING", "connectedAt": "2026-03-05T10:00:00.000Z"
"jobId": 42,
"job": {
"id": 42,
"title": "Inception",
"status": "ENCODING"
}
} }
} }
``` ```
--- ### PIPELINE_STATE_CHANGED
### PROGRESS_UPDATE Snapshot bei Zustandswechsel.
Wird während aktiver Prozesse (Ripping/Encoding) regelmäßig gesendet.
```json ```json
{ {
"type": "PROGRESS_UPDATE", "type": "PIPELINE_STATE_CHANGED",
"data": { "payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5, "progress": 73.5,
"eta": "00:12:34", "eta": "00:12:34",
"speed": "45.2 fps", "statusText": "Encoding mit HandBrake",
"phase": "ENCODING" "context": {},
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 0
}
} }
} }
``` ```
**Felder:** ### PIPELINE_PROGRESS
| Feld | Typ | Beschreibung | Laufende Fortschrittsupdates während aktiver Phasen.
|-----|-----|-------------|
| `progress` | number | Fortschritt 0100 |
| `eta` | string | Geschätzte Restzeit (`HH:MM:SS`) |
| `speed` | string | Encoding-Geschwindigkeit (nur beim Encoding) |
| `phase` | string | Aktuelle Phase (`RIPPING` oder `ENCODING`) |
--- ```json
{
"type": "PIPELINE_PROGRESS",
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5,
"eta": "00:12:34",
"statusText": "ENCODING 73.50% - task 1 of 1"
}
}
```
### PIPELINE_QUEUE_CHANGED
Aktualisierung der Job-Queue.
```json
{
"type": "PIPELINE_QUEUE_CHANGED",
"payload": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
}
```
### DISC_DETECTED ### DISC_DETECTED
Wird gesendet, wenn eine Disc erkannt wird. Disc erkannt.
```json ```json
{ {
"type": "DISC_DETECTED", "type": "DISC_DETECTED",
"data": { "payload": {
"device": "/dev/sr0" "device": {
"path": "/dev/sr0",
"discLabel": "INCEPTION"
}
} }
} }
``` ```
---
### DISC_REMOVED ### DISC_REMOVED
Wird gesendet, wenn eine Disc ausgeworfen wird. Disc entfernt.
```json ```json
{ {
"type": "DISC_REMOVED", "type": "DISC_REMOVED",
"data": { "payload": {
"device": "/dev/sr0" "device": {
"path": "/dev/sr0"
}
} }
} }
``` ```
--- ### PIPELINE_ERROR
### JOB_COMPLETE Fehler bei Pipeline-Disc-Events im Backend.
Wird gesendet, wenn ein Job erfolgreich abgeschlossen wurde.
```json ```json
{ {
"type": "JOB_COMPLETE", "type": "PIPELINE_ERROR",
"data": { "payload": {
"jobId": 42, "message": "..."
"title": "Inception",
"outputPath": "/mnt/nas/movies/Inception (2010).mkv"
} }
} }
``` ```
--- ### DISK_DETECTION_ERROR
### ERROR Fehler im Laufwerkserkennungsdienst.
Wird gesendet, wenn ein Fehler aufgetreten ist.
```json ```json
{ {
"type": "ERROR", "type": "DISK_DETECTION_ERROR",
"data": { "payload": {
"jobId": 42, "message": "..."
"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": [ ... ]
}
} }
} }
``` ```
@@ -182,44 +164,23 @@ Wird gesendet, wenn der Benutzer den Encode-Plan bestätigen soll.
## Reconnect-Verhalten ## Reconnect-Verhalten
Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect: `useWebSocket.js` versucht bei Verbindungsabbruch automatisch erneut zu verbinden.
``` - fester Retry-Intervall: `1500ms`
Verbindung verloren - erneuter Versuch bis zum Unmount der Komponente
Warte 1s → Reconnect-Versuch
↓ (Fehlschlag)
Warte 2s → Reconnect-Versuch
↓ (Fehlschlag)
Warte 4s → ...
Max. 30s Wartezeit
```
--- ---
## Beispiel: React-Hook ## React-Beispiel
```js ```js
import { useEffect, useState } from 'react'; import { useWebSocket } from './hooks/useWebSocket';
function usePipelineState() { useWebSocket({
const [state, setState] = useState({ state: 'IDLE' }); onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGED') {
useEffect(() => { setPipeline(msg.payload);
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;
} }
});
``` ```

View File

@@ -19,13 +19,16 @@ Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, d
| Methode | Beschreibung | | Methode | Beschreibung |
|---------|-------------| |---------|-------------|
| `analyzeDisc()` | Startet MakeMKV-Analyse der eingelegten Disc | | `analyzeDisc()` | Legt Job an und öffnet Metadaten-Auswahl |
| `selectMetadata(jobId, omdbData, playlist)` | Setzt Metadaten und Playlist für einen Job | | `selectMetadata({...})` | Setzt Metadaten/Playlist und triggert Auto-Start |
| `startJob(jobId)` | Startet den Ripping-Prozess | | `startPreparedJob(jobId)` | Startet vorbereiteten Job (oder Queue) |
| `confirmEncode(jobId, trackSelection)` | Bestätigt Encode mit Track-Auswahl | | `confirmEncodeReview(jobId, options)` | Bestätigt Review inkl. Track/Skript-Auswahl |
| `cancelPipeline()` | Bricht aktiven Prozess ab | | `cancel(jobId)` | Bricht laufenden Job ab oder entfernt Queue-Eintrag |
| `retryJob(jobId)` | Wiederholt fehlgeschlagenen Job | | `retry(jobId)` | Startet fehlgeschlagenen/abgebrochenen Job neu |
| `reencodeJob(jobId)` | Encodiert bestehende Raw-MKV 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 ### Zustandsübergänge
@@ -34,20 +37,26 @@ Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, d
```mermaid ```mermaid
flowchart LR flowchart LR
START(( )) --> IDLE START(( )) --> IDLE
IDLE -->|analyzeDisc()| ANALYZING[ANALYZING] IDLE -->|analyzeDisc()| META[METADATA\nSELECTION]
ANALYZING -->|MakeMKV fertig| META[METADATA\nSELECTION]
META -->|selectMetadata()| RTS[READY_TO\nSTART] 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] 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] MIC -->|Tracks analysiert| RTE[READY_TO\nENCODE]
RTE -->|confirmEncode()| ENC[ENCODING] RTE -->|confirmEncodeReview() + startPreparedJob()| ENC[ENCODING]
ENC -->|HandBrake fertig| FIN([FINISHED]) ENC -->|HandBrake + Post-Skripte fertig| FIN([FINISHED])
ENC -->|Abbruch| CAN([CANCELLED])
ENC -->|Fehler| ERR([ERROR]) ENC -->|Fehler| ERR([ERROR])
RIP -->|Fehler| ERR RIP -->|Fehler| ERR
ERR -->|retryJob() / cancel| IDLE RIP -->|Abbruch| CAN
ERR -->|retry() / cancel()| IDLE
CAN -->|retry() / analyzeDisc()| IDLE
FIN -->|cancel / neue Disc| IDLE FIN -->|cancel / neue Disc| IDLE
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 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 ERR fill:#ffebee,stroke:#ef5350,color:#c62828
style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0 style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
@@ -71,12 +80,12 @@ flowchart LR
### Polling ### 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 ```js
// Ereignisse // Ereignisse
emit('disc-detected', { device: '/dev/sr0' }) emit('discInserted', { path: '/dev/sr0' })
emit('disc-removed', { device: '/dev/sr0' }) emit('discRemoved', { path: '/dev/sr0' })
``` ```
--- ---
@@ -120,8 +129,9 @@ WebSocket-Server für Echtzeit-Client-Kommunikation.
### API ### API
```js ```js
broadcast({ type: 'PIPELINE_STATE_CHANGE', data: { state, jobId } }); broadcast('PIPELINE_STATE_CHANGED', { state, activeJobId });
broadcast({ type: 'PROGRESS_UPDATE', data: { progress, eta } }); broadcast('PIPELINE_PROGRESS', { state, progress, eta, statusText });
broadcast('PIPELINE_QUEUE_CHANGED', queueSnapshot);
``` ```
--- ---
@@ -168,13 +178,12 @@ Verwaltet alle Anwendungseinstellungen.
| Kategorie | Einstellungen | | Kategorie | Einstellungen |
|-----------|--------------| |-----------|--------------|
| `paths` | `raw_dir`, `movie_dir`, `log_dir` | | `Pfade` | `raw_dir`, `movie_dir`, `log_dir` |
| `tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command` | | `Laufwerk` | `drive_mode`, `drive_device`, `disc_poll_interval_ms`, `makemkv_source_index` |
| `encoding` | `handbrake_preset`, `handbrake_extra_args`, `output_extension`, `filename_template` | | `Monitoring` | `hardware_monitoring_enabled`, `hardware_monitoring_interval_ms` |
| `drive` | `drive_mode`, `drive_device`, `disc_poll_interval_ms` | | `Tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command`, `pipeline_max_parallel_jobs` |
| `makemkv` | `makemkv_min_length_minutes`, `makemkv_backup_mode` | | `Metadaten` | `omdb_api_key`, `omdb_default_type` |
| `omdb` | `omdb_api_key`, `omdb_default_type` | | `Benachrichtigungen` | `pushover_user_key`, `pushover_api_token` |
| `notifications` | `pushover_user_key`, `pushover_api_token` |
--- ---

View File

@@ -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. Die Hauptseite von Ripster zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen.
**Funktionen:** **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 - Live-Fortschrittsbalken mit ETA
- Trigger für Metadaten-Dialog - Trigger für Metadaten-Dialog
- Playlist-Entscheidungs-UI (bei Blu-ray Obfuskierung) - Playlist-Entscheidungs-UI (bei Blu-ray Obfuskierung)
- Encode-Review mit Track-Auswahl - Encode-Review mit Track-Auswahl
- Job-Steuerung (Start, Abbruch, Retry) - Job-Steuerung (Start, Abbruch, Retry, Queue-Interaktion)
**Zugehörige Komponenten:** **Zugehörige Komponenten:**
- `PipelineStatusCard` Status-Widget - `PipelineStatusCard` Status-Widget
- `MetadataSelectionDialog` OMDb-Suche und Playlist-Auswahl - `MetadataSelectionDialog` OMDb-Suche und Playlist-Auswahl
- `MediaInfoReviewPanel` Track-Auswahl vor dem Encoding - `MediaInfoReviewPanel` Track-Auswahl vor dem Encoding
- `DiscDetectedDialog` Benachrichtigung bei Disc-Erkennung - Queue- und Job-Karten-UI direkt in `DashboardPage`
### SettingsPage.jsx ### SettingsPage.jsx
@@ -34,9 +34,9 @@ Konfigurationsoberfläche für alle Ripster-Einstellungen.
- PushOver-Verbindungstest - PushOver-Verbindungstest
- Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen - 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:** **Funktionen:**
- Sortier- und filterbares Job-Verzeichnis - Sortier- und filterbares Job-Verzeichnis
@@ -88,7 +88,7 @@ Track-Auswahl-Panel vor dem Encoding.
│ ☑ Track 1: Deutsch │ │ ☑ Track 1: Deutsch │
│ ☐ Track 2: English │ │ ☐ 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. Zentraler Custom-Hook für die WebSocket-Verbindung.
```js ```js
const { status, lastMessage } = useWebSocket({ useWebSocket({
onMessage: (msg) => { onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGE') { if (msg.type === 'PIPELINE_STATE_CHANGED') {
setPipelineState(msg.data); setPipelineState(msg.payload);
} }
} }
}); });
@@ -134,9 +134,8 @@ const { status, lastMessage } = useWebSocket({
**Features:** **Features:**
- Automatische Verbindung zu `/ws` - Automatische Verbindung zu `/ws`
- Reconnect mit exponential backoff - Reconnect mit festem Intervall (`1500ms`)
- Message-Parsing (JSON) - Message-Parsing (JSON)
- Status-Tracking (connecting, connected, disconnected)
--- ---
@@ -148,8 +147,11 @@ Zentraler HTTP-Client für alle Backend-Anfragen.
// Beispiel-Aufrufe // Beispiel-Aufrufe
const state = await api.getPipelineState(); const state = await api.getPipelineState();
const results = await api.searchOmdb('Inception'); const results = await api.searchOmdb('Inception');
await api.selectMetadata(jobId, omdbData, playlist); await api.selectMetadata({ jobId, title, year, imdbId, selectedPlaylist });
await api.confirmEncode(jobId, { audioTracks: [0, 1], subtitleTracks: [0] }); await api.confirmEncodeReview(jobId, {
selectedEncodeTitleId: 1,
selectedTrackSelection: { 1: { audioTrackIds: [1], subtitleTrackIds: [3] } }
});
``` ```
**Features:** **Features:**

View File

@@ -82,7 +82,7 @@ App.jsx (React Router)
├── Pages ├── Pages
│ ├── DashboardPage.jsx ← Haupt-Interface │ ├── DashboardPage.jsx ← Haupt-Interface
│ ├── SettingsPage.jsx │ ├── SettingsPage.jsx
│ └── HistoryPage.jsx │ └── DatabasePage.jsx ← Historie/DB-Ansicht
├── Components ├── Components
│ ├── PipelineStatusCard.jsx │ ├── PipelineStatusCard.jsx
│ ├── MetadataSelectionDialog.jsx │ ├── MetadataSelectionDialog.jsx

View File

@@ -34,10 +34,10 @@ Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON ü
```json ```json
{ {
"type": "PIPELINE_STATE_CHANGE", "type": "PIPELINE_STATE_CHANGED",
"data": { "payload": {
"state": "ENCODING", "state": "ENCODING",
"jobId": 42, "activeJobId": 42,
"progress": 73.5, "progress": 73.5,
"eta": "00:12:34" "eta": "00:12:34"
} }
@@ -48,16 +48,17 @@ Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON ü
| Typ | Beschreibung | | Typ | Beschreibung |
|----|-------------| |----|-------------|
| `PIPELINE_STATE_CHANGE` | Pipeline-Zustand hat gewechselt | | `PIPELINE_STATE_CHANGED` | Pipeline-Zustand hat gewechselt |
| `PROGRESS_UPDATE` | Fortschritt (% und ETA) | | `PIPELINE_PROGRESS` | Fortschritt (% und ETA) |
| `PIPELINE_QUEUE_CHANGED` | Queue-Status geändert |
| `DISC_DETECTED` | Disc wurde erkannt | | `DISC_DETECTED` | Disc wurde erkannt |
| `DISC_REMOVED` | Disc wurde entfernt | | `DISC_REMOVED` | Disc wurde entfernt |
| `ERROR` | Fehler aufgetreten | | `PIPELINE_ERROR` | Pipeline-Fehler aufgetreten |
| `JOB_COMPLETE` | Job abgeschlossen | | `DISK_DETECTION_ERROR` | Laufwerkserkennung-Fehler |
### Reconnect-Logik ### 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.
--- ---

View File

@@ -82,7 +82,7 @@ Häufig verwendete Presets:
|---------|-----|---------|---------|-------------| |---------|-----|---------|---------|-------------|
| `drive_mode` | select | `auto` | `auto`, `explicit` | Laufwerk-Erkennungsmodus | | `drive_mode` | select | `auto` | `auto`, `explicit` | Laufwerk-Erkennungsmodus |
| `drive_device` | string | `/dev/sr0` | — | Geräte-Pfad (nur bei `explicit`) | | `drive_device` | string | `/dev/sr0` | — | Geräte-Pfad (nur bei `explicit`) |
| `disc_poll_interval_ms` | number | `5000` | 100060000 | Polling-Intervall in Millisekunden | | `disc_poll_interval_ms` | number | `4000` | 100060000 | Polling-Intervall in Millisekunden |
**`drive_mode` Optionen:** **`drive_mode` Optionen:**

View File

@@ -84,7 +84,7 @@ Das Template unterstützt folgende Platzhalter:
|------------|---------|-------------| |------------|---------|-------------|
| `drive_mode` | `auto` | `auto` (automatisch erkennen) oder `explicit` (festes Gerät) | | `drive_mode` | `auto` | `auto` (automatisch erkennen) oder `explicit` (festes Gerät) |
| `drive_device` | `/dev/sr0` | Geräte-Pfad (nur bei `explicit`) | | `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 |
--- ---

View File

@@ -4,7 +4,7 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
--- ---
## Übersicht: Pipeline-Zustände ## Übersicht: Pipeline-Ablauf
<div class="pipeline-steps"> <div class="pipeline-steps">
<div class="pipeline-step"> <div class="pipeline-step">
@@ -20,7 +20,7 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
<div class="pipeline-step"> <div class="pipeline-step">
<div class="pipeline-step-badge step-running">2</div> <div class="pipeline-step-badge step-running">2</div>
<div class="pipeline-step-label">METADATA_SELECTION</div> <div class="pipeline-step-label">METADATA_SELECTION</div>
<div class="pipeline-step-sub">Scan &amp; Metadaten</div> <div class="pipeline-step-sub">OMDb &amp; Dialog</div>
</div> </div>
<div class="pipeline-step"> <div class="pipeline-step">
<div class="pipeline-step-badge step-wait">⚠</div> <div class="pipeline-step-badge step-wait">⚠</div>
@@ -53,9 +53,9 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
<div class="pipeline-step-sub">HandBrake</div> <div class="pipeline-step-sub">HandBrake</div>
</div> </div>
<div class="pipeline-step"> <div class="pipeline-step">
<div class="pipeline-step-badge step-encode">8</div> <div class="pipeline-step-badge step-encode">8*</div>
<div class="pipeline-step-label">POST_ENCODE_SCRIPTS</div> <div class="pipeline-step-label">POST-ENCODE</div>
<div class="pipeline-step-sub">Skripte<br><em>(optional)</em></div> <div class="pipeline-step-sub">Skripte<br><em>(innerhalb ENCODING)</em></div>
</div> </div>
<div class="pipeline-step"> <div class="pipeline-step">
<div class="pipeline-step-badge step-done">✓</div> <div class="pipeline-step-badge step-done">✓</div>
@@ -77,31 +77,29 @@ Nach der [Installation](installation.md) und [Konfiguration](configuration.md) f
IDLE -->|Disc erkannt| DD[DISC_DETECTED] IDLE -->|Disc erkannt| DD[DISC_DETECTED]
DD -->|Analyse starten| META[METADATA\nSELECTION] DD -->|Analyse starten| META[METADATA\nSELECTION]
META -->|1 Kandidat| RTS[READY_TO\nSTART] META -->|Metadaten übernommen| RTS[READY_TO\nSTART]
META -->|mehrere Kandidaten| WUD[WAITING_FOR\nUSER_DECISION] META -->|vorhandenes RAW +\nPlaylist offen| WUD[WAITING_FOR\nUSER_DECISION]
WUD -->|Playlist bestätigt| RTS 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 -->|MKV fertig| MIC
RIP -->|Fehler| ERR 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] MIC --> RTE[READY_TO\nENCODE]
RTE -->|bestätigt| ENC[ENCODING] RTE -->|Encoding starten| ENC[ENCODING]
ENC -->|mit Skripten| PES[POST_ENCODE\nSCRIPTS] ENC -->|inkl. Post-Skripte| FIN([FINISHED])
ENC -->|ohne Skripte| FIN([FINISHED])
ENC -->|Fehler| ERR ENC -->|Fehler| ERR
PES -->|Erfolg| FIN
PES -->|Fehler| ERR
ERR([ERROR]) -->|Retry / Cancel| IDLE ERR([ERROR]) -->|Retry / Cancel| IDLE
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32
style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828
style WUD fill:#fff8e1,stroke:#ffa726,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 ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
``` ```
@@ -122,13 +120,14 @@ cd ripster
## Schritt 2 Disc einlegen → `DISC_DETECTED` ## 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:** **Was passiert im Code:**
- `diskDetectionService` emittiert `disc:inserted` mit Geräteinformationen - `diskDetectionService` emittiert `discInserted` mit Geräteinformationen
- `pipelineService.onDiscInserted()` wird aufgerufen - `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 - Der **"Analyse starten"**-Button wird aktiv
!!! tip "Manuelle Auslösung" !!! 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` ## Schritt 3 Analyse starten → `METADATA_SELECTION`
Klicke auf **"Analyse starten"** oder warte auf automatischen Start. Klicke auf **"Analyse starten"**.
**Was passiert im Code:** **Was passiert im Code:**
@@ -183,21 +182,18 @@ Falls kein passendes Ergebnis gefunden wird:
**Was passiert nach Bestätigung:** **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 - Job wird auf `READY_TO_START` gesetzt (kurzer Übergangszustand)
- Alle Titel und deren Segment-Reihenfolgen werden analysiert - Falls bereits RAW vorhanden: direkter Sprung zu `MEDIAINFO_CHECK`
- Das Ergebnis entscheidet über den nächsten Zustand (→ Schritt 5) - 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` Dieser Zustand erscheint nur bei mehrdeutigen Blu-ray-Playlists (typisch nach RAW-Analyse im Backup-Modus).
Der Dialog schließt sich automatisch. Die empfohlene Playlist wird still übernommen. Weiter zu **Schritt 6**.
### 5b) Obfuskierung erkannt → `WAITING_FOR_USER_DECISION`
Der **Playlist-Auswahl-Dialog** erscheint **zusätzlich** (nach dem Metadaten-Dialog): 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) 847 Playlists insgesamt · 3 relevante Kandidaten (≥ 15 min)
Empfehlung: 00800 (vorausgewählt) 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 - 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" !!! info "Scoring-Details"
Wie die Scores berechnet werden, erklärt die [Playlist-Analyse](../pipeline/playlist-analysis.md)-Seite. 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? **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 - **Ja, Raw-Datei vorhanden** → Direkt zu Schritt 7 (Track-Review), kein erneutes Ripping
- **Nein** → MakeMKV-Ripping startet - **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):** **Was MakeMKV ausführt (MKV-Modus):**
@@ -269,7 +268,7 @@ PRGT:5011,0,"Sichern..." → Aktueller Task-Name
## Schritt 7 Track-Review → `READY_TO_ENCODE` ## 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 ```bash
HandBrakeCLI --scan -i <quelle> -t 0 HandBrakeCLI --scan -i <quelle> -t 0
@@ -297,7 +296,7 @@ Danach öffnet sich das **Encode-Review-Panel** (`READY_TO_ENCODE`):
│ ☑ │ Track 1: Deutsch │ Einbr.☐ │Forc.☐│Default☑ │ │ ☑ │ Track 1: Deutsch │ Einbr.☐ │Forc.☐│Default☑ │
│ ☐ │ Track 2: English │ 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. 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 | | 1 | `IDLE` | Disc einlegen | Disc-Polling erkennt Disc |
| 2 | `DISC_DETECTED` | "Analyse starten" klicken | Job anlegen, OMDb vorsuchen | | 2 | `DISC_DETECTED` | "Analyse starten" klicken | Job anlegen, OMDb vorsuchen |
| 3 | `METADATA_SELECTION` | Film im Dialog auswählen | Playlist-Analyse durchführen | | 3 | `METADATA_SELECTION` | Film im Dialog auswählen | Start automatisch einplanen/auslösen |
| 4a | `READY_TO_START` | — | Empfehlung automatisch übernommen | | 4 | `READY_TO_START` | meist keine | Übergangszustand vor Auto-Start |
| 4b | `WAITING_FOR_USER_DECISION` | Playlist manuell wählen | Auf Bestätigung warten | | 5 | `RIPPING` | Warten | MakeMKV rippt, Fortschritt streamen |
| 5 | `READY_TO_START` | "Starten" klicken | MakeMKV-Ripping starten | | 6 | `MEDIAINFO_CHECK` | Warten | HandBrake-Scan, Encode-Plan bauen |
| 6 | `RIPPING` | Warten | MakeMKV rippt, Fortschritt streamen | | 7 | `WAITING_FOR_USER_DECISION` (optional) | Playlist manuell wählen | Auf Bestätigung warten |
| 7 | `MEDIAINFO_CHECK` | Warten | HandBrake-Scan, Encode-Plan bauen | | 8 | `READY_TO_ENCODE` | Tracks prüfen + "Encoding starten" | Auswahl übernehmen, Start auslösen |
| 8 | `READY_TO_ENCODE` | Tracks prüfen + bestätigen | Auswahl in Plan übernehmen | | 9 | `ENCODING` | Warten | HandBrake encodiert, inkl. Post-Skripte |
| 9 | `ENCODING` | Warten | HandBrake encodiert, Fortschritt streamen |
| 10 | `FINISHED` | — | Datei fertig, Benachrichtigung senden | | 10 | `FINISHED` | — | Datei fertig, Benachrichtigung senden |

View File

@@ -118,25 +118,24 @@ open http://localhost:5173
flowchart LR flowchart LR
IDLE --> DD[DISC_DETECTED] IDLE --> DD[DISC_DETECTED]
DD --> META[METADATA\nSELECTION] DD --> META[METADATA\nSELECTION]
META -->|1 Kandidat| RTS[READY_TO\nSTART] META --> RTS[READY_TO\nSTART]
META -->|Obfuskierung| WUD[WAITING_FOR\nUSER_DECISION] RTS -->|Auto-Start| RIP[RIPPING]
WUD --> RTS RTS -->|Auto-Start mit RAW| MIC
RTS --> RIP[RIPPING]
RTS -->|Raw vorhanden| MIC
RIP --> MIC[MEDIAINFO\nCHECK] RIP --> MIC[MEDIAINFO\nCHECK]
MIC -->|Playlist offen (Backup)| WUD[WAITING_FOR\nUSER_DECISION]
WUD --> MIC
MIC --> RTE[READY_TO\nENCODE] MIC --> RTE[READY_TO\nENCODE]
RTE --> ENC[ENCODING] RTE --> ENC[ENCODING]
ENC --> PES[POST_ENCODE\nSCRIPTS] ENC -->|inkl. Post-Skripte| FIN([FINISHED])
ENC -->|keine Skripte| FIN([FINISHED])
PES --> FIN
ENC --> ERR([ERROR]) ENC --> ERR([ERROR])
RIP --> ERR RIP --> ERR
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32
style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 style ERR fill:#ffebee,stroke:#ef5350,color:#c62828
style WUD fill:#fff8e1,stroke:#ffa726,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 ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
``` ```
</div> </div>
`READY_TO_START` ist in der Praxis meist ein kurzer Übergangszustand: der Job wird nach Metadaten-Auswahl automatisch gestartet oder in die Queue eingeplant.

View File

@@ -243,7 +243,7 @@ Das Review-Panel zeigt:
│ [✓] │ Track 1: Deutsch │Einbr.[ ]│Forced[ ]│Default[✓]│ │ [✓] │ Track 1: Deutsch │Einbr.[ ]│Forced[ ]│Default[✓]│
│ [ ] │ Track 2: English │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`) ## 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 ```json
Payload: { Payload: {

View File

@@ -21,7 +21,8 @@ FINISHED
``` ```
!!! warning "Abbruch bei Fehler" !!! 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": { "postEncodeScripts": {
"configured": 2, "configured": 2,
"attempted": 2,
"succeeded": 2, "succeeded": 2,
"failed": 0, "failed": 0,
"skipped": 0, "skipped": 0,
"aborted": false, "aborted": false,
"results": [ "results": [
{ {
"scriptId": "script-1", "scriptId": 1,
"name": "Zu Plex verschieben", "scriptName": "Zu Plex verschieben",
"exitCode": 0, "status": "SUCCESS"
"stdout": "Verschoben: Inception nach /mnt/media/movies",
"stderr": "",
"durationMs": 342
}, },
{ {
"scriptId": "script-2", "scriptId": 2,
"name": "Webhook auslösen", "scriptName": "Webhook auslösen",
"exitCode": 0, "status": "SUCCESS"
"stdout": "",
"stderr": "",
"durationMs": 128
} }
] ]
} }
@@ -149,6 +145,7 @@ Das Ergebnis der Skript-Ausführung wird im Job-Datensatz gespeichert und in der
| Feld | Beschreibung | | Feld | Beschreibung |
|------|-------------| |------|-------------|
| `configured` | Anzahl ausgewählter Skripte | | `configured` | Anzahl ausgewählter Skripte |
| `attempted` | Anzahl tatsächlich gestarteter Skripte |
| `succeeded` | Erfolgreich ausgeführt (Exit-Code 0) | | `succeeded` | Erfolgreich ausgeführt (Exit-Code 0) |
| `failed` | Fehlgeschlagen | | `failed` | Fehlgeschlagen |
| `skipped` | Nicht ausgeführt (wegen vorherigem Fehler) | | `skipped` | Nicht ausgeführt (wegen vorherigem Fehler) |

View File

@@ -15,32 +15,33 @@ flowchart LR
IDLE -->|Disc erkannt| DD[DISC_DETECTED] IDLE -->|Disc erkannt| DD[DISC_DETECTED]
DD -->|Analyse starten| META[METADATA\nSELECTION] DD -->|Analyse starten| META[METADATA\nSELECTION]
META -->|1 Kandidat| RTS[READY_TO\nSTART] META -->|Metadaten übernommen| RTS[READY_TO\nSTART]
META -->|mehrere Kandidaten| WUD[WAITING_FOR\nUSER_DECISION] META -->|vorhandenes RAW +\nPlaylist offen| WUD[WAITING_FOR\nUSER_DECISION]
WUD -->|Playlist bestätigt| RTS
RTS -->|Raw vorhanden| MIC[MEDIAINFO\nCHECK] RTS -->|Auto-Start| RIP[RIPPING]
RTS -->|Ripping starten| RIP[RIPPING] RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK]
RIP -->|MKV fertig| MIC RIP -->|MKV fertig| MIC
RIP -->|Fehler| ERR 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] 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 -->|inkl. Post-Skripte| FIN([FINISHED])
ENC -->|ohne Skripte| FIN([FINISHED])
ENC -->|Fehler| ERR ENC -->|Fehler| ERR
ENC -->|Abbruch| CAN
PES -->|Erfolg| FIN
PES -->|Fehler| ERR
ERR([ERROR]) -->|Retry / Cancel| IDLE ERR([ERROR]) -->|Retry / Cancel| IDLE
CAN -->|Retry / Neu-Analyse| IDLE
FIN -->|Neue Disc| IDLE FIN -->|Neue Disc| IDLE
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32 style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32
style ERR fill:#ffebee,stroke:#ef5350,color:#c62828 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 WUD fill:#fff8e1,stroke:#ffa726,color:#e65100
style PES fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0 style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
style MIC 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 ## Zustandsbeschreibungen
### IDLE ### IDLE
@@ -66,7 +89,8 @@ flowchart LR
**Disc erkannt, wartet auf Benutzeraktion.** **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 - **"Analyse starten"**-Button wird aktiv
- Kein Prozess läuft noch - Kein Prozess läuft noch
@@ -76,20 +100,21 @@ flowchart LR
### METADATA_SELECTION ### 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 2. OMDb-Vorsuche mit erkanntem Disc-Label
3. `MetadataSelectionDialog` öffnet sich mit vorgeladenen Ergebnissen 3. `MetadataSelectionDialog` öffnet sich mit vorgeladenen Ergebnissen
4. Benutzer wählt Filmtitel (oder gibt manuell ein) 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` | | Kein verwertbares RAW vorhanden | `READY_TO_START` → automatisch `RIPPING` (oder Queue) |
| Mehrere Kandidaten nach Mindestlänge | `WAITING_FOR_USER_DECISION` | | 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 - Alle Kandidaten mit Score, Laufzeit und Bewertungslabel
- Empfohlene Playlist ist vorausgewählt - Empfohlene Playlist ist vorausgewählt
- Benutzer bestätigt mit **"Playlist übernehmen"** - 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:** **Darstellung im Dashboard:**
@@ -121,7 +147,10 @@ flowchart LR
[Playlist übernehmen] [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) Mehr Details: [Playlist-Analyse](playlist-analysis.md)
@@ -129,15 +158,17 @@ Mehr Details: [Playlist-Analyse](playlist-analysis.md)
### READY_TO_START ### READY_TO_START
**Metadaten und Playlist bestätigt, bereit zum Starten.** **Übergangs-/Fallback-Zustand vor dem eigentlichen Start.**
- Job-Datensatz in Datenbank aktualisiert - Wird nach Metadaten-Bestätigung kurz gesetzt
- **"Starten"**-Button im Dashboard aktiv - `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:** **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. 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.** **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 1. Optional: Playlist-Auflösung bei Blu-ray-Backup (inkl. MakeMKV/HandBrake-Zuordnung)
2. **Encode-Plan-Erstellung** mit automatischer Track-Vorauswahl 2. **HandBrake-Scan** (`HandBrakeCLI --scan`) auf RAW-Input
3. **Encode-Plan-Erstellung** mit automatischer Track-Vorauswahl
Kein Benutzereingriff läuft automatisch durch. 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 ### READY_TO_ENCODE
**Encode-Plan bereit, wartet auf Benutzer-Bestätigung.** **Encode-Plan bereit.**
Das `MediaInfoReviewPanel` zeigt: Das `MediaInfoReviewPanel` zeigt:
@@ -198,7 +233,10 @@ Das `MediaInfoReviewPanel` zeigt:
- **Untertitel-Tracks** mit Flags (Einbrennen, Forced, Default) - **Untertitel-Tracks** mit Flags (Einbrennen, Forced, Default)
- **Post-Encode-Skripte** Auswahl und Reihenfolge der auszuführenden Skripte - **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) 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 !!! note "Skriptfehler"
Skriptfehler führen zum Abbruch der Skriptkette, der Job bleibt jedoch im Abschlusszustand `FINISHED` mit entsprechendem Hinweis im Status-Text/Log.
**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)
--- ---
@@ -259,7 +273,17 @@ Details: [Post-Encode-Skripte](post-encode-scripts.md)
- Ausgabedatei liegt im konfigurierten `movie_dir` - Ausgabedatei liegt im konfigurierten `movie_dir`
- Job-Status in Datenbank: `FINISHED` - Job-Status in Datenbank: `FINISHED`
- PushOver-Benachrichtigung (falls konfiguriert) - 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 - Fehlerdetails im Job-Datensatz gespeichert
- Fehler-Logs in History abrufbar - Fehler-Logs in History abrufbar
- **Retry**: Neustart vom Fehlerzustand - **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 - 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 ### Job wiederholen
@@ -291,8 +315,8 @@ POST /api/pipeline/cancel
POST /api/pipeline/retry/:jobId POST /api/pipeline/retry/:jobId
``` ```
- Setzt Job zurück auf `READY_TO_START` - Startet den Job neu in `RIPPING` (oder reiht den Retry in die Queue ein)
- Metadaten und Playlist-Auswahl bleiben erhalten - Metadaten bleiben erhalten; Encode-/Scan-Daten werden neu erzeugt
### Re-Encode ### Re-Encode