#!/usr/bin/env python3 """HMS MediaEngine – Einstiegspunkt. Startet Engine (GStreamer), Art-Net-Empfang, Web-Server (UI/API/Upload). Alle Einstellungen (inkl. Art-Net-Mapping) sind in der Web-UI konfigurierbar und werden in config.json gespeichert. Usage: python3 run.py # Standardstart (UI im Browser) python3 run.py video1.mp4 # Dateien importieren + als Layer python3 run.py --setup # Nativer Dialog (Port/Fullscreen) python3 run.py --bootstrap # GStreamer pruefen/installieren python3 run.py --generate-setup # Setup-Skripte erzeugen """ from __future__ import annotations import argparse import signal import sys import time from pathlib import Path ROOT = Path(__file__).resolve().parent sys.path.insert(0, str(ROOT)) # Portable GStreamer-Installation (HMS-Portable-Install.command) # laedt die Umgebungsvariablen, BEVOR gi importiert wird. _RUNTIME = ROOT / "runtime" _FW = _RUNTIME / "GStreamer.framework" / "Versions" / "1.0" if _FW.is_dir(): import os as _os _lib = str(_FW / "lib") _os.environ["DYLD_LIBRARY_PATH"] = _lib + ":" + _os.environ.get("DYLD_LIBRARY_PATH", "") _gst = str(_FW / "lib" / "gstreamer-1.0") _os.environ["GST_PLUGIN_PATH"] = _gst _os.environ["GST_PLUGIN_SYSTEM_PATH_1_0"] = _gst _gi_types = str(_FW / "lib" / "girepository-1.0") _os.environ["GI_TYPELIB_PATH"] = _gi_types + ":" + _os.environ.get("GI_TYPELIB_PATH", "") for _v in ("python3.12", "python3.13", "python3.11"): _sp = _FW / "lib" / _v / "site-packages" if (_sp / "gi").is_dir(): sys.path.insert(0, str(_sp)) break print(f"[Start] Portable GStreamer aktiv: {_FW}") import gi # noqa: E402 gi.require_version("Gst", "1.0") from gi.repository import Gst # noqa: E402 Gst.init(None) from hms_app import config as cfgmod # noqa: E402 from hms_app.artnet import ArtNetInput # noqa: E402 from hms_app.engine import MediaEngine # noqa: E402 from hms_app.media import MediaLibrary # noqa: E402 from hms_app.server import EngineServer # noqa: E402 def main() -> int: ap = argparse.ArgumentParser(description="HMS MediaEngine") ap.add_argument("files", nargs="*", help="Medien-Dateien importieren") ap.add_argument("--setup", action="store_true", help="Nativer Einstellungsdialog (tkinter)") ap.add_argument("--bootstrap", action="store_true", help="GStreamer pruefen/nachinstallieren") ap.add_argument("--generate-setup", action="store_true", help="setup_windows.ps1 / setup_linux.sh erzeugen") ap.add_argument("--port", type=int, default=None, help="Web-Port ueberschreibt config.json") ap.add_argument("--data-root", type=str, default=None, help="Eigener Datenordner fuer mehrere Instanzen " "(config/media/projekte dort statt neben run.py)") args = ap.parse_args() if args.generate_setup: from hms_app import setup as setupmod setupmod.generate_setup_scripts(ROOT) return 0 if args.bootstrap: from hms_app import setup as setupmod ok = setupmod.check_and_install_dependencies() return 0 if ok else 1 DATA_ROOT = (Path(args.data_root).expanduser().resolve() if args.data_root else ROOT) if DATA_ROOT != ROOT: DATA_ROOT.mkdir(parents=True, exist_ok=True) settings = cfgmod.load(DATA_ROOT) if args.port is not None: settings["web"]["port"] = args.port if args.setup: from hms_app import setup as setupmod cfg = setupmod.run_setup(settings) settings["web"]["port"] = cfg["port"] settings["output"]["fullscreen"] = cfg["fullscreen"] cfgmod.save(DATA_ROOT, settings) library = MediaLibrary(settings, DATA_ROOT) library.ensure_dirs() engine = MediaEngine(settings, library) artnet = ArtNetInput(engine, settings) server = EngineServer(engine, artnet, library, settings, DATA_ROOT) # CLI-Dateien importieren und als Layer aktivieren for f in args.files: p = Path(f).expanduser() name = library.import_copy(p) if p.exists() else None if name: engine.add_layer(name) print(f"[Start] Importiert + Layer: {name}") else: print(f"[Start] Import fehlgeschlagen: {f}") engine.rebuild() # Autostart-Projekt laden (falls konfiguriert und vorhanden) auto = settings["engine"].get("autostart_project") if auto: from hms_app.projects import ProjectStore doc = ProjectStore(DATA_ROOT).load(str(auto)) if doc is not None: if engine.load_state(doc.get("state") or {}): print(f"[Start] Autostart-Projekt geladen: {auto}") else: print(f"[Start] Autostart-Projekt fehlgeschlagen: {auto}") else: print(f"[Start] Autostart-Projekt nicht gefunden: {auto}") artnet.start() if not server.start(): return 1 running = True def stop_handler(_sig, _frame): nonlocal running running = False signal.signal(signal.SIGINT, stop_handler) signal.signal(signal.SIGTERM, stop_handler) print() print("=" * 62) print(" HMS MediaEngine laeuft") print(f" UI : http://localhost:{settings['web']['port']}/") print(f" Art-Net : UDP {settings['artnet']['listen_port']} " f"(Universes {settings['artnet']['universes']})") print(" Einstellungen (Art-Net, Preview, Layer, Upload): Web-UI") print(" Beenden : Strg+C") print("=" * 62) try: while running: time.sleep(1) except KeyboardInterrupt: pass finally: print("\nBeende...") server.stop() artnet.stop() engine.stop() print("Beendet.") return 0 if __name__ == "__main__": sys.exit(main())