2026-09-11 11:05:01 +02:00
|
|
|
|
"""Bootstrap, Setup-Skripte für Windows/Linux, Einstellungs-Dialog."""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-09-11 13:34:43 +02:00
|
|
|
|
import shutil
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-09-11 13:34:43 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-11 13:34:43 +02:00
|
|
|
|
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"
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-11 11:05:01 +02:00
|
|
|
|
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)
|
2026-09-11 13:34:43 +02:00
|
|
|
|
p3 = root / "setup_macos.sh"
|
|
|
|
|
|
p3.write_text(MACOS_SETUP, encoding="utf-8")
|
|
|
|
|
|
p3.chmod(0o755)
|
2026-09-11 11:05:01 +02:00
|
|
|
|
print(f"Erzeugt: {p1}")
|
|
|
|
|
|
print(f"Erzeugt: {p2}")
|
2026-09-11 13:34:43 +02:00
|
|
|
|
print(f"Erzeugt: {p3}")
|
2026-09-11 11:05:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|