1 line
180 KiB
JSON
1 line
180 KiB
JSON
{"config":{"lang":["de"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Ripster Handbuch","text":"<p>Dieses Dokumentationsset ist als Benutzerhandbuch aufgebaut: erst Bedienung und Alltag, dann Technik im Anhang.</p>"},{"location":"#schnellstart-in-3-schritten","title":"Schnellstart in 3 Schritten","text":"<ol> <li>Voraussetzungen pr\u00fcfen und installieren: Installation</li> <li>Grundkonfiguration in der UI setzen: Ersteinrichtung</li> <li>Ersten vollst\u00e4ndigen Job durchlaufen: Erster Lauf</li> </ol>"},{"location":"#was-du-hier-findest","title":"Was du hier findest","text":"<ul> <li>Benutzerhandbuch</li> <li>Installation</li> <li>GUI-Seiten im Detail (<code>Jobs</code>, <code>Ripper</code>, <code>Converter</code>, <code>Audiobooks</code>, <code>Settings</code>, <code>Historie</code>, <code>Downloads</code>, <code>Database</code>)</li> <li>typische Arbeitsabl\u00e4ufe aus Anwendersicht</li> <li>Technischer Anhang</li> <li>vollst\u00e4ndige Einstellungsreferenz</li> <li>Pipeline-/API-/Architekturdetails</li> <li>Deployment und Tool-Hintergr\u00fcnde</li> </ul>"},{"location":"#empfohlene-lesereihenfolge","title":"Empfohlene Lesereihenfolge","text":"<ol> <li>Benutzerhandbuch \u00dcberblick</li> <li>GUI-Seiten</li> <li>Workflows aus Nutzersicht</li> <li>Bei Bedarf: Technischer Anhang</li> </ol>"},{"location":"api/","title":"Anhang: API-Referenz","text":"<p>REST- und WebSocket-Schnittstellen f\u00fcr Integration, Automatisierung und Debugging.</p>"},{"location":"api/#basis-url","title":"Basis-URL","text":"<pre><code>http://localhost:3001\n</code></pre> <p>API-Prefix: <code>/api</code></p>"},{"location":"api/#api-gruppen","title":"API-Gruppen","text":"<ul> <li> <p> Health</p> <p>Service-Liveness.</p> <p><code>GET /api/health</code></p> </li> <li> <p> Pipeline API</p> <p>Analyse, Start/Retry/Cancel, Queue, Re-Encode.</p> <p> Pipeline API</p> </li> <li> <p> Settings API</p> <p>Einstellungen, Skripte/Ketten, User-Presets.</p> <p> Settings API</p> </li> <li> <p> History API</p> <p>Job-Historie, Orphan-Import, L\u00f6schoperationen.</p> <p> History API</p> </li> <li> <p> Cron API</p> <p>Zeitgesteuerte Skript-/Kettenausf\u00fchrung.</p> <p> Cron API</p> </li> <li> <p> Converter API</p> <p>Datei-Explorer, Upload, Job-Verwaltung f\u00fcr den Converter.</p> <p> Converter API</p> </li> <li> <p> Downloads API</p> <p>Download-Queue f\u00fcr Ausgabedateien aus der Job-Historie.</p> <p> Downloads API</p> </li> <li> <p> Runtime Activities API</p> <p>Laufende und abgeschlossene Aktivit\u00e4ten (Skripte, Ketten, Cron, Tasks).</p> <p> Runtime Activities</p> </li> <li> <p>:material-websocket: WebSocket Events</p> <p>Pipeline-, Queue-, Disk-, Settings-, Cron-, Converter- und Download-Events.</p> <p> WebSocket</p> </li> </ul>"},{"location":"api/#hinweis","title":"Hinweis","text":"<p>Ripster hat keine eingebaute Authentifizierung und ist f\u00fcr lokalen, gesch\u00fctzten Betrieb gedacht.</p>"},{"location":"api/converter/","title":"Converter API","text":"<p>Endpunkte f\u00fcr den Datei-Converter: Datei-Explorer, Upload, Job-Verwaltung.</p> <p>Basis-Pfad: <code>/api/converter</code></p>"},{"location":"api/converter/#scan-explorer","title":"Scan & Explorer","text":""},{"location":"api/converter/#get-apiconvertertree","title":"<code>GET /api/converter/tree</code>","text":"<p>Vollst\u00e4ndiger Verzeichnisbaum des <code>converter_raw_dir</code> (FS-basiert, keine DB).</p> <p>Response:</p> <pre><code>{\n \"tree\": [\n {\n \"name\": \"filme\",\n \"relPath\": \"filme\",\n \"isDir\": true,\n \"children\": [\n {\n \"name\": \"film.mkv\",\n \"relPath\": \"filme/film.mkv\",\n \"isDir\": false,\n \"size\": 10485760\n }\n ]\n }\n ]\n}\n</code></pre>"},{"location":"api/converter/#get-apiconverterbrowseparentrelpath","title":"<code>GET /api/converter/browse?parent=relPath</code>","text":"<p>DB-basierter Datei-Explorer. Gibt Eintr\u00e4ge f\u00fcr einen Unterordner zur\u00fcck.</p> <p>Query-Parameter:</p> Parameter Typ Beschreibung <code>parent</code> string Relativer Pfad zum Unterordner (leer = Root) <p>Response:</p> <pre><code>{\n \"entries\": [\n {\n \"id\": 1,\n \"rel_path\": \"filme/film.mkv\",\n \"is_dir\": false,\n \"size\": 10485760,\n \"job_id\": null\n }\n ],\n \"rawDir\": \"/data/output/converter-raw\"\n}\n</code></pre>"},{"location":"api/converter/#post-apiconverterscan","title":"<code>POST /api/converter/scan</code>","text":"<p>Manuellen Scan des <code>converter_raw_dir</code> ausl\u00f6sen. Aktualisiert <code>converter_scan_entries</code> in der DB.</p> <p>Response:</p> <pre><code>{ \"result\": { \"added\": 3, \"removed\": 1 } }\n</code></pre>"},{"location":"api/converter/#jobs-erstellen","title":"Jobs erstellen","text":""},{"location":"api/converter/#post-apiconverterjobsfrom-selection","title":"<code>POST /api/converter/jobs/from-selection</code>","text":"<p>Jobs aus im Datei-Explorer ausgew\u00e4hlten Dateien erstellen.</p> <p>Body:</p> <pre><code>{\n \"relPaths\": [\"filme/film.mkv\", \"musik/track.flac\"],\n \"audioMode\": \"individual\"\n}\n</code></pre> Feld Typ Beschreibung <code>relPaths</code> string[] Relative Pfade der ausgew\u00e4hlten Dateien <code>audioMode</code> string <code>individual</code> (ein Job pro Datei) oder <code>shared</code> (ein gemeinsamer Job) <p>Response:</p> <pre><code>{ \"jobs\": [{ \"id\": 42, \"status\": \"READY_TO_START\" }] }\n</code></pre>"},{"location":"api/converter/#post-apiconvertercreate-jobs","title":"<code>POST /api/converter/create-jobs</code>","text":"<p>Jobs aus DB-Scan-Eintr\u00e4gen erstellen.</p> <p>Body:</p> <pre><code>{\n \"entries\": [\n { \"relPath\": \"filme/film.mkv\", \"converterMediaType\": \"video\" }\n ]\n}\n</code></pre>"},{"location":"api/converter/#post-apiconverterupload","title":"<code>POST /api/converter/upload</code>","text":"<p>Dateien hochladen (Multipart, max. 50 Dateien).</p> <p>Form-Felder:</p> Feld Typ Beschreibung <code>files</code> File[] Hochzuladende Dateien <code>folderName</code> string (Optional) Ziel-Unterordner <p>Response:</p> <pre><code>{\n \"folders\": [{ \"folderRelPath\": \"upload-2026-03-30\", \"fileCount\": 2 }]\n}\n</code></pre>"},{"location":"api/converter/#job-verwaltung","title":"Job-Verwaltung","text":""},{"location":"api/converter/#get-apiconverterjobs","title":"<code>GET /api/converter/jobs</code>","text":"<p>Alle Converter-Jobs zur\u00fcckgeben.</p> <p>Response:</p> <pre><code>{ \"jobs\": [{ \"id\": 42, \"status\": \"READY_TO_START\", \"title\": \"film.mkv\" }] }\n</code></pre>"},{"location":"api/converter/#get-apiconverterjobsjobid","title":"<code>GET /api/converter/jobs/:jobId</code>","text":"<p>Einzelnen Converter-Job abrufen.</p>"},{"location":"api/converter/#post-apiconverterjobsjobidconfig","title":"<code>POST /api/converter/jobs/:jobId/config</code>","text":"<p>Konfigurationsentwurf f\u00fcr einen Job speichern (persistiert in <code>encode_plan_json</code>).</p> <p>Body: Partielles Konfig-Objekt (Ausgabeformat, Presets, Metadaten, Track-Auswahl).</p>"},{"location":"api/converter/#post-apiconverterjobsjobidassign-files","title":"<code>POST /api/converter/jobs/:jobId/assign-files</code>","text":"<p>Dateien einem bestehenden (noch nicht gestarteten) Job hinzuf\u00fcgen.</p> <p>Body:</p> <pre><code>{ \"relPaths\": [\"musik/track2.flac\"] }\n</code></pre>"},{"location":"api/converter/#post-apiconverterjobsjobidremove-file","title":"<code>POST /api/converter/jobs/:jobId/remove-file</code>","text":"<p>Datei aus einem Job entfernen (per relativer Pfad).</p> <p>Body:</p> <pre><code>{ \"relPath\": \"musik/track2.flac\" }\n</code></pre>"},{"location":"api/converter/#post-apiconverterjobsjobidremove-input","title":"<code>POST /api/converter/jobs/:jobId/remove-input</code>","text":"<p>Datei aus einem Job entfernen (per absolutem Eingabepfad).</p> <p>Body:</p> <pre><code>{ \"inputPath\": \"/data/output/converter-raw/musik/track2.flac\" }\n</code></pre>"},{"location":"api/converter/#post-apiconverterjobsjobidstart","title":"<code>POST /api/converter/jobs/:jobId/start</code>","text":"<p>Job mit finaler Konfiguration starten.</p> <p>Body:</p> <pre><code>{\n \"converterMediaType\": \"video\",\n \"outputFormat\": \"mkv\",\n \"userPreset\": \"H.264 MKV 1080p30\",\n \"trackSelection\": {},\n \"handBrakeTitleId\": 1,\n \"audioFormatOptions\": {}\n}\n</code></pre>"},{"location":"api/converter/#post-apiconverterjobsjobidcancel","title":"<code>POST /api/converter/jobs/:jobId/cancel</code>","text":"<p>Laufenden Job abbrechen.</p>"},{"location":"api/converter/#delete-apiconverterjobsjobid","title":"<code>DELETE /api/converter/jobs/:jobId</code>","text":"<p>Job aus der DB l\u00f6schen.</p>"},{"location":"api/converter/#datei-operationen","title":"Datei-Operationen","text":"<p>Alle Datei-Operationen arbeiten direkt auf dem Dateisystem (ohne DB-Aktualisierung). Ein anschlie\u00dfender Scan-Aufruf synchronisiert die DB.</p>"},{"location":"api/converter/#delete-apiconverterfiles","title":"<code>DELETE /api/converter/files</code>","text":"<p>Datei oder Ordner l\u00f6schen.</p> <p>Body:</p> <pre><code>{ \"relPath\": \"filme/film.mkv\" }\n</code></pre>"},{"location":"api/converter/#post-apiconverterfilesrename","title":"<code>POST /api/converter/files/rename</code>","text":"<p>Datei oder Ordner umbenennen.</p> <p>Body:</p> <pre><code>{ \"relPath\": \"filme/film.mkv\", \"newName\": \"film-neu.mkv\" }\n</code></pre>"},{"location":"api/converter/#post-apiconverterfilesmove","title":"<code>POST /api/converter/files/move</code>","text":"<p>Datei oder Ordner verschieben.</p> <p>Body:</p> <pre><code>{ \"relPath\": \"filme/film.mkv\", \"targetParentRelPath\": \"archiv\" }\n</code></pre> <p><code>targetParentRelPath = \"\"</code> verschiebt in das Root-Verzeichnis.</p>"},{"location":"api/converter/#post-apiconverterfilesfolder","title":"<code>POST /api/converter/files/folder</code>","text":"<p>Neuen Ordner anlegen.</p> <p>Body:</p> <pre><code>{ \"parentRelPath\": \"filme\", \"name\": \"neu\" }\n</code></pre>"},{"location":"api/crons/","title":"Cron API","text":"<p>Ripster enth\u00e4lt ein eingebautes Cron-System f\u00fcr Skripte und Skript-Ketten (<code>sourceType: script|chain</code>).</p>"},{"location":"api/crons/#get-apicrons","title":"GET /api/crons","text":"<p>Listet alle Cron-Jobs.</p> <pre><code>{\n \"jobs\": [\n {\n \"id\": 1,\n \"name\": \"Nachtlauf Backup\",\n \"cronExpression\": \"0 2 * * *\",\n \"sourceType\": \"script\",\n \"sourceId\": 3,\n \"sourceName\": \"Backup-Skript\",\n \"enabled\": true,\n \"pushoverEnabled\": true,\n \"lastRunAt\": \"2026-03-10T02:00:00.000Z\",\n \"lastRunStatus\": \"success\",\n \"nextRunAt\": \"2026-03-11T02:00:00.000Z\",\n \"createdAt\": \"2026-03-01T10:00:00.000Z\",\n \"updatedAt\": \"2026-03-10T02:00:05.000Z\"\n }\n ]\n}\n</code></pre>"},{"location":"api/crons/#post-apicrons","title":"POST /api/crons","text":"<p>Erstellt Cron-Job.</p> <pre><code>{\n \"name\": \"Nachtlauf Backup\",\n \"cronExpression\": \"0 2 * * *\",\n \"sourceType\": \"script\",\n \"sourceId\": 3,\n \"enabled\": true,\n \"pushoverEnabled\": true\n}\n</code></pre> <p>Response: <code>201</code> mit <code>{ \"job\": { ... } }</code></p>"},{"location":"api/crons/#get-apicronsid","title":"GET /api/crons/:id","text":"<p>Response:</p> <pre><code>{ \"job\": { \"id\": 1, \"name\": \"...\" } }\n</code></pre>"},{"location":"api/crons/#put-apicronsid","title":"PUT /api/crons/:id","text":"<p>Aktualisiert Cron-Job. Felder wie bei <code>POST</code>.</p> <p>Response:</p> <pre><code>{ \"job\": { ... } }\n</code></pre>"},{"location":"api/crons/#delete-apicronsid","title":"DELETE /api/crons/:id","text":"<p>Response:</p> <pre><code>{ \"removed\": { \"id\": 1, \"name\": \"Nachtlauf Backup\" } }\n</code></pre>"},{"location":"api/crons/#get-apicronsidlogs","title":"GET /api/crons/:id/logs","text":"<p>Liefert Ausf\u00fchrungs-Logs.</p> <p>Query-Parameter:</p> Parameter Typ Default Beschreibung <code>limit</code> number <code>20</code> Anzahl Eintr\u00e4ge, max. <code>100</code> <p>Response:</p> <pre><code>{\n \"logs\": [\n {\n \"id\": 42,\n \"cronJobId\": 1,\n \"startedAt\": \"2026-03-10T02:00:01.000Z\",\n \"finishedAt\": \"2026-03-10T02:00:05.000Z\",\n \"status\": \"success\",\n \"output\": \"Backup abgeschlossen.\",\n \"errorMessage\": null\n }\n ]\n}\n</code></pre> <p><code>status</code>: <code>running</code> | <code>success</code> | <code>error</code></p>"},{"location":"api/crons/#post-apicronsidrun","title":"POST /api/crons/:id/run","text":"<p>Triggert Job manuell (asynchron).</p> <p>Response:</p> <pre><code>{ \"triggered\": true, \"cronJobId\": 1 }\n</code></pre> <p>Wenn Job bereits l\u00e4uft: <code>409</code>.</p>"},{"location":"api/crons/#post-apicronsvalidate-expression","title":"POST /api/crons/validate-expression","text":"<p>Validiert 5-Felder-Cron-Ausdruck und berechnet n\u00e4chsten Lauf.</p> <p>Request:</p> <pre><code>{ \"cronExpression\": \"*/15 * * * *\" }\n</code></pre> <p>G\u00fcltige Response:</p> <pre><code>{\n \"valid\": true,\n \"nextRunAt\": \"2026-03-10T14:15:00.000Z\"\n}\n</code></pre> <p>Ung\u00fcltige Response:</p> <pre><code>{\n \"valid\": false,\n \"error\": \"Cron-Ausdruck muss genau 5 Felder haben (Minute Stunde Tag Monat Wochentag).\",\n \"nextRunAt\": null\n}\n</code></pre>"},{"location":"api/crons/#cron-format","title":"Cron-Format","text":"<p>Ripster unterst\u00fctzt 5 Felder:</p> <pre><code>Minute Stunde Tag Monat Wochentag\n</code></pre> <p>Beispiele:</p> <ul> <li><code>0 2 * * *</code> t\u00e4glich 02:00</li> <li><code>*/15 * * * *</code> alle 15 Minuten</li> <li><code>0 6 * * 1-5</code> Mo-Fr 06:00</li> </ul>"},{"location":"api/crons/#websocket-events-zu-cron","title":"WebSocket-Events zu Cron","text":"<ul> <li><code>CRON_JOBS_UPDATED</code> bei Create/Update/Delete</li> <li><code>CRON_JOB_UPDATED</code> bei Laufzeitstatus (<code>running</code> -> <code>success|error</code>)</li> </ul>"},{"location":"api/downloads/","title":"Downloads API","text":"<p>Endpunkte f\u00fcr die Download-Queue. Ausgabedateien aus der Job-Historie k\u00f6nnen als ZIP heruntergeladen werden.</p> <p>Basis-Pfad: <code>/api/downloads</code></p>"},{"location":"api/downloads/#get-apidownloads","title":"<code>GET /api/downloads</code>","text":"<p>Liste aller Download-Eintr\u00e4ge plus Zusammenfassung.</p> <p>Response:</p> <pre><code>{\n \"items\": [\n {\n \"id\": \"dl-42-raw\",\n \"jobId\": 42,\n \"target\": \"raw\",\n \"status\": \"ready\",\n \"archiveName\": \"Job_42_raw.zip\",\n \"outputPath\": null,\n \"createdAt\": \"2026-03-30T10:00:00.000Z\",\n \"updatedAt\": \"2026-03-30T10:01:00.000Z\",\n \"errorMessage\": null\n }\n ],\n \"summary\": {\n \"total\": 3,\n \"pending\": 1,\n \"processing\": 0,\n \"ready\": 1,\n \"failed\": 1\n }\n}\n</code></pre> <p>Status-Werte:</p> Status Beschreibung <code>pending</code> In der Queue, noch nicht gestartet <code>processing</code> ZIP wird gerade erstellt <code>ready</code> ZIP fertig, Download verf\u00fcgbar <code>failed</code> Fehler bei der Erstellung"},{"location":"api/downloads/#get-apidownloadssummary","title":"<code>GET /api/downloads/summary</code>","text":"<p>Nur die Zusammenfassung der Download-Queue (ohne Item-Liste).</p> <p>Response:</p> <pre><code>{\n \"summary\": {\n \"total\": 3,\n \"pending\": 1,\n \"processing\": 0,\n \"ready\": 1,\n \"failed\": 1\n }\n}\n</code></pre>"},{"location":"api/downloads/#post-apidownloadshistoryjobid","title":"<code>POST /api/downloads/history/:jobId</code>","text":"<p>Job-Ausgabe in die Download-Queue einreihen.</p> <p>Parameter:</p> Parameter Typ Beschreibung <code>jobId</code> number ID des Jobs in der Historie <p>Body:</p> <pre><code>{\n \"target\": \"raw\",\n \"outputPath\": null\n}\n</code></pre> Feld Typ Default Beschreibung <code>target</code> string <code>raw</code> <code>raw</code> (RAW-Dateien) oder <code>movie</code> (Ausgabedateien) <code>outputPath</code> string <code>null</code> Optionaler expliziter Ausgabepfad <p>Response (201 Created):</p> <pre><code>{\n \"created\": true,\n \"id\": \"dl-42-raw\",\n \"status\": \"pending\",\n \"summary\": { \"total\": 1, \"pending\": 1, \"processing\": 0, \"ready\": 0, \"failed\": 0 }\n}\n</code></pre> <p>Ist der Eintrag bereits vorhanden, wird <code>201</code> durch <code>200</code> ersetzt und <code>created: false</code> zur\u00fcckgegeben.</p>"},{"location":"api/downloads/#get-apidownloadsidfile","title":"<code>GET /api/downloads/:id/file</code>","text":"<p>Fertige ZIP-Datei herunterladen.</p> <p>Parameter:</p> Parameter Typ Beschreibung <code>id</code> string Download-ID (z. B. <code>dl-42-raw</code>) <p>Response: Datei-Download (<code>Content-Disposition: attachment</code>).</p> <p>Schl\u00e4gt fehl mit <code>404</code>, wenn kein Download-Eintrag mit dieser ID existiert oder die Datei noch nicht bereit ist.</p>"},{"location":"api/downloads/#delete-apidownloadsid","title":"<code>DELETE /api/downloads/:id</code>","text":"<p>Download-Eintrag l\u00f6schen (auch fertige ZIP-Dateien werden vom Dateisystem entfernt).</p> <p>Response:</p> <pre><code>{\n \"ok\": true,\n \"summary\": { \"total\": 2, \"pending\": 0, \"processing\": 0, \"ready\": 2, \"failed\": 0 }\n}\n</code></pre>"},{"location":"api/downloads/#websocket","title":"WebSocket","text":"<p>Status\u00e4nderungen werden \u00fcber <code>DOWNLOADS_UPDATED</code> in Echtzeit gemeldet. Vollst\u00e4ndige Dokumentation: WebSocket Events.</p>"},{"location":"api/history/","title":"History API","text":"<p>Endpunkte f\u00fcr Job-Historie, Metadaten-Nachpflege, Orphan-Import und L\u00f6schoperationen.</p>"},{"location":"api/history/#get-apihistory","title":"GET /api/history","text":"<p>Liefert Jobs mit optionalen Filtern.</p> <p>Query-Parameter:</p> Parameter Typ Beschreibung <code>status</code> string Einzelstatus-Filter (legacy-kompatibel). <code>statuses</code> string Mehrfachstatus als CSV, z. B. <code>FINISHED,ERROR</code>. <code>search</code> string Suche in Titel-/IMDb-Feldern. <code>limit</code> number Maximale Anzahl Ergebnisse. <code>lite</code> bool Ohne Dateisystem-Checks (schneller). <code>includeChildren</code> bool Child-Jobs (z. B. Serien-/Multipart-Kinder) explizit einbeziehen. <p>Beispiel:</p> <pre><code>GET /api/history?statuses=FINISHED,ERROR&search=Inception&limit=50&lite=1\n</code></pre> <p>Response (Beispiel):</p> <pre><code>{\n \"jobs\": [\n {\n \"id\": 42,\n \"status\": \"FINISHED\",\n \"title\": \"Inception\",\n \"job_kind\": \"bluray\",\n \"raw_path\": \"/mnt/raw/Inception - RAW - job-42\",\n \"output_path\": \"/mnt/movies/Inception (2010)/Inception (2010).mkv\",\n \"mediaType\": \"bluray\",\n \"ripSuccessful\": true,\n \"encodeSuccess\": true,\n \"created_at\": \"2026-03-10T08:00:00.000Z\",\n \"updated_at\": \"2026-03-10T10:00:00.000Z\"\n }\n ]\n}\n</code></pre>"},{"location":"api/history/#get-apihistoryid","title":"GET /api/history/:id","text":"<p>Liefert Job-Detail.</p> <p>Query-Parameter:</p> Parameter Typ Standard Beschreibung <code>includeLogs</code> bool <code>false</code> Prozesslog laden. <code>includeLiveLog</code> bool <code>false</code> Live-/Tail-Log laden. <code>includeAllLogs</code> bool <code>false</code> vollst\u00e4ndiges Log statt Tail. <code>logTailLines</code> number <code>800</code> Tail-L\u00e4nge falls nicht <code>includeAllLogs</code>. <code>lite</code> bool <code>false</code> Detailantwort ohne FS-Checks. <p>Response (Beispiel):</p> <pre><code>{\n \"job\": {\n \"id\": 42,\n \"status\": \"FINISHED\",\n \"makemkvInfo\": {},\n \"mediainfoInfo\": {},\n \"handbrakeInfo\": {},\n \"encodePlan\": {},\n \"log\": \"...\",\n \"log_count\": 1,\n \"logMeta\": {\n \"loaded\": true,\n \"total\": 800,\n \"returned\": 800,\n \"truncated\": true\n }\n }\n}\n</code></pre>"},{"location":"api/history/#get-apihistoryorphan-raw","title":"GET /api/history/orphan-raw","text":"<p>Sucht RAW-Ordner ohne zugeh\u00f6rigen Job.</p>"},{"location":"api/history/#post-apihistoryorphan-rawimport","title":"POST /api/history/orphan-raw/import","text":"<p>Importiert einen RAW-Ordner als Job und triggert optional die Analyse des Imports.</p> <p>Request:</p> <pre><code>{ \"rawPath\": \"/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99\" }\n</code></pre> <p>Response (Beispiel):</p> <pre><code>{\n \"job\": { \"id\": 77, \"status\": \"FINISHED\" },\n \"activation\": { \"started\": true },\n \"activationError\": null\n}\n</code></pre>"},{"location":"api/history/#post-apihistoryidomdbassign","title":"POST /api/history/:id/omdb/assign","text":"<p>Weist OMDb-/Filmdaten nachtr\u00e4glich zu.</p>"},{"location":"api/history/#post-apihistoryidcdassign","title":"POST /api/history/:id/cd/assign","text":"<p>Weist CD-Metadaten (z. B. MusicBrainz) nachtr\u00e4glich zu.</p>"},{"location":"api/history/#post-apihistoryiderrorack","title":"POST /api/history/:id/error/ack","text":"<p>Quittiert einen Fehlerzustand im Historieneintrag.</p>"},{"location":"api/history/#get-apihistoryiddelete-preview","title":"GET /api/history/:id/delete-preview","text":"<p>Liefert eine Vorschau der l\u00f6schbaren Pfade (inkl. Scope auf verkn\u00fcpfte Jobs).</p> <p>Query-Parameter:</p> Parameter Typ Standard Beschreibung <code>includeRelated</code> bool <code>true</code> Verkn\u00fcpfte Jobs in die Vorschau einbeziehen. <p>Response (Beispiel):</p> <pre><code>{\n \"preview\": {\n \"jobId\": 42,\n \"relatedJobs\": [{ \"id\": 42 }, { \"id\": 73 }],\n \"pathCandidates\": {\n \"raw\": [{ \"path\": \"/mnt/raw/...\", \"exists\": true, \"isDirectory\": true }],\n \"movie\": [{ \"path\": \"/mnt/movies/...\", \"exists\": true, \"isDirectory\": true }]\n }\n }\n}\n</code></pre>"},{"location":"api/history/#post-apihistoryiddelete-files","title":"POST /api/history/:id/delete-files","text":"<p>L\u00f6scht Dateien eines Jobs, beh\u00e4lt DB-Eintrag.</p> <p>Request:</p> <pre><code>{\n \"target\": \"both\",\n \"includeRelated\": true,\n \"selectedRawPaths\": [\"/mnt/raw/Inception - RAW - Disc1\"],\n \"selectedMoviePaths\": [\"/mnt/movies/Inception (2010)\"]\n}\n</code></pre> <p><code>target</code>: <code>raw</code> | <code>movie</code> | <code>both</code></p> <p><code>selectedRawPaths</code> / <code>selectedMoviePaths</code> sind optional und begrenzen die L\u00f6schung auf ausgew\u00e4hlte Pfade aus der Vorschau.</p>"},{"location":"api/history/#post-apihistoryiddelete","title":"POST /api/history/:id/delete","text":"<p>L\u00f6scht Job aus DB; optional auch Dateien.</p> <p>Request:</p> <pre><code>{\n \"target\": \"both\",\n \"includeRelated\": true,\n \"resetDriveState\": false,\n \"keepDetectedDevice\": true,\n \"preserveRawForImportJobs\": false,\n \"selectedRawPaths\": [\"/mnt/raw/Inception - RAW - Disc1\"],\n \"selectedMoviePaths\": [\"/mnt/movies/Inception (2010)\"]\n}\n</code></pre> <p><code>target</code>: <code>none</code> | <code>raw</code> | <code>movie</code> | <code>both</code></p> <p>Response (Beispiel):</p> <pre><code>{\n \"deleted\": true,\n \"jobId\": 42,\n \"fileTarget\": \"both\",\n \"fileSummary\": {\n \"target\": \"both\",\n \"raw\": { \"filesDeleted\": 10 },\n \"movie\": { \"filesDeleted\": 1 }\n },\n \"uiReset\": {\n \"reset\": true,\n \"state\": \"IDLE\"\n },\n \"safeguards\": {\n \"containsOrphanRawImportJob\": false,\n \"resetDriveStateApplied\": false,\n \"keepDetectedDeviceApplied\": true\n }\n}\n</code></pre>"},{"location":"api/history/#hinweise","title":"Hinweise","text":"<ul> <li>Ein aktiver Pipeline-Job kann nicht gel\u00f6scht werden (<code>409</code>).</li> <li>L\u00f6schoperationen sind irreversibel.</li> <li>Bei Serien-/Multipart-Containern steuern <code>includeRelated</code> und Pfadselektionen den genauen Scope.</li> </ul>"},{"location":"api/pipeline/","title":"Pipeline API","text":"<p>Endpunkte zur Steuerung des Pipeline-Workflows.</p>"},{"location":"api/pipeline/#get-apipipelinestate","title":"GET /api/pipeline/state","text":"<p>Liefert aktuellen Pipeline- und Hardware-Monitoring-Snapshot.</p> <p>Response (Beispiel):</p> <pre><code>{\n \"pipeline\": {\n \"state\": \"READY_TO_ENCODE\",\n \"activeJobId\": 42,\n \"progress\": 0,\n \"eta\": null,\n \"statusText\": \"Mediainfo best\u00e4tigt - Encode manuell starten\",\n \"context\": {\n \"jobId\": 42\n },\n \"jobProgress\": {\n \"42\": {\n \"state\": \"MEDIAINFO_CHECK\",\n \"progress\": 68.5,\n \"eta\": null,\n \"statusText\": \"MEDIAINFO_CHECK 68.50%\"\n }\n },\n \"queue\": {\n \"maxParallelJobs\": 1,\n \"runningCount\": 1,\n \"queuedCount\": 2,\n \"runningJobs\": [],\n \"queuedJobs\": []\n }\n },\n \"hardwareMonitoring\": {\n \"enabled\": true,\n \"intervalMs\": 5000,\n \"updatedAt\": \"2026-03-10T09:00:00.000Z\",\n \"sample\": {\n \"cpu\": {},\n \"memory\": {},\n \"gpu\": {},\n \"storage\": {}\n },\n \"error\": null\n }\n}\n</code></pre>"},{"location":"api/pipeline/#post-apipipelineanalyze","title":"POST /api/pipeline/analyze","text":"<p>Startet Disc-Analyse und legt Job an.</p> <p>Response:</p> <pre><code>{\n \"result\": {\n \"jobId\": 42,\n \"detectedTitle\": \"INCEPTION\",\n \"omdbCandidates\": []\n }\n}\n</code></pre>"},{"location":"api/pipeline/#post-apipipelinerescan-disc","title":"POST /api/pipeline/rescan-disc","text":"<p>Erzwingt erneute Laufwerkspr\u00fcfung.</p> <p>Response (Beispiel):</p> <pre><code>{\n \"result\": {\n \"present\": true,\n \"changed\": true,\n \"emitted\": \"discInserted\",\n \"device\": {\n \"path\": \"/dev/sr0\",\n \"discLabel\": \"INCEPTION\",\n \"mediaProfile\": \"bluray\"\n }\n }\n}\n</code></pre>"},{"location":"api/pipeline/#post-apipipelinerescan-drive","title":"POST /api/pipeline/rescan-drive","text":"<p>Erzwingt Rescan f\u00fcr ein konkretes Laufwerk.</p> <p>Request:</p> <pre><code>{ \"devicePath\": \"/dev/sr0\" }\n</code></pre>"},{"location":"api/pipeline/#get-apipipelineomdbsearchq","title":"GET /api/pipeline/omdb/search?q= <p>OMDb-Titelsuche.</p> <p>Response:</p> <pre><code>{\n \"results\": [\n {\n \"imdbId\": \"tt1375666\",\n \"title\": \"Inception\",\n \"year\": \"2010\",\n \"type\": \"movie\",\n \"poster\": \"https://...\"\n }\n ]\n}\n</code></pre>","text":""},{"location":"api/pipeline/#get-apipipelinetmdbseriessearchqseason","title":"GET /api/pipeline/tmdb/series/search?q=&season= <p>TMDb-Seriensuche (f\u00fcr Serien-Workflow bei DVD/Blu-ray).</p>","text":""},{"location":"api/pipeline/#get-apipipelinecddrives","title":"GET /api/pipeline/cd/drives <p>Liefert Snapshot der aktuell bekannten CD-Laufwerke.</p>","text":""},{"location":"api/pipeline/#get-apipipelinecdmusicbrainzsearchq","title":"GET /api/pipeline/cd/musicbrainz/search?q= <p>MusicBrainz-Suche f\u00fcr CD-Metadaten.</p>","text":""},{"location":"api/pipeline/#get-apipipelinecdmusicbrainzreleasembid","title":"GET /api/pipeline/cd/musicbrainz/release/:mbId <p>L\u00e4dt Release-Details zu einer MusicBrainz-ID.</p>","text":""},{"location":"api/pipeline/#post-apipipelinecdselect-metadata","title":"POST /api/pipeline/cd/select-metadata <p>\u00dcbernimmt CD-Metadaten f\u00fcr einen Job.</p> <p>Request (Beispiel):</p> <pre><code>{\n \"jobId\": 91,\n \"title\": \"Album\",\n \"artist\": \"Artist\",\n \"year\": 2020,\n \"mbId\": \"f5093c06-23e3-404f-aeaa-40f72885ee3a\",\n \"coverUrl\": \"https://...\",\n \"tracks\": []\n}\n</code></pre>","text":""},{"location":"api/pipeline/#post-apipipelinecdstartjobid","title":"POST /api/pipeline/cd/start/:jobId <p>Startet/queued CD-Rip mit Format-/Script-/Chain-Konfiguration.</p>","text":""},{"location":"api/pipeline/#post-apipipelineaudiobookupload","title":"POST /api/pipeline/audiobook/upload <p>Upload von AAX-Datei (Multipart/FormData).</p> <p>FormData-Felder:</p> <ul> <li><code>file</code> (Pflicht)</li> <li><code>format</code> (optional)</li> <li><code>startImmediately</code> (optional)</li> </ul>","text":""},{"location":"api/pipeline/#get-apipipelineaudiobookpending-activation","title":"GET /api/pipeline/audiobook/pending-activation <p>Zeigt AAX-Jobs mit fehlenden Activation-Bytes.</p>","text":""},{"location":"api/pipeline/#post-apipipelineaudiobookstartjobid","title":"POST /api/pipeline/audiobook/start/:jobId <p>Startet Audiobook-Job mit optionaler Konfiguration.</p>","text":""},{"location":"api/pipeline/#get-apipipelineaudiobookjobs","title":"GET /api/pipeline/audiobook/jobs <p>Liefert Audiobook-Jobliste.</p>","text":""},{"location":"api/pipeline/#get-apipipelineaudiobookoutput-tree","title":"GET /api/pipeline/audiobook/output-tree <p>Read-only Baumansicht des Audiobook-Ausgabeordners.</p>","text":""},{"location":"api/pipeline/#post-apipipelineselect-metadata","title":"POST /api/pipeline/select-metadata <p>Setzt Metadaten (und optional Playlist) f\u00fcr einen Job.</p> <p>Request:</p> <pre><code>{\n \"jobId\": 42,\n \"title\": \"Inception\",\n \"year\": 2010,\n \"imdbId\": \"tt1375666\",\n \"poster\": \"https://...\",\n \"fromOmdb\": true,\n \"selectedPlaylist\": \"00800\"\n}\n</code></pre> <p>Wichtige optionale Felder:</p> <ul> <li><code>selectedHandBrakeTitleId</code> / <code>selectedHandBrakeTitleIds</code>: Titel-Auswahl f\u00fcr Review/MediaInfo.</li> <li><code>metadataProvider</code>: <code>omdb</code> oder <code>tmdb</code>.</li> <li><code>workflowKind</code>: bei Disc-Jobs typischerweise <code>film</code> oder <code>series</code>.</li> <li><code>discNumber</code>: Pflicht f\u00fcr Serien-Disc-Zuordnung (<code>workflowKind=series</code>).</li> <li><code>duplicateAction</code>: Duplikatverhalten bei Film-Metadaten (<code>allow_new</code> oder <code>multipart_movie</code>).</li> <li><code>existingJobId</code>: optionaler Referenzjob f\u00fcr <code>multipart_movie</code>.</li> <li><code>existingDiscNumber</code>: Disc-Nummer des bestehenden Jobs f\u00fcr <code>multipart_movie</code>.</li> </ul> <p>Response:</p> <pre><code>{ \"job\": { \"id\": 42, \"status\": \"READY_TO_START\" } }\n</code></pre> <p>Konflikt-Response (Beispiel, HTTP 409):</p> <pre><code>{\n \"message\": \"Metadaten bereits in der Historie gefunden. Bitte Auswahl \u00fcbernehmen oder Multipart movie w\u00e4hlen.\",\n \"details\": [\n {\n \"code\": \"METADATA_DUPLICATE_FOUND\",\n \"mediaProfile\": \"bluray\",\n \"existingJob\": {\n \"id\": 17,\n \"title\": \"Inception\",\n \"year\": 2010,\n \"status\": \"FINISHED\",\n \"jobKind\": \"bluray\",\n \"discNumber\": 1,\n \"isMultipartMovie\": false\n }\n }\n ]\n}\n</code></pre> <p>Relevante Fehlercodes (<code>POST /api/pipeline/select-metadata</code>):</p> Code Bedeutung <code>METADATA_DUPLICATE_FOUND</code> Film-Metadaten existieren bereits in der Historie (Konfliktdialog im UI). <code>MULTIPART_DISC_REQUIRED</code> F\u00fcr Multipart fehlen Disc-Nummern (<code>discNumber</code>/<code>existingDiscNumber</code>). <code>MULTIPART_DISC_ALREADY_EXISTS</code> Disc-Nummer im Multipart-Container bereits vergeben oder doppelt. <code>MULTIPART_MEDIA_MISMATCH</code> Multipart nur bei gleichem Medientyp (DVD oder Blu-ray). <code>MULTIPART_SERIES_NOT_ALLOWED</code> Multipart ist nur f\u00fcr Film-Jobs erlaubt, nicht f\u00fcr Serien-Workflow. <code>MULTIPART_METADATA_MISMATCH</code> Ausgew\u00e4hlter bestehender Job passt nicht zu den Film-Metadaten. <code>SERIES_DISC_ALREADY_EXISTS</code> Serien-Disc-Nummer innerhalb einer Staffel bereits vorhanden.","text":""},{"location":"api/pipeline/#post-apipipelinejobsjobidraw-decision","title":"POST /api/pipeline/jobs/:jobId/raw-decision <p>Trifft Entscheidung bei vorhandenem RAW (<code>continue</code> oder <code>restart</code> je nach Dialogfluss).</p> <p>Request:</p> <pre><code>{ \"decision\": \"continue\" }\n</code></pre>","text":""},{"location":"api/pipeline/#post-apipipelinestartjobid","title":"POST /api/pipeline/start/:jobId <p>Startet vorbereiteten Job oder queued ihn (je nach Parallel-Limit).</p> <p>M\u00f6gliche Responses:</p> <pre><code>{ \"result\": { \"started\": true, \"stage\": \"RIPPING\" } }\n</code></pre> <pre><code>{ \"result\": { \"queued\": true, \"started\": false, \"queuePosition\": 2, \"action\": \"START_PREPARED\" } }\n</code></pre>","text":""},{"location":"api/pipeline/#post-apipipelineconfirm-encodejobid","title":"POST /api/pipeline/confirm-encode/:jobId <p>Best\u00e4tigt Review-Auswahl (Tracks, Pre/Post-Skripte/Ketten, User-Preset).</p> <p>Request (typisch):</p> <pre><code>{\n \"selectedEncodeTitleId\": 1,\n \"selectedTrackSelection\": {\n \"1\": {\n \"audioTrackIds\": [1, 2],\n \"subtitleTrackIds\": [3]\n }\n },\n \"selectedPreEncodeScriptIds\": [1],\n \"selectedPostEncodeScriptIds\": [2, 7],\n \"selectedPreEncodeChainIds\": [3],\n \"selectedPostEncodeChainIds\": [4],\n \"selectedUserPresetId\": 5,\n \"skipPipelineStateUpdate\": false\n}\n</code></pre> <p>Response:</p> <pre><code>{ \"job\": { \"id\": 42, \"encode_review_confirmed\": 1 } }\n</code></pre>","text":""},{"location":"api/pipeline/#post-apipipelinecancel","title":"POST /api/pipeline/cancel <p>Bricht laufenden Job ab oder entfernt Queue-Eintrag.</p> <p>Request (optional):</p> <pre><code>{ \"jobId\": 42 }\n</code></pre> <p>M\u00f6gliche Responses:</p> <pre><code>{ \"result\": { \"cancelled\": true, \"queuedOnly\": true, \"jobId\": 42 } }\n</code></pre> <pre><code>{ \"result\": { \"cancelled\": true, \"queuedOnly\": false, \"jobId\": 42 } }\n</code></pre> <pre><code>{ \"result\": { \"cancelled\": true, \"queuedOnly\": false, \"pending\": true, \"jobId\": 42 } }\n</code></pre>","text":""},{"location":"api/pipeline/#post-apipipelineretryjobid","title":"POST /api/pipeline/retry/:jobId <p>Retry f\u00fcr <code>ERROR</code>/<code>CANCELLED</code>-Jobs (oder Queue-Einreihung).</p>","text":""},{"location":"api/pipeline/#post-apipipelinereencodejobid","title":"POST /api/pipeline/reencode/:jobId <p>Startet Re-Encode aus bestehendem RAW.</p>","text":""},{"location":"api/pipeline/#post-apipipelinerestart-reviewjobid","title":"POST /api/pipeline/restart-review/:jobId <p>Berechnet Review aus RAW neu.</p>","text":""},{"location":"api/pipeline/#post-apipipelinerestart-encodejobid","title":"POST /api/pipeline/restart-encode/:jobId <p>Startet Encoding mit letzter best\u00e4tigter Review neu.</p>","text":""},{"location":"api/pipeline/#post-apipipelinerestart-cd-reviewjobid","title":"POST /api/pipeline/restart-cd-review/:jobId <p>Berechnet CD-Review aus vorhandenem RAW neu.</p>","text":""},{"location":"api/pipeline/#post-apipipelineresume-readyjobid","title":"POST /api/pipeline/resume-ready/:jobId <p>L\u00e4dt <code>READY_TO_ENCODE</code>-Job nach Neustart wieder in aktive Session.</p>","text":""},{"location":"api/pipeline/#get-apipipelineoutput-foldersjobid","title":"GET /api/pipeline/output-folders/:jobId <p>Liefert bekannte Output-Ordner entlang der Job-Lineage.</p>","text":""},{"location":"api/pipeline/#post-apipipelinedelete-output-foldersjobid","title":"POST /api/pipeline/delete-output-folders/:jobId <p>L\u00f6scht ausgew\u00e4hlte Output-Ordner.</p> <p>Request:</p> <pre><code>{ \"folderPaths\": [\"/mnt/movies/Inception (2010)\"] }\n</code></pre> <p>Alle Endpunkte liefern <code>{ result: ... }</code> bzw. <code>{ job: ... }</code>.</p>","text":""},{"location":"api/pipeline/#queue-endpunkte","title":"Queue-Endpunkte","text":""},{"location":"api/pipeline/#get-apipipelinequeue","title":"GET /api/pipeline/queue","text":"<p>Liefert Queue-Snapshot.</p> <pre><code>{\n \"queue\": {\n \"maxParallelJobs\": 1,\n \"runningCount\": 1,\n \"queuedCount\": 3,\n \"runningJobs\": [\n {\n \"jobId\": 41,\n \"title\": \"Inception\",\n \"status\": \"ENCODING\",\n \"lastState\": \"ENCODING\"\n }\n ],\n \"queuedJobs\": [\n {\n \"entryId\": 11,\n \"position\": 1,\n \"type\": \"job\",\n \"jobId\": 42,\n \"action\": \"START_PREPARED\",\n \"actionLabel\": \"Start\",\n \"title\": \"Matrix\",\n \"status\": \"READY_TO_ENCODE\",\n \"lastState\": \"READY_TO_ENCODE\",\n \"hasScripts\": true,\n \"hasChains\": false,\n \"enqueuedAt\": \"2026-03-10T09:00:00.000Z\"\n },\n {\n \"entryId\": 12,\n \"position\": 2,\n \"type\": \"wait\",\n \"waitSeconds\": 30,\n \"title\": \"Warten 30s\",\n \"status\": \"QUEUED\",\n \"enqueuedAt\": \"2026-03-10T09:01:00.000Z\"\n }\n ],\n \"updatedAt\": \"2026-03-10T09:01:02.000Z\"\n }\n}\n</code></pre>"},{"location":"api/pipeline/#post-apipipelinequeuereorder","title":"POST /api/pipeline/queue/reorder","text":"<p>Sortiert Queue-Eintr\u00e4ge neu.</p> <p>Request:</p> <pre><code>{\n \"orderedEntryIds\": [12, 11]\n}\n</code></pre> <p>Legacy fallback wird akzeptiert:</p> <pre><code>{\n \"orderedJobIds\": [42, 43]\n}\n</code></pre>"},{"location":"api/pipeline/#post-apipipelinequeueentry","title":"POST /api/pipeline/queue/entry","text":"<p>F\u00fcgt Nicht-Job-Queue-Eintrag hinzu (<code>script</code>, <code>chain</code>, <code>wait</code>).</p> <p>Request-Beispiele:</p> <pre><code>{ \"type\": \"script\", \"scriptId\": 3 }\n</code></pre> <pre><code>{ \"type\": \"chain\", \"chainId\": 2, \"insertAfterEntryId\": 11 }\n</code></pre> <pre><code>{ \"type\": \"wait\", \"waitSeconds\": 45 }\n</code></pre> <p>Response:</p> <pre><code>{\n \"result\": { \"entryId\": 12, \"type\": \"wait\", \"position\": 2 },\n \"queue\": { \"...\": \"...\" }\n}\n</code></pre>"},{"location":"api/pipeline/#delete-apipipelinequeueentryentryid","title":"DELETE /api/pipeline/queue/entry/:entryId","text":"<p>Entfernt Queue-Eintrag.</p> <p>Response:</p> <pre><code>{ \"queue\": { \"...\": \"...\" } }\n</code></pre>"},{"location":"api/pipeline/#pipeline-zustande","title":"Pipeline-Zust\u00e4nde State Bedeutung <code>IDLE</code> Wartet auf Medium <code>DISC_DETECTED</code> Medium erkannt <code>ANALYZING</code> MakeMKV-Analyse l\u00e4uft <code>METADATA_SELECTION</code> Metadaten-Auswahl <code>WAITING_FOR_USER_DECISION</code> Nutzerentscheidung erforderlich (Playlist-Auswahl oder RAW-Entscheidung) <code>READY_TO_START</code> \u00dcbergang vor Start <code>RIPPING</code> MakeMKV-Rip l\u00e4uft <code>MEDIAINFO_CHECK</code> Titel-/Track-Auswertung <code>READY_TO_ENCODE</code> Review bereit <code>ENCODING</code> HandBrake-Encoding l\u00e4uft <code>CD_METADATA_SELECTION</code> CD-Metadatenauswahl aktiv <code>CD_READY_TO_RIP</code> CD-Job ist startbereit <code>CD_ANALYZING</code> CD-Struktur/Tracks werden vorbereitet <code>CD_RIPPING</code> CD-Ripping l\u00e4uft <code>CD_ENCODING</code> CD-Encode l\u00e4uft <code>FINISHED</code> Abgeschlossen <code>DONE</code> Abgeschlossen (v. a. Audiobook/Converter/CD-Varianten) <code>CANCELLED</code> Abgebrochen <code>ERROR</code> Fehler","text":""},{"location":"api/runtime-activities/","title":"Runtime Activities API","text":"<p>Ripster verfolgt alle laufenden und k\u00fcrzlich abgeschlossenen Aktivit\u00e4ten (Skripte, Skript-Ketten, Cron-Jobs, interne Tasks) in Echtzeit \u00fcber den <code>RuntimeActivityService</code>.</p>"},{"location":"api/runtime-activities/#ubersicht","title":"\u00dcbersicht","text":"<p>Aktivit\u00e4ten entstehen, wenn Ripster intern Aktionen ausf\u00fchrt \u2013 z. B. beim Start eines Cron-Jobs, beim Ausf\u00fchren einer Skript-Kette oder beim Durchlaufen von Pipeline-Schritten. Sie sind nicht persistent (kein DB-Speicher) und werden nur im Arbeitsspeicher gehalten.</p> <ul> <li>Aktive Aktivit\u00e4ten (<code>active</code>): Laufen gerade.</li> <li>Letzte Aktivit\u00e4ten (<code>recent</code>): Abgeschlossen, max. 120 Eintr\u00e4ge.</li> </ul> <p>\u00c4nderungen werden \u00fcber WebSocket (<code>RUNTIME_ACTIVITY_CHANGED</code>) in Echtzeit gesendet.</p>"},{"location":"api/runtime-activities/#aktivitats-objekt","title":"Aktivit\u00e4ts-Objekt","text":"<pre><code>{\n \"id\": 7,\n \"type\": \"chain\",\n \"name\": \"Post-Encode Aufr\u00e4umen\",\n \"status\": \"running\",\n \"source\": \"cron\",\n \"message\": \"Schritt 2 von 3\",\n \"currentStep\": \"cleanup.sh\",\n \"currentStepType\": \"script\",\n \"currentScriptName\": \"cleanup.sh\",\n \"stepIndex\": 2,\n \"stepTotal\": 3,\n \"parentActivityId\": null,\n \"jobId\": 42,\n \"cronJobId\": 3,\n \"chainId\": 5,\n \"scriptId\": null,\n \"canCancel\": true,\n \"canNextStep\": false,\n \"outcome\": \"running\",\n \"errorMessage\": null,\n \"output\": null,\n \"stdout\": null,\n \"stderr\": null,\n \"stdoutTruncated\": false,\n \"stderrTruncated\": false,\n \"startedAt\": \"2026-03-10T10:00:00.000Z\",\n \"finishedAt\": null,\n \"durationMs\": null,\n \"exitCode\": null,\n \"success\": null\n}\n</code></pre>"},{"location":"api/runtime-activities/#felder","title":"Felder","text":"Feld Typ Beschreibung <code>id</code> <code>number</code> Eindeutige ID (Laufz\u00e4hler, nicht persistent) <code>type</code> <code>string</code> Art der Aktivit\u00e4t: <code>script</code> | <code>chain</code> | <code>cron</code> | <code>task</code> <code>name</code> <code>string \\| null</code> Anzeigename der Aktivit\u00e4t <code>status</code> <code>string</code> Aktueller Status: <code>running</code> | <code>success</code> | <code>error</code> <code>source</code> <code>string \\| null</code> Ausl\u00f6ser (z. B. <code>cron</code>, <code>pipeline</code>, <code>manual</code>) <code>message</code> <code>string \\| null</code> Kurztext zum aktuellen Zustand <code>currentStep</code> <code>string \\| null</code> Name des aktuell ausgef\u00fchrten Schritts <code>currentStepType</code> <code>string \\| null</code> Typ des Schritts (z. B. <code>script</code>, <code>wait</code>) <code>currentScriptName</code> <code>string \\| null</code> Name des Skripts im aktuellen Schritt <code>stepIndex</code> <code>number \\| null</code> Aktueller Schritt (1-basiert) <code>stepTotal</code> <code>number \\| null</code> Gesamtanzahl Schritte <code>parentActivityId</code> <code>number \\| null</code> ID der \u00fcbergeordneten Aktivit\u00e4t <code>jobId</code> <code>number \\| null</code> Verkn\u00fcpfte Job-ID <code>cronJobId</code> <code>number \\| null</code> Verkn\u00fcpfte Cron-Job-ID <code>chainId</code> <code>number \\| null</code> Verkn\u00fcpfte Skript-Ketten-ID <code>scriptId</code> <code>number \\| null</code> Verkn\u00fcpfte Skript-ID <code>canCancel</code> <code>boolean</code> Abbrechen \u00fcber API m\u00f6glich <code>canNextStep</code> <code>boolean</code> N\u00e4chster Schritt \u00fcber API ausl\u00f6sbar <code>outcome</code> <code>string \\| null</code> Abschluss-Ergebnis: <code>success</code> | <code>error</code> | <code>cancelled</code> | <code>skipped</code> | <code>running</code> <code>errorMessage</code> <code>string \\| null</code> Fehlermeldung (max. 2.000 Zeichen) <code>output</code> <code>string \\| null</code> Allgemeine Ausgabe (max. 12.000 Zeichen) <code>stdout</code> <code>string \\| null</code> Standardausgabe des Prozesses (max. 12.000 Zeichen) <code>stderr</code> <code>string \\| null</code> Fehlerausgabe des Prozesses (max. 12.000 Zeichen) <code>stdoutTruncated</code> <code>boolean</code> <code>true</code>, wenn <code>stdout</code> gek\u00fcrzt wurde <code>stderrTruncated</code> <code>boolean</code> <code>true</code>, wenn <code>stderr</code> gek\u00fcrzt wurde <code>startedAt</code> <code>string</code> ISO-8601-Zeitstempel des Starts <code>finishedAt</code> <code>string \\| null</code> ISO-8601-Zeitstempel des Endes <code>durationMs</code> <code>number \\| null</code> Laufzeit in Millisekunden <code>exitCode</code> <code>number \\| null</code> Exit-Code des Prozesses <code>success</code> <code>boolean \\| null</code> Erfolgsstatus (<code>null</code> bei laufender Aktivit\u00e4t)"},{"location":"api/runtime-activities/#snapshot-objekt","title":"Snapshot-Objekt","text":"<p>Alle Aktivit\u00e4ts-Endpunkte geben einen Snapshot zur\u00fcck:</p> <pre><code>{\n \"active\": [ /* laufende Aktivit\u00e4ten, nach startedAt absteigend */ ],\n \"recent\": [ /* abgeschlossene Aktivit\u00e4ten, nach finishedAt absteigend, max. 120 */ ],\n \"updatedAt\": \"2026-03-10T10:05:00.000Z\"\n}\n</code></pre>"},{"location":"api/runtime-activities/#endpunkte","title":"Endpunkte","text":""},{"location":"api/runtime-activities/#get-apiruntimeactivities","title":"GET <code>/api/runtime/activities</code>","text":"<p>Aktuellen Aktivit\u00e4ts-Snapshot abrufen.</p> <p>Antwort:</p> <pre><code>{\n \"active\": [],\n \"recent\": [\n {\n \"id\": 5,\n \"type\": \"script\",\n \"name\": \"notify.sh\",\n \"status\": \"success\",\n \"outcome\": \"success\",\n \"startedAt\": \"2026-03-10T09:58:00.000Z\",\n \"finishedAt\": \"2026-03-10T09:58:02.000Z\",\n \"durationMs\": 2100,\n \"exitCode\": 0,\n \"success\": true,\n \"canCancel\": false,\n \"canNextStep\": false\n }\n ],\n \"updatedAt\": \"2026-03-10T10:05:00.000Z\"\n}\n</code></pre>"},{"location":"api/runtime-activities/#post-apiruntimeactivitiesidcancel","title":"POST <code>/api/runtime/activities/:id/cancel</code>","text":"<p>Aktive Aktivit\u00e4t abbrechen (nur wenn <code>canCancel: true</code>).</p> <p>Parameter:</p> Name In Typ Beschreibung <code>id</code> path <code>number</code> Aktivit\u00e4ts-ID <code>reason</code> body <code>string</code> Optionaler Abbruchgrund <p>Request Body:</p> <pre><code>{ \"reason\": \"Manueller Abbruch durch Benutzer\" }\n</code></pre> <p>Antwort (Erfolg):</p> <pre><code>{\n \"ok\": true,\n \"action\": null,\n \"snapshot\": { \"active\": [], \"recent\": [], \"updatedAt\": \"...\" }\n}\n</code></pre> <p>Fehlercodes:</p> HTTP Bedeutung <code>404</code> Aktivit\u00e4t nicht gefunden oder bereits abgeschlossen <code>409</code> Abbrechen wird von dieser Aktivit\u00e4t nicht unterst\u00fctzt"},{"location":"api/runtime-activities/#post-apiruntimeactivitiesidnext-step","title":"POST <code>/api/runtime/activities/:id/next-step</code>","text":"<p>N\u00e4chsten Schritt einer Aktivit\u00e4t ausl\u00f6sen (nur wenn <code>canNextStep: true</code>).</p> <p>Parameter:</p> Name In Typ Beschreibung <code>id</code> path <code>number</code> Aktivit\u00e4ts-ID <p>Antwort (Erfolg):</p> <pre><code>{\n \"ok\": true,\n \"action\": null,\n \"snapshot\": { \"active\": [], \"recent\": [], \"updatedAt\": \"...\" }\n}\n</code></pre> <p>Fehlercodes:</p> HTTP Bedeutung <code>404</code> Aktivit\u00e4t nicht gefunden <code>409</code> N\u00e4chster Schritt wird von dieser Aktivit\u00e4t nicht unterst\u00fctzt"},{"location":"api/runtime-activities/#post-apiruntimeactivitiesclear-recent","title":"POST <code>/api/runtime/activities/clear-recent</code>","text":"<p>Alle abgeschlossenen Aktivit\u00e4ten aus <code>recent</code> l\u00f6schen.</p> <p>Antwort:</p> <pre><code>{\n \"ok\": true,\n \"removed\": 14,\n \"snapshot\": { \"active\": [], \"recent\": [], \"updatedAt\": \"...\" }\n}\n</code></pre>"},{"location":"api/runtime-activities/#grenzwerte","title":"Grenzwerte","text":"Wert Limit Maximale <code>recent</code>-Eintr\u00e4ge 120 Maximale L\u00e4nge <code>stdout</code> / <code>stderr</code> / <code>output</code> 12.000 Zeichen Maximale L\u00e4nge <code>errorMessage</code> / <code>message</code> 2.000 Zeichen Maximale L\u00e4nge <code>outcome</code> 40 Zeichen <p>Gek\u00fcrzte Ausgaben erhalten den Suffix <code>...[gek\u00fcrzt]</code> (bei Inline-Text) bzw. <code>\\n...[gek\u00fcrzt]</code> (bei mehrzeiligem Output).</p>"},{"location":"api/runtime-activities/#echtzeit-updates","title":"Echtzeit-Updates","text":"<p>\u00c4nderungen werden automatisch als <code>RUNTIME_ACTIVITY_CHANGED</code> WebSocket-Event gesendet. Die Frontend-Komponente braucht <code>GET /api/runtime/activities</code> nur beim initialen Laden aufzurufen.</p>"},{"location":"api/settings/","title":"Settings API","text":"<p>Endpunkte f\u00fcr Einstellungen, Skripte, Skript-Ketten und User-Presets.</p>"},{"location":"api/settings/#get-apisettings","title":"GET /api/settings","text":"<p>Liefert alle Einstellungen kategorisiert.</p> <p>Response (Struktur):</p> <pre><code>{\n \"categories\": [\n {\n \"category\": \"Pfade\",\n \"settings\": [\n {\n \"key\": \"raw_dir\",\n \"label\": \"Raw Ausgabeordner\",\n \"type\": \"path\",\n \"required\": true,\n \"description\": \"...\",\n \"defaultValue\": \"data/output/raw\",\n \"options\": [],\n \"validation\": { \"minLength\": 1 },\n \"value\": \"data/output/raw\",\n \"orderIndex\": 100\n }\n ]\n }\n ]\n}\n</code></pre>"},{"location":"api/settings/#put-apisettingskey","title":"PUT /api/settings/:key","text":"<p>Aktualisiert eine einzelne Einstellung.</p> <p>Request:</p> <pre><code>{ \"value\": \"/mnt/storage/raw\" }\n</code></pre> <p>Response:</p> <pre><code>{\n \"setting\": {\n \"key\": \"raw_dir\",\n \"value\": \"/mnt/storage/raw\"\n },\n \"reviewRefresh\": {\n \"triggered\": false,\n \"reason\": \"not_ready\"\n }\n}\n</code></pre> <p><code>reviewRefresh</code> ist <code>null</code> oder ein Objekt mit Status der optionalen Review-Neuberechnung.</p>"},{"location":"api/settings/#put-apisettings","title":"PUT /api/settings","text":"<p>Aktualisiert mehrere Einstellungen atomar.</p> <p>Request:</p> <pre><code>{\n \"settings\": {\n \"raw_dir\": \"/mnt/storage/raw\",\n \"movie_dir\": \"/mnt/storage/movies\",\n \"handbrake_preset_bluray\": \"H.264 MKV 1080p30\"\n }\n}\n</code></pre> <p>Response:</p> <pre><code>{\n \"changes\": [\n { \"key\": \"raw_dir\", \"value\": \"/mnt/storage/raw\" },\n { \"key\": \"movie_dir\", \"value\": \"/mnt/storage/movies\" }\n ],\n \"reviewRefresh\": {\n \"triggered\": true,\n \"jobId\": 42,\n \"relevantKeys\": [\"handbrake_preset_bluray\"]\n }\n}\n</code></pre> <p>Bei Validierungsfehlern kommt <code>400</code> mit <code>error.details[]</code>.</p>"},{"location":"api/settings/#get-apisettingshandbrake-presets","title":"GET /api/settings/handbrake-presets","text":"<p>Liest Preset-Liste via <code>HandBrakeCLI -z</code> (mit Fallback auf konfigurierte Presets).</p> <p>Response (Beispiel):</p> <pre><code>{\n \"source\": \"handbrake-cli\",\n \"message\": null,\n \"options\": [\n { \"label\": \"General/\", \"value\": \"__group__general\", \"disabled\": true, \"category\": \"General\" },\n { \"label\": \" Fast 1080p30\", \"value\": \"Fast 1080p30\", \"category\": \"General\" }\n ]\n}\n</code></pre>"},{"location":"api/settings/#post-apisettingspushovertest","title":"POST /api/settings/pushover/test","text":"<p>Sendet Testnachricht \u00fcber aktuelle PushOver-Settings.</p> <p>Request (optional):</p> <pre><code>{\n \"title\": \"Test\",\n \"message\": \"Ripster Test\"\n}\n</code></pre> <p>Response:</p> <pre><code>{\n \"result\": {\n \"sent\": true,\n \"eventKey\": \"test\",\n \"requestId\": \"...\"\n }\n}\n</code></pre> <p>Wenn PushOver deaktiviert ist oder Credentials fehlen, kommt i. d. R. ebenfalls <code>200</code> mit <code>sent: false</code> + <code>reason</code>.</p>"},{"location":"api/settings/#skripte","title":"Skripte","text":"<p>Basis: <code>/api/settings/scripts</code></p>"},{"location":"api/settings/#get-apisettingsscripts","title":"GET /api/settings/scripts","text":"<pre><code>{ \"scripts\": [ { \"id\": 1, \"name\": \"...\", \"scriptBody\": \"...\", \"orderIndex\": 1, \"createdAt\": \"...\", \"updatedAt\": \"...\" } ] }\n</code></pre>"},{"location":"api/settings/#post-apisettingsscripts","title":"POST /api/settings/scripts","text":"<pre><code>{ \"name\": \"Move\", \"scriptBody\": \"mv \\\"$RIPSTER_OUTPUT_PATH\\\" /mnt/movies/\" }\n</code></pre> <p>Response: <code>201</code> mit <code>{ \"script\": { ... } }</code></p>"},{"location":"api/settings/#put-apisettingsscriptsid","title":"PUT /api/settings/scripts/:id","text":"<p>Body wie <code>POST</code>, Response <code>{ \"script\": { ... } }</code>.</p>"},{"location":"api/settings/#delete-apisettingsscriptsid","title":"DELETE /api/settings/scripts/:id","text":"<p>Response <code>{ \"removed\": { ... } }</code>.</p>"},{"location":"api/settings/#post-apisettingsscriptsreorder","title":"POST /api/settings/scripts/reorder","text":"<pre><code>{ \"orderedScriptIds\": [3, 1, 2] }\n</code></pre> <p>Response <code>{ \"scripts\": [ ... ] }</code>.</p>"},{"location":"api/settings/#post-apisettingsscriptsidtest","title":"POST /api/settings/scripts/:id/test","text":"<p>F\u00fchrt Skript als Testlauf aus.</p> <pre><code>{\n \"result\": {\n \"scriptId\": 1,\n \"scriptName\": \"Move\",\n \"success\": true,\n \"exitCode\": 0,\n \"signal\": null,\n \"timedOut\": false,\n \"durationMs\": 120,\n \"stdout\": \"...\",\n \"stderr\": \"...\",\n \"stdoutTruncated\": false,\n \"stderrTruncated\": false\n }\n}\n</code></pre>"},{"location":"api/settings/#umgebungsvariablen-fur-skripte","title":"Umgebungsvariablen f\u00fcr Skripte","text":"<p>Diese Variablen werden beim Ausf\u00fchren gesetzt:</p> <ul> <li><code>RIPSTER_SCRIPT_RUN_AT</code></li> <li><code>RIPSTER_JOB_ID</code></li> <li><code>RIPSTER_JOB_TITLE</code></li> <li><code>RIPSTER_MODE</code></li> <li><code>RIPSTER_INPUT_PATH</code></li> <li><code>RIPSTER_OUTPUT_PATH</code></li> <li><code>RIPSTER_RAW_PATH</code></li> <li><code>RIPSTER_SCRIPT_ID</code></li> <li><code>RIPSTER_SCRIPT_NAME</code></li> <li><code>RIPSTER_SCRIPT_SOURCE</code></li> </ul>"},{"location":"api/settings/#skript-ketten","title":"Skript-Ketten","text":"<p>Basis: <code>/api/settings/script-chains</code></p> <p>Eine Kette hat Schritte vom Typ:</p> <ul> <li><code>script</code> (<code>scriptId</code> erforderlich)</li> <li><code>wait</code> (<code>waitSeconds</code> 1..3600)</li> </ul>"},{"location":"api/settings/#get-apisettingsscript-chains","title":"GET /api/settings/script-chains","text":"<p>Response <code>{ \"chains\": [ ... ] }</code> (inkl. <code>steps[]</code>).</p>"},{"location":"api/settings/#get-apisettingsscript-chainsid","title":"GET /api/settings/script-chains/:id","text":"<p>Response <code>{ \"chain\": { ... } }</code>.</p>"},{"location":"api/settings/#post-apisettingsscript-chains","title":"POST /api/settings/script-chains","text":"<pre><code>{\n \"name\": \"After Encode\",\n \"steps\": [\n { \"stepType\": \"script\", \"scriptId\": 1 },\n { \"stepType\": \"wait\", \"waitSeconds\": 15 },\n { \"stepType\": \"script\", \"scriptId\": 2 }\n ]\n}\n</code></pre> <p>Response: <code>201</code> mit <code>{ \"chain\": { ... } }</code></p>"},{"location":"api/settings/#put-apisettingsscript-chainsid","title":"PUT /api/settings/script-chains/:id","text":"<p>Body wie <code>POST</code>, Response <code>{ \"chain\": { ... } }</code>.</p>"},{"location":"api/settings/#delete-apisettingsscript-chainsid","title":"DELETE /api/settings/script-chains/:id","text":"<p>Response <code>{ \"removed\": { ... } }</code>.</p>"},{"location":"api/settings/#post-apisettingsscript-chainsreorder","title":"POST /api/settings/script-chains/reorder","text":"<pre><code>{ \"orderedChainIds\": [2, 1, 3] }\n</code></pre> <p>Response <code>{ \"chains\": [ ... ] }</code>.</p>"},{"location":"api/settings/#post-apisettingsscript-chainsidtest","title":"POST /api/settings/script-chains/:id/test","text":"<p>Response:</p> <pre><code>{\n \"result\": {\n \"chainId\": 2,\n \"chainName\": \"After Encode\",\n \"steps\": 3,\n \"succeeded\": 3,\n \"failed\": 0,\n \"aborted\": false,\n \"results\": []\n }\n}\n</code></pre>"},{"location":"api/settings/#user-presets","title":"User-Presets","text":"<p>Basis: <code>/api/settings/user-presets</code></p>"},{"location":"api/settings/#get-apisettingsuser-presets","title":"GET /api/settings/user-presets","text":"<p>Optionaler Query-Parameter: <code>media_type=bluray|dvd|other|all</code></p> <pre><code>{\n \"presets\": [\n {\n \"id\": 1,\n \"name\": \"Blu-ray HQ\",\n \"mediaType\": \"bluray\",\n \"handbrakePreset\": \"H.264 MKV 1080p30\",\n \"extraArgs\": \"--encoder-preset slow\",\n \"description\": \"...\",\n \"createdAt\": \"...\",\n \"updatedAt\": \"...\"\n }\n ]\n}\n</code></pre>"},{"location":"api/settings/#post-apisettingsuser-presets","title":"POST /api/settings/user-presets","text":"<pre><code>{\n \"name\": \"Blu-ray HQ\",\n \"mediaType\": \"bluray\",\n \"handbrakePreset\": \"H.264 MKV 1080p30\",\n \"extraArgs\": \"--encoder-preset slow\",\n \"description\": \"optional\"\n}\n</code></pre> <p>Response: <code>201</code> mit <code>{ \"preset\": { ... } }</code></p>"},{"location":"api/settings/#put-apisettingsuser-presetsid","title":"PUT /api/settings/user-presets/:id","text":"<p>Body mit beliebigen Feldern aus <code>POST</code>, Response <code>{ \"preset\": { ... } }</code>.</p>"},{"location":"api/settings/#delete-apisettingsuser-presetsid","title":"DELETE /api/settings/user-presets/:id","text":"<p>Response <code>{ \"removed\": { ... } }</code>.</p>"},{"location":"api/settings/#weitere-endpunkte","title":"Weitere Endpunkte","text":""},{"location":"api/settings/#get-apisettingseffective-paths","title":"GET /api/settings/effective-paths","text":"<p>Liefert aufgel\u00f6ste, effektiv genutzte Pfade aus den Settings (f\u00fcr UI- und Diagnosezwecke).</p>"},{"location":"api/settings/#post-apisettingscoverartrecover","title":"POST /api/settings/coverart/recover","text":"<p>Startet eine manuelle Coverart-Recovery.</p> <p>Response (Beispiel):</p> <pre><code>{\n \"result\": { \"started\": true },\n \"scheduler\": { \"enabled\": true }\n}\n</code></pre>"},{"location":"api/settings/#activation-bytes-aax","title":"Activation Bytes (AAX)","text":""},{"location":"api/settings/#get-apisettingsactivation-bytes","title":"GET /api/settings/activation-bytes","text":"<p>Liest gecachte AAX-Activation-Bytes.</p>"},{"location":"api/settings/#post-apisettingsactivation-bytes","title":"POST /api/settings/activation-bytes","text":"<p>Speichert Activation-Bytes.</p> <p>Request:</p> <pre><code>{\n \"checksum\": \"abc123...\",\n \"activationBytes\": \"1a2b3c4d\"\n}\n</code></pre>"},{"location":"api/settings/#laufwerke","title":"Laufwerke","text":""},{"location":"api/settings/#get-apisettingsdrives","title":"GET /api/settings/drives","text":"<p>Liefert erkannte optische Laufwerke (<code>/dev/...</code>) inkl. MakeMKV-Disc-Index.</p>"},{"location":"api/settings/#post-apisettingsdrivesforce-unlock","title":"POST /api/settings/drives/force-unlock","text":"<p>Erzwingt Entsperren aktiver Laufwerkslocks.</p> <p>Hinweis: Expertenmodus (<code>ui_expert_mode</code>) ist erforderlich, sonst <code>403</code>.</p> <p>Request (ein Laufwerk):</p> <pre><code>{ \"devicePath\": \"/dev/sr0\" }\n</code></pre> <p>Request (alle):</p> <pre><code>{ \"all\": true }\n</code></pre>"},{"location":"api/settings/#ui-preferences","title":"UI Preferences","text":""},{"location":"api/settings/#get-apisettingsprefskey","title":"GET /api/settings/prefs/:key","text":"<p>Liest einen persistierten UI-Preference-Wert.</p>"},{"location":"api/settings/#put-apisettingsprefskey","title":"PUT /api/settings/prefs/:key","text":"<p>Speichert einen persistierten UI-Preference-Wert.</p> <p>Request:</p> <pre><code>{ \"value\": \"{\\\"columns\\\":[\\\"id\\\",\\\"status\\\"]}\" }\n</code></pre>"},{"location":"api/websocket/","title":"WebSocket Events","text":"<p>Ripster sendet Echtzeit-Updates \u00fcber <code>/ws</code>.</p>"},{"location":"api/websocket/#verbindung","title":"Verbindung","text":"<pre><code>const ws = new WebSocket('ws://localhost:3001/ws');\n\nws.onmessage = (event) => {\n const msg = JSON.parse(event.data);\n console.log(msg.type, msg.payload);\n};\n</code></pre>"},{"location":"api/websocket/#nachrichtenformat","title":"Nachrichtenformat","text":"<p>Die meisten Broadcasts haben dieses Schema:</p> <pre><code>{\n \"type\": \"EVENT_TYPE\",\n \"payload\": {},\n \"timestamp\": \"2026-03-10T09:00:00.000Z\"\n}\n</code></pre> <p>Ausnahme: <code>WS_CONNECTED</code> beim Verbindungsaufbau enth\u00e4lt kein <code>timestamp</code>.</p>"},{"location":"api/websocket/#event-typen","title":"Event-Typen","text":""},{"location":"api/websocket/#ws_connected","title":"WS_CONNECTED","text":"<p>Sofort nach erfolgreicher Verbindung.</p> <pre><code>{\n \"type\": \"WS_CONNECTED\",\n \"payload\": {\n \"connectedAt\": \"2026-03-10T09:00:00.000Z\"\n }\n}\n</code></pre>"},{"location":"api/websocket/#pipeline_state_changed","title":"PIPELINE_STATE_CHANGED","text":"<p>Neuer Pipeline-Snapshot.</p> <pre><code>{\n \"type\": \"PIPELINE_STATE_CHANGED\",\n \"payload\": {\n \"state\": \"ENCODING\",\n \"activeJobId\": 42,\n \"progress\": 62.5,\n \"eta\": \"00:12:34\",\n \"statusText\": \"ENCODING 62.50%\",\n \"context\": {},\n \"jobProgress\": {\n \"42\": {\n \"state\": \"ENCODING\",\n \"progress\": 62.5,\n \"eta\": \"00:12:34\",\n \"statusText\": \"ENCODING 62.50%\"\n }\n },\n \"queue\": {\n \"maxParallelJobs\": 1,\n \"runningCount\": 1,\n \"queuedCount\": 2,\n \"runningJobs\": [],\n \"queuedJobs\": []\n }\n }\n}\n</code></pre>"},{"location":"api/websocket/#pipeline_progress","title":"PIPELINE_PROGRESS","text":"<p>Laufende Fortschrittsupdates.</p> <pre><code>{\n \"type\": \"PIPELINE_PROGRESS\",\n \"payload\": {\n \"state\": \"ENCODING\",\n \"activeJobId\": 42,\n \"progress\": 62.5,\n \"eta\": \"00:12:34\",\n \"statusText\": \"ENCODING 62.50%\"\n }\n}\n</code></pre>"},{"location":"api/websocket/#pipeline_queue_changed","title":"PIPELINE_QUEUE_CHANGED","text":"<p>Queue-Snapshot aktualisiert.</p>"},{"location":"api/websocket/#disc_detected-disc_removed","title":"DISC_DETECTED / DISC_REMOVED","text":"<p>Disc-Insertion/-Removal.</p> <pre><code>{\n \"type\": \"DISC_DETECTED\",\n \"payload\": {\n \"device\": {\n \"path\": \"/dev/sr0\",\n \"discLabel\": \"INCEPTION\",\n \"model\": \"ASUS BW-16D1HT\",\n \"fstype\": \"udf\",\n \"mountpoint\": null,\n \"mediaProfile\": \"bluray\"\n }\n }\n}\n</code></pre> <p><code>mediaProfile</code>: <code>bluray</code> | <code>dvd</code> | <code>other</code> | <code>null</code></p>"},{"location":"api/websocket/#hardware_monitor_update","title":"HARDWARE_MONITOR_UPDATE","text":"<p>Snapshot aus Hardware-Monitoring.</p> <pre><code>{\n \"type\": \"HARDWARE_MONITOR_UPDATE\",\n \"payload\": {\n \"enabled\": true,\n \"intervalMs\": 5000,\n \"updatedAt\": \"2026-03-10T09:00:00.000Z\",\n \"sample\": {\n \"cpu\": {},\n \"memory\": {},\n \"gpu\": {},\n \"storage\": {}\n },\n \"error\": null\n }\n}\n</code></pre>"},{"location":"api/websocket/#pipeline_error","title":"PIPELINE_ERROR","text":"<p>Fehler bei Disc-Event-Verarbeitung in Pipeline.</p>"},{"location":"api/websocket/#disk_detection_error","title":"DISK_DETECTION_ERROR","text":"<p>Fehler in Laufwerkserkennung.</p>"},{"location":"api/websocket/#settings_updated","title":"SETTINGS_UPDATED","text":"<p>Einzelnes Setting wurde gespeichert.</p>"},{"location":"api/websocket/#settings_bulk_updated","title":"SETTINGS_BULK_UPDATED","text":"<p>Bulk-Settings gespeichert.</p> <pre><code>{\n \"type\": \"SETTINGS_BULK_UPDATED\",\n \"payload\": {\n \"count\": 3,\n \"keys\": [\"raw_dir\", \"movie_dir\", \"handbrake_preset_bluray\"]\n }\n}\n</code></pre>"},{"location":"api/websocket/#settings_scripts_updated","title":"SETTINGS_SCRIPTS_UPDATED","text":"<p>Skript ge\u00e4ndert (<code>created|updated|deleted|reordered</code>).</p>"},{"location":"api/websocket/#settings_script_chains_updated","title":"SETTINGS_SCRIPT_CHAINS_UPDATED","text":"<p>Skript-Kette ge\u00e4ndert (<code>created|updated|deleted|reordered</code>).</p>"},{"location":"api/websocket/#user_presets_updated","title":"USER_PRESETS_UPDATED","text":"<p>User-Preset ge\u00e4ndert (<code>created|updated|deleted</code>).</p>"},{"location":"api/websocket/#cron_jobs_updated","title":"CRON_JOBS_UPDATED","text":"<p>Cron-Config ge\u00e4ndert (<code>created|updated|deleted</code>).</p>"},{"location":"api/websocket/#cron_job_updated","title":"CRON_JOB_UPDATED","text":"<p>Laufzeitstatus eines Cron-Jobs ge\u00e4ndert.</p> <pre><code>{\n \"type\": \"CRON_JOB_UPDATED\",\n \"payload\": {\n \"id\": 1,\n \"lastRunStatus\": \"running\",\n \"lastRunAt\": \"2026-03-10T10:00:00.000Z\",\n \"nextRunAt\": null\n }\n}\n</code></pre>"},{"location":"api/websocket/#runtime_activity_changed","title":"RUNTIME_ACTIVITY_CHANGED","text":"<p>Vollst\u00e4ndiger Snapshot aller laufenden und k\u00fcrzlich abgeschlossenen Aktivit\u00e4ten.</p> <p>Wird ausgel\u00f6st, wenn eine Aktivit\u00e4t gestartet, aktualisiert oder abgeschlossen wird sowie nach <code>clear-recent</code>.</p> <pre><code>{\n \"type\": \"RUNTIME_ACTIVITY_CHANGED\",\n \"payload\": {\n \"active\": [\n {\n \"id\": 7,\n \"type\": \"chain\",\n \"name\": \"Post-Encode Aufr\u00e4umen\",\n \"status\": \"running\",\n \"source\": \"cron\",\n \"message\": \"Schritt 2 von 3\",\n \"currentStep\": \"cleanup.sh\",\n \"currentStepType\": \"script\",\n \"stepIndex\": 2,\n \"stepTotal\": 3,\n \"canCancel\": true,\n \"canNextStep\": false,\n \"outcome\": \"running\",\n \"startedAt\": \"2026-03-10T10:00:00.000Z\",\n \"finishedAt\": null,\n \"durationMs\": null\n }\n ],\n \"recent\": [],\n \"updatedAt\": \"2026-03-10T10:00:05.000Z\"\n }\n}\n</code></pre> <p>Vollst\u00e4ndige Feldbeschreibung: Runtime Activities API.</p>"},{"location":"api/websocket/#converter_scan_update","title":"CONVERTER_SCAN_UPDATE","text":"<p>Wird nach einem Scan des Converter-Eingangsordners gesendet (manuell oder per Polling).</p> <pre><code>{\n \"type\": \"CONVERTER_SCAN_UPDATE\",\n \"payload\": {\n \"entryCount\": 12\n }\n}\n</code></pre>"},{"location":"api/websocket/#downloads_updated","title":"DOWNLOADS_UPDATED","text":"<p>Wird bei jeder Status\u00e4nderung in der Download-Queue gesendet.</p> <pre><code>{\n \"type\": \"DOWNLOADS_UPDATED\",\n \"payload\": {\n \"reason\": \"ready\",\n \"item\": {\n \"id\": \"dl-1\",\n \"jobId\": 42,\n \"status\": \"ready\",\n \"archiveName\": \"Job_42_movie.zip\",\n \"createdAt\": \"2026-03-30T10:00:00.000Z\"\n },\n \"summary\": {\n \"total\": 3,\n \"pending\": 1,\n \"processing\": 0,\n \"ready\": 1,\n \"failed\": 1\n }\n }\n}\n</code></pre> <p>M\u00f6gliche <code>reason</code>-Werte: <code>queued</code>, <code>processing</code>, <code>ready</code>, <code>failed</code>, <code>deleted</code>.</p>"},{"location":"api/websocket/#reconnect-verhalten","title":"Reconnect-Verhalten","text":"<p><code>useWebSocket</code> verbindet bei Abbruch automatisch neu:</p> <ul> <li>Retry-Intervall: <code>1500ms</code></li> <li>Wiederverbindung bis Komponente unmounted wird</li> </ul>"},{"location":"appendix/","title":"Technischer Anhang","text":"<p>Dieser Bereich enth\u00e4lt die technische Referenz hinter dem Benutzerhandbuch.</p>"},{"location":"appendix/#inhalt","title":"Inhalt","text":"<ul> <li>Konfiguration</li> <li>komplette Feldreferenz</li> <li>Umgebungsvariablen</li> <li>Pipeline intern</li> <li>Zustandsmodell</li> <li>Encode-Planung</li> <li>Playlist-Analyse</li> <li>Pre-/Post-Encode-Ausf\u00fchrungen</li> <li>API-Referenz</li> <li>REST-Endpunkte</li> <li>WebSocket-Events</li> <li>Architektur</li> <li>Backend-/Frontend-Aufbau</li> <li>Datenbank</li> <li>Deployment</li> <li>Betrieb in Entwicklung und Produktion</li> <li>Externe Tools</li> <li>MakeMKV, HandBrake, MediaInfo</li> </ul>"},{"location":"appendix/#wann-du-in-den-anhang-wechselst","title":"Wann du in den Anhang wechselst","text":"<ul> <li>du integrierst Ripster mit anderen Systemen</li> <li>du betreibst mehrere Instanzen oder willst tiefer debuggen</li> <li>du brauchst Feld-/API-/Schema-Details f\u00fcr Automatisierung</li> </ul>"},{"location":"architecture/","title":"Anhang: Architektur","text":"<p>Ripster ist eine Client-Server-Anwendung mit REST + WebSocket und externen CLI-Tools.</p>"},{"location":"architecture/#systemuberblick","title":"System\u00fcberblick","text":"<pre><code>graph TB\n subgraph Browser[\"Browser (React)\"]\n Ripper[Ripper]\n Settings[Einstellungen]\n History[Historie]\n end\n\n subgraph Backend[\"Node.js Backend\"]\n API[REST API\\nExpress]\n WS[WebSocket\\n/ws]\n Pipeline[pipelineService]\n Cron[cronService]\n DB[(SQLite)]\n end\n\n subgraph Tools[\"Externe Tools\"]\n MakeMKV[makemkvcon]\n HandBrake[HandBrakeCLI]\n MediaInfo[mediainfo]\n end\n\n Browser <-->|HTTP| API\n Browser <-->|WebSocket| WS\n Pipeline --> MakeMKV\n Pipeline --> HandBrake\n Pipeline --> MediaInfo\n API --> DB\n Pipeline --> DB\n Cron --> DB</code></pre>"},{"location":"architecture/#details","title":"Details","text":"<ul> <li> \u00dcbersicht</li> <li> Backend-Services</li> <li> Frontend-Komponenten</li> <li> Datenbank</li> </ul>"},{"location":"architecture/backend/","title":"Backend-Services","text":"<p>Das Backend ist in Services aufgeteilt, die von Express-Routen orchestriert werden. Seit v0.12.0 erfolgt die Medienverarbeitung \u00fcber ein Plugin-System.</p>"},{"location":"architecture/backend/#plugin-system-srcplugins","title":"Plugin-System (<code>src/plugins/</code>)","text":"<p>Ab v0.12.0 ist die Pipeline modular aufgebaut. Jeder Medientyp wird von einem eigenen Plugin verwaltet.</p>"},{"location":"architecture/backend/#basisklassen","title":"Basisklassen","text":"<ul> <li><code>PluginBase.js</code> \u2014 Abstrakte Basisklasse <code>SourcePlugin</code> mit Lifecycle-Methoden</li> <li><code>PluginRegistry.js</code> \u2014 Dynamische Plugin-Erkennung, priorit\u00e4tsbasierte Auswahl, Settings-Schema-Aggregation</li> <li><code>PluginContext.js</code> \u2014 Ausf\u00fchrungskontext (Logger, Callbacks, Signals) f\u00fcr Plugin-Aufrufe</li> </ul>"},{"location":"architecture/backend/#plugin-lifecycle","title":"Plugin-Lifecycle","text":"<p>Jedes Plugin implementiert die folgenden Methoden:</p> Methode Beschreibung <code>detect(discInfo)</code> Medientyp-Erkennung <code>analyze(devicePath, job, ctx)</code> Analyse / Metadaten-Extraktion <code>rip(job, ctx)</code> Rohdaten-Extraktion <code>review(job, ctx)</code> Optionale Vorschau-Vorbereitung <code>encode(job, ctx)</code> Finales Encoding/Konvertierung <code>finalize(job, ctx)</code> Nachbearbeitung <code>onCancel(job, ctx)</code> Bereinigung bei Abbruch <code>onRetry(job, ctx)</code> Zustand-Reset vor Retry"},{"location":"architecture/backend/#implementierte-plugins","title":"Implementierte Plugins","text":"<ul> <li><code>BluRayPlugin.js</code> \u2014 Blu-ray via MakeMKV (Backup-Modus)</li> <li><code>DVDPlugin.js</code> \u2014 DVD via MakeMKV (MKV-Modus)</li> <li><code>CdPlugin.js</code> \u2014 Audio-CD via cdparanoia + FFmpeg (experimentell)</li> <li><code>AudiobookPlugin.js</code> \u2014 AAX/M4B via FFmpeg mit Kapitel-Splitting</li> <li><code>ConverterPlugin.js</code> \u2014 Generische Audio/Video-Konvertierung via FFmpeg</li> <li><code>VideoDiscPlugin.js</code> \u2014 Gemeinsame Basis f\u00fcr Blu-ray/DVD (MediaInfo-Parsing)</li> </ul>"},{"location":"architecture/backend/#pipelineservicejs","title":"<code>pipelineService.js</code>","text":"<p>Zentrale Workflow-Orchestrierung.</p> <p>Aufgaben:</p> <ul> <li>Pipeline-State-Machine + Persistenz (<code>pipeline_state</code>)</li> <li>Disc-Analyse/Rip/Review/Encode via Plugin-Delegation</li> <li>Queue-Management (Jobs + <code>script|chain|wait</code> Eintr\u00e4ge)</li> <li>Retry/Re-Encode/Restart-Flows</li> <li>WebSocket-Broadcasts f\u00fcr State/Progress/Queue</li> <li>Converter-Job-Verwaltung (<code>createConverterJobFromEntry</code>, <code>uploadConverterFiles</code>, <code>startConverterJob</code>)</li> </ul> <p>Wichtige Methoden:</p> <ul> <li><code>analyzeDisc()</code></li> <li><code>selectMetadata()</code></li> <li><code>startPreparedJob()</code></li> <li><code>confirmEncodeReview()</code></li> <li><code>cancel()</code></li> <li><code>retry()</code></li> <li><code>reencodeFromRaw()</code></li> <li><code>restartReviewFromRaw()</code></li> <li><code>restartEncodeWithLastSettings()</code></li> <li><code>resumeReadyToEncodeJob()</code></li> <li><code>enqueueNonJobEntry()</code>, <code>reorderQueue()</code>, <code>removeQueueEntry()</code></li> <li><code>createConverterJobFromEntry()</code>, <code>createConverterJobsFromSelection()</code></li> <li><code>uploadConverterFiles()</code>, <code>startConverterJob()</code></li> </ul>"},{"location":"architecture/backend/#diskdetectionservicejs","title":"<code>diskDetectionService.js</code>","text":"<p>Pollt Laufwerk(e) und emittiert:</p> <ul> <li><code>discInserted</code></li> <li><code>discRemoved</code></li> <li><code>error</code></li> </ul> <p>Zusatz:</p> <ul> <li>Modus <code>auto</code> oder <code>explicit</code></li> <li>heuristische <code>mediaProfile</code>-Erkennung (<code>bluray</code>/<code>dvd</code>/<code>other</code>)</li> <li><code>rescanAndEmit()</code> f\u00fcr manuellen Trigger</li> </ul>"},{"location":"architecture/backend/#settingsservicejs","title":"<code>settingsService.js</code>","text":"<p>Settings-Layer mit Validation/Serialisierung.</p> <p>Features:</p> <ul> <li><code>getCategorizedSettings()</code> f\u00fcr UI-Form</li> <li><code>setSettingValue()</code> / <code>setSettingsBulk()</code></li> <li>profilspezifische Aufl\u00f6sung (<code>resolveEffectiveToolSettings</code>)</li> <li>CLI-Config-Building f\u00fcr MakeMKV/HandBrake/MediaInfo/FFmpeg</li> <li>HandBrake-Preset-Liste via <code>HandBrakeCLI -z</code></li> <li>MakeMKV-Registration-Command aus <code>makemkv_registration_key</code></li> <li>Pfadaufl\u00f6sung f\u00fcr alle Medienprofile inkl. Audiobook und Converter</li> </ul>"},{"location":"architecture/backend/#historyservicejs","title":"<code>historyService.js</code>","text":"<p>Historie + Dateioperationen.</p> <p>Features:</p> <ul> <li>Job-Liste/Detail inkl. Log-Tail</li> <li>Orphan-RAW-Erkennung und Import</li> <li>OMDb-Nachzuweisung</li> <li>Dateil\u00f6schung (<code>raw|movie|both</code>)</li> <li>Job-L\u00f6schung (<code>none|raw|movie|both</code>)</li> </ul>"},{"location":"architecture/backend/#audiobookservicejs","title":"<code>audiobookService.js</code>","text":"<p>Audiobook-Verarbeitung (AAX/M4B).</p> <p>Features:</p> <ul> <li>FFprobe-basierte Analyse (Kapitel, Metadaten)</li> <li>FFmpeg-basiertes Encoding mit Kapitel-Splitting</li> <li>Ausgabeformate: M4B, MP3, FLAC</li> <li>Template-basierte Pfade (<code>{author}</code>, <code>{title}</code>, <code>{year}</code>, <code>{narrator}</code>, <code>{series}</code>, <code>{part}</code>)</li> </ul>"},{"location":"architecture/backend/#activationbytesservicejs","title":"<code>activationBytesService.js</code>","text":"<p>Verwaltung von Audible-Activation-Bytes f\u00fcr AAX-DRM-Handling.</p> <p>Features:</p> <ul> <li>SHA-1-Pr\u00fcfsumme der AAX-Datei berechnen</li> <li>Activation Bytes in <code>aax_activation_bytes</code>-Tabelle cachen</li> <li>Lookup via <code>audnexService</code> (wenn konfiguriert)</li> </ul>"},{"location":"architecture/backend/#converterscanservicejs","title":"<code>converterScanService.js</code>","text":"<p>Dateisystem-\u00dcberwachung des Converter-Eingangsordners.</p> <p>Features:</p> <ul> <li>Vollst\u00e4ndiger FS-Baum (<code>getTree()</code>)</li> <li>DB-basierter Datei-Explorer (<code>getEntries()</code>)</li> <li>Manueller und automatischer Scan (Polling)</li> <li>Pfad-Normalisierung mit Traversal-Schutz</li> <li>WebSocket-Broadcast: <code>CONVERTER_SCAN_UPDATE</code></li> </ul>"},{"location":"architecture/backend/#downloadservicejs","title":"<code>downloadService.js</code>","text":"<p>Download-Queue f\u00fcr Ausgabedateien aus der Historie.</p> <p>Features:</p> <ul> <li>Job in Download-Queue einreihen (<code>enqueueHistoryJob</code>)</li> <li>ZIP-Archiv aus Ausgabedateien erstellen (<code>archiver</code>)</li> <li>Datei-Streaming via <code>res.download()</code></li> <li>WebSocket-Broadcast: <code>DOWNLOADS_UPDATED</code> bei Status-\u00c4nderungen</li> <li>Download-Item l\u00f6schen</li> </ul>"},{"location":"architecture/backend/#cronservicejs","title":"<code>cronService.js</code>","text":"<p>Integriertes Cron-System ohne externe Parser-Library.</p> <p>Features:</p> <ul> <li>5-Feld-Cron-Parser + <code>nextRun</code>-Berechnung</li> <li>Quellen: <code>script</code> oder <code>chain</code></li> <li>Laufzeitlogs (<code>cron_run_logs</code>)</li> <li>manuelles Triggern</li> <li>WebSocket-Events: <code>CRON_JOBS_UPDATED</code>, <code>CRON_JOB_UPDATED</code></li> </ul>"},{"location":"architecture/backend/#runtimeactivityservicejs","title":"<code>runtimeActivityService.js</code>","text":"<p>In-Memory-Tracking aller laufenden und k\u00fcrzlich abgeschlossenen Aktivit\u00e4ten (Skripte, Ketten, Cron-Jobs, Tasks).</p> <p>Features:</p> <ul> <li><code>startActivity(type, payload)</code> \u2192 Aktivit\u00e4t registrieren, ID zur\u00fcckgeben</li> <li><code>updateActivity(id, patch)</code> \u2192 Laufende Aktivit\u00e4t aktualisieren</li> <li><code>completeActivity(id, payload)</code> \u2192 Aktivit\u00e4t abschlie\u00dfen und in <code>recent</code> verschieben</li> <li><code>setControls(id, { cancel, nextStep })</code> \u2192 Steuer-Handler registrieren (f\u00fcr <code>canCancel</code>/<code>canNextStep</code>)</li> <li><code>requestCancel(id)</code> / <code>requestNextStep(id)</code> \u2192 Steuer-Handler aufrufen</li> <li><code>clearRecent()</code> \u2192 Abgeschlossene Aktivit\u00e4ten l\u00f6schen</li> <li><code>getSnapshot()</code> \u2192 Snapshot mit <code>active</code> + <code>recent</code> + <code>updatedAt</code></li> <li>Broadcasts <code>RUNTIME_ACTIVITY_CHANGED</code> \u00fcber WebSocket bei jeder \u00c4nderung</li> </ul> <p>Limits:</p> <ul> <li><code>recent</code> max. 120 Eintr\u00e4ge</li> <li><code>stdout</code>/<code>stderr</code>/<code>output</code> max. 12.000 Zeichen</li> <li><code>message</code>/<code>errorMessage</code> max. 2.000 Zeichen</li> </ul> <p>Vollst\u00e4ndige API-Dokumentation: Runtime Activities API</p>"},{"location":"architecture/backend/#weitere-services","title":"Weitere Services","text":"<ul> <li><code>scriptService.js</code> (CRUD + Test + Wrapper-Ausf\u00fchrung)</li> <li><code>scriptChainService.js</code> (CRUD + Step-Execution)</li> <li><code>userPresetService.js</code> (HandBrake User-Presets)</li> <li><code>hardwareMonitorService.js</code> (CPU/RAM/GPU/Storage)</li> <li><code>websocketService.js</code> (Client-Registry + Broadcast)</li> <li><code>notificationService.js</code> (PushOver)</li> <li><code>cdRipService.js</code> (CD-Ripping mit cdparanoia)</li> <li><code>musicBrainzService.js</code> (MusicBrainz-Metadaten-Lookup)</li> <li><code>audnexService.js</code> (Audnex-API f\u00fcr Audiobook-Metadaten)</li> <li><code>coverArtRecoveryService.js</code> (Cover-Art-Wiederherstellung)</li> <li><code>thumbnailService.js</code> (Vorschaubild-Generierung)</li> <li><code>omdbService.js</code> (OMDb-Metadaten-Suche)</li> <li><code>logger.js</code> (rotierende Datei-Logs)</li> </ul>"},{"location":"architecture/backend/#bootstrapping-srcindexjs","title":"Bootstrapping (<code>src/index.js</code>)","text":"<p>Beim Start:</p> <ol> <li>DB init/migrate</li> <li>Pipeline-Init (inkl. Plugin-Registry)</li> <li>Cron-Init</li> <li>Express-Routes + Error-Handler</li> <li>WebSocket-Server auf <code>/ws</code></li> <li>Hardware-Monitoring-Init</li> <li>Disk-Detection-Start</li> <li>Converter-Scan-Service-Init (Polling falls aktiviert)</li> </ol>"},{"location":"architecture/database/","title":"Datenbank","text":"<p>Ripster verwendet SQLite (<code>backend/data/ripster.db</code>).</p>"},{"location":"architecture/database/#tabellen","title":"Tabellen","text":"<pre><code>settings_schema\nsettings_values\njobs\njob_lineage_artifacts\njob_output_folders\npipeline_state\nscripts\nscript_chains\nscript_chain_steps\nuser_presets\ncron_jobs\ncron_run_logs\naax_activation_bytes\nuser_prefs\nconverter_scan_entries\n</code></pre>"},{"location":"architecture/database/#jobs","title":"<code>jobs</code>","text":"<p>Speichert Pipeline-Lifecycle und Artefakte pro Job.</p> <p>Zentrale Felder:</p> <ul> <li>Metadaten: <code>title</code>, <code>year</code>, <code>imdb_id</code>, <code>poster_url</code>, <code>omdb_json</code>, <code>selected_from_omdb</code></li> <li>Laufzeit: <code>start_time</code>, <code>end_time</code>, <code>status</code>, <code>last_state</code></li> <li>Pfade: <code>raw_path</code>, <code>output_path</code>, <code>encode_input_path</code></li> <li>Tool-Ausgaben: <code>makemkv_info_json</code>, <code>handbrake_info_json</code>, <code>mediainfo_info_json</code>, <code>encode_plan_json</code></li> <li>Kontrolle: <code>encode_review_confirmed</code>, <code>rip_successful</code>, <code>error_message</code></li> <li>Medientyp: <code>media_type</code> (<code>bluray|dvd|cd|audiobook|converter</code>)</li> <li>Job-Typ/Beziehung: <code>job_kind</code>, <code>parent_job_id</code></li> <li>Multipart-/Disc-Merkmale: <code>is_multipart_movie</code>, <code>disc_number</code>, <code>metadata_fingerprint</code></li> <li>Pr\u00fcfsumme: <code>aax_checksum</code> (f\u00fcr AAX-Dateien)</li> <li>Audit: <code>created_at</code>, <code>updated_at</code></li> </ul> <p>Typische <code>job_kind</code>-Werte:</p> <ul> <li>Standard: <code>bluray</code>, <code>dvd</code>, <code>cd</code>, <code>audiobook</code>, <code>converter_audio</code>, <code>converter_video</code>, <code>converter_iso</code></li> <li>Serien: <code>dvd_series_container</code>, <code>dvd_series_child</code></li> <li>Multipart Film: <code>multipart_movie_container</code>, <code>multipart_movie_child</code></li> </ul> <p>Wichtige Indizes:</p> <ul> <li><code>idx_jobs_status</code></li> <li><code>idx_jobs_parent_job_id</code></li> <li><code>idx_jobs_job_kind</code></li> <li><code>idx_jobs_disc_number</code></li> <li><code>idx_jobs_metadata_fingerprint</code></li> </ul>"},{"location":"architecture/database/#job_lineage_artifacts","title":"<code>job_lineage_artifacts</code>","text":"<p>Verkn\u00fcpft Jobs mit ihren Quell-Jobs (z. B. Re-Encode aus einem vorherigen Rip).</p> <p>Felder:</p> <ul> <li><code>job_id</code> \u2014 der abgeleitete Job</li> <li><code>source_job_id</code> \u2014 der Quell-Job</li> <li><code>artifact_type</code> \u2014 Art der Verkn\u00fcpfung (z. B. <code>reencode</code>, <code>restart_review</code>)</li> <li><code>created_at</code></li> </ul>"},{"location":"architecture/database/#job_output_folders","title":"<code>job_output_folders</code>","text":"<p>Verfolgt Ausgabepfade pro Job (mehrere Ausgaben m\u00f6glich, z. B. bei Kapitel-Splitting).</p> <p>Felder:</p> <ul> <li><code>job_id</code></li> <li><code>folder_path</code></li> <li><code>label</code> \u2014 optionaler Bezeichner</li> <li><code>created_at</code></li> </ul>"},{"location":"architecture/database/#pipeline_state","title":"<code>pipeline_state</code>","text":"<p>Singleton-Tabelle (<code>id = 1</code>) f\u00fcr aktiven Snapshot:</p> <ul> <li><code>state</code></li> <li><code>active_job_id</code></li> <li><code>progress</code></li> <li><code>eta</code></li> <li><code>status_text</code></li> <li><code>context_json</code></li> <li><code>updated_at</code></li> </ul>"},{"location":"architecture/database/#settings_schema-settings_values","title":"<code>settings_schema</code> + <code>settings_values</code>","text":"<ul> <li><code>settings_schema</code>: Definition (Typ, Default, Validation, Reihenfolge)</li> <li><code>settings_values</code>: aktueller Wert pro Key</li> </ul>"},{"location":"architecture/database/#scripts-script_chains-script_chain_steps","title":"<code>scripts</code>, <code>script_chains</code>, <code>script_chain_steps</code>","text":"<ul> <li><code>scripts</code>: Shell-Skripte (<code>name</code>, <code>script_body</code>, <code>order_index</code>)</li> <li><code>script_chains</code>: Ketten (<code>name</code>, <code>order_index</code>)</li> <li><code>script_chain_steps</code>: Schritte je Kette</li> <li><code>step_type</code>: <code>script</code> oder <code>wait</code></li> <li><code>script_id</code> oder <code>wait_seconds</code></li> </ul>"},{"location":"architecture/database/#user_presets","title":"<code>user_presets</code>","text":"<p>Benannte HandBrake-Preset-Sets:</p> <ul> <li><code>name</code></li> <li><code>media_type</code> (<code>bluray|dvd|other|all</code>)</li> <li><code>handbrake_preset</code></li> <li><code>extra_args</code></li> <li><code>description</code></li> </ul>"},{"location":"architecture/database/#cron_jobs-cron_run_logs","title":"<code>cron_jobs</code> + <code>cron_run_logs</code>","text":"<ul> <li><code>cron_jobs</code>: Zeitplan + Status</li> <li><code>cron_run_logs</code>: einzelne L\u00e4ufe</li> <li><code>status</code>: <code>running|success|error</code></li> <li><code>output</code></li> <li><code>error_message</code></li> </ul>"},{"location":"architecture/database/#aax_activation_bytes","title":"<code>aax_activation_bytes</code>","text":"<p>Cache f\u00fcr Audible-Activation-Bytes (AAX-DRM).</p> <p>Felder:</p> <ul> <li><code>checksum</code> \u2014 SHA-1-Pr\u00fcfsumme der AAX-Datei (Prim\u00e4rschl\u00fcssel)</li> <li><code>activation_bytes</code> \u2014 Hex-String der Activation Bytes</li> <li><code>created_at</code></li> </ul>"},{"location":"architecture/database/#user_prefs","title":"<code>user_prefs</code>","text":"<p>Benutzer-spezifische Einstellungen (Key-Value).</p> <p>Felder:</p> <ul> <li><code>key</code></li> <li><code>value</code></li> <li><code>updated_at</code></li> </ul>"},{"location":"architecture/database/#converter_scan_entries","title":"<code>converter_scan_entries</code>","text":"<p>DB-seitige Erfassung gescannter Dateien im Converter-Eingangsordner.</p> <p>Felder:</p> <ul> <li><code>rel_path</code> \u2014 relativer Pfad zum <code>converter_raw_dir</code></li> <li><code>is_dir</code> \u2014 Verzeichnis-Flag</li> <li><code>size</code> \u2014 Dateigr\u00f6\u00dfe in Bytes</li> <li><code>modified_at</code> \u2014 Datei-\u00c4nderungsdatum</li> <li><code>job_id</code> \u2014 zugewiesener Converter-Job (falls vorhanden)</li> <li><code>scanned_at</code></li> </ul>"},{"location":"architecture/database/#migrationrecovery","title":"Migration/Recovery","text":"<p>Beim Start werden Schema und Settings-Metadaten automatisch abgeglichen.</p> <p>Bei korruptem SQLite-File:</p> <ol> <li>Datei wird nach <code>backend/data/corrupt-backups/</code> verschoben</li> <li>neue DB wird initialisiert</li> <li>Schema wird neu aufgebaut</li> </ol>"},{"location":"architecture/database/#direkte-inspektion","title":"Direkte Inspektion","text":"<pre><code>sqlite3 backend/data/ripster.db\n\n.mode table\nSELECT id, status, title, media_type, created_at FROM jobs ORDER BY created_at DESC;\nSELECT id, parent_job_id, job_kind, is_multipart_movie, disc_number FROM jobs ORDER BY created_at DESC;\nSELECT key, value FROM settings_values ORDER BY key;\nSELECT checksum, activation_bytes FROM aax_activation_bytes;\nSELECT rel_path, job_id FROM converter_scan_entries;\n</code></pre>"},{"location":"architecture/frontend/","title":"Frontend-Komponenten","text":"<p>Frontend: React + PrimeReact + Vite.</p>"},{"location":"architecture/frontend/#hauptseiten","title":"Hauptseiten","text":""},{"location":"architecture/frontend/#jobsinboxpagejsx","title":"<code>JobsInboxPage.jsx</code>","text":"<p>Zentrale Job-Inbox:</p> <ul> <li>einheitliche Liste \u00fcber Ripper-, Converter- und Audiobook-Jobs</li> <li>View-/Status-Filter, Suche, Paginierung</li> <li>Schnellsprung in passende Detailseite (<code>Ripper</code>/<code>Converter</code>)</li> </ul>"},{"location":"architecture/frontend/#ripperpagejsx","title":"<code>RipperPage.jsx</code>","text":"<p>Pipeline-Steuerung:</p> <ul> <li>Status/Progress/ETA</li> <li>Metadaten-Dialog</li> <li>Playlist-Entscheidung</li> <li>Review-Panel (Track-Auswahl)</li> <li>Queue-Interaktion (reorder/add/remove)</li> <li>Job-Aktionen (Start/Cancel/Retry/Re-Encode)</li> <li>Hardware-Monitoring-Anzeige</li> <li>Aktivit\u00e4ts-Tracking (Skripte, Ketten, Cron)</li> </ul>"},{"location":"architecture/frontend/#settingspagejsx","title":"<code>SettingsPage.jsx</code>","text":"<p>Konfiguration:</p> <ul> <li>dynamisches Settings-Formular (<code>DynamicSettingsForm</code>)</li> <li>Skripte/Ketten inkl. Reorder/Test</li> <li>User-Presets</li> <li>Cron-Jobs (<code>CronJobsTab</code>)</li> </ul>"},{"location":"architecture/frontend/#historypagejsx","title":"<code>HistoryPage.jsx</code>","text":"<p>Historie:</p> <ul> <li>Job-Liste/Filter</li> <li>Job-Details + Logs</li> <li>OMDb-Nachzuweisung</li> <li>Re-Encode/Restart-Workflows</li> </ul>"},{"location":"architecture/frontend/#converterpagejsx","title":"<code>ConverterPage.jsx</code>","text":"<p>Datei-Converter:</p> <ul> <li>Datei-Explorer des Converter-Eingangsordners (Baum-Ansicht)</li> <li>Upload von Audio/Video-Dateien (bis zu 50 Dateien gleichzeitig)</li> <li>Dateiverwaltung: Umbenennen, Verschieben, L\u00f6schen, Ordner erstellen</li> <li>Jobs aus Dateiauswahl erstellen</li> <li>Converter-Job-Konfiguration (Ausgabeformat, Preset, Tracks)</li> <li>Job-Start, Abbruch, L\u00f6schung</li> <li>Automatischer Scan-Status</li> </ul>"},{"location":"architecture/frontend/#audiobookspagejsx","title":"<code>AudiobooksPage.jsx</code>","text":"<p>Dedizierter AAX-/Audiobook-Flow:</p> <ul> <li>Upload-Panel f\u00fcr AAX-Dateien</li> <li>aktive Audiobook-Jobkarten mit Start/Cancel/Retry/Delete</li> <li>Audiobook-Konfigurationspanel und Output-Explorer</li> </ul>"},{"location":"architecture/frontend/#downloadspagejsx","title":"<code>DownloadsPage.jsx</code>","text":"<p>Download-Queue:</p> <ul> <li>Ausgabedateien aus der Job-Historie in die Queue einreihen</li> <li>Download-Status verfolgen (ausstehend, wird verarbeitet, bereit, fehlgeschlagen)</li> <li>Dateien als ZIP herunterladen</li> <li>Download-Eintr\u00e4ge l\u00f6schen</li> </ul>"},{"location":"architecture/frontend/#databasepagejsx","title":"<code>DatabasePage.jsx</code>","text":"<p>Expert-Modus:</p> <ul> <li>Tabellarische Rohsicht der Job-Datenbank</li> <li>Orphan-RAW-Import</li> </ul>"},{"location":"architecture/frontend/#wichtige-komponenten","title":"Wichtige Komponenten","text":"<ul> <li><code>PipelineStatusCard.jsx</code> \u2014 Pipeline-Status-Anzeige mit Progress</li> <li><code>MetadataSelectionDialog.jsx</code> \u2014 OMDb-Metadaten-Auswahl</li> <li><code>MediaInfoReviewPanel.jsx</code> \u2014 Track-Auswahl-Interface (Video/Audio/Untertitel)</li> <li><code>JobDetailDialog.jsx</code> \u2014 Job-Detailansicht mit Logs</li> <li><code>CronJobsTab.jsx</code> \u2014 Cron-Job-Verwaltung</li> <li><code>DynamicSettingsForm.jsx</code> \u2014 Schema-gesteuertes Einstellungsformular</li> <li><code>ConverterJobCard.jsx</code> \u2014 Converter-Job-Darstellung</li> <li><code>CdRipConfigPanel.jsx</code> \u2014 Konfiguration f\u00fcr Audio-CD-Ripping</li> </ul>"},{"location":"architecture/frontend/#api-client-apiclientjs","title":"API-Client (<code>api/client.js</code>)","text":"<ul> <li>zentraler <code>request()</code> mit JSON-Handling</li> <li>Fehlerobjekt aus API wird auf <code>Error(message)</code> gemappt</li> <li><code>VITE_API_BASE</code> default <code>/api</code></li> </ul>"},{"location":"architecture/frontend/#websocket-hooksusewebsocketjs","title":"WebSocket (<code>hooks/useWebSocket.js</code>)","text":"<ul> <li>URL: <code>VITE_WS_URL</code> oder automatisch <code>ws(s)://<host>/ws</code></li> <li>Auto-Reconnect mit 1500ms Intervall</li> </ul> <p>In <code>App.jsx</code> werden u. a. verarbeitet:</p> <ul> <li><code>PIPELINE_STATE_CHANGED</code></li> <li><code>PIPELINE_PROGRESS</code></li> <li><code>PIPELINE_QUEUE_CHANGED</code></li> <li>Disk erkannt / Disk entfernt</li> <li><code>HARDWARE_MONITOR_UPDATE</code></li> <li><code>DOWNLOADS_UPDATED</code></li> <li><code>CONVERTER_SCAN_UPDATE</code></li> <li><code>RUNTIME_ACTIVITY_CHANGED</code></li> </ul>"},{"location":"architecture/frontend/#buildrun","title":"Build/Run","text":"<pre><code># dev\nnpm run dev --prefix frontend\n\n# prod build\nnpm run build --prefix frontend\n</code></pre>"},{"location":"architecture/overview/","title":"Architektur-\u00dcbersicht","text":""},{"location":"architecture/overview/#kernprinzipien","title":"Kernprinzipien","text":""},{"location":"architecture/overview/#event-getriebene-pipeline","title":"Event-getriebene Pipeline","text":"<p><code>pipelineService</code> h\u00e4lt einen Snapshot der State-Machine und broadcastet \u00c4nderungen sofort via WebSocket.</p> <pre><code>State-\u00c4nderung -> PIPELINE_STATE_CHANGED/PIPELINE_PROGRESS -> Frontend-Update\n</code></pre>"},{"location":"architecture/overview/#service-layer","title":"Service-Layer","text":"<pre><code>Route -> Service -> DB/Tool-Execution\n</code></pre> <p>Routes enthalten kaum Business-Logik.</p>"},{"location":"architecture/overview/#schema-getriebene-settings","title":"Schema-getriebene Settings","text":"<p>Settings sind DB-schema-getrieben (<code>settings_schema</code> + <code>settings_values</code>), UI rendert dynamisch aus diesen Daten.</p>"},{"location":"architecture/overview/#echtzeit-kommunikation","title":"Echtzeit-Kommunikation","text":"<p>WebSocket l\u00e4uft auf <code>/ws</code>.</p> <p>Wichtige Events:</p> <ul> <li><code>PIPELINE_STATE_CHANGED</code>, <code>PIPELINE_PROGRESS</code>, <code>PIPELINE_QUEUE_CHANGED</code></li> <li>Disk erkannt, Disk entfernt</li> <li><code>HARDWARE_MONITOR_UPDATE</code></li> <li><code>SETTINGS_UPDATED</code>, <code>SETTINGS_BULK_UPDATED</code></li> <li><code>SETTINGS_SCRIPTS_UPDATED</code>, <code>SETTINGS_SCRIPT_CHAINS_UPDATED</code>, <code>USER_PRESETS_UPDATED</code></li> <li><code>CRON_JOBS_UPDATED</code>, <code>CRON_JOB_UPDATED</code></li> <li><code>PIPELINE_ERROR</code>, <code>DISK_DETECTION_ERROR</code></li> </ul>"},{"location":"architecture/overview/#prozessausfuhrung","title":"Prozessausf\u00fchrung","text":"<p>Externe Tools werden als Child-Processes gestartet (<code>processRunner</code>):</p> <ul> <li>Streaming von stdout/stderr</li> <li>Progress-Parsing (<code>progressParsers.js</code>)</li> <li>kontrollierter Abbruch (SIGINT/SIGKILL-Fallback)</li> </ul>"},{"location":"architecture/overview/#persistenz","title":"Persistenz","text":"<p>SQLite-Datei: <code>backend/data/ripster.db</code></p> <p>Kern-Tabellen:</p> <ul> <li><code>jobs</code>, <code>pipeline_state</code></li> <li><code>settings_schema</code>, <code>settings_values</code></li> <li><code>scripts</code>, <code>script_chains</code>, <code>script_chain_steps</code></li> <li><code>user_presets</code></li> <li><code>cron_jobs</code>, <code>cron_run_logs</code></li> </ul> <p>Beim Start werden Schema und Settings-Migrationen automatisch ausgef\u00fchrt.</p>"},{"location":"architecture/overview/#fehlerbehandlung","title":"Fehlerbehandlung","text":"<p>Zentrales Error-Handling liefert:</p> <pre><code>{\n \"error\": {\n \"message\": \"...\",\n \"statusCode\": 400,\n \"reqId\": \"...\",\n \"details\": []\n }\n}\n</code></pre> <p>Fehlgeschlagene Jobs bleiben in der Historie (<code>Fehler</code> oder <code>Abgebrochen</code>) und k\u00f6nnen erneut gestartet werden.</p>"},{"location":"architecture/overview/#cors-runtime-konfig","title":"CORS & Runtime-Konfig","text":"<ul> <li><code>CORS_ORIGIN</code> default: <code>*</code></li> <li><code>LOG_LEVEL</code> default: <code>info</code></li> <li>DB-/Log-Pfade \u00fcber <code>DB_PATH</code>/<code>LOG_DIR</code> konfigurierbar</li> </ul>"},{"location":"configuration/","title":"Anhang: Konfiguration","text":"<p>Dieser Abschnitt ist die technische Referenz zu allen Konfigurationsarten in Ripster.</p>"},{"location":"configuration/#inhalte","title":"Inhalte","text":"<ul> <li> <p> Einstellungsreferenz</p> <p>Vollst\u00e4ndige Liste aller UI-Settings (Typ, Default, Hinweise).</p> <p> Einstellungsreferenz</p> </li> <li> <p> Umgebungsvariablen</p> <p><code>backend/.env</code> und <code>frontend/.env</code> inkl. Priorit\u00e4ten.</p> <p> Umgebungsvariablen</p> </li> </ul>"},{"location":"configuration/#zuruck-zum-handbuch","title":"Zur\u00fcck zum Handbuch","text":"<ul> <li>Benutzerhandbuch \u00dcberblick</li> </ul>"},{"location":"configuration/environment/","title":"Umgebungsvariablen","text":"<p>Umgebungsvariablen steuern Backend/Vite au\u00dferhalb der DB-basierten UI-Settings.</p>"},{"location":"configuration/environment/#backend-backendenv","title":"Backend (<code>backend/.env</code>)","text":"Variable Default (Code) Beschreibung <code>PORT</code> <code>3001</code> Express-Port <code>DB_PATH</code> <code>backend/data/ripster.db</code> SQLite-Datei (relativ zu <code>backend/</code>) <code>LOG_DIR</code> <code>backend/logs</code> Fallback-Logverzeichnis (wenn <code>log_dir</code>-Setting nicht gesetzt/lesbar) <code>CORS_ORIGIN</code> <code>*</code> CORS-Origin f\u00fcr API <code>LOG_LEVEL</code> <code>info</code> <code>debug</code>, <code>info</code>, <code>warn</code>, <code>error</code> <p>Beispiel:</p> <pre><code>PORT=3001\nDB_PATH=/var/lib/ripster/ripster.db\nLOG_DIR=/var/log/ripster\nCORS_ORIGIN=http://192.168.1.50:5173\nLOG_LEVEL=info\n</code></pre> <p>Hinweis: <code>backend/.env.example</code> enth\u00e4lt bewusst dev-freundliche Werte (z. B. lokaler <code>CORS_ORIGIN</code>).</p>"},{"location":"configuration/environment/#frontend-frontendenv","title":"Frontend (<code>frontend/.env</code>)","text":"Variable Default Beschreibung <code>VITE_API_BASE</code> <code>/api</code> API-Basis f\u00fcr Fetch-Client <code>VITE_WS_URL</code> automatisch aus <code>window.location</code> + <code>/ws</code> Optional explizite WebSocket-URL <code>VITE_PUBLIC_ORIGIN</code> leer \u00d6ffentliche Vite-Origin (Remote-Dev) <code>VITE_ALLOWED_HOSTS</code> <code>true</code> Komma-separierte Hostliste f\u00fcr Vite <code>allowedHosts</code> <code>VITE_HMR_PROTOCOL</code> abgeleitet aus <code>VITE_PUBLIC_ORIGIN</code> HMR-Protokoll (<code>ws</code>/<code>wss</code>) <code>VITE_HMR_HOST</code> abgeleitet aus <code>VITE_PUBLIC_ORIGIN</code> HMR-Host <code>VITE_HMR_CLIENT_PORT</code> abgeleitet aus <code>VITE_PUBLIC_ORIGIN</code> HMR-Client-Port <p>Beispiele:</p> <pre><code># lokal (mit Vite-Proxy)\nVITE_API_BASE=/api\n</code></pre> <pre><code># remote dev\nVITE_API_BASE=http://192.168.1.50:3001/api\nVITE_WS_URL=ws://192.168.1.50:3001/ws\nVITE_PUBLIC_ORIGIN=http://192.168.1.50:5173\nVITE_ALLOWED_HOSTS=192.168.1.50,ripster.local\nVITE_HMR_PROTOCOL=ws\nVITE_HMR_HOST=192.168.1.50\nVITE_HMR_CLIENT_PORT=5173\n</code></pre>"},{"location":"configuration/environment/#prioritat","title":"Priorit\u00e4t","text":"<ol> <li>Prozess-Umgebungsvariablen</li> <li><code>.env</code></li> <li>Code-Defaults</li> </ol>"},{"location":"configuration/settings-reference/","title":"Einstellungsreferenz","text":"<p>Diese Seite listet die Felder so, wie sie in der GUI unter <code>Settings</code> angezeigt werden.</p> <p>Hinweis: Interne Schl\u00fcsselnamen werden hier bewusst nicht verwendet. Falls du sie f\u00fcr Integrationen brauchst, nutze die API-Dokumentation.</p>"},{"location":"configuration/settings-reference/#profil-system","title":"Profil-System","text":"<p>Viele Felder sind pro Medientyp getrennt vorhanden:</p> <ul> <li>Blu-ray</li> <li>DVD</li> <li>Sonstiges</li> <li>Audiobook (neu ab v0.12.0)</li> </ul>"},{"location":"configuration/settings-reference/#template-platzhalter","title":"Template-Platzhalter","text":""},{"location":"configuration/settings-reference/#video-templates","title":"Video-Templates","text":"<ul> <li><code>${title}</code> \u2014 Titel</li> <li><code>${year}</code> \u2014 Erscheinungsjahr</li> <li><code>${imdbId}</code> \u2014 IMDb-ID</li> </ul>"},{"location":"configuration/settings-reference/#audiobook-templates","title":"Audiobook-Templates","text":"<ul> <li><code>{author}</code> \u2014 Autor</li> <li><code>{title}</code> \u2014 Titel</li> <li><code>{year}</code> \u2014 Erscheinungsjahr</li> <li><code>{narrator}</code> \u2014 Sprecher</li> <li><code>{series}</code> \u2014 Buchreihe</li> <li><code>{part}</code> \u2014 Teil-Nummer</li> <li><code>{format}</code> \u2014 Ausgabeformat</li> </ul>"},{"location":"configuration/settings-reference/#converter-templates-video","title":"Converter-Templates (Video)","text":"<ul> <li><code>{title}</code>, <code>{year}</code></li> </ul>"},{"location":"configuration/settings-reference/#converter-templates-audio","title":"Converter-Templates (Audio)","text":"<ul> <li><code>{artist}</code>, <code>{title}</code>, <code>{album}</code>, <code>{year}</code>, <code>{trackNr}</code></li> </ul> <p>Nicht gesetzte Werte werden zu <code>unknown</code>.</p>"},{"location":"configuration/settings-reference/#kategorie-pfade","title":"Kategorie: Pfade","text":"Feldname in der GUI Typ Default <code>Raw Ausgabeordner</code> path <code>data/output/raw</code> <code>Raw Ausgabeordner (Blu-ray)</code> path <code>null</code> <code>Raw Ausgabeordner (DVD)</code> path <code>null</code> <code>Raw Ausgabeordner (Sonstiges)</code> path <code>null</code> <code>Eigent\u00fcmer Raw-Ordner (Blu-ray)</code> string <code>null</code> <code>Eigent\u00fcmer Raw-Ordner (DVD)</code> string <code>null</code> <code>Eigent\u00fcmer Raw-Ordner (Sonstiges)</code> string <code>null</code> <code>Film Ausgabeordner</code> path <code>data/output/movies</code> <code>Film Ausgabeordner (Blu-ray)</code> path <code>null</code> <code>Film Ausgabeordner (DVD)</code> path <code>null</code> <code>Film Ausgabeordner (Sonstiges)</code> path <code>null</code> <code>Eigent\u00fcmer Film-Ordner (Blu-ray)</code> string <code>null</code> <code>Eigent\u00fcmer Film-Ordner (DVD)</code> string <code>null</code> <code>Eigent\u00fcmer Film-Ordner (Sonstiges)</code> string <code>null</code> <code>Log Ordner</code> path <code>data/logs</code> <code>Audiobook RAW-Ordner</code> path <code>null</code> <code>Eigent\u00fcmer Audiobook RAW-Ordner</code> string <code>null</code> <code>Audiobook Output-Ordner</code> path <code>null</code> <code>Eigent\u00fcmer Audiobook Output-Ordner</code> string <code>null</code> <code>Output Template (Audiobook)</code> string <code>{author}/{author} - {title} ({year})</code> <code>Audiobook RAW Template</code> string <code>{author} - {title} ({year})</code> <code>Converter Raw-Ordner</code> path <code>null</code> <code>Converter Ausgabe (Video)</code> path <code>null</code> <code>Converter Ausgabe (Audio)</code> path <code>null</code> <code>Output-Template (Video)</code> string <code>{title}</code> <code>Output-Template (Audio)</code> string <code>{artist} - {title}</code>"},{"location":"configuration/settings-reference/#kategorie-laufwerk","title":"Kategorie: Laufwerk","text":"Feldname in der GUI Typ Default Hinweis <code>Laufwerksmodus</code> select <code>auto</code> <code>Auto Discovery</code> oder <code>Explizites Device</code> <code>Device Pfad</code> path <code>/dev/sr0</code> relevant bei <code>Explizites Device</code> <code>MakeMKV Source Index</code> number <code>0</code> Disc-Index im Auto-Modus <code>Disc Auto-Erkennung aktiviert</code> boolean <code>true</code> Automatische Disc-Erkennung per Polling <code>Polling Intervall (ms)</code> number <code>4000</code> 1000..60000"},{"location":"configuration/settings-reference/#kategorie-monitoring","title":"Kategorie: Monitoring","text":"Feldname in der GUI Typ Default <code>Hardware Monitoring aktiviert</code> boolean <code>true</code> <code>Hardware Monitoring Intervall (ms)</code> number <code>5000</code>"},{"location":"configuration/settings-reference/#kategorie-tools-global","title":"Kategorie: Tools (global)","text":"Feldname in der GUI Typ Default <code>MakeMKV Kommando</code> string <code>makemkvcon</code> <code>MakeMKV Key</code> string <code>null</code> <code>Mediainfo Kommando</code> string <code>mediainfo</code> <code>Minimale Titellaenge (Minuten)</code> number <code>60</code> <code>HandBrake Kommando</code> string <code>HandBrakeCLI</code> <code>FFmpeg Kommando</code> string <code>ffmpeg</code> <code>FFprobe Kommando</code> string <code>ffprobe</code> <code>Encode-Neustart: unvollst\u00e4ndige Ausgabe l\u00f6schen</code> boolean <code>true</code> <code>Parallele Jobs</code> number <code>1</code>"},{"location":"configuration/settings-reference/#blu-ray-spezifisch","title":"Blu-ray-spezifisch","text":"Feldname in der GUI Typ Default <code>Mediainfo Extra Args</code> (Blu-ray) string <code>null</code> <code>MakeMKV Rip Modus</code> (Blu-ray) select <code>backup</code> <code>MakeMKV Analyze Extra Args</code> (Blu-ray) string <code>null</code> <code>MakeMKV Rip Extra Args</code> (Blu-ray) string <code>null</code> <code>HandBrake Preset</code> (Blu-ray) string <code>H.264 MKV 1080p30</code> <code>HandBrake Extra Args</code> (Blu-ray) string <code>null</code> <code>Ausgabeformat</code> (Blu-ray) select <code>mkv</code> <code>Dateiname Template</code> (Blu-ray) string <code>${title} (${year})</code> <code>Ordnername Template</code> (Blu-ray) string <code>null</code>"},{"location":"configuration/settings-reference/#dvd-spezifisch","title":"DVD-spezifisch","text":"Feldname in der GUI Typ Default <code>Mediainfo Extra Args</code> (DVD) string <code>null</code> <code>MakeMKV Rip Modus</code> (DVD) select <code>mkv</code> <code>MakeMKV Analyze Extra Args</code> (DVD) string <code>null</code> <code>MakeMKV Rip Extra Args</code> (DVD) string <code>null</code> <code>HandBrake Preset</code> (DVD) string <code>H.264 MKV 480p30</code> <code>HandBrake Extra Args</code> (DVD) string <code>null</code> <code>Ausgabeformat</code> (DVD) select <code>mkv</code> <code>Dateiname Template</code> (DVD) string <code>${title} (${year})</code> <code>Ordnername Template</code> (DVD) string <code>null</code>"},{"location":"configuration/settings-reference/#kategorie-converter","title":"Kategorie: Converter","text":"Feldname in der GUI Typ Default Hinweis <code>Erlaubte Datei-Endungen*</code> string <code>mkv,mp4,m2ts,iso,avi,mov,flac,mp3,wav,m4a,ogg,opus</code> In der UI als Checkbox-Liste, ohne Punkt <code>Auto-Scan (Polling)</code> boolean <code>false</code> Converter-Ordner automatisch scannen <code>Polling-Intervall (Sekunden)</code> number <code>300</code> 30..86400"},{"location":"configuration/settings-reference/#kategorie-metadaten","title":"Kategorie: Metadaten","text":"Feldname in der GUI Typ Default <code>OMDb API Key</code> string <code>null</code> <code>OMDb Typ</code> select <code>movie</code>"},{"location":"configuration/settings-reference/#kategorie-benachrichtigungen-pushover","title":"Kategorie: Benachrichtigungen (PushOver)","text":"Feldname in der GUI Typ Default <code>PushOver aktiviert</code> boolean <code>false</code> <code>PushOver Token</code> string <code>null</code> <code>PushOver User</code> string <code>null</code> <code>PushOver Device (optional)</code> string <code>null</code> <code>PushOver Titel-Pr\u00e4fix</code> string <code>Ripster</code> <code>PushOver Priority</code> number <code>0</code> <code>PushOver Timeout (ms)</code> number <code>7000</code> <code>Bei Metadaten-Auswahl senden</code> boolean <code>true</code> <code>Bei Rip-Start senden</code> boolean <code>true</code> <code>Bei Encode-Start senden</code> boolean <code>true</code> <code>Bei Erfolg senden</code> boolean <code>true</code> <code>Bei Fehler senden</code> boolean <code>true</code> <code>Bei Abbruch senden</code> boolean <code>true</code> <code>Bei Re-Encode Start senden</code> boolean <code>true</code> <code>Bei Re-Encode Erfolg senden</code> boolean <code>true</code>"},{"location":"deployment/","title":"Anhang: Deployment","text":"<p>Technische Betriebsdokumentation f\u00fcr Entwicklung und Produktion.</p> <ul> <li> <p> Entwicklungsumgebung</p> <p>Lokale Entwicklung mit Hot-Reload.</p> <p> Entwicklung</p> </li> <li> <p> Produktion</p> <p>Installation und Betrieb auf Servern.</p> <p> Produktion</p> </li> </ul>"},{"location":"deployment/development/","title":"Entwicklungsumgebung","text":""},{"location":"deployment/development/#voraussetzungen","title":"Voraussetzungen","text":"<ul> <li>Node.js >= 20.19.0</li> <li>externe Tools installiert (<code>makemkvcon</code>, <code>HandBrakeCLI</code>, <code>mediainfo</code>)</li> </ul>"},{"location":"deployment/development/#schnellstart","title":"Schnellstart","text":"<pre><code>./start.sh\n</code></pre> <p>Startet:</p> <ul> <li>Backend (<code>http://localhost:3001</code>, mit nodemon)</li> <li>Frontend (<code>http://localhost:5173</code>, mit Vite HMR)</li> </ul> <p>Stoppen: <code>Ctrl+C</code>.</p>"},{"location":"deployment/development/#manuell","title":"Manuell","text":""},{"location":"deployment/development/#backend","title":"Backend","text":"<pre><code>cd backend\nnpm install\nnpm run dev\n</code></pre>"},{"location":"deployment/development/#frontend","title":"Frontend","text":"<pre><code>cd frontend\nnpm install\nnpm run dev\n</code></pre>"},{"location":"deployment/development/#vite-proxy-dev","title":"Vite-Proxy (Dev)","text":"<p><code>frontend/vite.config.js</code> proxied standardm\u00e4\u00dfig:</p> <ul> <li><code>/api</code> -> <code>http://127.0.0.1:3001</code></li> <li><code>/ws</code> -> <code>ws://127.0.0.1:3001</code></li> </ul>"},{"location":"deployment/development/#remote-dev-optional","title":"Remote-Dev (optional)","text":"<p>Beispiel <code>frontend/.env.local</code>:</p> <pre><code>VITE_API_BASE=http://192.168.1.100:3001/api\nVITE_WS_URL=ws://192.168.1.100:3001/ws\nVITE_PUBLIC_ORIGIN=http://192.168.1.100:5173\nVITE_ALLOWED_HOSTS=192.168.1.100,ripster.local\nVITE_HMR_PROTOCOL=ws\nVITE_HMR_HOST=192.168.1.100\nVITE_HMR_CLIENT_PORT=5173\n</code></pre>"},{"location":"deployment/development/#nutzliche-kommandos","title":"N\u00fctzliche Kommandos","text":"<pre><code># Root dev (backend + frontend)\nnpm run dev\n\n# einzeln\nnpm run dev:backend\nnpm run dev:frontend\n\n# Frontend Build\nnpm run build:frontend\n</code></pre>"},{"location":"deployment/production/","title":"Produktions-Deployment","text":""},{"location":"deployment/production/#automatische-installation-empfohlen","title":"Automatische Installation (empfohlen)","text":"<p>Das mitgelieferte <code>install.sh</code> richtet Ripster vollautomatisch ein \u2013 inklusive Node.js, MakeMKV, HandBrake, nginx und systemd-Dienst.</p> <p>Unterst\u00fctzte Systeme laut Script: Debian, Ubuntu, Linux Mint, Pop!_OS Voraussetzung: root-Rechte, Internetzugang</p>"},{"location":"deployment/production/#schnellstart-via-curl","title":"Schnellstart via curl","text":"<pre><code>curl -fsSL https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh | sudo bash\n</code></pre> <p>Oder mit wget:</p> <pre><code>wget -qO- https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh | sudo bash\n</code></pre> <p>Optionen nur via Datei</p> <p>Beim Pipen von curl/wget k\u00f6nnen keine Argumente \u00fcbergeben werden. F\u00fcr benutzerdefinierte Optionen zuerst herunterladen und dann mit <code>sudo bash install.sh [Optionen]</code> ausf\u00fchren.</p>"},{"location":"deployment/production/#optionen","title":"Optionen","text":"Option Standard Beschreibung <code>--branch <branch></code> <code>dev</code> Git-Branch f\u00fcr die Installation <code>--dir <pfad></code> <code>/opt/ripster</code> Installationsverzeichnis <code>--user <benutzer></code> <code>ripster</code> Systembenutzer f\u00fcr den Dienst <code>--port <port></code> <code>3001</code> Backend-Port <code>--host <hostname></code> Auto (Maschinen-IP) Hostname/IP f\u00fcr die Weboberfl\u00e4che <code>--no-makemkv</code> \u2013 MakeMKV-Installation \u00fcberspringen <code>--no-handbrake</code> \u2013 HandBrake-Installation \u00fcberspringen <code>--no-nginx</code> \u2013 nginx-Einrichtung \u00fcberspringen <code>--reinstall</code> \u2013 Bestehende Installation aktualisieren (Daten bleiben erhalten) <code>-h</code>, <code>--help</code> \u2013 Hilfe anzeigen"},{"location":"deployment/production/#beispiele","title":"Beispiele","text":"<pre><code># Standard-Installation\nsudo bash install.sh\n\n# Anderen Branch und Port verwenden\nsudo bash install.sh --branch dev --port 8080\n\n# Ohne MakeMKV (bereits installiert)\nsudo bash install.sh --no-makemkv\n\n# Bestehende Installation aktualisieren\nsudo bash install.sh --reinstall\n\n# Ohne nginx (eigener Reverse-Proxy)\nsudo bash install.sh --no-nginx --host mein-server.local\n</code></pre>"},{"location":"deployment/production/#was-das-skript-erledigt","title":"Was das Skript erledigt","text":"<ol> <li>Systempr\u00fcfung \u2013 OS-Erkennung und Root-Check</li> <li>Systempakete \u2013 <code>curl</code>, <code>wget</code>, <code>git</code>, <code>mediainfo</code>, <code>udev</code> u. a.</li> <li>Node.js 20 \u2013 via NodeSource, falls noch nicht installiert</li> <li>MakeMKV \u2013 aktuelle Version wird aus dem offiziellen Forum ermittelt und aus dem Quellcode kompiliert (kann mit <code>--no-makemkv</code> \u00fcbersprungen werden)</li> <li>HandBrake \u2013 interaktive Auswahl:<ul> <li>Option 1: Standard (<code>apt install handbrake-cli</code>)</li> <li>Option 2: Geb\u00fcndelte GPU-Version mit NVDEC aus <code>bin/HandBrakeCLI</code></li> </ul> </li> <li>Systembenutzer <code>ripster</code> \u2013 ohne Login-Shell und ohne Home-Verzeichnis; Gruppen werden (falls vorhanden) erg\u00e4nzt: <code>cdrom</code>, <code>optical</code>, <code>disk</code>, <code>video</code>, <code>render</code></li> <li>Repository \u2013 klont Branch nach <code>--dir</code> (bei <code>--reinstall</code>: sichert <code>backend/data</code>, aktualisiert Repo, stellt Daten wieder her)</li> <li>Verzeichnisse \u2013 stellt u. a. sicher: <code>backend/data</code>, <code>backend/logs</code>, <code>backend/data/output/raw</code>, <code>backend/data/output/movies</code>, <code>backend/data/logs</code></li> <li>npm-Abh\u00e4ngigkeiten \u2013 Root, Backend (nur production), Frontend</li> <li>Frontend-Build \u2013 <code>npm run build</code> mit relativen API-URLs (nginx-kompatibel)</li> <li>Backend <code>.env</code> \u2013 wird automatisch generiert (bei <code>--reinstall</code> bleibt bestehende <code>.env</code> erhalten)</li> <li>Berechtigungen \u2013 zun\u00e4chst <code>ripster:ripster</code> auf Installationsverzeichnis; bei sudo-Aufruf werden Output-/Log-Ordner auf <code><aufrufender user>:ripster</code> mit <code>775</code> gesetzt; <code>.env</code> wird auf <code>600</code> gesetzt</li> <li>MakeMKV User-Ordner \u2013 erstellt bei sudo-Aufruf <code>~/.MakeMKV</code> f\u00fcr den aufrufenden Benutzer</li> <li>systemd-Dienst \u2013 <code>ripster-backend.service</code> erstellt, aktiviert und gestartet</li> <li>nginx \u2013 konfiguriert als Reverse-Proxy f\u00fcr Frontend, <code>/api/</code> und <code>/ws</code> (kann mit <code>--no-nginx</code> \u00fcbersprungen werden)</li> </ol>"},{"location":"deployment/production/#nach-der-installation","title":"Nach der Installation","text":"<pre><code># Status pr\u00fcfen\nsudo systemctl status ripster-backend\n\n# Logs verfolgen\nsudo journalctl -u ripster-backend -f\n\n# Neustart\nsudo systemctl restart ripster-backend\n\n# Aktualisieren\nsudo bash /opt/ripster/install.sh --reinstall\n</code></pre> <p>Zugriff: <code>http://<Maschinen-IP></code> (oder der mit <code>--host</code> angegebene Hostname)</p>"},{"location":"deployment/production/#handbrake-modus-gpunvdec","title":"HandBrake-Modus (GPU/NVDEC)","text":"<p>Bei nicht-interaktiver Ausf\u00fchrung (Pipe von curl) wird automatisch die Standard-Version gew\u00e4hlt. F\u00fcr die GPU-Version zuerst herunterladen:</p> <pre><code>curl -fsSL https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh -o install.sh\nsudo bash install.sh\n# \u2192 Interaktive Auswahl: Option 2 f\u00fcr NVDEC\n</code></pre> <p>Das geb\u00fcndelte Binary liegt unter <code>bin/HandBrakeCLI</code> und wird nach <code>/usr/local/bin/HandBrakeCLI</code> kopiert.</p>"},{"location":"deployment/production/#manuelle-installation","title":"Manuelle Installation","text":"<p>Die folgenden Abschnitte beschreiben die einzelnen Schritte f\u00fcr manuelle oder angepasste Setups.</p>"},{"location":"deployment/production/#empfohlene-architektur","title":"Empfohlene Architektur","text":"<pre><code>Client\n -> nginx (Reverse Proxy + statisches Frontend)\n -> Backend API/WebSocket (Node.js, Port 3001)\n</code></pre> <p>Wichtig: Das Backend serviert im aktuellen Stand keine <code>frontend/dist</code>-Dateien automatisch.</p>"},{"location":"deployment/production/#1-frontend-builden","title":"1) Frontend builden","text":"<pre><code>cd frontend\nnpm install\nnpm run build\n</code></pre> <p>Artefakte liegen in <code>frontend/dist/</code>.</p>"},{"location":"deployment/production/#2-backend-als-systemd-service","title":"2) Backend als systemd-Service","text":"<p>Beispiel <code>/etc/systemd/system/ripster-backend.service</code>:</p> <pre><code>[Unit]\nDescription=Ripster Backend\nAfter=network.target\n\n[Service]\nType=simple\nUser=ripster\nWorkingDirectory=/opt/ripster/backend\nExecStart=/usr/bin/env node src/index.js\nRestart=on-failure\nRestartSec=5\nEnvironment=NODE_ENV=production\nEnvironment=PORT=3001\nEnvironment=LOG_LEVEL=info\n\n[Install]\nWantedBy=multi-user.target\n</code></pre> <p>Aktivieren:</p> <pre><code>sudo systemctl daemon-reload\nsudo systemctl enable --now ripster-backend\nsudo systemctl status ripster-backend\n</code></pre>"},{"location":"deployment/production/#3-nginx-konfigurieren","title":"3) nginx konfigurieren","text":"<p>Beispiel <code>/etc/nginx/sites-available/ripster</code>:</p> <pre><code>server {\n listen 80;\n server_name ripster.local;\n client_max_body_size 8G;\n\n root /opt/ripster/frontend/dist;\n index index.html;\n\n location / {\n try_files $uri $uri/ /index.html;\n }\n\n location /api/ {\n proxy_pass http://127.0.0.1:3001;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n }\n\n location /ws {\n proxy_pass http://127.0.0.1:3001;\n proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_set_header Host $host;\n }\n}\n</code></pre> <p>Aktivieren:</p> <pre><code>sudo ln -s /etc/nginx/sites-available/ripster /etc/nginx/sites-enabled/\nsudo nginx -t\nsudo systemctl reload nginx\n</code></pre> <p>Hinweis: Fuer groessere Uploads wie <code>.aax</code>-Audiobooks muss <code>client_max_body_size</code> ausreichend hoch gesetzt sein. Im mitgelieferten Beispiel sind <code>8G</code> hinterlegt.</p>"},{"location":"deployment/production/#datenbank-backup","title":"Datenbank-Backup","text":"<pre><code>sqlite3 /opt/ripster/backend/data/ripster.db \\\n \".backup '/var/backups/ripster-$(date +%Y%m%d).db'\"\n</code></pre>"},{"location":"deployment/production/#sicherheit","title":"Sicherheit","text":"<ul> <li>Ripster hat keine eingebaute Authentifizierung.</li> <li>F\u00fcr externen Zugriff mindestens Basic Auth + TLS + Netzwerksegmentierung/VPN einsetzen.</li> <li>Secrets nicht ins Repo committen (<code>.env</code>, Settings-Felder).</li> </ul>"},{"location":"getting-started/","title":"Benutzerhandbuch \u00dcberblick","text":"<p>Dieses Kapitel ist f\u00fcr den Betrieb von Ripster im Alltag geschrieben.</p>"},{"location":"getting-started/#zielgruppe","title":"Zielgruppe","text":"<ul> <li>Anwender, die Discs verarbeiten wollen</li> <li>Betreiber, die den t\u00e4glichen Ablauf stabil fahren m\u00f6chten</li> <li>Power-User, die Queue/Skripte/Cron im UI steuern m\u00f6chten</li> </ul>"},{"location":"getting-started/#kapitelstruktur","title":"Kapitelstruktur","text":"Kapitel Zweck Voraussetzungen Produktions- vs. Dev-Voraussetzungen klar trennen Installation Ripster aufsetzen und starten Ersteinrichtung Pfade, Tools und Metadaten korrekt setzen Erster Lauf Ein kompletter Job von Disc bis Datei GUI-Seiten Alle Ansichten und Aktionen im Detail Workflows Typische Abl\u00e4ufe und Entscheidungen aus User-Sicht"},{"location":"getting-started/#wenn-du-neu-startest","title":"Wenn du neu startest","text":"<ol> <li>Voraussetzungen</li> <li>Installation</li> <li>Ersteinrichtung</li> <li>Erster Lauf</li> </ol>"},{"location":"getting-started/#wenn-ripster-bereits-lauft","title":"Wenn Ripster bereits l\u00e4uft","text":"<ol> <li>GUI-Seiten</li> <li>Workflows</li> <li>Bei Detailfragen: Technischer Anhang</li> </ol>"},{"location":"getting-started/configuration/","title":"Ersteinrichtung","text":"<p>Nach der Installation erfolgt die t\u00e4gliche Konfiguration fast vollst\u00e4ndig in der GUI unter <code>Settings</code>.</p>"},{"location":"getting-started/configuration/#ziel","title":"Ziel","text":"<p>Vor dem ersten echten Job m\u00fcssen Pfade, Tools und Metadatenzugriff sauber gesetzt sein.</p>"},{"location":"getting-started/configuration/#reihenfolge-empfohlen","title":"Reihenfolge (empfohlen)","text":""},{"location":"getting-started/configuration/#1-settings-tab-konfiguration","title":"1. <code>Settings</code> -> Tab <code>Konfiguration</code>","text":"<p>Setze zuerst diese Pflichtwerte:</p> Bereich Wichtige Felder Pfade <code>Raw Ausgabeordner</code>, <code>Film Ausgabeordner</code>, <code>Log Ordner</code> Tools <code>MakeMKV Kommando</code>, <code>HandBrake Kommando</code>, <code>Mediainfo Kommando</code> Metadaten <code>OMDb API Key</code>, optional <code>OMDb Typ</code> <p>Danach <code>\u00c4nderungen speichern</code>.</p>"},{"location":"getting-started/configuration/#2-medienprofile-prufen","title":"2. Medienprofile pr\u00fcfen","text":"<p>Wenn du Blu-ray und DVD unterschiedlich behandeln willst, pflege die profilbezogenen Felder:</p> <ul> <li><code>*_bluray</code></li> <li><code>*_dvd</code></li> <li>optional <code>*_other</code></li> </ul> <p>Typische Beispiele:</p> <ul> <li><code>HandBrake Preset</code> (Blu-ray und DVD)</li> <li><code>Raw Ausgabeordner</code> (Blu-ray und DVD)</li> <li><code>Dateiname Template</code> (Blu-ray und DVD)</li> </ul>"},{"location":"getting-started/configuration/#3-queue-und-monitoring-festlegen","title":"3. Queue und Monitoring festlegen","text":"<ul> <li><code>Parallele Jobs</code> f\u00fcr den gleichzeitigen Durchsatz</li> <li><code>Hardware Monitoring aktiviert</code> + <code>Hardware Monitoring Intervall (ms)</code> f\u00fcr Live-Metriken im Ripper</li> </ul>"},{"location":"getting-started/configuration/#4-optional-push-benachrichtigungen","title":"4. Optional: Push-Benachrichtigungen","text":"<p>In den Benachrichtigungsfeldern setzen:</p> <ul> <li><code>PushOver aktiviert</code></li> <li><code>PushOver Token</code></li> <li><code>PushOver User</code></li> </ul> <p>Dann \u00fcber <code>PushOver Test</code> direkt pr\u00fcfen.</p>"},{"location":"getting-started/configuration/#2-minuten-funktionstest","title":"2-Minuten-Funktionstest","text":"<ol> <li><code>Ripper</code> \u00f6ffnen</li> <li>Disc einlegen</li> <li><code>Analyse starten</code></li> <li>Metadaten \u00fcbernehmen</li> <li>Bis <code>Bereit zum Encodieren</code> laufen lassen</li> </ol> <p>Wenn diese Schritte funktionieren, ist die Grundkonfiguration korrekt.</p>"},{"location":"getting-started/configuration/#wenn-werte-nicht-gespeichert-werden","title":"Wenn Werte nicht gespeichert werden","text":"<ul> <li>Feld mit Fehler markieren lassen (rote Validierung im Formular)</li> <li>Pfadangaben und numerische Werte pr\u00fcfen</li> <li>bei Tool-Pfaden direkt CLI-Aufruf im Terminal testen</li> </ul>"},{"location":"getting-started/configuration/#weiter","title":"Weiter","text":"<ul> <li>Erster Lauf</li> <li>GUI-Seiten im Detail</li> </ul>"},{"location":"getting-started/installation/","title":"Installation","text":"<p>Die empfohlene Installation l\u00e4uft \u00fcber <code>install.sh</code> und richtet Ripster vollst\u00e4ndig ein.</p> <p>Du musst daf\u00fcr keine Tools manuell vorinstallieren. Das Skript installiert die ben\u00f6tigten Abh\u00e4ngigkeiten automatisch, sofern sie nicht explizit mit <code>--no-*</code> \u00fcbersprungen werden.</p>"},{"location":"getting-started/installation/#unterstutzte-systeme-und-anforderungen","title":"Unterst\u00fctzte Systeme und Anforderungen","text":"<ul> <li>unterst\u00fctzt laut Script: <code>debian</code>, <code>ubuntu</code>, <code>linuxmint</code>, <code>pop</code></li> <li>Ausf\u00fchrung als <code>root</code> (oder via <code>sudo</code>)</li> <li>Internetzugang w\u00e4hrend der Installation</li> </ul>"},{"location":"getting-started/installation/#schritt-fur-schritt","title":"Schritt-f\u00fcr-Schritt","text":""},{"location":"getting-started/installation/#1-installationsskript-herunterladen","title":"1. Installationsskript herunterladen","text":"<pre><code>wget -qO install.sh https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh\n</code></pre>"},{"location":"getting-started/installation/#2-installation-ausfuhren","title":"2. Installation ausf\u00fchren","text":"<pre><code>sudo bash install.sh\n</code></pre> <p>W\u00e4hrend der Installation w\u00e4hlst du den HandBrake-Modus:</p> <ul> <li><code>1</code> Standard (Paketinstallation)</li> <li><code>2</code> GPU/NVDEC (geb\u00fcndeltes Binary)</li> </ul>"},{"location":"getting-started/installation/#3-dienststatus-prufen","title":"3. Dienststatus pr\u00fcfen","text":"<pre><code>sudo systemctl status ripster-backend\n</code></pre>"},{"location":"getting-started/installation/#4-weboberflache-offnen","title":"4. Weboberfl\u00e4che \u00f6ffnen","text":"<ul> <li>mit nginx (Standard): <code>http://<Server-IP></code></li> <li>ohne nginx (<code>--no-nginx</code>): API auf <code>http://<Server-IP>:3001/api</code></li> </ul>"},{"location":"getting-started/installation/#was-installsh-konkret-einrichtet","title":"Was <code>install.sh</code> konkret einrichtet","text":"<ol> <li>pr\u00fcft Betriebssystem, Root-Rechte und ermittelt Host/IP</li> <li>aktualisiert Paketquellen und installiert Basispakete (<code>curl</code>, <code>wget</code>, <code>git</code>, <code>mediainfo</code>, <code>udev</code> ...)</li> <li>installiert Node.js 20 (falls nicht passend vorhanden)</li> <li>installiert optional MakeMKV (Build aus den offiziellen Quellen)</li> <li>installiert optional HandBrakeCLI (Standard oder GPU/NVDEC)</li> <li>installiert optional nginx</li> <li>legt den Systembenutzer <code>ripster</code> an (ohne Login-Shell, ohne Home) und erg\u00e4nzt Gruppen (<code>cdrom</code>, <code>optical</code>, <code>disk</code>, <code>video</code>, <code>render</code>, falls vorhanden)</li> <li>klont das Repository nach <code>/opt/ripster</code> (oder aktualisiert bei <code>--reinstall</code>)</li> <li>legt Verzeichnisse an:</li> <li><code>/opt/ripster/backend/data</code></li> <li><code>/opt/ripster/backend/logs</code></li> <li><code>/opt/ripster/backend/data/output/raw</code></li> <li><code>/opt/ripster/backend/data/output/movies</code></li> <li><code>/opt/ripster/backend/data/logs</code></li> <li>installiert npm-Abh\u00e4ngigkeiten (Root, Backend, Frontend) und baut das Frontend</li> <li>erzeugt <code>backend/.env</code> (bei <code>--reinstall</code> bleibt bestehende <code>.env</code> erhalten)</li> <li>setzt Rechte/Besitz und erstellt bei sudo-Installation zus\u00e4tzlich <code>~/.MakeMKV</code> f\u00fcr den aufrufenden Benutzer</li> <li>erzeugt und startet <code>ripster-backend.service</code></li> <li>konfiguriert und startet nginx (falls nicht \u00fcbersprungen)</li> </ol>"},{"location":"getting-started/installation/#wichtige-optionen","title":"Wichtige Optionen","text":"Option Default laut Script Zweck <code>--branch <branch></code> <code>dev</code> Branch f\u00fcr die Installation <code>--dir <pfad></code> <code>/opt/ripster</code> Installationsverzeichnis <code>--user <benutzer></code> <code>ripster</code> Service-Benutzer <code>--port <port></code> <code>3001</code> Backend-Port <code>--host <hostname></code> automatisch ermittelte Host-IP Hostname/IP f\u00fcr Webzugriff/CORS <code>--no-makemkv</code> aus MakeMKV-Installation \u00fcberspringen <code>--no-handbrake</code> aus HandBrake-Installation \u00fcberspringen <code>--no-nginx</code> aus nginx-Setup \u00fcberspringen <code>--reinstall</code> aus bestehende Installation aktualisieren <p>Beispiele:</p> <pre><code>sudo bash install.sh --branch dev\nsudo bash install.sh --port 8080 --host ripster.local\nsudo bash install.sh --reinstall\n</code></pre>"},{"location":"getting-started/installation/#betrieb-im-alltag","title":"Betrieb im Alltag","text":"<pre><code># Logs live ansehen\nsudo journalctl -u ripster-backend -f\n\n# Dienst neu starten\nsudo systemctl restart ripster-backend\n\n# Update aus bestehender Installation\nsudo bash /opt/ripster/install.sh --reinstall\n</code></pre>"},{"location":"getting-started/installation/#haufige-stolperstellen","title":"H\u00e4ufige Stolperstellen","text":"<ul> <li>Laufwerk nicht zugreifbar: Laufwerksrechte/Gruppen pr\u00fcfen</li> <li>CLI-Tool fehlt wegen <code>--no-*</code>: Tool nachinstallieren oder Befehl in Settings korrigieren</li> <li>UI nicht erreichbar: nginx-Status und Firewall pr\u00fcfen</li> </ul>"},{"location":"getting-started/installation/#danach-weiter","title":"Danach weiter","text":"<ol> <li>Ersteinrichtung</li> <li>Erster Lauf</li> </ol>"},{"location":"getting-started/prerequisites/","title":"Voraussetzungen","text":"<p>Die Voraussetzungen h\u00e4ngen davon ab, wie du Ripster betreibst.</p>"},{"location":"getting-started/prerequisites/#produktionsbetrieb-mit-installsh-standard","title":"Produktionsbetrieb mit <code>install.sh</code> (Standard)","text":"<p>F\u00fcr den normalen Betrieb sind nur wenige Punkte vorab n\u00f6tig.</p>"},{"location":"getting-started/prerequisites/#pflicht","title":"Pflicht","text":"<ul> <li>unterst\u00fctztes Linux-System (laut Script: Debian, Ubuntu, Linux Mint, Pop!_OS)</li> <li><code>root</code>-Rechte</li> <li>Internetzugang w\u00e4hrend der Installation</li> <li>optisches Laufwerk f\u00fcr Disc-Betrieb</li> </ul> <p><code>install.sh</code> installiert die ben\u00f6tigten Tools selbst (u. a. Node.js, MakeMKV, HandBrakeCLI, MediaInfo), sofern sie nicht explizit per <code>--no-*</code> \u00fcbersprungen werden.</p>"},{"location":"getting-started/prerequisites/#laufwerk-kurz-prufen","title":"Laufwerk kurz pr\u00fcfen","text":"<pre><code>ls /dev/sr*\nlsblk | grep rom\n</code></pre> <p>Wenn n\u00f6tig Rechte setzen (Beispiel):</p> <pre><code>sudo chmod a+rw /dev/sr0\n</code></pre>"},{"location":"getting-started/prerequisites/#optional-vorab","title":"Optional vorab","text":"<ul> <li>OMDb API-Key (kann auch nach Installation in den <code>Settings</code> gesetzt werden)</li> <li>PushOver-Zugangsdaten (optional)</li> </ul>"},{"location":"getting-started/prerequisites/#entwicklungsmodus-nur-fur-dev","title":"Entwicklungsmodus (nur f\u00fcr Dev)","text":"<p>Wenn du lokal entwickelst (<code>./start.sh</code>, <code>npm run dev</code>), gelten zus\u00e4tzliche Voraussetzungen:</p> <ul> <li>Node.js >= 20.19.0</li> <li><code>makemkvcon</code>, <code>HandBrakeCLI</code>, <code>mediainfo</code> im <code>PATH</code></li> </ul> <p>Details: Entwicklungsumgebung</p>"},{"location":"getting-started/prerequisites/#abschluss-checkliste","title":"Abschluss-Checkliste","text":"<ul> <li>[ ] Produktionsbetrieb: Linux + root + Internet + Laufwerk vorhanden</li> <li>[ ] Dev-Modus (nur falls ben\u00f6tigt): Node.js und CLI-Tools verf\u00fcgbar</li> </ul>"},{"location":"getting-started/quickstart/","title":"Erster Lauf","text":"<p>Dieser Ablauf zeigt einen vollst\u00e4ndigen Job aus Anwendersicht: von Disc-Erkennung bis fertiger Datei.</p>"},{"location":"getting-started/quickstart/#1-ripper-offnen-und-disc-einlegen","title":"1. Ripper \u00f6ffnen und Disc einlegen","text":"<p>Erwartung:</p> <ul> <li>Status wechselt auf <code>Medium erkannt</code></li> <li>im Bereich <code>Disk-Information</code> sind Laufwerksdaten sichtbar</li> </ul> <p>Wenn nichts passiert: <code>Laufwerk neu lesen</code>.</p>"},{"location":"getting-started/quickstart/#2-analyse-starten","title":"2. Analyse starten","text":"<p>Aktion im Ripper:</p> <ul> <li><code>Analyse starten</code></li> </ul> <p>Erwartung:</p> <ul> <li>Status <code>Analyse</code></li> <li>danach Metadaten-Dialog</li> </ul>"},{"location":"getting-started/quickstart/#3-metadaten-auswahlen","title":"3. Metadaten ausw\u00e4hlen","text":"<p>Im Dialog <code>Metadaten ausw\u00e4hlen</code>:</p> <ol> <li>OMDb-Suche nutzen oder manuell eintragen</li> <li>passenden Treffer markieren</li> <li><code>Auswahl \u00fcbernehmen</code></li> </ol>"},{"location":"getting-started/quickstart/#4-auf-den-nachsten-zustand-reagieren","title":"4. Auf den n\u00e4chsten Zustand reagieren","text":"<ul> <li>Normalfall ohne vorhandenes RAW: <code>Rippen</code> -> <code>Mediainfo-Pruefung</code> -> <code>Bereit zum Encodieren</code></li> <li>bei vorhandenem RAW: direkt <code>Mediainfo-Pruefung</code> -> <code>Bereit zum Encodieren</code></li> <li>bei unklarer Blu-ray-Playlist: <code>Warte auf Auswahl</code> (Playlist ausw\u00e4hlen und \u00fcbernehmen)</li> </ul>"},{"location":"getting-started/quickstart/#5-review-in-bereit-zum-encodieren","title":"5. Review in <code>Bereit zum Encodieren</code>","text":"<p>Im aufgeklappten Job (<code>Pipeline-Status</code>):</p> <ul> <li>Encode-Titel w\u00e4hlen</li> <li>Audio-/Subtitle-Spuren pr\u00fcfen</li> <li>optional User-Preset ausw\u00e4hlen</li> <li>optional Pre-/Post-Skripte bzw. Ketten hinzuf\u00fcgen</li> </ul> <p>Dann <code>Encoding starten</code>.</p>"},{"location":"getting-started/quickstart/#6-encoding-uberwachen","title":"6. Encoding \u00fcberwachen","text":"<p>W\u00e4hrend <code>Encodieren</code>:</p> <ul> <li>Fortschritt + ETA im Ripper</li> <li>Live-Log im <code>Pipeline-Status</code></li> <li>Queue- und Skript/Cron-Status parallel beobachtbar</li> </ul>"},{"location":"getting-started/quickstart/#7-ergebnis-prufen","title":"7. Ergebnis pr\u00fcfen","text":"<p>Bei <code>Fertig</code>:</p> <ol> <li>Seite <code>Historie</code> \u00f6ffnen</li> <li>Job in Details \u00f6ffnen</li> <li>Output-Pfad, Status und Log pr\u00fcfen</li> </ol>"},{"location":"getting-started/quickstart/#typische-folgeaktionen","title":"Typische Folgeaktionen","text":"<ul> <li>Falsches OMDb-Match: in <code>Historie</code> -> <code>OMDb neu zuordnen</code></li> <li>Neue Encodierung aus RAW: <code>RAW neu encodieren</code></li> <li>Pr\u00fcfung komplett neu aufbauen: <code>Review neu starten</code></li> </ul>"},{"location":"gui/","title":"GUI-Seiten","text":"<p>Ripster hat sieben Hauptseiten in der Navigation plus eine Expert-Seite.</p>"},{"location":"gui/#seitenuberblick","title":"Seiten\u00fcberblick","text":"Seite Zweck Jobs zentrale Inbox mit allen Jobs (Ripper, Converter, Audiobooks) Ripper Live-Betrieb: Pipeline, Queue, Aktivit\u00e4ten, Disc-Infos Audiobooks AAX-Upload, Audiobook-Jobsteuerung und Output-Explorer Settings Konfiguration, Skripte, Ketten, Presets, Cronjobs Historie abgeschlossene/laufende Jobs durchsuchen und nachbearbeiten Converter Audio/Video-Dateien konvertieren, Datei-Explorer, Upload Downloads Ausgabedateien als ZIP herunterladen Database (Expert) tabellarische Rohsicht inkl. Orphan-RAW-Import"},{"location":"gui/#empfohlene-nutzung-im-alltag","title":"Empfohlene Nutzung im Alltag","text":"<ol> <li>Ersten \u00dcberblick \u00fcber alle Jobs: <code>Jobs</code></li> <li>Start eines neuen Disc-Jobs: <code>Ripper</code></li> <li>Audio/Video-Dateien konvertieren: <code>Converter</code></li> <li>AAX/Audiobook-Flow: <code>Audiobooks</code></li> <li>Regeln/Automatisierung anpassen: <code>Settings</code></li> <li>Ergebnisse pr\u00fcfen oder Jobs nachbearbeiten: <code>Historie</code></li> <li>Dateien herunterladen: <code>Downloads</code></li> <li>Sonderf\u00e4lle/Recovery: <code>Database</code></li> </ol>"},{"location":"gui/#hinweise-zur-navigation","title":"Hinweise zur Navigation","text":"<ul> <li><code>Jobs</code>, <code>Ripper</code>, <code>Converter</code>, <code>Audiobooks</code>, <code>Settings</code>, <code>Historie</code> und <code>Downloads</code> sind direkt in der Kopfnavigation.</li> <li><code>Database</code> ist als Expert-Route verf\u00fcgbar: <code>/database</code>.</li> </ul>"},{"location":"gui/audiobooks/","title":"Audiobooks","text":"<p>Die Seite <code>Audiobooks</code> ist die dedizierte Oberfl\u00e4che f\u00fcr AAX- und Audiobook-Jobs.</p>"},{"location":"gui/audiobooks/#aufbau","title":"Aufbau","text":"<ol> <li><code>Audiobooks Upload</code></li> <li><code>Audiobook Jobs</code></li> <li><code>Audiobook Output Explorer</code></li> </ol>"},{"location":"gui/audiobooks/#1-audiobooks-upload","title":"1) Audiobooks Upload","text":"<p>Hier werden AAX-Dateien hochgeladen und als Job vorbereitet.</p> <p>Typische Schritte:</p> <ol> <li>Datei ausw\u00e4hlen</li> <li>optional Format/Startverhalten setzen</li> <li>Upload starten</li> </ol> <p>W\u00e4hrend des Uploads zeigt die Seite Fortschritt und Status an.</p>"},{"location":"gui/audiobooks/#2-audiobook-jobs","title":"2) Audiobook Jobs","text":"<p>Aktive Audiobook-Jobs werden als Karten dargestellt (mit Status und Fortschritt).</p> <p>Pro Job sind je nach Zustand Aktionen verf\u00fcgbar:</p> <ul> <li>Starten (mit aktueller Audiobook-Konfiguration)</li> <li>Abbrechen</li> <li>Retry</li> <li>L\u00f6schen</li> </ul> <p>Zus\u00e4tzlich:</p> <ul> <li><code>Im Ripper</code> springt in die Hauptansicht</li> <li>Jobs laden periodisch nach und reagieren auf WebSocket-Updates</li> </ul>"},{"location":"gui/audiobooks/#3-audiobook-output-explorer","title":"3) Audiobook Output Explorer","text":"<p>Read-only Baumansicht des konfigurierten Audiobook-Ausgabeordners:</p> <ul> <li>Ordner- und Dateiansicht</li> <li>Gr\u00f6\u00dfen-/Zeitstempel\u00fcbersicht</li> <li>schnelle Kontrolle der erzeugten Ergebnisse</li> </ul>"},{"location":"gui/audiobooks/#abgrenzung-zum-converter","title":"Abgrenzung zum Converter","text":"<p>Audio/Video-Datei-Konvertierung l\u00e4uft weiterhin auf der Seite <code>Converter</code>. Der AAX-spezifische Flow (Upload, Start, Jobsteuerung, Output-Explorer) liegt auf <code>Audiobooks</code>.</p>"},{"location":"gui/converter/","title":"Converter","text":"<p>Die Converter-Seite erm\u00f6glicht das Konvertieren vorhandener Audio- und Video-Dateien.</p>"},{"location":"gui/converter/#uberblick","title":"\u00dcberblick","text":"<p>Der Converter arbeitet mit einem konfigurierbaren Eingangsordner (<code>converter_raw_dir</code>). Dateien werden dort abgelegt (manuell oder per Upload) und dann als Converter-Jobs in die Pipeline eingereiht.</p>"},{"location":"gui/converter/#datei-explorer","title":"Datei-Explorer","text":"<p>Der Datei-Explorer zeigt den Inhalt des <code>converter_raw_dir</code> als Baumstruktur.</p>"},{"location":"gui/converter/#verfugbare-aktionen","title":"Verf\u00fcgbare Aktionen","text":"Aktion Beschreibung Upload Dateien direkt aus dem Browser hochladen (bis zu 50 Dateien) Ordner erstellen Neuen Unterordner anlegen Umbenennen Datei oder Ordner umbenennen Verschieben Datei oder Ordner in einen anderen Unterordner verschieben L\u00f6schen Datei oder Ordner l\u00f6schen (unwiderruflich) Scannen Eingangsordner manuell neu scannen"},{"location":"gui/converter/#dateiauswahl","title":"Dateiauswahl","text":"<p>Dateien k\u00f6nnen per Checkbox ausgew\u00e4hlt werden. Aus der Auswahl lassen sich Jobs erstellen:</p> <ul> <li>Ein Job pro Datei \u2014 jede Datei wird als eigenst\u00e4ndiger Job angelegt</li> <li>Gemeinsamer Job (Audio) \u2014 mehrere Audio-Dateien werden zu einem gemeinsamen Job zusammengefasst</li> </ul> <p>Erlaubte Dateiendungen werden in den Settings unter <code>Converter > Erlaubte Datei-Endungen*</code> als Checkboxen konfiguriert.</p>"},{"location":"gui/converter/#jobs-erstellen-und-konfigurieren","title":"Jobs erstellen und konfigurieren","text":"<p>Nach der Auswahl und Job-Erstellung erscheinen die Jobs in der Job-Liste.</p>"},{"location":"gui/converter/#job-konfiguration","title":"Job-Konfiguration","text":"<p>Jeder Job kann konfiguriert werden:</p> <ul> <li>Medientyp: Video, Audio oder Audiobook</li> <li>Ausgabeformat: z. B. <code>mkv</code>, <code>mp4</code>, <code>m4b</code>, <code>mp3</code>, <code>flac</code></li> <li>HandBrake-Preset: f\u00fcr Video-Konvertierung</li> <li>Track-Auswahl: Video-, Audio- und Untertitel-Spuren</li> <li>Metadaten: Titel, Autor, Jahr (f\u00fcr Audiobooks)</li> <li>MusicBrainz-Suche: automatische Metadaten-Suche f\u00fcr Audiodateien</li> </ul>"},{"location":"gui/converter/#job-start","title":"Job-Start","text":"<p>Jobs starten sofort oder reihen sich in die Queue ein (abh\u00e4ngig von <code>Parallele Jobs</code>).</p>"},{"location":"gui/converter/#auto-scan-polling","title":"Auto-Scan (Polling)","text":"<p>Wenn in den Settings <code>Converter > Auto-Scan (Polling)</code> aktiviert ist, wird der Eingangsordner automatisch in regelm\u00e4\u00dfigen Abst\u00e4nden gescannt. Neue Dateien werden in der DB erfasst und im Explorer angezeigt.</p> <p>Das Scan-Intervall wird unter <code>Converter > Polling-Intervall (Sekunden)</code> konfiguriert (Standard: 300 Sekunden).</p>"},{"location":"gui/converter/#hinweis-zu-audiobooks","title":"Hinweis zu Audiobooks","text":"<p>Der dedizierte AAX-/Audiobook-Flow ist auf der Seite Audiobooks dokumentiert.</p>"},{"location":"gui/converter/#einstellungen","title":"Einstellungen","text":"Setting Pfad in GUI Eingangsordner <code>Settings > Pfade > Converter Raw-Ordner</code> Videoausgabe <code>Settings > Pfade > Converter Ausgabe (Video)</code> Audioausgabe <code>Settings > Pfade > Converter Ausgabe (Audio)</code> Erlaubte Endungen <code>Settings > Converter > Erlaubte Datei-Endungen*</code> Auto-Scan <code>Settings > Converter > Auto-Scan (Polling)</code> Scan-Intervall <code>Settings > Converter > Polling-Intervall (Sekunden)</code> Output-Template Video <code>Settings > Pfade > Output-Template (Video)</code> Output-Template Audio <code>Settings > Pfade > Output-Template (Audio)</code>"},{"location":"gui/database/","title":"Database (Expert)","text":"<p><code>/database</code> ist eine erweiterte Ansicht f\u00fcr Power-User und Recovery-F\u00e4lle.</p>"},{"location":"gui/database/#zugriff","title":"Zugriff","text":"<ul> <li>Route direkt aufrufen: <code>/database</code></li> <li>nicht Teil der Standard-Navigation</li> </ul>"},{"location":"gui/database/#bereiche","title":"Bereiche","text":""},{"location":"gui/database/#gefundene-raw-eintrage","title":"<code>Gefundene RAW-Eintr\u00e4ge</code>","text":"<p>Listet RAW-Ordner, die keinen zugeh\u00f6rigen Job-Eintrag haben.</p> <p>Aktionen:</p> <ul> <li><code>RAW pr\u00fcfen</code> (Scan der konfigurierten RAW-Pfade)</li> <li><code>Job anlegen</code> (Orphan-RAW in Historie importieren)</li> </ul>"},{"location":"gui/database/#typischer-einsatz","title":"Typischer Einsatz","text":"<ul> <li>nach manuellen Dateioperationen</li> <li>nach Migrationen oder Recovery</li> <li>wenn RAW-Dateien vorhanden sind, aber kein Historieneintrag existiert</li> </ul>"},{"location":"gui/database/#vorsicht","title":"Vorsicht","text":"<p>Diese Seite erlaubt Eingriffe mit direkter Auswirkung auf Datenbestand und Historie. Vor L\u00f6sch- oder Importaktionen Pfade und Zieljob sorgf\u00e4ltig pr\u00fcfen.</p>"},{"location":"gui/downloads/","title":"Downloads","text":"<p>Die Downloads-Seite erm\u00f6glicht es, Ausgabedateien aus abgeschlossenen Jobs als ZIP-Archiv herunterzuladen.</p>"},{"location":"gui/downloads/#uberblick","title":"\u00dcberblick","text":"<p>Jobs, die in der Historie abgeschlossen sind, k\u00f6nnen in die Download-Queue eingereiht werden. Ripster erstellt daraus ein ZIP-Archiv, das direkt im Browser heruntergeladen werden kann.</p>"},{"location":"gui/downloads/#download-queue","title":"Download-Queue","text":"<p>Die Queue zeigt alle aktuellen Download-Eintr\u00e4ge mit ihrem Status:</p> Status Beschreibung Ausstehend In der Queue, ZIP-Erstellung noch nicht gestartet Wird verarbeitet ZIP-Archiv wird gerade erstellt Bereit ZIP fertig, Download-Button verf\u00fcgbar Fehlgeschlagen Fehler bei der ZIP-Erstellung"},{"location":"gui/downloads/#download-einreihen","title":"Download einreihen","text":"<p>Downloads k\u00f6nnen \u00fcber die Historie eingereiht werden:</p> <ol> <li>Job in der Historie \u00f6ffnen</li> <li>Download einreihen w\u00e4hlen</li> <li>Ziel ausw\u00e4hlen: <code>RAW-Dateien</code> oder <code>Ausgabedateien</code></li> <li>Den Eintrag in der Downloads-Queue verfolgen</li> </ol>"},{"location":"gui/downloads/#download-starten","title":"Download starten","text":"<p>Sobald der Status Bereit angezeigt wird, kann die ZIP-Datei mit dem Download-Button heruntergeladen werden.</p> <p>ZIP-Dateinamen folgen dem Muster: <code>Job_<id>_<target>.zip</code></p>"},{"location":"gui/downloads/#eintrage-loschen","title":"Eintr\u00e4ge l\u00f6schen","text":"<p>Abgeschlossene oder fehlerhafte Eintr\u00e4ge k\u00f6nnen einzeln gel\u00f6scht werden. Die dazugeh\u00f6rige ZIP-Datei wird dabei ebenfalls vom Server entfernt.</p>"},{"location":"gui/downloads/#echtzeit-updates","title":"Echtzeit-Updates","text":"<p>Die Downloads-Seite aktualisiert sich automatisch per WebSocket (<code>DOWNLOADS_UPDATED</code>). Status\u00e4nderungen (z. B. von <code>Wird verarbeitet</code> zu <code>Bereit</code>) werden ohne Seiten-Reload angezeigt.</p>"},{"location":"gui/history/","title":"Historie","text":"<p>Die Seite <code>Historie</code> ist f\u00fcr Suche, Pr\u00fcfung und Nachbearbeitung bestehender Jobs.</p>"},{"location":"gui/history/#hauptansicht","title":"Hauptansicht","text":"<p>Filter und Werkzeuge:</p> <ul> <li>Suche (Titel/IMDb)</li> <li>Status-Filter</li> <li>Medium-Filter (<code>Blu-ray</code>, <code>DVD</code>, <code>Sonstiges</code>)</li> <li>Sortierung</li> <li>Listen-/Grid-Layout</li> </ul> <p>Jeder Eintrag zeigt:</p> <ul> <li>Poster, Titel, Jahr, IMDb</li> <li>Medium-Indikator</li> <li>Status</li> <li>Start/Ende</li> <li>Verf\u00fcgbarkeit von RAW/Movie</li> <li>Ratings (wenn OMDb-Daten vorhanden)</li> </ul> <p>Klick auf einen Eintrag \u00f6ffnet die Detailansicht.</p>"},{"location":"gui/history/#job-detaildialog","title":"Job-Detaildialog","text":"<p>Bereiche:</p> <ul> <li>Film-Infos + OMDb-Details</li> <li>Job-Infos (Status, Pfade, Erfolgsflags, Fehler)</li> <li>hinterlegte Encode-Auswahl</li> <li>ausgef\u00fchrter HandBrake-Befehl</li> <li>strukturierte JSON-Bl\u00f6cke (OMDb/MakeMKV/MediaInfo/EncodePlan/HandBrake)</li> <li>Log-Ladefunktionen (<code>Tail</code>, <code>Vollst\u00e4ndig</code>)</li> </ul> <p>Bei Serien-Containern zeigt der Dialog statt eines globalen Log-Blocks einen <code>Disks</code>-Bereich mit einzelnen Disk-Akkordeons. Jede Disk kann dort separat gepr\u00fcft und bearbeitet werden.</p>"},{"location":"gui/history/#typische-aktionen-im-detaildialog","title":"Typische Aktionen im Detaildialog","text":"<ul> <li><code>OMDb neu zuordnen</code></li> <li><code>Encode neu starten</code></li> <li><code>Review neu starten</code></li> <li><code>RAW neu encodieren</code></li> <li><code>RAW l\u00f6schen</code>, <code>Movie l\u00f6schen</code>, <code>Beides l\u00f6schen</code></li> <li><code>Historieneintrag l\u00f6schen</code></li> <li>bei Queue-Lock: <code>Aus Queue l\u00f6schen</code></li> </ul> <p>Zus\u00e4tzlich bei Serien-Container-Disks:</p> <ul> <li><code>Im Ripper \u00f6ffnen</code> (f\u00fcr <code>READY_TO_ENCODE</code>-Kinder)</li> <li>diskbezogene L\u00f6schaktionen (<code>RAW</code>, <code>Folgen</code>, <code>Beides</code>) mit Child-/Subjob-Scope</li> <li>diskbezogenes L\u00f6schen des Historieneintrags (inkl. Subjobs)</li> </ul>"},{"location":"gui/history/#wann-welche-aktion","title":"Wann welche Aktion?","text":"Ziel Aktion Metadaten korrigieren <code>OMDb neu zuordnen</code> mit gleicher best\u00e4tigter Auswahl neu encodieren <code>Encode neu starten</code> Titel-/Spurpr\u00fcfung komplett neu berechnen <code>Review neu starten</code> aus vorhandenem RAW erneut encodieren <code>RAW neu encodieren</code> Speicher freigeben Dateil\u00f6schaktionen"},{"location":"gui/history/#loschen-mit-vorschau","title":"L\u00f6schen mit Vorschau","text":"<p>Beim L\u00f6schen eines Historieneintrags (<code>Eintrag l\u00f6schen</code>) gibt es eine Pfadvorschau:</p> <ul> <li>getrennt nach <code>RAW</code> und Ausgabe (<code>Movie</code>/<code>Folgen</code>)</li> <li>f\u00fcr Serien-/Multipart-F\u00e4lle mit Scope auf verkn\u00fcpfte Jobs</li> <li>selektierbare Pfade vor dem finalen L\u00f6schen</li> </ul> <p>Neu im RAW-Bereich:</p> <ul> <li>bei mehreren RAW-Kandidaten kann pro Pfad gew\u00e4hlt werden, was entfernt wird</li> <li>mindestens ein RAW-Pfad muss gew\u00e4hlt sein, wenn RAW-L\u00f6schung Teil der Aktion ist</li> </ul> <p>Die Buttons (<code>Eintrag + nur RAW</code>, <code>Eintrag + nur ...</code>, <code>Eintrag + RAW + ...</code>) ber\u00fccksichtigen die aktuelle Pfadauswahl und deaktivieren sich bei unvollst\u00e4ndiger Auswahl.</p>"},{"location":"gui/history/#logs","title":"Logs","text":"<ul> <li><code>Tail laden (800)</code> f\u00fcr schnelle Fehleranalyse</li> <li><code>Vollst\u00e4ndiges Log laden</code> f\u00fcr vollst\u00e4ndige Nachverfolgung</li> </ul>"},{"location":"gui/jobs/","title":"Jobs","text":"<p>Die Seite <code>Jobs</code> ist die zentrale Inbox \u00fcber alle Job-Typen.</p>"},{"location":"gui/jobs/#zweck","title":"Zweck","text":"<p><code>Jobs</code> zeigt denselben Datenbestand in einer einheitlichen Liste:</p> <ul> <li>Ripper-Jobs (Blu-ray/DVD/CD)</li> <li>Converter-Jobs (Audio/Video/ISO)</li> <li>Audiobook-Jobs</li> </ul> <p>Die Seite eignet sich als schneller Einstieg, bevor man in den jeweiligen Fachbereich springt.</p>"},{"location":"gui/jobs/#filter-und-ansicht","title":"Filter und Ansicht","text":""},{"location":"gui/jobs/#view-filter","title":"View-Filter","text":"<ul> <li><code>Alle Jobs</code></li> <li><code>Ripper-View</code></li> <li><code>Converter-View</code></li> <li><code>Audiobooks</code></li> </ul>"},{"location":"gui/jobs/#status-filter","title":"Status-Filter","text":"<ul> <li><code>Alle</code></li> <li><code>Aktiv</code></li> <li><code>Fehler/Abbruch</code></li> </ul> <p>Zus\u00e4tzlich gibt es eine Freitextsuche (Titel, Status, IMDb, Job-Kind etc.) und eine Paginierung.</p>"},{"location":"gui/jobs/#was-ein-eintrag-zeigt","title":"Was ein Eintrag zeigt","text":"<ul> <li>Job-ID und Titel</li> <li>Status-Badge (inkl. Queue-Ber\u00fccksichtigung)</li> <li>Job-Typ-Badge (z. B. Blu-ray, Converter Audio, Audiobook)</li> <li>Zeitstempel der letzten Aktualisierung</li> </ul> <p>Die Liste aktualisiert sich zyklisch automatisch.</p>"},{"location":"gui/jobs/#aktion","title":"Aktion","text":"<p>Pro Eintrag gibt es einen Schnellzugriff:</p> <ul> <li><code>Im Ripper \u00f6ffnen</code> f\u00fcr Disc-/Audiobook-orientierte Jobs</li> <li><code>Im Converter \u00f6ffnen</code> f\u00fcr Converter-Jobs</li> </ul> <p>So gelangst du mit einem Klick in die passende Detailoberfl\u00e4che.</p>"},{"location":"gui/ripper/","title":"Ripper","text":"<p>Das Ripper ist die Betriebszentrale f\u00fcr laufende Jobs.</p>"},{"location":"gui/ripper/#aufbau-der-seite","title":"Aufbau der Seite","text":"<p>Die Bereiche erscheinen in dieser Reihenfolge:</p> <ol> <li><code>Hardware Monitoring</code></li> <li><code>Job Queue</code></li> <li><code>Skript- / Cron-Status</code></li> <li><code>Job \u00dcbersicht</code></li> <li><code>Disk-Information</code></li> </ol>"},{"location":"gui/ripper/#1-hardware-monitoring","title":"1) Hardware Monitoring","text":"<p>Zeigt live:</p> <ul> <li>CPU (gesamt + optional pro Kern)</li> <li>RAM</li> <li>GPU-Auslastung/Temperatur/VRAM</li> <li>freien Speicher in den konfigurierten Pfaden</li> </ul> <p>Wichtig f\u00fcr den Betrieb:</p> <ul> <li>Hohe Speicherauslastung oder fast volle Zielpfade fr\u00fch erkennen</li> <li>\u00fcber <code>Settings</code> aktivierbar/deaktivierbar (<code>Hardware Monitoring aktiviert</code>)</li> </ul>"},{"location":"gui/ripper/#2-job-queue","title":"2) Job Queue","text":"<p>Zwei Spalten:</p> <ul> <li><code>Laufende Jobs</code></li> <li><code>Warteschlange</code></li> </ul> <p>M\u00f6gliche Aktionen:</p> <ul> <li>Queue per Drag-and-Drop umsortieren</li> <li>Queue-Job entfernen (<code>X</code>)</li> <li>zus\u00e4tzliche Queue-Elemente einf\u00fcgen (<code>+</code>):</li> <li>Skript</li> <li>Skriptkette</li> <li>Wartezeit</li> </ul> <p>Hinweis:</p> <ul> <li><code>Parallel</code> zeigt den Wert aus <code>Settings</code> -> <code>Parallele Jobs</code>.</li> </ul>"},{"location":"gui/ripper/#3-skript-cron-status","title":"3) Skript- / Cron-Status","text":"<p>Zeigt:</p> <ul> <li>aktive Ausf\u00fchrungen (Skripte, Ketten, Cron)</li> <li>zuletzt abgeschlossene Ausf\u00fchrungen</li> </ul> <p>M\u00f6gliche Aktionen:</p> <ul> <li>laufende Ketten: <code>N\u00e4chster Schritt</code></li> <li>laufende Eintr\u00e4ge: <code>Abbrechen</code></li> <li>Historie der Aktivit\u00e4ten: <code>Liste leeren</code></li> </ul>"},{"location":"gui/ripper/#4-job-ubersicht","title":"4) Job \u00dcbersicht","text":"<p>Kompakte Jobliste mit Status, Fortschritt, ETA. Klick auf einen Job klappt die Detailsteuerung auf.</p> <p>Zus\u00e4tzliche Kennzeichnungen in der Liste:</p> <ul> <li><code>Serien-Container</code>/<code>Serien-Disk</code> f\u00fcr Staffel-Workflows</li> <li><code>Multipart Movie</code> bei zusammengef\u00fchrten Film-Discs</li> <li>Untertitel mit Disc-/Container-Infos (z. B. <code>Disc 2 | Container #123</code>)</li> </ul> <p>Im aufgeklappten Zustand erscheint die Karte <code>Pipeline-Status</code> mit allen zustandsabh\u00e4ngigen Aktionen.</p>"},{"location":"gui/ripper/#zustandsabhangige-hauptaktionen","title":"Zustandsabh\u00e4ngige Hauptaktionen","text":"Zustand Typische Aktion <code>Medium erkannt</code> / <code>Bereit</code> <code>Analyse starten</code> <code>Metadatenauswahl</code> <code>Metadaten \u00f6ffnen</code> <code>Warte auf Auswahl</code> Playlist w\u00e4hlen und <code>Playlist \u00fcbernehmen</code> <code>Startbereit</code> <code>Job starten</code> <code>Bereit zum Encodieren</code> Tracks/Skripte pr\u00fcfen, dann <code>Encoding starten</code> laufend (<code>Analyse</code>/<code>Rippen</code>/<code>Encodieren</code>) <code>Abbrechen</code> <code>Fehler</code> / <code>Abgebrochen</code> <code>Retry Rippen</code>, <code>Disk-Analyse neu starten</code> <p>Zus\u00e4tzlich je nach Job:</p> <ul> <li><code>Review neu starten</code></li> <li><code>Encode neu starten</code></li> <li><code>Aus Queue l\u00f6schen</code></li> </ul>"},{"location":"gui/ripper/#titel-spurprufung-bereit-zum-encodieren","title":"Titel-/Spurpr\u00fcfung (<code>Bereit zum Encodieren</code>)","text":"<p>Im selben Block siehst du:</p> <ul> <li>Auswahl des Encode-Titels</li> <li>Audio-/Subtitle-Trackauswahl</li> <li>User-Preset-Auswahl</li> <li>Pre-/Post-Encode-Skripte und Ketten</li> <li>Preview des finalen HandBrakeCLI-Befehls</li> </ul>"},{"location":"gui/ripper/#5-disk-information","title":"5) Disk-Information","text":"<p>Zeigt aktuelles Laufwerk und Disc-Metadaten (<code>Pfad</code>, <code>Modell</code>, <code>Disc-Label</code>, <code>Mount</code>).</p> <p>Aktionen:</p> <ul> <li><code>Laufwerk neu lesen</code></li> <li><code>Disk neu analysieren</code></li> <li><code>Metadaten-Modal \u00f6ffnen</code></li> </ul> <p>Hinweis:</p> <ul> <li><code>Laufwerk neu lesen</code> ist nicht mehr an einen festen Pipeline-State gebunden und kann genutzt werden, solange kein Rescan-Request l\u00e4uft.</li> </ul>"},{"location":"gui/ripper/#wichtige-dialoge-im-ripper","title":"Wichtige Dialoge im Ripper","text":""},{"location":"gui/ripper/#metadaten-auswahlen","title":"Metadaten ausw\u00e4hlen","text":"<ul> <li>OMDb-Suche + Ergebnisliste</li> <li>manuelle Eingabe als Fallback</li> <li><code>Auswahl \u00fcbernehmen</code> startet den n\u00e4chsten Pipeline-Schritt</li> </ul> <p>Bei Film-Duplikaten in der Historie \u00f6ffnet sich ein Konfliktdialog:</p> <ul> <li><code>\u00dcbernahme erzwingen</code>: legt trotzdem einen neuen Job mit denselben Metadaten an</li> <li><code>Multipart movie</code>: verkn\u00fcpft bestehenden und neuen Job in einem Multipart-Container (mit separaten Disc-Nummern)</li> <li><code>Zur\u00fcck zur Auswahl</code>: zur\u00fcck in die Metadatenliste</li> </ul>"},{"location":"gui/ripper/#abbruch-bereinigung","title":"Abbruch-Bereinigung","text":"<p>Nach Abbruch kann Ripster optional fragen, ob erzeugte RAW- oder Movie-Dateien gel\u00f6scht werden sollen.</p>"},{"location":"gui/ripper/#queue-eintrag-einfugen","title":"Queue-Eintrag einf\u00fcgen","text":"<p>Erstellt gezielt einen Skript-, Ketten- oder Warte-Eintrag an einer bestimmten Queue-Position.</p>"},{"location":"gui/settings/","title":"Settings","text":"<p>Die Seite <code>Settings</code> steuert Konfiguration und Automatisierung.</p>"},{"location":"gui/settings/#tabs-im-uberblick","title":"Tabs im \u00dcberblick","text":"Tab Zweck <code>Konfiguration</code> alle Kernsettings (Pfade, Tools, Monitoring, Metadaten, Queue, Benachrichtigungen) <code>Scripte</code> einzelne Bash-Skripte verwalten und testen <code>Skriptketten</code> Sequenzen aus Skript- und Warte-Schritten bauen <code>Encode-Presets</code> benutzerdefinierte Presets f\u00fcr das Review im Ripper <code>Cronjobs</code> zeitgesteuerte Skript-/Kettenausf\u00fchrung"},{"location":"gui/settings/#tab-konfiguration","title":"Tab <code>Konfiguration</code>","text":"<p>Wichtiges Bedienmuster:</p> <ol> <li>Werte \u00e4ndern</li> <li><code>\u00c4nderungen speichern</code></li> <li>bei Bedarf <code>\u00c4nderungen verwerfen</code> oder <code>Neu laden</code></li> </ol> <p>Zus\u00e4tzlich:</p> <ul> <li><code>PushOver Test</code> sendet eine Testnachricht</li> <li>\u00c4nderungen werden erst nach Speichern wirksam</li> <li>Tool-Preset-Felder bieten HandBrake-Presetauswahl direkt im Formular</li> </ul>"},{"location":"gui/settings/#tab-scripte","title":"Tab <code>Scripte</code>","text":"<p>Funktionen:</p> <ul> <li>Skript anlegen, bearbeiten, l\u00f6schen</li> <li>Skript testen (<code>Test</code>)</li> <li>Reihenfolge per Drag-and-Drop</li> </ul> <p>Praxis:</p> <ul> <li>Reihenfolge ist wichtig, weil ausgew\u00e4hlte Skripte sp\u00e4ter sequentiell abgearbeitet werden.</li> <li>Testresultate zeigen Exit-Code, Dauer und stdout/stderr.</li> </ul>"},{"location":"gui/settings/#tab-skriptketten","title":"Tab <code>Skriptketten</code>","text":"<p>Funktionen:</p> <ul> <li>Kette anlegen/bearbeiten/l\u00f6schen</li> <li>Kette testen</li> <li>Reihenfolge der Ketten per Drag-and-Drop</li> </ul> <p>Im Ketten-Editor:</p> <ul> <li>Bausteine links (<code>Warten</code>, vorhandene Skripte)</li> <li>Schritte rechts per Klick oder Drag-and-Drop hinzuf\u00fcgen</li> <li>Schrittreihenfolge im Canvas \u00e4ndern</li> </ul>"},{"location":"gui/settings/#tab-encode-presets","title":"Tab <code>Encode-Presets</code>","text":"<p>Ein Preset b\u00fcndelt:</p> <ul> <li>optional HandBrake-Preset (<code>-Z</code>)</li> <li>optionale Extra-Args</li> <li>Medientyp (<code>Universell</code>, <code>Blu-ray</code>, <code>DVD</code>, <code>Sonstiges</code>)</li> </ul> <p>Verwendung:</p> <ul> <li>Diese Presets erscheinen sp\u00e4ter im Ripper im Review (<code>Bereit zum Encodieren</code>).</li> </ul>"},{"location":"gui/settings/#tab-cronjobs","title":"Tab <code>Cronjobs</code>","text":"<p>Funktionen:</p> <ul> <li>Cronjob anlegen und bearbeiten</li> <li>Quelle w\u00e4hlen: Skript oder Skriptkette</li> <li>Cron-Ausdruck validieren</li> <li><code>Jetzt ausf\u00fchren</code></li> <li>Logs je Cronjob anzeigen</li> <li><code>Aktiviert</code> und <code>Pushover</code> toggeln</li> </ul> <p>Hilfen:</p> <ul> <li>Beispiele f\u00fcr Cron-Ausdr\u00fccke direkt im Dialog</li> <li>Link zu <code>crontab.guru</code> im Editor</li> </ul>"},{"location":"gui/settings/#empfehlung-fur-stabile-nutzung","title":"Empfehlung f\u00fcr stabile Nutzung","text":"<ol> <li>Erst <code>Konfiguration</code> sauber setzen</li> <li>dann Skripte/Ketten testen</li> <li>danach Cronjobs aktivieren</li> </ol>"},{"location":"pipeline/","title":"Anhang: Pipeline intern","text":"<p>Dieser Abschnitt beschreibt die technische Pipeline-Logik hinter den UI-Workflows.</p> <ul> <li> <p> Workflow & Zust\u00e4nde</p> <p>Zustandsmodell, \u00dcberg\u00e4nge, Queue-Verhalten.</p> <p> Workflow</p> </li> <li> <p> Encode-Planung</p> <p>Aufbereitung von Titeln/Tracks und Best\u00e4tigungslogik.</p> <p> Encoding</p> </li> <li> <p> Playlist-Analyse</p> <p>Bewertung mehrdeutiger Blu-ray-Playlists.</p> <p> Playlist-Analyse</p> </li> <li> <p> Pre-/Post-Encode-Ausf\u00fchrungen</p> <p>Skript- und Kettenlauf vor/nach dem Encoding.</p> <p> Encode-Skripte</p> </li> </ul>"},{"location":"pipeline/#zuruck-zum-handbuch","title":"Zur\u00fcck zum Handbuch","text":"<ul> <li>Workflows aus Nutzersicht</li> </ul>"},{"location":"pipeline/encoding/","title":"Encode-Planung & Track-Auswahl","text":"<p>Vor dem eigentlichen Encoding erstellt Ripster einen Encode-Plan und zeigt ihn im Review an.</p>"},{"location":"pipeline/encoding/#ablauf","title":"Ablauf","text":"<pre><code>Quelle bestimmen (Disc/RAW)\n -> HandBrake-Scan (--scan --json)\n -> Plan erstellen (Titel, Audio, Untertitel)\n -> Status: Bereit zum Encodieren\n -> Benutzer bestaetigt Auswahl\n -> finaler HandBrake-Aufruf\n</code></pre>"},{"location":"pipeline/encoding/#review-inhalt-status-bereit-zum-encodieren","title":"Review-Inhalt (Status: <code>Bereit zum Encodieren</code>)","text":"<ul> <li>ausw\u00e4hlbarer Encode-Titel</li> <li>Audio-Track-Auswahl</li> <li>Untertitel-Track-Auswahl inkl. Flags</li> <li><code>burnIn</code></li> <li><code>forced</code></li> <li><code>defaultTrack</code></li> <li>optionale User-Presets (HandBrake Preset + Extra Args)</li> <li>optionale Pre-/Post-Skripte und Ketten</li> </ul>"},{"location":"pipeline/encoding/#bestaetigung-confirm-encode","title":"Bestaetigung (<code>confirm-encode</code>)","text":"<p>Typischer Payload:</p> <pre><code>{\n \"selectedEncodeTitleId\": 1,\n \"selectedTrackSelection\": {\n \"1\": {\n \"audioTrackIds\": [1, 2],\n \"subtitleTrackIds\": [3]\n }\n },\n \"selectedPreEncodeScriptIds\": [1],\n \"selectedPostEncodeScriptIds\": [2],\n \"selectedPreEncodeChainIds\": [3],\n \"selectedPostEncodeChainIds\": [4],\n \"selectedUserPresetId\": 5\n}\n</code></pre> <p>Die best\u00e4tigte Auswahl wird im Job gespeichert und f\u00fcr Neustarts wiederverwendet.</p>"},{"location":"pipeline/encoding/#handbrake-aufruf","title":"HandBrake-Aufruf","text":"<p>Grundstruktur:</p> <pre><code>HandBrakeCLI \\\n -i <input> \\\n -o <output> \\\n -t <titleId> \\\n -Z \"<preset>\" \\\n <extra-args> \\\n -a <audioTrackIds|none> \\\n -s <subtitleTrackIds|none>\n</code></pre> <p>Untertitel-Flags werden bei Bedarf erg\u00e4nzt:</p> <ul> <li><code>--subtitle-burned=<id></code></li> <li><code>--subtitle-default=<id></code></li> <li><code>--subtitle-forced=<id></code> oder <code>--subtitle-forced</code></li> </ul>"},{"location":"pipeline/encoding/#pre-post-encode-ausfuhrungen","title":"Pre-/Post-Encode-Ausf\u00fchrungen","text":"<ul> <li>Pre-Encode l\u00e4uft vor HandBrake</li> <li>Post-Encode l\u00e4uft nach HandBrake</li> </ul> <p>Fehlerverhalten:</p> <ul> <li>Pre-Encode-Fehler: Job endet mit Status <code>Fehler</code> (Encode startet nicht)</li> <li>Post-Encode-Fehler: Job kann <code>Fertig</code> bleiben, enth\u00e4lt aber Fehlerhinweis/Script-Summary</li> </ul>"},{"location":"pipeline/encoding/#dateinamenordner","title":"Dateinamen/Ordner","text":"<p>Der finale Outputpfad wird aus den Templates in den Settings aufgebaut.</p> <p>Platzhalter:</p> <ul> <li><code>${title}</code></li> <li><code>${year}</code></li> <li><code>${imdbId}</code></li> </ul> <p>Ung\u00fcltige Dateizeichen werden bereinigt.</p>"},{"location":"pipeline/playlist-analysis/","title":"Playlist-Analyse","text":"<p>Ripster analysiert bei Blu-ray-\u00e4hnlichen Quellen Playlists und fordert bei Mehrdeutigkeit eine manuelle Auswahl an.</p>"},{"location":"pipeline/playlist-analysis/#ziel","title":"Ziel","text":"<p>Erkennen, welche Playlist sehr wahrscheinlich der Hauptfilm ist, statt versehentlich eine Fake-/Dummy-Playlist zu verwenden.</p>"},{"location":"pipeline/playlist-analysis/#eingabedaten","title":"Eingabedaten","text":"<p>Die Analyse basiert auf MakeMKV-Infos (u. a. Playlist-/Segment-Struktur, Laufzeiten, Titelzuordnung).</p>"},{"location":"pipeline/playlist-analysis/#auswertung-vereinfacht","title":"Auswertung (vereinfacht)","text":"<p>F\u00fcr Kandidaten werden u. a. ber\u00fccksichtigt:</p> <ul> <li>Laufzeit</li> <li>Segment-Reihenfolge</li> <li>R\u00fcckw\u00e4rtsspr\u00fcnge/gro\u00dfe Spr\u00fcnge</li> <li>Koh\u00e4renz linearer Segmentfolgen</li> <li>Duplikatgruppen mit \u00e4hnlicher Laufzeit</li> </ul> <p>Daraus entstehen intern Kandidatenlisten, Bewertungen und eine Empfehlung.</p>"},{"location":"pipeline/playlist-analysis/#wann-muss-der-benutzer-entscheiden","title":"Wann muss der Benutzer entscheiden?","text":"<p>Wenn nach Filterung mehr als ein relevanter Kandidat \u00fcbrig bleibt, wechselt der Job in der GUI auf:</p> <ul> <li><code>Warte auf Auswahl</code></li> </ul> <p>Dann muss eine Playlist best\u00e4tigt werden, bevor der Workflow weiterl\u00e4uft.</p>"},{"location":"pipeline/playlist-analysis/#konfigurationseinfluss","title":"Konfigurationseinfluss","text":"Feld in <code>Settings</code> Wirkung <code>Minimale Titellaenge (Minuten)</code> Mindestlaufzeit f\u00fcr Kandidaten <p>Default ist aktuell <code>60</code> Minuten.</p>"},{"location":"pipeline/playlist-analysis/#ui-verhalten","title":"UI-Verhalten","text":"<p>Bei manueller Entscheidung zeigt das Ripper Kandidaten inkl. Score/Bewertung und markiert eine Empfehlung.</p> <p>Nach Best\u00e4tigung:</p> <ul> <li>mit vorhandenem RAW -> zur\u00fcck zu <code>Mediainfo-Pruefung</code></li> <li>ohne RAW -> Startpfad \u00fcber <code>Startbereit</code> / <code>Rippen</code></li> </ul>"},{"location":"pipeline/post-encode-scripts/","title":"Encode-Skripte (Pre & Post)","text":"<p>Ripster kann Skripte und Skript-Ketten vor und nach dem Encode ausf\u00fchren.</p>"},{"location":"pipeline/post-encode-scripts/#ablauf","title":"Ablauf","text":"<pre><code>Bereit zum Encodieren\n -> Pre-Encode Skripte/Ketten\n -> HandBrake Encoding\n -> Post-Encode Skripte/Ketten\n -> Fertig oder Fehler\n</code></pre>"},{"location":"pipeline/post-encode-scripts/#auswahl-im-review","title":"Auswahl im Review","text":"<p>Im Review-Panel kannst du getrennt w\u00e4hlen:</p> <ul> <li><code>selectedPreEncodeScriptIds</code></li> <li><code>selectedPostEncodeScriptIds</code></li> <li><code>selectedPreEncodeChainIds</code></li> <li><code>selectedPostEncodeChainIds</code></li> </ul>"},{"location":"pipeline/post-encode-scripts/#fehlerverhalten","title":"Fehlerverhalten","text":"<ul> <li>Pre-Encode-Fehler stoppen die Kette und f\u00fchren zu <code>Fehler</code>.</li> <li>Post-Encode-Fehler stoppen die restlichen Post-Schritte; Job kann dennoch <code>Fertig</code> sein (mit Fehlerzusatz im Status/Log).</li> </ul>"},{"location":"pipeline/post-encode-scripts/#verfugbare-umgebungsvariablen","title":"Verf\u00fcgbare Umgebungsvariablen","text":"<p>Beim Script-Run werden gesetzt:</p> <ul> <li><code>RIPSTER_SCRIPT_RUN_AT</code></li> <li><code>RIPSTER_JOB_ID</code></li> <li><code>RIPSTER_JOB_TITLE</code></li> <li><code>RIPSTER_MODE</code></li> <li><code>RIPSTER_INPUT_PATH</code></li> <li><code>RIPSTER_OUTPUT_PATH</code></li> <li><code>RIPSTER_RAW_PATH</code></li> <li><code>RIPSTER_SCRIPT_ID</code></li> <li><code>RIPSTER_SCRIPT_NAME</code></li> <li><code>RIPSTER_SCRIPT_SOURCE</code></li> </ul>"},{"location":"pipeline/post-encode-scripts/#skript-ketten","title":"Skript-Ketten","text":"<p>Ketten unterst\u00fctzen zwei Step-Typen:</p> <ul> <li><code>script</code> (f\u00fchrt ein hinterlegtes Skript aus)</li> <li><code>wait</code> (wartet <code>waitSeconds</code>)</li> </ul> <p>Bei Fehler in einem Script-Step wird die Kette abgebrochen.</p>"},{"location":"pipeline/post-encode-scripts/#testlaufe","title":"Testl\u00e4ufe","text":"<ul> <li>Skript testen: <code>POST /api/settings/scripts/:id/test</code></li> <li>Kette testen: <code>POST /api/settings/script-chains/:id/test</code></li> </ul> <p>Ergebnisse enthalten Erfolg/Exit-Code, Laufzeit und stdout/stderr.</p>"},{"location":"pipeline/workflow/","title":"Workflow & Zust\u00e4nde","text":"<p>Ripster steuert den Ablauf als Zustandsmaschine.</p>"},{"location":"pipeline/workflow/#zustandsdiagramm-vereinfacht","title":"Zustandsdiagramm (vereinfacht)","text":"<pre><code>flowchart LR\n A[Bereit] --> B[Medium erkannt]\n B --> C[Analyse]\n C --> D[Metadatenauswahl]\n D --> E[Startbereit]\n E --> F[Rippen]\n E --> G[Mediainfo-Pruefung]\n G --> H[Warte auf Auswahl]\n H --> G\n G --> I[Bereit zum Encodieren]\n I --> J[Encodieren]\n J --> K[Fertig]\n J --> L[Fehler]\n F --> L\n F --> M[Abgebrochen]</code></pre>"},{"location":"pipeline/workflow/#statusliste-gui-anzeige","title":"Statusliste (GUI-Anzeige)","text":"Status in der GUI Bedeutung <code>Bereit</code> Wartet auf Disc <code>Medium erkannt</code> Disc wurde erkannt <code>Analyse</code> MakeMKV-Analyse l\u00e4uft <code>Metadatenauswahl</code> Metadaten m\u00fcssen best\u00e4tigt werden <code>Warte auf Auswahl</code> Playlist-Auswahl ist erforderlich <code>Warte auf Auswahl</code> auch f\u00fcr RAW-Entscheidung bei vorhandenen Quelldaten genutzt <code>Startbereit</code> kurzer \u00dcbergang vor Start <code>Rippen</code> MakeMKV-Rip l\u00e4uft <code>Mediainfo-Pruefung</code> Titel/Spuren werden ausgewertet <code>Bereit zum Encodieren</code> Review ist bereit <code>Encodieren</code> HandBrake l\u00e4uft <code>CD-Metadatenauswahl</code> CD-Metadaten m\u00fcssen best\u00e4tigt werden <code>CD-Startbereit</code> CD-Job wartet auf Start <code>CD-Analyse</code> CD-Track-/Strukturaufbereitung <code>CD-Rippen</code> Audio-CD-Rip l\u00e4uft <code>CD-Encodieren</code> CD-Encode l\u00e4uft <code>Fertig</code> erfolgreich abgeschlossen <code>Abgebrochen</code> manuell oder technisch abgebrochen <code>Fehler</code> fehlgeschlagen"},{"location":"pipeline/workflow/#typische-pfade","title":"Typische Pfade","text":""},{"location":"pipeline/workflow/#standardfall-kein-vorhandenes-raw","title":"Standardfall (kein vorhandenes RAW)","text":"<ol> <li>Medium erkannt</li> <li>Analyse + Metadaten</li> <li>Rippen</li> <li>Mediainfo-Pruefung</li> <li>Bereit zum Encodieren</li> <li>Encodieren</li> <li>Fertig</li> </ol>"},{"location":"pipeline/workflow/#vorhandenes-raw","title":"Vorhandenes RAW","text":"<p><code>Startbereit</code> springt direkt zu <code>Mediainfo-Pruefung</code> (kein neuer Rip).</p>"},{"location":"pipeline/workflow/#mehrdeutige-blu-ray-playlist","title":"Mehrdeutige Blu-ray-Playlist","text":"<p><code>Mediainfo-Pruefung</code> -> <code>Warte auf Auswahl</code> bis Playlist best\u00e4tigt wurde.</p>"},{"location":"pipeline/workflow/#vorhandenes-raw-mit-nutzerentscheidung","title":"Vorhandenes RAW mit Nutzerentscheidung","text":"<p><code>Metadatenauswahl</code> -> <code>Warte auf Auswahl</code> bis Entscheidung f\u00fcr Weiterverarbeitung (<code>continue</code>) oder Neustart getroffen wurde.</p>"},{"location":"pipeline/workflow/#cd-flow","title":"CD-Flow","text":"<p><code>CD-Metadatenauswahl</code> -> <code>CD-Startbereit</code> -> <code>CD-Rippen</code> -> <code>CD-Encodieren</code> -> <code>Fertig</code></p>"},{"location":"pipeline/workflow/#queue-verhalten","title":"Queue-Verhalten","text":"<p>Wenn der Wert <code>Parallele Jobs</code> erreicht ist:</p> <ul> <li>neue Starts werden als Queue-Eintr\u00e4ge abgelegt</li> <li>die Queue kann zus\u00e4tzlich Nicht-Job-Eintr\u00e4ge enthalten (<code>Skript</code>, <code>Kette</code>, <code>Warten</code>)</li> <li>Reihenfolge ist per UI/API \u00e4nderbar</li> </ul>"},{"location":"pipeline/workflow/#abbruch-wiederaufnahme-neustart","title":"Abbruch, Wiederaufnahme, Neustart","text":"<ul> <li><code>Abbrechen</code>: laufenden Job stoppen oder Queue-Eintrag entfernen</li> <li><code>Retry Rippen</code>: Fehler-/Abbruch-Job erneut starten</li> <li><code>RAW neu encodieren</code>: aus vorhandenem RAW neu encodieren</li> <li><code>Review neu starten</code>: Review aus RAW neu aufbauen</li> <li><code>Encode neu starten</code>: Encoding mit letzter best\u00e4tigter Auswahl neu starten</li> </ul>"},{"location":"tools/","title":"Anhang: Externe Tools","text":"<p>Ripster orchestriert externe CLI-Tools. Dieser Abschnitt erkl\u00e4rt deren Rolle im Gesamtsystem.</p> <ul> <li> <p> MakeMKV</p> <p>Disc-Analyse und Ripping.</p> <p> MakeMKV</p> </li> <li> <p> HandBrake</p> <p>Video-Encoding inklusive Preset-Logik.</p> <p> HandBrake</p> </li> <li> <p> MediaInfo</p> <p>Track-/Containeranalyse f\u00fcr Review und Auswahl.</p> <p> MediaInfo</p> </li> </ul>"},{"location":"tools/handbrake/","title":"HandBrake","text":"<p>Ripster verwendet <code>HandBrakeCLI</code> f\u00fcr Scan und Encode.</p>"},{"location":"tools/handbrake/#verwendete-aufrufe","title":"Verwendete Aufrufe","text":""},{"location":"tools/handbrake/#scan-review-aufbau","title":"Scan (Review-Aufbau)","text":"<pre><code>HandBrakeCLI --scan --json -i <input> -t 0\n</code></pre>"},{"location":"tools/handbrake/#encode-vereinfacht","title":"Encode (vereinfacht)","text":"<pre><code>HandBrakeCLI \\\n -i <input> \\\n -o <output> \\\n -t <titleId> \\\n -Z \"<preset>\" \\\n <extra-args> \\\n -a <audioTrackIds|none> \\\n -s <subtitleTrackIds|none>\n</code></pre> <p>Optional erg\u00e4nzt Ripster:</p> <ul> <li><code>--subtitle-burned=<id></code></li> <li><code>--subtitle-default=<id></code></li> <li><code>--subtitle-forced=<id></code> oder <code>--subtitle-forced</code></li> </ul>"},{"location":"tools/handbrake/#presets-auslesen","title":"Presets auslesen","text":"<p>Ripster liest Presets mit:</p> <pre><code>HandBrakeCLI -z\n</code></pre>"},{"location":"tools/handbrake/#relevante-felder-in-settings","title":"Relevante Felder in <code>Settings</code>","text":"Feldname in der GUI Bedeutung <code>HandBrake Kommando</code> CLI-Binary <code>HandBrake Preset</code> (Blu-ray/DVD) profilspezifisches Preset <code>HandBrake Extra Args</code> (Blu-ray/DVD) profilspezifische Zusatzargumente <code>Ausgabeformat</code> (Blu-ray/DVD) Dateiendung der finalen Datei <code>Encode-Neustart: unvollst\u00e4ndige Ausgabe l\u00f6schen</code> unvollst\u00e4ndige Ausgabe bei Neustart l\u00f6schen"},{"location":"tools/handbrake/#fortschritts-parsing","title":"Fortschritts-Parsing","text":"<p>Ripster parst HandBrake-Stderr (Prozent/ETA/Detail) und sendet WebSocket-Progress (<code>PIPELINE_PROGRESS</code>).</p>"},{"location":"tools/handbrake/#troubleshooting","title":"Troubleshooting","text":"<ul> <li>Preset nicht gefunden: Preset-Namen mit <code>HandBrakeCLI -z</code> pr\u00fcfen</li> <li>sehr langsames Encoding: Preset/Extra-Args pr\u00fcfen (z. B. <code>--encoder-preset</code>)</li> </ul> <p>Das Produktions-Installer-Script <code>install.sh</code> bietet eine Option zur Installation eines geb\u00fcndelten HandBrakeCLI-Binaries mit NVDEC-Unterst\u00fctzung (NVIDIA GPU-Dekodierung). Diese Option erscheint interaktiv w\u00e4hrend der Installation.</p>"},{"location":"tools/makemkv/","title":"MakeMKV","text":"<p>Ripster nutzt <code>makemkvcon</code> f\u00fcr Disc-Analyse und Rip.</p>"},{"location":"tools/makemkv/#verwendete-aufrufe","title":"Verwendete Aufrufe","text":""},{"location":"tools/makemkv/#analyse","title":"Analyse","text":"<pre><code>makemkvcon -r info <source>\n</code></pre> <p><code><source></code> ist typischerweise:</p> <ul> <li><code>disc:<index></code> (Auto-Modus)</li> <li><code>dev:/dev/sr0</code> (explicit)</li> <li><code>file:<path></code> (Datei/Ordner-Analyse)</li> </ul>"},{"location":"tools/makemkv/#rip-mkv-modus","title":"Rip (MKV-Modus)","text":"<pre><code>makemkvcon mkv <source> <title-or-all> <rawDir> [--minlength=...] [...extraArgs]\n</code></pre>"},{"location":"tools/makemkv/#rip-backup-modus","title":"Rip (Backup-Modus)","text":"<pre><code>makemkvcon backup <source> <rawDir> --decrypt\n</code></pre>"},{"location":"tools/makemkv/#registrierungsschlussel-optional","title":"Registrierungsschl\u00fcssel (optional)","text":"<p>Wenn in <code>Settings</code> ein <code>MakeMKV Key</code> gesetzt ist, f\u00fchrt Ripster vor Analyse/Rip aus:</p> <pre><code>makemkvcon reg <key>\n</code></pre>"},{"location":"tools/makemkv/#relevante-felder-in-settings","title":"Relevante Felder in <code>Settings</code>","text":"Feldname in der GUI Bedeutung <code>MakeMKV Kommando</code> CLI-Binary <code>MakeMKV Source Index</code> Source-Index im Auto-Modus <code>Minimale Titellaenge (Minuten)</code> Mindestlaufzeitfilter <code>MakeMKV Rip Modus</code> (Blu-ray/DVD) <code>mkv</code> oder <code>backup</code> <code>MakeMKV Analyze Extra Args</code> (Blu-ray/DVD) Zusatzargumente f\u00fcr Analyse <code>MakeMKV Rip Extra Args</code> (Blu-ray/DVD) Zusatzargumente f\u00fcr Rip"},{"location":"tools/makemkv/#hinweise","title":"Hinweise","text":"<ul> <li>Blu-ray-Backups werden oft f\u00fcr robuste Playlist-Analyse genutzt.</li> <li>MakeMKV-Ausgaben werden geparst und als <code>makemkvInfo</code> im Job gespeichert.</li> </ul>"},{"location":"tools/mediainfo/","title":"MediaInfo","text":"<p>Ripster nutzt <code>mediainfo</code> zur JSON-Analyse von Medien-Dateien.</p>"},{"location":"tools/mediainfo/#aufruf","title":"Aufruf","text":"<pre><code>mediainfo --Output=JSON <input>\n</code></pre> <p>Der Input ist typischerweise eine RAW-Datei oder ein vom Workflow gew\u00e4hlter Inputpfad.</p>"},{"location":"tools/mediainfo/#verwendung-in-ripster","title":"Verwendung in Ripster","text":"<ul> <li>Track-/Codec-Metadaten f\u00fcr Review-Plan</li> <li>Fallback-Informationen in bestimmten Analysepfaden</li> <li>Persistenz als <code>mediainfoInfo</code> im Job</li> </ul>"},{"location":"tools/mediainfo/#relevante-settings","title":"Relevante Settings","text":"Key Bedeutung <code>mediainfo_command</code> CLI-Binary <code>mediainfo_extra_args_bluray</code> / <code>_dvd</code> profilspezifische Zusatzargumente"},{"location":"tools/mediainfo/#troubleshooting","title":"Troubleshooting","text":"<ul> <li>JSON-Test: <code>mediainfo --Output=JSON <datei></code></li> <li>unbekannte Sprache erscheint oft als <code>und</code> (undetermined)</li> </ul>"},{"location":"workflows/","title":"Workflows aus Nutzersicht","text":"<p>Diese Seite beschreibt typische Abl\u00e4ufe mit den passenden UI-Aktionen.</p>"},{"location":"workflows/#workflow-1-standardlauf-disc-fertige-datei","title":"Workflow 1: Standardlauf (Disc -> fertige Datei)","text":"<ol> <li><code>Ripper</code>: Disc einlegen, <code>Analyse starten</code></li> <li>Metadaten im Dialog \u00fcbernehmen</li> <li>bei <code>Bereit zum Encodieren</code> Titel/Tracks pr\u00fcfen</li> <li><code>Encoding starten</code></li> <li>Ergebnis in <code>Historie</code> kontrollieren</li> </ol>"},{"location":"workflows/#workflow-2-playlist-entscheidung-bei-blu-ray","title":"Workflow 2: Playlist-Entscheidung bei Blu-ray","text":"<ol> <li>Job landet in <code>Warte auf Auswahl</code></li> <li>im <code>Pipeline-Status</code> Playlist-Kandidaten vergleichen</li> <li>gew\u00fcnschte Playlist ausw\u00e4hlen</li> <li><code>Playlist \u00fcbernehmen</code></li> <li>danach normal weiter bis <code>Bereit zum Encodieren</code></li> </ol>"},{"location":"workflows/#workflow-3-mehrere-jobs-mit-queue","title":"Workflow 3: Mehrere Jobs mit Queue","text":"<ol> <li>Parallel-Limit in <code>Settings</code> \u00fcber <code>Parallele Jobs</code> setzen</li> <li>neue Jobs starten; \u00fcbersch\u00fcssige Starts gehen in <code>Job Queue</code></li> <li>Reihenfolge per Drag-and-Drop anpassen</li> <li>bei Bedarf Skript/Kette/Warten als Queue-Eintrag erg\u00e4nzen</li> </ol>"},{"location":"workflows/#workflow-4-nachbearbeitung-eines-bestehenden-jobs","title":"Workflow 4: Nachbearbeitung eines bestehenden Jobs","text":"<p>In <code>Historie</code> -> Detaildialog:</p> <ul> <li>Metadaten korrigieren: <code>OMDb neu zuordnen</code></li> <li>gleiche Einstellungen erneut nutzen: <code>Encode neu starten</code></li> <li>Analyse neu aufbauen: <code>Review neu starten</code></li> <li>aus RAW erneut encodieren: <code>RAW neu encodieren</code></li> </ul>"},{"location":"workflows/#workflow-5-automatisierung-mit-skripten-und-cron","title":"Workflow 5: Automatisierung mit Skripten und Cron","text":"<ol> <li><code>Settings</code> -> <code>Scripte</code>: Skripte anlegen und testen</li> <li><code>Settings</code> -> <code>Skriptketten</code>: Ketten bauen und testen</li> <li>im Ripper-Review Pre-/Post-Ausf\u00fchrungen pro Job ausw\u00e4hlen</li> <li><code>Settings</code> -> <code>Cronjobs</code>: zeitgesteuerte Ausf\u00fchrung konfigurieren</li> <li>Status im Ripper (<code>Skript- / Cron-Status</code>) \u00fcberwachen</li> </ol>"},{"location":"workflows/#workflow-6-abbruch-und-recovery","title":"Workflow 6: Abbruch und Recovery","text":""},{"location":"workflows/#fall-a-job-wurde-abgebrochen","title":"Fall A: Job wurde abgebrochen","text":"<ul> <li>im Ripper optional erzeugte RAW/Movie-Datei bereinigen</li> <li>anschlie\u00dfend je nach Ziel: <code>Retry Rippen</code> oder <code>Disk-Analyse neu starten</code></li> </ul>"},{"location":"workflows/#fall-b-job-steht-in-bereit-zum-encodieren-ist-aber-nicht-aktive-session","title":"Fall B: Job steht in <code>Bereit zum Encodieren</code>, ist aber nicht aktive Session","text":"<ul> <li>in <code>Historie</code>: <code>Im Ripper \u00f6ffnen</code></li> <li>im Ripper Review erneut pr\u00fcfen und starten</li> </ul>"},{"location":"workflows/#fall-c-raw-ohne-historieneintrag","title":"Fall C: RAW ohne Historieneintrag","text":"<ul> <li><code>/database</code> \u00f6ffnen</li> <li>Bereich <code>Gefundene RAW-Eintr\u00e4ge</code></li> <li><code>Job anlegen</code></li> </ul>"},{"location":"workflows/#workflow-7-duplikat-als-multipart-movie-zusammenfuhren","title":"Workflow 7: Duplikat als Multipart Movie zusammenf\u00fchren","text":"<ol> <li><code>Ripper</code> -> <code>Metadaten ausw\u00e4hlen</code> f\u00fcr einen Film-Job</li> <li>bei Duplikat-Konflikt <code>Multipart movie</code> w\u00e4hlen</li> <li>Disc-Nummern f\u00fcr bestehenden und neuen Job setzen (jeweils >= 1, unterschiedlich)</li> <li>Metadaten \u00fcbernehmen</li> <li>in <code>Historie</code> den gemeinsamen Multipart-Container pr\u00fcfen</li> </ol>"},{"location":"workflows/#workflow-8-selektives-loschen-in-der-historie","title":"Workflow 8: Selektives L\u00f6schen in der Historie","text":"<ol> <li>in <code>Historie</code> einen Job \u00f6ffnen und <code>Historieneintrag l\u00f6schen</code></li> <li>L\u00f6schvorschau pr\u00fcfen (<code>RAW</code> + Ausgabe)</li> <li>gew\u00fcnschte RAW-/Ausgabepfade gezielt ausw\u00e4hlen</li> <li>passende Aktion ausf\u00fchren (<code>Eintrag + nur RAW</code>, <code>Eintrag + nur ...</code>, <code>Eintrag + RAW + ...</code>)</li> </ol>"},{"location":"workflows/#workflow-9-audiobook-aax-verarbeiten","title":"Workflow 9: Audiobook (AAX) verarbeiten","text":"<ol> <li>Seite <code>Audiobooks</code> \u00f6ffnen</li> <li>AAX-Datei hochladen</li> <li>Job-Konfiguration pr\u00fcfen und starten</li> <li>Fortschritt im Job-Panel verfolgen</li> <li>Ergebnis im <code>Audiobook Output Explorer</code> und in der <code>Historie</code> pr\u00fcfen</li> </ol>"}]} |