Files
hms-mediaengine/apps/renderer/hms_renderer/pipelines.py
T
HMS MediaEngine Agent 0922cc1d68 Phase 0: Repository-Initialisierung nach Bauplan v1.2
- Struktur gemäß §8 (Eigentumsgrenzen), PLAN.md als normative Basis
- Pflichtdokumente: STATUS.md, ERRORS.md, TEST_REPORT.md, CHANGELOG.md, ADRs
- ADR-0001 Python 3.13-Pin, ADR-0002 GStreamer 1.28.6-Pin (Windows),
  ADR-0003 IPC TCP+MessagePack v1
- Kernpakete: hms_protocol, hms_domain, hms_parameter, hms_artnet,
  hms_adaptive, hms_capabilities, hms_plugin_sdk
- Renderer-Spike: D3D11-Primärpfad + Dev-GL-Pfad (§36 Nr. 4-5)
- Control Core: FastAPI REST + WebSocket (§36 Nr. 9)
- Beispielplugins: Passthrough + Gaussian Blur (3 Adaptive-Quality-
  Varianten, HLSL/GLSL/GLES)
- Tools: Art-Net-Emulator, Fixture-Generator (Master32/Layer64-CSV),
  Capability-Probe
- JSON-Schemas: IPC, Plugin, Projekt, Cluster
- 121 Unit-/Integrationstests grün, Ruff grün

Gate 0 bleibt offen: Hardwaremessungen nur auf echter Windows-Referenz-
hardware gültig (§29.7, §33).
2026-09-11 00:36:59 +02:00

80 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Renderer-Pipeline-Definitionen (PLAN.md §12, §13, §36 Nr. 45).
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