68ce87e5ac
Vollständige Anwendung in hms_app/ (6 Module) + schlanker run.py: - config.py: config.json-Persistenz, Defaults, strikte Validierung (Bereiche, Universes, Kanal-Mapping; ungültige Settings => 400) - media.py: Upload-Streaming (chunkweise, Größenlimit, .part-Sicherung), Duplikat-Umbenennung, Thumbnail-Erzeugung per GStreamer, Metadaten-Cache (Dauer via query_duration), Löschen, Pfad-Schutz - artnet.py: ArtDMX voll KONFIGURIERBAR (Port, Universes, Kanäle), Sequenz-/Duplikat-Filter, Signalverlust-Policy hold/fade_black, Watchdog, Live-Restart nach Settings-Änderung - engine.py: dynamische Layer (Video + Bild via imagefreeze), atomarer Rebuild mit Rollback (alte Pipeline läuft bei Fehler weiter), Positions/Größen/Z-Order/Alpha je Layer, EOS-Loop, DMX-Mapping - server.py: REST komplett (upload/delete, layers add/remove/update, master/blackout/playback, settings GET/POST mit Validierung), Thumbnails + Datei-Download, Traversal-Schutz - ui.py: 4-Tabs-WebUI: Live (Preview+Slider+Diagnose), Medien (Drag&Drop-Upload mit Fortschritt, Thumbnails, Als-Layer/Delete), Layer-Editor (Alpha/Pos/Größe/Z), Einstellungen (Art-Net komplett umstellbar inkl. Port/Universes/Kanäle, Preview, Engine, Upload-Limit) - setup.py: Bootstrap + setup_windows.ps1/setup_linux.sh Generierung - run.py: Einstieg (--port, --setup, --bootstrap, --generate-setup, Datei-Import via CLI) E2E-BEWEIS (14/14 PASS, im Container ausgeführt): 1 Health OK · 2 Video-Upload via HTTP · 3 Bild-Upload · 4 Bibliothek mit Metadaten (Video-Dauer erkannt) · 5 Thumbnail JPEG · 6 Traversal-Schutz (404) · 7/8 Layer Video+Bild dynamisch · 9 Engine rendert (91 Frames) · 10 Art-Net live auf Port 6455 mit Master-Kanal 10 umgestellt · 11 DMX steuert Master 0.251 auf NEUEM Port/Kanal · 12 Layer-Alpha via DMX · 13 config.json persistiert · 14 Layer-Remove ohne Absturz (258 Frames, atomar) · Ungültige Settings werden mit 400 + Feldname abgewiesen Fixes: Bild-Caps kombiniert (parse-Fehler), Thumbnail-Namens-Mapping, kind_of-Methode, atomarer Rebuild mit Rollback, DMX-accepted-Zähler
130 lines
4.0 KiB
Python
130 lines
4.0 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()
|
||
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())
|