02765e6b34
1) Cue-/Szenenliste JE Layer (35/35 E2E): - Cue sichern = Snapshot (Quelle, Alpha, Position, Groesse, Z-Order, FX1/FX2 + Intensitaeten); Name + fade_ms + follow_ms pro Cue - GO vor/zurueck (steps), Goto (Index), Wrap-around am Listenende - FADE: Quellwechsel = dip-to-black (Phase1 runter, Wechsel bei 0, Phase2 hoch; E2E: alpha 0.49 -> 0.32 -> Ziel 0.8), sonst sanfter Tween von Alpha + FX-Intensitaeten; laufende Fades abbrechbar - AUTO-FOLLOW: follow_ms schaltet nach Ablauf automatisch weiter (E2E: Cue mit 600ms -> Wrap zum naechsten) - DMX CUE-GO: cue_go_channel (Settings + UI), steigende Flanke >=128 = GO auf allen Layern; ohne Flanke kein GO (E2E: 0->1, 1==1 bei High-Halten, neue Flanke 1->0); Sequenzfilter aktiv - Cue-Entfernen mit Indexkorrektur; Cues in Projekten speicher-/ladbar (3 Cues nach Projekt-Load erhalten) - UI: Cue-Bereich je Layer (Cue sichern / GO / Zurueck / Goto / Loeschen, aktiver Cue markiert, Fade+Follow-Anzeige) - Fehlerfaelle: unbekannter Layer 400, fehlendes Medium 400, unbekannter Generator 400 2) macOS (Apple Silicon + Intel) mit Skript: - setup_macos.sh: Homebrew installiert python@3.12, python-tk, GStreamer + alle Plugin-Sets + PyGObject; Apple-Silicon-PATH- Hinweis (/opt/homebrew); Erklaerung warum Homebrew statt gstreamer-.pkg (keine PyGObject-Bindings in .pkg) - Bootstrap (--bootstrap): darwin-Zweig mit brew-Installationspfad - run.py --generate-setup erzeugt jetzt 3 Skripte (Windows .ps1 / Linux .sh / macOS .sh); Bash-Syntax geprueft 3) Gefundene + behobene FEHLER: - WEB-UI-ABSTURZ (echter JS-Ternary-Klammerfehler in loadServers, seit Verteilungs-Commit unentdeckt, weil API-Tests den Browser- Code nie parsen): Klammerstruktur korrigiert + JS-Syntax nun per node --check verifizierbar (Syntax OK) + Remote-Buttons robust per data-Attribute (kein Quote-Escaping mehr) - update_layer blockierte Quellwechsel Video->Generator (Cues brauchen beides): Name entscheidet jetzt den Typ, Wechsel in beide Richtungen - Cue-E2E-Timing: dip-to-black hat 2 Phasen (Pruefung 0.2s/0.6s), Auto-Follow-Index-Fix, Art-Net-Sequenznummern im Test inkrementierend (Sequenzfilter verwirft Duplikate korrekt) E2E-Bilanz: Cue 35/35 + Regressionen Projekte 25/25, Verteilung 33/33, App 14/14, FX 16/16 = 123 Checks gruen
249 lines
9.6 KiB
Python
249 lines
9.6 KiB
Python
"""Bootstrap, Setup-Skripte für Windows/Linux, Einstellungs-Dialog."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
LINUX_PKGS = [
|
||
"gstreamer1.0-tools", "gstreamer1.0-plugins-base",
|
||
"gstreamer1.0-plugins-good", "gstreamer1.0-plugins-bad",
|
||
"gstreamer1.0-plugins-ugly", "gstreamer1.0-libav",
|
||
"python3-gst-1.0", "gir1.2-gst-1.0", "python3-tk",
|
||
]
|
||
|
||
|
||
def check_and_install_dependencies() -> bool:
|
||
"""Prüft GStreamer; installiert bei Bedarf (Linux) bzw. lädt MSI (Windows)."""
|
||
print("[Bootstrap] Prüfe GStreamer...")
|
||
try:
|
||
import gi # noqa: F401
|
||
gi.require_version("Gst", "1.0")
|
||
from gi.repository import Gst as _Gst
|
||
_Gst.init(None)
|
||
print(f"[Bootstrap] GStreamer {_Gst.version_string()} OK")
|
||
return True
|
||
except (ImportError, ValueError):
|
||
pass
|
||
|
||
if sys.platform == "linux":
|
||
print("[Bootstrap] Installiere GStreamer (sudo apt-get)...")
|
||
try:
|
||
subprocess.run(["sudo", "apt-get", "update", "-qq"],
|
||
check=True, timeout=180)
|
||
subprocess.run(
|
||
["sudo", "apt-get", "install", "-y", "-qq"] + LINUX_PKGS,
|
||
check=True, timeout=600)
|
||
print("[Bootstrap] GStreamer installiert. Programm neu starten.")
|
||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired,
|
||
FileNotFoundError):
|
||
print("[Bootstrap] Manuell installieren:")
|
||
print(" sudo apt-get install " + " ".join(LINUX_PKGS))
|
||
return False
|
||
|
||
if sys.platform == "darwin":
|
||
pkgs = ["python@3.12", "python-tk", "gstreamer",
|
||
"gst-plugins-base", "gst-plugins-good",
|
||
"gst-plugins-bad", "gst-plugins-ugly",
|
||
"gst-libav", "pygobject"]
|
||
brew = shutil.which("brew")
|
||
if brew:
|
||
print("[Bootstrap] Installiere GStreamer + PyGObject "
|
||
"via Homebrew (dauert evtl. einige Minuten)...")
|
||
try:
|
||
subprocess.run([brew, "install"] + pkgs,
|
||
check=False, timeout=1800)
|
||
print("[Bootstrap] Fertig. Programm mit brew-python3 "
|
||
"neu starten.")
|
||
except subprocess.TimeoutExpired:
|
||
print("[Bootstrap] Timeout. Manuell: brew install "
|
||
+ " ".join(pkgs))
|
||
else:
|
||
print("[Bootstrap] Homebrew nicht gefunden.")
|
||
print(" Erst Homebrew installieren:")
|
||
print(' /bin/bash -c "$(curl -fsSL '
|
||
'https://raw.githubusercontent.com/Homebrew/'
|
||
'install/HEAD/install.sh)"')
|
||
print(" Danach: ./setup_macos.sh")
|
||
return False
|
||
|
||
if sys.platform == "win32":
|
||
url = ("https://gstreamer.freedesktop.org/data/pkg/windows/"
|
||
"1.28.4/msvc/"
|
||
"gstreamer-1.0-msvc-x86_64-1.28.4.msi")
|
||
msi = Path.home() / "Downloads" / "gstreamer-1.0-msvc-x86_64.msi"
|
||
try:
|
||
import urllib.request
|
||
if not msi.exists():
|
||
print(f"[Bootstrap] Lade {url}")
|
||
urllib.request.urlretrieve(url, msi)
|
||
subprocess.run(["msiexec", "/i", str(msi), "/quiet",
|
||
"ADDLOCAL=ALL"], check=False)
|
||
print("[Bootstrap] GStreamer-MSI gestartet. "
|
||
"Nach Installation neu starten.")
|
||
except Exception as e: # noqa: BLE001
|
||
print(f"[Bootstrap] Download-Fehler: {e}")
|
||
print(f"[Bootstrap] Manuell installieren: {url}")
|
||
return False
|
||
|
||
return False
|
||
|
||
|
||
MACOS_SETUP = r'''#!/bin/bash
|
||
# HMS MediaEngine - macOS Setup (Apple Silicon & Intel, einmalig ausfuehren)
|
||
set -e
|
||
echo "=== HMS MediaEngine macOS Setup ==="
|
||
echo "Architektur: $(uname -m)"
|
||
|
||
if ! command -v brew >/dev/null; then
|
||
echo "Homebrew fehlt. Zunaechst installieren:"
|
||
echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
||
echo "Dann dieses Skript erneut ausfuehren."
|
||
exit 1
|
||
fi
|
||
|
||
echo "[1/2] Installiere GStreamer, PyGObject und tkinter (brew)..."
|
||
brew install python@3.12 python-tk gstreamer gst-plugins-base \
|
||
gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav pygobject
|
||
|
||
echo "[2/2] Umgebung pruefen..."
|
||
if [ "$(uname -m)" = "arm64" ] && [ -f /opt/homebrew/bin/brew ]; then
|
||
echo "Apple Silicon: Homebrew-Umgebung einbinden mit:"
|
||
echo ' eval "$(/opt/homebrew/bin/brew shellenv)"'
|
||
fi
|
||
echo "Pruefung:"
|
||
python3 -c "import gi; gi.require_version('Gst','1.0'); from gi.repository import Gst; Gst.init(None); print('GStreamer OK:', Gst.version_string())" || \
|
||
{ echo "PyGObject fehlt noch -> brew python3 verwenden (PATH!)"; exit 1; }
|
||
|
||
echo ""
|
||
echo "Hinweis: Die .pkg-Pakete von gstreamer.freedesktop.org/data/pkg/macos/"
|
||
echo "enthalten KEINE PyGObject-Bindings - Homebrew ist der empfohlene Weg."
|
||
echo ""
|
||
echo "=== Fertig ==="
|
||
echo "Start: python3 run.py"
|
||
echo "Dialog: python3 run.py --setup"
|
||
'''
|
||
|
||
|
||
WINDOWS_SETUP = r'''# HMS MediaEngine - Windows Setup (einmalig ausfuehren)
|
||
$ErrorActionPreference = "Stop"
|
||
Write-Host "=== HMS MediaEngine Windows Setup ===" -ForegroundColor Cyan
|
||
|
||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||
if (-not $isAdmin) {
|
||
Write-Host "Als Administrator ausfuehren (Rechtsklick -> Als Administrator ausfuehren)" -ForegroundColor Red
|
||
exit 1
|
||
}
|
||
|
||
Write-Host "[1/2] GStreamer 1.28.4 MSVC..." -ForegroundColor Yellow
|
||
$gstRoot = [Environment]::GetEnvironmentVariable("GSTREAMER_1_0_ROOT_MSVC_X86_64", "Machine")
|
||
if (-not $gstRoot) {
|
||
$url = "https://gstreamer.freedesktop.org/data/pkg/windows/1.28.4/msvc/gstreamer-1.0-msvc-x86_64-1.28.4.msi"
|
||
$msi = "$env:TEMP\gstreamer.msi"
|
||
Invoke-WebRequest -Uri $url -OutFile $msi
|
||
Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /quiet ADDLOCAL=ALL" -Wait
|
||
Write-Host " GStreamer installiert"
|
||
} else { Write-Host " bereits vorhanden" }
|
||
|
||
Write-Host "[2/2] Python 3.13 (falls fehlt) mit tkinter..." -ForegroundColor Yellow
|
||
$py = Get-Command python -ErrorAction SilentlyContinue
|
||
if (-not $py) {
|
||
$pyUrl = "https://www.python.org/ftp/python/3.13.2/python-3.13.2-amd64.exe"
|
||
$exe = "$env:TEMP\python-installer.exe"
|
||
Invoke-WebRequest -Uri $pyUrl -OutFile $exe
|
||
Start-Process $exe -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_tcltk=1" -Wait
|
||
Write-Host " Python installiert"
|
||
} else { Write-Host " bereits vorhanden" }
|
||
|
||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";$env:Path"
|
||
Write-Host ""
|
||
Write-Host "=== Fertig ===" -ForegroundColor Green
|
||
Write-Host "Start: python run.py"
|
||
Write-Host "Dialog: python run.py --setup"
|
||
Write-Host "Media-Verwaltung, Layer und Einstellungen: Web-UI im Browser"
|
||
'''
|
||
|
||
LINUX_SETUP = r'''#!/bin/bash
|
||
# HMS MediaEngine - Linux Setup (einmalig ausfuehren)
|
||
set -e
|
||
echo "=== HMS MediaEngine Linux Setup ==="
|
||
if command -v apt-get >/dev/null; then
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y -qq gstreamer1.0-tools gstreamer1.0-plugins-base \
|
||
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
|
||
gstreamer1.0-libav python3-gst-1.0 gir1.2-gst-1.0 python3-tk xvfb
|
||
elif command -v dnf >/dev/null; then
|
||
sudo dnf install -y gstreamer1 gstreamer1-plugins-base gstreamer1-plugins-good \
|
||
gstreamer1-plugins-bad-free gstreamer1-plugins-ugly gstreamer1-libav \
|
||
python3-gobject python3-tkinter
|
||
fi
|
||
echo "=== Fertig ==="
|
||
echo "Start: python3 run.py"
|
||
echo "Media-Verwaltung, Layer und Einstellungen: Web-UI im Browser"
|
||
'''
|
||
|
||
|
||
def generate_setup_scripts(root: Path) -> None:
|
||
p1 = root / "setup_windows.ps1"
|
||
p1.write_text(WINDOWS_SETUP, encoding="utf-8")
|
||
p2 = root / "setup_linux.sh"
|
||
p2.write_text(LINUX_SETUP, encoding="utf-8")
|
||
p2.chmod(0o755)
|
||
p3 = root / "setup_macos.sh"
|
||
p3.write_text(MACOS_SETUP, encoding="utf-8")
|
||
p3.chmod(0o755)
|
||
print(f"Erzeugt: {p1}")
|
||
print(f"Erzeugt: {p2}")
|
||
print(f"Erzeugt: {p3}")
|
||
|
||
|
||
def run_setup(settings: dict) -> dict:
|
||
"""Nativer Dialog für Port/Fullscreen; Medien-Verwaltung bleibt im Web."""
|
||
result = {"port": int(settings["web"]["port"]),
|
||
"fullscreen": bool(settings["output"]["fullscreen"])}
|
||
try:
|
||
import tkinter as tk
|
||
from tkinter import ttk
|
||
except ImportError:
|
||
print("tkinter nicht verfügbar (Linux: apt install python3-tk).")
|
||
return result
|
||
|
||
root = tk.Tk()
|
||
root.title("HMS MediaEngine – Einstellungen")
|
||
root.geometry("440x240")
|
||
root.configure(bg="#16181d")
|
||
style = ttk.Style(root)
|
||
style.theme_use("clam")
|
||
style.configure("TLabel", background="#16181d", foreground="#c8ccd4")
|
||
main = ttk.Frame(root, padding=18)
|
||
main.pack(fill="both", expand=True)
|
||
ttk.Label(main, text="HMS MediaEngine",
|
||
font=("System", 15, "bold")).pack(pady=(0, 2))
|
||
ttk.Label(main, text="Grundeinstellungen",
|
||
foreground="#8a8f9a").pack(pady=(0, 12))
|
||
|
||
row = ttk.Frame(main)
|
||
row.pack(fill="x", pady=4)
|
||
ttk.Label(row, text="Web-UI Port:").pack(side="left")
|
||
port_var = tk.StringVar(value=str(result["port"]))
|
||
ttk.Entry(row, textvariable=port_var, width=7).pack(
|
||
side="left", padx=8)
|
||
fs_var = tk.BooleanVar(value=result["fullscreen"])
|
||
ttk.Checkbutton(row, text="Fullscreen-Output",
|
||
variable=fs_var).pack(side="left", padx=10)
|
||
|
||
def go():
|
||
try:
|
||
result["port"] = int(port_var.get() or 8080)
|
||
except ValueError:
|
||
pass
|
||
result["fullscreen"] = bool(fs_var.get())
|
||
root.destroy()
|
||
|
||
ttk.Button(main, text="Starten", command=go).pack(
|
||
fill="x", pady=(14, 0), ipady=6)
|
||
root.mainloop()
|
||
return result
|