Bugfix and Docs
This commit is contained in:
@@ -1,329 +1,103 @@
|
||||
# Encode-Planung & Track-Auswahl
|
||||
|
||||
`encodePlan.js` analysiert die HandBrake-Scan-Ausgabe, wählt Audio- und Untertitelspuren anhand von Regeln vor und erstellt einen vollständigen Encode-Plan für die Benutzer-Review.
|
||||
Ripster erzeugt vor dem Encode einen `encodePlan` und lässt ihn im Review-Panel bestätigen.
|
||||
|
||||
---
|
||||
|
||||
## Ablauf im Pipeline-Kontext
|
||||
## Ablauf
|
||||
|
||||
```
|
||||
RIPPING abgeschlossen (oder Pre-Rip-Scan)
|
||||
↓
|
||||
HandBrake --scan (alle Titel & Tracks einlesen)
|
||||
↓
|
||||
buildTrackSelectors() ← Regeln aus Einstellungen ableiten
|
||||
↓
|
||||
selectTrackIds() ← Tracks anhand Regeln vorauswählen
|
||||
↓
|
||||
resolveAudioEncoderAction() ← Encoder-Aktion pro Track bestimmen
|
||||
↓
|
||||
buildDiscScanReview() ← Vollständigen Encode-Plan erstellen
|
||||
↓
|
||||
READY_TO_ENCODE ← Benutzer-Review im Frontend
|
||||
↓
|
||||
applyManualTrackSelectionToPlan() ← Benutzer-Auswahl anwenden
|
||||
↓
|
||||
ENCODING ← HandBrake-CLI mit finalem Plan starten
|
||||
```text
|
||||
Quelle bestimmen (Disc/RAW)
|
||||
-> HandBrake-Scan (--scan --json)
|
||||
-> Plan erstellen (Titel, Audio, Untertitel)
|
||||
-> READY_TO_ENCODE
|
||||
-> Benutzer bestätigt Auswahl
|
||||
-> finaler HandBrake-Aufruf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Rip Track-Scan
|
||||
## Review-Inhalt (`READY_TO_ENCODE`)
|
||||
|
||||
Ripster führt einen **HandBrake-Scan** bereits **vor dem eigentlichen Ripping** durch:
|
||||
|
||||
```bash
|
||||
HandBrakeCLI --scan -i /dev/sr0 -t 0
|
||||
```
|
||||
|
||||
Dieser Scan liest alle Titel und deren Tracks aus der Disc (ohne zu encodieren). So kann der Benutzer die Track-Auswahl bereits vor dem zeitintensiven Rip-Prozess bestätigen.
|
||||
|
||||
!!! info "Pre-Rip vs. Post-Rip"
|
||||
Ob der Scan vor oder nach dem Ripping passiert, hängt vom konfigurierten Modus ab. Bei direktem Disc-Zugriff ist Pre-Rip möglich; nach einem MakeMKV-Backup wird die entstandene `.mkv`-Datei gescannt.
|
||||
- auswählbarer Encode-Titel
|
||||
- Audio-Track-Selektion
|
||||
- Untertitel-Track-Selektion inkl. Flags
|
||||
- `burnIn`
|
||||
- `forced`
|
||||
- `defaultTrack`
|
||||
- optionale User-Presets (HandBrake-Preset + Extra-Args)
|
||||
- optionale Pre-/Post-Skripte und Ketten
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Track-Selektor-Regeln (`buildTrackSelectors`)
|
||||
## Bestätigung (`confirm-encode`)
|
||||
|
||||
Die Regeln werden aus den HandBrake-Einstellungen abgeleitet. Es gibt fünf **Selektionsmodi**:
|
||||
|
||||
| Modus | Beschreibung |
|
||||
|------|-------------|
|
||||
| `none` | Keine Tracks dieser Art übernehmen |
|
||||
| `first` | Nur den ersten Track übernehmen |
|
||||
| `all` | Alle Tracks übernehmen |
|
||||
| `language` | Nur Tracks in bestimmten Sprachen |
|
||||
| `explicit` | Bestimmte Track-IDs explizit angeben |
|
||||
|
||||
Der aktive Modus wird aus den `handbrake_*`-Einstellungen und `handbrake_extra_args` abgeleitet. Explizite CLI-Argumente (`--audio`, `--audio-lang-list`) überschreiben die Basis-Konfiguration.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Automatische Vorauswahl (`selectTrackIds`)
|
||||
|
||||
### Audio-Tracks
|
||||
|
||||
```
|
||||
Modus 'none' → Keine Audio-Tracks
|
||||
Modus 'all' → Alle Tracks (oder nur erster, wenn firstOnly)
|
||||
Modus 'language' → Alle Tracks in den konfigurierten Sprachen
|
||||
Modus 'explicit' → Nur die angegebenen Track-IDs
|
||||
Modus 'first' → Nur Track 1
|
||||
```
|
||||
|
||||
Jeder Audio-Track erhält das Feld `selectedByRule: true/false` – dieses zeigt dem Benutzer, welche Tracks automatisch vorausgewählt wurden.
|
||||
|
||||
**Sprach-Normalisierung (`normalizeLanguage`):**
|
||||
|
||||
Alle Sprachcodes werden auf **ISO 639-2** (3-Buchstaben) normalisiert:
|
||||
|
||||
| Eingabe | Normalisiert |
|
||||
|--------|-------------|
|
||||
| `de`, `ger` | `deu` |
|
||||
| `German` | `deu` |
|
||||
| `en`, `eng` | `eng` |
|
||||
| `English` | `eng` |
|
||||
| `fr`, `fre` | `fra` |
|
||||
| `ja`, `jpn` | `jpn` |
|
||||
| Unbekannt | `und` |
|
||||
|
||||
### Untertitel-Tracks
|
||||
|
||||
Gleiche Modus-Logik wie Audio, aber mit **zusätzlichen Flags** pro Track:
|
||||
|
||||
| Flag | Bedeutung |
|
||||
|------|-----------|
|
||||
| `burnIn` | Untertitel in Video einbrennen (`--subtitle-burned`) |
|
||||
| `forced` | Nur erzwungene Untertitel übernehmen (`--subtitle-forced`) |
|
||||
| `defaultTrack` | Als Standard-Untertitelspur markieren (`--subtitle-default`) |
|
||||
|
||||
Diese Flags werden im Encode-Review als Checkboxen angezeigt.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Encoder-Aktion bestimmen (`resolveAudioEncoderAction`)
|
||||
|
||||
Für jeden vorausgewählten Audio-Track bestimmt Ripster die Encoder-Aktion:
|
||||
|
||||
```
|
||||
Encoder-Einstellung Codec-Support in Copy-Mask? Aktion
|
||||
─────────────────────────────────────────────────────────────────────
|
||||
Kein Encoder / 'preset-default' → preset-default HandBrake-Preset entscheidet
|
||||
encoder.startsWith('copy')
|
||||
UND Codec in audioCopyMask → copy Direktkopie (verlustfrei)
|
||||
UND Codec NICHT in audioCopyMask→ fallback Transcode mit Fallback-Encoder
|
||||
sonstiger Encoder → transcode Transcode mit explizitem Encoder
|
||||
```
|
||||
|
||||
**Encoder-Aktionstypen:**
|
||||
|
||||
| Typ | Label (UI) | Qualität |
|
||||
|----|-----------|---------|
|
||||
| `preset-default` | `Preset-Default (HandBrake)` | HandBrake entscheidet |
|
||||
| `copy` | `Copy (ac3)` | Verlustfrei |
|
||||
| `fallback` | `Fallback Transcode (av_aac)` | Mit Qualitätsverlust |
|
||||
| `transcode` | `Transcode (av_aac)` | Mit Qualitätsverlust |
|
||||
|
||||
**Copy-kompatible Codecs (Standard Copy-Mask):**
|
||||
|
||||
| Codec | Encoder-String |
|
||||
|-------|---------------|
|
||||
| AC-3 | `copy:ac3` |
|
||||
| E-AC-3 | `copy:eac3` |
|
||||
| AAC | `copy:aac` |
|
||||
| MP3 | `copy:mp3` |
|
||||
| TrueHD | `copy:truehd` |
|
||||
| DTS | `copy:dts` *(nur mit spez. HandBrake-Build)* |
|
||||
| DTS-HD | `copy:dtshd` *(nur mit spez. HandBrake-Build)* |
|
||||
|
||||
!!! warning "DTS im Standard-HandBrake"
|
||||
Standard-HandBrake-Builds unterstützen kein DTS-Passthrough. DTS-Tracks werden dann automatisch auf den Fallback-Encoder umgestellt (Standard: `av_aac`).
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Encode-Plan-Struktur
|
||||
|
||||
Der vollständige Plan wird im Job-Datensatz als `encode_plan_json` gespeichert:
|
||||
Typischer Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "pre_rip",
|
||||
"preRip": true,
|
||||
"encodeInputTitleId": 1,
|
||||
"encodeInputPath": "disc-track-scan://title-1",
|
||||
"selectors": {
|
||||
"audio": { "mode": "language", "languages": ["deu", "eng"], "copyMask": ["copy:ac3", "copy:eac3"] },
|
||||
"subtitle": { "mode": "none" }
|
||||
},
|
||||
"titles": [
|
||||
{
|
||||
"id": 1,
|
||||
"fileName": "Disc Title 1",
|
||||
"durationSeconds": 8885,
|
||||
"selectedByMinLength": true,
|
||||
"isEncodeInput": true,
|
||||
"audioTracks": [
|
||||
{
|
||||
"id": 1,
|
||||
"sourceTrackId": 1,
|
||||
"language": "eng",
|
||||
"languageLabel": "English",
|
||||
"title": "5.1 Surround",
|
||||
"format": "AC3",
|
||||
"codecToken": "ac3",
|
||||
"channels": "6",
|
||||
"selectedByRule": true,
|
||||
"selectedForEncode": true,
|
||||
"encodePreviewActions": [
|
||||
{ "type": "copy", "encoder": "copy:ac3", "label": "Copy (ac3)" }
|
||||
],
|
||||
"encodePreviewSummary": "Copy (ac3)"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"sourceTrackId": 2,
|
||||
"language": "deu",
|
||||
"languageLabel": "Deutsch",
|
||||
"format": "DTS",
|
||||
"codecToken": "dts",
|
||||
"channels": "6",
|
||||
"selectedByRule": true,
|
||||
"selectedForEncode": true,
|
||||
"encodePreviewActions": [
|
||||
{ "type": "fallback", "encoder": "av_aac", "label": "Fallback Transcode (av_aac)" }
|
||||
],
|
||||
"encodePreviewSummary": "Fallback Transcode (av_aac)"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"language": "fra",
|
||||
"languageLabel": "Français",
|
||||
"selectedByRule": false,
|
||||
"selectedForEncode": false,
|
||||
"encodePreviewSummary": "Nicht übernommen"
|
||||
}
|
||||
],
|
||||
"subtitleTracks": [
|
||||
{
|
||||
"id": 1,
|
||||
"language": "deu",
|
||||
"selectedByRule": true,
|
||||
"selectedForEncode": true,
|
||||
"burnIn": false,
|
||||
"forced": false,
|
||||
"defaultTrack": true,
|
||||
"subtitlePreviewSummary": "Übernehmen",
|
||||
"subtitlePreviewFlags": ["default"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Benutzer-Review im Frontend (`MediaInfoReviewPanel`)
|
||||
|
||||
Das Review-Panel zeigt:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Encode-Review Titel: Disc Title 1 │
|
||||
│ Laufzeit: 2:28:05 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Audio-Spuren │
|
||||
├──────┬──────────────────────────┬──────────────────────────────┤
|
||||
│ [✓] │ Track 1: English (AC3) │ Copy (ac3) │
|
||||
│ [✓] │ Track 2: Deutsch (DTS) │ Fallback Transcode (av_aac) │
|
||||
│ [ ] │ Track 3: Français (DTS) │ Nicht übernommen │
|
||||
├──────┴──────────────────────────┴──────────────────────────────┤
|
||||
│ Untertitel-Spuren │
|
||||
├──────┬──────────────────────────┬────────┬────────┬────────────┤
|
||||
│ [✓] │ Track 1: Deutsch │Einbr.[ ]│Forced[ ]│Default[✓]│
|
||||
│ [ ] │ Track 2: English │Einbr.[ ]│Forced[ ]│Default[ ]│
|
||||
├──────┴──────────────────────────┴────────┴────────┴────────────┤
|
||||
│ [Encoding starten] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Der Benutzer kann:
|
||||
- **Audio-Tracks** per Checkbox aktivieren/deaktivieren
|
||||
- **Untertitel-Flags** (Einbrennen, Forced, Default) setzen
|
||||
- **Mehrere Titel** bei der Titleauswahl wechseln (für Discs mit mehreren Haupttiteln)
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Benutzer-Auswahl anwenden (`applyManualTrackSelectionToPlan`)
|
||||
|
||||
Im Frontend wird die Benutzer-Auswahl beim Klick auf **"Encoding starten"** (ggf. automatisch) bestätigt und dann auf den Plan angewendet:
|
||||
|
||||
```json
|
||||
Payload: {
|
||||
"selectedEncodeTitleId": 1,
|
||||
"selectedTrackSelection": {
|
||||
"1": {
|
||||
"audioTrackIds": [1, 2],
|
||||
"subtitleTrackIds": [1]
|
||||
"subtitleTrackIds": [3]
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedPreEncodeScriptIds": [1],
|
||||
"selectedPostEncodeScriptIds": [2],
|
||||
"selectedPreEncodeChainIds": [3],
|
||||
"selectedPostEncodeChainIds": [4],
|
||||
"selectedUserPresetId": 5
|
||||
}
|
||||
```
|
||||
|
||||
Jeder Track erhält `selectedForEncode: true/false` entsprechend der Auswahl. Die Encoder-Aktionen (`encodeActions`) der nicht gewählten Tracks werden geleert.
|
||||
Ripster speichert die bestätigte Auswahl in `jobs.encode_plan_json` und markiert `encode_review_confirmed = 1`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: HandBrake-CLI-Befehl
|
||||
## HandBrake-Aufruf
|
||||
|
||||
Aus dem finalisierten Plan baut Ripster den HandBrake-Aufruf:
|
||||
Grundstruktur:
|
||||
|
||||
```bash
|
||||
HandBrakeCLI \
|
||||
-i /dev/sr0 \
|
||||
-o "/mnt/movies/Inception (2010).mkv" \
|
||||
-t 1 \
|
||||
--preset "H.265 MKV 1080p30" \
|
||||
-a 1,2 \
|
||||
-E copy:ac3,av_aac \
|
||||
-s 1 \
|
||||
--subtitle-default 1
|
||||
-i <input> \
|
||||
-o <output> \
|
||||
-t <titleId> \
|
||||
-Z "<preset>" \
|
||||
<extra-args> \
|
||||
-a <audioTrackIds|none> \
|
||||
-s <subtitleTrackIds|none>
|
||||
```
|
||||
|
||||
| Argument | Quelle |
|
||||
|---------|--------|
|
||||
| `-i` | `encode_input_path` aus Job |
|
||||
| `-o` | Ausgabepfad aus `filename_template` + `movie_dir` |
|
||||
| `-t` | Gewählter Titel-Index |
|
||||
| `-a` | Kommagetrennte Audio-Track-IDs der ausgewählten Tracks |
|
||||
| `-E` | Kommagetrennte Encoder-Aktionen (eine pro Track, gleiche Reihenfolge wie `-a`) |
|
||||
| `-s` | Kommagetrennte Untertitel-Track-IDs |
|
||||
| `--subtitle-default` | Track-ID der als Default markierten Untertitelspur |
|
||||
| `--preset` | `handbrake_preset`-Einstellung |
|
||||
| Extras | `handbrake_extra_args`-Einstellung |
|
||||
Untertitel-Flags werden bei Bedarf ergänzt:
|
||||
|
||||
- `--subtitle-burned=<id>`
|
||||
- `--subtitle-default=<id>`
|
||||
- `--subtitle-forced=<id>` oder `--subtitle-forced`
|
||||
|
||||
---
|
||||
|
||||
## Dateiname-Template
|
||||
## Pre-/Post-Encode-Ausführungen
|
||||
|
||||
| Platzhalter | Wert | Beispiel |
|
||||
|------------|------|---------|
|
||||
| `{title}` | Filmtitel von OMDb | `Inception` |
|
||||
| `{year}` | Erscheinungsjahr | `2010` |
|
||||
| `{imdb_id}` | IMDb-ID | `tt1375666` |
|
||||
| `{type}` | `movie` oder `series` | `movie` |
|
||||
- Pre-Encode läuft vor HandBrake
|
||||
- Post-Encode läuft nach HandBrake
|
||||
|
||||
Sonderzeichen (`:`, `/`, `?`, `*` etc.) werden automatisch aus dem Dateinamen entfernt.
|
||||
Verhalten bei Fehlern:
|
||||
|
||||
- Pre-Encode-Fehler: Job wird als `ERROR` beendet (Encode startet nicht)
|
||||
- Post-Encode-Fehler: Job kann `FINISHED` bleiben, enthält aber Fehlerhinweis/Script-Summary
|
||||
|
||||
---
|
||||
|
||||
## Re-Encoding
|
||||
## Dateinamen/Ordner
|
||||
|
||||
Ein abgeschlossener Job kann ohne erneutes Ripping neu encodiert werden:
|
||||
Der finale Outputpfad wird aus Settings-Templates aufgebaut.
|
||||
|
||||
1. Job in der **History** öffnen
|
||||
2. **"Re-Encode"** klicken
|
||||
3. Track-Auswahl anpassen (oder bestehende übernehmen)
|
||||
4. Encoding startet mit den aktuellen `handbrake_*`-Einstellungen
|
||||
Platzhalter:
|
||||
|
||||
Nützlich bei geänderten Presets, anderen Sprach-Präferenzen oder nach einem Einstellungs-Update.
|
||||
- `${title}`
|
||||
- `${year}`
|
||||
- `${imdbId}`
|
||||
|
||||
Ungültige Dateizeichen werden sanitisiert.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Pipeline
|
||||
|
||||
Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
|
||||
Der Pipeline-Bereich beschreibt den Kern-Workflow von Ripster.
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
@@ -8,7 +8,7 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
|
||||
|
||||
---
|
||||
|
||||
Der vollständige Ripping-Workflow mit allen Zustandsübergängen.
|
||||
Zustände, Übergänge und Queue-Verhalten.
|
||||
|
||||
[:octicons-arrow-right-24: Workflow](workflow.md)
|
||||
|
||||
@@ -16,7 +16,7 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
|
||||
|
||||
---
|
||||
|
||||
Wie Ripster Audio- und Untertitel-Tracks analysiert und Encode-Pläne erstellt.
|
||||
Wie Titel/Tracks für HandBrake vorbereitet und bestätigt werden.
|
||||
|
||||
[:octicons-arrow-right-24: Encoding](encoding.md)
|
||||
|
||||
@@ -24,16 +24,16 @@ Der Pipeline-Abschnitt beschreibt den Kern-Workflow von Ripster.
|
||||
|
||||
---
|
||||
|
||||
Erkennung von Blu-ray Playlist-Obfuskierung und Auswahl der korrekten Playlist.
|
||||
Bewertung mehrdeutiger Blu-ray-Playlists und manuelle Entscheidung.
|
||||
|
||||
[:octicons-arrow-right-24: Playlist-Analyse](playlist-analysis.md)
|
||||
|
||||
- :material-script-text: **Post-Encode-Skripte**
|
||||
- :material-script-text: **Encode-Skripte (Pre & Post)**
|
||||
|
||||
---
|
||||
|
||||
Automatische Ausführung von Shell-Skripten nach erfolgreichem Encoding – z. B. zum Verschieben oder Benachrichtigen.
|
||||
Skripte/Ketten vor und nach dem Encode ausführen.
|
||||
|
||||
[:octicons-arrow-right-24: Post-Encode-Skripte](post-encode-scripts.md)
|
||||
[:octicons-arrow-right-24: Encode-Skripte](post-encode-scripts.md)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,217 +1,65 @@
|
||||
# Playlist-Analyse
|
||||
|
||||
Einige Blu-rays verwenden **Playlist-Obfuskierung** als Kopierschutz. Ripster analysiert automatisch alle MakeMKV-Titel und empfiehlt die korrekte Playlist – auf Basis eines Segment-Scoring-Algorithmus aus `playlistAnalysis.js`.
|
||||
Ripster analysiert bei Blu-ray-ähnlichen Quellen Playlists und fordert bei Mehrdeutigkeit eine manuelle Auswahl an.
|
||||
|
||||
---
|
||||
|
||||
## Das Problem: Playlist-Obfuskierung
|
||||
## Ziel
|
||||
|
||||
Moderne Blu-rays können Dutzende bis Hunderte von Titeln/Playlists enthalten. Der eigentliche Film steckt in genau einer davon – alle anderen sind:
|
||||
|
||||
- **Kurze Dummy-Titel** (wenige Sekunden bis Minuten)
|
||||
- **Titel mit verschachtelten Segmenten** (absichtlich versetzte Reihenfolge, sodass der Film falsch gerippt wird)
|
||||
- **Titel gleicher Länge** (mehrere Playlists mit identischer Laufzeit, aber unterschiedlicher Segment-Reihenfolge)
|
||||
|
||||
Das Ziel der Obfuskierung: Ein einfacher Ripper wählt den erstbesten langen Titel – und bekommt ein zerstückeltes, unbrauchbares Video.
|
||||
Erkennen, welche Playlist wahrscheinlich der Hauptfilm ist, statt versehentlich eine Fake-/Dummy-Playlist zu verwenden.
|
||||
|
||||
---
|
||||
|
||||
## Wann wird die Analyse ausgelöst?
|
||||
## Eingabedaten
|
||||
|
||||
Die Playlist-Analyse wird automatisch gestartet **sobald der Benutzer Metadaten bestätigt** (nach dem Metadaten-Dialog). Ripster ruft `makemkvcon` im Info-Modus auf und parst die TINFO-Ausgabe.
|
||||
|
||||
```
|
||||
TINFO:<titleId>,26,"<segment-list>"
|
||||
```
|
||||
|
||||
Feld **26** enthält die kommagetrennte Liste der Segment-Nummern in der Abspielreihenfolge des Titels.
|
||||
Die Analyse basiert auf MakeMKV-Infos (u. a. Playlist-/Segment-Struktur, Laufzeiten, Titelzuordnung).
|
||||
|
||||
---
|
||||
|
||||
## Algorithmus im Detail (`playlistAnalysis.js`)
|
||||
## Auswertung (vereinfacht)
|
||||
|
||||
### Schritt 1 – Segment-Nummern parsen
|
||||
Für Kandidaten werden u. a. berücksichtigt:
|
||||
|
||||
```
|
||||
TINFO:1,26,"00000,00001,00002,00003" → [0, 1, 2, 3] linearer Film
|
||||
TINFO:2,26,"00100,00050,00100,00051" → [100, 50, 100, 51] Fake-Playlist
|
||||
```
|
||||
- Laufzeit
|
||||
- Segment-Reihenfolge
|
||||
- Rückwärtssprünge/große Sprünge
|
||||
- Kohärenz linearer Segmentfolgen
|
||||
- Duplikatgruppen mit ähnlicher Laufzeit
|
||||
|
||||
### Schritt 2 – Metriken berechnen (`computeSegmentMetrics`)
|
||||
Daraus entstehen:
|
||||
|
||||
Für jedes aufeinanderfolgende Segment-Paar `[a, b]` wird `diff = b − a` berechnet:
|
||||
|
||||
| Metrik | Bedingung | Bedeutung |
|
||||
|--------|----------|-----------|
|
||||
| `directSequenceSteps` | `diff == 1` | Aufeinanderfolgende Segmente → linearer Film |
|
||||
| `backwardJumps` | `b < a` | Rückwärtssprünge → verdächtig |
|
||||
| `largeJumps` | `\|diff\| > 20` | Große Sprünge → verdächtig |
|
||||
| `alternatingPairs` | Große Sprünge mit **wechselndem Vorzeichen** | Hin-und-her-Muster → starker Fake-Indikator |
|
||||
|
||||
**Score-Formel:**
|
||||
|
||||
```
|
||||
score = (directSequenceSteps × 2) − (backwardJumps × 3) − (largeJumps × 2)
|
||||
```
|
||||
|
||||
**Konkrete Beispiele:**
|
||||
|
||||
| Segmentfolge | directSeq | backward | large | score | Ergebnis |
|
||||
|-------------|-----------|----------|-------|-------|---------|
|
||||
| `0,1,2,3,4,5` | 5 | 0 | 0 | +10 | Echter Film |
|
||||
| `0,1,100,2,101,3` | 2 | 0 | 4 | -4 | Verdächtig |
|
||||
| `50,10,60,11,70,12` | 0 | 3 | 3 | -15 | Fake |
|
||||
|
||||
### Schritt 3 – Bewertungslabel vergeben (`buildEvaluationLabel`)
|
||||
|
||||
```
|
||||
alternatingRatio = alternatingPairs / largeJumps
|
||||
|
||||
if alternatingRatio >= 0.55 AND alternatingPairs >= 3:
|
||||
→ "Fake-Struktur (alternierendes Sprungmuster)"
|
||||
|
||||
else if backwardJumps > 0 OR largeJumps > 0:
|
||||
→ "Auffällige Segmentreihenfolge"
|
||||
|
||||
else:
|
||||
→ "wahrscheinlich korrekt (lineare Segmentfolge)"
|
||||
```
|
||||
|
||||
### Schritt 4 – Duplikat-Gruppen bilden (`buildSimilarityGroups`)
|
||||
|
||||
Alle Titel werden nach **ähnlicher Laufzeit** gruppiert (±90 Sekunden Toleranz). Gibt es mehrere Kandidaten mit ähnlicher Laufzeit, ist das ein klares Zeichen für Obfuskierung:
|
||||
|
||||
```
|
||||
8 Titel mit ~148 Minuten Laufzeit → Duplikat-Gruppe
|
||||
→ obfuscationDetected = true
|
||||
```
|
||||
|
||||
### Schritt 5 – Besten Kandidaten empfehlen (`scoreCandidates`)
|
||||
|
||||
Innerhalb der größten Duplikat-Gruppe werden alle Kandidaten sortiert nach:
|
||||
|
||||
1. `score` (höher = besser)
|
||||
2. `sequenceCoherence` (Anteil linearer Segmentschritte)
|
||||
3. Laufzeit (länger = besser)
|
||||
4. Dateigröße (größer = besser als Tiebreaker)
|
||||
|
||||
Der **erste Kandidat** der sortierten Liste ist die Empfehlung.
|
||||
|
||||
### Schritt 6 – Entscheidung erzwingen bei mehreren Kandidaten
|
||||
|
||||
Sobald nach `MIN_LENGTH_MINUTES` **mehr als eine** Playlist übrig bleibt, wird immer eine manuelle Auswahl verlangt:
|
||||
|
||||
```
|
||||
candidateCount > 1 → manualDecisionRequired = true
|
||||
candidateCount <= 1 → manualDecisionRequired = false
|
||||
```
|
||||
- `candidates`
|
||||
- `evaluatedCandidates` (inkl. Score/Label)
|
||||
- `recommendation`
|
||||
- `manualDecisionRequired`
|
||||
|
||||
---
|
||||
|
||||
## Wann greift der Benutzer ein?
|
||||
## Wann muss der Benutzer entscheiden?
|
||||
|
||||
```
|
||||
obfuscationDetected = duplicateDurationGroups.length > 0
|
||||
manualDecisionRequired = candidates.length > 1
|
||||
```
|
||||
Wenn nach Filterung mehr als ein relevanter Kandidat übrig bleibt, setzt Ripster `manualDecisionRequired = true` und wechselt auf:
|
||||
|
||||
| Ergebnis | Nächster Pipeline-Zustand | Aktion |
|
||||
|---------|--------------------------|--------|
|
||||
| Nur ein Kandidat nach Mindestlänge | `READY_TO_START` | Automatische Übernahme möglich |
|
||||
| Mehrere Kandidaten nach Mindestlänge | `WAITING_FOR_USER_DECISION` | Benutzer muss Playlist auswählen |
|
||||
- `WAITING_FOR_USER_DECISION`
|
||||
|
||||
Dann muss eine Playlist bestätigt werden, bevor der Workflow weiterläuft.
|
||||
|
||||
---
|
||||
|
||||
## Benutzeroberfläche: Playlist-Auswahl-Dialog
|
||||
## Konfigurationseinfluss
|
||||
|
||||
Wenn `manualDecisionRequired = true`, öffnet sich der Playlist-Dialog **nach** dem Metadaten-Dialog:
|
||||
| Key | Wirkung |
|
||||
|-----|---------|
|
||||
| `makemkv_min_length_minutes` | Mindestlaufzeit für Kandidaten |
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ Playlist-Auswahl │
|
||||
├──────────┬──────────┬──────────┬────────────────────────────────┤
|
||||
│ Playlist │ Laufzeit │ Score │ Bewertung │
|
||||
├──────────┼──────────┼──────────┼────────────────────────────────┤
|
||||
│ ★ 00800 │ 2:28:05 │ +18 │ wahrscheinlich korrekt │
|
||||
│ │ │ │ (lineare Segmentfolge) │
|
||||
├──────────┼──────────┼──────────┼────────────────────────────────┤
|
||||
│ 00801 │ 2:28:12 │ −4 │ Auffällige Segmentreihenfolge │
|
||||
├──────────┼──────────┼──────────┼────────────────────────────────┤
|
||||
│ 00900 │ 2:28:05 │ −32 │ Fake-Struktur │
|
||||
│ │ │ │ (alternierendes Sprungmuster) │
|
||||
└──────────┴──────────┴──────────┴────────────────────────────────┘
|
||||
Hinweis: 847 Playlists insgesamt. 3 relevante Kandidaten (≥ 15 min).
|
||||
Empfehlung: 00800 (★)
|
||||
```
|
||||
|
||||
- **★** markiert die empfohlene Playlist (vorausgewählt)
|
||||
- Nur Titel ≥ `makemkv_min_length_minutes` erscheinen in der Liste
|
||||
- Der Benutzer wählt per Radio-Button und klickt "Bestätigen"
|
||||
- Erst nach dieser Bestätigung wechselt die Pipeline zu `READY_TO_START`
|
||||
Default ist aktuell `60` Minuten.
|
||||
|
||||
---
|
||||
|
||||
## Vollständige Datenstruktur (`analyzeContext.playlistAnalysis`)
|
||||
## UI-Verhalten
|
||||
|
||||
```json
|
||||
{
|
||||
"titles": [
|
||||
{ "titleId": 1, "playlistId": "00800", "durationSeconds": 8885, "durationLabel": "2:28:05", "chapters": 28 }
|
||||
],
|
||||
"candidates": [
|
||||
{ "titleId": 1, "playlistId": "00800", "durationSeconds": 8885 },
|
||||
{ "titleId": 2, "playlistId": "00801", "durationSeconds": 8892 }
|
||||
],
|
||||
"evaluatedCandidates": [
|
||||
{
|
||||
"titleId": 1,
|
||||
"playlistId": "00800",
|
||||
"score": 18,
|
||||
"sequenceCoherence": 0.95,
|
||||
"evaluationLabel": "wahrscheinlich korrekt (lineare Segmentfolge)",
|
||||
"metrics": {
|
||||
"directSequenceSteps": 12,
|
||||
"backwardJumps": 0,
|
||||
"largeJumps": 1,
|
||||
"alternatingPairs": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"duplicateDurationGroups": [
|
||||
[
|
||||
{ "titleId": 1, "playlistId": "00800" },
|
||||
{ "titleId": 2, "playlistId": "00801" }
|
||||
]
|
||||
],
|
||||
"recommendation": {
|
||||
"titleId": 1,
|
||||
"playlistId": "00800",
|
||||
"score": 18,
|
||||
"reason": "Höchster Segment-Score in der größten Laufzeit-Gruppe"
|
||||
},
|
||||
"obfuscationDetected": true,
|
||||
"manualDecisionRequired": true
|
||||
}
|
||||
```
|
||||
Bei manueller Entscheidung zeigt das Dashboard Kandidaten inkl. Score/Bewertung und markiert eine Empfehlung.
|
||||
|
||||
---
|
||||
Nach Bestätigung:
|
||||
|
||||
## Konfiguration
|
||||
|
||||
| Einstellung | Standard | Wirkung |
|
||||
|------------|---------|---------|
|
||||
| `makemkv_min_length_minutes` | `15` | Titel kürzer als dieser Wert werden als Kandidaten ignoriert |
|
||||
|
||||
---
|
||||
|
||||
## Tipps bei Fehlempfehlung
|
||||
|
||||
!!! tip "Falsche Playlist gewählt?"
|
||||
Wenn das resultierende Video zerstückelt ist:
|
||||
|
||||
1. Job in der **History** öffnen
|
||||
2. **Re-Encode** starten – diesmal eine andere Playlist wählen
|
||||
3. Alternativ: Korrekte Playlist im [MakeMKV-Forum](https://www.makemkv.com/forum/) recherchieren
|
||||
|
||||
!!! info "Keine Segment-Daten verfügbar"
|
||||
Bei DVDs oder älteren Blu-rays liefert MakeMKV manchmal keine Segmentinfos (TINFO-Feld 26 fehlt). In diesem Fall entfällt die Analyse und der erste Titel über der Mindestlänge wird automatisch verwendet.
|
||||
- mit vorhandenem RAW -> zurück zu `MEDIAINFO_CHECK`
|
||||
- ohne RAW -> Startpfad über `READY_TO_START`/`RIPPING`
|
||||
|
||||
@@ -1,173 +1,70 @@
|
||||
# Encode-Skripte (Pre & Post)
|
||||
|
||||
Ripster unterstützt **Pre-Encode-** und **Post-Encode-Ausführungen**: Beliebige Shell-Skripte oder Skript-Ketten können automatisch vor und/oder nach dem Encoding-Schritt laufen – z. B. zum Vorbereiten von Verzeichnissen, Verschieben von Dateien oder Benachrichtigen externer Dienste.
|
||||
Ripster kann Skripte und Skript-Ketten vor und nach dem Encode ausführen.
|
||||
|
||||
---
|
||||
|
||||
## Funktionsweise
|
||||
## Ablauf
|
||||
|
||||
```
|
||||
```text
|
||||
READY_TO_ENCODE
|
||||
↓
|
||||
[Pre-Encode-Ausführungen] ← Fehler? → Abbruch
|
||||
Skript/Kette 1, 2, …
|
||||
↓
|
||||
ENCODING
|
||||
↓
|
||||
[Post-Encode-Ausführungen] ← Fehler? → Abbruch
|
||||
Skript/Kette 1, 2, …
|
||||
↓
|
||||
FINISHED
|
||||
```
|
||||
|
||||
!!! warning "Abbruch bei Fehler"
|
||||
Schlägt eine Ausführung fehl (Exit-Code ≠ 0), werden alle nachfolgenden Ausführungen der gleichen Phase **nicht mehr ausgeführt**.
|
||||
Der Job bleibt im Abschlusszustand `FINISHED`; der Fehler wird in Log/Status-Text und im Summary festgehalten.
|
||||
|
||||
---
|
||||
|
||||
## Skript- und Ketten-Verwaltung
|
||||
|
||||
Skripte und Skript-Ketten werden über die **Einstellungen-Seite** angelegt und verwaltet. Die Reihenfolge in der Liste kann per **Drag & Drop** geändert werden und bleibt persistent gespeichert.
|
||||
|
||||
### Skript anlegen
|
||||
|
||||
Navigiere zu **Einstellungen → Skripte** und klicke **"Neues Skript"**:
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|-------------|
|
||||
| **Name** | Anzeigename des Skripts (z. B. `Zu Plex verschieben`) |
|
||||
| **Befehl** | Shell-Befehl oder Skriptpfad (z. B. `/home/michael/scripts/move-to-plex.sh`) |
|
||||
| **Beschreibung** | Optionale Erklärung |
|
||||
|
||||
### Skript-Ketten
|
||||
|
||||
Eine **Skript-Kette** fasst mehrere Skripte zu einer benannten Einheit zusammen, die als ganzes ausgewählt werden kann. Nützlich für wiederkehrende Kombinationen (z. B. „Move + Notify Plex + Webhook"). Ketten werden genauso wie einzelne Skripte im Review-Panel ausgewählt.
|
||||
|
||||
### Verfügbare Umgebungsvariablen
|
||||
|
||||
Jedes Skript wird mit folgenden Umgebungsvariablen aufgerufen:
|
||||
|
||||
| Variable | Inhalt | Beispiel |
|
||||
|---------|--------|---------|
|
||||
| `RIPSTER_OUTPUT_PATH` | Absoluter Pfad der encodierten Datei | `/mnt/movies/Inception (2010).mkv` |
|
||||
| `RIPSTER_JOB_ID` | Job-ID in der Datenbank | `42` |
|
||||
| `RIPSTER_TITLE` | Filmtitel | `Inception` |
|
||||
| `RIPSTER_YEAR` | Erscheinungsjahr | `2010` |
|
||||
| `RIPSTER_IMDB_ID` | IMDb-ID | `tt1375666` |
|
||||
| `RIPSTER_RAW_PATH` | Pfad zur Raw-MKV-Datei | `/mnt/raw/Inception-2010/t00.mkv` |
|
||||
|
||||
### Beispiel-Skript: Datei nach Jellyfin verschieben
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /home/michael/scripts/move-to-jellyfin.sh
|
||||
|
||||
TARGET_DIR="/mnt/media/movies"
|
||||
mkdir -p "$TARGET_DIR"
|
||||
mv "$RIPSTER_OUTPUT_PATH" "$TARGET_DIR/"
|
||||
echo "Verschoben: $RIPSTER_TITLE nach $TARGET_DIR"
|
||||
```
|
||||
|
||||
### Beispiel-Skript: Webhook auslösen
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /home/michael/scripts/notify-webhook.sh
|
||||
|
||||
curl -s -X POST https://mein-webhook.example.com/ripster \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"title\": \"$RIPSTER_TITLE\", \"year\": \"$RIPSTER_YEAR\", \"path\": \"$RIPSTER_OUTPUT_PATH\"}"
|
||||
-> Pre-Encode Skripte/Ketten
|
||||
-> HandBrake Encoding
|
||||
-> Post-Encode Skripte/Ketten
|
||||
-> FINISHED oder ERROR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Im Encode-Review auswählen
|
||||
## Auswahl im Review
|
||||
|
||||
Im `READY_TO_ENCODE`-Zustand zeigt das **MediaInfoReviewPanel** zwei Abschnitte:
|
||||
Im Review-Panel kannst du getrennt wählen:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Pre-Encode Ausführungen (optional) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ≡ 1. Verzeichnis vorbereiten (Skript) [Entfernen]│
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Hinzufügen: [Skript/Kette auswählen ▾] [+ Hinzuf.]│
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Post-Encode Ausführungen (optional) │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ≡ 1. Zu Plex verschieben (Skript) [Entfernen]│
|
||||
│ ≡ 2. Notify-Kette (Kette) [Entfernen]│
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ Hinzufügen: [Skript/Kette auswählen ▾] [+ Hinzuf.]│
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Pre-Encode** und **Post-Encode** werden separat konfiguriert
|
||||
- Sowohl **einzelne Skripte** als auch **Skript-Ketten** können in beiden Phasen ausgewählt werden
|
||||
- **Reihenfolge** per Drag & Drop innerhalb jeder Phase ändern
|
||||
- **Hinzufügen** aus der Dropdown-Liste aller konfigurierten Skripte und Ketten
|
||||
- **Entfernen** einzelner Einträge
|
||||
- Auswahl kann pro Job frei variiert werden
|
||||
- `selectedPreEncodeScriptIds`
|
||||
- `selectedPostEncodeScriptIds`
|
||||
- `selectedPreEncodeChainIds`
|
||||
- `selectedPostEncodeChainIds`
|
||||
|
||||
---
|
||||
|
||||
## Skript testen
|
||||
## Fehlerverhalten
|
||||
|
||||
Über die Einstellungen kann jedes Skript mit einem Test-Job ausgeführt werden:
|
||||
|
||||
```http
|
||||
POST /api/settings/scripts/:scriptId/test
|
||||
```
|
||||
|
||||
Der Test-Aufruf befüllt die Umgebungsvariablen mit Platzhalter-Werten.
|
||||
- Pre-Encode-Fehler stoppen die Kette und führen zu `ERROR`.
|
||||
- Post-Encode-Fehler stoppen die restlichen Post-Schritte; Job kann dennoch `FINISHED` sein (mit Fehlerzusatz im Status/Log).
|
||||
|
||||
---
|
||||
|
||||
## Ausführungs-Ergebnis
|
||||
## Verfügbare Umgebungsvariablen
|
||||
|
||||
Das Ergebnis der Skript-Ausführung wird im Job-Datensatz gespeichert und in der History angezeigt:
|
||||
Beim Script-Run werden gesetzt:
|
||||
|
||||
```json
|
||||
{
|
||||
"postEncodeScripts": {
|
||||
"configured": 2,
|
||||
"attempted": 2,
|
||||
"succeeded": 2,
|
||||
"failed": 0,
|
||||
"skipped": 0,
|
||||
"aborted": false,
|
||||
"results": [
|
||||
{
|
||||
"scriptId": 1,
|
||||
"scriptName": "Zu Plex verschieben",
|
||||
"status": "SUCCESS"
|
||||
},
|
||||
{
|
||||
"scriptId": 2,
|
||||
"scriptName": "Webhook auslösen",
|
||||
"status": "SUCCESS"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|-------------|
|
||||
| `configured` | Anzahl ausgewählter Skripte |
|
||||
| `attempted` | Anzahl tatsächlich gestarteter Skripte |
|
||||
| `succeeded` | Erfolgreich ausgeführt (Exit-Code 0) |
|
||||
| `failed` | Fehlgeschlagen |
|
||||
| `skipped` | Nicht ausgeführt (wegen vorherigem Fehler) |
|
||||
| `aborted` | `true`, wenn die Kette abgebrochen wurde |
|
||||
- `RIPSTER_SCRIPT_RUN_AT`
|
||||
- `RIPSTER_JOB_ID`
|
||||
- `RIPSTER_JOB_TITLE`
|
||||
- `RIPSTER_MODE`
|
||||
- `RIPSTER_INPUT_PATH`
|
||||
- `RIPSTER_OUTPUT_PATH`
|
||||
- `RIPSTER_RAW_PATH`
|
||||
- `RIPSTER_SCRIPT_ID`
|
||||
- `RIPSTER_SCRIPT_NAME`
|
||||
- `RIPSTER_SCRIPT_SOURCE`
|
||||
|
||||
---
|
||||
|
||||
## API-Referenz
|
||||
## Skript-Ketten
|
||||
|
||||
Eine vollständige API-Dokumentation der Skript-Endpunkte findest du unter:
|
||||
Ketten unterstützen zwei Step-Typen:
|
||||
|
||||
[:octicons-arrow-right-24: Settings API – Skripte](../api/settings.md#skript-verwaltung)
|
||||
- `script` (führt ein hinterlegtes Skript aus)
|
||||
- `wait` (wartet `waitSeconds`)
|
||||
|
||||
Bei Fehler in einem Script-Step wird die Kette abgebrochen.
|
||||
|
||||
---
|
||||
|
||||
## Testläufe
|
||||
|
||||
- Skript testen: `POST /api/settings/scripts/:id/test`
|
||||
- Kette testen: `POST /api/settings/script-chains/:id/test`
|
||||
|
||||
Ergebnisse enthalten Erfolg/Exit-Code, Laufzeit und stdout/stderr.
|
||||
|
||||
@@ -1,329 +1,87 @@
|
||||
# Workflow & Zustände
|
||||
|
||||
Der Ripping-Workflow von Ripster ist als **State Machine** implementiert. Jeder Zustand hat klar definierte Übergangsbedingungen und Aktionen.
|
||||
Ripster steuert den Ablauf als State-Machine im `pipelineService`.
|
||||
|
||||
---
|
||||
|
||||
## Zustandsdiagramm
|
||||
|
||||
<div class="pipeline-diagram">
|
||||
## Zustandsdiagramm (vereinfacht)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
START(( )) --> IDLE
|
||||
|
||||
IDLE -->|Disc erkannt| DD[DISC_DETECTED]
|
||||
DD -->|Analyse starten| META[METADATA\nSELECTION]
|
||||
|
||||
META -->|Metadaten übernommen| RTS[READY_TO\nSTART]
|
||||
META -->|vorhandenes RAW +\nPlaylist offen| WUD[WAITING_FOR\nUSER_DECISION]
|
||||
|
||||
RTS -->|Auto-Start| RIP[RIPPING]
|
||||
RTS -->|Auto-Start mit RAW| MIC[MEDIAINFO\nCHECK]
|
||||
RIP -->|MKV fertig| MIC
|
||||
RIP -->|Fehler| ERR
|
||||
RIP -->|Abbruch| CAN([CANCELLED])
|
||||
|
||||
MIC -->|Playlist offen (Backup)| WUD
|
||||
WUD -->|Playlist bestätigt| MIC
|
||||
WUD -->|Playlist bestätigt,\nnoch kein RAW| RTS
|
||||
MIC --> RTE[READY_TO\nENCODE]
|
||||
RTE -->|Encoding starten\n(bestätigt bei Bedarf automatisch)| ENC[ENCODING]
|
||||
|
||||
ENC -->|inkl. Post-Skripte| FIN([FINISHED])
|
||||
ENC -->|Fehler| ERR
|
||||
ENC -->|Abbruch| CAN
|
||||
|
||||
ERR([ERROR]) -->|Retry / Cancel| IDLE
|
||||
CAN -->|Retry / Neu-Analyse| IDLE
|
||||
FIN -->|Neue Disc| IDLE
|
||||
|
||||
style FIN fill:#e8f5e9,stroke:#66bb6a,color:#2e7d32
|
||||
style ERR fill:#ffebee,stroke:#ef5350,color:#c62828
|
||||
style CAN fill:#fff3e0,stroke:#fb8c00,color:#e65100
|
||||
style WUD fill:#fff8e1,stroke:#ffa726,color:#e65100
|
||||
style ENC fill:#f3e5f5,stroke:#ab47bc,color:#6a1b9a
|
||||
style RIP fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
|
||||
style MIC fill:#e3f2fd,stroke:#42a5f5,color:#1565c0
|
||||
IDLE --> DISC_DETECTED
|
||||
DISC_DETECTED --> ANALYZING
|
||||
ANALYZING --> METADATA_SELECTION
|
||||
METADATA_SELECTION --> READY_TO_START
|
||||
READY_TO_START --> RIPPING
|
||||
READY_TO_START --> MEDIAINFO_CHECK
|
||||
MEDIAINFO_CHECK --> WAITING_FOR_USER_DECISION
|
||||
WAITING_FOR_USER_DECISION --> MEDIAINFO_CHECK
|
||||
MEDIAINFO_CHECK --> READY_TO_ENCODE
|
||||
READY_TO_ENCODE --> ENCODING
|
||||
ENCODING --> FINISHED
|
||||
ENCODING --> ERROR
|
||||
RIPPING --> ERROR
|
||||
RIPPING --> CANCELLED
|
||||
```
|
||||
|
||||
</div>
|
||||
---
|
||||
|
||||
## State-Liste
|
||||
|
||||
| State | Bedeutung |
|
||||
|------|-----------|
|
||||
| `IDLE` | Wartet auf Disc |
|
||||
| `DISC_DETECTED` | Disc erkannt |
|
||||
| `ANALYZING` | MakeMKV-Analyse läuft |
|
||||
| `METADATA_SELECTION` | Benutzer wählt Metadaten |
|
||||
| `WAITING_FOR_USER_DECISION` | Playlist-Auswahl nötig |
|
||||
| `READY_TO_START` | Übergangszustand vor Start |
|
||||
| `RIPPING` | MakeMKV-Rip läuft |
|
||||
| `MEDIAINFO_CHECK` | Quelle/Tracks werden ausgewertet |
|
||||
| `READY_TO_ENCODE` | Review ist bereit |
|
||||
| `ENCODING` | HandBrake läuft |
|
||||
| `FINISHED` | erfolgreich abgeschlossen |
|
||||
| `CANCELLED` | abgebrochen |
|
||||
| `ERROR` | fehlgeschlagen |
|
||||
|
||||
---
|
||||
|
||||
## UI-Badge-Bezeichnungen
|
||||
## Typische Pfade
|
||||
|
||||
Die Status-Badges im Dashboard verwenden diese Labels:
|
||||
### Standardfall (kein vorhandenes RAW)
|
||||
|
||||
| State | Badge-Label |
|
||||
|------|-------------|
|
||||
| `IDLE` | `Bereit` |
|
||||
| `DISC_DETECTED` | `Medium erkannt` |
|
||||
| `METADATA_SELECTION` | `Metadatenauswahl` |
|
||||
| `WAITING_FOR_USER_DECISION` | `Warte auf Auswahl` |
|
||||
| `READY_TO_START` | `Startbereit` |
|
||||
| `RIPPING` | `Rippen` |
|
||||
| `MEDIAINFO_CHECK` | `Mediainfo-Pruefung` |
|
||||
| `READY_TO_ENCODE` | `Bereit zum Encodieren` |
|
||||
| `ENCODING` | `Encodieren` |
|
||||
| `FINISHED` | `Fertig` |
|
||||
| `CANCELLED` | `Abgebrochen` |
|
||||
| `ERROR` | `Fehler` |
|
||||
| Queue (kein eigener State) | `In der Queue` |
|
||||
1. Disc erkannt
|
||||
2. Analyse + Metadaten
|
||||
3. `RIPPING`
|
||||
4. `MEDIAINFO_CHECK`
|
||||
5. `READY_TO_ENCODE`
|
||||
6. `ENCODING`
|
||||
7. `FINISHED`
|
||||
|
||||
### Vorhandenes RAW
|
||||
|
||||
`READY_TO_START` springt direkt zu `MEDIAINFO_CHECK` (kein neuer Rip).
|
||||
|
||||
### Mehrdeutige Blu-ray-Playlist
|
||||
|
||||
`MEDIAINFO_CHECK` -> `WAITING_FOR_USER_DECISION` bis Benutzer Playlist bestätigt.
|
||||
|
||||
---
|
||||
|
||||
## Zustandsbeschreibungen
|
||||
## Queue-Verhalten
|
||||
|
||||
### IDLE
|
||||
Wenn `pipeline_max_parallel_jobs` erreicht ist:
|
||||
|
||||
**Ausgangszustand.** Ripster wartet auf eine Disc.
|
||||
|
||||
- `diskDetectionService` pollt das Laufwerk im konfigurierten Intervall
|
||||
- Bei Disc-Erkennung: automatischer Übergang zu `DISC_DETECTED`
|
||||
- WebSocket-Event: `DISC_DETECTED`
|
||||
- Job-Aktionen werden als Queue-Einträge abgelegt
|
||||
- Queue kann zusätzlich Nicht-Job-Einträge enthalten (`script`, `chain`, `wait`)
|
||||
- Reihenfolge ist per API/UI änderbar
|
||||
|
||||
---
|
||||
|
||||
### DISC_DETECTED
|
||||
## Abbruch, Retry, Restart
|
||||
|
||||
**Disc erkannt, wartet auf Benutzeraktion.**
|
||||
|
||||
- Dashboard-Badge: **"Medium erkannt"**
|
||||
- Status-Text: **"Neue Disk erkannt"**
|
||||
- **"Analyse starten"**-Button wird aktiv
|
||||
- Kein Prozess läuft noch
|
||||
|
||||
**Übergang:** Benutzer klickt "Analyse starten" → `METADATA_SELECTION`
|
||||
|
||||
---
|
||||
|
||||
### METADATA_SELECTION
|
||||
|
||||
**Metadaten-Auswahl läuft.**
|
||||
|
||||
1. Job wird erstellt (`status = METADATA_SELECTION`)
|
||||
2. OMDb-Vorsuche mit erkanntem Disc-Label
|
||||
3. `MetadataSelectionDialog` öffnet sich mit vorgeladenen Ergebnissen
|
||||
4. Benutzer wählt Filmtitel (oder gibt manuell ein)
|
||||
5. Nach Bestätigung wird der Job automatisch für Start/Queue vorbereitet (`selectMetadata` + `startPreparedJob`)
|
||||
|
||||
**Übergang (automatisch nach Metadaten-Bestätigung):**
|
||||
|
||||
| Ergebnis | Nächster Zustand |
|
||||
|--------------------|-----------------|
|
||||
| Kein verwertbares RAW vorhanden | `READY_TO_START` → automatisch `RIPPING` (oder Queue) |
|
||||
| Verwertbares RAW vorhanden | `READY_TO_START` → automatisch `MEDIAINFO_CHECK` (oder Queue) |
|
||||
| Vorhandenes RAW + offene Playlist-Entscheidung | `WAITING_FOR_USER_DECISION` |
|
||||
|
||||
---
|
||||
|
||||
### WAITING_FOR_USER_DECISION
|
||||
|
||||
**Playlist-Obfuskierung erkannt – manuelle Auswahl erforderlich.**
|
||||
|
||||
!!! info "Neu seit „Skript Integration + UI Anpassungen""
|
||||
Dieser Zustand wurde eingeführt, um Blu-rays mit mehreren Playlists ähnlicher Länge korrekt zu behandeln.
|
||||
|
||||
- Playlist-Auswahl-Dialog wird im Dashboard angezeigt
|
||||
- Alle Kandidaten mit Score, Laufzeit und Bewertungslabel
|
||||
- Empfohlene Playlist ist vorausgewählt
|
||||
- Benutzer bestätigt mit **"Playlist übernehmen"**
|
||||
- Tritt häufig nach `MEDIAINFO_CHECK` auf (Backup-Analyse), seltener direkt nach `METADATA_SELECTION` bei vorhandenem RAW
|
||||
|
||||
**Darstellung im Dashboard:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Playlist-Auswahl erforderlich │
|
||||
│ Es wurden mehrere Titel mit ähnlicher Laufzeit gefunden. │
|
||||
├──────────┬──────────┬────────┬──────────────────────────┤
|
||||
│ Playlist │ Laufzeit │ Score │ Bewertung │
|
||||
├──────────┼──────────┼────────┼──────────────────────────┤
|
||||
│ ● 00800 │ 2:28:05 │ +18 │ wahrscheinlich korrekt │
|
||||
│ ○ 00801 │ 2:28:12 │ −4 │ Auffällige Segmentfolge │
|
||||
│ ○ 00900 │ 2:28:05 │ −32 │ Fake-Struktur │
|
||||
└──────────┴──────────┴────────┴──────────────────────────┘
|
||||
[Playlist übernehmen]
|
||||
```
|
||||
|
||||
**Übergang:** `selectMetadata(jobId, { selectedPlaylist })` setzt die Pipeline automatisch fort:
|
||||
|
||||
- mit vorhandenem RAW nach `MEDIAINFO_CHECK`
|
||||
- ohne RAW über `READY_TO_START` weiter Richtung `RIPPING`
|
||||
|
||||
Mehr Details: [Playlist-Analyse](playlist-analysis.md)
|
||||
|
||||
---
|
||||
|
||||
### READY_TO_START
|
||||
|
||||
**Übergangs-/Fallback-Zustand vor dem eigentlichen Start.**
|
||||
|
||||
- Wird nach Metadaten-Bestätigung kurz gesetzt
|
||||
- `startPreparedJob()` wird danach automatisch ausgeführt
|
||||
- Wenn Parallel-Limit erreicht ist, wird der Start stattdessen in die Queue eingereiht
|
||||
- **"Job starten"** ist primär für Sonderfälle/Fallback sichtbar
|
||||
|
||||
**Sonderfall – RAW-Datei bereits vorhanden:**
|
||||
Wenn für diesen Job bereits ein verwertbares RAW unter `raw_dir` existiert, wird Ripping übersprungen und direkt `MEDIAINFO_CHECK` gestartet.
|
||||
|
||||
**Übergang:** `startPreparedJob(jobId)` → `RIPPING` oder direkt `MEDIAINFO_CHECK`
|
||||
|
||||
---
|
||||
|
||||
### RIPPING
|
||||
|
||||
**MakeMKV rippt die Disc.**
|
||||
|
||||
=== "MKV-Modus (Standard)"
|
||||
|
||||
```bash
|
||||
makemkvcon mkv disc:0 all /path/to/raw/ --minlength=900 -r
|
||||
```
|
||||
|
||||
Erstellt MKV-Datei(en) direkt aus den gewählten Titeln.
|
||||
|
||||
=== "Backup-Modus"
|
||||
|
||||
```bash
|
||||
makemkvcon backup disc:0 /path/to/raw/backup/ --decrypt -r
|
||||
```
|
||||
|
||||
Erstellt vollständiges Disc-Backup inkl. Menüs.
|
||||
|
||||
**Live-Updates** aus MakeMKV-Ausgabe:
|
||||
|
||||
```
|
||||
PRGV:2048,0,65536 → Fortschritt-Berechnung
|
||||
PRGT:5011,0,"..." → Aktueller Task-Name
|
||||
```
|
||||
|
||||
**Typische Dauer:** DVD 20–45 min · Blu-ray 45–120 min
|
||||
|
||||
---
|
||||
|
||||
### MEDIAINFO_CHECK
|
||||
|
||||
**HandBrake-Scan und Encode-Plan-Erstellung.**
|
||||
|
||||
Dieser Zustand umfasst je nach Quelle mehrere Phasen:
|
||||
|
||||
1. Optional: Playlist-Auflösung bei Blu-ray-Backup (inkl. MakeMKV/HandBrake-Zuordnung)
|
||||
2. **HandBrake-Scan** (`HandBrakeCLI --scan`) auf RAW-Input
|
||||
3. **Encode-Plan-Erstellung** mit automatischer Track-Vorauswahl
|
||||
|
||||
Kein Benutzereingriff – läuft automatisch durch.
|
||||
|
||||
**Übergänge:**
|
||||
|
||||
- Eindeutige Quelle/Titelwahl möglich → `READY_TO_ENCODE`
|
||||
- Mehrdeutige Playlist erkannt → `WAITING_FOR_USER_DECISION`
|
||||
|
||||
---
|
||||
|
||||
### READY_TO_ENCODE
|
||||
|
||||
**Encode-Plan bereit.**
|
||||
|
||||
Das `MediaInfoReviewPanel` zeigt:
|
||||
|
||||
- **Titel-Auswahl** (bei Discs mit mehreren langen Titeln)
|
||||
- **Audio-Tracks** mit Encoder-Vorschau (Copy/Transcode/Fallback)
|
||||
- **Untertitel-Tracks** mit Flags (Einbrennen, Forced, Default)
|
||||
- **Post-Encode-Skripte** – Auswahl und Reihenfolge der auszuführenden Skripte
|
||||
|
||||
Im Frontend startet **"Encoding starten"** (bzw. **"Backup + Encoding starten"** im Pre-Rip-Modus) den nächsten Schritt.
|
||||
Falls die Review noch nicht bestätigt wurde, wird `confirmEncodeReview(...)` automatisch vor dem Start aufgerufen.
|
||||
|
||||
**Übergang:** `startPreparedJob(jobId)` → `ENCODING` (oder im Pre-Rip-Fall zuerst `RIPPING`)
|
||||
|
||||
---
|
||||
|
||||
### ENCODING
|
||||
|
||||
**HandBrake encodiert die Datei.**
|
||||
|
||||
```bash
|
||||
HandBrakeCLI \
|
||||
-i <quelle> -o <ziel> \
|
||||
-t <titelId> \
|
||||
--preset "H.265 MKV 1080p30" \
|
||||
-a 1,2 -E copy:ac3,av_aac \
|
||||
-s 1 --subtitle-default 1
|
||||
```
|
||||
|
||||
**Live-Updates** aus HandBrake-stderr:
|
||||
|
||||
```
|
||||
Encoding: task 1 of 1, 73.50 % (45.23 fps, avg 44.12 fps, ETA 00h12m34s)
|
||||
```
|
||||
|
||||
Post-Encode-Skripte werden innerhalb dieses Zustands sequenziell ausgeführt (kein separater Pipeline-State).
|
||||
|
||||
!!! note "Skriptfehler"
|
||||
Skriptfehler führen zum Abbruch der Skriptkette, der Job bleibt jedoch im Abschlusszustand `FINISHED` mit entsprechendem Hinweis im Status-Text/Log.
|
||||
|
||||
---
|
||||
|
||||
### FINISHED
|
||||
|
||||
**Job erfolgreich abgeschlossen.**
|
||||
|
||||
- Ausgabedatei liegt im konfigurierten `movie_dir`
|
||||
- Job-Status in Datenbank: `FINISHED`
|
||||
- PushOver-Benachrichtigung (falls konfiguriert)
|
||||
- WebSocket-Event: `PIPELINE_STATE_CHANGED` (State `FINISHED`)
|
||||
|
||||
---
|
||||
|
||||
### CANCELLED
|
||||
|
||||
**Job wurde vom Benutzer abgebrochen.**
|
||||
|
||||
- Entsteht bei aktivem Abbruch (`/api/pipeline/cancel`) während laufender Phase
|
||||
- Job-Status in Datenbank: `CANCELLED`
|
||||
- Im Dashboard stehen danach u. a. `Retry Rippen`, `Review neu starten` oder `Encode neu starten` (kontextabhängig) zur Verfügung
|
||||
|
||||
---
|
||||
|
||||
### ERROR
|
||||
|
||||
**Fehler aufgetreten.**
|
||||
|
||||
- Fehlerdetails im Job-Datensatz gespeichert
|
||||
- Fehler-Logs in History abrufbar
|
||||
- **Retry**: Neustart vom Fehlerzustand
|
||||
- **Neu analysieren**: Disc erneut als neuer Job starten
|
||||
|
||||
---
|
||||
|
||||
## Abbrechen & Retry
|
||||
|
||||
### Pipeline abbrechen
|
||||
|
||||
```http
|
||||
POST /api/pipeline/cancel
|
||||
```
|
||||
|
||||
- SIGINT → graceful exit (Timeout: 10 s) → SIGKILL
|
||||
- Laufender Job landet in `CANCELLED` (oder Queue-Eintrag wird entfernt, falls noch nicht gestartet)
|
||||
|
||||
### Job wiederholen
|
||||
|
||||
```http
|
||||
POST /api/pipeline/retry/:jobId
|
||||
```
|
||||
|
||||
- Startet den Job neu in `RIPPING` (oder reiht den Retry in die Queue ein)
|
||||
- Metadaten bleiben erhalten; Encode-/Scan-Daten werden neu erzeugt
|
||||
|
||||
### Re-Encode
|
||||
|
||||
```http
|
||||
POST /api/pipeline/reencode/:jobId
|
||||
```
|
||||
|
||||
- Encodiert bestehende Raw-MKV neu
|
||||
- Ermöglicht neue Track-Auswahl und andere Skripte
|
||||
- Kein Ripping erforderlich
|
||||
- `cancel`: laufenden Job abbrechen oder Queue-Eintrag entfernen
|
||||
- `retry`: Fehler-/Abbruch-Job neu starten
|
||||
- `reencode`: aus vorhandenem RAW neu encodieren
|
||||
- `restart-review`: Review aus RAW neu aufbauen
|
||||
- `restart-encode`: Encoding mit letzter bestätigter Auswahl neu starten
|
||||
|
||||
Reference in New Issue
Block a user