70502458d8
- hms_app/projects.py: ProjectStore mit atomarem Speichern (.hms.json in projects/), Format-Kennung, Namens-Sanitize (Pfad-Traversal-Schutz, Leerzeichen erlaubt), Liste/Loeschen - engine.py: export_state() (Layer mit Alpha/Pos/Groesse/Z/FX + Intensitaeten + Master) und load_state() (ersetzt alle Layer, fehlende Medien werden uebersprungen und gemeldet, Werte geclamped, ungueltige FX-IDs verworfen) - server.py: /api/projects (GET Liste + Autostart), POST projects/save|load|delete|autostart; Loeschen des Autostart-Projekts raeumt config.json automatisch auf - config.py: engine.autostart_project (validiert, optional) - run.py: laedt Autostart-Projekt beim Programmstart - ui.py: neuer Reiter 'Projekte': Name eingeben + Speichern, Tabelle mit Laden/Autostart/Loeschen, Autostart-Badge E2E-BEWEIS (25/25 PASS, mit ECHTEM Server-Neustart): 1 Server startet · 2 Upload · 3 Layer · 4 FX/Alpha/Pos gesetzt · 5 Projekt 'Show A' gespeichert · 6 Traversal-Name sanitizet zu 'evil' · 7 Liste · 8 Layerzahl · 9 Zustand geleert (master=0.1) · 10 Projekt geladen · 11 Layer wiederhergestellt · 12 Alpha 0.7 wiederhergestellt · 13 FX1 blur 0.8 wiederhergestellt · 14 Position x=50 y=20 wiederhergestellt · 15 Master 0.9 · 16 Engine rendert · 17 Autostart gesetzt · 18 Server NEU GESTARTET · 19 Autostart: Layer+FX automatisch geladen · 20 Autostart: Engine rendert (60 Frames) · 21 config.json Autostart · 22 fehlendes Projekt 404 · 23 geloescht · 24 Autostart automatisch geleert · 25 config.json geleert Regressionen: App-E2E 14/14 (app=0) + FX-E2E 16/16 (fx=0) unveraendert gruen
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
#!/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))
|
||
|
||
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")
|
||
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
|
||
|
||
settings = cfgmod.load(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(ROOT, settings)
|
||
|
||
library = MediaLibrary(settings, ROOT)
|
||
library.ensure_dirs()
|
||
engine = MediaEngine(settings, library)
|
||
artnet = ArtNetInput(engine, settings)
|
||
server = EngineServer(engine, artnet, library, settings, 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(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())
|