Bugfix and Docs

This commit is contained in:
2026-03-10 13:12:57 +00:00
parent 3516ff8486
commit ac4d77dddf
75 changed files with 3511 additions and 5142 deletions

View File

@@ -1,16 +1,12 @@
# Cron API
Ripster enthält ein eingebautes Cron-System, mit dem **Skripte** und **Skript-Ketten** zeitgesteuert oder manuell ausgeführt werden können. Der Cron-Dienst benötigt keine externen Pakete der Cron-Expression-Parser ist vollständig im Backend implementiert.
Ripster enthält ein eingebautes Cron-System für Skripte und Skript-Ketten (`sourceType: script|chain`).
---
## Endpunkte
## GET /api/crons
### `GET /api/crons`
Alle konfigurierten Cron-Jobs auflisten.
**Antwort:**
Listet alle Cron-Jobs.
```json
{
@@ -21,13 +17,14 @@ Alle konfigurierten Cron-Jobs auflisten.
"cronExpression": "0 2 * * *",
"sourceType": "script",
"sourceId": 3,
"sourceName": "Backup-Skript",
"enabled": true,
"pushoverEnabled": true,
"lastRunAt": "2026-03-09T02:00:00.000Z",
"lastRunAt": "2026-03-10T02:00:00.000Z",
"lastRunStatus": "success",
"nextRunAt": "2026-03-10T02:00:00.000Z",
"nextRunAt": "2026-03-11T02:00:00.000Z",
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-09T02:00:00.000Z"
"updatedAt": "2026-03-10T02:00:05.000Z"
}
]
}
@@ -35,11 +32,9 @@ Alle konfigurierten Cron-Jobs auflisten.
---
### `POST /api/crons`
## POST /api/crons
Neuen Cron-Job anlegen.
**Body:**
Erstellt Cron-Job.
```json
{
@@ -52,16 +47,25 @@ Neuen Cron-Job anlegen.
}
```
| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|-------------|
| `name` | string | ✓ | Anzeigename |
| `cronExpression` | string | ✓ | 5-Felder-Cron-Ausdruck (Minute Stunde Tag Monat Wochentag) |
| `sourceType` | string | ✓ | `"script"` oder `"chain"` |
| `sourceId` | number | ✓ | ID des Skripts bzw. der Kette |
| `enabled` | boolean | | Aktiviert (default: `true`) |
| `pushoverEnabled` | boolean | | PushOver-Benachrichtigung nach Ausführung (default: `true`) |
Response: `201` mit `{ "job": { ... } }`
**Antwort:** `201 Created`
---
## GET /api/crons/:id
Response:
```json
{ "job": { "id": 1, "name": "..." } }
```
---
## PUT /api/crons/:id
Aktualisiert Cron-Job. Felder wie bei `POST`.
Response:
```json
{ "job": { ... } }
@@ -69,53 +73,27 @@ Neuen Cron-Job anlegen.
---
### `GET /api/crons/:id`
## DELETE /api/crons/:id
Einzelnen Cron-Job abrufen.
**Antwort:**
Response:
```json
{ "job": { ... } }
{ "removed": { "id": 1, "name": "Nachtlauf Backup" } }
```
---
### `PUT /api/crons/:id`
## GET /api/crons/:id/logs
Cron-Job aktualisieren. Body-Felder entsprechen `POST /api/crons`.
**Antwort:**
```json
{ "job": { ... } }
```
---
### `DELETE /api/crons/:id`
Cron-Job löschen.
**Antwort:**
```json
{ "removed": { "id": 1 } }
```
---
### `GET /api/crons/:id/logs`
Ausführungs-Logs eines Cron-Jobs abrufen.
Liefert Ausführungs-Logs.
**Query-Parameter:**
| Parameter | Typ | Default | Beschreibung |
|-----------|-----|---------|-------------|
| `limit` | number | 20 | Anzahl Einträge (max. 100) |
| `limit` | number | `20` | Anzahl Einträge, max. `100` |
**Antwort:**
**Response:**
```json
{
@@ -123,62 +101,54 @@ Ausführungs-Logs eines Cron-Jobs abrufen.
{
"id": 42,
"cronJobId": 1,
"startedAt": "2026-03-09T02:00:01.000Z",
"finishedAt": "2026-03-09T02:00:05.000Z",
"startedAt": "2026-03-10T02:00:01.000Z",
"finishedAt": "2026-03-10T02:00:05.000Z",
"status": "success",
"exitCode": 0,
"stdout": "Backup abgeschlossen.",
"stderr": "",
"triggeredBy": "cron"
"output": "Backup abgeschlossen.",
"errorMessage": null
}
]
}
```
| Feld | Beschreibung |
|------|-------------|
| `status` | `"success"`, `"error"` oder `"running"` |
| `triggeredBy` | `"cron"` (zeitgesteuert) oder `"manual"` (manuell ausgelöst) |
`status`: `running` | `success` | `error`
---
### `POST /api/crons/:id/run`
## POST /api/crons/:id/run
Cron-Job sofort manuell auslösen (unabhängig vom Zeitplan).
Triggert Job manuell (asynchron).
**Antwort:**
**Response:**
```json
{
"status": "success",
"exitCode": 0,
"stdout": "...",
"stderr": ""
}
{ "triggered": true, "cronJobId": 1 }
```
Wenn Job bereits läuft: `409`.
---
### `POST /api/crons/validate-expression`
## POST /api/crons/validate-expression
Cron-Ausdruck validieren und nächsten Ausführungszeitpunkt berechnen.
Validiert 5-Felder-Cron-Ausdruck und berechnet nächsten Lauf.
**Body:**
**Request:**
```json
{ "cronExpression": "*/15 * * * *" }
```
**Antwort (gültig):**
**Gültige Response:**
```json
{
"valid": true,
"nextRunAt": "2026-03-09T14:15:00.000Z"
"nextRunAt": "2026-03-10T14:15:00.000Z"
}
```
**Antwort (ungültig):**
**Ungültige Response:**
```json
{
@@ -190,54 +160,23 @@ Cron-Ausdruck validieren und nächsten Ausführungszeitpunkt berechnen.
---
## Cron-Expression-Format
## Cron-Format
Ripster verwendet **5-Felder-Cron-Ausdrücke** (kein Sekunden-Feld):
Ripster unterstützt 5 Felder:
```
┌───────────── Minute (0-59)
│ ┌────────── Stunde (0-23)
│ │ ┌─────── Tag (1-31)
│ │ │ ┌──── Monat (1-12)
│ │ │ │ ┌─ Wochentag (0-7, 0 und 7 = Sonntag)
│ │ │ │ │
* * * * *
```text
Minute Stunde Tag Monat Wochentag
```
### Beispiele
Beispiele:
| Ausdruck | Beschreibung |
|----------|-------------|
| `0 2 * * *` | Täglich um 02:00 Uhr |
| `*/15 * * * *` | Alle 15 Minuten |
| `0 6 * * 1-5` | MontagFreitag um 06:00 Uhr |
| `30 23 * * 0` | Sonntags um 23:30 Uhr |
| `0 0 1 * *` | Erster Tag des Monats um Mitternacht |
### Unterstützte Syntax
| Syntax | Bedeutung |
|--------|----------|
| `*` | Jeder Wert |
| `*/n` | Jeder n-te Wert (Step) |
| `a-b` | Bereich von a bis b |
| `a,b,c` | Liste von Werten |
| Kombinierbar | z. B. `1,5-10,*/3` |
- `0 2 * * *` täglich 02:00
- `*/15 * * * *` alle 15 Minuten
- `0 6 * * 1-5` Mo-Fr 06:00
---
## WebSocket-Event
## WebSocket-Events zu Cron
Bei Änderungen an Cron-Jobs (Anlegen, Aktualisieren, Löschen) wird ein `CRON_JOBS_UPDATED`-Event gesendet:
```json
{
"type": "CRON_JOBS_UPDATED",
"payload": {
"action": "created",
"id": 1
}
}
```
`action` ist `"created"`, `"updated"` oder `"deleted"`.
- `CRON_JOBS_UPDATED` bei Create/Update/Delete
- `CRON_JOB_UPDATED` bei Laufzeitstatus (`running` -> `success|error`)

View File

@@ -1,23 +1,23 @@
# History API
Endpunkte für die Job-Histoire, Dateimanagement und Orphan-Import.
Endpunkte für Job-Historie, Orphan-Import und Löschoperationen.
---
## GET /api/history
Gibt eine Liste aller Jobs zurück, optional gefiltert.
Liefert Jobs (optionale Filter).
**Query-Parameter:**
| Parameter | Typ | Beschreibung |
|----------|-----|-------------|
| `status` | string | Filtert nach Status (z.B. `FINISHED`, `ERROR`) |
| `search` | string | Sucht in Filmtiteln |
| `status` | string | Filter nach Job-Status |
| `search` | string | Suche in Titel-Feldern |
**Beispiel:**
```
```text
GET /api/history?status=FINISHED&search=Inception
```
@@ -30,17 +30,15 @@ GET /api/history?status=FINISHED&search=Inception
"id": 42,
"status": "FINISHED",
"title": "Inception",
"imdb_id": "tt1375666",
"omdb_year": "2010",
"omdb_type": "movie",
"omdb_poster": "https://...",
"raw_path": "/mnt/nas/raw/Inception_t00.mkv",
"output_path": "/mnt/nas/movies/Inception (2010).mkv",
"created_at": "2024-01-15T10:00:00.000Z",
"updated_at": "2024-01-15T12:30:00.000Z"
"raw_path": "/mnt/raw/Inception - RAW - job-42",
"output_path": "/mnt/movies/Inception (2010)/Inception (2010).mkv",
"mediaType": "bluray",
"ripSuccessful": true,
"encodeSuccess": true,
"created_at": "2026-03-10T08:00:00.000Z",
"updated_at": "2026-03-10T10:00:00.000Z"
}
],
"total": 1
]
}
```
@@ -48,34 +46,37 @@ GET /api/history?status=FINISHED&search=Inception
## GET /api/history/:id
Gibt Detail-Informationen für einen einzelnen Job zurück.
**URL-Parameter:** `id` Job-ID
Liefert Job-Detail.
**Query-Parameter:**
| Parameter | Typ | Standard | Beschreibung |
|----------|-----|---------|-------------|
| `includeLogs` | boolean | `false` | Log-Inhalte einschließen |
| `includeLiveLog` | boolean | `false` | Aktuellen Live-Log einschließen |
| `includeLogs` | bool | `false` | Prozesslog laden |
| `includeLiveLog` | bool | `false` | alias-artig ebenfalls Prozesslog laden |
| `includeAllLogs` | bool | `false` | vollständiges Log statt Tail |
| `logTailLines` | number | `800` | Tail-Länge falls nicht `includeAllLogs` |
**Response:**
```json
{
"id": 42,
"status": "FINISHED",
"title": "Inception",
"imdb_id": "tt1375666",
"encode_plan": { ... },
"makemkv_output": { ... },
"mediainfo_output": { ... },
"handbrake_log": "/path/to/log",
"logs": {
"handbrake": "Encoding: task 1 of 1, 100.0%\n..."
},
"created_at": "2024-01-15T10:00:00.000Z",
"updated_at": "2024-01-15T12:30:00.000Z"
"job": {
"id": 42,
"status": "FINISHED",
"makemkvInfo": {},
"mediainfoInfo": {},
"handbrakeInfo": {},
"encodePlan": {},
"log": "...",
"log_count": 1,
"logMeta": {
"loaded": true,
"total": 800,
"returned": 800,
"truncated": true
}
}
}
```
@@ -83,14 +84,19 @@ Gibt Detail-Informationen für einen einzelnen Job zurück.
## GET /api/history/database
Gibt alle rohen Datenbankzeilen zurück (Debug-Ansicht).
Debug-Ansicht der DB-Zeilen (angereichert).
**Response:**
```json
{
"jobs": [ { "id": 1, "status": "FINISHED", ... } ],
"total": 15
"rows": [
{
"id": 42,
"status": "FINISHED",
"rawFolderName": "Inception - RAW - job-42"
}
]
}
```
@@ -98,18 +104,25 @@ Gibt alle rohen Datenbankzeilen zurück (Debug-Ansicht).
## GET /api/history/orphan-raw
Findet Raw-Ordner, die nicht als Jobs in der Datenbank registriert sind.
Sucht RAW-Ordner ohne zugehörigen Job.
**Response:**
```json
{
"orphans": [
"rawDir": "/mnt/raw",
"rawDirs": ["/mnt/raw", "/mnt/raw-bluray"],
"rows": [
{
"path": "/mnt/nas/raw/UnknownMovie_2023-12-01",
"size": "45.2 GB",
"modifiedAt": "2023-12-01T15:00:00.000Z",
"files": ["t00.mkv", "t01.mkv"]
"rawPath": "/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99",
"folderName": "Inception (2010) [tt1375666] - RAW - job-99",
"title": "Inception",
"year": 2010,
"imdbId": "tt1375666",
"folderJobId": 99,
"entryCount": 4,
"hasBlurayStructure": true,
"lastModifiedAt": "2026-03-10T09:00:00.000Z"
}
]
}
@@ -119,35 +132,28 @@ Findet Raw-Ordner, die nicht als Jobs in der Datenbank registriert sind.
## POST /api/history/orphan-raw/import
Importiert einen Orphan-Raw-Ordner als Job in die Datenbank.
Importiert RAW-Ordner als FINISHED-Job.
**Request:**
```json
{
"path": "/mnt/nas/raw/UnknownMovie_2023-12-01"
}
{ "rawPath": "/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99" }
```
**Response:**
```json
{
"ok": true,
"jobId": 99,
"message": "Orphan-Ordner als Job importiert"
"job": { "id": 77, "status": "FINISHED" },
"uiReset": { "reset": true, "state": "IDLE" }
}
```
Nach dem Import kann dem Job über `/api/history/:id/omdb/assign` Metadaten zugewiesen werden.
---
## POST /api/history/:id/omdb/assign
Weist einem bestehenden Job OMDb-Metadaten nachträglich zu.
**URL-Parameter:** `id` Job-ID
Weist OMDb-/Metadaten nachträglich zu.
**Request:**
@@ -155,44 +161,42 @@ Weist einem bestehenden Job OMDb-Metadaten nachträglich zu.
{
"imdbId": "tt1375666",
"title": "Inception",
"year": "2010",
"type": "movie",
"poster": "https://..."
"year": 2010,
"poster": "https://...",
"fromOmdb": true
}
```
**Response:**
```json
{ "ok": true }
{ "job": { "id": 42, "imdb_id": "tt1375666" } }
```
---
## POST /api/history/:id/delete-files
Löscht die Dateien eines Jobs (Raw und/oder Output), behält den Job-Eintrag.
**URL-Parameter:** `id` Job-ID
Löscht Dateien eines Jobs, behält DB-Eintrag.
**Request:**
```json
{
"deleteRaw": true,
"deleteOutput": false
}
{ "target": "both" }
```
`target`: `raw` | `movie` | `both`
**Response:**
```json
{
"ok": true,
"deleted": {
"raw": "/mnt/nas/raw/Inception_t00.mkv",
"output": null
}
"summary": {
"target": "both",
"raw": { "attempted": true, "deleted": true, "filesDeleted": 12, "dirsRemoved": 3, "reason": null },
"movie": { "attempted": true, "deleted": false, "filesDeleted": 0, "dirsRemoved": 0, "reason": "Movie-Datei/Pfad existiert nicht." }
},
"job": { "id": 42 }
}
```
@@ -200,23 +204,38 @@ Löscht die Dateien eines Jobs (Raw und/oder Output), behält den Job-Eintrag.
## POST /api/history/:id/delete
Löscht den Job-Eintrag aus der Datenbank, optional auch die Dateien.
**URL-Parameter:** `id` Job-ID
Löscht Job aus DB; optional auch Dateien.
**Request:**
```json
{
"deleteFiles": true
}
{ "target": "none" }
```
`target`: `none` | `raw` | `movie` | `both`
**Response:**
```json
{ "ok": true, "message": "Job gelöscht" }
{
"deleted": true,
"jobId": 42,
"fileTarget": "both",
"fileSummary": {
"target": "both",
"raw": { "filesDeleted": 10 },
"movie": { "filesDeleted": 1 }
},
"uiReset": {
"reset": true,
"state": "IDLE"
}
}
```
!!! warning "Unwiderruflich"
Das Löschen von Jobs und Dateien ist nicht rückgängig zu machen.
---
## Hinweise
- Ein aktiver Pipeline-Job kann nicht gelöscht werden (`409`).
- Alle Löschoperationen sind irreversibel.

View File

@@ -1,16 +1,21 @@
# API-Referenz
Ripster bietet eine **REST-API** für alle Operationen sowie einen **WebSocket-Endpunkt** für Echtzeit-Updates.
Ripster bietet eine REST-API für Steuerung/Verwaltung sowie einen WebSocket-Endpunkt für Echtzeit-Updates.
---
## Basis-URL
```
```text
http://localhost:3001
```
Konfigurierbar über die Umgebungsvariable `PORT`.
API-Prefix: `/api`
Beispiele:
- `GET /api/health`
- `GET /api/pipeline/state`
---
@@ -18,11 +23,19 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
<div class="grid cards" markdown>
- :material-heart-pulse: **Health**
---
Service-Liveness.
`GET /api/health`
- :material-pipe: **Pipeline API**
---
Pipeline-Steuerung: Analyse starten, Metadaten setzen, Ripping und Encoding steuern.
Analyse, Start/Retry/Cancel, Queue, Re-Encode.
[:octicons-arrow-right-24: Pipeline API](pipeline.md)
@@ -30,7 +43,7 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
---
Einstellungen lesen und schreiben.
Einstellungen, Skripte/Ketten, User-Presets.
[:octicons-arrow-right-24: Settings API](settings.md)
@@ -38,7 +51,7 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
---
Job-Geschichte abfragen, Jobs löschen, Orphan-Ordner importieren.
Job-Historie, Orphan-Import, Löschoperationen.
[:octicons-arrow-right-24: History API](history.md)
@@ -46,7 +59,7 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
---
Cron-Jobs verwalten, manuell auslösen und Ausführungs-Logs abrufen.
Zeitgesteuerte Skript-/Kettenausführung.
[:octicons-arrow-right-24: Cron API](crons.md)
@@ -54,7 +67,7 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
---
Echtzeit-Events für Pipeline-Status, Fortschritt und Disc-Erkennung.
Pipeline-, Queue-, Disk-, Settings-, Cron- und Monitoring-Events.
[:octicons-arrow-right-24: WebSocket](websocket.md)
@@ -64,30 +77,41 @@ Konfigurierbar über die Umgebungsvariable `PORT`.
## Authentifizierung
Die API hat **keine Authentifizierung**. Sie ist für den Einsatz im lokalen Netzwerk konzipiert.
!!! warning "Produktionsbetrieb"
Falls Ripster öffentlich erreichbar sein soll, schütze die API mit einem Reverse-Proxy (z. B. nginx mit Basic Auth oder OAuth).
Es gibt keine eingebaute Authentifizierung. Ripster ist für lokalen Betrieb gedacht.
---
## Fehlerformat
Alle API-Fehler werden im folgenden Format zurückgegeben:
Fehler werden zentral als JSON geliefert:
```json
{
"error": "Job nicht gefunden",
"details": "Kein Job mit ID 999 vorhanden"
"error": {
"message": "Job nicht gefunden.",
"statusCode": 404,
"reqId": "req_...",
"details": [
{
"field": "name",
"message": "Name darf nicht leer sein."
}
]
}
}
```
HTTP-Statuscodes:
`details` ist optional (z. B. bei Validierungsfehlern).
---
## Häufige Statuscodes
| Code | Bedeutung |
|-----|-----------|
|------|-----------|
| `200` | Erfolg |
| `400` | Ungültige Anfrage |
| `201` | Ressource erstellt |
| `400` | Ungültige Anfrage / Validierungsfehler |
| `404` | Ressource nicht gefunden |
| `409` | Konflikt (z.B. Pipeline bereits aktiv) |
| `500` | Interner Serverfehler |
| `409` | Konflikt (z. B. falscher Pipeline-Zustand, Job läuft bereits) |
| `500` | Interner Fehler |

View File

@@ -1,14 +1,14 @@
# Pipeline API
Alle Endpunkte zur Steuerung des Ripster-Workflows.
Endpunkte zur Steuerung des Pipeline-Workflows.
---
## GET /api/pipeline/state
Liefert den aktuellen Pipeline-Snapshot.
Liefert aktuellen Pipeline- und Hardware-Monitoring-Snapshot.
**Response:**
**Response (Beispiel):**
```json
{
@@ -17,45 +17,46 @@ Liefert den aktuellen Pipeline-Snapshot.
"activeJobId": 42,
"progress": 0,
"eta": null,
"statusText": "Mediainfo geladen - bitte bestätigen",
"statusText": "Mediainfo bestätigt - Encode manuell starten",
"context": {
"jobId": 42
},
"jobProgress": {
"42": {
"state": "MEDIAINFO_CHECK",
"progress": 68.5,
"eta": null,
"statusText": "MEDIAINFO_CHECK 68.50%"
}
},
"queue": {
"maxParallelJobs": 1,
"runningCount": 0,
"queuedCount": 0,
"runningCount": 1,
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
},
"hardwareMonitoring": {
"enabled": true,
"intervalMs": 5000,
"updatedAt": "2026-03-10T09:00:00.000Z",
"sample": {
"cpu": {},
"memory": {},
"gpu": {},
"storage": {}
},
"error": null
}
}
```
**Pipeline-Zustände:**
| Wert | Beschreibung |
|------|-------------|
| `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 |
---
## POST /api/pipeline/analyze
Startet die Analyse für die aktuell erkannte Disc.
**Request:** kein Body
Startet Disc-Analyse und legt Job an.
**Response:**
@@ -73,14 +74,21 @@ Startet die Analyse für die aktuell erkannte Disc.
## POST /api/pipeline/rescan-disc
Erzwingt eine erneute Laufwerksprüfung.
Erzwingt erneute Laufwerksprüfung.
**Response (Beispiel):**
```json
{
"result": {
"emitted": "discInserted"
"present": true,
"changed": true,
"emitted": "discInserted",
"device": {
"path": "/dev/sr0",
"discLabel": "INCEPTION",
"mediaProfile": "bluray"
}
}
}
```
@@ -89,7 +97,7 @@ Erzwingt eine erneute Laufwerksprüfung.
## GET /api/pipeline/omdb/search?q=<query>
Sucht OMDb-Titel.
OMDb-Titelsuche.
**Response:**
@@ -111,7 +119,7 @@ Sucht OMDb-Titel.
## POST /api/pipeline/select-metadata
Setzt Metadaten (und optional Playlist-Entscheidung).
Setzt Metadaten (und optional Playlist) für einen Job.
**Request:**
@@ -127,38 +135,35 @@ Setzt Metadaten (und optional Playlist-Entscheidung).
}
```
**Response:** `{ "job": { ... } }`
**Response:**
!!! 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.
```json
{ "job": { "id": 42, "status": "READY_TO_START" } }
```
---
## POST /api/pipeline/start/:jobId
Startet einen vorbereiteten Job manuell (z. B. Fallback/Queue-Szenario).
Startet vorbereiteten Job oder queued ihn (je nach Parallel-Limit).
**Response (Beispiel):**
**Mögliche Responses:**
```json
{
"result": {
"started": true,
"stage": "RIPPING"
}
}
{ "result": { "started": true, "stage": "RIPPING" } }
```
Mögliche `stage`-Werte sind u. a. `RIPPING`, `MEDIAINFO_CHECK`, `ENCODING`.
```json
{ "result": { "queued": true, "started": false, "queuePosition": 2, "action": "START_PREPARED" } }
```
---
## POST /api/pipeline/confirm-encode/:jobId
Bestätigt Review-Auswahl (Titel/Tracks/Post-Skripte).
Bestätigt Review-Auswahl (Tracks, Pre/Post-Skripte/Ketten, User-Preset).
**Request:**
**Request (typisch):**
```json
{
@@ -169,78 +174,70 @@ Bestätigt Review-Auswahl (Titel/Tracks/Post-Skripte).
"subtitleTrackIds": [3]
}
},
"selectedPreEncodeScriptIds": [1],
"selectedPostEncodeScriptIds": [2, 7],
"selectedPreEncodeChainIds": [3],
"selectedPostEncodeChainIds": [4],
"selectedUserPresetId": 5,
"skipPipelineStateUpdate": false
}
```
**Response:** `{ "job": { ... } }`
**Response:**
```json
{ "job": { "id": 42, "encode_review_confirmed": 1 } }
```
---
## POST /api/pipeline/cancel
Bricht laufenden Job ab oder entfernt einen Queue-Eintrag.
Bricht laufenden Job ab oder entfernt Queue-Eintrag.
**Request (optional):**
```json
{
"jobId": 42
}
{ "jobId": 42 }
```
**Response (Beispiel):**
**Mögliche Responses:**
```json
{
"result": {
"cancelled": true,
"queuedOnly": false,
"jobId": 42
}
}
{ "result": { "cancelled": true, "queuedOnly": true, "jobId": 42 } }
```
```json
{ "result": { "cancelled": true, "queuedOnly": false, "jobId": 42 } }
```
```json
{ "result": { "cancelled": true, "queuedOnly": false, "pending": true, "jobId": 42 } }
```
---
## POST /api/pipeline/retry/:jobId
Startet einen Job aus `ERROR`/`CANCELLED` erneut (oder reiht ihn in die Queue ein).
**Response:** `{ "result": { ... } }`
---
## POST /api/pipeline/resume-ready/:jobId
Lädt einen `READY_TO_ENCODE`-Job nach Neustart wieder in die aktive Session.
**Response:** `{ "job": { ... } }`
---
Retry für `ERROR`/`CANCELLED`-Jobs (oder Queue-Einreihung).
## POST /api/pipeline/reencode/:jobId
Startet Re-Encode aus bestehendem RAW.
**Response:** `{ "result": { ... } }`
---
## POST /api/pipeline/restart-review/:jobId
Berechnet die Review aus vorhandenem RAW neu.
**Response:** `{ "result": { ... } }`
---
Berechnet Review aus RAW neu.
## POST /api/pipeline/restart-encode/:jobId
Startet Encoding mit der zuletzt bestätigten Auswahl neu.
Startet Encoding mit letzter bestätigter Review neu.
**Response:** `{ "result": { ... } }`
## POST /api/pipeline/resume-ready/:jobId
Lädt `READY_TO_ENCODE`-Job nach Neustart wieder in aktive Session.
Alle Endpunkte liefern `{ result: ... }` bzw. `{ job: ... }`.
---
@@ -248,9 +245,51 @@ Startet Encoding mit der zuletzt bestätigten Auswahl neu.
### GET /api/pipeline/queue
Liefert den aktuellen Queue-Status.
Liefert Queue-Snapshot.
**Response:** `{ "queue": { ... } }`
```json
{
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 3,
"runningJobs": [
{
"jobId": 41,
"title": "Inception",
"status": "ENCODING",
"lastState": "ENCODING"
}
],
"queuedJobs": [
{
"entryId": 11,
"position": 1,
"type": "job",
"jobId": 42,
"action": "START_PREPARED",
"actionLabel": "Start",
"title": "Matrix",
"status": "READY_TO_ENCODE",
"lastState": "READY_TO_ENCODE",
"hasScripts": true,
"hasChains": false,
"enqueuedAt": "2026-03-10T09:00:00.000Z"
},
{
"entryId": 12,
"position": 2,
"type": "wait",
"waitSeconds": 30,
"title": "Warten 30s",
"status": "QUEUED",
"enqueuedAt": "2026-03-10T09:01:00.000Z"
}
],
"updatedAt": "2026-03-10T09:01:02.000Z"
}
}
```
### POST /api/pipeline/queue/reorder
@@ -260,8 +299,71 @@ Sortiert Queue-Einträge neu.
```json
{
"orderedJobIds": [42, 43, 41]
"orderedEntryIds": [12, 11]
}
```
**Response:** `{ "queue": { ... } }`
Legacy fallback wird akzeptiert:
```json
{
"orderedJobIds": [42, 43]
}
```
### POST /api/pipeline/queue/entry
Fügt Nicht-Job-Queue-Eintrag hinzu (`script`, `chain`, `wait`).
**Request-Beispiele:**
```json
{ "type": "script", "scriptId": 3 }
```
```json
{ "type": "chain", "chainId": 2, "insertAfterEntryId": 11 }
```
```json
{ "type": "wait", "waitSeconds": 45 }
```
**Response:**
```json
{
"result": { "entryId": 12, "type": "wait", "position": 2 },
"queue": { "...": "..." }
}
```
### DELETE /api/pipeline/queue/entry/:entryId
Entfernt Queue-Eintrag.
**Response:**
```json
{ "queue": { "...": "..." } }
```
---
## Pipeline-Zustände
| State | Bedeutung |
|------|-----------|
| `IDLE` | Wartet auf Medium |
| `DISC_DETECTED` | Medium erkannt |
| `ANALYZING` | MakeMKV-Analyse läuft |
| `METADATA_SELECTION` | Metadaten-Auswahl |
| `WAITING_FOR_USER_DECISION` | Playlist-Entscheidung erforderlich |
| `READY_TO_START` | Übergang vor Start |
| `RIPPING` | MakeMKV-Rip läuft |
| `MEDIAINFO_CHECK` | Titel-/Track-Auswertung |
| `READY_TO_ENCODE` | Review bereit |
| `ENCODING` | HandBrake-Encoding läuft |
| `FINISHED` | Abgeschlossen |
| `CANCELLED` | Abgebrochen |
| `ERROR` | Fehler |

View File

@@ -1,38 +1,36 @@
# Settings API
Endpunkte zum Lesen und Schreiben der Anwendungseinstellungen.
Endpunkte für Einstellungen, Skripte, Skript-Ketten und User-Presets.
---
## GET /api/settings
Gibt alle Einstellungen kategorisiert zurück.
Liefert alle Einstellungen kategorisiert.
**Response:**
**Response (Struktur):**
```json
{
"paths": {
"raw_dir": {
"value": "/mnt/nas/raw",
"schema": {
"type": "string",
"label": "Raw-Verzeichnis",
"description": "Speicherort für rohe MKV-Dateien",
"required": true
}
},
"movie_dir": {
"value": "/mnt/nas/movies",
"schema": { ... }
"categories": [
{
"category": "Pfade",
"settings": [
{
"key": "raw_dir",
"label": "Raw Ausgabeordner",
"type": "path",
"required": true,
"description": "...",
"defaultValue": "data/output/raw",
"options": [],
"validation": { "minLength": 1 },
"value": "data/output/raw",
"orderIndex": 100
}
]
}
},
"tools": { ... },
"encoding": { ... },
"drive": { ... },
"makemkv": { ... },
"omdb": { ... },
"notifications": { ... }
]
}
```
@@ -42,42 +40,44 @@ Gibt alle Einstellungen kategorisiert zurück.
Aktualisiert eine einzelne Einstellung.
**URL-Parameter:** `key` Einstellungs-Schlüssel
**Request:**
```json
{
"value": "/mnt/storage/raw"
}
{ "value": "/mnt/storage/raw" }
```
**Response:**
```json
{ "ok": true, "key": "raw_dir", "value": "/mnt/storage/raw" }
{
"setting": {
"key": "raw_dir",
"value": "/mnt/storage/raw"
},
"reviewRefresh": {
"triggered": false,
"reason": "not_ready"
}
}
```
**Fehlerfälle:**
- `400` Ungültiger Wert (Validierungsfehler)
- `404` Einstellung nicht gefunden
!!! note "Encode-Review-Refresh"
Wenn eine encoding-relevante Einstellung geändert wird (z.B. `handbrake_preset`), wird der Encode-Plan für den aktuell wartenden Job automatisch neu berechnet.
`reviewRefresh` ist `null` oder ein Objekt mit Status der optionalen Review-Neuberechnung.
---
## PUT /api/settings
Aktualisiert mehrere Einstellungen auf einmal.
Aktualisiert mehrere Einstellungen atomar.
**Request:**
```json
{
"raw_dir": "/mnt/storage/raw",
"movie_dir": "/mnt/storage/movies",
"handbrake_preset": "H.265 MKV 720p30"
"settings": {
"raw_dir": "/mnt/storage/raw",
"movie_dir": "/mnt/storage/movies",
"handbrake_preset_bluray": "H.264 MKV 1080p30"
}
}
```
@@ -85,9 +85,36 @@ Aktualisiert mehrere Einstellungen auf einmal.
```json
{
"ok": true,
"updated": ["raw_dir", "movie_dir", "handbrake_preset"],
"errors": []
"changes": [
{ "key": "raw_dir", "value": "/mnt/storage/raw" },
{ "key": "movie_dir", "value": "/mnt/storage/movies" }
],
"reviewRefresh": {
"triggered": true,
"jobId": 42,
"relevantKeys": ["handbrake_preset_bluray"]
}
}
```
Bei Validierungsfehlern kommt `400` mit `error.details[]`.
---
## GET /api/settings/handbrake-presets
Liest Preset-Liste via `HandBrakeCLI -z` (mit Fallback auf konfigurierte Presets).
**Response (Beispiel):**
```json
{
"source": "handbrake-cli",
"message": null,
"options": [
{ "label": "General/", "value": "__group__general", "disabled": true, "category": "General" },
{ "label": " Fast 1080p30", "value": "Fast 1080p30", "category": "General" }
]
}
```
@@ -95,260 +122,217 @@ Aktualisiert mehrere Einstellungen auf einmal.
## POST /api/settings/pushover/test
Sendet eine Test-Benachrichtigung über PushOver.
Sendet Testnachricht über aktuelle PushOver-Settings.
**Request:** Kein Body erforderlich (verwendet gespeicherte Zugangsdaten)
**Response (Erfolg):**
```json
{ "ok": true, "message": "Test-Benachrichtigung gesendet" }
```
**Response (Fehler):**
```json
{ "ok": false, "error": "Ungültiger API-Token" }
```
---
## Skript-Verwaltung
Skripte werden über eigene Endpunkte unter `/api/settings/scripts` verwaltet. Jedes Skript hat eine `scriptBody`-Property (der Shell-Befehl oder mehrzeiliges Skript) und einen `orderIndex` für die Sortierung.
### GET /api/settings/scripts
Gibt alle Skripte zurück, sortiert nach `orderIndex`.
**Response:**
**Request (optional):**
```json
{
"scripts": [
{
"id": 1,
"name": "Zu Plex verschieben",
"scriptBody": "mv \"$RIPSTER_OUTPUT_PATH\" /mnt/plex/movies/",
"orderIndex": 1,
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z"
}
]
"title": "Test",
"message": "Ripster Test"
}
```
---
### POST /api/settings/scripts
Legt ein neues Skript an.
**Request:**
```json
{
"name": "Zu Plex verschieben",
"scriptBody": "mv \"$RIPSTER_OUTPUT_PATH\" /mnt/plex/movies/"
}
```
| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|-------------|
| `name` | string | ✅ | Anzeigename (eindeutig) |
| `scriptBody` | string | ✅ | Shell-Befehl oder mehrzeiliges Skript |
**Response:** `201 Created` `{ "script": { ... } }`
---
### PUT /api/settings/scripts/:id
Aktualisiert ein vorhandenes Skript. Alle Felder optional.
---
### DELETE /api/settings/scripts/:id
Löscht ein Skript.
!!! warning "Referenzen"
Das Skript wird gelöscht, auch wenn es in Job-Historien referenziert ist. In zukünftigen Reviews erscheint es nicht mehr.
---
### POST /api/settings/scripts/:id/test
Führt ein Skript mit Platzhalter-Umgebungsvariablen aus (Testlauf).
**Response:**
```json
{
"ok": true,
"exitCode": 0,
"stdout": "Testausgabe des Skripts",
"stderr": "",
"durationMs": 245
}
```
**Platzhalter-Werte beim Testlauf:**
| Variable | Testwert |
|---------|---------|
| `RIPSTER_OUTPUT_PATH` | `/tmp/ripster-test-output.mkv` |
| `RIPSTER_JOB_ID` | `0` |
| `RIPSTER_TITLE` | `Test Film` |
| `RIPSTER_YEAR` | `2024` |
| `RIPSTER_IMDB_ID` | `tt0000000` |
| `RIPSTER_RAW_PATH` | `/tmp/ripster-test-raw.mkv` |
---
### POST /api/settings/scripts/reorder
Ändert die Reihenfolge der Skripte (persistiert in `order_index`).
**Request:**
```json
{ "orderedScriptIds": [3, 1, 2] }
```
**Response:** `{ "scripts": [ ... ] }` alle Skripte in neuer Reihenfolge.
---
## Skript-Ketten-Verwaltung
Skript-Ketten werden unter `/api/settings/script-chains` verwaltet.
### GET /api/settings/script-chains
Gibt alle Ketten zurück (inkl. Schritte).
### POST /api/settings/script-chains
Legt eine neue Kette an.
```json
{ "name": "Nach Jellyfin deployen" }
```
### PUT /api/settings/script-chains/:id
Aktualisiert eine Kette (Name, Schritte).
### DELETE /api/settings/script-chains/:id
Löscht eine Kette und alle ihre Schritte.
### POST /api/settings/script-chains/:id/test
Führt eine Kette mit Platzhalter-Umgebungsvariablen aus (Testlauf).
**Response:**
```json
{
"result": {
"success": true,
"steps": [
{ "scriptId": 1, "scriptName": "Zu Plex verschieben", "success": true, "exitCode": 0 }
]
"sent": true,
"eventKey": "test",
"requestId": "..."
}
}
```
Wenn PushOver deaktiviert ist oder Credentials fehlen, kommt i. d. R. ebenfalls `200` mit `sent: false` + `reason`.
---
## Skripte
Basis: `/api/settings/scripts`
### GET /api/settings/scripts
```json
{ "scripts": [ { "id": 1, "name": "...", "scriptBody": "...", "orderIndex": 1, "createdAt": "...", "updatedAt": "..." } ] }
```
### POST /api/settings/scripts
```json
{ "name": "Move", "scriptBody": "mv \"$RIPSTER_OUTPUT_PATH\" /mnt/movies/" }
```
Response: `201` mit `{ "script": { ... } }`
### PUT /api/settings/scripts/:id
Body wie `POST`, Response `{ "script": { ... } }`.
### DELETE /api/settings/scripts/:id
Response `{ "removed": { ... } }`.
### POST /api/settings/scripts/reorder
```json
{ "orderedScriptIds": [3, 1, 2] }
```
Response `{ "scripts": [ ... ] }`.
### POST /api/settings/scripts/:id/test
Führt Skript als Testlauf aus.
```json
{
"result": {
"scriptId": 1,
"scriptName": "Move",
"success": true,
"exitCode": 0,
"signal": null,
"timedOut": false,
"durationMs": 120,
"stdout": "...",
"stderr": "...",
"stdoutTruncated": false,
"stderrTruncated": false
}
}
```
### Umgebungsvariablen für Skripte
Diese Variablen werden beim Ausführen gesetzt:
- `RIPSTER_SCRIPT_RUN_AT`
- `RIPSTER_JOB_ID`
- `RIPSTER_JOB_TITLE`
- `RIPSTER_MODE`
- `RIPSTER_INPUT_PATH`
- `RIPSTER_OUTPUT_PATH`
- `RIPSTER_RAW_PATH`
- `RIPSTER_SCRIPT_ID`
- `RIPSTER_SCRIPT_NAME`
- `RIPSTER_SCRIPT_SOURCE`
---
## Skript-Ketten
Basis: `/api/settings/script-chains`
Eine Kette hat Schritte vom Typ:
- `script` (`scriptId` erforderlich)
- `wait` (`waitSeconds` 1..3600)
### GET /api/settings/script-chains
Response `{ "chains": [ ... ] }` (inkl. `steps[]`).
### GET /api/settings/script-chains/:id
Response `{ "chain": { ... } }`.
### POST /api/settings/script-chains
```json
{
"name": "After Encode",
"steps": [
{ "stepType": "script", "scriptId": 1 },
{ "stepType": "wait", "waitSeconds": 15 },
{ "stepType": "script", "scriptId": 2 }
]
}
```
Response: `201` mit `{ "chain": { ... } }`
### PUT /api/settings/script-chains/:id
Body wie `POST`, Response `{ "chain": { ... } }`.
### DELETE /api/settings/script-chains/:id
Response `{ "removed": { ... } }`.
### POST /api/settings/script-chains/reorder
Ändert die Reihenfolge der Ketten (persistiert in `order_index`).
**Request:**
```json
{ "orderedChainIds": [2, 1, 3] }
```
Response `{ "chains": [ ... ] }`.
### POST /api/settings/script-chains/:id/test
Response:
```json
{
"result": {
"chainId": 2,
"chainName": "After Encode",
"steps": 3,
"succeeded": 3,
"failed": 0,
"aborted": false,
"results": []
}
}
```
---
## User-Presets
Benannte HandBrake-Preset-Sammlungen, die im Encode-Review schnell angewendet werden können. Unter `/api/settings/user-presets` verwaltet.
Basis: `/api/settings/user-presets`
### GET /api/settings/user-presets
Gibt alle User-Presets zurück. Optional gefiltert per Query-Parameter `mediaType`.
**Query-Parameter:**
| Parameter | Werte | Beschreibung |
|-----------|-------|-------------|
| `mediaType` | `bluray`, `dvd`, `other`, `all` | Filtert Presets nach Medientyp |
**Response:**
Optionaler Query-Parameter: `media_type=bluray|dvd|other|all`
```json
{
"presets": [
{
"id": 1,
"name": "Blu-ray High Quality",
"name": "Blu-ray HQ",
"mediaType": "bluray",
"handbrakePreset": "H.265 MKV 1080p30",
"handbrakePreset": "H.264 MKV 1080p30",
"extraArgs": "--encoder-preset slow",
"description": "Langsam, aber beste Qualität",
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z"
"description": "...",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
---
### POST /api/settings/user-presets
Legt ein neues User-Preset an.
**Request:**
```json
{
"name": "Blu-ray High Quality",
"name": "Blu-ray HQ",
"mediaType": "bluray",
"handbrakePreset": "H.265 MKV 1080p30",
"handbrakePreset": "H.264 MKV 1080p30",
"extraArgs": "--encoder-preset slow",
"description": "Langsam, aber beste Qualität"
"description": "optional"
}
```
| Feld | Typ | Pflicht | Beschreibung |
|------|-----|---------|-------------|
| `name` | string | ✅ | Anzeigename |
| `mediaType` | string | — | `bluray`, `dvd`, `other`, `all` (Standard: `all`) |
| `handbrakePreset` | string | — | HandBrake-Preset-Name (`-Z`) |
| `extraArgs` | string | — | Zusatz-CLI-Argumente |
| `description` | string | — | Optionale Beschreibung |
**Response:** `201 Created` `{ "preset": { ... } }`
---
Response: `201` mit `{ "preset": { ... } }`
### PUT /api/settings/user-presets/:id
Aktualisiert ein User-Preset. Alle Felder optional.
---
Body mit beliebigen Feldern aus `POST`, Response `{ "preset": { ... } }`.
### DELETE /api/settings/user-presets/:id
Löscht ein User-Preset.
---
## Einstellungs-Schlüssel Referenz
Eine vollständige Übersicht aller Schlüssel:
[:octicons-arrow-right-24: Einstellungsreferenz](../configuration/settings-reference.md)
Response `{ "removed": { ... } }`.

View File

@@ -1,6 +1,6 @@
# WebSocket Events
Ripster sendet Echtzeit-Updates über WebSocket unter `/ws`.
Ripster sendet Echtzeit-Updates über `/ws`.
---
@@ -10,8 +10,8 @@ Ripster sendet Echtzeit-Updates über WebSocket unter `/ws`.
const ws = new WebSocket('ws://localhost:3001/ws');
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(message.type, message.payload);
const msg = JSON.parse(event.data);
console.log(msg.type, msg.payload);
};
```
@@ -19,36 +19,38 @@ ws.onmessage = (event) => {
## Nachrichtenformat
Alle Broadcasts haben dieses Schema:
Die meisten Broadcasts haben dieses Schema:
```json
{
"type": "EVENT_TYPE",
"payload": { },
"timestamp": "2026-03-05T10:00:00.000Z"
"payload": {},
"timestamp": "2026-03-10T09:00:00.000Z"
}
```
Ausnahme: `WS_CONNECTED` beim Verbindungsaufbau enthält kein `timestamp`.
---
## Event-Typen
### WS_CONNECTED
Wird direkt nach Verbindungsaufbau gesendet.
Sofort nach erfolgreicher Verbindung.
```json
{
"type": "WS_CONNECTED",
"payload": {
"connectedAt": "2026-03-05T10:00:00.000Z"
"connectedAt": "2026-03-10T09:00:00.000Z"
}
}
```
### PIPELINE_STATE_CHANGED
Snapshot bei Zustandswechsel.
Neuer Pipeline-Snapshot.
```json
{
@@ -56,14 +58,24 @@ Snapshot bei Zustandswechsel.
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5,
"progress": 62.5,
"eta": "00:12:34",
"statusText": "Encoding mit HandBrake",
"statusText": "ENCODING 62.50%",
"context": {},
"jobProgress": {
"42": {
"state": "ENCODING",
"progress": 62.5,
"eta": "00:12:34",
"statusText": "ENCODING 62.50%"
}
},
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 0
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
}
}
@@ -71,7 +83,7 @@ Snapshot bei Zustandswechsel.
### PIPELINE_PROGRESS
Laufende Fortschrittsupdates während aktiver Phasen.
Laufende Fortschrittsupdates.
```json
{
@@ -79,33 +91,20 @@ Laufende Fortschrittsupdates während aktiver Phasen.
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5,
"progress": 62.5,
"eta": "00:12:34",
"statusText": "ENCODING 73.50% - task 1 of 1"
"statusText": "ENCODING 62.50%"
}
}
```
### PIPELINE_QUEUE_CHANGED
Aktualisierung der Job-Queue.
Queue-Snapshot aktualisiert.
```json
{
"type": "PIPELINE_QUEUE_CHANGED",
"payload": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
}
```
### DISC_DETECTED / DISC_REMOVED
### DISC_DETECTED
Disc erkannt.
Disc-Insertion/-Removal.
```json
{
@@ -114,7 +113,6 @@ Disc erkannt.
"device": {
"path": "/dev/sr0",
"discLabel": "INCEPTION",
"label": "INCEPTION",
"model": "ASUS BW-16D1HT",
"fstype": "udf",
"mountpoint": null,
@@ -124,132 +122,93 @@ Disc erkannt.
}
```
`mediaProfile` ist `"bluray"`, `"dvd"`, `"other"` oder `null` (wenn nicht bestimmbar). Der Wert wird aus Dateisystemtyp (UDF/ISO9660), Laufwerk-Modell und Disc-Label abgeleitet.
`mediaProfile`: `bluray` | `dvd` | `other` | `null`
### DISC_REMOVED
### HARDWARE_MONITOR_UPDATE
Disc entfernt.
Snapshot aus Hardware-Monitoring.
```json
{
"type": "DISC_REMOVED",
"type": "HARDWARE_MONITOR_UPDATE",
"payload": {
"device": {
"path": "/dev/sr0"
}
"enabled": true,
"intervalMs": 5000,
"updatedAt": "2026-03-10T09:00:00.000Z",
"sample": {
"cpu": {},
"memory": {},
"gpu": {},
"storage": {}
},
"error": null
}
}
```
### PIPELINE_ERROR
Fehler bei Pipeline-Disc-Events im Backend.
```json
{
"type": "PIPELINE_ERROR",
"payload": {
"message": "..."
}
}
```
Fehler bei Disc-Event-Verarbeitung in Pipeline.
### DISK_DETECTION_ERROR
Fehler im Laufwerkserkennungsdienst.
Fehler in Laufwerkserkennung.
### SETTINGS_UPDATED
Einzelnes Setting wurde gespeichert.
### SETTINGS_BULK_UPDATED
Bulk-Settings gespeichert.
```json
{
"type": "DISK_DETECTION_ERROR",
"type": "SETTINGS_BULK_UPDATED",
"payload": {
"message": "..."
"count": 3,
"keys": ["raw_dir", "movie_dir", "handbrake_preset_bluray"]
}
}
```
### SETTINGS_SCRIPTS_UPDATED
Wird gesendet, wenn ein Skript angelegt, aktualisiert, gelöscht oder umsortiert wurde.
```json
{
"type": "SETTINGS_SCRIPTS_UPDATED",
"payload": {
"action": "reordered",
"count": 3
}
}
```
`action` ist `"created"`, `"updated"`, `"deleted"` oder `"reordered"`.
Skript geändert (`created|updated|deleted|reordered`).
### SETTINGS_SCRIPT_CHAINS_UPDATED
Wird gesendet bei Änderungen an Skript-Ketten.
```json
{
"type": "SETTINGS_SCRIPT_CHAINS_UPDATED",
"payload": {
"action": "created",
"id": 2
}
}
```
Skript-Kette geändert (`created|updated|deleted|reordered`).
### USER_PRESETS_UPDATED
Wird gesendet, wenn ein User-Preset angelegt, aktualisiert oder gelöscht wurde.
```json
{
"type": "USER_PRESETS_UPDATED",
"payload": {
"action": "created",
"id": 1
}
}
```
`action` ist `"created"`, `"updated"` oder `"deleted"`.
User-Preset geändert (`created|updated|deleted`).
### CRON_JOBS_UPDATED
Wird gesendet, wenn ein Cron-Job angelegt, aktualisiert oder gelöscht wurde.
Cron-Config geändert (`created|updated|deleted`).
### CRON_JOB_UPDATED
Laufzeitstatus eines Cron-Jobs geändert.
```json
{
"type": "CRON_JOBS_UPDATED",
"type": "CRON_JOB_UPDATED",
"payload": {
"action": "created",
"id": 1
"id": 1,
"lastRunStatus": "running",
"lastRunAt": "2026-03-10T10:00:00.000Z",
"nextRunAt": null
}
}
```
`action` ist `"created"`, `"updated"` oder `"deleted"`.
---
## Reconnect-Verhalten
`useWebSocket.js` versucht bei Verbindungsabbruch automatisch erneut zu verbinden.
`useWebSocket` verbindet bei Abbruch automatisch neu:
- fester Retry-Intervall: `1500ms`
- erneuter Versuch bis zum Unmount der Komponente
---
## React-Beispiel
```js
import { useWebSocket } from './hooks/useWebSocket';
useWebSocket({
onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGED') {
setPipeline(msg.payload);
}
}
});
```
- Retry-Intervall: `1500ms`
- Wiederverbindung bis Komponente unmounted wird

View File

@@ -1,305 +1,116 @@
# Backend-Services
Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, die jeweils eine klar abgegrenzte Verantwortlichkeit haben.
Das Backend ist in Services aufgeteilt, die von Express-Routen orchestriert werden.
---
## pipelineService.js
## `pipelineService.js`
**Der Kern von Ripster** orchestriert den gesamten Ripping-Workflow.
Zentrale Workflow-Orchestrierung.
### Zuständigkeiten
Aufgaben:
- Verwaltung des Pipeline-Zustands als State Machine
- Koordination zwischen allen externen Tools
- Generierung von Encode-Plänen
- Fehlerbehandlung und Recovery
- Pipeline-State-Machine + Persistenz (`pipeline_state`)
- Disc-Analyse/Rip/Review/Encode
- Queue-Management (Jobs + `script|chain|wait` Einträge)
- Retry/Re-Encode/Restart-Flows
- WebSocket-Broadcasts für State/Progress/Queue
### Haupt-Methoden
Wichtige Methoden:
| Methode | Beschreibung |
|---------|-------------|
| `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
<div class="pipeline-diagram">
```mermaid
flowchart LR
START(( )) --> IDLE
IDLE -->|analyzeDisc()| META[METADATA\nSELECTION]
META -->|selectMetadata()| RTS[READY_TO\nSTART]
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 -->|confirmEncodeReview() + startPreparedJob()| ENC[ENCODING]
ENC -->|Pre-Encode → HandBrake → Post-Encode fertig| FIN([FINISHED])
ENC -->|Abbruch| CAN([CANCELLED])
ENC -->|Fehler| ERR([ERROR])
RIP -->|Fehler| ERR
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
style MIC fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
```
</div>
- `analyzeDisc()`
- `selectMetadata()`
- `startPreparedJob()`
- `confirmEncodeReview()`
- `cancel()`
- `retry()`
- `reencodeFromRaw()`
- `restartReviewFromRaw()`
- `restartEncodeWithLastSettings()`
- `resumeReadyToEncodeJob()`
- `enqueueNonJobEntry()`, `reorderQueue()`, `removeQueueEntry()`
---
## diskDetectionService.js
## `diskDetectionService.js`
Überwacht das Disc-Laufwerk auf Disc-Einleger- und Auswurf-Ereignisse.
Pollt Laufwerk(e) und emittiert:
### Modi
- `discInserted`
- `discRemoved`
- `error`
| Modus | Beschreibung |
|------|-------------|
| `auto` | Erkennt verfügbare Laufwerke automatisch |
| `explicit` | Überwacht ein bestimmtes Gerät (z.B. `/dev/sr0`) |
Zusatz:
### Polling
Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 4000ms) und emittiert Events:
```js
// Ereignisse
emit('discInserted', { path: '/dev/sr0', mediaProfile: 'bluray', ... })
emit('discRemoved', { path: '/dev/sr0' })
```
### Media-Profil-Erkennung
Das erkannte Gerät enthält ein `mediaProfile`-Feld (`"bluray"`, `"dvd"`, `"other"` oder `null`). Die Erkennung nutzt eine Heuristik aus drei Quellen (absteigend nach Priorität):
1. Explizit gesetztes `media_profile` aus den Settings
2. Disc-Label und Laufwerks-Modell (Regex gegen bekannte Begriffe)
3. Dateisystemtyp: `udf` → bevorzugt DVD, kombiniert mit Modell; `iso9660/cdfs` → DVD oder CD
- Modus `auto` oder `explicit`
- heuristische `mediaProfile`-Erkennung (`bluray`/`dvd`/`other`)
- `rescanAndEmit()` für manuellen Trigger
---
## processRunner.js
## `settingsService.js`
Verwaltet externe CLI-Prozesse.
Settings-Layer mit Validation/Serialisierung.
### Features
Features:
- **Streaming**: stdout/stderr werden zeilenweise gelesen
- **Progress-Callbacks**: Ermöglicht Echtzeit-Fortschrittsanzeige
- **Graceful Shutdown**: SIGINT → Warte-Timeout → SIGKILL
- **Prozess-Registry**: Verfolgt aktive Prozesse für sauberes Beenden
### Nutzung
```js
const result = await runProcess(
'HandBrakeCLI',
['--input', rawFile, '--output', outputFile, '--preset', preset],
{
onStderr: (line) => parseHandBrakeProgress(line),
onStdout: (line) => logger.debug(line)
}
);
```
- `getCategorizedSettings()` für UI-Form
- `setSettingValue()` / `setSettingsBulk()`
- profilspezifische Auflösung (`resolveEffectiveToolSettings`)
- CLI-Config-Building für MakeMKV/HandBrake/MediaInfo
- HandBrake-Preset-Liste via `HandBrakeCLI -z`
- MakeMKV-Registration-Command aus `makemkv_registration_key`
---
## websocketService.js
## `historyService.js`
WebSocket-Server für Echtzeit-Client-Kommunikation.
Historie + Dateioperationen.
### Betrieb
Features:
- Läuft auf Pfad `/ws` des Express-Servers
- Hält eine Registry aller verbundenen Clients
- Ermöglicht Broadcast an alle Clients oder gezieltes Senden
### API
```js
broadcast('PIPELINE_STATE_CHANGED', { state, activeJobId });
broadcast('PIPELINE_PROGRESS', { state, progress, eta, statusText });
broadcast('PIPELINE_QUEUE_CHANGED', queueSnapshot);
```
- Job-Liste/Detail inkl. Log-Tail
- Orphan-RAW-Erkennung und Import
- OMDb-Nachzuweisung
- Dateilöschung (`raw|movie|both`)
- Job-Löschung (`none|raw|movie|both`)
---
## omdbService.js
## `cronService.js`
Integration mit der [OMDb API](https://www.omdbapi.com/).
Integriertes Cron-System ohne externe Parser-Library.
### Methoden
Features:
| Methode | Beschreibung |
|---------|-------------|
| `searchByTitle(title, type)` | Suche nach Titel (movie/series) |
| `fetchById(imdbId)` | Vollständige Metadaten per IMDb-ID |
### Zurückgegebene Daten
```json
{
"imdbId": "tt1375666",
"title": "Inception",
"year": "2010",
"type": "movie",
"poster": "https://...",
"plot": "...",
"director": "Christopher Nolan"
}
```
- 5-Feld-Cron-Parser + `nextRun`-Berechnung
- Quellen: `script` oder `chain`
- Laufzeitlogs (`cron_run_logs`)
- manuelles Triggern
- WebSocket-Events: `CRON_JOBS_UPDATED`, `CRON_JOB_UPDATED`
---
## settingsService.js
## Weitere Services
Verwaltet alle Anwendungseinstellungen.
### Features
- **Schema-getriebene Validierung**: Jede Einstellung hat Typ, Grenzen und Pflichtfeld-Flag
- **Kategorisierung**: Einstellungen sind in Kategorien gruppiert (Pfade, Tools, Metadaten, …)
- **Persistenz**: Werte in SQLite, Schema ebenfalls in SQLite
- **Profil-Auflösung**: `resolveEffectiveToolSettings(settingsMap, mediaProfile)` wählt automatisch die profil-spezifischen Werte (`_bluray`/`_dvd`) und fällt auf den globalen Wert zurück
### Profil-Auflösung
```js
// Löst alle profil-spezifischen Keys auf und gibt einen effektiven Einstellungs-Map zurück
const effective = await settingsService.getEffectiveSettingsMap('bluray');
// effective.handbrake_preset → Wert aus handbrake_preset_bluray (falls gesetzt)
// effective.raw_dir → Wert aus raw_dir_bluray (kein Fallback bei Pfaden)
```
### Einstellungs-Kategorien
| Kategorie | Ausgewählte Schlüssel |
|-----------|----------------------|
| `Pfade` | `raw_dir[_bluray/_dvd/_other]`, `movie_dir[_bluray/_dvd/_other]`, `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` |
| `Tools Blu-ray` | `handbrake_preset_bluray`, `makemkv_rip_mode_bluray`, … |
| `Tools DVD` | `handbrake_preset_dvd`, `makemkv_rip_mode_dvd`, … |
| `Metadaten` | `omdb_api_key`, `omdb_default_type` |
| `Benachrichtigungen` | `pushover_enabled`, `pushover_token`, `pushover_notify_*` |
- `scriptService.js` (CRUD + Test + Wrapper-Ausführung)
- `scriptChainService.js` (CRUD + Step-Execution)
- `userPresetService.js` (HandBrake User-Presets)
- `hardwareMonitorService.js` (CPU/RAM/GPU/Storage)
- `websocketService.js` (Client-Registry + Broadcast)
- `notificationService.js` (PushOver)
- `logger.js` (rotierende Datei-Logs)
---
## userPresetService.js
## Bootstrapping (`src/index.js`)
Verwaltet benannte HandBrake-Preset-Sammlungen pro Medientyp.
Beim Start:
### Methoden
| Methode | Beschreibung |
|---------|-------------|
| `listPresets(mediaType?)` | Alle Presets; optional nach Medientyp filtern (`bluray`/`dvd`/`other`/`all`) |
| `getPresetById(id)` | Einzelnes Preset |
| `createPreset(payload)` | Neues Preset anlegen |
| `updatePreset(id, payload)` | Preset aktualisieren |
| `deletePreset(id)` | Preset löschen |
!!! info "mediaType = 'all'"
Presets mit `mediaType = 'all'` erscheinen bei Filterung nach jedem Medientyp.
---
## historyService.js
Datenbankoperationen für Job-Historie.
### Hauptoperationen
| Operation | Beschreibung |
|-----------|-------------|
| `listJobs(filters)` | Jobs nach Status/Titel filtern |
| `getJob(id)` | Job-Details mit Logs abrufen |
| `findOrphanRawFolders()` | Nicht-getrackte Raw-Ordner finden |
| `importOrphanRaw(path)` | Orphan-Ordner als Job importieren |
| `assignOmdb(id, omdbData)` | OMDb-Metadaten nachträglich zuweisen |
| `deleteJob(id, deleteFiles)` | Job und optional Dateien löschen |
---
## cronService.js
Eingebautes Cron-System ohne externe Abhängigkeiten.
### Features
- **Eigener Expression-Parser**: Unterstützt alle Standard-5-Felder-Cron-Ausdrücke (`* /n`, Bereiche, Listen)
- **Skripte und Ketten**: Cron-Jobs können ein Skript (`sourceType: "script"`) oder eine Kette (`sourceType: "chain"`) ausführen
- **Log-Rotation**: Max. 50 Logs pro Job, Ausgabe auf 100.000 Zeichen begrenzt
- **PushOver-Integration**: Optionale Benachrichtigung nach jeder Ausführung
- **Manuelle Auslösung**: `triggerJobManually(id)` läuft unabhängig vom Zeitplan
### Methoden
| Methode | Beschreibung |
|---------|-------------|
| `listJobs()` | Alle Cron-Jobs |
| `createJob(payload)` | Neuen Job anlegen |
| `updateJob(id, payload)` | Job aktualisieren |
| `deleteJob(id)` | Job löschen |
| `getJobLogs(id, limit)` | Ausführungs-Logs |
| `triggerJobManually(id)` | Sofortige Ausführung |
| `validateExpression(expr)` | Ausdruck validieren |
| `getNextRunTime(expr)` | Nächsten Ausführungszeitpunkt berechnen |
---
## notificationService.js
PushOver-Push-Benachrichtigungen.
```js
await notify({
title: 'Ripster: Job abgeschlossen',
message: 'Inception (2010) wurde erfolgreich encodiert'
});
```
---
## logger.js
Strukturiertes Logging mit täglicher Log-Rotation.
### Log-Level
| Level | Verwendung |
|-------|-----------|
| `debug` | Detaillierte Entwicklungs-Informationen |
| `info` | Normale Betriebsereignisse |
| `warn` | Warnungen, die Aufmerksamkeit benötigen |
| `error` | Fehler, die den Betrieb beeinträchtigen |
### Log-Dateien
```
logs/
├── ripster-2024-01-15.log ← Tages-Log
└── jobs/
└── job-42-handbrake.log ← Prozess-spezifische Logs
```
1. DB init/migrate
2. Pipeline-Init
3. Cron-Init
4. Express-Routes + Error-Handler
5. WebSocket-Server auf `/ws`
6. Hardware-Monitoring-Init
7. Disk-Detection-Start

View File

@@ -1,273 +1,112 @@
# Datenbank
Ripster verwendet **SQLite3** als Datenbank. Die Datenbankdatei liegt unter `backend/data/ripster.db`.
Ripster verwendet SQLite (`backend/data/ripster.db`).
---
## Schema-Übersicht
## Tabellen
```sql
settings_schema -- Einstellungs-Definitionen
settings_values -- Benutzer-Werte
jobs -- Rip-Job-Datensätze
pipeline_state -- Aktueller Pipeline-Zustand (Singleton)
scripts -- Shell-Skripte für Pre-/Post-Encode-Ausführung
script_chains -- Geordnete Ketten aus mehreren Skripten
script_chain_steps -- Einzelschritte einer Skript-Kette
user_presets -- Benannte HandBrake-Preset-Sammlungen pro Medientyp
cron_jobs -- Zeitgesteuerte Aufgaben (eigener Cron-Parser)
cron_run_logs -- Ausführungs-Protokolle für Cron-Jobs
```text
settings_schema
settings_values
jobs
pipeline_state
scripts
script_chains
script_chain_steps
user_presets
cron_jobs
cron_run_logs
```
---
## Tabelle: jobs
## `jobs`
Die wichtigste Tabelle speichert alle Ripping-Jobs.
Speichert Pipeline-Lifecycle und Artefakte pro Job.
```sql
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
status TEXT NOT NULL, -- Aktueller Status
title TEXT, -- Filmtitel (von OMDb)
imdb_id TEXT, -- IMDb-ID
omdb_year TEXT, -- Erscheinungsjahr
omdb_type TEXT, -- movie/series
omdb_poster TEXT, -- Poster-URL
raw_path TEXT, -- Pfad zur Raw-MKV
output_path TEXT, -- Pfad zur Ausgabedatei
playlist TEXT, -- Gewählte Blu-ray Playlist
rip_successful INTEGER NOT NULL DEFAULT 0, -- 1 wenn Rip abgeschlossen
makemkv_output TEXT, -- MakeMKV-Ausgabe (JSON)
mediainfo_output TEXT, -- MediaInfo-Ausgabe (JSON)
encode_plan TEXT, -- Encode-Plan (JSON)
handbrake_log TEXT, -- HandBrake Log-Pfad
error_message TEXT, -- Fehlermeldung bei ERROR
error_details TEXT -- Detaillierte Fehler-Infos
);
```
Zentrale Felder:
!!! info "rip_successful"
Das Feld `rip_successful` wird auf `1` gesetzt, sobald MakeMKV den Rip-Schritt erfolgreich abgeschlossen hat unabhängig davon, ob danach ein Encode-Fehler auftritt. Damit lässt sich in der History unterscheiden, ob eine Raw-Datei vorhanden ist.
### Job-Status-Werte
| Status | Beschreibung |
|--------|-------------|
| `ANALYZING` | MakeMKV analysiert die Disc |
| `METADATA_SELECTION` | Wartet auf Benutzer-Metadaten-Auswahl |
| `READY_TO_START` | Bereit zum Starten |
| `RIPPING` | MakeMKV rippt die Disc |
| `MEDIAINFO_CHECK` | MediaInfo analysiert die Raw-Datei |
| `READY_TO_ENCODE` | Wartet auf Encode-Bestätigung |
| `ENCODING` | HandBrake encodiert |
| `FINISHED` | Erfolgreich abgeschlossen |
| `ERROR` | Fehler aufgetreten |
- Metadaten: `title`, `year`, `imdb_id`, `poster_url`, `omdb_json`, `selected_from_omdb`
- Laufzeit: `start_time`, `end_time`, `status`, `last_state`
- Pfade: `raw_path`, `output_path`, `encode_input_path`
- Tool-Ausgaben: `makemkv_info_json`, `handbrake_info_json`, `mediainfo_info_json`, `encode_plan_json`
- Kontrolle: `encode_review_confirmed`, `rip_successful`, `error_message`
- Audit: `created_at`, `updated_at`
---
## Tabelle: pipeline_state
## `pipeline_state`
Singleton-Tabelle für den aktuellen Pipeline-Zustand (immer genau 1 Zeile).
Singleton-Tabelle (`id = 1`) für aktiven Snapshot:
```sql
CREATE TABLE pipeline_state (
id INTEGER PRIMARY KEY CHECK(id = 1),
state TEXT NOT NULL DEFAULT 'IDLE',
job_id INTEGER, -- Aktiver Job (NULL wenn IDLE)
progress REAL, -- Fortschritt 0-100
eta TEXT, -- Geschätzte Restzeit
updated_at TEXT NOT NULL
);
```
- `state`
- `active_job_id`
- `progress`
- `eta`
- `status_text`
- `context_json`
- `updated_at`
---
## Tabelle: settings_schema
## `settings_schema` + `settings_values`
Definiert alle verfügbaren Einstellungen mit Metadaten.
```sql
CREATE TABLE settings_schema (
key TEXT PRIMARY KEY,
category TEXT NOT NULL, -- paths, tools, encoding, ...
type TEXT NOT NULL, -- string, number, boolean, select
label TEXT NOT NULL, -- Anzeigename
description TEXT, -- Hilfetext
default_val TEXT, -- Standardwert
required INTEGER, -- 1 = Pflichtfeld
min_val REAL, -- Minimalwert (für number)
max_val REAL, -- Maximalwert (für number)
options TEXT -- JSON-Array für select-Typ
);
```
- `settings_schema`: Definition (Typ, Default, Validation, Reihenfolge)
- `settings_values`: aktueller Wert pro Key
---
## Tabelle: settings_values
## `scripts`, `script_chains`, `script_chain_steps`
Speichert benutzer-konfigurierte Werte.
```sql
CREATE TABLE settings_values (
key TEXT PRIMARY KEY REFERENCES settings_schema(key),
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
- `scripts`: Shell-Skripte (`name`, `script_body`, `order_index`)
- `script_chains`: Ketten (`name`, `order_index`)
- `script_chain_steps`: Schritte je Kette
- `step_type`: `script` oder `wait`
- `script_id` oder `wait_seconds`
---
## Tabelle: scripts
## `user_presets`
Verwaltet Shell-Skripte, die vor oder nach dem Encode-Schritt ausgeführt werden können.
Benannte HandBrake-Preset-Sets:
```sql
CREATE TABLE scripts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
script_body TEXT NOT NULL,
order_index INTEGER NOT NULL DEFAULT 0, -- Sortierposition in der UI
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
## Tabelle: script_chains
Geordnete Ketten, die mehrere Skripte sequenziell zusammenfassen.
```sql
CREATE TABLE script_chains (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
order_index INTEGER NOT NULL DEFAULT 0, -- Sortierposition in der UI
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE script_chain_steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chain_id INTEGER NOT NULL REFERENCES script_chains(id) ON DELETE CASCADE,
script_id INTEGER NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
step_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Sortierung"
`order_index` in `scripts` und `script_chains` wird über die API (`reorderScripts` / `reorderChains`) per Drag & Drop in der UI gesetzt und bleibt persistent gespeichert.
- `name`
- `media_type` (`bluray|dvd|other|all`)
- `handbrake_preset`
- `extra_args`
- `description`
---
## Tabelle: user_presets
## `cron_jobs` + `cron_run_logs`
Speichert benannte HandBrake-Preset-Sammlungen pro Medientyp.
```sql
CREATE TABLE user_presets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
media_type TEXT NOT NULL DEFAULT 'all', -- 'bluray', 'dvd', 'other', 'all'
handbrake_preset TEXT,
extra_args TEXT,
description TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Medientyp-Filter"
`GET /api/settings/user-presets?mediaType=bluray` gibt Presets mit `media_type = 'bluray'` **und** `media_type = 'all'` zurück.
- `cron_jobs`: Zeitplan + Status
- `cron_run_logs`: einzelne Läufe
- `status`: `running|success|error`
- `output`
- `error_message`
---
## Tabellen: cron_jobs & cron_run_logs
## Migration/Recovery
Speichern den Zeitplan und die Ausführungs-Historie des eingebauten Cron-Systems.
Beim Start werden Schema und Settings-Metadaten automatisch abgeglichen.
```sql
CREATE TABLE cron_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
cron_expression TEXT NOT NULL, -- 5-Felder-Ausdruck (min h d m wd)
source_type TEXT NOT NULL, -- "script" oder "chain"
source_id INTEGER NOT NULL, -- ID des Skripts/der Kette
enabled INTEGER NOT NULL DEFAULT 1,
pushover_enabled INTEGER NOT NULL DEFAULT 1,
last_run_at TEXT,
last_run_status TEXT, -- "success", "error", "running"
next_run_at TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Bei korruptem SQLite-File:
CREATE TABLE cron_run_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cron_job_id INTEGER NOT NULL REFERENCES cron_jobs(id) ON DELETE CASCADE,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT NOT NULL, -- "success", "error", "running"
exit_code INTEGER,
stdout TEXT,
stderr TEXT,
triggered_by TEXT NOT NULL DEFAULT 'cron', -- "cron" oder "manual"
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
!!! info "Log-Rotation"
Pro Cron-Job werden maximal **50 Log-Einträge** gespeichert; ältere Einträge werden automatisch gelöscht. Stdout/Stderr werden auf **100.000 Zeichen** begrenzt.
1. Datei wird nach `backend/data/corrupt-backups/` verschoben
2. neue DB wird initialisiert
3. Schema wird neu aufgebaut
---
## Schema-Migrationen
`database.js` implementiert **automatische Migrationen**:
1. Beim Start wird das aktuelle Schema geprüft
2. Fehlende Tabellen werden erstellt
3. Fehlende Spalten werden hinzugefügt
4. Neue Default-Einstellungen werden eingefügt
### Korruptions-Recovery
Falls die Datenbankdatei korrupt ist:
```
1. Korrupte Datei wird erkannt (Verbindungsfehler / Integritätsprüfung)
2. Datei wird in backend/data/corrupt-backups/ verschoben
3. Neue, leere Datenbank wird erstellt
4. Schema wird neu initialisiert
5. Log-Eintrag mit Warnung
```
---
## Datenbankpfad konfigurieren
Standard: `./data/ripster.db` (relativ zum Backend-Verzeichnis)
Über Umgebungsvariable anpassen:
```env
DB_PATH=/var/lib/ripster/ripster.db
```
---
## Direkte Datenbankinspektion
## Direkte Inspektion
```bash
# SQLite3-CLI
sqlite3 backend/data/ripster.db
# Alle Jobs anzeigen
.mode table
SELECT id, status, title, created_at FROM jobs ORDER BY created_at DESC;
# Einstellungen anzeigen
SELECT key, value FROM settings_values;
SELECT key, value FROM settings_values ORDER BY key;
```

View File

@@ -1,192 +1,82 @@
# Frontend-Komponenten
Das Frontend ist mit **React 18** und **PrimeReact** gebaut und kommuniziert über REST-API und WebSocket mit dem Backend.
Frontend: React + PrimeReact + Vite.
---
## Seiten (Pages)
## Hauptseiten
### DashboardPage.jsx
### `DashboardPage.jsx`
Die Hauptseite von Ripster zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen.
Pipeline-Steuerung:
**Funktionen:**
- 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, Queue-Interaktion)
- Status/Progress/ETA
- Metadaten-Dialog
- Playlist-Entscheidung
- Review-Panel
- Queue-Interaktion (reorder/add/remove)
- Job-Aktionen (Start/Cancel/Retry/Re-Encode)
- Hardware-Monitoring-Anzeige
**Zugehörige Komponenten:**
- `PipelineStatusCard` Status-Widget
- `MetadataSelectionDialog` OMDb-Suche und Playlist-Auswahl
- `MediaInfoReviewPanel` Track-Auswahl vor dem Encoding
- Queue- und Job-Karten-UI direkt in `DashboardPage`
### `SettingsPage.jsx`
### SettingsPage.jsx
Konfiguration:
Konfigurationsoberfläche für alle Ripster-Einstellungen.
- dynamisches Settings-Formular (`DynamicSettingsForm`)
- Skripte/Ketten inkl. Reorder/Test
- User-Presets
- Cron-Jobs (`CronJobsTab`)
**Funktionen:**
- Dynamisch generiertes Formular aus dem Settings-Schema
- Echtzeit-Validierungsfeedback
- PushOver-Verbindungstest
- Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen
### `HistoryPage.jsx`
### DatabasePage.jsx (`/history`)
Historie:
Job-Historie und Datenbankansicht mit vollständigem Audit-Trail.
**Funktionen:**
- Sortier- und filterbares Job-Verzeichnis
- Statusfilter (FINISHED, ERROR, WAITING_FOR_USER_DECISION, ...)
- Job-Detail-Dialog mit vollständigen Logs
- Re-Encode, Löschen und Metadaten-Zuweisung
- Import von Orphan-Raw-Ordnern
- Job-Liste/Filter
- Job-Details + Logs
- OMDb-Nachzuweisung
- Re-Encode/Restart-Workflows
---
## Komponenten (Components)
## Wichtige Komponenten
### MetadataSelectionDialog.jsx
Dialog für die Metadaten-Auswahl nach der Disc-Analyse.
```
┌─────────────────────────────────────┐
│ Metadaten auswählen │
├─────────────────────────────────────┤
│ Suche: [Inception ] 🔍 │
├─────────────────────────────────────┤
│ Ergebnisse: │
│ ▶ Inception (2010) Movie │
│ Inception: ... (2011) Series │
├─────────────────────────────────────┤
│ Playlist (nur Blu-ray): │
│ ▶ 00800.mpls (2:30:15) ✓ Empfohlen │
│ 00801.mpls (0:01:23) │
├─────────────────────────────────────┤
│ [Bestätigen] │
└─────────────────────────────────────┘
```
### MediaInfoReviewPanel.jsx
Track-Auswahl-Panel vor dem Encoding.
```
┌─────────────────────────────────────┐
│ Encode-Review │
├─────────────────────────────────────┤
│ Audio-Tracks: │
│ ☑ Track 1: Deutsch (AC-3, 5.1) │
│ ☑ Track 2: English (TrueHD, 7.1) │
│ ☐ Track 3: Français (AC-3, 2.0) │
├─────────────────────────────────────┤
│ Untertitel: │
│ ☑ Track 1: Deutsch │
│ ☐ Track 2: English │
├─────────────────────────────────────┤
│ [Encoding starten] │
└─────────────────────────────────────┘
```
### DynamicSettingsForm.jsx
Wiederverwendbares Formular, das aus dem Settings-Schema generiert wird.
**Unterstützte Feldtypen:**
| Typ | UI-Element |
|----|-----------|
| `string` | Text-Input |
| `number` | Zahlen-Input mit Min/Max |
| `boolean` | Toggle/Checkbox |
| `select` | Dropdown |
| `password` | Passwort-Input |
### PipelineStatusCard.jsx
Status-Anzeige-Widget für die Dashboard-Seite.
### JobDetailDialog.jsx
Vollständiger Job-Detail-Dialog mit Logs-Viewer.
- `PipelineStatusCard.jsx`
- `MetadataSelectionDialog.jsx`
- `MediaInfoReviewPanel.jsx`
- `JobDetailDialog.jsx`
- `CronJobsTab.jsx`
---
## Hooks
## API-Client (`api/client.js`)
### useWebSocket.js
Zentraler Custom-Hook für die WebSocket-Verbindung.
```js
useWebSocket({
onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGED') {
setPipelineState(msg.payload);
}
}
});
```
**Features:**
- Automatische Verbindung zu `/ws`
- Reconnect mit festem Intervall (`1500ms`)
- Message-Parsing (JSON)
- zentraler `request()` mit JSON-Handling
- Fehlerobjekt aus API wird auf `Error(message)` gemappt
- `VITE_API_BASE` default `/api`
---
## API-Client (client.js)
## WebSocket (`hooks/useWebSocket.js`)
Zentraler HTTP-Client für alle Backend-Anfragen.
- URL: `VITE_WS_URL` oder automatisch `ws(s)://<host>/ws`
- Auto-Reconnect mit 1500ms Intervall
```js
// Beispiel-Aufrufe
const state = await api.getPipelineState();
const results = await api.searchOmdb('Inception');
await api.selectMetadata({ jobId, title, year, imdbId, selectedPlaylist });
await api.confirmEncodeReview(jobId, {
selectedEncodeTitleId: 1,
selectedTrackSelection: { 1: { audioTrackIds: [1], subtitleTrackIds: [3] } }
});
```
In `App.jsx` werden u. a. verarbeitet:
**Features:**
- Zentralisierte Fehlerbehandlung
- Automatische JSON-Serialisierung
- Basis-URL aus Umgebungsvariable (`VITE_API_BASE`)
- `PIPELINE_STATE_CHANGED`
- `PIPELINE_PROGRESS`
- `PIPELINE_QUEUE_CHANGED`
- `DISC_DETECTED` / `DISC_REMOVED`
- `HARDWARE_MONITOR_UPDATE`
---
## Build & Entwicklung
### Entwicklungsserver
## Build/Run
```bash
cd frontend
npm run dev
# → http://localhost:5173
```
### Vite-Proxy-Konfiguration
In der Entwicklungsumgebung proxied Vite API-Anfragen zum Backend:
```js
// vite.config.js
proxy: {
'/api': 'http://localhost:3001',
'/ws': { target: 'ws://localhost:3001', ws: true }
}
```
### Production-Build
```bash
cd frontend
npm run build
# → frontend/dist/
# dev
npm run dev --prefix frontend
# prod build
npm run build --prefix frontend
```

View File

@@ -1,6 +1,6 @@
# Architektur
Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikation über WebSockets aufgebaut.
Ripster ist eine Client-Server-Anwendung mit REST + WebSocket.
---
@@ -9,104 +9,63 @@ Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikatio
```mermaid
graph TB
subgraph Browser["Browser (React)"]
Dashboard["Dashboard"]
Settings["Einstellungen"]
History["History"]
Dashboard[Dashboard]
Settings[Einstellungen]
History[Historie]
end
subgraph Backend["Node.js Backend"]
API["REST API\n(Express)"]
WS["WebSocket\nServer"]
Pipeline["Pipeline\nService"]
DB["SQLite\nDatenbank"]
API[REST API\nExpress]
WS[WebSocket\n/ws]
Pipeline[pipelineService]
Cron[cronService]
DB[(SQLite)]
end
subgraph ExternalTools["Externe Tools"]
MakeMKV["makemkvcon"]
HandBrake["HandBrakeCLI"]
MediaInfo["mediainfo"]
subgraph Tools["Externe Tools"]
MakeMKV[makemkvcon]
HandBrake[HandBrakeCLI]
MediaInfo[mediainfo]
end
subgraph ExternalAPIs["Externe APIs"]
OMDb["OMDb API"]
PushOver["PushOver"]
end
Browser <-->|HTTP REST| API
Browser <-->|HTTP| API
Browser <-->|WebSocket| WS
Pipeline --> MakeMKV
Pipeline --> HandBrake
Pipeline --> MediaInfo
Pipeline <-->|Metadaten| OMDb
Pipeline -->|Benachrichtigungen| PushOver
API --> DB
Pipeline --> DB
Cron --> DB
```
---
## Schichten-Architektur
## Schichten
### Backend
```
index.js (Express Server)
├── Routes (API-Endpunkte)
│ ├── pipelineRoutes.js
│ ├── settingsRoutes.js
│ └── historyRoutes.js
├── Services (Business Logic)
│ ├── pipelineService.js ← Kern-Orchestrierung
│ ├── diskDetectionService.js
│ ├── processRunner.js
│ ├── websocketService.js
│ ├── omdbService.js
│ ├── settingsService.js
│ ├── notificationService.js
│ ├── historyService.js
│ └── logger.js
├── Database
│ ├── database.js
│ └── defaultSettings.js
└── Utils
├── encodePlan.js
├── playlistAnalysis.js
├── progressParsers.js
└── files.js
```
- `src/index.js` (Bootstrapping, Routes, WS, Services)
- `src/routes/*` (Pipeline, Settings, History, Crons)
- `src/services/*` (Business-Logik)
- `src/db/database.js` (Init/Migration)
- `src/utils/*` (Parser, Dateifunktionen, Validierung)
### Frontend
```
App.jsx (React Router)
├── Pages
│ ├── DashboardPage.jsx ← Haupt-Interface
│ ├── SettingsPage.jsx
│ └── DatabasePage.jsx ← Historie/DB-Ansicht
├── Components
│ ├── PipelineStatusCard.jsx
│ ├── MetadataSelectionDialog.jsx
│ ├── MediaInfoReviewPanel.jsx
│ ├── DynamicSettingsForm.jsx
│ └── JobDetailDialog.jsx
├── Hooks
│ └── useWebSocket.js
└── API
└── client.js
```
- `App.jsx` + `pages/*` (Dashboard, Settings, History)
- `components/*` (Status-/Review-/Dialog-Komponenten)
- `api/client.js` (REST-Client)
- `hooks/useWebSocket.js` (WS-Reconnect)
---
## Weiterführende Dokumentation
## Weiterführend
<div class="grid cards" markdown>
- [:octicons-arrow-right-24: Übersicht](overview.md)
- [:octicons-arrow-right-24: Backend-Services](backend.md)
- [:octicons-arrow-right-24: Frontend-Komponenten](frontend.md)
- [:octicons-arrow-right-24: Datenbank](database.md)
- [:octicons-arrow-right-24: Übersicht](overview.md)
- [:octicons-arrow-right-24: Backend-Services](backend.md)
- [:octicons-arrow-right-24: Frontend-Komponenten](frontend.md)
- [:octicons-arrow-right-24: Datenbank](database.md)
</div>

View File

@@ -2,144 +2,93 @@
---
## Kern-Designprinzipien
## Kernprinzipien
### Event-Driven Pipeline
### Event-getriebene Pipeline
Der gesamte Ripping-Workflow ist als **State Machine** implementiert. Der `pipelineService` verwaltet den aktuellen Zustand und emittiert Ereignisse bei jedem Zustandswechsel. Der WebSocket-Service überträgt diese Ereignisse sofort an alle verbundenen Clients.
`pipelineService` hält einen Snapshot der State-Machine und broadcastet Änderungen sofort via WebSocket.
```
Zustandswechsel → Event → WebSocket → Frontend-Update
```text
State-Änderung -> PIPELINE_STATE_CHANGED/PIPELINE_PROGRESS -> Frontend-Update
```
### Service-Layer-Muster
### Service-Layer
```
HTTP-Route Service → Datenbank
```text
Route -> Service -> DB/Tool-Execution
```
Routes delegieren die gesamte Business-Logik an Services. Services sind voneinander unabhängig und können einzeln getestet werden.
Routes enthalten kaum Business-Logik.
### Schema-getriebene Einstellungen
### Schema-getriebene Settings
Die Settings-Konfiguration definiert **sowohl** die Validierungsregeln als auch die UI-Struktur in einer einzigen Quelle (`settings_schema`-Tabelle). Die `DynamicSettingsForm`-Komponente rendert das Formular dynamisch aus dem Schema.
Settings sind DB-schema-getrieben (`settings_schema` + `settings_values`), UI rendert dynamisch aus diesen Daten.
---
## Echtzeit-Kommunikation
### WebSocket-Protokoll
WebSocket läuft auf `/ws`.
Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON übertragen:
Wichtige Events:
```json
{
"type": "PIPELINE_STATE_CHANGED",
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 73.5,
"eta": "00:12:34"
}
}
```
**Nachrichtentypen:**
| Typ | Beschreibung |
|----|-------------|
| `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 |
| `PIPELINE_ERROR` | Pipeline-Fehler aufgetreten |
| `DISK_DETECTION_ERROR` | Laufwerkserkennung-Fehler |
### Reconnect-Logik
Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit festem Intervall von 1500ms bei Verbindungsabbrüchen.
- `PIPELINE_STATE_CHANGED`, `PIPELINE_PROGRESS`, `PIPELINE_QUEUE_CHANGED`
- `DISC_DETECTED`, `DISC_REMOVED`
- `HARDWARE_MONITOR_UPDATE`
- `SETTINGS_UPDATED`, `SETTINGS_BULK_UPDATED`
- `SETTINGS_SCRIPTS_UPDATED`, `SETTINGS_SCRIPT_CHAINS_UPDATED`, `USER_PRESETS_UPDATED`
- `CRON_JOBS_UPDATED`, `CRON_JOB_UPDATED`
- `PIPELINE_ERROR`, `DISK_DETECTION_ERROR`
---
## Prozess-Management
## Prozessausführung
### processRunner.js
Externe Tools werden als Child-Processes gestartet (`processRunner`):
Externe Tools (MakeMKV, HandBrake, MediaInfo) werden als **Child Processes** gestartet:
```js
spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
```
- **stdout/stderr** werden zeilenweise gelesen und in Echtzeit verarbeitet
- **Progress-Parsing** erfolgt über reguläre Ausdrücke in `progressParsers.js`
- **Graceful Shutdown**: SIGINT → Timeout → SIGKILL
- **Prozess-Tracking**: Aktive Prozesse werden registriert für sauberes Beenden
- Streaming von stdout/stderr
- Progress-Parsing (`progressParsers.js`)
- kontrollierter Abbruch (SIGINT/SIGKILL-Fallback)
---
## Datenpersistenz
## Persistenz
### SQLite-Datenbank
SQLite-Datei: `backend/data/ripster.db`
Ripster verwendet eine **einzige SQLite-Datei** für alle persistenten Daten:
Kern-Tabellen:
```
backend/data/ripster.db
```
- `jobs`, `pipeline_state`
- `settings_schema`, `settings_values`
- `scripts`, `script_chains`, `script_chain_steps`
- `user_presets`
- `cron_jobs`, `cron_run_logs`
**Tabellen:**
| Tabelle | Inhalt |
|---------|--------|
| `jobs` | Alle Rip-Jobs mit Status, Logs, Metadaten |
| `pipeline_state` | Aktueller Pipeline-Zustand (Singleton) |
| `settings_schema` | Schema aller verfügbaren Einstellungen |
| `settings_values` | Benutzer-konfigurierte Werte |
### Migrations-Strategie
Beim Start prüft `database.js` automatisch, ob das Schema aktuell ist, und führt fehlende Migrationen aus. Korrupte Datenbankdateien werden in ein Quarantäne-Verzeichnis verschoben und eine neue Datenbank erstellt.
Beim Start werden Schema und Settings-Migrationen automatisch ausgeführt.
---
## Fehlerbehandlung
### Strukturierte Fehler
Zentrales Error-Handling liefert:
Alle Fehler werden mit Kontext-Metadaten protokolliert:
```js
logger.error('Encoding fehlgeschlagen', {
jobId: job.id,
command: cmd,
exitCode: code,
stderr: lastLines
});
```json
{
"error": {
"message": "...",
"statusCode": 400,
"reqId": "...",
"details": []
}
}
```
### Job-Fehler-Recovery
- Fehlgeschlagene Jobs bleiben in der Datenbank (Status `ERROR`)
- Vollständige Fehler-Logs werden im Job-Datensatz gespeichert
- **Retry-Funktion** ermöglicht Neustart von einem Fehler-Zustand
- **Re-Encode** erlaubt erneutes Encodieren ohne neu zu rippen
Fehlgeschlagene Jobs bleiben in der Historie (`ERROR` oder `CANCELLED`) und können erneut gestartet werden.
---
## Sicherheit
## CORS & Runtime-Konfig
### Eingabe-Validierung
- Alle Benutzer-Eingaben werden in `validators.js` validiert
- CLI-Argumente werden sicher über `commandLine.js` konstruiert (kein Shell-Injection-Risiko)
- Pfade werden sanitisiert bevor sie an externe Prozesse übergeben werden
### CORS-Konfiguration
```env
CORS_ORIGIN=http://localhost:5173
```
In Produktion sollte dieser Wert auf die tatsächliche Frontend-URL gesetzt werden.
- `CORS_ORIGIN` default: `*`
- `LOG_LEVEL` default: `info`
- DB-/Log-Pfade über `DB_PATH`/`LOG_DIR` konfigurierbar

View File

@@ -1,96 +1,67 @@
# Umgebungsvariablen
Umgebungsvariablen überschreiben die Standardwerte und eignen sich für Server-Deployments.
Umgebungsvariablen steuern Backend/Vite außerhalb der DB-basierten UI-Settings.
---
## Backend-Umgebungsvariablen
## Backend (`backend/.env`)
Konfigurationsdatei: `backend/.env`
| Variable | Default (Code) | Beschreibung |
|---------|------------------|-------------|
| `PORT` | `3001` | Express-Port |
| `DB_PATH` | `backend/data/ripster.db` | SQLite-Datei (relativ zu `backend/`) |
| `LOG_DIR` | `backend/logs` | Fallback-Logverzeichnis (wenn `log_dir`-Setting nicht gesetzt/lesbar) |
| `CORS_ORIGIN` | `*` | CORS-Origin für API |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
| Variable | Standard | Beschreibung |
|---------|---------|-------------|
| `PORT` | `3001` | Port des Express-Servers |
| `DB_PATH` | `./data/ripster.db` | Pfad zur SQLite-Datenbankdatei |
| `CORS_ORIGIN` | `http://localhost:5173` | Erlaubter CORS-Origin |
| `LOG_DIR` | `./logs` | Verzeichnis für Log-Dateien |
| `LOG_LEVEL` | `info` | Log-Level (`debug`, `info`, `warn`, `error`) |
### Beispiel: backend/.env
Beispiel:
```env
PORT=3001
DB_PATH=/var/lib/ripster/ripster.db
CORS_ORIGIN=http://192.168.1.100:5173
LOG_DIR=/var/log/ripster
CORS_ORIGIN=http://192.168.1.50:5173
LOG_LEVEL=info
```
Hinweis: `backend/.env.example` enthält bewusst dev-freundliche Werte (z. B. lokaler `CORS_ORIGIN`).
---
## Frontend-Umgebungsvariablen
## Frontend (`frontend/.env`)
Konfigurationsdatei: `frontend/.env`
| Variable | Standard | Beschreibung |
| Variable | Default | Beschreibung |
|---------|---------|-------------|
| `VITE_API_BASE` | `http://localhost:3001` | Backend-API-URL |
| `VITE_WS_URL` | `ws://localhost:3001` | WebSocket-URL |
| `VITE_PUBLIC_ORIGIN` | | Öffentliche Origin-URL (für CORS) |
| `VITE_HMR_HOST` | — | Vite HMR-Host (für Remote-Entwicklung) |
| `VITE_HMR_PORT` | — | Vite HMR-Port |
| `VITE_API_BASE` | `/api` | API-Basis für Fetch-Client |
| `VITE_WS_URL` | automatisch aus `window.location` + `/ws` | Optional explizite WebSocket-URL |
| `VITE_PUBLIC_ORIGIN` | leer | Öffentliche Vite-Origin (Remote-Dev) |
| `VITE_ALLOWED_HOSTS` | `true` | Komma-separierte Hostliste für Vite `allowedHosts` |
| `VITE_HMR_PROTOCOL` | abgeleitet aus `VITE_PUBLIC_ORIGIN` | HMR-Protokoll (`ws`/`wss`) |
| `VITE_HMR_HOST` | abgeleitet aus `VITE_PUBLIC_ORIGIN` | HMR-Host |
| `VITE_HMR_CLIENT_PORT` | abgeleitet aus `VITE_PUBLIC_ORIGIN` | HMR-Client-Port |
### Beispiel: frontend/.env (Entwicklung)
Beispiele:
```env
VITE_API_BASE=http://localhost:3001
VITE_WS_URL=ws://localhost:3001
# lokal (mit Vite-Proxy)
VITE_API_BASE=/api
```
### Beispiel: frontend/.env (Netzwerk-Zugriff)
```env
VITE_API_BASE=http://192.168.1.100:3001
VITE_WS_URL=ws://192.168.1.100:3001
VITE_PUBLIC_ORIGIN=http://192.168.1.100:5173
# remote dev
VITE_API_BASE=http://192.168.1.50:3001/api
VITE_WS_URL=ws://192.168.1.50:3001/ws
VITE_PUBLIC_ORIGIN=http://192.168.1.50:5173
VITE_ALLOWED_HOSTS=192.168.1.50,ripster.local
VITE_HMR_PROTOCOL=ws
VITE_HMR_HOST=192.168.1.50
VITE_HMR_CLIENT_PORT=5173
```
---
## .env.example Dateien
## Priorität
Das Repository enthält Vorlagen für beide Konfigurationsdateien:
```bash
# Backend
cp backend/.env.example backend/.env
# Frontend
cp frontend/.env.example frontend/.env
```
---
## Priorität der Konfiguration
Einstellungen werden in folgender Reihenfolge geladen (höhere Priorität überschreibt niedrigere):
```
1. Systemumgebungsvariablen (export VAR=value)
2. .env-Datei
3. Hardcodierte Standardwerte in config.js
```
---
## LOG_LEVEL
| Level | Ausgabe |
|-------|---------|
| `debug` | Alle Meldungen inkl. Debugging |
| `info` | Normale Betriebsinformationen |
| `warn` | Warnungen + Fehler |
| `error` | Nur Fehler |
!!! tip "Produktionsempfehlung"
Für Produktionsumgebungen `LOG_LEVEL=info` oder `LOG_LEVEL=warn` verwenden. `debug` erzeugt sehr viele Log-Einträge.
1. Prozess-Umgebungsvariablen
2. `.env`
3. Code-Defaults

View File

@@ -1,180 +1,165 @@
# Einstellungsreferenz
Vollständige Übersicht aller Ripster-Einstellungen. Alle Einstellungen werden über die Web-Oberfläche unter **Einstellungen** verwaltet und in SQLite gespeichert.
Alle Settings liegen in `settings_schema`/`settings_values` und werden über die UI verwaltet.
---
## Profil-System
Ripster erkennt den Medientyp einer eingelegten Disc (Blu-ray / DVD / CD) und wählt automatisch die passenden profil-spezifischen Einstellungen. Für viele Schlüssel gibt es zusätzlich zur globalen Einstellung eine Variante pro Profil:
Ripster arbeitet mit Media-Profilen:
| Profil | Erkennungsmerkmale |
|--------|--------------------|
| `bluray` | UDF-Dateisystem, Laufwerk-Modell enthält „Blu-ray", Disc-Label wie BDMV |
| `dvd` | ISO9660/UDF, Laufwerk-Modell enthält „DVD", VIDEO_TS-Struktur |
| `other` | Alles andere (CD, unbekannt) |
- `bluray`
- `dvd`
- `other`
**Auflösungsreihenfolge für profil-spezifische Einstellungen:**
Viele Tool-/Pfad-Settings existieren als Profil-Varianten (`*_bluray`, `*_dvd`, `*_other`).
1. Profil-spezifischer Wert (`_bluray` / `_dvd`) wenn gesetzt, hat dieser Vorrang
2. Alternativ-Profil als Fallback (Blu-ray → DVD-Wert als Fallback und umgekehrt)
Wichtig:
Pfad-Einstellungen (`raw_dir`, `movie_dir`) und Besitz-Einstellungen (`raw_dir_owner`, `movie_dir_owner`) werden **ausschließlich** aus dem passenden Profil bezogen kein Cross-Profil-Fallback.
- Für `raw_dir`, `movie_dir` und die zugehörigen `*_owner`-Keys gibt es **kein Cross-Profil-Fallback**.
- Für viele Tool-Keys werden profilspezifische Varianten bevorzugt.
---
## Template-Platzhalter
Datei-/Ordner-Templates unterstützen:
- `${title}`
- `${year}`
- `${imdbId}`
Nicht gesetzte Werte werden zu `unknown`.
---
## Kategorie: Pfade
| Schlüssel | Typ | Pflicht | Beschreibung |
|-----------|-----|---------|-------------|
| `raw_dir` | string | ✅ | Verzeichnis für rohe MKV-Dateien (Fallback wenn kein Profil-Wert) |
| `raw_dir_bluray` | string | — | Raw-Verzeichnis für Blu-rays |
| `raw_dir_dvd` | string | — | Raw-Verzeichnis für DVDs |
| `raw_dir_other` | string | — | Raw-Verzeichnis für sonstige Medien |
| `raw_dir_owner` | string | — | Besitzer für Raw-Verzeichnis (`user:group`, Fallback) |
| `raw_dir_bluray_owner` | string | — | Besitzer für Raw-Verzeichnis (Blu-ray) |
| `raw_dir_dvd_owner` | string | — | Besitzer für Raw-Verzeichnis (DVD) |
| `raw_dir_other_owner` | string | — | Besitzer für Raw-Verzeichnis (Sonstiges) |
| `movie_dir` | string | ✅ | Ausgabeverzeichnis für Filme (Fallback) |
| `movie_dir_bluray` | string | — | Ausgabeverzeichnis für Blu-rays |
| `movie_dir_dvd` | string | — | Ausgabeverzeichnis für DVDs |
| `movie_dir_other` | string | — | Ausgabeverzeichnis für sonstige Medien |
| `movie_dir_owner` | string | — | Besitzer für Ausgabeverzeichnis (Fallback) |
| `movie_dir_bluray_owner` | string | — | Besitzer für Ausgabeverzeichnis (Blu-ray) |
| `movie_dir_dvd_owner` | string | — | Besitzer für Ausgabeverzeichnis (DVD) |
| `movie_dir_other_owner` | string | — | Besitzer für Ausgabeverzeichnis (Sonstiges) |
| `log_dir` | string | — | Verzeichnis für Log-Dateien (Standard: `./logs`) |
| Key | Typ | Default |
|-----|-----|---------|
| `raw_dir` | path | `data/output/raw` |
| `raw_dir_bluray` | path | `null` |
| `raw_dir_dvd` | path | `null` |
| `raw_dir_other` | path | `null` |
| `raw_dir_bluray_owner` | string | `null` |
| `raw_dir_dvd_owner` | string | `null` |
| `raw_dir_other_owner` | string | `null` |
| `movie_dir` | path | `data/output/movies` |
| `movie_dir_bluray` | path | `null` |
| `movie_dir_dvd` | path | `null` |
| `movie_dir_other` | path | `null` |
| `movie_dir_bluray_owner` | string | `null` |
| `movie_dir_dvd_owner` | string | `null` |
| `movie_dir_other_owner` | string | `null` |
| `log_dir` | path | `data/logs` |
---
## Kategorie: Laufwerk
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `drive_mode` | select | `auto` | `auto` = automatisch erkennen, `explicit` = festes Gerät |
| `drive_device` | string | `/dev/sr0` | Geräte-Pfad (nur bei `explicit`) |
| `disc_poll_interval_ms` | number | `4000` | Polling-Intervall in Millisekunden (100060000) |
| `makemkv_source_index` | number | `0` | Laufwerk-Index für MakeMKV (bei mehreren Laufwerken) |
---
## Kategorie: Tools (global)
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `makemkv_command` | string | `makemkvcon` | Befehl oder absoluter Pfad zu MakeMKV |
| `handbrake_command` | string | `HandBrakeCLI` | Befehl oder absoluter Pfad zu HandBrake |
| `mediainfo_command` | string | `mediainfo` | Befehl oder absoluter Pfad zu MediaInfo |
| `makemkv_min_length_minutes` | number | `15` | Mindest-Titellänge in Minuten (0999) |
| `pipeline_max_parallel_jobs` | number | `1` | Maximale Anzahl parallel laufender Jobs (112) |
| `handbrake_restart_delete_incomplete_output` | boolean | `true` | Unvollständige Ausgabedatei beim Encode-Neustart löschen |
### Kategorie: Tools Blu-ray
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `makemkv_rip_mode_bluray` | select | `backup` | Rip-Modus: `mkv` oder `backup` |
| `makemkv_analyze_extra_args_bluray` | string | — | Zusatz-CLI-Parameter für Analyse (Blu-ray) |
| `makemkv_rip_extra_args_bluray` | string | — | Zusatz-CLI-Parameter für Rip (Blu-ray) |
| `mediainfo_extra_args_bluray` | string | — | Zusatz-CLI-Parameter für mediainfo (Blu-ray) |
| `handbrake_preset_bluray` | string | `H.264 MKV 1080p30` | HandBrake-Preset für Blu-rays |
| `handbrake_extra_args_bluray` | string | — | Zusatz-CLI-Argumente für HandBrake (Blu-ray) |
| `output_extension_bluray` | select | `mkv` | Ausgabeformat: `mkv` oder `mp4` |
| `filename_template_bluray` | string | `${title} (${year})` | Dateiname-Template (Blu-ray) |
| `output_folder_template_bluray` | string | — | Ordnername-Template (Blu-ray, leer = Dateiname-Template) |
### Kategorie: Tools DVD
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `makemkv_rip_mode_dvd` | select | `mkv` | Rip-Modus: `mkv` oder `backup` |
| `makemkv_analyze_extra_args_dvd` | string | — | Zusatz-CLI-Parameter für Analyse (DVD) |
| `makemkv_rip_extra_args_dvd` | string | — | Zusatz-CLI-Parameter für Rip (DVD) |
| `mediainfo_extra_args_dvd` | string | — | Zusatz-CLI-Parameter für mediainfo (DVD) |
| `handbrake_preset_dvd` | string | `H.264 MKV 480p30` | HandBrake-Preset für DVDs |
| `handbrake_extra_args_dvd` | string | — | Zusatz-CLI-Argumente für HandBrake (DVD) |
| `output_extension_dvd` | select | `mkv` | Ausgabeformat: `mkv` oder `mp4` |
| `filename_template_dvd` | string | `${title} (${year})` | Dateiname-Template (DVD) |
| `output_folder_template_dvd` | string | — | Ordnername-Template (DVD, leer = Dateiname-Template) |
### Globale Fallback-Einstellungen für Encode
Diese Werte werden verwendet, wenn kein profil-spezifischer Wert konfiguriert ist:
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `handbrake_preset` | string | `H.265 MKV 1080p30` | Fallback HandBrake-Preset |
| `handbrake_extra_args` | string | — | Fallback Extra-Args |
| `makemkv_rip_mode` | select | `mkv` | Fallback Rip-Modus |
| `makemkv_analyze_extra_args` | string | — | Fallback Analyse-Args |
| `makemkv_rip_extra_args` | string | — | Fallback Rip-Args |
| `mediainfo_extra_args` | string | — | Fallback MediaInfo-Args |
| `output_extension` | select | `mkv` | Fallback Ausgabeformat |
| `filename_template` | string | `${title} (${year})` | Fallback Dateiname-Template |
| `output_folder_template` | string | — | Fallback Ordnername-Template |
### Template-Platzhalter
| Platzhalter | Beispiel |
|------------|---------|
| `${title}` | `Inception` |
| `${year}` | `2010` |
| `${imdbId}` | `tt1375666` |
---
## Kategorie: Metadaten
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `omdb_api_key` | string | — | API-Key von [omdbapi.com](https://www.omdbapi.com/) |
| `omdb_default_type` | select | `movie` | Vorauswahl für OMDb-Suche: `movie`, `series`, `episode` |
---
## Kategorie: Benachrichtigungen (PushOver)
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `pushover_enabled` | boolean | `false` | Master-Schalter für PushOver |
| `pushover_token` | string | — | Application-Token |
| `pushover_user` | string | — | User-Key |
| `pushover_device` | string | — | Optionales Ziel-Device |
| `pushover_title_prefix` | string | `Ripster` | Präfix im Benachrichtigungstitel |
| `pushover_priority` | number | `0` | Priorität (-2 bis 2) |
| `pushover_timeout_ms` | number | `7000` | HTTP-Timeout für PushOver-Requests (ms) |
### Granulare Event-Schalter
| Schlüssel | Standard | Beschreibung |
|-----------|---------|-------------|
| `pushover_notify_metadata_ready` | `true` | Bei Metadaten-Auswahl benachrichtigen |
| `pushover_notify_rip_started` | `true` | Bei MakeMKV-Rip-Start |
| `pushover_notify_encoding_started` | `true` | Bei HandBrake-Start |
| `pushover_notify_job_finished` | `true` | Bei erfolgreichem Abschluss |
| `pushover_notify_job_error` | `true` | Bei Fehler |
| `pushover_notify_job_cancelled` | `true` | Bei manuellem Abbruch |
| `pushover_notify_reencode_started` | `true` | Bei Re-Encode-Start |
| `pushover_notify_reencode_finished` | `true` | Bei erfolgreichem Re-Encode |
| Key | Typ | Default | Hinweis |
|-----|-----|---------|--------|
| `drive_mode` | select | `auto` | `auto` oder `explicit` |
| `drive_device` | path | `/dev/sr0` | bei `explicit` relevant |
| `makemkv_source_index` | number | `0` | MakeMKV Source-Index |
| `disc_poll_interval_ms` | number | `4000` | 1000..60000 |
---
## Kategorie: Monitoring
| Schlüssel | Typ | Standard | Beschreibung |
|-----------|-----|---------|-------------|
| `hardware_monitoring_enabled` | boolean | `false` | Hardware-Monitoring aktivieren (CPU, RAM, Temp.) |
| `hardware_monitoring_interval_ms` | number | `5000` | Monitoring-Polling-Intervall (ms) |
| Key | Typ | Default |
|-----|-----|---------|
| `hardware_monitoring_enabled` | boolean | `true` |
| `hardware_monitoring_interval_ms` | number | `5000` |
---
## Standard-Einstellungen zurücksetzen
## Kategorie: Tools (global)
Einen einzelnen Wert über die Datenbank zurücksetzen:
| Key | Typ | Default |
|-----|-----|---------|
| `makemkv_command` | string | `makemkvcon` |
| `makemkv_registration_key` | string | `null` |
| `mediainfo_command` | string | `mediainfo` |
| `makemkv_min_length_minutes` | number | `60` |
| `handbrake_command` | string | `HandBrakeCLI` |
| `handbrake_restart_delete_incomplete_output` | boolean | `true` |
| `pipeline_max_parallel_jobs` | number | `1` |
```bash
sqlite3 backend/data/ripster.db \
"DELETE FROM settings_values WHERE key = 'handbrake_preset_bluray';"
```
### Blu-ray-spezifisch
Beim nächsten Laden wird der Standardwert aus `settings_schema.default_value` verwendet.
| Key | Typ | Default |
|-----|-----|---------|
| `mediainfo_extra_args_bluray` | string | `null` |
| `makemkv_rip_mode_bluray` | select | `backup` |
| `makemkv_analyze_extra_args_bluray` | string | `null` |
| `makemkv_rip_extra_args_bluray` | string | `null` |
| `handbrake_preset_bluray` | string | `H.264 MKV 1080p30` |
| `handbrake_extra_args_bluray` | string | `null` |
| `output_extension_bluray` | select | `mkv` |
| `filename_template_bluray` | string | `${title} (${year})` |
| `output_folder_template_bluray` | string | `null` |
### DVD-spezifisch
| Key | Typ | Default |
|-----|-----|---------|
| `mediainfo_extra_args_dvd` | string | `null` |
| `makemkv_rip_mode_dvd` | select | `mkv` |
| `makemkv_analyze_extra_args_dvd` | string | `null` |
| `makemkv_rip_extra_args_dvd` | string | `null` |
| `handbrake_preset_dvd` | string | `H.264 MKV 480p30` |
| `handbrake_extra_args_dvd` | string | `null` |
| `output_extension_dvd` | select | `mkv` |
| `filename_template_dvd` | string | `${title} (${year})` |
| `output_folder_template_dvd` | string | `null` |
---
## Kategorie: Metadaten
| Key | Typ | Default |
|-----|-----|---------|
| `omdb_api_key` | string | `null` |
| `omdb_default_type` | select | `movie` |
---
## Kategorie: Benachrichtigungen (PushOver)
| Key | Typ | Default |
|-----|-----|---------|
| `pushover_enabled` | boolean | `false` |
| `pushover_token` | string | `null` |
| `pushover_user` | string | `null` |
| `pushover_device` | string | `null` |
| `pushover_title_prefix` | string | `Ripster` |
| `pushover_priority` | number | `0` |
| `pushover_timeout_ms` | number | `7000` |
| `pushover_notify_metadata_ready` | boolean | `true` |
| `pushover_notify_rip_started` | boolean | `true` |
| `pushover_notify_encoding_started` | boolean | `true` |
| `pushover_notify_job_finished` | boolean | `true` |
| `pushover_notify_job_error` | boolean | `true` |
| `pushover_notify_job_cancelled` | boolean | `true` |
| `pushover_notify_reencode_started` | boolean | `true` |
| `pushover_notify_reencode_finished` | boolean | `true` |
---
## Entfernte Legacy-Keys
Diese Legacy-Keys werden bei Migration entfernt und sollten nicht mehr genutzt werden:
- `makemkv_backup_mode`
- `mediainfo_extra_args`
- `makemkv_rip_mode`
- `makemkv_analyze_extra_args`
- `makemkv_rip_extra_args`
- `handbrake_preset`
- `handbrake_extra_args`
- `output_extension`
- `filename_template`
- `output_folder_template`
- `pushover_notify_disc_detected`

View File

@@ -5,7 +5,7 @@
## Voraussetzungen
- Node.js >= 20.19.0
- Alle [externen Tools](../getting-started/prerequisites.md) installiert
- externe Tools installiert (`makemkvcon`, `HandBrakeCLI`, `mediainfo`)
---
@@ -15,15 +15,18 @@
./start.sh
```
Das Skript startet automatisch:
- **Backend** auf Port 3001 (mit Nodemon für Hot-Reload)
- **Frontend** auf Port 5173 (mit Vite HMR)
Startet:
- Backend (`http://localhost:3001`, mit nodemon)
- Frontend (`http://localhost:5173`, mit Vite HMR)
Stoppen: `Ctrl+C`.
---
## Manuelle Entwicklungsumgebung
## Manuell
### Terminal 1 Backend
### Backend
```bash
cd backend
@@ -31,9 +34,7 @@ npm install
npm run dev
```
Backend läuft auf `http://localhost:3001` mit **Nodemon** Neustart bei Dateiänderungen.
### Terminal 2 Frontend
### Frontend
```bash
cd frontend
@@ -41,97 +42,44 @@ npm install
npm run dev
```
Frontend läuft auf `http://localhost:5173` mit **Vite HMR** sofortige Browser-Updates.
---
## Vite-Proxy (Dev)
`frontend/vite.config.js` proxied standardmäßig:
- `/api` -> `http://127.0.0.1:3001`
- `/ws` -> `ws://127.0.0.1:3001`
---
## Vite-Proxy
## Remote-Dev (optional)
Im Entwicklungsmodus proxied Vite alle API- und WebSocket-Anfragen zum Backend:
```js
// frontend/vite.config.js
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true
},
'/ws': {
target: 'ws://localhost:3001',
ws: true
}
}
}
```
Das bedeutet: Im Browser macht das Frontend Anfragen an `localhost:5173/api/...` Vite leitet diese an `localhost:3001/api/...` weiter.
---
## Remote-Entwicklung
Falls Ripster auf einem entfernten Server entwickelt wird (z.B. Homeserver), muss die Vite-Konfiguration angepasst werden:
Beispiel `frontend/.env.local`:
```env
# frontend/.env.local
VITE_API_BASE=http://192.168.1.100:3001
VITE_WS_URL=ws://192.168.1.100:3001
VITE_API_BASE=http://192.168.1.100:3001/api
VITE_WS_URL=ws://192.168.1.100:3001/ws
VITE_PUBLIC_ORIGIN=http://192.168.1.100:5173
VITE_ALLOWED_HOSTS=192.168.1.100,ripster.local
VITE_HMR_PROTOCOL=ws
VITE_HMR_HOST=192.168.1.100
VITE_HMR_PORT=5173
VITE_HMR_CLIENT_PORT=5173
```
---
## Log-Level für Entwicklung
```env
# backend/.env
LOG_LEVEL=debug
```
Im Debug-Modus werden alle Ausgaben der externen Tools (MakeMKV, HandBrake) vollständig geloggt.
---
## Stoppen
## Nützliche Kommandos
```bash
./kill.sh
# Root dev (backend + frontend)
npm run dev
# einzeln
npm run dev:backend
npm run dev:frontend
# Frontend Build
npm run build:frontend
```
---
## Linting & Type-Checking
```bash
# Frontend (ESLint)
cd frontend && npm run lint
# Backend hat keine separaten Lint-Scripts,
# nutze direkt eslint falls konfiguriert
```
---
## Deployment-Script
Das `deploy-ripster.sh`-Script überträgt Code auf einen Remote-Server per SSH:
```bash
./deploy-ripster.sh
```
**Was das Script tut:**
1. `rsync` synchronisiert den Code (Backend-Quellcode ohne `data/`)
2. Die Datenbank (`backend/data/`) wird **nicht** überschrieben
3. Verbindung via SSH (konfigurierbar im Script)
**Anpassung des Scripts:**
```bash
# deploy-ripster.sh
REMOTE_HOST="192.168.1.100"
REMOTE_USER="michael"
REMOTE_PATH="/home/michael/ripster"
```

View File

@@ -4,42 +4,44 @@
## Empfohlene Architektur
```text
Client
-> nginx (Reverse Proxy + statisches Frontend)
-> Backend API/WebSocket (Node.js, Port 3001)
```
Internet / Heimnetz
nginx (Reverse Proxy)
┌────┴────┐
│ │
Backend Frontend
:3001 (statische Dateien)
```
Wichtig: Das Backend serviert im aktuellen Stand keine `frontend/dist`-Dateien automatisch.
---
## systemd-Service
Für ein dauerhaftes Betreiben als systemd-Service:
## 1) Frontend builden
```bash
sudo nano /etc/systemd/system/ripster.service
cd frontend
npm install
npm run build
```
Artefakte liegen in `frontend/dist/`.
---
## 2) Backend als systemd-Service
Beispiel `/etc/systemd/system/ripster-backend.service`:
```ini
[Unit]
Description=Ripster - Disc Ripping Service
Description=Ripster Backend
After=network.target
[Service]
Type=simple
User=michael
WorkingDirectory=/home/michael/ripster
ExecStart=/bin/bash /home/michael/ripster/start.sh
ExecStop=/bin/bash /home/michael/ripster/kill.sh
User=ripster
WorkingDirectory=/opt/ripster/backend
ExecStart=/usr/bin/env node src/index.js
Restart=on-failure
RestartSec=10s
# Umgebungsvariablen
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=LOG_LEVEL=info
@@ -48,61 +50,40 @@ Environment=LOG_LEVEL=info
WantedBy=multi-user.target
```
Aktivieren:
```bash
# Service aktivieren und starten
sudo systemctl daemon-reload
sudo systemctl enable ripster
sudo systemctl start ripster
# Status prüfen
sudo systemctl status ripster
# Logs anzeigen
journalctl -u ripster -f
sudo systemctl enable --now ripster-backend
sudo systemctl status ripster-backend
```
---
## Frontend-Build
## 3) nginx konfigurieren
Für Produktion das Frontend bauen:
```bash
cd frontend
npm run build
```
Die statischen Dateien landen in `frontend/dist/`.
---
## nginx-Konfiguration
Beispiel `/etc/nginx/sites-available/ripster`:
```nginx
# /etc/nginx/sites-available/ripster
server {
listen 80;
server_name ripster.local;
# Statisches Frontend
root /home/michael/ripster/frontend/dist;
root /opt/ripster/frontend/dist;
index index.html;
# SPA Fallback (React Router)
location / {
try_files $uri $uri/ /index.html;
}
# API-Proxy zum Backend
location /api/ {
proxy_pass http://localhost:3001;
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket-Proxy
location /ws {
proxy_pass http://localhost:3001;
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
@@ -111,83 +92,27 @@ server {
}
```
Aktivieren:
```bash
sudo ln -s /etc/nginx/sites-available/ripster /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo nginx -t
sudo systemctl reload nginx
```
---
## Nur-Backend-Produktion (ohne nginx)
Falls kein Reverse Proxy gewünscht ist, kann das Backend die Frontend-Dateien direkt ausliefern:
```bash
# Frontend bauen
cd frontend && npm run build
# Backend startet und serviert frontend/dist/
cd backend && NODE_ENV=production npm start
```
Das Backend ist so konfiguriert, dass es im Produktionsmodus die `frontend/dist/`-Dateien als statische Assets ausliefert.
---
## Datenbank-Backup
```bash
# Datenbank sichern
cp backend/data/ripster.db backend/data/ripster.db.backup.$(date +%Y%m%d)
# Oder mit SQLite-eigenem Backup-Befehl
sqlite3 backend/data/ripster.db ".backup '/mnt/backup/ripster.db'"
```
!!! tip "Automatisches Backup"
Cron-Job für tägliches Backup:
```cron
0 3 * * * sqlite3 /home/michael/ripster/backend/data/ripster.db ".backup '/mnt/backup/ripster-$(date +\%Y\%m\%d).db'"
```
---
## Log-Rotation
Ripster rotiert Logs automatisch täglich. Falls zusätzlich systemd-Journal-Rotation gewünscht ist:
```bash
# /etc/logrotate.d/ripster
/home/michael/ripster/backend/logs/*.log {
daily
rotate 14
compress
missingok
notifempty
}
sqlite3 /opt/ripster/backend/data/ripster.db \
".backup '/var/backups/ripster-$(date +%Y%m%d).db'"
```
---
## Sicherheitshinweise
## Sicherheit
!!! warning "Heimnetz-Einsatz"
Ripster ist für den Einsatz im **lokalen Heimnetz** konzipiert und enthält **keine Authentifizierung**. Stelle sicher, dass der Dienst nicht öffentlich erreichbar ist.
Falls öffentlicher Zugang benötigt wird:
1. **Basic Auth** via nginx:
```bash
sudo htpasswd -c /etc/nginx/.htpasswd michael
```
```nginx
location / {
auth_basic "Ripster";
auth_basic_user_file /etc/nginx/.htpasswd;
# ...
}
```
2. **VPN-Zugang** (empfohlen): Zugriff nur über WireGuard/OpenVPN
3. **SSL/TLS**: Let's Encrypt mit certbot für HTTPS
- Ripster hat keine eingebaute Authentifizierung.
- Für externen Zugriff mindestens Basic Auth + TLS + Netzwerksegmentierung/VPN einsetzen.
- Secrets nicht ins Repo committen (`.env`, Settings-Felder).

View File

@@ -1,118 +1,99 @@
# Konfiguration
Alle Einstellungen werden über die Web-Oberfläche unter **Einstellungen** verwaltet und in der SQLite-Datenbank gespeichert.
Die Hauptkonfiguration erfolgt über die UI (`Settings`) und wird in SQLite gespeichert.
---
## Pflichteinstellungen
## Pflichteinstellungen vor dem ersten Rip
Diese Einstellungen müssen vor dem ersten Rip konfiguriert werden:
### Pfade
### 1) Pfade
| Einstellung | Beschreibung | Beispiel |
|------------|-------------|---------|
| `raw_dir` | Verzeichnis für rohe MKV-Dateien | `/mnt/nas/raw` |
| `movie_dir` | Ausgabeverzeichnis für kodierte Filme | `/mnt/nas/movies` |
| `log_dir` | Verzeichnis für Log-Dateien | `/var/log/ripster` |
| `raw_dir` | Basisverzeichnis für RAW-Rips | `/mnt/ripster/raw` |
| `movie_dir` | Basisverzeichnis für finale Encodes | `/mnt/ripster/movies` |
| `log_dir` | Verzeichnis für Prozess-/Backend-Logs | `/mnt/ripster/logs` |
!!! warning "Berechtigungen"
Der Ripster-Prozess benötigt **Schreibrechte** auf alle konfigurierten Verzeichnisse.
Optional profilspezifisch:
```bash
# Verzeichnisse erstellen und Berechtigungen setzen
sudo mkdir -p /mnt/nas/{raw,movies}
sudo chown $USER:$USER /mnt/nas/{raw,movies}
```
- `raw_dir_bluray`, `raw_dir_dvd`, `raw_dir_other`
- `movie_dir_bluray`, `movie_dir_dvd`, `movie_dir_other`
### OMDb API
### 2) Tools
| Einstellung | Standard |
|------------|---------|
| `makemkv_command` | `makemkvcon` |
| `handbrake_command` | `HandBrakeCLI` |
| `mediainfo_command` | `mediainfo` |
### 3) OMDb
| Einstellung | Beschreibung |
|------------|-------------|
| `omdb_api_key` | API-Key von omdbapi.com |
| `omdb_default_type` | Standard-Suchtyp: `movie` oder `series` |
| `omdb_default_type` | `movie`, `series`, `episode` |
---
## Tool-Konfiguration
## Encode-Konfiguration (wichtig)
| Einstellung | Standard | Beschreibung |
|------------|---------|-------------|
| `makemkv_command` | `makemkvcon` | Pfad oder Befehl für MakeMKV |
| `handbrake_command` | `HandBrakeCLI` | Pfad oder Befehl für HandBrake |
| `mediainfo_command` | `mediainfo` | Pfad oder Befehl für MediaInfo |
Ripster arbeitet profilspezifisch, typischerweise über:
!!! tip "Absolute Pfade"
Falls die Tools nicht im `PATH` sind, verwende absolute Pfade:
```
/usr/local/bin/HandBrakeCLI
```
- Blu-ray: `handbrake_preset_bluray`, `handbrake_extra_args_bluray`, `output_extension_bluray`, `filename_template_bluray`
- DVD: `handbrake_preset_dvd`, `handbrake_extra_args_dvd`, `output_extension_dvd`, `filename_template_dvd`
---
### Template-Platzhalter
## Encoding-Konfiguration
Verfügbar in `filename_template_*` und `output_folder_template_*`:
| Einstellung | Standard | Beschreibung |
|------------|---------|-------------|
| `handbrake_preset` | `H.265 MKV 1080p30` | HandBrake-Preset-Name |
| `handbrake_extra_args` | _(leer)_ | Zusätzliche HandBrake-Argumente |
| `output_extension` | `mkv` | Dateiendung der Ausgabedatei |
| `filename_template` | `{title} ({year})` | Template für Dateinamen |
- `${title}`
- `${year}`
- `${imdbId}`
### Dateiname-Template
Beispiel:
Das Template unterstützt folgende Platzhalter:
| Platzhalter | Beschreibung | Beispiel |
|------------|-------------|---------|
| `{title}` | Filmtitel | `Inception` |
| `{year}` | Erscheinungsjahr | `2010` |
| `{imdb_id}` | IMDb-ID | `tt1375666` |
| `{type}` | `movie` oder `series` | `movie` |
**Beispiel-Template:**
```
{title} ({year})
→ Inception (2010).mkv
```text
${title} (${year})
-> Inception (2010).mkv
```
---
## Laufwerk-Konfiguration
## MakeMKV-spezifisch
| Einstellung | Standard | Beschreibung |
|------------|---------|-------------|
| `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` | `4000` | Polling-Intervall in Millisekunden |
| Einstellung | Standard | Hinweis |
|------------|---------|--------|
| `makemkv_min_length_minutes` | `60` | Kandidaten-Filter |
| `makemkv_rip_mode_bluray` | `backup` | `mkv` oder `backup` |
| `makemkv_rip_mode_dvd` | `mkv` | `mkv` oder `backup` |
| `makemkv_registration_key` | leer | optional, wird via `makemkvcon reg` gesetzt |
---
## MakeMKV-Konfiguration
## Monitoring & Queue
| Einstellung | Standard | Beschreibung |
|------------|---------|-------------|
| `makemkv_min_length_minutes` | `15` | Mindestlänge für Titel in Minuten |
| `makemkv_backup_mode` | `false` | Backup-Modus statt MKV-Modus |
!!! info "Backup-Modus"
Im Backup-Modus erstellt MakeMKV eine vollständige Kopie der Disc (inkl. Menüs). Der Standardmodus erstellt direkt MKV-Dateien.
| Einstellung | Standard |
|------------|---------|
| `hardware_monitoring_enabled` | `true` |
| `hardware_monitoring_interval_ms` | `5000` |
| `pipeline_max_parallel_jobs` | `1` |
---
## Benachrichtigungen (PushOver)
## PushOver (optional)
| Einstellung | Beschreibung |
|------------|-------------|
| `pushover_user_key` | Dein PushOver User-Key |
| `pushover_api_token` | API-Token deiner PushOver-App |
Basis:
Nach der Eingabe kann die Verbindung mit dem **Test-Button** geprüft werden.
- `pushover_enabled`
- `pushover_token`
- `pushover_user`
Zusätzlich pro Event ein/aus (z. B. `pushover_notify_job_finished`).
---
## Vollständige Einstellungsreferenz
## Verwandte Doku
Eine vollständige Liste aller Einstellungen mit Typen, Validierung und Standardwerten findest du unter:
[:octicons-arrow-right-24: Einstellungsreferenz](../configuration/settings-reference.md)
- [Einstellungsreferenz](../configuration/settings-reference.md)
- [Umgebungsvariablen](../configuration/environment.md)

View File

@@ -11,50 +11,46 @@ cd ripster
---
## Automatischer Start
Ripster enthält ein `start.sh`-Skript, das alle Abhängigkeiten installiert und Backend + Frontend gleichzeitig startet:
## Dev-Start (empfohlen)
```bash
./start.sh
```
Das Skript führt automatisch folgende Schritte durch:
`start.sh`:
1. **Node.js-Versionscheck** prüft ob >= 20.19.0 verfügbar ist (mit nvm/npx-Fallback)
2. **Abhängigkeiten installieren** `npm install` für Root, Backend und Frontend
3. **Dienste starten** Backend und Frontend werden parallel gestartet
1. prüft Node-Version (`>= 20.19.0`)
2. installiert Dependencies (Root/Backend/Frontend)
3. startet Backend + Frontend parallel
!!! success "Erfolgreich gestartet"
- Backend läuft auf `http://localhost:3001`
- Frontend läuft auf `http://localhost:5173`
Danach:
- Backend: `http://localhost:3001`
- Frontend: `http://localhost:5173`
Stoppen: mit `Ctrl+C` im laufenden Terminal.
---
## Manuelle Installation
Falls du mehr Kontrolle benötigst:
## Manuell starten
```bash
# Root-Abhängigkeiten
npm install
npm --prefix backend install
npm --prefix frontend install
npm run dev
```
# Backend-Abhängigkeiten
cd backend && npm install && cd ..
Oder getrennt:
# Frontend-Abhängigkeiten
cd frontend && npm install && cd ..
# Backend starten (Terminal 1)
cd backend && npm run dev
# Frontend starten (Terminal 2)
cd frontend && npm run dev
```bash
npm run dev:backend
npm run dev:frontend
```
---
## Umgebungsvariablen konfigurieren
## Optional: .env-Dateien anlegen
### Backend
@@ -62,13 +58,13 @@ cd frontend && npm run dev
cp backend/.env.example backend/.env
```
Bearbeite `backend/.env`:
Beispiel:
```env
PORT=3001
DB_PATH=./data/ripster.db
CORS_ORIGIN=http://localhost:5173
LOG_DIR=./logs
CORS_ORIGIN=http://localhost:5173
LOG_LEVEL=info
```
@@ -78,63 +74,30 @@ LOG_LEVEL=info
cp frontend/.env.example frontend/.env
```
Bearbeite `frontend/.env`:
Beispiel:
```env
VITE_API_BASE=http://localhost:3001
VITE_WS_URL=ws://localhost:3001
```
!!! tip "Alle Umgebungsvariablen"
Eine vollständige Übersicht aller Umgebungsvariablen findest du unter [Umgebungsvariablen](../configuration/environment.md).
---
## Datenbank initialisieren
Die SQLite-Datenbank wird **automatisch** beim ersten Start erstellt und mit dem Schema aus `db/schema.sql` initialisiert. Es sind keine manuellen Datenbankschritte erforderlich.
```
backend/data/
└── ripster.db ← Wird automatisch angelegt
VITE_API_BASE=/api
# optional:
# VITE_WS_URL=ws://localhost:3001/ws
```
---
## Stoppen
## Datenbank
```bash
./kill.sh
SQLite wird automatisch beim Backend-Start initialisiert:
```text
backend/data/ripster.db
```
Das Skript beendet Backend- und Frontend-Prozesse graceful.
---
## Verzeichnisstruktur nach Installation
```
ripster/
├── backend/
│ ├── data/ ← SQLite-Datenbank (nach erstem Start)
│ ├── logs/ ← Log-Dateien
│ ├── node_modules/ ← Backend-Abhängigkeiten
│ └── .env ← Backend-Konfiguration
├── frontend/
│ ├── node_modules/ ← Frontend-Abhängigkeiten
│ ├── dist/ ← Production-Build (nach npm run build)
│ └── .env ← Frontend-Konfiguration
└── node_modules/ ← Root-Abhängigkeiten (concurrently etc.)
```
Schema-Quelle: `db/schema.sql`
---
## Nächste Schritte
Nach erfolgreicher Installation:
1. Öffne [http://localhost:5173](http://localhost:5173)
2. Navigiere zu **Einstellungen**
3. Konfiguriere Pfade, API-Keys und Encoding-Presets
[:octicons-arrow-right-24: Zur Konfiguration](configuration.md)
1. Browser öffnen: `http://localhost:5173`
2. In `Settings` Pfade/Tools/API-Keys prüfen
3. Erste Disc einlegen und Workflow starten

View File

@@ -145,15 +145,6 @@ Für mobile Push-Benachrichtigungen bei Fertigstellung oder Fehlern:
- App kaufen auf [pushover.net](https://pushover.net) (~5 USD einmalig)
- **User Key** und **API Token** notieren
### SSH-Zugang (Deployment)
Für Remote-Deployment via `deploy-ripster.sh`:
```bash
# sshpass installieren
sudo apt-get install sshpass
```
---
## Checkliste

View File

@@ -1,411 +1,114 @@
# Schnellstart Vollständiger Workflow
# Schnellstart Erster kompletter Job
Nach der [Installation](installation.md) und [Konfiguration](configuration.md) führt diese Seite Schritt für Schritt durch den ersten Rip mit allen Details aus dem Code.
Diese Seite führt durch den typischen ersten Lauf.
---
## Übersicht: Pipeline-Ablauf
<div class="pipeline-steps">
<div class="pipeline-step">
<div class="pipeline-step-badge step-idle">●</div>
<div class="pipeline-step-label">IDLE</div>
<div class="pipeline-step-sub">Warten</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-idle">1</div>
<div class="pipeline-step-label">DISC_DETECTED</div>
<div class="pipeline-step-sub">Disc erkannt</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-running">2</div>
<div class="pipeline-step-label">METADATA_SELECTION</div>
<div class="pipeline-step-sub">OMDb &amp; Dialog</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-wait">⚠</div>
<div class="pipeline-step-label">WAITING_FOR_USER_DECISION</div>
<div class="pipeline-step-sub">Playlist wählen<br><em>(nur bei Obfusk.)</em></div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-user">3</div>
<div class="pipeline-step-label">READY_TO_START</div>
<div class="pipeline-step-sub">Bereit</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-running">4</div>
<div class="pipeline-step-label">RIPPING</div>
<div class="pipeline-step-sub">MakeMKV</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-running">5</div>
<div class="pipeline-step-label">MEDIAINFO_CHECK</div>
<div class="pipeline-step-sub">HandBrake-Scan</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-user">6</div>
<div class="pipeline-step-label">READY_TO_ENCODE</div>
<div class="pipeline-step-sub">Track-Review</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-encode">7</div>
<div class="pipeline-step-label">ENCODING</div>
<div class="pipeline-step-sub">HandBrake</div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-encode">8*</div>
<div class="pipeline-step-label">POST-ENCODE</div>
<div class="pipeline-step-sub">Skripte<br><em>(innerhalb ENCODING)</em></div>
</div>
<div class="pipeline-step">
<div class="pipeline-step-badge step-done">✓</div>
<div class="pipeline-step-label">FINISHED</div>
<div class="pipeline-step-sub">Fertig</div>
</div>
</div>
**Legende:** <span style="color:#546e7a">● Warten</span> &nbsp;|&nbsp; <span style="color:#1565c0">■ Läuft automatisch</span> &nbsp;|&nbsp; <span style="color:#3949ab">■ Benutzeraktion</span> &nbsp;|&nbsp; <span style="color:#e65100">⚠ Optional</span> &nbsp;|&nbsp; <span style="color:#6a1b9a">■ Encodierung</span> &nbsp;|&nbsp; <span style="color:#2e7d32">✓ Fertig</span>
??? note "Vollständiges Zustandsdiagramm (inkl. Fehler- &amp; Alternativpfade)"
<div class="pipeline-diagram">
```mermaid
flowchart LR
START(( )) --> IDLE
IDLE -->|Disc erkannt| DD[DISC_DETECTED]
DD -->|Analyse starten| META[METADATA\nSELECTION]
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]
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 -->|Encoding starten| ENC[ENCODING]
ENC -->|inkl. Post-Skripte| FIN([FINISHED])
ENC -->|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 ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
```
</div>
---
## Schritt 1 Ripster starten
## 1) Starten
```bash
cd ripster
./start.sh
```
Öffne [http://localhost:5173](http://localhost:5173) im Browser. Das Dashboard zeigt `IDLE`.
Öffne `http://localhost:5173`.
---
## Schritt 2 Disc einlegen → `DISC_DETECTED`
## 2) Disc einlegen
Lege eine DVD oder Blu-ray ein. Der `diskDetectionService` pollt das Laufwerk alle `disc_poll_interval_ms` Millisekunden (Standard: 4 Sekunden).
Pipeline wechselt auf `DISC_DETECTED`.
**Was passiert im Code:**
- `diskDetectionService` emittiert `discInserted` mit Geräteinformationen
- `pipelineService.onDiscInserted()` wird aufgerufen
- Dashboard-Status-Badge zeigt **"Medium erkannt"**
- Status-Text zeigt **"Neue Disk erkannt"**
- Der **"Analyse starten"**-Button wird aktiv
!!! tip "Manuelle Auslösung"
Falls die automatische Erkennung nicht greift:
```bash
curl -X POST http://localhost:3001/api/pipeline/analyze
```
---
## Schritt 3 Analyse starten → `METADATA_SELECTION`
Klicke auf **"Analyse starten"**.
**Was passiert im Code:**
1. Ein neuer Job-Datensatz wird in der Datenbank angelegt (`status: METADATA_SELECTION`)
2. Ripster versucht, den Titel automatisch aus dem Disc-Label/Modell zu ermitteln
3. Mit diesem erkannten Titel wird sofort eine **OMDb-Suche** ausgelöst
4. Der `MetadataSelectionDialog` öffnet sich im Frontend mit den vorgeladenen Suchergebnissen
**Erkannter Titel:** Der Disc-Label (z. B. `INCEPTION`) wird als Suchbegriff verwendet. Falls kein Label vorhanden, bleibt das Suchfeld leer.
---
## Schritt 4 Metadaten auswählen (`MetadataSelectionDialog`)
Der Dialog zeigt vorgeladene OMDb-Suchergebnisse. Du kannst:
### 4a) OMDb-Suchergebnis wählen
```
┌─────────────────────────────────────────────────┐
│ Suche: [Inception ] 🔍 │
├─────────────────────────────────────────────────┤
│ ▶ Inception (2010) · Movie · tt1375666 │
│ Inception: ... · Series · ... │
├─────────────────────────────────────────────────┤
│ [Auswahl übernehmen] │
└─────────────────────────────────────────────────┘
```
- Suche durch Titel anpassen und Enter drücken
- Typ-Filter: `movie` / `series` umschalten möglich
- Einen Eintrag anklicken, dann **"Auswahl übernehmen"**
### 4b) Manuelle Eingabe (ohne OMDb)
Falls kein passendes Ergebnis gefunden wird:
- Titel, Jahr und IMDb-ID manuell eingeben
- OMDb-Poster wird übersprungen
**Was passiert nach Bestätigung:**
Ripster ruft `pipelineService.selectMetadata()` auf und startet den nächsten Schritt automatisch:
- 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 Optional: Playlist-Auswahl → `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):
```
┌───────────────────────────────────────────────────────────────┐
│ Playlist-Auswahl │
│ Es wurden mehrere Titel mit ähnlicher Laufzeit gefunden. │
│ Bitte wähle die korrekte Playlist: │
├───────────┬──────────┬────────┬──────────────────────────────┤
│ Playlist │ Laufzeit │ Score │ Bewertung │
├───────────┼──────────┼────────┼──────────────────────────────┤
│ ● 00800 │ 2:28:05 │ +18 │ wahrscheinlich korrekt │
│ │ │ │ (lineare Segmentfolge) │
├───────────┼──────────┼────────┼──────────────────────────────┤
│ ○ 00801 │ 2:28:12 │ 4 │ Auffällige Segmentreihenfolge │
├───────────┼──────────┼────────┼──────────────────────────────┤
│ ○ 00900 │ 2:28:05 │ 32 │ Fake-Struktur │
│ │ │ │ (alternierendes Sprungmuster) │
└───────────┴──────────┴────────┴──────────────────────────────┘
847 Playlists insgesamt · 3 relevante Kandidaten (≥ 15 min)
Empfehlung: 00800 (vorausgewählt)
[Playlist übernehmen]
```
- Die empfohlene Playlist ist **vorausgewählt** (Checkbox)
- Score und Bewertungslabel helfen bei der Entscheidung
- 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 → `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
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):**
Falls nötig manuell neu scannen:
```bash
makemkvcon mkv disc:0 all /mnt/raw/Inception-2010/ \
--minlength=900 -r
curl -X POST http://localhost:3001/api/pipeline/rescan-disc
```
**Was MakeMKV ausführt (Backup-Modus):**
---
## 3) Analyse starten
Klicke im Dashboard auf `Analyse starten`.
Intern:
- Job wird angelegt
- MakeMKV-Analyse läuft (`ANALYZING`)
- UI wechselt in Metadatenauswahl (`METADATA_SELECTION`)
---
## 4) Metadaten bestätigen
Im Dialog:
- OMDb-Ergebnis wählen oder manuell eintragen
- bei Playlist-Abfrage ggf. `selectedPlaylist` wählen
Nach Bestätigung startet Ripster automatisch weiter.
---
## 5) Pipeline-Pfade
Abhängig von Job/RAW-Situation:
- **kein RAW vorhanden** -> `RIPPING`
- **RAW vorhanden** -> `MEDIAINFO_CHECK`
- **mehrdeutige Playlist** -> `WAITING_FOR_USER_DECISION`
Wenn Parallel-Limit erreicht ist, wird der Job in die Queue eingereiht.
---
## 6) Review (`READY_TO_ENCODE`)
Im Review-Panel:
- Titel auswählen (falls mehrere)
- Audio-/Subtitle-Tracks auswählen
- optional User-Preset anwenden
- optional Pre-/Post-Skripte und Ketten hinzufügen
Mit `Encoding starten` wird `confirm-encode` + Start ausgelöst.
---
## 7) Encoding (`ENCODING`)
Während Encoding:
- Live-Fortschritt/ETA über WebSocket
- Pre-Encode-Ausführungen laufen vor HandBrake
- Post-Encode-Ausführungen laufen nach HandBrake
Wichtig:
- Pre-Encode-Fehler -> Job endet in `ERROR`
- Post-Encode-Fehler -> Job kann `FINISHED` bleiben, aber mit Fehlerhinweis im Status/Log
---
## 8) Abschluss (`FINISHED`)
Ergebnis:
- Ausgabe in `movie_dir` (ggf. profilspezifisch)
- Job in Historie sichtbar
- Logs im konfigurierten `log_dir`
---
## Nützliche API-Shortcuts
```bash
makemkvcon backup disc:0 /mnt/raw/Inception-2010-backup/ \
--decrypt -r
# Pipeline-Snapshot
curl http://localhost:3001/api/pipeline/state
# Queue-Snapshot
curl http://localhost:3001/api/pipeline/queue
# Jobs
curl http://localhost:3001/api/history
```
**Live-Fortschritt** wird aus der MakeMKV-Ausgabe geparst:
```
PRGV:2048,0,65536 → Fortschritt wird berechnet und per WebSocket gesendet
PRGT:5011,0,"Sichern..." → Aktueller Task-Name
```
**Typische Dauer:**
- DVD: 2045 Minuten
- Blu-ray: 45120 Minuten
---
## Schritt 7 Track-Review → `READY_TO_ENCODE`
Nach dem Ripping, nach Playlist-Übernahme oder direkt bei vorhandenem RAW startet der **HandBrake-Scan**:
```bash
HandBrakeCLI --scan -i <quelle> -t 0
```
Dieser Scan liest alle Tracks aus ohne zu encodieren. Ripster baut daraus den Encode-Plan mit automatischer Vorauswahl:
**Status: `MEDIAINFO_CHECK`** läuft automatisch, kein Benutzereingriff
Danach öffnet sich das **Encode-Review-Panel** (`READY_TO_ENCODE`):
```
┌─────────────────────────────────────────────────────────────────┐
│ Encode-Review │
│ Titel: Disc Title 1 · Laufzeit: 2:28:05 · 28 Kapitel │
├─────────────────────────────────────────────────────────────────┤
│ Audio-Spuren │
├──────┬─────────────────────────────┬───────────────────────────┤
│ ☑ │ Track 1: English (AC3, 5.1) │ Copy (ac3) │
│ ☑ │ Track 2: Deutsch (DTS, 5.1) │ Fallback Transcode (av_aac)│
│ ☐ │ Track 3: Français (AC3, 2.0) │ Nicht übernommen │
├──────┴─────────────────────────────┴───────────────────────────┤
│ Untertitel-Spuren │
├──────┬─────────────────────────────┬────────┬──────┬──────────┤
│ ☑ │ Track 1: Deutsch │ Einbr.☐ │Forc.☐│Default☑ │
│ ☐ │ Track 2: English │ Einbr.☐ │Forc.☐│Default☐ │
├──────┴─────────────────────────────┴────────┴──────┴──────────┤
│ [Encoding starten] │
└─────────────────────────────────────────────────────────────────┘
```
### Audio-Track-Aktionen verstehen
| Symbol/Text | Bedeutung |
|------------|-----------|
| `Copy (ac3)` | Track wird **verlustfrei** direkt übernommen |
| `Copy (truehd)` | TrueHD-Track wird direkt übernommen |
| `Transcode (av_aac)` | Track wird zu AAC umgewandelt |
| `Fallback Transcode (av_aac)` | Copy nicht möglich → automatisch zu AAC |
| `Preset-Default (HandBrake)` | HandBrake-Preset entscheidet |
| `Nicht übernommen` | Track ist nicht ausgewählt |
### Untertitel-Flags
| Flag | Bedeutung |
|------|-----------|
| **Einbrennen** | Untertitel werden fest ins Video gebrannt (nur ein Track möglich) |
| **Forced** | Nur erzwungene Untertitel-Einblendungen übernehmen |
| **Default** | Diese Spur wird beim Abspielen automatisch aktiviert |
### Vorauswahl-Regeln
Die Tracks mit `` wurden nach der Regel aus den Einstellungen automatisch vorausgewählt (`selectedByRule: true`). Die Auswahl kann frei geändert werden.
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.
---
## Schritt 8 Encoding → `ENCODING`
HandBrake startet mit dem finalisierten Plan:
```bash
HandBrakeCLI \
-i /dev/sr0 \
-o "/mnt/movies/Inception (2010).mkv" \
-t 1 \
--preset "H.265 MKV 1080p30" \
-a 1,2 \
-E copy:ac3,av_aac \
-s 1 \
--subtitle-default 1
```
**Live-Fortschritt** wird aus HandBrake-stderr geparst:
```
Encoding: task 1 of 1, 73.50 % (45.23 fps, avg 44.12 fps, ETA 00h12m34s)
```
Das Dashboard zeigt:
- Fortschrittsbalken (0100 %)
- Aktuelle Encoding-Geschwindigkeit (FPS)
- Geschätzte Restzeit (ETA)
**Typische Dauer (abhängig von CPU/GPU und Preset):**
- Schnelles Preset (`fast`): 0.5× Echtzeit
- Standard-Preset: 13× Echtzeit
- Langsames Preset (`slow`): 510× Echtzeit
---
## Schritt 9 Fertig! → `FINISHED`
```
/mnt/nas/movies/
└── Inception (2010).mkv ✓ Encodierung abgeschlossen
```
- Job-Status in der Datenbank: `FINISHED`
- PushOver-Benachrichtigung (falls konfiguriert)
- Eintrag in der [History](http://localhost:5173/history) mit vollständigen Logs
---
## Fehlerbehandlung
### Job im Status `ERROR`
1. **Dashboard**: Details-Button → Log-Ausgabe prüfen
2. **Retry**: Job vom Fehlerzustand neu starten (behält Metadaten)
3. **History**: Vollständige Logs und Fehlerdetails
### Häufige Fehlerursachen
| Fehler | Ursache | Lösung |
|-------|---------|--------|
| MakeMKV: Lizenzfehler | Abgelaufene Beta-Lizenz | Neue Lizenz im [MakeMKV-Forum](https://www.makemkv.com/forum/viewtopic.php?t=1053) |
| HandBrake: Preset nicht gefunden | Preset-Name falsch | `HandBrakeCLI --preset-list` prüfen |
| Keine Disc erkannt | Laufwerk-Berechtigungen | `sudo chmod a+rw /dev/sr0` |
| Falsches Video (zerstückelt) | Falsche Playlist | Job re-encodieren mit anderer Playlist |
| OMDb: Keine Ergebnisse | API-Key fehlt oder Titel nicht gefunden | Einstellungen prüfen; manuell eingeben |
---
## Kurzübersicht aller Schritte
| # | Status | Benutzeraktion | Was Ripster tut |
|--|--------|---------------|----------------|
| 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 | 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 |

View File

@@ -1,329 +1,103 @@
# Encode-Planung & Track-Auswahl
`encodePlan.js` analysiert die HandBrake-Scan-Ausgabe, wählt Audio- und Untertitelspuren anhand von Regeln vor und erstellt einen vollständigen Encode-Plan für die Benutzer-Review.
Ripster erzeugt vor dem Encode einen `encodePlan` und lässt ihn im Review-Panel bestätigen.
---
## Ablauf im Pipeline-Kontext
## Ablauf
```
RIPPING abgeschlossen (oder Pre-Rip-Scan)
HandBrake --scan (alle Titel & Tracks einlesen)
buildTrackSelectors() ← Regeln aus Einstellungen ableiten
selectTrackIds() ← Tracks anhand Regeln vorauswählen
resolveAudioEncoderAction() ← Encoder-Aktion pro Track bestimmen
buildDiscScanReview() ← Vollständigen Encode-Plan erstellen
READY_TO_ENCODE ← Benutzer-Review im Frontend
applyManualTrackSelectionToPlan() ← Benutzer-Auswahl anwenden
ENCODING ← HandBrake-CLI mit finalem Plan starten
```text
Quelle bestimmen (Disc/RAW)
-> HandBrake-Scan (--scan --json)
-> Plan erstellen (Titel, Audio, Untertitel)
-> READY_TO_ENCODE
-> Benutzer bestätigt Auswahl
-> finaler HandBrake-Aufruf
```
---
## Phase 1: Pre-Rip Track-Scan
## Review-Inhalt (`READY_TO_ENCODE`)
Ripster führt einen **HandBrake-Scan** bereits **vor dem eigentlichen Ripping** durch:
```bash
HandBrakeCLI --scan -i /dev/sr0 -t 0
```
Dieser Scan liest alle Titel und deren Tracks aus der Disc (ohne zu encodieren). So kann der Benutzer die Track-Auswahl bereits vor dem zeitintensiven Rip-Prozess bestätigen.
!!! info "Pre-Rip vs. Post-Rip"
Ob der Scan vor oder nach dem Ripping passiert, hängt vom konfigurierten Modus ab. Bei direktem Disc-Zugriff ist Pre-Rip möglich; nach einem MakeMKV-Backup wird die entstandene `.mkv`-Datei gescannt.
- auswählbarer Encode-Titel
- Audio-Track-Selektion
- Untertitel-Track-Selektion inkl. Flags
- `burnIn`
- `forced`
- `defaultTrack`
- optionale User-Presets (HandBrake-Preset + Extra-Args)
- optionale Pre-/Post-Skripte und Ketten
---
## Phase 2: Track-Selektor-Regeln (`buildTrackSelectors`)
## Bestätigung (`confirm-encode`)
Die Regeln werden aus den HandBrake-Einstellungen abgeleitet. Es gibt fünf **Selektionsmodi**:
| Modus | Beschreibung |
|------|-------------|
| `none` | Keine Tracks dieser Art übernehmen |
| `first` | Nur den ersten Track übernehmen |
| `all` | Alle Tracks übernehmen |
| `language` | Nur Tracks in bestimmten Sprachen |
| `explicit` | Bestimmte Track-IDs explizit angeben |
Der aktive Modus wird aus den `handbrake_*`-Einstellungen und `handbrake_extra_args` abgeleitet. Explizite CLI-Argumente (`--audio`, `--audio-lang-list`) überschreiben die Basis-Konfiguration.
---
## Phase 3: Automatische Vorauswahl (`selectTrackIds`)
### Audio-Tracks
```
Modus 'none' → Keine Audio-Tracks
Modus 'all' → Alle Tracks (oder nur erster, wenn firstOnly)
Modus 'language' → Alle Tracks in den konfigurierten Sprachen
Modus 'explicit' → Nur die angegebenen Track-IDs
Modus 'first' → Nur Track 1
```
Jeder Audio-Track erhält das Feld `selectedByRule: true/false` dieses zeigt dem Benutzer, welche Tracks automatisch vorausgewählt wurden.
**Sprach-Normalisierung (`normalizeLanguage`):**
Alle Sprachcodes werden auf **ISO 639-2** (3-Buchstaben) normalisiert:
| Eingabe | Normalisiert |
|--------|-------------|
| `de`, `ger` | `deu` |
| `German` | `deu` |
| `en`, `eng` | `eng` |
| `English` | `eng` |
| `fr`, `fre` | `fra` |
| `ja`, `jpn` | `jpn` |
| Unbekannt | `und` |
### Untertitel-Tracks
Gleiche Modus-Logik wie Audio, aber mit **zusätzlichen Flags** pro Track:
| Flag | Bedeutung |
|------|-----------|
| `burnIn` | Untertitel in Video einbrennen (`--subtitle-burned`) |
| `forced` | Nur erzwungene Untertitel übernehmen (`--subtitle-forced`) |
| `defaultTrack` | Als Standard-Untertitelspur markieren (`--subtitle-default`) |
Diese Flags werden im Encode-Review als Checkboxen angezeigt.
---
## Phase 4: Encoder-Aktion bestimmen (`resolveAudioEncoderAction`)
Für jeden vorausgewählten Audio-Track bestimmt Ripster die Encoder-Aktion:
```
Encoder-Einstellung Codec-Support in Copy-Mask? Aktion
─────────────────────────────────────────────────────────────────────
Kein Encoder / 'preset-default' → preset-default HandBrake-Preset entscheidet
encoder.startsWith('copy')
UND Codec in audioCopyMask → copy Direktkopie (verlustfrei)
UND Codec NICHT in audioCopyMask→ fallback Transcode mit Fallback-Encoder
sonstiger Encoder → transcode Transcode mit explizitem Encoder
```
**Encoder-Aktionstypen:**
| Typ | Label (UI) | Qualität |
|----|-----------|---------|
| `preset-default` | `Preset-Default (HandBrake)` | HandBrake entscheidet |
| `copy` | `Copy (ac3)` | Verlustfrei |
| `fallback` | `Fallback Transcode (av_aac)` | Mit Qualitätsverlust |
| `transcode` | `Transcode (av_aac)` | Mit Qualitätsverlust |
**Copy-kompatible Codecs (Standard Copy-Mask):**
| Codec | Encoder-String |
|-------|---------------|
| AC-3 | `copy:ac3` |
| E-AC-3 | `copy:eac3` |
| AAC | `copy:aac` |
| MP3 | `copy:mp3` |
| TrueHD | `copy:truehd` |
| DTS | `copy:dts` *(nur mit spez. HandBrake-Build)* |
| DTS-HD | `copy:dtshd` *(nur mit spez. HandBrake-Build)* |
!!! warning "DTS im Standard-HandBrake"
Standard-HandBrake-Builds unterstützen kein DTS-Passthrough. DTS-Tracks werden dann automatisch auf den Fallback-Encoder umgestellt (Standard: `av_aac`).
---
## Phase 5: Encode-Plan-Struktur
Der vollständige Plan wird im Job-Datensatz als `encode_plan_json` gespeichert:
Typischer Payload:
```json
{
"mode": "pre_rip",
"preRip": true,
"encodeInputTitleId": 1,
"encodeInputPath": "disc-track-scan://title-1",
"selectors": {
"audio": { "mode": "language", "languages": ["deu", "eng"], "copyMask": ["copy:ac3", "copy:eac3"] },
"subtitle": { "mode": "none" }
},
"titles": [
{
"id": 1,
"fileName": "Disc Title 1",
"durationSeconds": 8885,
"selectedByMinLength": true,
"isEncodeInput": true,
"audioTracks": [
{
"id": 1,
"sourceTrackId": 1,
"language": "eng",
"languageLabel": "English",
"title": "5.1 Surround",
"format": "AC3",
"codecToken": "ac3",
"channels": "6",
"selectedByRule": true,
"selectedForEncode": true,
"encodePreviewActions": [
{ "type": "copy", "encoder": "copy:ac3", "label": "Copy (ac3)" }
],
"encodePreviewSummary": "Copy (ac3)"
},
{
"id": 2,
"sourceTrackId": 2,
"language": "deu",
"languageLabel": "Deutsch",
"format": "DTS",
"codecToken": "dts",
"channels": "6",
"selectedByRule": true,
"selectedForEncode": true,
"encodePreviewActions": [
{ "type": "fallback", "encoder": "av_aac", "label": "Fallback Transcode (av_aac)" }
],
"encodePreviewSummary": "Fallback Transcode (av_aac)"
},
{
"id": 3,
"language": "fra",
"languageLabel": "Français",
"selectedByRule": false,
"selectedForEncode": false,
"encodePreviewSummary": "Nicht übernommen"
}
],
"subtitleTracks": [
{
"id": 1,
"language": "deu",
"selectedByRule": true,
"selectedForEncode": true,
"burnIn": false,
"forced": false,
"defaultTrack": true,
"subtitlePreviewSummary": "Übernehmen",
"subtitlePreviewFlags": ["default"]
}
]
}
]
}
```
---
## Phase 6: Benutzer-Review im Frontend (`MediaInfoReviewPanel`)
Das Review-Panel zeigt:
```
┌─────────────────────────────────────────────────────────────────┐
│ Encode-Review Titel: Disc Title 1 │
│ Laufzeit: 2:28:05 │
├─────────────────────────────────────────────────────────────────┤
│ Audio-Spuren │
├──────┬──────────────────────────┬──────────────────────────────┤
│ [✓] │ Track 1: English (AC3) │ Copy (ac3) │
│ [✓] │ Track 2: Deutsch (DTS) │ Fallback Transcode (av_aac) │
│ [ ] │ Track 3: Français (DTS) │ Nicht übernommen │
├──────┴──────────────────────────┴──────────────────────────────┤
│ Untertitel-Spuren │
├──────┬──────────────────────────┬────────┬────────┬────────────┤
│ [✓] │ Track 1: Deutsch │Einbr.[ ]│Forced[ ]│Default[✓]│
│ [ ] │ Track 2: English │Einbr.[ ]│Forced[ ]│Default[ ]│
├──────┴──────────────────────────┴────────┴────────┴────────────┤
│ [Encoding starten] │
└─────────────────────────────────────────────────────────────────┘
```
Der Benutzer kann:
- **Audio-Tracks** per Checkbox aktivieren/deaktivieren
- **Untertitel-Flags** (Einbrennen, Forced, Default) setzen
- **Mehrere Titel** bei der Titleauswahl wechseln (für Discs mit mehreren Haupttiteln)
---
## Phase 7: Benutzer-Auswahl anwenden (`applyManualTrackSelectionToPlan`)
Im Frontend wird die Benutzer-Auswahl beim Klick auf **"Encoding starten"** (ggf. automatisch) bestätigt und dann auf den Plan angewendet:
```json
Payload: {
"selectedEncodeTitleId": 1,
"selectedTrackSelection": {
"1": {
"audioTrackIds": [1, 2],
"subtitleTrackIds": [1]
"subtitleTrackIds": [3]
}
}
},
"selectedPreEncodeScriptIds": [1],
"selectedPostEncodeScriptIds": [2],
"selectedPreEncodeChainIds": [3],
"selectedPostEncodeChainIds": [4],
"selectedUserPresetId": 5
}
```
Jeder Track erhält `selectedForEncode: true/false` entsprechend der Auswahl. Die Encoder-Aktionen (`encodeActions`) der nicht gewählten Tracks werden geleert.
Ripster speichert die bestätigte Auswahl in `jobs.encode_plan_json` und markiert `encode_review_confirmed = 1`.
---
## Phase 8: HandBrake-CLI-Befehl
## HandBrake-Aufruf
Aus dem finalisierten Plan baut Ripster den HandBrake-Aufruf:
Grundstruktur:
```bash
HandBrakeCLI \
-i /dev/sr0 \
-o "/mnt/movies/Inception (2010).mkv" \
-t 1 \
--preset "H.265 MKV 1080p30" \
-a 1,2 \
-E copy:ac3,av_aac \
-s 1 \
--subtitle-default 1
-i <input> \
-o <output> \
-t <titleId> \
-Z "<preset>" \
<extra-args> \
-a <audioTrackIds|none> \
-s <subtitleTrackIds|none>
```
| Argument | Quelle |
|---------|--------|
| `-i` | `encode_input_path` aus Job |
| `-o` | Ausgabepfad aus `filename_template` + `movie_dir` |
| `-t` | Gewählter Titel-Index |
| `-a` | Kommagetrennte Audio-Track-IDs der ausgewählten Tracks |
| `-E` | Kommagetrennte Encoder-Aktionen (eine pro Track, gleiche Reihenfolge wie `-a`) |
| `-s` | Kommagetrennte Untertitel-Track-IDs |
| `--subtitle-default` | Track-ID der als Default markierten Untertitelspur |
| `--preset` | `handbrake_preset`-Einstellung |
| Extras | `handbrake_extra_args`-Einstellung |
Untertitel-Flags werden bei Bedarf ergänzt:
- `--subtitle-burned=<id>`
- `--subtitle-default=<id>`
- `--subtitle-forced=<id>` oder `--subtitle-forced`
---
## Dateiname-Template
## Pre-/Post-Encode-Ausführungen
| Platzhalter | Wert | Beispiel |
|------------|------|---------|
| `{title}` | Filmtitel von OMDb | `Inception` |
| `{year}` | Erscheinungsjahr | `2010` |
| `{imdb_id}` | IMDb-ID | `tt1375666` |
| `{type}` | `movie` oder `series` | `movie` |
- Pre-Encode läuft vor HandBrake
- Post-Encode läuft nach HandBrake
Sonderzeichen (`:`, `/`, `?`, `*` etc.) werden automatisch aus dem Dateinamen entfernt.
Verhalten bei Fehlern:
- Pre-Encode-Fehler: Job wird als `ERROR` beendet (Encode startet nicht)
- Post-Encode-Fehler: Job kann `FINISHED` bleiben, enthält aber Fehlerhinweis/Script-Summary
---
## Re-Encoding
## Dateinamen/Ordner
Ein abgeschlossener Job kann ohne erneutes Ripping neu encodiert werden:
Der finale Outputpfad wird aus Settings-Templates aufgebaut.
1. Job in der **History** öffnen
2. **"Re-Encode"** klicken
3. Track-Auswahl anpassen (oder bestehende übernehmen)
4. Encoding startet mit den aktuellen `handbrake_*`-Einstellungen
Platzhalter:
Nützlich bei geänderten Presets, anderen Sprach-Präferenzen oder nach einem Einstellungs-Update.
- `${title}`
- `${year}`
- `${imdbId}`
Ungültige Dateizeichen werden sanitisiert.

View File

@@ -1,6 +1,6 @@
# Pipeline
Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
Der Pipeline-Bereich beschreibt den Kern-Workflow von Ripster.
<div class="grid cards" markdown>
@@ -8,7 +8,7 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
---
Der vollständige Ripping-Workflow mit allen Zustandsübergängen.
Zustände, Übergänge und Queue-Verhalten.
[:octicons-arrow-right-24: Workflow](workflow.md)
@@ -16,7 +16,7 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
---
Wie Ripster Audio- und Untertitel-Tracks analysiert und Encode-Pläne erstellt.
Wie Titel/Tracks für HandBrake vorbereitet und bestätigt werden.
[:octicons-arrow-right-24: Encoding](encoding.md)
@@ -24,16 +24,16 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
---
Erkennung von Blu-ray Playlist-Obfuskierung und Auswahl der korrekten Playlist.
Bewertung mehrdeutiger Blu-ray-Playlists und manuelle Entscheidung.
[:octicons-arrow-right-24: Playlist-Analyse](playlist-analysis.md)
- :material-script-text: **Post-Encode-Skripte**
- :material-script-text: **Encode-Skripte (Pre & Post)**
---
Automatische Ausführung von Shell-Skripten nach erfolgreichem Encoding z. B. zum Verschieben oder Benachrichtigen.
Skripte/Ketten vor und nach dem Encode ausführen.
[:octicons-arrow-right-24: Post-Encode-Skripte](post-encode-scripts.md)
[:octicons-arrow-right-24: Encode-Skripte](post-encode-scripts.md)
</div>

View File

@@ -1,217 +1,65 @@
# Playlist-Analyse
Einige Blu-rays verwenden **Playlist-Obfuskierung** als Kopierschutz. Ripster analysiert automatisch alle MakeMKV-Titel und empfiehlt die korrekte Playlist auf Basis eines Segment-Scoring-Algorithmus aus `playlistAnalysis.js`.
Ripster analysiert bei Blu-ray-ähnlichen Quellen Playlists und fordert bei Mehrdeutigkeit eine manuelle Auswahl an.
---
## Das Problem: Playlist-Obfuskierung
## Ziel
Moderne Blu-rays können Dutzende bis Hunderte von Titeln/Playlists enthalten. Der eigentliche Film steckt in genau einer davon alle anderen sind:
- **Kurze Dummy-Titel** (wenige Sekunden bis Minuten)
- **Titel mit verschachtelten Segmenten** (absichtlich versetzte Reihenfolge, sodass der Film falsch gerippt wird)
- **Titel gleicher Länge** (mehrere Playlists mit identischer Laufzeit, aber unterschiedlicher Segment-Reihenfolge)
Das Ziel der Obfuskierung: Ein einfacher Ripper wählt den erstbesten langen Titel und bekommt ein zerstückeltes, unbrauchbares Video.
Erkennen, welche Playlist wahrscheinlich der Hauptfilm ist, statt versehentlich eine Fake-/Dummy-Playlist zu verwenden.
---
## Wann wird die Analyse ausgelöst?
## Eingabedaten
Die Playlist-Analyse wird automatisch gestartet **sobald der Benutzer Metadaten bestätigt** (nach dem Metadaten-Dialog). Ripster ruft `makemkvcon` im Info-Modus auf und parst die TINFO-Ausgabe.
```
TINFO:<titleId>,26,"<segment-list>"
```
Feld **26** enthält die kommagetrennte Liste der Segment-Nummern in der Abspielreihenfolge des Titels.
Die Analyse basiert auf MakeMKV-Infos (u. a. Playlist-/Segment-Struktur, Laufzeiten, Titelzuordnung).
---
## Algorithmus im Detail (`playlistAnalysis.js`)
## Auswertung (vereinfacht)
### Schritt 1 Segment-Nummern parsen
Für Kandidaten werden u. a. berücksichtigt:
```
TINFO:1,26,"00000,00001,00002,00003" → [0, 1, 2, 3] linearer Film
TINFO:2,26,"00100,00050,00100,00051" → [100, 50, 100, 51] Fake-Playlist
```
- Laufzeit
- Segment-Reihenfolge
- Rückwärtssprünge/große Sprünge
- Kohärenz linearer Segmentfolgen
- Duplikatgruppen mit ähnlicher Laufzeit
### Schritt 2 Metriken berechnen (`computeSegmentMetrics`)
Daraus entstehen:
Für jedes aufeinanderfolgende Segment-Paar `[a, b]` wird `diff = b a` berechnet:
| Metrik | Bedingung | Bedeutung |
|--------|----------|-----------|
| `directSequenceSteps` | `diff == 1` | Aufeinanderfolgende Segmente → linearer Film |
| `backwardJumps` | `b < a` | Rückwärtssprünge → verdächtig |
| `largeJumps` | `\|diff\| > 20` | Große Sprünge → verdächtig |
| `alternatingPairs` | Große Sprünge mit **wechselndem Vorzeichen** | Hin-und-her-Muster → starker Fake-Indikator |
**Score-Formel:**
```
score = (directSequenceSteps × 2) (backwardJumps × 3) (largeJumps × 2)
```
**Konkrete Beispiele:**
| Segmentfolge | directSeq | backward | large | score | Ergebnis |
|-------------|-----------|----------|-------|-------|---------|
| `0,1,2,3,4,5` | 5 | 0 | 0 | +10 | Echter Film |
| `0,1,100,2,101,3` | 2 | 0 | 4 | -4 | Verdächtig |
| `50,10,60,11,70,12` | 0 | 3 | 3 | -15 | Fake |
### Schritt 3 Bewertungslabel vergeben (`buildEvaluationLabel`)
```
alternatingRatio = alternatingPairs / largeJumps
if alternatingRatio >= 0.55 AND alternatingPairs >= 3:
→ "Fake-Struktur (alternierendes Sprungmuster)"
else if backwardJumps > 0 OR largeJumps > 0:
→ "Auffällige Segmentreihenfolge"
else:
→ "wahrscheinlich korrekt (lineare Segmentfolge)"
```
### Schritt 4 Duplikat-Gruppen bilden (`buildSimilarityGroups`)
Alle Titel werden nach **ähnlicher Laufzeit** gruppiert (±90 Sekunden Toleranz). Gibt es mehrere Kandidaten mit ähnlicher Laufzeit, ist das ein klares Zeichen für Obfuskierung:
```
8 Titel mit ~148 Minuten Laufzeit → Duplikat-Gruppe
→ obfuscationDetected = true
```
### Schritt 5 Besten Kandidaten empfehlen (`scoreCandidates`)
Innerhalb der größten Duplikat-Gruppe werden alle Kandidaten sortiert nach:
1. `score` (höher = besser)
2. `sequenceCoherence` (Anteil linearer Segmentschritte)
3. Laufzeit (länger = besser)
4. Dateigröße (größer = besser als Tiebreaker)
Der **erste Kandidat** der sortierten Liste ist die Empfehlung.
### Schritt 6 Entscheidung erzwingen bei mehreren Kandidaten
Sobald nach `MIN_LENGTH_MINUTES` **mehr als eine** Playlist übrig bleibt, wird immer eine manuelle Auswahl verlangt:
```
candidateCount > 1 → manualDecisionRequired = true
candidateCount <= 1 → manualDecisionRequired = false
```
- `candidates`
- `evaluatedCandidates` (inkl. Score/Label)
- `recommendation`
- `manualDecisionRequired`
---
## Wann greift der Benutzer ein?
## Wann muss der Benutzer entscheiden?
```
obfuscationDetected = duplicateDurationGroups.length > 0
manualDecisionRequired = candidates.length > 1
```
Wenn nach Filterung mehr als ein relevanter Kandidat übrig bleibt, setzt Ripster `manualDecisionRequired = true` und wechselt auf:
| Ergebnis | Nächster Pipeline-Zustand | Aktion |
|---------|--------------------------|--------|
| Nur ein Kandidat nach Mindestlänge | `READY_TO_START` | Automatische Übernahme möglich |
| Mehrere Kandidaten nach Mindestlänge | `WAITING_FOR_USER_DECISION` | Benutzer muss Playlist auswählen |
- `WAITING_FOR_USER_DECISION`
Dann muss eine Playlist bestätigt werden, bevor der Workflow weiterläuft.
---
## Benutzeroberfläche: Playlist-Auswahl-Dialog
## Konfigurationseinfluss
Wenn `manualDecisionRequired = true`, öffnet sich der Playlist-Dialog **nach** dem Metadaten-Dialog:
| Key | Wirkung |
|-----|---------|
| `makemkv_min_length_minutes` | Mindestlaufzeit für Kandidaten |
```
┌───────────────────────────────────────────────────────────────────┐
│ Playlist-Auswahl │
├──────────┬──────────┬──────────┬────────────────────────────────┤
│ Playlist │ Laufzeit │ Score │ Bewertung │
├──────────┼──────────┼──────────┼────────────────────────────────┤
│ ★ 00800 │ 2:28:05 │ +18 │ wahrscheinlich korrekt │
│ │ │ │ (lineare Segmentfolge) │
├──────────┼──────────┼──────────┼────────────────────────────────┤
│ 00801 │ 2:28:12 │ 4 │ Auffällige Segmentreihenfolge │
├──────────┼──────────┼──────────┼────────────────────────────────┤
│ 00900 │ 2:28:05 │ 32 │ Fake-Struktur │
│ │ │ │ (alternierendes Sprungmuster) │
└──────────┴──────────┴──────────┴────────────────────────────────┘
Hinweis: 847 Playlists insgesamt. 3 relevante Kandidaten (≥ 15 min).
Empfehlung: 00800 (★)
```
- **★** markiert die empfohlene Playlist (vorausgewählt)
- Nur Titel ≥ `makemkv_min_length_minutes` erscheinen in der Liste
- Der Benutzer wählt per Radio-Button und klickt "Bestätigen"
- Erst nach dieser Bestätigung wechselt die Pipeline zu `READY_TO_START`
Default ist aktuell `60` Minuten.
---
## Vollständige Datenstruktur (`analyzeContext.playlistAnalysis`)
## UI-Verhalten
```json
{
"titles": [
{ "titleId": 1, "playlistId": "00800", "durationSeconds": 8885, "durationLabel": "2:28:05", "chapters": 28 }
],
"candidates": [
{ "titleId": 1, "playlistId": "00800", "durationSeconds": 8885 },
{ "titleId": 2, "playlistId": "00801", "durationSeconds": 8892 }
],
"evaluatedCandidates": [
{
"titleId": 1,
"playlistId": "00800",
"score": 18,
"sequenceCoherence": 0.95,
"evaluationLabel": "wahrscheinlich korrekt (lineare Segmentfolge)",
"metrics": {
"directSequenceSteps": 12,
"backwardJumps": 0,
"largeJumps": 1,
"alternatingPairs": 0
}
}
],
"duplicateDurationGroups": [
[
{ "titleId": 1, "playlistId": "00800" },
{ "titleId": 2, "playlistId": "00801" }
]
],
"recommendation": {
"titleId": 1,
"playlistId": "00800",
"score": 18,
"reason": "Höchster Segment-Score in der größten Laufzeit-Gruppe"
},
"obfuscationDetected": true,
"manualDecisionRequired": true
}
```
Bei manueller Entscheidung zeigt das Dashboard Kandidaten inkl. Score/Bewertung und markiert eine Empfehlung.
---
Nach Bestätigung:
## Konfiguration
| Einstellung | Standard | Wirkung |
|------------|---------|---------|
| `makemkv_min_length_minutes` | `15` | Titel kürzer als dieser Wert werden als Kandidaten ignoriert |
---
## Tipps bei Fehlempfehlung
!!! tip "Falsche Playlist gewählt?"
Wenn das resultierende Video zerstückelt ist:
1. Job in der **History** öffnen
2. **Re-Encode** starten diesmal eine andere Playlist wählen
3. Alternativ: Korrekte Playlist im [MakeMKV-Forum](https://www.makemkv.com/forum/) recherchieren
!!! info "Keine Segment-Daten verfügbar"
Bei DVDs oder älteren Blu-rays liefert MakeMKV manchmal keine Segmentinfos (TINFO-Feld 26 fehlt). In diesem Fall entfällt die Analyse und der erste Titel über der Mindestlänge wird automatisch verwendet.
- mit vorhandenem RAW -> zurück zu `MEDIAINFO_CHECK`
- ohne RAW -> Startpfad über `READY_TO_START`/`RIPPING`

View File

@@ -1,173 +1,70 @@
# Encode-Skripte (Pre & Post)
Ripster unterstützt **Pre-Encode-** und **Post-Encode-Ausführungen**: Beliebige Shell-Skripte oder Skript-Ketten können automatisch vor und/oder nach dem Encoding-Schritt laufen z. B. zum Vorbereiten von Verzeichnissen, Verschieben von Dateien oder Benachrichtigen externer Dienste.
Ripster kann Skripte und Skript-Ketten vor und nach dem Encode ausführen.
---
## Funktionsweise
## Ablauf
```
```text
READY_TO_ENCODE
[Pre-Encode-Ausführungen] ← Fehler? → Abbruch
Skript/Kette 1, 2, …
ENCODING
[Post-Encode-Ausführungen] ← Fehler? → Abbruch
Skript/Kette 1, 2, …
FINISHED
```
!!! warning "Abbruch bei Fehler"
Schlägt eine Ausführung fehl (Exit-Code ≠ 0), werden alle nachfolgenden Ausführungen der gleichen Phase **nicht mehr ausgeführt**.
Der Job bleibt im Abschlusszustand `FINISHED`; der Fehler wird in Log/Status-Text und im Summary festgehalten.
---
## Skript- und Ketten-Verwaltung
Skripte und Skript-Ketten werden über die **Einstellungen-Seite** angelegt und verwaltet. Die Reihenfolge in der Liste kann per **Drag & Drop** geändert werden und bleibt persistent gespeichert.
### Skript anlegen
Navigiere zu **Einstellungen → Skripte** und klicke **"Neues Skript"**:
| Feld | Beschreibung |
|------|-------------|
| **Name** | Anzeigename des Skripts (z. B. `Zu Plex verschieben`) |
| **Befehl** | Shell-Befehl oder Skriptpfad (z. B. `/home/michael/scripts/move-to-plex.sh`) |
| **Beschreibung** | Optionale Erklärung |
### Skript-Ketten
Eine **Skript-Kette** fasst mehrere Skripte zu einer benannten Einheit zusammen, die als ganzes ausgewählt werden kann. Nützlich für wiederkehrende Kombinationen (z. B. „Move + Notify Plex + Webhook"). Ketten werden genauso wie einzelne Skripte im Review-Panel ausgewählt.
### Verfügbare Umgebungsvariablen
Jedes Skript wird mit folgenden Umgebungsvariablen aufgerufen:
| Variable | Inhalt | Beispiel |
|---------|--------|---------|
| `RIPSTER_OUTPUT_PATH` | Absoluter Pfad der encodierten Datei | `/mnt/movies/Inception (2010).mkv` |
| `RIPSTER_JOB_ID` | Job-ID in der Datenbank | `42` |
| `RIPSTER_TITLE` | Filmtitel | `Inception` |
| `RIPSTER_YEAR` | Erscheinungsjahr | `2010` |
| `RIPSTER_IMDB_ID` | IMDb-ID | `tt1375666` |
| `RIPSTER_RAW_PATH` | Pfad zur Raw-MKV-Datei | `/mnt/raw/Inception-2010/t00.mkv` |
### Beispiel-Skript: Datei nach Jellyfin verschieben
```bash
#!/bin/bash
# /home/michael/scripts/move-to-jellyfin.sh
TARGET_DIR="/mnt/media/movies"
mkdir -p "$TARGET_DIR"
mv "$RIPSTER_OUTPUT_PATH" "$TARGET_DIR/"
echo "Verschoben: $RIPSTER_TITLE nach $TARGET_DIR"
```
### Beispiel-Skript: Webhook auslösen
```bash
#!/bin/bash
# /home/michael/scripts/notify-webhook.sh
curl -s -X POST https://mein-webhook.example.com/ripster \
-H "Content-Type: application/json" \
-d "{\"title\": \"$RIPSTER_TITLE\", \"year\": \"$RIPSTER_YEAR\", \"path\": \"$RIPSTER_OUTPUT_PATH\"}"
-> Pre-Encode Skripte/Ketten
-> HandBrake Encoding
-> Post-Encode Skripte/Ketten
-> FINISHED oder ERROR
```
---
## Im Encode-Review auswählen
## Auswahl im Review
Im `READY_TO_ENCODE`-Zustand zeigt das **MediaInfoReviewPanel** zwei Abschnitte:
Im Review-Panel kannst du getrennt wählen:
```
┌──────────────────────────────────────────────────────────┐
Pre-Encode Ausführungen (optional) │
├──────────────────────────────────────────────────────────┤
│ ≡ 1. Verzeichnis vorbereiten (Skript) [Entfernen]│
├──────────────────────────────────────────────────────────┤
│ Hinzufügen: [Skript/Kette auswählen ▾] [+ Hinzuf.]│
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Post-Encode Ausführungen (optional) │
├──────────────────────────────────────────────────────────┤
│ ≡ 1. Zu Plex verschieben (Skript) [Entfernen]│
│ ≡ 2. Notify-Kette (Kette) [Entfernen]│
├──────────────────────────────────────────────────────────┤
│ Hinzufügen: [Skript/Kette auswählen ▾] [+ Hinzuf.]│
└──────────────────────────────────────────────────────────┘
```
- **Pre-Encode** und **Post-Encode** werden separat konfiguriert
- Sowohl **einzelne Skripte** als auch **Skript-Ketten** können in beiden Phasen ausgewählt werden
- **Reihenfolge** per Drag & Drop innerhalb jeder Phase ändern
- **Hinzufügen** aus der Dropdown-Liste aller konfigurierten Skripte und Ketten
- **Entfernen** einzelner Einträge
- Auswahl kann pro Job frei variiert werden
- `selectedPreEncodeScriptIds`
- `selectedPostEncodeScriptIds`
- `selectedPreEncodeChainIds`
- `selectedPostEncodeChainIds`
---
## Skript testen
## Fehlerverhalten
Über die Einstellungen kann jedes Skript mit einem Test-Job ausgeführt werden:
```http
POST /api/settings/scripts/:scriptId/test
```
Der Test-Aufruf befüllt die Umgebungsvariablen mit Platzhalter-Werten.
- Pre-Encode-Fehler stoppen die Kette und führen zu `ERROR`.
- Post-Encode-Fehler stoppen die restlichen Post-Schritte; Job kann dennoch `FINISHED` sein (mit Fehlerzusatz im Status/Log).
---
## Ausführungs-Ergebnis
## Verfügbare Umgebungsvariablen
Das Ergebnis der Skript-Ausführung wird im Job-Datensatz gespeichert und in der History angezeigt:
Beim Script-Run werden gesetzt:
```json
{
"postEncodeScripts": {
"configured": 2,
"attempted": 2,
"succeeded": 2,
"failed": 0,
"skipped": 0,
"aborted": false,
"results": [
{
"scriptId": 1,
"scriptName": "Zu Plex verschieben",
"status": "SUCCESS"
},
{
"scriptId": 2,
"scriptName": "Webhook auslösen",
"status": "SUCCESS"
}
]
}
}
```
| 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) |
| `aborted` | `true`, wenn die Kette abgebrochen wurde |
- `RIPSTER_SCRIPT_RUN_AT`
- `RIPSTER_JOB_ID`
- `RIPSTER_JOB_TITLE`
- `RIPSTER_MODE`
- `RIPSTER_INPUT_PATH`
- `RIPSTER_OUTPUT_PATH`
- `RIPSTER_RAW_PATH`
- `RIPSTER_SCRIPT_ID`
- `RIPSTER_SCRIPT_NAME`
- `RIPSTER_SCRIPT_SOURCE`
---
## API-Referenz
## Skript-Ketten
Eine vollständige API-Dokumentation der Skript-Endpunkte findest du unter:
Ketten unterstützen zwei Step-Typen:
[:octicons-arrow-right-24: Settings API Skripte](../api/settings.md#skript-verwaltung)
- `script` (führt ein hinterlegtes Skript aus)
- `wait` (wartet `waitSeconds`)
Bei Fehler in einem Script-Step wird die Kette abgebrochen.
---
## Testläufe
- Skript testen: `POST /api/settings/scripts/:id/test`
- Kette testen: `POST /api/settings/script-chains/:id/test`
Ergebnisse enthalten Erfolg/Exit-Code, Laufzeit und stdout/stderr.

View File

@@ -1,329 +1,87 @@
# Workflow & Zustände
Der Ripping-Workflow von Ripster ist als **State Machine** implementiert. Jeder Zustand hat klar definierte Übergangsbedingungen und Aktionen.
Ripster steuert den Ablauf als State-Machine im `pipelineService`.
---
## Zustandsdiagramm
<div class="pipeline-diagram">
## Zustandsdiagramm (vereinfacht)
```mermaid
flowchart LR
START(( )) --> IDLE
IDLE -->|Disc erkannt| DD[DISC_DETECTED]
DD -->|Analyse starten| META[METADATA\nSELECTION]
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]
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 -->|Encoding starten\n(bestätigt bei Bedarf automatisch)| ENC[ENCODING]
ENC -->|inkl. Post-Skripte| FIN([FINISHED])
ENC -->|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 ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
style MIC fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
IDLE --> DISC_DETECTED
DISC_DETECTED --> ANALYZING
ANALYZING --> METADATA_SELECTION
METADATA_SELECTION --> READY_TO_START
READY_TO_START --> RIPPING
READY_TO_START --> MEDIAINFO_CHECK
MEDIAINFO_CHECK --> WAITING_FOR_USER_DECISION
WAITING_FOR_USER_DECISION --> MEDIAINFO_CHECK
MEDIAINFO_CHECK --> READY_TO_ENCODE
READY_TO_ENCODE --> ENCODING
ENCODING --> FINISHED
ENCODING --> ERROR
RIPPING --> ERROR
RIPPING --> CANCELLED
```
</div>
---
## State-Liste
| State | Bedeutung |
|------|-----------|
| `IDLE` | Wartet auf Disc |
| `DISC_DETECTED` | Disc erkannt |
| `ANALYZING` | MakeMKV-Analyse läuft |
| `METADATA_SELECTION` | Benutzer wählt Metadaten |
| `WAITING_FOR_USER_DECISION` | Playlist-Auswahl nötig |
| `READY_TO_START` | Übergangszustand vor Start |
| `RIPPING` | MakeMKV-Rip läuft |
| `MEDIAINFO_CHECK` | Quelle/Tracks werden ausgewertet |
| `READY_TO_ENCODE` | Review ist bereit |
| `ENCODING` | HandBrake läuft |
| `FINISHED` | erfolgreich abgeschlossen |
| `CANCELLED` | abgebrochen |
| `ERROR` | fehlgeschlagen |
---
## UI-Badge-Bezeichnungen
## Typische Pfade
Die Status-Badges im Dashboard verwenden diese Labels:
### Standardfall (kein vorhandenes RAW)
| 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` |
1. Disc erkannt
2. Analyse + Metadaten
3. `RIPPING`
4. `MEDIAINFO_CHECK`
5. `READY_TO_ENCODE`
6. `ENCODING`
7. `FINISHED`
### Vorhandenes RAW
`READY_TO_START` springt direkt zu `MEDIAINFO_CHECK` (kein neuer Rip).
### Mehrdeutige Blu-ray-Playlist
`MEDIAINFO_CHECK` -> `WAITING_FOR_USER_DECISION` bis Benutzer Playlist bestätigt.
---
## Zustandsbeschreibungen
## Queue-Verhalten
### IDLE
Wenn `pipeline_max_parallel_jobs` erreicht ist:
**Ausgangszustand.** Ripster wartet auf eine Disc.
- `diskDetectionService` pollt das Laufwerk im konfigurierten Intervall
- Bei Disc-Erkennung: automatischer Übergang zu `DISC_DETECTED`
- WebSocket-Event: `DISC_DETECTED`
- Job-Aktionen werden als Queue-Einträge abgelegt
- Queue kann zusätzlich Nicht-Job-Einträge enthalten (`script`, `chain`, `wait`)
- Reihenfolge ist per API/UI änderbar
---
### DISC_DETECTED
## Abbruch, Retry, Restart
**Disc erkannt, wartet auf Benutzeraktion.**
- Dashboard-Badge: **"Medium erkannt"**
- Status-Text: **"Neue Disk erkannt"**
- **"Analyse starten"**-Button wird aktiv
- Kein Prozess läuft noch
**Übergang:** Benutzer klickt "Analyse starten" → `METADATA_SELECTION`
---
### METADATA_SELECTION
**Metadaten-Auswahl läuft.**
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 wird der Job automatisch für Start/Queue vorbereitet (`selectMetadata` + `startPreparedJob`)
**Übergang (automatisch nach Metadaten-Bestätigung):**
| Ergebnis | Nächster Zustand |
|--------------------|-----------------|
| 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` |
---
### WAITING_FOR_USER_DECISION
**Playlist-Obfuskierung erkannt manuelle Auswahl erforderlich.**
!!! info "Neu seit „Skript Integration + UI Anpassungen""
Dieser Zustand wurde eingeführt, um Blu-rays mit mehreren Playlists ähnlicher Länge korrekt zu behandeln.
- Playlist-Auswahl-Dialog wird im Dashboard angezeigt
- 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:**
```
┌──────────────────────────────────────────────────────────┐
│ Playlist-Auswahl erforderlich │
│ Es wurden mehrere Titel mit ähnlicher Laufzeit gefunden. │
├──────────┬──────────┬────────┬──────────────────────────┤
│ Playlist │ Laufzeit │ Score │ Bewertung │
├──────────┼──────────┼────────┼──────────────────────────┤
│ ● 00800 │ 2:28:05 │ +18 │ wahrscheinlich korrekt │
│ ○ 00801 │ 2:28:12 │ 4 │ Auffällige Segmentfolge │
│ ○ 00900 │ 2:28:05 │ 32 │ Fake-Struktur │
└──────────┴──────────┴────────┴──────────────────────────┘
[Playlist übernehmen]
```
**Ü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)
---
### READY_TO_START
**Übergangs-/Fallback-Zustand vor dem eigentlichen Start.**
- 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 ein verwertbares RAW unter `raw_dir` existiert, wird Ripping übersprungen und direkt `MEDIAINFO_CHECK` gestartet.
**Übergang:** `startPreparedJob(jobId)``RIPPING` oder direkt `MEDIAINFO_CHECK`
---
### RIPPING
**MakeMKV rippt die Disc.**
=== "MKV-Modus (Standard)"
```bash
makemkvcon mkv disc:0 all /path/to/raw/ --minlength=900 -r
```
Erstellt MKV-Datei(en) direkt aus den gewählten Titeln.
=== "Backup-Modus"
```bash
makemkvcon backup disc:0 /path/to/raw/backup/ --decrypt -r
```
Erstellt vollständiges Disc-Backup inkl. Menüs.
**Live-Updates** aus MakeMKV-Ausgabe:
```
PRGV:2048,0,65536 → Fortschritt-Berechnung
PRGT:5011,0,"..." → Aktueller Task-Name
```
**Typische Dauer:** DVD 2045 min · Blu-ray 45120 min
---
### MEDIAINFO_CHECK
**HandBrake-Scan und Encode-Plan-Erstellung.**
Dieser Zustand umfasst je nach Quelle mehrere Phasen:
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.
**Übergänge:**
- Eindeutige Quelle/Titelwahl möglich → `READY_TO_ENCODE`
- Mehrdeutige Playlist erkannt → `WAITING_FOR_USER_DECISION`
---
### READY_TO_ENCODE
**Encode-Plan bereit.**
Das `MediaInfoReviewPanel` zeigt:
- **Titel-Auswahl** (bei Discs mit mehreren langen Titeln)
- **Audio-Tracks** mit Encoder-Vorschau (Copy/Transcode/Fallback)
- **Untertitel-Tracks** mit Flags (Einbrennen, Forced, Default)
- **Post-Encode-Skripte** Auswahl und Reihenfolge der auszuführenden Skripte
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`)
---
### ENCODING
**HandBrake encodiert die Datei.**
```bash
HandBrakeCLI \
-i <quelle> -o <ziel> \
-t <titelId> \
--preset "H.265 MKV 1080p30" \
-a 1,2 -E copy:ac3,av_aac \
-s 1 --subtitle-default 1
```
**Live-Updates** aus HandBrake-stderr:
```
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).
!!! note "Skriptfehler"
Skriptfehler führen zum Abbruch der Skriptkette, der Job bleibt jedoch im Abschlusszustand `FINISHED` mit entsprechendem Hinweis im Status-Text/Log.
---
### FINISHED
**Job erfolgreich abgeschlossen.**
- Ausgabedatei liegt im konfigurierten `movie_dir`
- Job-Status in Datenbank: `FINISHED`
- PushOver-Benachrichtigung (falls konfiguriert)
- 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
---
### ERROR
**Fehler aufgetreten.**
- Fehlerdetails im Job-Datensatz gespeichert
- Fehler-Logs in History abrufbar
- **Retry**: Neustart vom Fehlerzustand
- **Neu analysieren**: Disc erneut als neuer Job starten
---
## Abbrechen & Retry
### Pipeline abbrechen
```http
POST /api/pipeline/cancel
```
- SIGINT → graceful exit (Timeout: 10 s) → SIGKILL
- Laufender Job landet in `CANCELLED` (oder Queue-Eintrag wird entfernt, falls noch nicht gestartet)
### Job wiederholen
```http
POST /api/pipeline/retry/:jobId
```
- 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
```http
POST /api/pipeline/reencode/:jobId
```
- Encodiert bestehende Raw-MKV neu
- Ermöglicht neue Track-Auswahl und andere Skripte
- Kein Ripping erforderlich
- `cancel`: laufenden Job abbrechen oder Queue-Eintrag entfernen
- `retry`: Fehler-/Abbruch-Job neu starten
- `reencode`: aus vorhandenem RAW neu encodieren
- `restart-review`: Review aus RAW neu aufbauen
- `restart-encode`: Encoding mit letzter bestätigter Auswahl neu starten

View File

@@ -1,137 +1,69 @@
# HandBrake
HandBrake encodiert die rohen MKV-Dateien in das gewünschte Format. Ripster nutzt `HandBrakeCLI`.
Ripster verwendet `HandBrakeCLI` für Scan und Encode.
---
## Verwendeter Befehl
## Verwendete Aufrufe
### Scan (Review-Aufbau)
```bash
HandBrakeCLI --scan --json -i <input> -t 0
```
### Encode (vereinfacht)
```bash
HandBrakeCLI \
--input "/mnt/raw/Film_t00.mkv" \
--output "/mnt/movies/Film (2010).mkv" \
--preset "H.265 MKV 1080p30" \
--audio 1,2 \
--aencoder copy:ac3,ffaac \
--subtitle 1 \
--subtitle-default 1
-i <input> \
-o <output> \
-t <titleId> \
-Z "<preset>" \
<extra-args> \
-a <audioTrackIds|none> \
-s <subtitleTrackIds|none>
```
Optional ergänzt Ripster:
- `--subtitle-burned=<id>`
- `--subtitle-default=<id>`
- `--subtitle-forced=<id>` oder `--subtitle-forced`
---
## Presets
## Presets auslesen
HandBrake verwendet **Presets** für vorkonfigurierte Encoding-Einstellungen.
### Empfohlene Presets
| Preset | Codec | Auflösung | Für |
|--------|-------|----------|-----|
| `H.265 MKV 1080p30` | HEVC/H.265 | 1080p | Beste Qualität/Größe |
| `H.265 MKV 720p30` | HEVC/H.265 | 720p | Kleinere Dateien |
| `H.264 MKV 1080p30` | AVC/H.264 | 1080p | Breiteste Kompatibilität |
| `HQ 1080p30 Surround` | HEVC/H.265 | 1080p | Hohe Qualität mit Surround |
### Alle Presets anzeigen
Ripster liest Presets mit:
```bash
HandBrakeCLI --preset-list
HandBrakeCLI -z
```
---
## Audio-Encoding
## Relevante Settings
### Copy-kompatible Codecs
HandBrake kann folgende Codecs direkt kopieren (kein Qualitätsverlust):
| Codec | `--aencoder` Wert |
|-------|-----------------|
| AC-3 | `copy:ac3` |
| AAC | `copy:aac` |
| MP3 | `copy:mp3` |
| TrueHD | `copy:truehd` |
| E-AC-3 | `copy:eac3` |
### Transcoding
Codecs die nicht kopiert werden können, werden zu AAC transcodiert:
| Original | Transcodiert zu |
|---------|----------------|
| DTS | AAC (`ffaac`) |
| DTS-HD | AAC (`ffaac`) |
---
## Extra-Argumente
Über die Einstellung `handbrake_extra_args` können beliebige HandBrake-Argumente hinzugefügt werden:
```
# Cropping deaktivieren
--crop 0:0:0:0
# Loose Anamorphic
--loose-anamorphic
# Bestimmte Qualität setzen
--quality 20
```
| Key | Bedeutung |
|-----|-----------|
| `handbrake_command` | CLI-Binary |
| `handbrake_preset_bluray` / `handbrake_preset_dvd` | profilspezifisches Preset |
| `handbrake_extra_args_bluray` / `handbrake_extra_args_dvd` | profilspezifische Zusatzargumente |
| `output_extension_bluray` / `output_extension_dvd` | Ausgabeformat |
| `handbrake_restart_delete_incomplete_output` | unvollständige Ausgabe bei Neustart löschen |
---
## Fortschritts-Parsing
Ripster parst die HandBrake-Ausgabe auf stderr für die Fortschrittsanzeige:
```
Encoding: task 1 of 1, 73.50 % (45.23 fps, avg 44.12 fps, ETA 00h12m34s)
```
`progressParsers.js` extrahiert:
- Prozentzahl
- Aktuelle FPS
- ETA
---
## Konfiguration in Ripster
| Einstellung | Beschreibung |
|------------|-------------|
| `handbrake_command` | Pfad/Befehl für `HandBrakeCLI` |
| `handbrake_preset` | Preset-Name |
| `handbrake_extra_args` | Zusätzliche CLI-Argumente |
| `output_extension` | Dateiendung der Ausgabe |
Ripster parst HandBrake-Stderr (Prozent/ETA/Detail) und sendet WebSocket-Progress (`PIPELINE_PROGRESS`).
---
## Troubleshooting
### HandBrake findet Preset nicht
- Preset nicht gefunden: Preset-Namen mit `HandBrakeCLI -z` prüfen
- sehr langsames Encoding: Preset/Extra-Args prüfen (z. B. `--encoder-preset`)
```bash
# Preset-Liste anzeigen
HandBrakeCLI --preset-list 2>&1 | grep -i "h.265"
```
Preset-Namen sind case-sensitive!
### Encoding sehr langsam
```bash
# CPU-Encoding-Preset anpassen (schneller = schlechtere Qualität)
handbrake_extra_args = --encoder-preset fast
```
Verfügbare Presets: `ultrafast`, `superfast`, `veryfast`, `faster`, `fast`, `medium`, `slow`, `slower`, `veryslow`
### GPU-Encoding nutzen (NVIDIA)
```
handbrake_preset = H.265 NVENC 1080p
```
Erfordert HandBrake-Build mit NVENC-Unterstützung und NVIDIA-GPU.
Das Produktions-Installer-Script `install.sh` bietet eine Option zur Installation eines gebündelten HandBrakeCLI-Binaries mit NVDEC-Unterstützung (NVIDIA GPU-Dekodierung). Diese Option erscheint interaktiv während der Installation.

View File

@@ -1,160 +1,61 @@
# MakeMKV
MakeMKV analysiert und rippt DVDs und Blu-rays. Ripster nutzt `makemkvcon` (die CLI-Version).
Ripster nutzt `makemkvcon` für Disc-Analyse und Rip.
---
## Verwendete Befehle
## Verwendete Aufrufe
### Disc-Analyse
### Analyse
```bash
makemkvcon -r --cache=1 info disc:0
makemkvcon -r info <source>
```
Gibt alle Titel und Playlists der eingelegten Disc aus. Ripster parst diese Ausgabe um die verfügbaren Tracks und Playlists zu bestimmen.
`<source>` ist typischerweise:
**Parameter:**
- `-r` Maschinen-lesbares Ausgabeformat
- `--cache=1` Minimaler Disc-Cache
- `info disc:0` Informationsabfrage für erstes Laufwerk
- `disc:<index>` (Auto-Modus)
- `dev:/dev/sr0` (explicit)
- `file:<path>` (Datei/Ordner-Analyse)
### MKV-Modus (Standard)
### Rip (MKV-Modus)
```bash
makemkvcon mkv disc:0 all /path/to/raw/ \
--minlength=900 \
-r
makemkvcon mkv <source> <title-or-all> <rawDir> [--minlength=...] [...extraArgs]
```
Erstellt MKV-Dateien aus allen Titeln, die länger als 15 Minuten sind.
**Parameter:**
- `mkv` MKV-Ausgabemodus
- `disc:0` Erstes Disc-Laufwerk
- `all` Alle passenden Titel (nicht nur einen bestimmten)
- `--minlength=900` Mindestlänge in Sekunden (entspricht 15 Minuten)
### Backup-Modus
### Rip (Backup-Modus)
```bash
makemkvcon backup disc:0 /path/to/raw/backup/ \
--decrypt \
-r
```
Erstellt ein vollständiges Disc-Backup mit Menüs.
**Parameter:**
- `backup` Backup-Modus
- `--decrypt` Verschlüsselung entfernen
---
## Ausgabeformat
MakeMKV gibt Fortschritt und Status in einem strukturierten Format aus:
```
PRGV:current,total,max → Fortschrittsbalken-Werte
PRGT:code,id,"Beschreibung" → Aktueller Task
PRGC:code,id,"Beschreibung" → Aktueller Sub-Task
MSG:code,flags,count,"Text" → Nachricht
```
Ripster's `progressParsers.js` parst diese Ausgabe für die Live-Fortschrittsanzeige.
---
## LibDriveIO-Modus (Pflicht)
!!! danger "Laufwerk muss im LibDriveIO-Modus betrieben werden"
MakeMKV greift auf Discs über **LibDriveIO** zu eine Bibliothek, die direkt auf Rohdaten des Laufwerks zugreift und den Standard-OS-Treiber umgeht. Ohne diesen Modus kann MakeMKV verschlüsselte Blu-rays (insbesondere UHD) **nicht lesen**.
### Was ist LibDriveIO?
LibDriveIO ist MakeMKVs interne Treiberschicht für den direkten Laufwerkszugriff. Sie ermöglicht:
- Lesen von verschlüsselten Blu-ray-Sektoren (AACS, BD+, AACS2)
- Zugriff auf Disc-Strukturen, die über Standard-OS-APIs nicht erreichbar sind
- UHD-Blu-ray-Entschlüsselung ohne externe Bibliotheken
### Voraussetzungen für den LibDriveIO-Modus
Das Laufwerk muss **LibDriveIO-kompatibel** sein und entsprechend betrieben werden:
1. **Kompatibles Laufwerk** Nicht alle Laufwerke unterstützen den Rohdatenzugriff. UHD-kompatible Laufwerke (z. B. LG, Pioneer bestimmter Firmware-Versionen) sind erforderlich.
2. **Laufwerk-Berechtigungen** Der Prozess benötigt direkten Zugriff auf das Blockdevice:
```bash
sudo chmod a+rw /dev/sr0
# oder dauerhaft über udev-Regel
```
3. **Kein OS-seitiger Disc-Mount** Das Laufwerk darf beim Ripping **nicht** durch das OS automatisch gemountet sein (AutoMount deaktivieren):
```bash
# Automount temporär deaktivieren (GNOME)
gsettings set org.gnome.desktop.media-handling automount false
```
### How-To: LibDriveIO einrichten
Die vollständige Anleitung zur Einrichtung und zu kompatiblen Laufwerken findet sich im offiziellen MakeMKV-Forum:
[:octicons-link-external-24: MakeMKV Forum LibDriveIO How-To](https://www.makemkv.com/forum/viewtopic.php?t=18856){ .md-button }
!!! tip "Prüfen ob LibDriveIO aktiv ist"
In der MakeMKV-Ausgabe erscheint beim Laufwerkszugriff `LibDriveIO` statt `LibMMMBD`, wenn der direkte Modus aktiv ist.
---
## MakeMKV-Lizenz
MakeMKV ist **Beta-Software** und kostenlos für den persönlichen Gebrauch während der Beta-Phase. Eine Beta-Lizenz ist regelmäßig im [MakeMKV-Forum](https://www.makemkv.com/forum/viewtopic.php?t=1053) verfügbar.
Ohne gültige Lizenz können Blu-rays nicht entschlüsselt werden.
### Lizenz eintragen
Die Lizenz wird in den MakeMKV-Einstellungen eingetragen (GUI) oder direkt in:
```
~/.MakeMKV/settings.conf
```
```
app_Key = "XXXX-XXXX-XXXX-XXXX-XXXX"
makemkvcon backup <source> <rawDir> --decrypt
```
---
## Konfiguration in Ripster
## Registrierungsschlüssel (optional)
| Einstellung | Beschreibung |
|------------|-------------|
| `makemkv_command` | Pfad/Befehl für `makemkvcon` |
| `makemkv_min_length_minutes` | Mindest-Titellänge (Standard: 15 Min) |
| `makemkv_backup_mode` | Backup-Modus statt MKV |
---
## Troubleshooting
### MakeMKV erkennt Disc nicht
Wenn `makemkv_registration_key` gesetzt ist, führt Ripster vor Analyse/Rip aus:
```bash
# Laufwerk-Berechtigungen prüfen
ls -la /dev/sr0
sudo chmod a+rw /dev/sr0
# Oder Benutzer zur Gruppe cdrom hinzufügen
sudo usermod -a -G cdrom $USER
makemkvcon reg <key>
```
### Langer Analyseprozess
---
Blu-ray-Analyse kann bei Discs mit vielen Playlists 5+ Minuten dauern. Dies ist normal.
## Relevante Settings
### Fehlermeldung: "LibMMBD"
| Key | Bedeutung |
|-----|-----------|
| `makemkv_command` | CLI-Binary |
| `makemkv_source_index` | Source-Index im Auto-Modus |
| `makemkv_min_length_minutes` | Mindestlaufzeitfilter |
| `makemkv_rip_mode_bluray` / `makemkv_rip_mode_dvd` | `mkv` oder `backup` |
| `makemkv_analyze_extra_args_bluray` / `_dvd` | Zusatzargs Analyse |
| `makemkv_rip_extra_args_bluray` / `_dvd` | Zusatzargs Rip |
LibMMBD ist MakeMKVs interne Verschlüsselungsbibliothek. Bei Fehlern die MakeMKV-Version aktualisieren.
---
## Hinweise
- Blu-ray-Backups werden oft für robuste Playlist-Analyse genutzt.
- MakeMKV-Ausgaben werden geparst und als `makemkvInfo` im Job gespeichert.

View File

@@ -1,108 +1,37 @@
# MediaInfo
MediaInfo analysiert die Track-Struktur von Mediendateien. Ripster nutzt es nach dem Ripping um Audio- und Untertitelspuren zu identifizieren.
Ripster nutzt `mediainfo` zur JSON-Analyse von Medien-Dateien.
---
## Verwendeter Befehl
## Aufruf
```bash
mediainfo --Output=JSON /path/to/raw/film.mkv
mediainfo --Output=JSON <input>
```
Gibt vollständige Track-Informationen als JSON zurück.
Der Input ist typischerweise eine RAW-Datei oder ein vom Workflow gewählter Inputpfad.
---
## Ausgabe-Struktur
## Verwendung in Ripster
```json
{
"media": {
"track": [
{
"@type": "General",
"Duration": "8885.042",
"Format": "Matroska"
},
{
"@type": "Video",
"Format": "HEVC",
"Width": "1920",
"Height": "1080",
"FrameRate": "23.976"
},
{
"@type": "Audio",
"StreamOrder": "1",
"Format": "TrueHD",
"Channels": "8",
"Language": "en"
},
{
"@type": "Audio",
"StreamOrder": "2",
"Format": "AC-3",
"Channels": "6",
"Language": "de"
},
{
"@type": "Text",
"StreamOrder": "1",
"Format": "UTF-8",
"Language": "de"
}
]
}
}
```
- Track-/Codec-Metadaten für Review-Plan
- Fallback-Informationen in bestimmten Analysepfaden
- Persistenz als `mediainfoInfo` im Job
---
## Verarbeitung in Ripster
## Relevante Settings
`encodePlan.js` verarbeitet die MediaInfo-Ausgabe:
1. **Track-Extraktion**: Alle Audio- und Untertitel-Tracks werden extrahiert
2. **Sprach-Normalisierung**: Sprachcodes werden auf ISO 639-3 normalisiert
3. **Codec-Klassifizierung**: Bestimmt ob Codec kopiert oder transcodiert werden kann
4. **Track-Labels**: Benutzerfreundliche Bezeichnungen (z.B. "Deutsch (AC-3, 5.1)")
### Track-Label-Format
```
{Sprache} ({Format}, {Kanäle})
```
Beispiele:
- `Deutsch (AC-3, 5.1)`
- `English (TrueHD, 7.1)`
- `Français (AC-3, 2.0)`
---
## Konfiguration in Ripster
| Einstellung | Beschreibung |
|------------|-------------|
| `mediainfo_command` | Pfad/Befehl für `mediainfo` |
| Key | Bedeutung |
|-----|-----------|
| `mediainfo_command` | CLI-Binary |
| `mediainfo_extra_args_bluray` / `_dvd` | profilspezifische Zusatzargumente |
---
## Troubleshooting
### MediaInfo gibt kein JSON aus
```bash
# Version prüfen
mediainfo --Version
# JSON-Ausgabe testen
mediainfo --Output=JSON /path/to/test.mkv
```
MediaInfo >= 17.10 wird empfohlen.
### Sprache als "und" angezeigt
`und` steht für "undetermined" die Sprache ist in der MKV-Datei nicht getaggt. Dies ist bei manchen Rips normal. Der Track wird trotzdem angezeigt und kann manuell ausgewählt werden.
- JSON-Test: `mediainfo --Output=JSON <datei>`
- unbekannte Sprache erscheint oft als `und` (undetermined)