2026-09-11 09:36:04 +02:00
|
|
|
|
#!/usr/bin/env python3
|
2026-09-11 11:05:01 +02:00
|
|
|
|
"""HMS MediaEngine – Einstiegspunkt.
|
2026-09-11 09:42:51 +02:00
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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.
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
Usage:
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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
|
2026-09-11 09:36:04 +02:00
|
|
|
|
"""
|
|
|
|
|
|
|
2026-09-11 09:42:51 +02:00
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-09-11 09:36:04 +02:00
|
|
|
|
import argparse
|
|
|
|
|
|
import signal
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
|
|
|
|
sys.path.insert(0, str(ROOT))
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
import gi # noqa: E402
|
2026-09-11 09:36:04 +02:00
|
|
|
|
gi.require_version("Gst", "1.0")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
from gi.repository import Gst # noqa: E402
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
Gst.init(None)
|
|
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-09-11 09:42:51 +02:00
|
|
|
|
def main() -> int:
|
|
|
|
|
|
ap = argparse.ArgumentParser(description="HMS MediaEngine")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
ap.add_argument("files", nargs="*", help="Medien-Dateien importieren")
|
2026-09-11 09:42:51 +02:00
|
|
|
|
ap.add_argument("--setup", action="store_true",
|
2026-09-11 11:05:01 +02:00
|
|
|
|
help="Nativer Einstellungsdialog (tkinter)")
|
2026-09-11 09:42:51 +02:00
|
|
|
|
ap.add_argument("--bootstrap", action="store_true",
|
2026-09-11 11:05:01 +02:00
|
|
|
|
help="GStreamer pruefen/nachinstallieren")
|
2026-09-11 09:42:51 +02:00
|
|
|
|
ap.add_argument("--generate-setup", action="store_true",
|
|
|
|
|
|
help="setup_windows.ps1 / setup_linux.sh erzeugen")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
ap.add_argument("--port", type=int, default=None,
|
|
|
|
|
|
help="Web-Port ueberschreibt config.json")
|
2026-09-11 13:11:54 +02:00
|
|
|
|
ap.add_argument("--data-root", type=str, default=None,
|
|
|
|
|
|
help="Eigener Datenordner fuer mehrere Instanzen "
|
|
|
|
|
|
"(config/media/projekte dort statt neben run.py)")
|
2026-09-11 09:42:51 +02:00
|
|
|
|
args = ap.parse_args()
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
if args.generate_setup:
|
2026-09-11 11:05:01 +02:00
|
|
|
|
from hms_app import setup as setupmod
|
|
|
|
|
|
setupmod.generate_setup_scripts(ROOT)
|
2026-09-11 09:36:04 +02:00
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
if args.bootstrap:
|
2026-09-11 11:05:01 +02:00
|
|
|
|
from hms_app import setup as setupmod
|
|
|
|
|
|
ok = setupmod.check_and_install_dependencies()
|
|
|
|
|
|
return 0 if ok else 1
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
2026-09-11 13:11:54 +02:00
|
|
|
|
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)
|
2026-09-11 11:05:01 +02:00
|
|
|
|
if args.port is not None:
|
|
|
|
|
|
settings["web"]["port"] = args.port
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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"]
|
2026-09-11 13:11:54 +02:00
|
|
|
|
cfgmod.save(DATA_ROOT, settings)
|
2026-09-11 11:05:01 +02:00
|
|
|
|
|
2026-09-11 13:11:54 +02:00
|
|
|
|
library = MediaLibrary(settings, DATA_ROOT)
|
2026-09-11 11:05:01 +02:00
|
|
|
|
library.ensure_dirs()
|
|
|
|
|
|
engine = MediaEngine(settings, library)
|
|
|
|
|
|
artnet = ArtNetInput(engine, settings)
|
2026-09-11 13:11:54 +02:00
|
|
|
|
server = EngineServer(engine, artnet, library, settings, DATA_ROOT)
|
2026-09-11 11:05:01 +02:00
|
|
|
|
|
|
|
|
|
|
# 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()
|
2026-09-11 12:55:50 +02:00
|
|
|
|
|
|
|
|
|
|
# Autostart-Projekt laden (falls konfiguriert und vorhanden)
|
|
|
|
|
|
auto = settings["engine"].get("autostart_project")
|
|
|
|
|
|
if auto:
|
|
|
|
|
|
from hms_app.projects import ProjectStore
|
2026-09-11 13:11:54 +02:00
|
|
|
|
doc = ProjectStore(DATA_ROOT).load(str(auto))
|
2026-09-11 12:55:50 +02:00
|
|
|
|
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}")
|
|
|
|
|
|
|
2026-09-11 09:42:51 +02:00
|
|
|
|
artnet.start()
|
2026-09-11 11:05:01 +02:00
|
|
|
|
if not server.start():
|
|
|
|
|
|
return 1
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
running = True
|
2026-09-11 09:42:51 +02:00
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
def stop_handler(_sig, _frame):
|
2026-09-11 09:36:04 +02:00
|
|
|
|
nonlocal running
|
|
|
|
|
|
running = False
|
|
|
|
|
|
|
2026-09-11 09:42:51 +02:00
|
|
|
|
signal.signal(signal.SIGINT, stop_handler)
|
|
|
|
|
|
signal.signal(signal.SIGTERM, stop_handler)
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
2026-09-11 09:42:51 +02:00
|
|
|
|
print()
|
2026-09-11 11:05:01 +02:00
|
|
|
|
print("=" * 62)
|
2026-09-11 09:42:51 +02:00
|
|
|
|
print(" HMS MediaEngine laeuft")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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")
|
2026-09-11 09:42:51 +02:00
|
|
|
|
print(" Beenden : Strg+C")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
print("=" * 62)
|
2026-09-11 09:36:04 +02:00
|
|
|
|
|
|
|
|
|
|
try:
|
2026-09-11 11:05:01 +02:00
|
|
|
|
while running:
|
2026-09-11 09:36:04 +02:00
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
|
pass
|
|
|
|
|
|
finally:
|
|
|
|
|
|
print("\nBeende...")
|
|
|
|
|
|
server.stop()
|
2026-09-11 09:42:51 +02:00
|
|
|
|
artnet.stop()
|
|
|
|
|
|
engine.stop()
|
2026-09-11 09:36:04 +02:00
|
|
|
|
print("Beendet.")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
sys.exit(main())
|