real-debrid-downloader/tasks/plan-german-audio-track.md
Sucukdeluxe 77661389f3 Add "keep only German audio" post-extract step for .DL. files
- New video-processor.ts: ffmpeg/ffprobe remux that keeps only the German
  audio track (by language tag, with safe fallbacks) and strips the ".DL."
  marker from the filename
- Runs after extraction in both the deferred and hybrid post-process paths,
  inside the per-package file-op chain; abortable, disk-space checked,
  mtime-preserving, atomic temp->replace so the original is never lost
- System ffmpeg via PATH / RD_FFMPEG_BIN; toggle + track-mode select in settings
2026-06-07 21:17:26 +02:00

8.4 KiB
Raw Blame History

Plan: „Nur deutsche Tonspur behalten" (.DL.) als Tool-Funktion

Quelle der Idee: User-Script Remove Non German Audio.py (ffmpeg -map 0:v:0 -map 0:a:0 -c copy -map_metadata -1, + .DL.. Rename). Soll als toggle­barer Post-Extract-Schritt nach jedem Entpacken laufen, nur für MKV/MP4 mit .DL. im Namen (Dual-Language), und nur die deutsche Spur behalten. Fundiert per 6-Agent-Analyse + Advisor.

1. Verhalten (Soll)

  • Läuft automatisch nach dem Entpacken eines Pakets (wenn Toggle an), bevor MKV-Collect.
  • Pro extrahierter Video-Datei mit .DL. im Namen (case-insensitive, nur .mkv/.mp4):
    1. Audiospuren prüfen → deutsche/erste Spur bestimmen (Modus = User-Entscheidung, s.u.).
    2. Wenn >1 Audiospur: remux (stream-copy, kein Re-Encode) → behält Video + 1 Audio (+ optional dt. Untertitel) → Temp-Datei → atomar ersetzen.
    3. .DL. aus dem Dateinamen strippen (.DL.., .DL→``), Companion-Dateien (Untertitel/.nfo) mitziehen.
    4. Wenn nur 1 Audiospur: kein Remux (spart Neuschreiben großer Dateien), ABER .DL.-Strip trotzdem.
  • Status pro Item sichtbar (z.B. „Tonspur wird bereinigt" / „Deutsche Spur behalten").

2. Architektur

  • NEUES Modul src/main/video-processor.ts (spiegelt extractor.ts: exportierte async-Funktion
    • Options-Bag, KEINE DI-Klasse — es gibt keinen Constructor-Seam). Enthält:
    • ffmpeg/ffprobe-Spawn nach dem runExtractCommand-Muster (extractor.ts:1296): spawn(cmd,args,{windowsHide:true}), Promise-Wrapper, Timeout-Watchdog → killProcessTree (taskkill /T /F), AbortSignal IN den Child geben.
    • Pure exportierte Helfer für Unit-Tests: pickGermanAudioTrack(probeJson, mode), stripDualLangMarker(name), buildFfmpegRemuxArgs(...), computeRemuxTimeoutMs(bytes).
    • ffmpeg-Exit-Codes ≠ 7-Zip (NICHT die „exit 1 = ok"-Logik kopieren — nur das Spawn/Await/Kill-Gerüst).
    • ffprobe-JSON auf stdout NICHT durch den 48KB-Tail-Cap (appendLimited) — stdout separat voll puffern.
  • ffmpeg-Discovery (Option a, empfohlen): System-PATH + RD_FFMPEG_BIN env + lazy ffmpeg -version-Probe gecacht (spiegelt RD_7Z_BIN, extractor.ts:1030-1083). Nicht bündeln (~80-150MB → triggert den eigenen 150MB-Large-Bundle-Selfcheck debug-setup.ts:22 + GPL-Lizenzpflicht). Wenn ffmpeg fehlt → Schritt überspringen + WARN loggen + (optional) in Health-Check/Errors surfacen. NIE Downloads blockieren.
  • CPU-Priorität: lowerExtractProcessPriority(pid, priority) + extractOsPriority wiederverwenden, Priorität als expliziten Param (nicht das Modul-Global currentExtractCpuPriority — Cross-Talk-Gefahr). Honoriert settings.extractCpuPriority.

3. Einhängepunkte (BEIDE Pfade — kritisch!)

Post-Processing ist pro Paket, zwei Pfade; Hybrid-Pakete durchlaufen NIE den Deferred-Pass:

  • Deferred (download-manager.ts ~11614): nach autoRenameExtractedVideoFiles, VOR archive-cleanup/collect.
  • Hybrid (download-manager.ts ~10944): zwischen Rename und Collect im detached Block.
  • Beide: innerhalb chainPackageFileOp(pkg.id, ...) (serialisiert Datei-Ops pro Paket), nur auf pkg.extractDir operieren — NIE im geteilten mkvLibraryDir (= der v1.7.107-revertierte Cross-Package-Crash; autoRename bricht bei Overlap ab, 3905-3919).
  • Gate: neuen Flag in den Post-Process-Aggregator OR-en (~7078-7084), sonst läuft der Schritt nie standalone. Hängt inhärent an autoExtract (braucht entpackte Dateien).
  • Datei-Enumeration: collectVideoFiles(rootDir) (rekursiv, SAMPLE_VIDEO_EXTENSIONS, constants.ts:28) — nur .mkv/.mp4 verarbeiten; Sample/Bonus-Dateien per vorhandenem Skip-Prädikat auslassen.

4. Der .DL.-Knoten (LÖST den „Feature no-op"-Fehler)

  • Selektion = „Datei hat .DL."; der Schritt strippt .DL.. → KEIN früherer Schritt darf den Marker entfernen.
  • autoRename NICHT ändern (behält .DL. verbatim) → Marker überlebt bis zum Video-Schritt.
  • Video-Schritt läuft nach autoRename → sieht .DL. → remuxt + strippt .DL. atomar pro Datei.
  • NUR collectMkvFilesToLibrary.deriveCleanCollectFileName bekommt den .DL.-Strip als Post-Transform (läuft NACH dem Video-Schritt → kann den Selektor nicht brechen, verhindert nur Re-Einführung aus dem Ordner-Token). Companion-Files via renameCompanionFiles/moveCompanionFiles mitziehen.

5. Sicherheitsmodell (Original NIE verlieren)

  • Remux → Temp-Datei → Größe > 0 (idealerweise ~plausibel) prüfen → erst dann atomar ersetzen/umbenennen (renamePathWithExdevFallback + verifyRenameAsync). ffmpeg-Fehler/Abbruch → Temp löschen, Original bleibt.
  • Disk-Space-Pre-Check: vor Remux freien Platz ≥ Dateigröße (+Marge) prüfen, sonst skip+log (Temp verdoppelt transient den Platz auf einer Platte, die grad entpackt hat / parallel lädt).
  • AbortSignal in den ffmpeg-Child (Deferred-/Hybrid-Controller) → Stop/Cancel/Reset killt laufenden Remux.
  • mtime erhalten (fs.utimes nach Remux) → sonst überspringt Hybrid-Collect (deferFreshFiles=true) die frisch angefasste Datei.
  • Sicherheits-Invariante (BEIDE Modi): Original nur ersetzen, wenn die behaltene Spur sicher die richtige ist. Bei Unsicherheit (keine Tags / kein Deutsch gefunden) → Datei UNANGETASTET lassen + loggen, statt versehentlich die einzige brauchbare Spur zu löschen.
  • Dispositions-Flag der behaltenen Spur auf „default" setzen.
  • Best-effort pro Datei: ein Fehler markiert NICHT das Paket als failed und blockiert nicht den Collect anderer Dateien.

6. ffmpeg/ffprobe-Aufrufe (Stream-Copy, schnell)

  • Probe (nur im Tag-Modus): ffprobe -v error -select_streams a -show_entries stream=index:stream_tags=language,title -of json INPUT
  • Remux erste Spur (Script-Parität): ffmpeg -i INPUT -map 0:v:0 -map 0:a:0 [-map 0:s? je nach Untertitel-Option] -c copy -map_metadata -1 -disposition:a:0 default -y TEMP
  • Remux deutsche Spur (Tag-Modus): -map 0:v:0 -map 0:a:<dt-Index> ... (Index aus ffprobe).

7. Settings/UI-Wiring (5 Pflicht-Stellen, +1 optional)

  1. src/shared/types.ts AppSettings: keepGermanAudioOnly: boolean (+ ggf. germanAudioMode, keepGermanSubs, ffmpegPath).
  2. src/main/constants.ts defaultSettings: keepGermanAudioOnly: false etc.
  3. src/main/storage.ts normalizeSettings: Boolean(...) (Pfad: asText, NICHT normalizeAbsoluteDir → leer = System-ffmpeg).
  4. src/renderer/App.tsx Settings-Tab „entpacken" neben collectMkvToLibrary: Toggle + eingerückte Sub-Optionen (disabled wenn aus).
  5. src/renderer/App.tsx emptySnapshot()-Literal (~840-859) — sonst tsc-Fehler (Feld non-optional).
  6. (optional) src/main/support-data.ts ~95: Flag in Diagnose-Export spiegeln.

8. Tests + Verifikations-Gate

  • ffmpeg in Tests gemockt (kein echter ffmpeg-Lauf): neues Modul via vi.mock in download-manager.test.ts (assert: korrekt aufgerufen + Sequenz nach autoRename / vor collect, Deferred + Hybrid). KEIN blankes vi.mock("node:child_process") in download-manager.test.ts (bricht echte Extractor-ZIP-Tests).
  • Separate video-processor.test.ts: node:child_process mocken → ffmpeg/ffprobe-ARGS asserten (Track-Wahl, Untertitel-Option).
  • Pure Helfer fs-frei testen (wie tests/auto-rename.test.ts): pickGermanAudioTrack, stripDualLangMarker.
  • Negativ-Test: Toggle aus → keine Verarbeitung. Edge: 1-Audio-.DL. → nur Rename, kein Remux. Kein-Deutsch → unangetastet.
  • Gate: tsc-Baseline = 6 vorbestehende Fehler (NICHT clean) → „keine NEUEN tsc-Fehler" + vitest 728→728+N grün + npm run self-check grün.

9. OFFENE ENTSCHEIDUNGEN (vor Bau — per AskUserQuestion)

  • A. Spurauswahl: Script-Parität (immer erste Audiospur, kein ffprobe, validiertes Verhalten) vs. Smart (deutsche Spur per Sprach-Tag, Fallback erste Spur, skip wenn kein Deutsch).
  • B. Untertitel: weglassen (wie Script) vs. deutsche Untertitel behalten.
  • C. ffmpeg-Quelle: nur System-PATH + RD_FFMPEG_BIN env vs. zusätzlich Settings-Pfad-Feld im UI.

10. Umsetzungsreihenfolge (nach Entscheidungen)

  1. video-processor.ts + pure Helfer + deren Unit-Tests (TDD).
  2. ffmpeg/ffprobe-Discovery (probe+cache).
  3. Settings-Wiring (5 Stellen) + UI-Toggle.
  4. Einhängen in Deferred + Hybrid (in chainPackageFileOp), Gate OR-en.
  5. collect deriveCleanCollectFileName: .DL.-Strip-Safety-Net.
  6. Logging (logRenameProcess, neuer Stage 'audio-strip').
  7. Tests (download-manager mock + video-processor args + negativ/edge). Gate prüfen.