2026-09-11 19:41:26 +02:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""Gemeinsame Launcher-Logik fuer Windows-Starter und macOS.
|
|
|
|
|
|
|
|
|
|
|
|
Unsichtbarer Ablauf: GStreamer vorhanden -> Engine im Hintergrund
|
|
|
|
|
|
starten, auf die Web-UI warten, Browser oeffnen.
|
|
|
|
|
|
Fehlt etwas -> grafischer Installer (installer_gui.py) oeffnen.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
import subprocess
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
import urllib.request
|
|
|
|
|
|
import webbrowser
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
|
|
|
|
IS_WIN = sys.platform == "win32"
|
|
|
|
|
|
|
2026-09-11 23:33:19 +02:00
|
|
|
|
# Portable GStreamer laden (macOS: HMS-Portable-Install.command)
|
|
|
|
|
|
_fw = ROOT / "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
|
|
|
|
|
|
_os.environ["GI_TYPELIB_PATH"] = (str(_fw / "lib" / "girepository-1.0")
|
|
|
|
|
|
+ ":" + _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
|
|
|
|
|
|
|
2026-09-11 19:41:26 +02:00
|
|
|
|
|
|
|
|
|
|
def web_port() -> int:
|
|
|
|
|
|
try:
|
|
|
|
|
|
cfg = json.loads((ROOT / "config.json").read_text("utf-8"))
|
|
|
|
|
|
return int(cfg.get("web", {}).get("port", 8080))
|
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
|
return 8080
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gst_ok() -> bool:
|
|
|
|
|
|
try:
|
|
|
|
|
|
import gi # noqa: F401
|
|
|
|
|
|
gi.require_version("Gst", "1.0")
|
|
|
|
|
|
from gi.repository import Gst
|
|
|
|
|
|
Gst.init(None)
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _spawn(py: str, target: str, hidden: bool) -> None:
|
|
|
|
|
|
if IS_WIN and hidden:
|
|
|
|
|
|
subprocess.Popen(
|
|
|
|
|
|
[py, str(ROOT / target)], cwd=str(ROOT),
|
|
|
|
|
|
creationflags=0x08000000, # CREATE_NO_WINDOW
|
|
|
|
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
else:
|
|
|
|
|
|
subprocess.Popen(
|
|
|
|
|
|
[py, str(ROOT / target)], cwd=str(ROOT),
|
|
|
|
|
|
stdout=open("/tmp/hms-mediaengine.log", "ab"),
|
|
|
|
|
|
stderr=subprocess.STDOUT, start_new_session=not IS_WIN)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def wait_health(port: int, timeout_s: float = 25.0) -> bool:
|
|
|
|
|
|
deadline = time.monotonic() + timeout_s
|
|
|
|
|
|
while time.monotonic() < deadline:
|
|
|
|
|
|
try:
|
|
|
|
|
|
with urllib.request.urlopen(
|
|
|
|
|
|
f"http://localhost:{port}/api/health",
|
|
|
|
|
|
timeout=1.5) as r:
|
|
|
|
|
|
if json.loads(r.read().decode()).get("status") == "ok":
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception: # noqa: BLE001
|
|
|
|
|
|
time.sleep(0.4)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def launch() -> int:
|
|
|
|
|
|
if gst_ok():
|
|
|
|
|
|
_spawn(sys.executable, "run.py", hidden=True)
|
|
|
|
|
|
port = web_port()
|
|
|
|
|
|
if wait_health(port):
|
|
|
|
|
|
webbrowser.open(f"http://localhost:{port}")
|
|
|
|
|
|
return 0
|
|
|
|
|
|
print("[Launcher] Engine antwortete nicht rechtzeitig – "
|
|
|
|
|
|
"oeffne Installer.", file=sys.stderr)
|
|
|
|
|
|
_spawn(sys.executable, "installer_gui.py", hidden=False)
|
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
sys.exit(launch())
|