GENERATOREN + MEDIEN-VERTEILUNG + AUTOSTART-SETTINGS (33/33 E2E)
1) MADRIX-artige Generator-Layer (15 prozedurale Quellen): - engine.GENERATORS: ball (animiert), gradient, pinwheel (animiert), zone-plate (animiert), checkers, snow (Rauschen animiert), circular, spokes, smpte, gamut, red/green/blue/black/white - alle per gst-launch verifiziert, kombinierbar mit FX1/FX2 (Generator+FX gerendert, 28 Frames im Test) - add_generator + Pattern-Wechsel zur Laufzeit (ball->snow) mit Rebuild, Katalog-Endpoint /api/generators, UI-Dropdown im Layer-Tab - Generatoren in Projekten speicher-/ladbar (gradient nach Load aktiv) - Ungueltige Generator-IDs werden mit 400 abgewiesen 2) Medien-Verteilung ueber mehrere Server: - hms_app/distribution.py: Server-Check (/api/health), Remote-Medien-Liste, Streaming-Import mit Upload-Sicherung - config.servers: Remote-Server registrieren (Name+URL, HTTP-only, Duplikat-Schutz), /api/servers (online/offline + Medien), servers/import: Datei von Remote ins lokale Archiv ziehen - UI: Remote-Server in Einstellungen verwalten, Remote-Medien im Medien-Tab mit 'Ins Archiv ziehen'-Button - run.py --data-root: mehrere Instanzen auf einer Maschine mit getrennten config/media/projects-Ordnern - E2E mit ZWEI echten Servern: A registriert B, sieht B-Medien, zieht clip byte-identisch (SHA verifiziert) und spielt ihn als Layer; Offline-Erkennung nach B-Stop; 404/400-Faelle 3) Autostart in den Einstellungen: - Einstellungen: Autostart-Projekt-Dropdown (aus Projektliste), abwaehlbar; Projekte-Tab weiterhin mit Autostart-Button - FIX Settings-Save-Merge: POST /api/settings merged jetzt auf die AKTUELLEN Settings - Autostart und registrierte Server gehen beim Speichern anderer Einstellungen nicht mehr verloren (E2E: Autostart ueberlebt Settings-Save, FPS uebernommen) E2E: 33/33 PASS (Verteilung/Generatoren) + Regressionen: Projekte 25/25, App 14/14, FX 16/16 - alle gruen
This commit is contained in:
+85
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
@@ -9,6 +10,7 @@ import urllib.parse
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
from . import config as cfgmod
|
||||
from . import distribution
|
||||
from .projects import ProjectStore
|
||||
from .ui import HTML_UI
|
||||
|
||||
@@ -102,6 +104,23 @@ class EngineServer:
|
||||
self._json(artnet.monitor())
|
||||
elif path == "/api/fx":
|
||||
self._json(engine.fx_catalog())
|
||||
elif path == "/api/generators":
|
||||
self._json({"items": engine.generators()})
|
||||
elif path == "/api/servers":
|
||||
out = []
|
||||
for s_ in settings.get("servers", []):
|
||||
entry = {"name": s_.get("name"),
|
||||
"url": s_.get("url"),
|
||||
"online": False, "items": []}
|
||||
try:
|
||||
data = distribution.list_media(
|
||||
str(s_.get("url", "")))
|
||||
entry["online"] = True
|
||||
entry["items"] = data.get("items", [])
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
out.append(entry)
|
||||
self._json({"servers": out})
|
||||
elif path == "/api/projects":
|
||||
self._json({"items": server.projects.list(),
|
||||
"autostart": settings["engine"].get(
|
||||
@@ -134,6 +153,67 @@ class EngineServer:
|
||||
self._send_error_json(400, engine.last_error or "?" )
|
||||
else:
|
||||
self._json({"layer": layer})
|
||||
elif path == "/api/layers/add-generator":
|
||||
body = self._read_json()
|
||||
layer = engine.add_generator(
|
||||
str(body.get("generator", "")))
|
||||
if layer is None:
|
||||
self._send_error_json(
|
||||
400, engine.last_error or "unbekannter Generator")
|
||||
else:
|
||||
self._json({"layer": layer})
|
||||
elif path == "/api/servers/add":
|
||||
body = self._read_json()
|
||||
name = str(body.get("name", "")).strip()
|
||||
url = str(body.get("url", "")).strip()
|
||||
if (not name or not url.startswith(
|
||||
("http://", "https://"))):
|
||||
self._send_error_json(
|
||||
400, "Name und http(s)-URL erforderlich")
|
||||
elif any(s_.get("name") == name
|
||||
for s_ in settings.get("servers", [])):
|
||||
self._send_error_json(400, "Name bereits vergeben")
|
||||
elif not distribution.check(url):
|
||||
self._json({"added": False,
|
||||
"error": "Server nicht erreichbar "
|
||||
"oder kein HMS MediaEngine"})
|
||||
else:
|
||||
settings.setdefault("servers", []).append(
|
||||
{"name": name, "url": url})
|
||||
cfgmod.save(root, settings)
|
||||
self._json({"added": True, "name": name})
|
||||
elif path == "/api/servers/remove":
|
||||
body = self._read_json()
|
||||
name = str(body.get("name", ""))
|
||||
before = len(settings.get("servers", []))
|
||||
settings["servers"] = [
|
||||
s_ for s_ in settings.get("servers", [])
|
||||
if s_.get("name") != name]
|
||||
if len(settings["servers"]) == before:
|
||||
self._send_error_json(404, "Server nicht gefunden")
|
||||
else:
|
||||
cfgmod.save(root, settings)
|
||||
self._json({"removed": True})
|
||||
elif path == "/api/servers/import":
|
||||
body = self._read_json()
|
||||
srv_name = str(body.get("server", ""))
|
||||
media_name = str(body.get("media", ""))
|
||||
url = None
|
||||
for s_ in settings.get("servers", []):
|
||||
if s_.get("name") == srv_name:
|
||||
url = str(s_.get("url", ""))
|
||||
break
|
||||
if url is None:
|
||||
self._send_error_json(404, "Server nicht gefunden")
|
||||
elif not media_name:
|
||||
self._send_error_json(400, "Medienname fehlt")
|
||||
else:
|
||||
ok, result = distribution.import_media(
|
||||
url, media_name, library)
|
||||
if ok:
|
||||
self._json({"saved": result})
|
||||
else:
|
||||
self._send_error_json(400, str(result))
|
||||
elif path == "/api/layers/remove":
|
||||
body = self._read_json()
|
||||
ok = engine.remove_layer(int(body.get("id", -1)))
|
||||
@@ -226,7 +306,11 @@ class EngineServer:
|
||||
|
||||
def _post_settings(self):
|
||||
body = self._read_json()
|
||||
normalized, err = cfgmod.validate(body)
|
||||
# WICHTIG: auf aktuelle Settings mergen, damit nicht
|
||||
# gesendete Felder (autostart_project, servers) erhalten
|
||||
# bleiben und nicht auf Defaults zurueckfallen.
|
||||
merged = cfgmod.deep_merge(copy.deepcopy(settings), body)
|
||||
normalized, err = cfgmod.validate(merged)
|
||||
if err is not None:
|
||||
self._send_error_json(400, err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user