diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 731dfe3..c9ce6de 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -89,15 +89,29 @@ jobs: mkdir -p .worktrees git worktree add --detach .worktrees/main "${MAIN_REF}" - mkdocs build --strict --verbose \ + if ! mkdocs build --strict --verbose \ --config-file .worktrees/main/mkdocs.yml \ - --site-dir site + --site-dir site; then + echo "::error::Main-Dokumentation konnte nicht gebaut werden." + exit 1 + fi if [ -n "${DEV_REF}" ]; then git worktree add --detach .worktrees/dev "${DEV_REF}" - mkdocs build --strict --verbose \ + if ! mkdocs build --strict --verbose \ --config-file .worktrees/dev/mkdocs.yml \ - --site-dir site/dev + --site-dir site/dev; then + echo "::warning::Dev-Dokumentation konnte nicht gebaut werden. Es wird ein Platzhalter unter /dev erzeugt." + rm -rf site/dev + mkdir -p site/dev + cat > site/dev/index.html <<'EOF' + + +
Dev-Dokumentation ist aktuell auf GitHub noch nicht verfügbar.
+ + EOF + fi else mkdir -p site/dev cat > site/dev/index.html <<'EOF' diff --git a/.worktrees/main b/.worktrees/main new file mode 160000 index 0000000..190f6fe --- /dev/null +++ b/.worktrees/main @@ -0,0 +1 @@ +Subproject commit 190f6fe1a5287a5c2b299f42e648290f274f1fd8 diff --git a/site/404.html b/site/404.html deleted file mode 100644 index c065753..0000000 --- a/site/404.html +++ /dev/null @@ -1 +0,0 @@ -Ripster enthält ein eingebautes Cron-System für Skripte und Skript-Ketten (sourceType: script|chain).
Listet alle Cron-Jobs.
{
- "jobs": [
- {
- "id": 1,
- "name": "Nachtlauf Backup",
- "cronExpression": "0 2 * * *",
- "sourceType": "script",
- "sourceId": 3,
- "sourceName": "Backup-Skript",
- "enabled": true,
- "pushoverEnabled": true,
- "lastRunAt": "2026-03-10T02:00:00.000Z",
- "lastRunStatus": "success",
- "nextRunAt": "2026-03-11T02:00:00.000Z",
- "createdAt": "2026-03-01T10:00:00.000Z",
- "updatedAt": "2026-03-10T02:00:05.000Z"
- }
- ]
-}
-Erstellt Cron-Job.
{
- "name": "Nachtlauf Backup",
- "cronExpression": "0 2 * * *",
- "sourceType": "script",
- "sourceId": 3,
- "enabled": true,
- "pushoverEnabled": true
-}
-Response: 201 mit { "job": { ... } }
Response:
Aktualisiert Cron-Job. Felder wie bei POST.
Response:
Response:
Liefert Ausführungs-Logs.
Query-Parameter:
| Parameter | Typ | Default | Beschreibung |
|---|---|---|---|
limit | number | 20 | Anzahl Einträge, max. 100 |
Response:
{
- "logs": [
- {
- "id": 42,
- "cronJobId": 1,
- "startedAt": "2026-03-10T02:00:01.000Z",
- "finishedAt": "2026-03-10T02:00:05.000Z",
- "status": "success",
- "output": "Backup abgeschlossen.",
- "errorMessage": null
- }
- ]
-}
-status: running | success | error
Triggert Job manuell (asynchron).
Response:
Wenn Job bereits läuft: 409.
Validiert 5-Felder-Cron-Ausdruck und berechnet nächsten Lauf.
Request:
Gültige Response:
Ungültige Response:
{
- "valid": false,
- "error": "Cron-Ausdruck muss genau 5 Felder haben (Minute Stunde Tag Monat Wochentag).",
- "nextRunAt": null
-}
-Ripster unterstützt 5 Felder:
Beispiele:
0 2 * * * täglich 02:00*/15 * * * * alle 15 Minuten0 6 * * 1-5 Mo-Fr 06:00CRON_JOBS_UPDATED bei Create/Update/DeleteCRON_JOB_UPDATED bei Laufzeitstatus (running -> success|error)Endpunkte für Job-Historie, Orphan-Import und Löschoperationen.
Liefert Jobs (optionale Filter).
Query-Parameter:
| Parameter | Typ | Beschreibung |
|---|---|---|
status | string | Filter nach Job-Status |
search | string | Suche in Titel-Feldern |
Beispiel:
Response:
{
- "jobs": [
- {
- "id": 42,
- "status": "FINISHED",
- "title": "Inception",
- "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"
- }
- ]
-}
-Liefert Job-Detail.
Query-Parameter:
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
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:
{
- "job": {
- "id": 42,
- "status": "FINISHED",
- "makemkvInfo": {},
- "mediainfoInfo": {},
- "handbrakeInfo": {},
- "encodePlan": {},
- "log": "...",
- "log_count": 1,
- "logMeta": {
- "loaded": true,
- "total": 800,
- "returned": 800,
- "truncated": true
- }
- }
-}
-Debug-Ansicht der DB-Zeilen (angereichert).
Response:
{
- "rows": [
- {
- "id": 42,
- "status": "FINISHED",
- "rawFolderName": "Inception - RAW - job-42"
- }
- ]
-}
-Sucht RAW-Ordner ohne zugehörigen Job.
Response:
{
- "rawDir": "/mnt/raw",
- "rawDirs": ["/mnt/raw", "/mnt/raw-bluray"],
- "rows": [
- {
- "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"
- }
- ]
-}
-Importiert RAW-Ordner als FINISHED-Job.
Request:
Response:
Weist OMDb-/Metadaten nachträglich zu.
Request:
{
- "imdbId": "tt1375666",
- "title": "Inception",
- "year": 2010,
- "poster": "https://...",
- "fromOmdb": true
-}
-Response:
Löscht Dateien eines Jobs, behält DB-Eintrag.
Request:
target: raw | movie | both
Response:
{
- "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 }
-}
-Löscht Job aus DB; optional auch Dateien.
Request:
target: none | raw | movie | both
Response:
{
- "deleted": true,
- "jobId": 42,
- "fileTarget": "both",
- "fileSummary": {
- "target": "both",
- "raw": { "filesDeleted": 10 },
- "movie": { "filesDeleted": 1 }
- },
- "uiReset": {
- "reset": true,
- "state": "IDLE"
- }
-}
-409).REST- und WebSocket-Schnittstellen für Integration, Automatisierung und Debugging.
API-Prefix: /api
Health
Service-Liveness.
GET /api/health
Pipeline API
Analyse, Start/Retry/Cancel, Queue, Re-Encode.
Settings API
Einstellungen, Skripte/Ketten, User-Presets.
History API
Job-Historie, Orphan-Import, Löschoperationen.
Cron API
Zeitgesteuerte Skript-/Kettenausführung.
WebSocket Events
Pipeline-, Queue-, Disk-, Settings-, Cron- und Monitoring-Events.
Ripster hat keine eingebaute Authentifizierung und ist für lokalen, geschützten Betrieb gedacht.
Endpunkte zur Steuerung des Pipeline-Workflows.
Liefert aktuellen Pipeline- und Hardware-Monitoring-Snapshot.
Response (Beispiel):
{
- "pipeline": {
- "state": "READY_TO_ENCODE",
- "activeJobId": 42,
- "progress": 0,
- "eta": null,
- "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": 1,
- "queuedCount": 2,
- "runningJobs": [],
- "queuedJobs": []
- }
- },
- "hardwareMonitoring": {
- "enabled": true,
- "intervalMs": 5000,
- "updatedAt": "2026-03-10T09:00:00.000Z",
- "sample": {
- "cpu": {},
- "memory": {},
- "gpu": {},
- "storage": {}
- },
- "error": null
- }
-}
-Startet Disc-Analyse und legt Job an.
Response:
Erzwingt erneute Laufwerksprüfung.
Response (Beispiel):
{
- "result": {
- "present": true,
- "changed": true,
- "emitted": "discInserted",
- "device": {
- "path": "/dev/sr0",
- "discLabel": "INCEPTION",
- "mediaProfile": "bluray"
- }
- }
-}
-OMDb-Titelsuche.
Response:
{
- "results": [
- {
- "imdbId": "tt1375666",
- "title": "Inception",
- "year": "2010",
- "type": "movie",
- "poster": "https://..."
- }
- ]
-}
-Setzt Metadaten (und optional Playlist) für einen Job.
Request:
{
- "jobId": 42,
- "title": "Inception",
- "year": 2010,
- "imdbId": "tt1375666",
- "poster": "https://...",
- "fromOmdb": true,
- "selectedPlaylist": "00800"
-}
-Response:
Startet vorbereiteten Job oder queued ihn (je nach Parallel-Limit).
Mögliche Responses:
{ "result": { "queued": true, "started": false, "queuePosition": 2, "action": "START_PREPARED" } }
-Bestätigt Review-Auswahl (Tracks, Pre/Post-Skripte/Ketten, User-Preset).
Request (typisch):
{
- "selectedEncodeTitleId": 1,
- "selectedTrackSelection": {
- "1": {
- "audioTrackIds": [1, 2],
- "subtitleTrackIds": [3]
- }
- },
- "selectedPreEncodeScriptIds": [1],
- "selectedPostEncodeScriptIds": [2, 7],
- "selectedPreEncodeChainIds": [3],
- "selectedPostEncodeChainIds": [4],
- "selectedUserPresetId": 5,
- "skipPipelineStateUpdate": false
-}
-Response:
Bricht laufenden Job ab oder entfernt Queue-Eintrag.
Request (optional):
Mögliche Responses:
Retry für ERROR/CANCELLED-Jobs (oder Queue-Einreihung).
Startet Re-Encode aus bestehendem RAW.
Berechnet Review aus RAW neu.
Startet Encoding mit letzter bestätigter Review neu.
Lädt READY_TO_ENCODE-Job nach Neustart wieder in aktive Session.
Alle Endpunkte liefern { result: ... } bzw. { job: ... }.
Liefert Queue-Snapshot.
{
- "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"
- }
-}
-Sortiert Queue-Einträge neu.
Request:
Legacy fallback wird akzeptiert:
Fügt Nicht-Job-Queue-Eintrag hinzu (script, chain, wait).
Request-Beispiele:
Response:
Entfernt Queue-Eintrag.
Response:
| 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 |
Ripster verfolgt alle laufenden und kürzlich abgeschlossenen Aktivitäten (Skripte, Skript-Ketten, Cron-Jobs, interne Tasks) in Echtzeit über den RuntimeActivityService.
Aktivitäten entstehen, wenn Ripster intern Aktionen ausführt – z. B. beim Start eines Cron-Jobs, beim Ausführen einer Skript-Kette oder beim Durchlaufen von Pipeline-Schritten. Sie sind nicht persistent (kein DB-Speicher) und werden nur im Arbeitsspeicher gehalten.
active): Laufen gerade.recent): Abgeschlossen, max. 120 Einträge.Änderungen werden über WebSocket (RUNTIME_ACTIVITY_CHANGED) in Echtzeit gesendet.
{
- "id": 7,
- "type": "chain",
- "name": "Post-Encode Aufräumen",
- "status": "running",
- "source": "cron",
- "message": "Schritt 2 von 3",
- "currentStep": "cleanup.sh",
- "currentStepType": "script",
- "currentScriptName": "cleanup.sh",
- "stepIndex": 2,
- "stepTotal": 3,
- "parentActivityId": null,
- "jobId": 42,
- "cronJobId": 3,
- "chainId": 5,
- "scriptId": null,
- "canCancel": true,
- "canNextStep": false,
- "outcome": "running",
- "errorMessage": null,
- "output": null,
- "stdout": null,
- "stderr": null,
- "stdoutTruncated": false,
- "stderrTruncated": false,
- "startedAt": "2026-03-10T10:00:00.000Z",
- "finishedAt": null,
- "durationMs": null,
- "exitCode": null,
- "success": null
-}
-| Feld | Typ | Beschreibung |
|---|---|---|
id | number | Eindeutige ID (Laufzähler, nicht persistent) |
type | string | Art der Aktivität: script | chain | cron | task |
name | string \| null | Anzeigename der Aktivität |
status | string | Aktueller Status: running | success | error |
source | string \| null | Auslöser (z. B. cron, pipeline, manual) |
message | string \| null | Kurztext zum aktuellen Zustand |
currentStep | string \| null | Name des aktuell ausgeführten Schritts |
currentStepType | string \| null | Typ des Schritts (z. B. script, wait) |
currentScriptName | string \| null | Name des Skripts im aktuellen Schritt |
stepIndex | number \| null | Aktueller Schritt (1-basiert) |
stepTotal | number \| null | Gesamtanzahl Schritte |
parentActivityId | number \| null | ID der übergeordneten Aktivität |
jobId | number \| null | Verknüpfte Job-ID |
cronJobId | number \| null | Verknüpfte Cron-Job-ID |
chainId | number \| null | Verknüpfte Skript-Ketten-ID |
scriptId | number \| null | Verknüpfte Skript-ID |
canCancel | boolean | Abbrechen über API möglich |
canNextStep | boolean | Nächster Schritt über API auslösbar |
outcome | string \| null | Abschluss-Ergebnis: success | error | cancelled | skipped | running |
errorMessage | string \| null | Fehlermeldung (max. 2.000 Zeichen) |
output | string \| null | Allgemeine Ausgabe (max. 12.000 Zeichen) |
stdout | string \| null | Standardausgabe des Prozesses (max. 12.000 Zeichen) |
stderr | string \| null | Fehlerausgabe des Prozesses (max. 12.000 Zeichen) |
stdoutTruncated | boolean | true, wenn stdout gekürzt wurde |
stderrTruncated | boolean | true, wenn stderr gekürzt wurde |
startedAt | string | ISO-8601-Zeitstempel des Starts |
finishedAt | string \| null | ISO-8601-Zeitstempel des Endes |
durationMs | number \| null | Laufzeit in Millisekunden |
exitCode | number \| null | Exit-Code des Prozesses |
success | boolean \| null | Erfolgsstatus (null bei laufender Aktivität) |
Alle Aktivitäts-Endpunkte geben einen Snapshot zurück:
{
- "active": [ /* laufende Aktivitäten, nach startedAt absteigend */ ],
- "recent": [ /* abgeschlossene Aktivitäten, nach finishedAt absteigend, max. 120 */ ],
- "updatedAt": "2026-03-10T10:05:00.000Z"
-}
-/api/activities¶Aktuellen Aktivitäts-Snapshot abrufen.
Antwort:
{
- "active": [],
- "recent": [
- {
- "id": 5,
- "type": "script",
- "name": "notify.sh",
- "status": "success",
- "outcome": "success",
- "startedAt": "2026-03-10T09:58:00.000Z",
- "finishedAt": "2026-03-10T09:58:02.000Z",
- "durationMs": 2100,
- "exitCode": 0,
- "success": true,
- "canCancel": false,
- "canNextStep": false
- }
- ],
- "updatedAt": "2026-03-10T10:05:00.000Z"
-}
-/api/activities/:id/cancel¶Aktive Aktivität abbrechen (nur wenn canCancel: true).
Parameter:
| Name | In | Typ | Beschreibung |
|---|---|---|---|
id | path | number | Aktivitäts-ID |
reason | body | string | Optionaler Abbruchgrund |
Request Body:
Antwort (Erfolg):
{
- "ok": true,
- "action": null,
- "snapshot": { "active": [], "recent": [], "updatedAt": "..." }
-}
-Fehlercodes:
| HTTP | Bedeutung |
|---|---|
404 | Aktivität nicht gefunden oder bereits abgeschlossen |
409 | Abbrechen wird von dieser Aktivität nicht unterstützt |
/api/activities/:id/next-step¶Nächsten Schritt einer Aktivität auslösen (nur wenn canNextStep: true).
Parameter:
| Name | In | Typ | Beschreibung |
|---|---|---|---|
id | path | number | Aktivitäts-ID |
Antwort (Erfolg):
{
- "ok": true,
- "action": null,
- "snapshot": { "active": [], "recent": [], "updatedAt": "..." }
-}
-Fehlercodes:
| HTTP | Bedeutung |
|---|---|
404 | Aktivität nicht gefunden |
409 | Nächster Schritt wird von dieser Aktivität nicht unterstützt |
/api/activities/clear-recent¶Alle abgeschlossenen Aktivitäten aus recent löschen.
Antwort:
{
- "ok": true,
- "removed": 14,
- "snapshot": { "active": [], "recent": [], "updatedAt": "..." }
-}
-| Wert | Limit |
|---|---|
Maximale recent-Einträge | 120 |
Maximale Länge stdout / stderr / output | 12.000 Zeichen |
Maximale Länge errorMessage / message | 2.000 Zeichen |
Maximale Länge outcome | 40 Zeichen |
Gekürzte Ausgaben erhalten den Suffix ...[gekürzt] (bei Inline-Text) bzw. \n...[gekürzt] (bei mehrzeiligem Output).
Änderungen werden automatisch als RUNTIME_ACTIVITY_CHANGED WebSocket-Event gesendet. Die Frontend-Komponente braucht GET /api/activities nur beim initialen Laden aufzurufen.
Endpunkte für Einstellungen, Skripte, Skript-Ketten und User-Presets.
Liefert alle Einstellungen kategorisiert.
Response (Struktur):
{
- "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
- }
- ]
- }
- ]
-}
-Aktualisiert eine einzelne Einstellung.
Request:
Response:
{
- "setting": {
- "key": "raw_dir",
- "value": "/mnt/storage/raw"
- },
- "reviewRefresh": {
- "triggered": false,
- "reason": "not_ready"
- }
-}
-reviewRefresh ist null oder ein Objekt mit Status der optionalen Review-Neuberechnung.
Aktualisiert mehrere Einstellungen atomar.
Request:
{
- "settings": {
- "raw_dir": "/mnt/storage/raw",
- "movie_dir": "/mnt/storage/movies",
- "handbrake_preset_bluray": "H.264 MKV 1080p30"
- }
-}
-Response:
{
- "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[].
Liest Preset-Liste via HandBrakeCLI -z (mit Fallback auf konfigurierte Presets).
Response (Beispiel):
{
- "source": "handbrake-cli",
- "message": null,
- "options": [
- { "label": "General/", "value": "__group__general", "disabled": true, "category": "General" },
- { "label": " Fast 1080p30", "value": "Fast 1080p30", "category": "General" }
- ]
-}
-Sendet Testnachricht über aktuelle PushOver-Settings.
Request (optional):
Response:
Wenn PushOver deaktiviert ist oder Credentials fehlen, kommt i. d. R. ebenfalls 200 mit sent: false + reason.
Basis: /api/settings/scripts
{ "scripts": [ { "id": 1, "name": "...", "scriptBody": "...", "orderIndex": 1, "createdAt": "...", "updatedAt": "..." } ] }
-Response: 201 mit { "script": { ... } }
Body wie POST, Response { "script": { ... } }.
Response { "removed": { ... } }.
Response { "scripts": [ ... ] }.
Führt Skript als Testlauf aus.
{
- "result": {
- "scriptId": 1,
- "scriptName": "Move",
- "success": true,
- "exitCode": 0,
- "signal": null,
- "timedOut": false,
- "durationMs": 120,
- "stdout": "...",
- "stderr": "...",
- "stdoutTruncated": false,
- "stderrTruncated": false
- }
-}
-Diese Variablen werden beim Ausführen gesetzt:
RIPSTER_SCRIPT_RUN_ATRIPSTER_JOB_IDRIPSTER_JOB_TITLERIPSTER_MODERIPSTER_INPUT_PATHRIPSTER_OUTPUT_PATHRIPSTER_RAW_PATHRIPSTER_SCRIPT_IDRIPSTER_SCRIPT_NAMERIPSTER_SCRIPT_SOURCEBasis: /api/settings/script-chains
Eine Kette hat Schritte vom Typ:
script (scriptId erforderlich)wait (waitSeconds 1..3600)Response { "chains": [ ... ] } (inkl. steps[]).
Response { "chain": { ... } }.
{
- "name": "After Encode",
- "steps": [
- { "stepType": "script", "scriptId": 1 },
- { "stepType": "wait", "waitSeconds": 15 },
- { "stepType": "script", "scriptId": 2 }
- ]
-}
-Response: 201 mit { "chain": { ... } }
Body wie POST, Response { "chain": { ... } }.
Response { "removed": { ... } }.
Response { "chains": [ ... ] }.
Response:
{
- "result": {
- "chainId": 2,
- "chainName": "After Encode",
- "steps": 3,
- "succeeded": 3,
- "failed": 0,
- "aborted": false,
- "results": []
- }
-}
-Basis: /api/settings/user-presets
Optionaler Query-Parameter: media_type=bluray|dvd|other|all
{
- "presets": [
- {
- "id": 1,
- "name": "Blu-ray HQ",
- "mediaType": "bluray",
- "handbrakePreset": "H.264 MKV 1080p30",
- "extraArgs": "--encoder-preset slow",
- "description": "...",
- "createdAt": "...",
- "updatedAt": "..."
- }
- ]
-}
-{
- "name": "Blu-ray HQ",
- "mediaType": "bluray",
- "handbrakePreset": "H.264 MKV 1080p30",
- "extraArgs": "--encoder-preset slow",
- "description": "optional"
-}
-Response: 201 mit { "preset": { ... } }
Body mit beliebigen Feldern aus POST, Response { "preset": { ... } }.
Response { "removed": { ... } }.
Ripster sendet Echtzeit-Updates über /ws.
const ws = new WebSocket('ws://localhost:3001/ws');
-
-ws.onmessage = (event) => {
- const msg = JSON.parse(event.data);
- console.log(msg.type, msg.payload);
-};
-Die meisten Broadcasts haben dieses Schema:
Ausnahme: WS_CONNECTED beim Verbindungsaufbau enthält kein timestamp.
Sofort nach erfolgreicher Verbindung.
Neuer Pipeline-Snapshot.
{
- "type": "PIPELINE_STATE_CHANGED",
- "payload": {
- "state": "ENCODING",
- "activeJobId": 42,
- "progress": 62.5,
- "eta": "00:12:34",
- "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": 2,
- "runningJobs": [],
- "queuedJobs": []
- }
- }
-}
-Laufende Fortschrittsupdates.
{
- "type": "PIPELINE_PROGRESS",
- "payload": {
- "state": "ENCODING",
- "activeJobId": 42,
- "progress": 62.5,
- "eta": "00:12:34",
- "statusText": "ENCODING 62.50%"
- }
-}
-Queue-Snapshot aktualisiert.
Disc-Insertion/-Removal.
{
- "type": "DISC_DETECTED",
- "payload": {
- "device": {
- "path": "/dev/sr0",
- "discLabel": "INCEPTION",
- "model": "ASUS BW-16D1HT",
- "fstype": "udf",
- "mountpoint": null,
- "mediaProfile": "bluray"
- }
- }
-}
-mediaProfile: bluray | dvd | other | null
Snapshot aus Hardware-Monitoring.
{
- "type": "HARDWARE_MONITOR_UPDATE",
- "payload": {
- "enabled": true,
- "intervalMs": 5000,
- "updatedAt": "2026-03-10T09:00:00.000Z",
- "sample": {
- "cpu": {},
- "memory": {},
- "gpu": {},
- "storage": {}
- },
- "error": null
- }
-}
-Fehler bei Disc-Event-Verarbeitung in Pipeline.
Fehler in Laufwerkserkennung.
Einzelnes Setting wurde gespeichert.
Bulk-Settings gespeichert.
{
- "type": "SETTINGS_BULK_UPDATED",
- "payload": {
- "count": 3,
- "keys": ["raw_dir", "movie_dir", "handbrake_preset_bluray"]
- }
-}
-Skript geändert (created|updated|deleted|reordered).
Skript-Kette geändert (created|updated|deleted|reordered).
User-Preset geändert (created|updated|deleted).
Cron-Config geändert (created|updated|deleted).
Laufzeitstatus eines Cron-Jobs geändert.
{
- "type": "CRON_JOB_UPDATED",
- "payload": {
- "id": 1,
- "lastRunStatus": "running",
- "lastRunAt": "2026-03-10T10:00:00.000Z",
- "nextRunAt": null
- }
-}
-Vollständiger Snapshot aller laufenden und kürzlich abgeschlossenen Aktivitäten.
Wird ausgelöst, wenn eine Aktivität gestartet, aktualisiert oder abgeschlossen wird sowie nach clear-recent.
{
- "type": "RUNTIME_ACTIVITY_CHANGED",
- "payload": {
- "active": [
- {
- "id": 7,
- "type": "chain",
- "name": "Post-Encode Aufräumen",
- "status": "running",
- "source": "cron",
- "message": "Schritt 2 von 3",
- "currentStep": "cleanup.sh",
- "currentStepType": "script",
- "stepIndex": 2,
- "stepTotal": 3,
- "canCancel": true,
- "canNextStep": false,
- "outcome": "running",
- "startedAt": "2026-03-10T10:00:00.000Z",
- "finishedAt": null,
- "durationMs": null
- }
- ],
- "recent": [],
- "updatedAt": "2026-03-10T10:00:05.000Z"
- }
-}
-Vollständige Feldbeschreibung: Runtime Activities API.
useWebSocket verbindet bei Abbruch automatisch neu:
1500msDieser Bereich enthält die technische Referenz hinter dem Benutzerhandbuch.
Das Backend ist in Services aufgeteilt, die von Express-Routen orchestriert werden.
pipelineService.js¶Zentrale Workflow-Orchestrierung.
Aufgaben:
pipeline_state)script|chain|wait Einträge)Wichtige Methoden:
analyzeDisc()selectMetadata()startPreparedJob()confirmEncodeReview()cancel()retry()reencodeFromRaw()restartReviewFromRaw()restartEncodeWithLastSettings()resumeReadyToEncodeJob()enqueueNonJobEntry(), reorderQueue(), removeQueueEntry()diskDetectionService.js¶Pollt Laufwerk(e) und emittiert:
discInserteddiscRemovederrorZusatz:
auto oder explicitmediaProfile-Erkennung (bluray/dvd/other)rescanAndEmit() für manuellen TriggersettingsService.js¶Settings-Layer mit Validation/Serialisierung.
Features:
getCategorizedSettings() für UI-FormsetSettingValue() / setSettingsBulk()resolveEffectiveToolSettings)HandBrakeCLI -zmakemkv_registration_keyhistoryService.js¶Historie + Dateioperationen.
Features:
raw|movie|both)none|raw|movie|both)cronService.js¶Integriertes Cron-System ohne externe Parser-Library.
Features:
nextRun-Berechnungscript oder chaincron_run_logs)CRON_JOBS_UPDATED, CRON_JOB_UPDATEDruntimeActivityService.js¶In-Memory-Tracking aller laufenden und kürzlich abgeschlossenen Aktivitäten (Skripte, Ketten, Cron-Jobs, Tasks).
Features:
startActivity(type, payload) → Aktivität registrieren, ID zurückgebenupdateActivity(id, patch) → Laufende Aktivität aktualisierencompleteActivity(id, payload) → Aktivität abschließen und in recent verschiebensetControls(id, { cancel, nextStep }) → Steuer-Handler registrieren (für canCancel/canNextStep)requestCancel(id) / requestNextStep(id) → Steuer-Handler aufrufenclearRecent() → Abgeschlossene Aktivitäten löschengetSnapshot() → Snapshot mit active + recent + updatedAtRUNTIME_ACTIVITY_CHANGED über WebSocket bei jeder ÄnderungLimits:
recent max. 120 Einträgestdout/stderr/output max. 12.000 Zeichenmessage/errorMessage max. 2.000 ZeichenVollständige API-Dokumentation: Runtime Activities API
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)src/index.js)¶Beim Start:
/wsRipster verwendet SQLite (backend/data/ripster.db).
settings_schema
-settings_values
-jobs
-pipeline_state
-scripts
-script_chains
-script_chain_steps
-user_presets
-cron_jobs
-cron_run_logs
-jobs¶Speichert Pipeline-Lifecycle und Artefakte pro Job.
Zentrale Felder:
title, year, imdb_id, poster_url, omdb_json, selected_from_omdbstart_time, end_time, status, last_stateraw_path, output_path, encode_input_pathmakemkv_info_json, handbrake_info_json, mediainfo_info_json, encode_plan_jsonencode_review_confirmed, rip_successful, error_messagecreated_at, updated_atpipeline_state¶Singleton-Tabelle (id = 1) für aktiven Snapshot:
stateactive_job_idprogressetastatus_textcontext_jsonupdated_atsettings_schema + settings_values¶settings_schema: Definition (Typ, Default, Validation, Reihenfolge)settings_values: aktueller Wert pro Keyscripts, script_chains, script_chain_steps¶scripts: Shell-Skripte (name, script_body, order_index)script_chains: Ketten (name, order_index)script_chain_steps: Schritte je Kettestep_type: script oder waitscript_id oder wait_secondsuser_presets¶Benannte HandBrake-Preset-Sets:
namemedia_type (bluray|dvd|other|all)handbrake_presetextra_argsdescriptioncron_jobs + cron_run_logs¶cron_jobs: Zeitplan + Statuscron_run_logs: einzelne Läufestatus: running|success|erroroutputerror_messageBeim Start werden Schema und Settings-Metadaten automatisch abgeglichen.
Bei korruptem SQLite-File:
backend/data/corrupt-backups/ verschobenFrontend: React + PrimeReact + Vite.
DashboardPage.jsx¶Pipeline-Steuerung:
SettingsPage.jsx¶Konfiguration:
DynamicSettingsForm)CronJobsTab)HistoryPage.jsx¶Historie:
PipelineStatusCard.jsxMetadataSelectionDialog.jsxMediaInfoReviewPanel.jsxJobDetailDialog.jsxCronJobsTab.jsxapi/client.js)¶request() mit JSON-HandlingError(message) gemapptVITE_API_BASE default /apihooks/useWebSocket.js)¶VITE_WS_URL oder automatisch ws(s)://<host>/wsIn App.jsx werden u. a. verarbeitet:
PIPELINE_STATE_CHANGEDPIPELINE_PROGRESSPIPELINE_QUEUE_CHANGEDHARDWARE_MONITOR_UPDATERipster ist eine Client-Server-Anwendung mit REST + WebSocket und externen CLI-Tools.
graph TB
- subgraph Browser["Browser (React)"]
- Dashboard[Dashboard]
- Settings[Einstellungen]
- History[Historie]
- end
-
- subgraph Backend["Node.js Backend"]
- API[REST API\nExpress]
- WS[WebSocket\n/ws]
- Pipeline[pipelineService]
- Cron[cronService]
- DB[(SQLite)]
- end
-
- subgraph Tools["Externe Tools"]
- MakeMKV[makemkvcon]
- HandBrake[HandBrakeCLI]
- MediaInfo[mediainfo]
- end
-
- Browser <-->|HTTP| API
- Browser <-->|WebSocket| WS
- Pipeline --> MakeMKV
- Pipeline --> HandBrake
- Pipeline --> MediaInfo
- API --> DB
- Pipeline --> DB
- Cron --> DB pipelineService hält einen Snapshot der State-Machine und broadcastet Änderungen sofort via WebSocket.
Routes enthalten kaum Business-Logik.
Settings sind DB-schema-getrieben (settings_schema + settings_values), UI rendert dynamisch aus diesen Daten.
WebSocket läuft auf /ws.
Wichtige Events:
PIPELINE_STATE_CHANGED, PIPELINE_PROGRESS, PIPELINE_QUEUE_CHANGEDHARDWARE_MONITOR_UPDATESETTINGS_UPDATED, SETTINGS_BULK_UPDATEDSETTINGS_SCRIPTS_UPDATED, SETTINGS_SCRIPT_CHAINS_UPDATED, USER_PRESETS_UPDATEDCRON_JOBS_UPDATED, CRON_JOB_UPDATEDPIPELINE_ERROR, DISK_DETECTION_ERRORExterne Tools werden als Child-Processes gestartet (processRunner):
progressParsers.js)SQLite-Datei: backend/data/ripster.db
Kern-Tabellen:
jobs, pipeline_statesettings_schema, settings_valuesscripts, script_chains, script_chain_stepsuser_presetscron_jobs, cron_run_logsBeim Start werden Schema und Settings-Migrationen automatisch ausgeführt.
Zentrales Error-Handling liefert:
Fehlgeschlagene Jobs bleiben in der Historie (Fehler oder Abgebrochen) und können erneut gestartet werden.
CORS_ORIGIN default: *LOG_LEVEL default: infoDB_PATH/LOG_DIR konfigurierbar0&&i[i.length-1])&&(p[0]===6||p[0]===2)){r=0;continue}if(p[0]===3&&(!i||p[1]>i[0]&&p[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function K(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],s;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(a){s={error:a}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(s)throw s.error}}return i}function B(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o