1 line
101 KiB
JSON
1 line
101 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","text":"<p>Halbautomatische Disc-Ripping-Plattform f\u00fcr DVDs und Blu-rays</p> <ul> <li> <p> Automatisiertes Ripping</p> <p>Disc einlegen \u2013 Ripster erkennt sie automatisch und startet den Analyse-Workflow mit MakeMKV.</p> <p> Workflow verstehen</p> </li> <li> <p> Metadata-Integration</p> <p>Automatische Suche in der OMDb-Datenbank f\u00fcr Filmtitel, Poster und IMDb-IDs.</p> <p> Konfiguration</p> </li> <li> <p> Flexibles Encoding</p> <p>HandBrake-Encoding mit individueller Track-Auswahl f\u00fcr Audio- und Untertitelspuren.</p> <p> Encode-Planung</p> </li> <li> <p> Job-Historie</p> <p>Vollst\u00e4ndiges Audit-Trail aller Ripping-Jobs mit Logs und Re-Encode-Funktion.</p> <p> History API</p> </li> </ul>"},{"location":"#was-ist-ripster","title":"Was ist Ripster?","text":"<p>Ripster ist eine webbasierte Anwendung zur halbautomatischen Digitalisierung von DVDs und Blu-rays. Die Anwendung kombiniert bew\u00e4hrte Open-Source-Tools zu einem durchg\u00e4ngigen, komfortablen Workflow:</p> <pre><code>Disc einlegen \u2192 Erkennung \u2192 Analyse \u2192 Metadaten w\u00e4hlen \u2192 Rippen \u2192 Encodieren \u2192 Fertig\n</code></pre>"},{"location":"#kernfunktionen","title":"Kernfunktionen","text":"Feature Beschreibung Echtzeit-Updates WebSocket-basierte Live-Statusanzeige ohne Reload Intelligente Playlist-Analyse Erkennt Blu-ray Playlist-Verschleierung (Fake-Playlists) Track-Auswahl Individuelle Auswahl von Audio- und Untertitelspuren Orphan-Recovery Import von bereits gerippten Dateien als Jobs PushOver-Benachrichtigungen Mobile Alerts bei Fertigstellung oder Fehlern DB-Korruptions-Recovery Automatische Quarant\u00e4ne bei korrupten SQLite-Dateien Re-Encoding Erneutes Encodieren ohne neu rippen"},{"location":"#technologie-stack","title":"Technologie-Stack","text":"BackendFrontendExterne Tools <ul> <li>Node.js >= 20.19.0 mit Express.js</li> <li>SQLite3 mit automatischen Schema-Migrationen</li> <li>WebSocket (<code>ws</code>) f\u00fcr Echtzeit-Kommunikation</li> <li>Externe CLI-Tools: <code>makemkvcon</code>, <code>HandBrakeCLI</code>, <code>mediainfo</code></li> </ul> <ul> <li>React 18.3.1 mit React Router</li> <li>Vite 5.4.12 als Build-Tool</li> <li>PrimeReact 10.9.2 als UI-Bibliothek</li> <li>WebSocket-Client f\u00fcr Live-Updates</li> </ul> Tool Zweck <code>makemkvcon</code> Disc-Analyse & MKV/Backup-Ripping <code>HandBrakeCLI</code> Video-Encoding <code>mediainfo</code> Track-Informationen aus gerippten Dateien OMDb API Filmmetadaten (Titel, Poster, IMDb-ID)"},{"location":"#schnellstart","title":"Schnellstart","text":"<pre><code># 1. Repository klonen\ngit clone https://github.com/YOUR_GITHUB_USERNAME/ripster.git\ncd ripster\n\n# 2. Starten (Node.js >= 20 erforderlich)\n./start.sh\n\n# 3. Browser \u00f6ffnen\nopen http://localhost:5173\n</code></pre> <p>Erste Schritte</p> <p>Die vollst\u00e4ndige Installationsanleitung mit allen Voraussetzungen findest du unter Erste Schritte.</p>"},{"location":"#pipeline-uberblick","title":"Pipeline-\u00dcberblick","text":"<pre><code>flowchart LR\n IDLE --> DD[DISC_DETECTED]\n DD --> META[METADATA\\nSELECTION]\n META --> RTS[READY_TO\\nSTART]\n RTS -->|Auto-Start| RIP[RIPPING]\n RTS -->|Auto-Start mit RAW| MIC\n RIP --> MIC[MEDIAINFO\\nCHECK]\n MIC -->|Playlist offen (Backup)| WUD[WAITING_FOR\\nUSER_DECISION]\n WUD --> MIC\n MIC --> RTE[READY_TO\\nENCODE]\n RTE --> ENC[ENCODING]\n ENC -->|inkl. Post-Skripte| FIN([FINISHED])\n ENC --> ERR([ERROR])\n RIP --> ERR\n\n style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32\n style ERR fill:#ffebee,stroke:#ef5350,color:#c62828\n style WUD fill:#fff8e1,stroke:#ffa726,color:#e65100\n style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a</code></pre> <p><code>READY_TO_START</code> ist in der Praxis meist ein kurzer \u00dcbergangszustand: der Job wird nach Metadaten-Auswahl automatisch gestartet oder in die Queue eingeplant.</p>"},{"location":"api/","title":"API-Referenz","text":"<p>Ripster bietet eine REST-API f\u00fcr Steuerung/Verwaltung sowie einen WebSocket-Endpunkt f\u00fcr Echtzeit-Updates.</p>"},{"location":"api/#basis-url","title":"Basis-URL","text":"<pre><code>http://localhost:3001\n</code></pre> <p>API-Prefix: <code>/api</code></p> <p>Beispiele:</p> <ul> <li><code>GET /api/health</code></li> <li><code>GET /api/pipeline/state</code></li> </ul>"},{"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/#authentifizierung","title":"Authentifizierung","text":"<p>Es gibt keine eingebaute Authentifizierung. Ripster ist f\u00fcr lokalen Betrieb gedacht.</p>"},{"location":"api/#fehlerformat","title":"Fehlerformat","text":"<p>Fehler werden zentral als JSON geliefert:</p> <pre><code>{\n \"error\": {\n \"message\": \"Job nicht gefunden.\",\n \"statusCode\": 404,\n \"reqId\": \"req_...\",\n \"details\": [\n {\n \"field\": \"name\",\n \"message\": \"Name darf nicht leer sein.\"\n }\n ]\n }\n}\n</code></pre> <p><code>details</code> ist optional (z. B. bei Validierungsfehlern).</p>"},{"location":"api/#haufige-statuscodes","title":"H\u00e4ufige Statuscodes","text":"Code Bedeutung <code>200</code> Erfolg <code>201</code> Ressource erstellt <code>400</code> Ung\u00fcltige Anfrage / Validierungsfehler <code>404</code> Ressource nicht gefunden <code>409</code> Konflikt (z. B. falscher Pipeline-Zustand, Job l\u00e4uft bereits) <code>500</code> Interner Fehler"},{"location":"api/crons/","title":"Cron API","text":"<p>Ripster enth\u00e4lt ein eingebautes Cron-System f\u00fcr Skripte und Skript-Ketten (<code>sourceType: script|chain</code>).</p>"},{"location":"api/crons/#get-apicrons","title":"GET /api/crons","text":"<p>Listet alle Cron-Jobs.</p> <pre><code>{\n \"jobs\": [\n {\n \"id\": 1,\n \"name\": \"Nachtlauf Backup\",\n \"cronExpression\": \"0 2 * * *\",\n \"sourceType\": \"script\",\n \"sourceId\": 3,\n \"sourceName\": \"Backup-Skript\",\n \"enabled\": true,\n \"pushoverEnabled\": true,\n \"lastRunAt\": \"2026-03-10T02:00:00.000Z\",\n \"lastRunStatus\": \"success\",\n \"nextRunAt\": \"2026-03-11T02:00:00.000Z\",\n \"createdAt\": \"2026-03-01T10:00:00.000Z\",\n \"updatedAt\": \"2026-03-10T02:00:05.000Z\"\n }\n ]\n}\n</code></pre>"},{"location":"api/crons/#post-apicrons","title":"POST /api/crons","text":"<p>Erstellt Cron-Job.</p> <pre><code>{\n \"name\": \"Nachtlauf Backup\",\n \"cronExpression\": \"0 2 * * *\",\n \"sourceType\": \"script\",\n \"sourceId\": 3,\n \"enabled\": true,\n \"pushoverEnabled\": true\n}\n</code></pre> <p>Response: <code>201</code> mit <code>{ \"job\": { ... } }</code></p>"},{"location":"api/crons/#get-apicronsid","title":"GET /api/crons/:id","text":"<p>Response:</p> <pre><code>{ \"job\": { \"id\": 1, \"name\": \"...\" } }\n</code></pre>"},{"location":"api/crons/#put-apicronsid","title":"PUT /api/crons/:id","text":"<p>Aktualisiert Cron-Job. Felder wie bei <code>POST</code>.</p> <p>Response:</p> <pre><code>{ \"job\": { ... } }\n</code></pre>"},{"location":"api/crons/#delete-apicronsid","title":"DELETE /api/crons/:id","text":"<p>Response:</p> <pre><code>{ \"removed\": { \"id\": 1, \"name\": \"Nachtlauf Backup\" } }\n</code></pre>"},{"location":"api/crons/#get-apicronsidlogs","title":"GET /api/crons/:id/logs","text":"<p>Liefert Ausf\u00fchrungs-Logs.</p> <p>Query-Parameter:</p> Parameter Typ Default Beschreibung <code>limit</code> number <code>20</code> Anzahl Eintr\u00e4ge, max. <code>100</code> <p>Response:</p> <pre><code>{\n \"logs\": [\n {\n \"id\": 42,\n \"cronJobId\": 1,\n \"startedAt\": \"2026-03-10T02:00:01.000Z\",\n \"finishedAt\": \"2026-03-10T02:00:05.000Z\",\n \"status\": \"success\",\n \"output\": \"Backup abgeschlossen.\",\n \"errorMessage\": null\n }\n ]\n}\n</code></pre> <p><code>status</code>: <code>running</code> | <code>success</code> | <code>error</code></p>"},{"location":"api/crons/#post-apicronsidrun","title":"POST /api/crons/:id/run","text":"<p>Triggert Job manuell (asynchron).</p> <p>Response:</p> <pre><code>{ \"triggered\": true, \"cronJobId\": 1 }\n</code></pre> <p>Wenn Job bereits l\u00e4uft: <code>409</code>.</p>"},{"location":"api/crons/#post-apicronsvalidate-expression","title":"POST /api/crons/validate-expression","text":"<p>Validiert 5-Felder-Cron-Ausdruck und berechnet n\u00e4chsten Lauf.</p> <p>Request:</p> <pre><code>{ \"cronExpression\": \"*/15 * * * *\" }\n</code></pre> <p>G\u00fcltige Response:</p> <pre><code>{\n \"valid\": true,\n \"nextRunAt\": \"2026-03-10T14:15:00.000Z\"\n}\n</code></pre> <p>Ung\u00fcltige Response:</p> <pre><code>{\n \"valid\": false,\n \"error\": \"Cron-Ausdruck muss genau 5 Felder haben (Minute Stunde Tag Monat Wochentag).\",\n \"nextRunAt\": null\n}\n</code></pre>"},{"location":"api/crons/#cron-format","title":"Cron-Format","text":"<p>Ripster unterst\u00fctzt 5 Felder:</p> <pre><code>Minute Stunde Tag Monat Wochentag\n</code></pre> <p>Beispiele:</p> <ul> <li><code>0 2 * * *</code> t\u00e4glich 02:00</li> <li><code>*/15 * * * *</code> alle 15 Minuten</li> <li><code>0 6 * * 1-5</code> Mo-Fr 06:00</li> </ul>"},{"location":"api/crons/#websocket-events-zu-cron","title":"WebSocket-Events zu Cron","text":"<ul> <li><code>CRON_JOBS_UPDATED</code> bei Create/Update/Delete</li> <li><code>CRON_JOB_UPDATED</code> bei Laufzeitstatus (<code>running</code> -> <code>success|error</code>)</li> </ul>"},{"location":"api/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&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/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) => {\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/#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":"architecture/","title":"Architektur","text":"<p>Ripster ist eine Client-Server-Anwendung mit REST + WebSocket.</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 <-->|HTTP| API\n Browser <-->|WebSocket| WS\n Pipeline --> MakeMKV\n Pipeline --> HandBrake\n Pipeline --> MediaInfo\n API --> DB\n Pipeline --> DB\n Cron --> DB</code></pre>"},{"location":"architecture/#schichten","title":"Schichten","text":""},{"location":"architecture/#backend","title":"Backend","text":"<ul> <li><code>src/index.js</code> (Bootstrapping, Routes, WS, Services)</li> <li><code>src/routes/*</code> (Pipeline, Settings, History, Crons)</li> <li><code>src/services/*</code> (Business-Logik)</li> <li><code>src/db/database.js</code> (Init/Migration)</li> <li><code>src/utils/*</code> (Parser, Dateifunktionen, Validierung)</li> </ul>"},{"location":"architecture/#frontend","title":"Frontend","text":"<ul> <li><code>App.jsx</code> + <code>pages/*</code> (Dashboard, Settings, History)</li> <li><code>components/*</code> (Status-/Review-/Dialog-Komponenten)</li> <li><code>api/client.js</code> (REST-Client)</li> <li><code>hooks/useWebSocket.js</code> (WS-Reconnect)</li> </ul>"},{"location":"architecture/#weiterfuhrend","title":"Weiterf\u00fchrend","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/#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)://<host>/ws</code></li> <li>Auto-Reconnect mit 1500ms Intervall</li> </ul> <p>In <code>App.jsx</code> werden u. a. verarbeitet:</p> <ul> <li><code>PIPELINE_STATE_CHANGED</code></li> <li><code>PIPELINE_PROGRESS</code></li> <li><code>PIPELINE_QUEUE_CHANGED</code></li> <li><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 -> PIPELINE_STATE_CHANGED/PIPELINE_PROGRESS -> Frontend-Update\n</code></pre>"},{"location":"architecture/overview/#service-layer","title":"Service-Layer","text":"<pre><code>Route -> Service -> DB/Tool-Execution\n</code></pre> <p>Routes enthalten kaum Business-Logik.</p>"},{"location":"architecture/overview/#schema-getriebene-settings","title":"Schema-getriebene Settings","text":"<p>Settings sind DB-schema-getrieben (<code>settings_schema</code> + <code>settings_values</code>), UI rendert dynamisch aus diesen Daten.</p>"},{"location":"architecture/overview/#echtzeit-kommunikation","title":"Echtzeit-Kommunikation","text":"<p>WebSocket l\u00e4uft auf <code>/ws</code>.</p> <p>Wichtige Events:</p> <ul> <li><code>PIPELINE_STATE_CHANGED</code>, <code>PIPELINE_PROGRESS</code>, <code>PIPELINE_QUEUE_CHANGED</code></li> <li><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 & 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":"Konfiguration","text":"<ul> <li> <p> Einstellungsreferenz</p> <p>Alle verf\u00fcgbaren Einstellungen mit Typen, Standardwerten und Beschreibungen.</p> <p> Einstellungsreferenz</p> </li> <li> <p> Umgebungsvariablen</p> <p>Umgebungsvariablen f\u00fcr Backend und Frontend.</p> <p> Umgebungsvariablen</p> </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":"Deployment","text":"<ul> <li> <p> Entwicklungsumgebung</p> <p>Lokale Entwicklungsumgebung einrichten.</p> <p> Entwicklung</p> </li> <li> <p> Produktion</p> <p>Ripster auf einem Server dauerhaft betreiben.</p> <p> Produktion</p> </li> </ul>"},{"location":"deployment/development/","title":"Entwicklungsumgebung","text":""},{"location":"deployment/development/#voraussetzungen","title":"Voraussetzungen","text":"<ul> <li>Node.js >= 20.19.0</li> <li>externe Tools installiert (<code>makemkvcon</code>, <code>HandBrakeCLI</code>, <code>mediainfo</code>)</li> </ul>"},{"location":"deployment/development/#schnellstart","title":"Schnellstart","text":"<pre><code>./start.sh\n</code></pre> <p>Startet:</p> <ul> <li>Backend (<code>http://localhost:3001</code>, mit nodemon)</li> <li>Frontend (<code>http://localhost:5173</code>, mit Vite HMR)</li> </ul> <p>Stoppen: <code>Ctrl+C</code>.</p>"},{"location":"deployment/development/#manuell","title":"Manuell","text":""},{"location":"deployment/development/#backend","title":"Backend","text":"<pre><code>cd backend\nnpm install\nnpm run dev\n</code></pre>"},{"location":"deployment/development/#frontend","title":"Frontend","text":"<pre><code>cd frontend\nnpm install\nnpm run dev\n</code></pre>"},{"location":"deployment/development/#vite-proxy-dev","title":"Vite-Proxy (Dev)","text":"<p><code>frontend/vite.config.js</code> proxied standardm\u00e4\u00dfig:</p> <ul> <li><code>/api</code> -> <code>http://127.0.0.1:3001</code></li> <li><code>/ws</code> -> <code>ws://127.0.0.1:3001</code></li> </ul>"},{"location":"deployment/development/#remote-dev-optional","title":"Remote-Dev (optional)","text":"<p>Beispiel <code>frontend/.env.local</code>:</p> <pre><code>VITE_API_BASE=http://192.168.1.100:3001/api\nVITE_WS_URL=ws://192.168.1.100:3001/ws\nVITE_PUBLIC_ORIGIN=http://192.168.1.100:5173\nVITE_ALLOWED_HOSTS=192.168.1.100,ripster.local\nVITE_HMR_PROTOCOL=ws\nVITE_HMR_HOST=192.168.1.100\nVITE_HMR_CLIENT_PORT=5173\n</code></pre>"},{"location":"deployment/development/#nutzliche-kommandos","title":"N\u00fctzliche Kommandos","text":"<pre><code># Root dev (backend + frontend)\nnpm run dev\n\n# einzeln\nnpm run dev:backend\nnpm run dev:frontend\n\n# Frontend Build\nnpm run build:frontend\n</code></pre>"},{"location":"deployment/development/#deploy-script-optional","title":"Deploy-Script (optional)","text":"<p><code>deploy-ripster.sh</code> synchronisiert den lokalen Stand auf einen Remote-Host per <code>rsync</code>/SSH und sch\u00fctzt <code>backend/data</code>.</p>"},{"location":"deployment/production/","title":"Produktions-Deployment","text":""},{"location":"deployment/production/#empfohlene-architektur","title":"Empfohlene Architektur","text":"<pre><code>Client\n -> nginx (Reverse Proxy + statisches Frontend)\n -> Backend API/WebSocket (Node.js, Port 3001)\n</code></pre> <p>Wichtig: Das Backend serviert im aktuellen Stand keine <code>frontend/dist</code>-Dateien automatisch.</p>"},{"location":"deployment/production/#1-frontend-builden","title":"1) Frontend builden","text":"<pre><code>cd frontend\nnpm install\nnpm run build\n</code></pre> <p>Artefakte liegen in <code>frontend/dist/</code>.</p>"},{"location":"deployment/production/#2-backend-als-systemd-service","title":"2) Backend als systemd-Service","text":"<p>Beispiel <code>/etc/systemd/system/ripster-backend.service</code>:</p> <pre><code>[Unit]\nDescription=Ripster Backend\nAfter=network.target\n\n[Service]\nType=simple\nUser=ripster\nWorkingDirectory=/opt/ripster/backend\nExecStart=/usr/bin/env node src/index.js\nRestart=on-failure\nRestartSec=5\nEnvironment=NODE_ENV=production\nEnvironment=PORT=3001\nEnvironment=LOG_LEVEL=info\n\n[Install]\nWantedBy=multi-user.target\n</code></pre> <p>Aktivieren:</p> <pre><code>sudo systemctl daemon-reload\nsudo systemctl enable --now ripster-backend\nsudo systemctl status ripster-backend\n</code></pre>"},{"location":"deployment/production/#3-nginx-konfigurieren","title":"3) nginx konfigurieren","text":"<p>Beispiel <code>/etc/nginx/sites-available/ripster</code>:</p> <pre><code>server {\n listen 80;\n server_name ripster.local;\n\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":"Erste Schritte","text":"<p>Dieser Abschnitt f\u00fchrt dich durch die Installation und Einrichtung von Ripster.</p>"},{"location":"getting-started/#uberblick","title":"\u00dcberblick","text":"<ul> <li> <p>:material-list-check: Voraussetzungen</p> <p>Systemanforderungen und externe Tools, die vor der Installation ben\u00f6tigt werden.</p> <p> Voraussetzungen pr\u00fcfen</p> </li> <li> <p> Installation</p> <p>Schritt-f\u00fcr-Schritt-Anleitung zur Installation von Ripster.</p> <p> Installation starten</p> </li> <li> <p> Konfiguration</p> <p>Einrichten von Pfaden, API-Keys und Encoding-Presets.</p> <p> Konfigurieren</p> </li> <li> <p> Schnellstart</p> <p>Rippe deinen ersten Film in wenigen Minuten.</p> <p> Loslegen</p> </li> </ul>"},{"location":"getting-started/configuration/","title":"Konfiguration","text":"<p>Die Hauptkonfiguration erfolgt \u00fcber die UI (<code>Settings</code>) und wird in SQLite gespeichert.</p>"},{"location":"getting-started/configuration/#pflichteinstellungen-vor-dem-ersten-rip","title":"Pflichteinstellungen vor dem ersten Rip","text":""},{"location":"getting-started/configuration/#1-pfade","title":"1) Pfade","text":"Einstellung Beschreibung Beispiel <code>raw_dir</code> Basisverzeichnis f\u00fcr RAW-Rips <code>/mnt/ripster/raw</code> <code>movie_dir</code> Basisverzeichnis f\u00fcr finale Encodes <code>/mnt/ripster/movies</code> <code>log_dir</code> Verzeichnis f\u00fcr Prozess-/Backend-Logs <code>/mnt/ripster/logs</code> <p>Optional profilspezifisch:</p> <ul> <li><code>raw_dir_bluray</code>, <code>raw_dir_dvd</code>, <code>raw_dir_other</code></li> <li><code>movie_dir_bluray</code>, <code>movie_dir_dvd</code>, <code>movie_dir_other</code></li> </ul>"},{"location":"getting-started/configuration/#2-tools","title":"2) Tools","text":"Einstellung Standard <code>makemkv_command</code> <code>makemkvcon</code> <code>handbrake_command</code> <code>HandBrakeCLI</code> <code>mediainfo_command</code> <code>mediainfo</code>"},{"location":"getting-started/configuration/#3-omdb","title":"3) OMDb","text":"Einstellung Beschreibung <code>omdb_api_key</code> API-Key von omdbapi.com <code>omdb_default_type</code> <code>movie</code>, <code>series</code>, <code>episode</code>"},{"location":"getting-started/configuration/#encode-konfiguration-wichtig","title":"Encode-Konfiguration (wichtig)","text":"<p>Ripster arbeitet profilspezifisch, typischerweise \u00fcber:</p> <ul> <li>Blu-ray: <code>handbrake_preset_bluray</code>, <code>handbrake_extra_args_bluray</code>, <code>output_extension_bluray</code>, <code>filename_template_bluray</code></li> <li>DVD: <code>handbrake_preset_dvd</code>, <code>handbrake_extra_args_dvd</code>, <code>output_extension_dvd</code>, <code>filename_template_dvd</code></li> </ul>"},{"location":"getting-started/configuration/#template-platzhalter","title":"Template-Platzhalter","text":"<p>Verf\u00fcgbar in <code>filename_template_*</code> und <code>output_folder_template_*</code>:</p> <ul> <li><code>${title}</code></li> <li><code>${year}</code></li> <li><code>${imdbId}</code></li> </ul> <p>Beispiel:</p> <pre><code>${title} (${year})\n-> Inception (2010).mkv\n</code></pre>"},{"location":"getting-started/configuration/#makemkv-spezifisch","title":"MakeMKV-spezifisch","text":"Einstellung Standard Hinweis <code>makemkv_min_length_minutes</code> <code>60</code> Kandidaten-Filter <code>makemkv_rip_mode_bluray</code> <code>backup</code> <code>mkv</code> oder <code>backup</code> <code>makemkv_rip_mode_dvd</code> <code>mkv</code> <code>mkv</code> oder <code>backup</code> <code>makemkv_registration_key</code> leer optional, wird via <code>makemkvcon reg</code> gesetzt"},{"location":"getting-started/configuration/#monitoring-queue","title":"Monitoring & Queue","text":"Einstellung Standard <code>hardware_monitoring_enabled</code> <code>true</code> <code>hardware_monitoring_interval_ms</code> <code>5000</code> <code>pipeline_max_parallel_jobs</code> <code>1</code>"},{"location":"getting-started/configuration/#pushover-optional","title":"PushOver (optional)","text":"<p>Basis:</p> <ul> <li><code>pushover_enabled</code></li> <li><code>pushover_token</code></li> <li><code>pushover_user</code></li> </ul> <p>Zus\u00e4tzlich pro Event ein/aus (z. B. <code>pushover_notify_job_finished</code>).</p>"},{"location":"getting-started/configuration/#verwandte-doku","title":"Verwandte Doku","text":"<ul> <li>Einstellungsreferenz</li> <li>Umgebungsvariablen</li> </ul>"},{"location":"getting-started/installation/","title":"Installation","text":""},{"location":"getting-started/installation/#repository-klonen","title":"Repository klonen","text":"<pre><code>git clone https://github.com/YOUR_GITHUB_USERNAME/ripster.git\ncd ripster\n</code></pre>"},{"location":"getting-started/installation/#dev-start-empfohlen","title":"Dev-Start (empfohlen)","text":"<pre><code>./start.sh\n</code></pre> <p><code>start.sh</code>:</p> <ol> <li>pr\u00fcft Node-Version (<code>>= 20.19.0</code>)</li> <li>installiert Dependencies (Root/Backend/Frontend)</li> <li>startet Backend + Frontend parallel</li> </ol> <p>Danach:</p> <ul> <li>Backend: <code>http://localhost:3001</code></li> <li>Frontend: <code>http://localhost:5173</code></li> </ul> <p>Stoppen: mit <code>Ctrl+C</code> im laufenden Terminal.</p>"},{"location":"getting-started/installation/#manuell-starten","title":"Manuell starten","text":"<pre><code>npm install\nnpm --prefix backend install\nnpm --prefix frontend install\nnpm run dev\n</code></pre> <p>Oder getrennt:</p> <pre><code>npm run dev:backend\nnpm run dev:frontend\n</code></pre>"},{"location":"getting-started/installation/#optional-env-dateien-anlegen","title":"Optional: .env-Dateien anlegen","text":""},{"location":"getting-started/installation/#backend","title":"Backend","text":"<pre><code>cp backend/.env.example backend/.env\n</code></pre> <p>Beispiel:</p> <pre><code>PORT=3001\nDB_PATH=./data/ripster.db\nLOG_DIR=./logs\nCORS_ORIGIN=http://localhost:5173\nLOG_LEVEL=info\n</code></pre>"},{"location":"getting-started/installation/#frontend","title":"Frontend","text":"<pre><code>cp frontend/.env.example frontend/.env\n</code></pre> <p>Beispiel:</p> <pre><code>VITE_API_BASE=/api\n# optional:\n# VITE_WS_URL=ws://localhost:3001/ws\n</code></pre>"},{"location":"getting-started/installation/#datenbank","title":"Datenbank","text":"<p>SQLite wird automatisch beim Backend-Start initialisiert:</p> <pre><code>backend/data/ripster.db\n</code></pre> <p>Schema-Quelle: <code>db/schema.sql</code></p>"},{"location":"getting-started/installation/#nachste-schritte","title":"N\u00e4chste Schritte","text":"<ol> <li>Browser \u00f6ffnen: <code>http://localhost:5173</code></li> <li>In <code>Settings</code> Pfade/Tools/API-Keys pr\u00fcfen</li> <li>Erste Disc einlegen und Workflow starten</li> </ol>"},{"location":"getting-started/prerequisites/","title":"Voraussetzungen","text":"<p>Bevor du Ripster installierst, stelle sicher, dass folgende Software auf deinem System verf\u00fcgbar ist.</p>"},{"location":"getting-started/prerequisites/#system-anforderungen","title":"System-Anforderungen","text":"Anforderung Mindestversion Empfohlen Betriebssystem Linux / macOS Ubuntu 22.04+ Node.js 20.19.0 20.x LTS RAM 4 GB 8 GB+ Festplatte 50 GB frei 500 GB+ (f\u00fcr Roh-MKVs)"},{"location":"getting-started/prerequisites/#nodejs","title":"Node.js","text":"<p>Ripster ben\u00f6tigt Node.js >= 20.19.0.</p> nvm (empfohlen)Ubuntu/DebianmacOS (Homebrew) <pre><code># nvm installieren\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash\n\n# Node.js 20 installieren\nnvm install 20\nnvm use 20\n\n# Version pr\u00fcfen\nnode --version # v20.x.x\n</code></pre> <pre><code>curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\nsudo apt-get install -y nodejs\n\nnode --version # v20.x.x\n</code></pre> <pre><code>brew install node@20\nnode --version # v20.x.x\n</code></pre>"},{"location":"getting-started/prerequisites/#externe-tools","title":"Externe Tools","text":""},{"location":"getting-started/prerequisites/#makemkv","title":"MakeMKV","text":"<p>Lizenz erforderlich</p> <p>MakeMKV ist f\u00fcr den pers\u00f6nlichen Gebrauch kostenlos (Beta-Lizenz), ben\u00f6tigt aber eine g\u00fcltige Lizenz.</p> <pre><code># Ubuntu/Debian - PPA verwenden\nsudo add-apt-repository ppa:heyarje/makemkv-beta\nsudo apt-get update\nsudo apt-get install makemkv-bin makemkv-oss\n\n# Installierte Version pr\u00fcfen\nmakemkvcon --version\n</code></pre> <p> MakeMKV Download</p>"},{"location":"getting-started/prerequisites/#handbrake-cli","title":"HandBrake CLI","text":"<pre><code># Ubuntu/Debian\nsudo add-apt-repository ppa:stebbins/handbrake-releases\nsudo apt-get update\nsudo apt-get install handbrake-cli\n\n# Version pr\u00fcfen\nHandBrakeCLI --version\n\n# macOS\nbrew install handbrake\n</code></pre> <p> HandBrake Download</p>"},{"location":"getting-started/prerequisites/#mediainfo","title":"MediaInfo","text":"<pre><code># Ubuntu/Debian\nsudo apt-get install mediainfo\n\n# macOS\nbrew install mediainfo\n\n# Version pr\u00fcfen\nmediainfo --Version\n</code></pre>"},{"location":"getting-started/prerequisites/#disc-laufwerk","title":"Disc-Laufwerk","text":"<p>Ripster ben\u00f6tigt ein physisches DVD- oder Blu-ray-Laufwerk.</p> <p>LibDriveIO-Modus erforderlich</p> <p>Das Laufwerk muss im LibDriveIO-Modus betrieben werden \u2013 MakeMKV greift direkt auf Rohdaten des Laufwerks zu. Ohne diesen Modus k\u00f6nnen verschl\u00fcsselte Blu-rays (insbesondere UHD) nicht gelesen werden.</p> <p>Nicht alle Laufwerke unterst\u00fctzen den direkten Zugriff. Eine Anleitung zur Einrichtung und Liste kompatibler Laufwerke findet sich im MakeMKV-Forum.</p> <pre><code># Laufwerk pr\u00fcfen\nls /dev/sr*\n# oder\nlsblk | grep rom\n\n# Laufwerk-Berechtigungen setzen (erforderlich f\u00fcr LibDriveIO)\nsudo chmod a+rw /dev/sr0\n</code></pre> <p>Blu-ray unter Linux</p> <p>MakeMKV bringt mit LibDriveIO eine eigene Entschl\u00fcsselung mit \u2013 externe Bibliotheken wie <code>libaacs</code> sind in der Regel nicht erforderlich.</p>"},{"location":"getting-started/prerequisites/#omdb-api-key","title":"OMDb API-Key","text":"<p>Ripster verwendet die OMDb API f\u00fcr Filmmetadaten.</p> <ol> <li>Registriere dich kostenlos auf omdbapi.com</li> <li>Best\u00e4tige deine E-Mail-Adresse</li> <li>Notiere deinen API-Key \u2013 du gibst ihn sp\u00e4ter in den Einstellungen ein</li> </ol>"},{"location":"getting-started/prerequisites/#optionale-voraussetzungen","title":"Optionale Voraussetzungen","text":""},{"location":"getting-started/prerequisites/#pushover-benachrichtigungen","title":"PushOver (Benachrichtigungen)","text":"<p>F\u00fcr mobile Push-Benachrichtigungen bei Fertigstellung oder Fehlern:</p> <ul> <li>App kaufen auf pushover.net (~5 USD einmalig)</li> <li>User Key und API Token notieren</li> </ul>"},{"location":"getting-started/prerequisites/#ssh-zugang-deployment","title":"SSH-Zugang (Deployment)","text":"<p>F\u00fcr Remote-Deployment via <code>deploy-ripster.sh</code>:</p> <pre><code># sshpass installieren\nsudo apt-get install sshpass\n</code></pre>"},{"location":"getting-started/prerequisites/#checkliste","title":"Checkliste","text":"<ul> <li>[ ] Node.js >= 20.19.0 installiert (<code>node --version</code>)</li> <li>[ ] <code>makemkvcon</code> installiert (<code>makemkvcon --version</code>)</li> <li>[ ] <code>HandBrakeCLI</code> installiert (<code>HandBrakeCLI --version</code>)</li> <li>[ ] <code>mediainfo</code> installiert (<code>mediainfo --Version</code>)</li> <li>[ ] DVD/Blu-ray Laufwerk vorhanden (<code>ls /dev/sr*</code>)</li> <li>[ ] OMDb API-Key beschafft</li> </ul>"},{"location":"getting-started/quickstart/","title":"Schnellstart \u2013 Erster kompletter Job","text":"<p>Diese Seite f\u00fchrt durch den typischen ersten Lauf.</p>"},{"location":"getting-started/quickstart/#1-starten","title":"1) Starten","text":"<pre><code>cd ripster\n./start.sh\n</code></pre> <p>\u00d6ffne <code>http://localhost:5173</code>.</p>"},{"location":"getting-started/quickstart/#2-disc-einlegen","title":"2) Disc einlegen","text":"<p>Pipeline wechselt auf <code>DISC_DETECTED</code>.</p> <p>Falls n\u00f6tig manuell neu scannen:</p> <pre><code>curl -X POST http://localhost:3001/api/pipeline/rescan-disc\n</code></pre>"},{"location":"getting-started/quickstart/#3-analyse-starten","title":"3) Analyse starten","text":"<p>Klicke im Dashboard auf <code>Analyse starten</code>.</p> <p>Intern:</p> <ul> <li>Job wird angelegt</li> <li>MakeMKV-Analyse l\u00e4uft (<code>ANALYZING</code>)</li> <li>UI wechselt in Metadatenauswahl (<code>METADATA_SELECTION</code>)</li> </ul>"},{"location":"getting-started/quickstart/#4-metadaten-bestatigen","title":"4) Metadaten best\u00e4tigen","text":"<p>Im Dialog:</p> <ul> <li>OMDb-Ergebnis w\u00e4hlen oder manuell eintragen</li> <li>bei Playlist-Abfrage ggf. <code>selectedPlaylist</code> w\u00e4hlen</li> </ul> <p>Nach Best\u00e4tigung startet Ripster automatisch weiter.</p>"},{"location":"getting-started/quickstart/#5-pipeline-pfade","title":"5) Pipeline-Pfade","text":"<p>Abh\u00e4ngig von Job/RAW-Situation:</p> <ul> <li>kein RAW vorhanden -> <code>RIPPING</code></li> <li>RAW vorhanden -> <code>MEDIAINFO_CHECK</code></li> <li>mehrdeutige Playlist -> <code>WAITING_FOR_USER_DECISION</code></li> </ul> <p>Wenn Parallel-Limit erreicht ist, wird der Job in die Queue eingereiht.</p>"},{"location":"getting-started/quickstart/#6-review-ready_to_encode","title":"6) Review (<code>READY_TO_ENCODE</code>)","text":"<p>Im Review-Panel:</p> <ul> <li>Titel ausw\u00e4hlen (falls mehrere)</li> <li>Audio-/Subtitle-Tracks ausw\u00e4hlen</li> <li>optional User-Preset anwenden</li> <li>optional Pre-/Post-Skripte und Ketten hinzuf\u00fcgen</li> </ul> <p>Mit <code>Encoding starten</code> wird <code>confirm-encode</code> + Start ausgel\u00f6st.</p>"},{"location":"getting-started/quickstart/#7-encoding-encoding","title":"7) Encoding (<code>ENCODING</code>)","text":"<p>W\u00e4hrend Encoding:</p> <ul> <li>Live-Fortschritt/ETA \u00fcber WebSocket</li> <li>Pre-Encode-Ausf\u00fchrungen laufen vor HandBrake</li> <li>Post-Encode-Ausf\u00fchrungen laufen nach HandBrake</li> </ul> <p>Wichtig:</p> <ul> <li>Pre-Encode-Fehler -> Job endet in <code>ERROR</code></li> <li>Post-Encode-Fehler -> Job kann <code>FINISHED</code> bleiben, aber mit Fehlerhinweis im Status/Log</li> </ul>"},{"location":"getting-started/quickstart/#8-abschluss-finished","title":"8) Abschluss (<code>FINISHED</code>)","text":"<p>Ergebnis:</p> <ul> <li>Ausgabe in <code>movie_dir</code> (ggf. profilspezifisch)</li> <li>Job in Historie sichtbar</li> <li>Logs im konfigurierten <code>log_dir</code></li> </ul>"},{"location":"getting-started/quickstart/#nutzliche-api-shortcuts","title":"N\u00fctzliche API-Shortcuts","text":"<pre><code># Pipeline-Snapshot\ncurl http://localhost:3001/api/pipeline/state\n\n# Queue-Snapshot\ncurl http://localhost:3001/api/pipeline/queue\n\n# Jobs\ncurl http://localhost:3001/api/history\n</code></pre>"},{"location":"pipeline/","title":"Pipeline","text":"<p>Der Pipeline-Bereich beschreibt den Kern-Workflow von Ripster.</p> <ul> <li> <p> Workflow & Zust\u00e4nde</p> <p>Zust\u00e4nde, \u00dcberg\u00e4nge und Queue-Verhalten.</p> <p> Workflow</p> </li> <li> <p> Encode-Planung</p> <p>Wie Titel/Tracks f\u00fcr HandBrake vorbereitet und best\u00e4tigt werden.</p> <p> Encoding</p> </li> <li> <p> Playlist-Analyse</p> <p>Bewertung mehrdeutiger Blu-ray-Playlists und manuelle Entscheidung.</p> <p> Playlist-Analyse</p> </li> <li> <p> Encode-Skripte (Pre & Post)</p> <p>Skripte/Ketten vor und nach dem Encode ausf\u00fchren.</p> <p> Encode-Skripte</p> </li> </ul>"},{"location":"pipeline/encoding/","title":"Encode-Planung & 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 -> HandBrake-Scan (--scan --json)\n -> Plan erstellen (Titel, Audio, Untertitel)\n -> READY_TO_ENCODE\n -> Benutzer best\u00e4tigt Auswahl\n -> 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 <input> \\\n -o <output> \\\n -t <titleId> \\\n -Z \"<preset>\" \\\n <extra-args> \\\n -a <audioTrackIds|none> \\\n -s <subtitleTrackIds|none>\n</code></pre> <p>Untertitel-Flags werden bei Bedarf erg\u00e4nzt:</p> <ul> <li><code>--subtitle-burned=<id></code></li> <li><code>--subtitle-default=<id></code></li> <li><code>--subtitle-forced=<id></code> oder <code>--subtitle-forced</code></li> </ul>"},{"location":"pipeline/encoding/#pre-post-encode-ausfuhrungen","title":"Pre-/Post-Encode-Ausf\u00fchrungen","text":"<ul> <li>Pre-Encode l\u00e4uft vor HandBrake</li> <li>Post-Encode l\u00e4uft nach HandBrake</li> </ul> <p>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 -> zur\u00fcck zu <code>MEDIAINFO_CHECK</code></li> <li>ohne RAW -> Startpfad \u00fcber <code>READY_TO_START</code>/<code>RIPPING</code></li> </ul>"},{"location":"pipeline/post-encode-scripts/","title":"Encode-Skripte (Pre & Post)","text":"<p>Ripster kann Skripte und Skript-Ketten vor und nach dem Encode ausf\u00fchren.</p>"},{"location":"pipeline/post-encode-scripts/#ablauf","title":"Ablauf","text":"<pre><code>READY_TO_ENCODE\n -> Pre-Encode Skripte/Ketten\n -> HandBrake Encoding\n -> Post-Encode Skripte/Ketten\n -> 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 & 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 --> DISC_DETECTED\n DISC_DETECTED --> ANALYZING\n ANALYZING --> METADATA_SELECTION\n METADATA_SELECTION --> READY_TO_START\n READY_TO_START --> RIPPING\n READY_TO_START --> MEDIAINFO_CHECK\n MEDIAINFO_CHECK --> WAITING_FOR_USER_DECISION\n WAITING_FOR_USER_DECISION --> MEDIAINFO_CHECK\n MEDIAINFO_CHECK --> READY_TO_ENCODE\n READY_TO_ENCODE --> ENCODING\n ENCODING --> FINISHED\n ENCODING --> ERROR\n RIPPING --> ERROR\n RIPPING --> 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> -> <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":"Externe Tools","text":"<p>Ripster ist ein Orchestrator \u2013 die eigentliche Arbeit erledigen diese bew\u00e4hrten Open-Source-Tools:</p> <ul> <li> <p> MakeMKV</p> <p>Disc-Analyse und Ripping. Erstellt MKV-Dateien oder vollst\u00e4ndige Backups.</p> <p> MakeMKV</p> </li> <li> <p> HandBrake</p> <p>Video-Encoding mit umfangreichen Preset-Optionen.</p> <p> HandBrake</p> </li> <li> <p> MediaInfo</p> <p>Analyse von Track-Informationen in Mediendateien.</p> <p> MediaInfo</p> </li> </ul>"},{"location":"tools/handbrake/","title":"HandBrake","text":"<p>Ripster verwendet <code>HandBrakeCLI</code> f\u00fcr Scan und Encode.</p>"},{"location":"tools/handbrake/#verwendete-aufrufe","title":"Verwendete Aufrufe","text":""},{"location":"tools/handbrake/#scan-review-aufbau","title":"Scan (Review-Aufbau)","text":"<pre><code>HandBrakeCLI --scan --json -i <input> -t 0\n</code></pre>"},{"location":"tools/handbrake/#encode-vereinfacht","title":"Encode (vereinfacht)","text":"<pre><code>HandBrakeCLI \\\n -i <input> \\\n -o <output> \\\n -t <titleId> \\\n -Z \"<preset>\" \\\n <extra-args> \\\n -a <audioTrackIds|none> \\\n -s <subtitleTrackIds|none>\n</code></pre> <p>Optional erg\u00e4nzt Ripster:</p> <ul> <li><code>--subtitle-burned=<id></code></li> <li><code>--subtitle-default=<id></code></li> <li><code>--subtitle-forced=<id></code> oder <code>--subtitle-forced</code></li> </ul>"},{"location":"tools/handbrake/#presets-auslesen","title":"Presets auslesen","text":"<p>Ripster liest Presets mit:</p> <pre><code>HandBrakeCLI -z\n</code></pre>"},{"location":"tools/handbrake/#relevante-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>"},{"location":"tools/makemkv/","title":"MakeMKV","text":"<p>Ripster nutzt <code>makemkvcon</code> f\u00fcr Disc-Analyse und Rip.</p>"},{"location":"tools/makemkv/#verwendete-aufrufe","title":"Verwendete Aufrufe","text":""},{"location":"tools/makemkv/#analyse","title":"Analyse","text":"<pre><code>makemkvcon -r info <source>\n</code></pre> <p><code><source></code> ist typischerweise:</p> <ul> <li><code>disc:<index></code> (Auto-Modus)</li> <li><code>dev:/dev/sr0</code> (explicit)</li> <li><code>file:<path></code> (Datei/Ordner-Analyse)</li> </ul>"},{"location":"tools/makemkv/#rip-mkv-modus","title":"Rip (MKV-Modus)","text":"<pre><code>makemkvcon mkv <source> <title-or-all> <rawDir> [--minlength=...] [...extraArgs]\n</code></pre>"},{"location":"tools/makemkv/#rip-backup-modus","title":"Rip (Backup-Modus)","text":"<pre><code>makemkvcon backup <source> <rawDir> --decrypt\n</code></pre>"},{"location":"tools/makemkv/#registrierungsschlussel-optional","title":"Registrierungsschl\u00fcssel (optional)","text":"<p>Wenn <code>makemkv_registration_key</code> gesetzt ist, f\u00fchrt Ripster vor Analyse/Rip aus:</p> <pre><code>makemkvcon reg <key>\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 <input>\n</code></pre> <p>Der Input ist typischerweise eine RAW-Datei oder ein vom Workflow gew\u00e4hlter Inputpfad.</p>"},{"location":"tools/mediainfo/#verwendung-in-ripster","title":"Verwendung in Ripster","text":"<ul> <li>Track-/Codec-Metadaten f\u00fcr Review-Plan</li> <li>Fallback-Informationen in bestimmten Analysepfaden</li> <li>Persistenz als <code>mediainfoInfo</code> im Job</li> </ul>"},{"location":"tools/mediainfo/#relevante-settings","title":"Relevante Settings","text":"Key Bedeutung <code>mediainfo_command</code> CLI-Binary <code>mediainfo_extra_args_bluray</code> / <code>_dvd</code> profilspezifische Zusatzargumente"},{"location":"tools/mediainfo/#troubleshooting","title":"Troubleshooting","text":"<ul> <li>JSON-Test: <code>mediainfo --Output=JSON <datei></code></li> <li>unbekannte Sprache erscheint oft als <code>und</code> (undetermined)</li> </ul>"}]} |