Bugfix and Docs
This commit is contained in:
@@ -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` | Montag–Freitag 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`)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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": { ... } }`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (1000–60000) |
|
||||
| `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 (0–999) |
|
||||
| `pipeline_max_parallel_jobs` | number | `1` | Maximale Anzahl parallel laufender Jobs (1–12) |
|
||||
| `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`
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 & 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> | <span style="color:#1565c0">■ Läuft automatisch</span> | <span style="color:#3949ab">■ Benutzeraktion</span> | <span style="color:#e65100">⚠ Optional</span> | <span style="color:#6a1b9a">■ Encodierung</span> | <span style="color:#2e7d32">✓ Fertig</span>
|
||||
|
||||
??? note "Vollständiges Zustandsdiagramm (inkl. Fehler- & 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: 20–45 Minuten
|
||||
- Blu-ray: 45–120 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 (0–100 %)
|
||||
- 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: 1–3× Echtzeit
|
||||
- Langsames Preset (`slow`): 5–10× 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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 20–45 min · Blu-ray 45–120 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user