Files
ripster/site/search/search_index.json
2026-03-11 14:59:23 +00:00

1 line
129 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>Dashboard</code>, <code>Settings</code>, <code>Historie</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> WebSocket Events</p> <p>Pipeline-, Queue-, Disk-, Settings-, Cron- und Monitoring-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/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> -&gt; <code>success|error</code>)</li> </ul>"},{"location":"api/history/","title":"History API","text":"<p>Endpunkte f\u00fcr Job-Historie, Orphan-Import und L\u00f6schoperationen.</p>"},{"location":"api/history/#get-apihistory","title":"GET /api/history","text":"<p>Liefert Jobs (optionale Filter).</p> <p>Query-Parameter:</p> Parameter Typ Beschreibung <code>status</code> string Filter nach Job-Status <code>search</code> string Suche in Titel-Feldern <p>Beispiel:</p> <pre><code>GET /api/history?status=FINISHED&amp;search=Inception\n</code></pre> <p>Response:</p> <pre><code>{\n \"jobs\": [\n {\n \"id\": 42,\n \"status\": \"FINISHED\",\n \"title\": \"Inception\",\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> alias-artig ebenfalls Prozesslog 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> <p>Response:</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-apihistorydatabase","title":"GET /api/history/database","text":"<p>Debug-Ansicht der DB-Zeilen (angereichert).</p> <p>Response:</p> <pre><code>{\n \"rows\": [\n {\n \"id\": 42,\n \"status\": \"FINISHED\",\n \"rawFolderName\": \"Inception - RAW - job-42\"\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> <p>Response:</p> <pre><code>{\n \"rawDir\": \"/mnt/raw\",\n \"rawDirs\": [\"/mnt/raw\", \"/mnt/raw-bluray\"],\n \"rows\": [\n {\n \"rawPath\": \"/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99\",\n \"folderName\": \"Inception (2010) [tt1375666] - RAW - job-99\",\n \"title\": \"Inception\",\n \"year\": 2010,\n \"imdbId\": \"tt1375666\",\n \"folderJobId\": 99,\n \"entryCount\": 4,\n \"hasBlurayStructure\": true,\n \"lastModifiedAt\": \"2026-03-10T09:00:00.000Z\"\n }\n ]\n}\n</code></pre>"},{"location":"api/history/#post-apihistoryorphan-rawimport","title":"POST /api/history/orphan-raw/import","text":"<p>Importiert RAW-Ordner als FINISHED-Job.</p> <p>Request:</p> <pre><code>{ \"rawPath\": \"/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99\" }\n</code></pre> <p>Response:</p> <pre><code>{\n \"job\": { \"id\": 77, \"status\": \"FINISHED\" },\n \"uiReset\": { \"reset\": true, \"state\": \"IDLE\" }\n}\n</code></pre>"},{"location":"api/history/#post-apihistoryidomdbassign","title":"POST /api/history/:id/omdb/assign","text":"<p>Weist OMDb-/Metadaten nachtr\u00e4glich zu.</p> <p>Request:</p> <pre><code>{\n \"imdbId\": \"tt1375666\",\n \"title\": \"Inception\",\n \"year\": 2010,\n \"poster\": \"https://...\",\n \"fromOmdb\": true\n}\n</code></pre> <p>Response:</p> <pre><code>{ \"job\": { \"id\": 42, \"imdb_id\": \"tt1375666\" } }\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>{ \"target\": \"both\" }\n</code></pre> <p><code>target</code>: <code>raw</code> | <code>movie</code> | <code>both</code></p> <p>Response:</p> <pre><code>{\n \"summary\": {\n \"target\": \"both\",\n \"raw\": { \"attempted\": true, \"deleted\": true, \"filesDeleted\": 12, \"dirsRemoved\": 3, \"reason\": null },\n \"movie\": { \"attempted\": true, \"deleted\": false, \"filesDeleted\": 0, \"dirsRemoved\": 0, \"reason\": \"Movie-Datei/Pfad existiert nicht.\" }\n },\n \"job\": { \"id\": 42 }\n}\n</code></pre>"},{"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>{ \"target\": \"none\" }\n</code></pre> <p><code>target</code>: <code>none</code> | <code>raw</code> | <code>movie</code> | <code>both</code></p> <p>Response:</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}\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>Alle L\u00f6schoperationen sind irreversibel.</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/#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/#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>Response:</p> <pre><code>{ \"job\": { \"id\": 42, \"status\": \"READY_TO_START\" } }\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-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> <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> Playlist-Entscheidung erforderlich <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>FINISHED</code> Abgeschlossen <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-apiactivities","title":"GET <code>/api/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-apiactivitiesidcancel","title":"POST <code>/api/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-apiactivitiesidnext-step","title":"POST <code>/api/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-apiactivitiesclear-recent","title":"POST <code>/api/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/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/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) =&gt; {\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/#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 Dashboard[Dashboard]\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 &lt;--&gt;|HTTP| API\n Browser &lt;--&gt;|WebSocket| WS\n Pipeline --&gt; MakeMKV\n Pipeline --&gt; HandBrake\n Pipeline --&gt; MediaInfo\n API --&gt; DB\n Pipeline --&gt; DB\n Cron --&gt; 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.</p>"},{"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</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> </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> </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</li> <li>HandBrake-Preset-Liste via <code>HandBrakeCLI -z</code></li> <li>MakeMKV-Registration-Command aus <code>makemkv_registration_key</code></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/#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>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</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> </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\npipeline_state\nscripts\nscript_chains\nscript_chain_steps\nuser_presets\ncron_jobs\ncron_run_logs\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>Audit: <code>created_at</code>, <code>updated_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/#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, created_at FROM jobs ORDER BY created_at DESC;\nSELECT key, value FROM settings_values ORDER BY key;\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/#dashboardpagejsx","title":"<code>DashboardPage.jsx</code>","text":"<p>Pipeline-Steuerung:</p> <ul> <li>Status/Progress/ETA</li> <li>Metadaten-Dialog</li> <li>Playlist-Entscheidung</li> <li>Review-Panel</li> <li>Queue-Interaktion (reorder/add/remove)</li> <li>Job-Aktionen (Start/Cancel/Retry/Re-Encode)</li> <li>Hardware-Monitoring-Anzeige</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/#wichtige-komponenten","title":"Wichtige Komponenten","text":"<ul> <li><code>PipelineStatusCard.jsx</code></li> <li><code>MetadataSelectionDialog.jsx</code></li> <li><code>MediaInfoReviewPanel.jsx</code></li> <li><code>JobDetailDialog.jsx</code></li> <li><code>CronJobsTab.jsx</code></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)://&lt;host&gt;/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><code>DISC_DETECTED</code> / <code>DISC_REMOVED</code></li> <li><code>HARDWARE_MONITOR_UPDATE</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 -&gt; PIPELINE_STATE_CHANGED/PIPELINE_PROGRESS -&gt; Frontend-Update\n</code></pre>"},{"location":"architecture/overview/#service-layer","title":"Service-Layer","text":"<pre><code>Route -&gt; Service -&gt; 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><code>DISC_DETECTED</code>, <code>DISC_REMOVED</code></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>ERROR</code> oder <code>CANCELLED</code>) und k\u00f6nnen erneut gestartet werden.</p>"},{"location":"architecture/overview/#cors-runtime-konfig","title":"CORS &amp; 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>Alle Settings liegen in <code>settings_schema</code>/<code>settings_values</code> und werden \u00fcber die UI verwaltet.</p>"},{"location":"configuration/settings-reference/#profil-system","title":"Profil-System","text":"<p>Ripster arbeitet mit Media-Profilen:</p> <ul> <li><code>bluray</code></li> <li><code>dvd</code></li> <li><code>other</code></li> </ul> <p>Viele Tool-/Pfad-Settings existieren als Profil-Varianten (<code>*_bluray</code>, <code>*_dvd</code>, <code>*_other</code>).</p> <p>Wichtig:</p> <ul> <li>F\u00fcr <code>raw_dir</code>, <code>movie_dir</code> und die zugeh\u00f6rigen <code>*_owner</code>-Keys gibt es kein Cross-Profil-Fallback.</li> <li>F\u00fcr viele Tool-Keys werden profilspezifische Varianten bevorzugt.</li> </ul>"},{"location":"configuration/settings-reference/#template-platzhalter","title":"Template-Platzhalter","text":"<p>Datei-/Ordner-Templates unterst\u00fctzen:</p> <ul> <li><code>${title}</code></li> <li><code>${year}</code></li> <li><code>${imdbId}</code></li> </ul> <p>Nicht gesetzte Werte werden zu <code>unknown</code>.</p>"},{"location":"configuration/settings-reference/#kategorie-pfade","title":"Kategorie: Pfade","text":"Key Typ Default <code>raw_dir</code> path <code>data/output/raw</code> <code>raw_dir_bluray</code> path <code>null</code> <code>raw_dir_dvd</code> path <code>null</code> <code>raw_dir_other</code> path <code>null</code> <code>raw_dir_bluray_owner</code> string <code>null</code> <code>raw_dir_dvd_owner</code> string <code>null</code> <code>raw_dir_other_owner</code> string <code>null</code> <code>movie_dir</code> path <code>data/output/movies</code> <code>movie_dir_bluray</code> path <code>null</code> <code>movie_dir_dvd</code> path <code>null</code> <code>movie_dir_other</code> path <code>null</code> <code>movie_dir_bluray_owner</code> string <code>null</code> <code>movie_dir_dvd_owner</code> string <code>null</code> <code>movie_dir_other_owner</code> string <code>null</code> <code>log_dir</code> path <code>data/logs</code>"},{"location":"configuration/settings-reference/#kategorie-laufwerk","title":"Kategorie: Laufwerk","text":"Key Typ Default Hinweis <code>drive_mode</code> select <code>auto</code> <code>auto</code> oder <code>explicit</code> <code>drive_device</code> path <code>/dev/sr0</code> bei <code>explicit</code> relevant <code>makemkv_source_index</code> number <code>0</code> MakeMKV Source-Index <code>disc_poll_interval_ms</code> number <code>4000</code> 1000..60000"},{"location":"configuration/settings-reference/#kategorie-monitoring","title":"Kategorie: Monitoring","text":"Key Typ Default <code>hardware_monitoring_enabled</code> boolean <code>true</code> <code>hardware_monitoring_interval_ms</code> number <code>5000</code>"},{"location":"configuration/settings-reference/#kategorie-tools-global","title":"Kategorie: Tools (global)","text":"Key Typ Default <code>makemkv_command</code> string <code>makemkvcon</code> <code>makemkv_registration_key</code> string <code>null</code> <code>mediainfo_command</code> string <code>mediainfo</code> <code>makemkv_min_length_minutes</code> number <code>60</code> <code>handbrake_command</code> string <code>HandBrakeCLI</code> <code>handbrake_restart_delete_incomplete_output</code> boolean <code>true</code> <code>pipeline_max_parallel_jobs</code> number <code>1</code>"},{"location":"configuration/settings-reference/#blu-ray-spezifisch","title":"Blu-ray-spezifisch","text":"Key Typ Default <code>mediainfo_extra_args_bluray</code> string <code>null</code> <code>makemkv_rip_mode_bluray</code> select <code>backup</code> <code>makemkv_analyze_extra_args_bluray</code> string <code>null</code> <code>makemkv_rip_extra_args_bluray</code> string <code>null</code> <code>handbrake_preset_bluray</code> string <code>H.264 MKV 1080p30</code> <code>handbrake_extra_args_bluray</code> string <code>null</code> <code>output_extension_bluray</code> select <code>mkv</code> <code>filename_template_bluray</code> string <code>${title} (${year})</code> <code>output_folder_template_bluray</code> string <code>null</code>"},{"location":"configuration/settings-reference/#dvd-spezifisch","title":"DVD-spezifisch","text":"Key Typ Default <code>mediainfo_extra_args_dvd</code> string <code>null</code> <code>makemkv_rip_mode_dvd</code> select <code>mkv</code> <code>makemkv_analyze_extra_args_dvd</code> string <code>null</code> <code>makemkv_rip_extra_args_dvd</code> string <code>null</code> <code>handbrake_preset_dvd</code> string <code>H.264 MKV 480p30</code> <code>handbrake_extra_args_dvd</code> string <code>null</code> <code>output_extension_dvd</code> select <code>mkv</code> <code>filename_template_dvd</code> string <code>${title} (${year})</code> <code>output_folder_template_dvd</code> string <code>null</code>"},{"location":"configuration/settings-reference/#kategorie-metadaten","title":"Kategorie: Metadaten","text":"Key Typ Default <code>omdb_api_key</code> string <code>null</code> <code>omdb_default_type</code> select <code>movie</code>"},{"location":"configuration/settings-reference/#kategorie-benachrichtigungen-pushover","title":"Kategorie: Benachrichtigungen (PushOver)","text":"Key Typ Default <code>pushover_enabled</code> boolean <code>false</code> <code>pushover_token</code> string <code>null</code> <code>pushover_user</code> string <code>null</code> <code>pushover_device</code> string <code>null</code> <code>pushover_title_prefix</code> string <code>Ripster</code> <code>pushover_priority</code> number <code>0</code> <code>pushover_timeout_ms</code> number <code>7000</code> <code>pushover_notify_metadata_ready</code> boolean <code>true</code> <code>pushover_notify_rip_started</code> boolean <code>true</code> <code>pushover_notify_encoding_started</code> boolean <code>true</code> <code>pushover_notify_job_finished</code> boolean <code>true</code> <code>pushover_notify_job_error</code> boolean <code>true</code> <code>pushover_notify_job_cancelled</code> boolean <code>true</code> <code>pushover_notify_reencode_started</code> boolean <code>true</code> <code>pushover_notify_reencode_finished</code> boolean <code>true</code>"},{"location":"configuration/settings-reference/#entfernte-legacy-keys","title":"Entfernte Legacy-Keys","text":"<p>Diese Legacy-Keys werden bei Migration entfernt und sollten nicht mehr genutzt werden:</p> <ul> <li><code>makemkv_backup_mode</code></li> <li><code>mediainfo_extra_args</code></li> <li><code>makemkv_rip_mode</code></li> <li><code>makemkv_analyze_extra_args</code></li> <li><code>makemkv_rip_extra_args</code></li> <li><code>handbrake_preset</code></li> <li><code>handbrake_extra_args</code></li> <li><code>output_extension</code></li> <li><code>filename_template</code></li> <li><code>output_folder_template</code></li> <li><code>pushover_notify_disc_detected</code></li> </ul>"},{"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 &gt;= 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> -&gt; <code>http://127.0.0.1:3001</code></li> <li><code>/ws</code> -&gt; <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 auf Debian/Ubuntu ein \u2013 inklusive Node.js, MakeMKV, HandBrake, nginx und systemd-Dienst.</p> <p>Unterst\u00fctzte Systeme: Debian 11/12, Ubuntu 22.04/24.04 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 &lt;branch&gt;</code> <code>main</code> Git-Branch f\u00fcr die Installation <code>--dir &lt;pfad&gt;</code> <code>/opt/ripster</code> Installationsverzeichnis <code>--user &lt;benutzer&gt;</code> <code>ripster</code> Systembenutzer f\u00fcr den Dienst <code>--port &lt;port&gt;</code> <code>3001</code> Backend-Port <code>--host &lt;hostname&gt;</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, Gruppen: <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 DB, pullt, stellt DB wieder her)</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 erhalten)</li> <li>Berechtigungen \u2013 <code>ripster:ripster</code> auf Installationsverzeichnis, <code>600</code> auf <code>.env</code></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://&lt;Maschinen-IP&gt;</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 -&gt; nginx (Reverse Proxy + statisches Frontend)\n -&gt; 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\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>"},{"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> -&gt; Tab <code>Konfiguration</code>","text":"<p>Setze zuerst diese Pflichtwerte:</p> Bereich Wichtige Felder Pfade <code>raw_dir</code>, <code>movie_dir</code>, <code>log_dir</code> Tools <code>makemkv_command</code>, <code>handbrake_command</code>, <code>mediainfo_command</code> Metadaten <code>omdb_api_key</code>, optional <code>omdb_default_type</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_bluray</code> und <code>handbrake_preset_dvd</code></li> <li><code>raw_dir_bluray</code> und <code>raw_dir_dvd</code></li> <li><code>filename_template_bluray</code> und <code>filename_template_dvd</code></li> </ul>"},{"location":"getting-started/configuration/#3-queue-und-monitoring-festlegen","title":"3. Queue und Monitoring festlegen","text":"<ul> <li><code>pipeline_max_parallel_jobs</code> f\u00fcr parallele Jobs</li> <li><code>hardware_monitoring_enabled</code> und Intervall f\u00fcr Live-Metriken im Dashboard</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_enabled</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>Dashboard</code> \u00f6ffnen</li> <li>Disc einlegen</li> <li><code>Analyse starten</code></li> <li>Metadaten \u00fcbernehmen</li> <li>Bis <code>READY_TO_ENCODE</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. <code>install.sh</code> installiert die ben\u00f6tigten Abh\u00e4ngigkeiten automatisch (au\u00dfer du \u00fcberspringst sie mit <code>--no-*</code>).</p>"},{"location":"getting-started/installation/#zielbild-nach-der-installation","title":"Zielbild nach der Installation","text":"<ul> <li>Ripster-Backend als <code>systemd</code>-Dienst</li> <li>Frontend \u00fcber nginx erreichbar</li> <li>UI auf <code>http://&lt;Server-IP&gt;</code></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 wirst du nach dem HandBrake-Modus gefragt:</p> <ul> <li><code>1</code> Standard (<code>apt</code>)</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: <code>http://&lt;Server-IP&gt;</code></li> <li>Ohne nginx (<code>--no-nginx</code>): API auf <code>http://&lt;Server-IP&gt;:3001/api</code></li> </ul>"},{"location":"getting-started/installation/#wichtige-optionen","title":"Wichtige Optionen","text":"Option Zweck <code>--branch &lt;branch&gt;</code> anderen Branch installieren <code>--dir &lt;pfad&gt;</code> Installationsverzeichnis \u00e4ndern <code>--port &lt;port&gt;</code> Backend-Port setzen <code>--host &lt;hostname&gt;</code> Hostname/IP f\u00fcr nginx/CORS <code>--no-makemkv</code> MakeMKV nicht installieren <code>--no-handbrake</code> HandBrake nicht installieren <code>--no-nginx</code> nginx-Konfiguration \u00fcberspringen <code>--reinstall</code> Update einer bestehenden Installation <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><code>Permission denied</code> am Laufwerk: Laufwerksrechte/Gruppen pr\u00fcfen</li> <li>Tools nicht gefunden: <code>makemkvcon</code>, <code>HandBrakeCLI</code>, <code>mediainfo</code> im <code>PATH</code> pr\u00fcfen</li> <li>UI nicht erreichbar: nginx-Status und Port/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 (Debian/Ubuntu)</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 &gt;= 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-dashboard-offnen-und-disc-einlegen","title":"1. Dashboard \u00f6ffnen und Disc einlegen","text":"<p>Erwartung:</p> <ul> <li>Status wechselt auf <code>DISC_DETECTED</code> bzw. <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 Dashboard:</p> <ul> <li><code>Analyse starten</code></li> </ul> <p>Erwartung:</p> <ul> <li>Status <code>ANALYZING</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>RIPPING</code> -&gt; <code>MEDIAINFO_CHECK</code> -&gt; <code>READY_TO_ENCODE</code></li> <li>bei vorhandenem RAW: direkt <code>MEDIAINFO_CHECK</code> -&gt; <code>READY_TO_ENCODE</code></li> <li>bei unklarer Blu-ray-Playlist: <code>WAITING_FOR_USER_DECISION</code> (Playlist ausw\u00e4hlen und \u00fcbernehmen)</li> </ul>"},{"location":"getting-started/quickstart/#5-review-in-ready_to_encode","title":"5. Review in <code>READY_TO_ENCODE</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>ENCODING</code>:</p> <ul> <li>Fortschritt + ETA im Dashboard</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>FINISHED</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> -&gt; <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 drei Hauptseiten in der Navigation plus eine Expert-Seite.</p>"},{"location":"gui/#seitenuberblick","title":"Seiten\u00fcberblick","text":"Seite Zweck Dashboard Live-Betrieb: Pipeline, Queue, Aktivit\u00e4ten, Disc-Infos Settings Konfiguration, Skripte, Ketten, Presets, Cronjobs Historie abgeschlossene/laufende Jobs durchsuchen und nachbearbeiten Database (Expert) tabellarische Rohsicht inkl. Orphan-RAW-Import"},{"location":"gui/#empfohlene-nutzung-im-alltag","title":"Empfohlene Nutzung im Alltag","text":"<ol> <li>Start eines neuen Jobs: <code>Dashboard</code></li> <li>Regeln/Automatisierung anpassen: <code>Settings</code></li> <li>Ergebnisse pr\u00fcfen oder Jobs nachbearbeiten: <code>Historie</code></li> <li>Sonderf\u00e4lle/Recovery: <code>Database</code></li> </ol>"},{"location":"gui/#hinweise-zur-navigation","title":"Hinweise zur Navigation","text":"<ul> <li><code>Dashboard</code>, <code>Settings</code>, <code>Historie</code> sind direkt in der Kopfnavigation.</li> <li><code>Database</code> ist als Expert-Route verf\u00fcgbar: <code>/database</code>.</li> </ul>"},{"location":"gui/dashboard/","title":"Dashboard","text":"<p>Das Dashboard ist die Betriebszentrale f\u00fcr laufende Jobs.</p>"},{"location":"gui/dashboard/#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/dashboard/#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_*</code>)</li> </ul>"},{"location":"gui/dashboard/#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 das aktuell konfigurierte Parallel-Limit (<code>pipeline_max_parallel_jobs</code>).</li> </ul>"},{"location":"gui/dashboard/#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/dashboard/#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>Im aufgeklappten Zustand erscheint die Karte <code>Pipeline-Status</code> mit allen zustandsabh\u00e4ngigen Aktionen.</p>"},{"location":"gui/dashboard/#zustandsabhangige-hauptaktionen","title":"Zustandsabh\u00e4ngige Hauptaktionen","text":"Zustand Typische Aktion <code>DISC_DETECTED</code> / <code>IDLE</code> <code>Analyse starten</code> <code>METADATA_SELECTION</code> <code>Metadaten \u00f6ffnen</code> <code>WAITING_FOR_USER_DECISION</code> Playlist w\u00e4hlen und <code>Playlist \u00fcbernehmen</code> <code>READY_TO_START</code> <code>Job starten</code> <code>READY_TO_ENCODE</code> Tracks/Skripte pr\u00fcfen, dann <code>Encoding starten</code> laufend (<code>ANALYZING</code>/<code>RIPPING</code>/<code>ENCODING</code>) <code>Abbrechen</code> <code>ERROR</code> / <code>CANCELLED</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/dashboard/#titel-spurprufung-ready_to_encode","title":"Titel-/Spurpr\u00fcfung (<code>READY_TO_ENCODE</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/dashboard/#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>"},{"location":"gui/dashboard/#wichtige-dialoge-im-dashboard","title":"Wichtige Dialoge im Dashboard","text":""},{"location":"gui/dashboard/#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>"},{"location":"gui/dashboard/#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/dashboard/#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/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/#1-historie-datenbank","title":"1) <code>Historie &amp; Datenbank</code>","text":"<p>Tabellarische Jobansicht mit:</p> <ul> <li>ID, Poster, Medium, Titel</li> <li>Status</li> <li>Start/Ende</li> </ul> <p>Aktionen im Detaildialog entsprechen weitgehend der Seite <code>Historie</code> (inkl. Re-Encode, Review-Neustart, OMDb-Zuordnung, Dateil\u00f6schung).</p>"},{"location":"gui/database/#2-raw-ohne-historie","title":"2) <code>RAW ohne Historie</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/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>"},{"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>"},{"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/#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/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 Dashboard <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 Dashboard im Review (<code>READY_TO_ENCODE</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 &amp; 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 &amp; Track-Auswahl","text":"<p>Ripster erzeugt vor dem Encode einen <code>encodePlan</code> und l\u00e4sst ihn im Review-Panel best\u00e4tigen.</p>"},{"location":"pipeline/encoding/#ablauf","title":"Ablauf","text":"<pre><code>Quelle bestimmen (Disc/RAW)\n -&gt; HandBrake-Scan (--scan --json)\n -&gt; Plan erstellen (Titel, Audio, Untertitel)\n -&gt; READY_TO_ENCODE\n -&gt; Benutzer best\u00e4tigt Auswahl\n -&gt; finaler HandBrake-Aufruf\n</code></pre>"},{"location":"pipeline/encoding/#review-inhalt-ready_to_encode","title":"Review-Inhalt (<code>READY_TO_ENCODE</code>)","text":"<ul> <li>ausw\u00e4hlbarer Encode-Titel</li> <li>Audio-Track-Selektion</li> <li>Untertitel-Track-Selektion 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/#bestatigung-confirm-encode","title":"Best\u00e4tigung (<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>Ripster speichert die best\u00e4tigte Auswahl in <code>jobs.encode_plan_json</code> und markiert <code>encode_review_confirmed = 1</code>.</p>"},{"location":"pipeline/encoding/#handbrake-aufruf","title":"HandBrake-Aufruf","text":"<p>Grundstruktur:</p> <pre><code>HandBrakeCLI \\\n -i &lt;input&gt; \\\n -o &lt;output&gt; \\\n -t &lt;titleId&gt; \\\n -Z \"&lt;preset&gt;\" \\\n &lt;extra-args&gt; \\\n -a &lt;audioTrackIds|none&gt; \\\n -s &lt;subtitleTrackIds|none&gt;\n</code></pre> <p>Untertitel-Flags werden bei Bedarf erg\u00e4nzt:</p> <ul> <li><code>--subtitle-burned=&lt;id&gt;</code></li> <li><code>--subtitle-default=&lt;id&gt;</code></li> <li><code>--subtitle-forced=&lt;id&gt;</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>Verhalten bei Fehlern:</p> <ul> <li>Pre-Encode-Fehler: Job wird als <code>ERROR</code> beendet (Encode startet nicht)</li> <li>Post-Encode-Fehler: Job kann <code>FINISHED</code> bleiben, enth\u00e4lt aber Fehlerhinweis/Script-Summary</li> </ul>"},{"location":"pipeline/encoding/#dateinamenordner","title":"Dateinamen/Ordner","text":"<p>Der finale Outputpfad wird aus Settings-Templates 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 sanitisiert.</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 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:</p> <ul> <li><code>candidates</code></li> <li><code>evaluatedCandidates</code> (inkl. Score/Label)</li> <li><code>recommendation</code></li> <li><code>manualDecisionRequired</code></li> </ul>"},{"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, setzt Ripster <code>manualDecisionRequired = true</code> und wechselt auf:</p> <ul> <li><code>WAITING_FOR_USER_DECISION</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":"Key Wirkung <code>makemkv_min_length_minutes</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 Dashboard Kandidaten inkl. Score/Bewertung und markiert eine Empfehlung.</p> <p>Nach Best\u00e4tigung:</p> <ul> <li>mit vorhandenem RAW -&gt; zur\u00fcck zu <code>MEDIAINFO_CHECK</code></li> <li>ohne RAW -&gt; Startpfad \u00fcber <code>READY_TO_START</code>/<code>RIPPING</code></li> </ul>"},{"location":"pipeline/post-encode-scripts/","title":"Encode-Skripte (Pre &amp; 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>READY_TO_ENCODE\n -&gt; Pre-Encode Skripte/Ketten\n -&gt; HandBrake Encoding\n -&gt; Post-Encode Skripte/Ketten\n -&gt; FINISHED oder ERROR\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>ERROR</code>.</li> <li>Post-Encode-Fehler stoppen die restlichen Post-Schritte; Job kann dennoch <code>FINISHED</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 &amp; Zust\u00e4nde","text":"<p>Ripster steuert den Ablauf als State-Machine im <code>pipelineService</code>.</p>"},{"location":"pipeline/workflow/#zustandsdiagramm-vereinfacht","title":"Zustandsdiagramm (vereinfacht)","text":"<pre><code>flowchart LR\n IDLE --&gt; DISC_DETECTED\n DISC_DETECTED --&gt; ANALYZING\n ANALYZING --&gt; METADATA_SELECTION\n METADATA_SELECTION --&gt; READY_TO_START\n READY_TO_START --&gt; RIPPING\n READY_TO_START --&gt; MEDIAINFO_CHECK\n MEDIAINFO_CHECK --&gt; WAITING_FOR_USER_DECISION\n WAITING_FOR_USER_DECISION --&gt; MEDIAINFO_CHECK\n MEDIAINFO_CHECK --&gt; READY_TO_ENCODE\n READY_TO_ENCODE --&gt; ENCODING\n ENCODING --&gt; FINISHED\n ENCODING --&gt; ERROR\n RIPPING --&gt; ERROR\n RIPPING --&gt; CANCELLED</code></pre>"},{"location":"pipeline/workflow/#state-liste","title":"State-Liste","text":"State Bedeutung <code>IDLE</code> Wartet auf Disc <code>DISC_DETECTED</code> Disc erkannt <code>ANALYZING</code> MakeMKV-Analyse l\u00e4uft <code>METADATA_SELECTION</code> Benutzer w\u00e4hlt Metadaten <code>WAITING_FOR_USER_DECISION</code> Playlist-Auswahl n\u00f6tig <code>READY_TO_START</code> \u00dcbergangszustand vor Start <code>RIPPING</code> MakeMKV-Rip l\u00e4uft <code>MEDIAINFO_CHECK</code> Quelle/Tracks werden ausgewertet <code>READY_TO_ENCODE</code> Review ist bereit <code>ENCODING</code> HandBrake l\u00e4uft <code>FINISHED</code> erfolgreich abgeschlossen <code>CANCELLED</code> abgebrochen <code>ERROR</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>Disc erkannt</li> <li>Analyse + Metadaten</li> <li><code>RIPPING</code></li> <li><code>MEDIAINFO_CHECK</code></li> <li><code>READY_TO_ENCODE</code></li> <li><code>ENCODING</code></li> <li><code>FINISHED</code></li> </ol>"},{"location":"pipeline/workflow/#vorhandenes-raw","title":"Vorhandenes RAW","text":"<p><code>READY_TO_START</code> springt direkt zu <code>MEDIAINFO_CHECK</code> (kein neuer Rip).</p>"},{"location":"pipeline/workflow/#mehrdeutige-blu-ray-playlist","title":"Mehrdeutige Blu-ray-Playlist","text":"<p><code>MEDIAINFO_CHECK</code> -&gt; <code>WAITING_FOR_USER_DECISION</code> bis Benutzer Playlist best\u00e4tigt.</p>"},{"location":"pipeline/workflow/#queue-verhalten","title":"Queue-Verhalten","text":"<p>Wenn <code>pipeline_max_parallel_jobs</code> erreicht ist:</p> <ul> <li>Job-Aktionen werden als Queue-Eintr\u00e4ge abgelegt</li> <li>Queue kann zus\u00e4tzlich Nicht-Job-Eintr\u00e4ge enthalten (<code>script</code>, <code>chain</code>, <code>wait</code>)</li> <li>Reihenfolge ist per API/UI \u00e4nderbar</li> </ul>"},{"location":"pipeline/workflow/#abbruch-retry-restart","title":"Abbruch, Retry, Restart","text":"<ul> <li><code>cancel</code>: laufenden Job abbrechen oder Queue-Eintrag entfernen</li> <li><code>retry</code>: Fehler-/Abbruch-Job neu starten</li> <li><code>reencode</code>: aus vorhandenem RAW neu encodieren</li> <li><code>restart-review</code>: Review aus RAW neu aufbauen</li> <li><code>restart-encode</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 &lt;input&gt; -t 0\n</code></pre>"},{"location":"tools/handbrake/#encode-vereinfacht","title":"Encode (vereinfacht)","text":"<pre><code>HandBrakeCLI \\\n -i &lt;input&gt; \\\n -o &lt;output&gt; \\\n -t &lt;titleId&gt; \\\n -Z \"&lt;preset&gt;\" \\\n &lt;extra-args&gt; \\\n -a &lt;audioTrackIds|none&gt; \\\n -s &lt;subtitleTrackIds|none&gt;\n</code></pre> <p>Optional erg\u00e4nzt Ripster:</p> <ul> <li><code>--subtitle-burned=&lt;id&gt;</code></li> <li><code>--subtitle-default=&lt;id&gt;</code></li> <li><code>--subtitle-forced=&lt;id&gt;</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-settings","title":"Relevante Settings","text":"Key Bedeutung <code>handbrake_command</code> CLI-Binary <code>handbrake_preset_bluray</code> / <code>handbrake_preset_dvd</code> profilspezifisches Preset <code>handbrake_extra_args_bluray</code> / <code>handbrake_extra_args_dvd</code> profilspezifische Zusatzargumente <code>output_extension_bluray</code> / <code>output_extension_dvd</code> Ausgabeformat <code>handbrake_restart_delete_incomplete_output</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 &lt;source&gt;\n</code></pre> <p><code>&lt;source&gt;</code> ist typischerweise:</p> <ul> <li><code>disc:&lt;index&gt;</code> (Auto-Modus)</li> <li><code>dev:/dev/sr0</code> (explicit)</li> <li><code>file:&lt;path&gt;</code> (Datei/Ordner-Analyse)</li> </ul>"},{"location":"tools/makemkv/#rip-mkv-modus","title":"Rip (MKV-Modus)","text":"<pre><code>makemkvcon mkv &lt;source&gt; &lt;title-or-all&gt; &lt;rawDir&gt; [--minlength=...] [...extraArgs]\n</code></pre>"},{"location":"tools/makemkv/#rip-backup-modus","title":"Rip (Backup-Modus)","text":"<pre><code>makemkvcon backup &lt;source&gt; &lt;rawDir&gt; --decrypt\n</code></pre>"},{"location":"tools/makemkv/#registrierungsschlussel-optional","title":"Registrierungsschl\u00fcssel (optional)","text":"<p>Wenn <code>makemkv_registration_key</code> gesetzt ist, f\u00fchrt Ripster vor Analyse/Rip aus:</p> <pre><code>makemkvcon reg &lt;key&gt;\n</code></pre>"},{"location":"tools/makemkv/#relevante-settings","title":"Relevante Settings","text":"Key Bedeutung <code>makemkv_command</code> CLI-Binary <code>makemkv_source_index</code> Source-Index im Auto-Modus <code>makemkv_min_length_minutes</code> Mindestlaufzeitfilter <code>makemkv_rip_mode_bluray</code> / <code>makemkv_rip_mode_dvd</code> <code>mkv</code> oder <code>backup</code> <code>makemkv_analyze_extra_args_bluray</code> / <code>_dvd</code> Zusatzargs Analyse <code>makemkv_rip_extra_args_bluray</code> / <code>_dvd</code> Zusatzargs 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 &lt;input&gt;\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 &lt;datei&gt;</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 -&gt; fertige Datei)","text":"<ol> <li><code>Dashboard</code>: Disc einlegen, <code>Analyse starten</code></li> <li>Metadaten im Dialog \u00fcbernehmen</li> <li>bei <code>READY_TO_ENCODE</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>WAITING_FOR_USER_DECISION</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>READY_TO_ENCODE</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> setzen (<code>pipeline_max_parallel_jobs</code>)</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> -&gt; 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> -&gt; <code>Scripte</code>: Skripte anlegen und testen</li> <li><code>Settings</code> -&gt; <code>Skriptketten</code>: Ketten bauen und testen</li> <li>im Dashboard-Review Pre-/Post-Ausf\u00fchrungen pro Job ausw\u00e4hlen</li> <li><code>Settings</code> -&gt; <code>Cronjobs</code>: zeitgesteuerte Ausf\u00fchrung konfigurieren</li> <li>Status im Dashboard (<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 Dashboard 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-ready_to_encode-ist-aber-nicht-aktive-session","title":"Fall B: Job steht in <code>READY_TO_ENCODE</code>, ist aber nicht aktive Session","text":"<ul> <li>in <code>Historie</code> oder <code>Database</code>: <code>Im Dashboard \u00f6ffnen</code></li> <li>im Dashboard 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>RAW ohne Historie</code></li> <li><code>Job anlegen</code></li> </ul>"}]}