"""Renderer-Pipeline-Definitionen (PLAN.md §12, §13, §36 Nr. 4–5). Windows-Primärpfad (§12.6): d3d11h264dec → D3D11Memory → d3d11compositor → d3d11videosink. Kein CPU-Rundweg (Decoder → RAM → Upload verboten). DevGL-Pfad: Nur für Entwicklung/CI-Umgebungen ohne D3D11; ausdrücklich gekennzeichnet, kein stiller Ersatz im Normalbetrieb (§5.2, §33). """ from __future__ import annotations import shutil from dataclasses import dataclass @dataclass(frozen=True) class D3D11Pipeline: """Primärpipeline Windows: durchgängig D3D11Memory.""" video_a: str video_b: str width: int = 1920 height: int = 1080 fullscreen: bool = True def launch_string(self) -> str: sink = "d3d11videosink fullscreen=true" if self.fullscreen else "d3d11videosink" # Zwei Quellen → Compositor → Ausgabe (§36 Nr. 5: zwei Videos GPU-mischen) return ( f"d3d11compositor name=mix sink_0::xpos=0 sink_0::ypos=0 " f"sink_0::width={self.width // 2} sink_0::height={self.height} " f"sink_1::xpos={self.width // 2} sink_1::ypos=0 " f"sink_1::width={self.width // 2} sink_1::height={self.height} ! " f"d3d11convert ! video/x-raw(memory:D3D11Memory),format=RGBA," f"width={self.width},height={self.height} ! {sink} " f"uridecodebin uri=file:///{self.video_a} ! queue ! " f"d3d11convert ! mix. " f"uridecodebin uri=file:///{self.video_b} ! queue ! " f"d3d11convert ! mix." ) @dataclass(frozen=True) class DevGLPipeline: """Entwicklungspfad ohne D3D11 (explizit gekennzeichnet, kein Normalpfad). Nur für CI/Dev ohne Windows-GPU; die Backend-Schnittstelle bleibt identisch (§12.6).""" video_a: str video_b: str width: int = 960 height: int = 540 def launch_string(self) -> str: return ( f"glvideomixer name=mix ! glimagesink " f"uridecodebin uri=file:///{self.video_a} ! queue ! glupload ! mix. " f"uridecodebin uri=file:///{self.video_b} ! queue ! glupload ! mix." ) def build_single_video_pipeline(video: str, d3d11: bool = True) -> str: """Minimale Einzelquellen-Pipeline (§36 Nr. 4: Testvideo → Vollbild).""" if d3d11: return f"uridecodebin uri=file:///{video} ! d3d11convert ! d3d11videosink fullscreen=true" return f"uridecodebin uri=file:///{video} ! glupload ! glimagesink" def build_compositor_pipeline(video_a: str, video_b: str, d3d11: bool = True) -> str: """Zwei-Quellen-Compositing (§36 Nr. 5: GPU-Mischen ohne CPU-Readback).""" if d3d11: return D3D11Pipeline(video_a=video_a, video_b=video_b).launch_string() return DevGLPipeline(video_a=video_a, video_b=video_b).launch_string() def gst_available() -> bool: """True, wenn ein GStreamer-CLI auf dem System liegt (Diagnose, kein Fake).""" return shutil.which("gst-launch-1.0") is not None