- 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
8.4 KiB
8.4 KiB
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 togglebarer 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):- Audiospuren prüfen → deutsche/erste Spur bestimmen (Modus = User-Entscheidung, s.u.).
- Wenn >1 Audiospur: remux (stream-copy, kein Re-Encode) → behält Video + 1 Audio (+ optional dt. Untertitel) → Temp-Datei → atomar ersetzen.
.DL.aus dem Dateinamen strippen (.DL.→.,.DL→``), Companion-Dateien (Untertitel/.nfo) mitziehen.- 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(spiegeltextractor.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_BINenv + lazyffmpeg -version-Probe gecacht (spiegeltRD_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)+extractOsPrioritywiederverwenden, Priorität als expliziten Param (nicht das Modul-GlobalcurrentExtractCpuPriority— Cross-Talk-Gefahr). Honoriertsettings.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 aufpkg.extractDiroperieren — NIE im geteiltenmkvLibraryDir(= 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.deriveCleanCollectFileNamebekommt 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 viarenameCompanionFiles/moveCompanionFilesmitziehen.
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.utimesnach 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)
src/shared/types.tsAppSettings:keepGermanAudioOnly: boolean(+ ggf.germanAudioMode,keepGermanSubs,ffmpegPath).src/main/constants.tsdefaultSettings:keepGermanAudioOnly: falseetc.src/main/storage.tsnormalizeSettings:Boolean(...)(Pfad:asText, NICHT normalizeAbsoluteDir → leer = System-ffmpeg).src/renderer/App.tsxSettings-Tab „entpacken" neben collectMkvToLibrary: Toggle + eingerückte Sub-Optionen (disabled wenn aus).src/renderer/App.tsxemptySnapshot()-Literal (~840-859) — sonst tsc-Fehler (Feld non-optional).- (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.mockin download-manager.test.ts (assert: korrekt aufgerufen + Sequenz nach autoRename / vor collect, Deferred + Hybrid). KEIN blankesvi.mock("node:child_process")in download-manager.test.ts (bricht echte Extractor-ZIP-Tests). - Separate
video-processor.test.ts:node:child_processmocken → 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-checkgrü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_BINenv vs. zusätzlich Settings-Pfad-Feld im UI.
10. Umsetzungsreihenfolge (nach Entscheidungen)
video-processor.ts+ pure Helfer + deren Unit-Tests (TDD).- ffmpeg/ffprobe-Discovery (probe+cache).
- Settings-Wiring (5 Stellen) + UI-Toggle.
- Einhängen in Deferred + Hybrid (in chainPackageFileOp), Gate OR-en.
- collect deriveCleanCollectFileName:
.DL.-Strip-Safety-Net. - Logging (logRenameProcess, neuer Stage 'audio-strip').
- Tests (download-manager mock + video-processor args + negativ/edge). Gate prüfen.