diff --git a/HMS MediaEngine.app/Contents/Info.plist b/HMS MediaEngine.app/Contents/Info.plist new file mode 100644 index 0000000..bdcbe60 --- /dev/null +++ b/HMS MediaEngine.app/Contents/Info.plist @@ -0,0 +1,16 @@ + + + + + CFBundleNameHMS MediaEngine + CFBundleDisplayNameHMS MediaEngine + CFBundleIdentifierde.hms.mediaengine + CFBundleVersion0.1.0 + CFBundleShortVersionString0.1.0 + CFBundlePackageTypeAPPL + CFBundleExecutableHMS-Launcher + LSMinimumSystemVersion11.0 + NSHighResolutionCapable + CFBundleInfoDictionaryVersion6.0 + + diff --git a/HMS MediaEngine.app/Contents/MacOS/HMS-Launcher b/HMS MediaEngine.app/Contents/MacOS/HMS-Launcher new file mode 100755 index 0000000..f393f05 --- /dev/null +++ b/HMS MediaEngine.app/Contents/MacOS/HMS-Launcher @@ -0,0 +1,26 @@ +#!/bin/bash +# HMS MediaEngine App-Start (vom Finder, ohne sichtbares Terminal) +DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +PY="" +for p in /opt/homebrew/bin/python3 /usr/local/bin/python3 python3; do + if command -v "$p" >/dev/null 2>&1; then PY="$p"; break; fi +done +if [ -z "$PY" ]; then + osascript -e 'display alert "Python nicht gefunden" message "Bitte HMS-Mac-Install.command im Projektordner einmal ausfuehren - es installiert alles Grafische." as critical' >/dev/null 2>&1 + exit 1 +fi +cd "$DIR" +if "$PY" -c "import gi; gi.require_version('Gst','1.0')" >/dev/null 2>&1; then + nohup "$PY" "$DIR/run.py" >>/tmp/hms-mediaengine.log 2>&1 & + PORT=8080 + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ + 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40; do + curl -s -o /dev/null "http://localhost:$PORT/api/health" && break + sleep 0.5 + done + open "http://localhost:$PORT" + exit 0 +else + nohup "$PY" "$DIR/installer_gui.py" >/tmp/hms-installer.log 2>&1 & + exit 0 +fi diff --git a/HMS-Install.vbs b/HMS-Install.vbs new file mode 100644 index 0000000..164bb66 --- /dev/null +++ b/HMS-Install.vbs @@ -0,0 +1,33 @@ +' HMS MediaEngine - Erstinstallation (Windows, Doppelklick) +' Oeffnet den grafischen Installer (Fenster, kein Tippen noetig). +Option Explicit +Dim fso, sh, py, cand, dir_, candidates +Set fso = CreateObject("Scripting.FileSystemObject") +Set sh = CreateObject("WScript.Shell") +dir_ = fso.GetParentFolderName(WScript.ScriptFullName) + +py = "" +On Error Resume Next +candidates = Array( _ + sh.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\Programs\Python\Python313\pythonw.exe", _ + sh.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\Programs\Python\Python312\pythonw.exe", _ + "C:\Python313\pythonw.exe", _ + "C:\Python312\pythonw.exe") +For Each cand In candidates + If py = "" Then + If fso.FileExists(cand) Then py = cand + End If +Next +On Error GoTo 0 + +If py = "" Then + MsgBox "Python fehlt noch." & vbCrLf & vbCrLf & _ + "Der Browser oeffnet jetzt die offizielle Python-Downloadseite." & vbCrLf & _ + "Nach der Python-Installation (Haeckchen bei 'Add python.exe to PATH'!)" & vbCrLf & _ + "bitte HMS-Install.vbs erneut doppelklicken.", 64, "HMS MediaEngine" + sh.Run "cmd /c start https://www.python.org/downloads/", 0, False + WScript.Quit 1 +End If + +sh.CurrentDirectory = dir_ +sh.Run Chr(34) & py & Chr(34) & " " & Chr(34) & dir_ & "\installer_gui.py" & Chr(34), 1, False diff --git a/HMS-Mac-Install.command b/HMS-Mac-Install.command new file mode 100755 index 0000000..42855c8 --- /dev/null +++ b/HMS-Mac-Install.command @@ -0,0 +1,50 @@ +#!/bin/bash +# HMS MediaEngine – macOS-Installation per Doppelklick (Finder) +# Ablauf: brew pruefen/installieren -> python3+tk pruefen -> +# GUI-Installer starten (Passwort-Dialog kommt von macOS selbst). +# Falls python3/tkinter fehlt: Fallback-Dialoge nativ per osascript. + +cd "$(dirname "$0")" + +echo "HMS MediaEngine – Installation" + +eingabe() { + osascript -e 'display alert "'$1'" message "'$2'" buttons {"Abbrechen","Weiter"} default button "Weiter" as informational' >/dev/null 2>&1 + [ $? -eq 0 ] +} + +# --- Homebrew --- +BREW="" +for p in /opt/homebrew/bin/brew /usr/local/bin/brew; do + if [ -x "$p" ]; then BREW="$p"; break; fi +done +if [ -z "$BREW" ]; then + if eingabe "Homebrew installieren?" "HMS MediaEngine benötigt Homebrew (der Paketmanager für macOS). Es wird jetzt installiert – danach geht es automatisch weiter."; then + NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + BREW=/opt/homebrew/bin/brew + else + exit 1 + fi +fi + +# --- Python 3.12 + tkinter --- +"$BREW" list python-tk >/dev/null 2>&1 || { + if eingabe "Python-Komponenten installieren?" "Es wird python@3.12 mit tkinter installiert (für die grafische Oberfläche)."; then + "$BREW" install python@3.12 python-tk + fi +} + +# --- GStreamer --- +"$BREW" list gstreamer >/dev/null 2>&1 || { + if eingabe "GStreamer installieren?" "Es werden GStreamer und alle Video-/Audio-Plugins installiert (ca. 5–10 Minuten)."; then + "$BREW" install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav pygobject + fi +} + +# --- GUI-Installer starten --- +PY=/opt/homebrew/bin/python3 +if [ ! -x "$PY" ]; then PY=/usr/local/bin/python3; fi + +echo "" +echo "Starte grafischen Installer (Fenster öffnet sich)..." +"$PY" installer_gui.py diff --git a/HMS-Start.vbs b/HMS-Start.vbs new file mode 100644 index 0000000..c50191f --- /dev/null +++ b/HMS-Start.vbs @@ -0,0 +1,51 @@ +' HMS MediaEngine - Windows-Starter (unsichtbar, Doppelklick) +' Startet die Engine im Hintergrund (nur Browser sichtbar). +' Fehlen Abhaengigkeiten, oeffnet sich der grafische Installer. +Option Explicit +Dim fso, sh, py, cand, dir_, tmpFile, ts, out +Dim candidates(3) +Set fso = CreateObject("Scripting.FileSystemObject") +Set sh = CreateObject("WScript.Shell") +dir_ = fso.GetParentFolderName(WScript.ScriptFullName) + +py = "" +candidates(0) = sh.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\Programs\Python\Python313\pythonw.exe" +candidates(1) = sh.ExpandEnvironmentStrings("%LOCALAPPDATA%") & "\Programs\Python\Python312\pythonw.exe" +candidates(2) = "C:\Python313\pythonw.exe" +candidates(3) = "C:\Python312\pythonw.exe" +For Each cand In candidates + If py = "" Then + If fso.FileExists(cand) Then py = cand + End If +Next + +If py = "" Then + ' PATH-Suche VERSTECKT (sh.Exec wuerde ein Konsolenfenster zeigen): + ' cmd versteckt laufen lassen, Ausgabe in Temp-Datei + tmpFile = sh.ExpandEnvironmentStrings("%TEMP%") & "\hms_py_path.txt" + On Error Resume Next + sh.Run "cmd /c where pythonw > """ & tmpFile & """ 2>nul", 0, True + If fso.FileExists(tmpFile) Then + Set ts = fso.OpenTextFile(tmpFile, 1) + If Not ts.AtEndOfStream Then + out = Trim(ts.ReadLine()) + If Len(out) > 4 Then + If fso.FileExists(out) Then py = out + End If + End If + ts.Close + fso.DeleteFile tmpFile, True + End If + On Error GoTo 0 +End If + +If py = "" Then + MsgBox "Python wurde nicht gefunden." & vbCrLf & vbCrLf & _ + "Bitte einmal 'HMS-Install.vbs' doppelklicken -" & vbCrLf & _ + "es installiert Python und GStreamer ueber grafische Installer.", _ + 48, "HMS MediaEngine" + WScript.Quit 1 +End If + +sh.CurrentDirectory = dir_ +sh.Run Chr(34) & py & Chr(34) & " " & Chr(34) & dir_ & "\launcher.pyw" & Chr(34), 0, False diff --git a/installer_gui.py b/installer_gui.py new file mode 100644 index 0000000..00b409f --- /dev/null +++ b/installer_gui.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python3 +"""HMS MediaEngine – Grafischer Installer (Fenster statt Konsole). + +Wird vom macOS-App-Bundle oder HMS-Start.vbs (Windows) geöffnet, wenn +Abhaengigkeiten fehlen. Der Nutzer sieht ein Fenster – kein Terminal: + +- macOS: GStreamer/PyGObject via Homebrew. Native Passwort-Eingabe durch + macOS-Systemdialog (osascript 'with administrator privileges'). +- Windows: Offizielle Installer-GUIs (GStreamer-MSI, Python-Setup) werden + gestartet – der Nutzer klickt nur 'Weiter/Fertig'. + +Nach der Installation: Button 'MediaEngine starten' – die App laeuft + danach unsichtbar im Hintergrund, nur der Browser ist sichtbar. +""" + +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile +import threading +import urllib.request +import webbrowser +from pathlib import Path + +import tkinter as tk +from tkinter import ttk, messagebox + +ROOT = Path(__file__).resolve().parent +IS_MAC = sys.platform == "darwin" +IS_WIN = sys.platform == "win32" + +GST_MSI = ("https://gstreamer.freedesktop.org/data/pkg/windows/" + "1.28.4/msvc/gstreamer-1.0-msvc-x86_64-1.28.4.msi") +PY_EXE = "https://www.python.org/ftp/python/3.13.2/python-3.13.2-amd64.exe" +BREW_PKGS = [ + "python@3.12", "python-tk", "gstreamer", "gst-plugins-base", + "gst-plugins-good", "gst-plugins-bad", "gst-plugins-ugly", + "gst-libav", "pygobject", +] + + +def brew_path() -> str | None: + for p in ("/opt/homebrew/bin/brew", "/usr/local/bin/brew"): + if Path(p).exists(): + return p + return None + + +def gst_ok() -> bool: + try: + import gi # noqa: F401 + gi.require_version("Gst", "1.0") + from gi.repository import Gst + Gst.init(None) + return True + except Exception: # noqa: BLE001 + return False + + +def py_ok() -> bool: + try: + r = subprocess.run(["python", "--version"], capture_output=True, + timeout=10, + creationflags=0x08000000 if IS_WIN else 0) + return r.returncode == 0 + except Exception: # noqa: BLE001 + return False + + +def mac_python_ok() -> bool: + """Ein python3, das spaeter auch gi importieren kann (brew-Python).""" + for cand in ("/opt/homebrew/bin/python3", "/usr/local/bin/python3", + "python3"): + try: + r = subprocess.run([cand, "-c", "import tkinter"], + capture_output=True, timeout=10) + if r.returncode == 0: + return True + except Exception: # noqa: BLE001 + continue + return False + + +def dl(url: str, dest: Path) -> None: + def hook(n, bs, total): + if total > 0: + pct = min(100, n * bs * 100 // total) + app.on_progress(pct) + urllib.request.urlretrieve(url, dest, hook) + + +def run_quiet(cmd: list[str]) -> None: + subprocess.run(cmd, check=False, + creationflags=0x08000000 if IS_WIN else 0) + + +class InstallerApp: + def __init__(self): + self.root = tk.Tk() + self.root.title("HMS MediaEngine – Installation") + self.root.geometry("680x520") + self.root.configure(bg="#16181d") + try: + style = ttk.Style(self.root) + style.theme_use("clam") + except Exception: # noqa: BLE001 + pass + + self.steps: list[tuple[str, bool]] = [] + self.log_lines: list[str] = [] + self._build() + self.refresh_status() + + # ---------- UI ---------- + + def _build(self): + head = tk.Frame(self.root, bg="#16181d") + head.pack(fill="x", padx=16, pady=(12, 6)) + tk.Label(head, text="HMS MediaEngine", + font=("System", 17, "bold"), + bg="#16181d", fg="#e8eaee").pack(anchor="w") + tk.Label(head, text="Grafische Installation – kein Terminal nötig", + bg="#16181d", fg="#8a8f9a").pack(anchor="w") + + self.status_var = tk.StringVar() + tk.Label(self.root, textvariable=self.status_var, + bg="#16181d", fg="#c8ccd4").pack(fill="x", padx=16) + + self.bar = ttk.Progressbar(self.root, mode="determinate", + length=640) + self.bar.pack(fill="x", padx=16, pady=8) + + logbox = tk.Frame(self.root, bg="#0d0f12") + logbox.pack(fill="both", expand=True, padx=16, pady=6) + self.log = tk.Text(logbox, bg="#0d0f12", fg="#c8ccd4", + insertbackground="#c8ccd4", height=14, + relief="flat", font=("Menlo" if IS_MAC + else "Consolas", 10)) + self.log.pack(fill="both", expand=True, side="left") + scroll = ttk.Scrollbar(logbox, command=self.log.yview) + scroll.pack(side="right", fill="y") + self.log.configure(yscrollcommand=scroll.set) + + btns = tk.Frame(self.root, bg="#16181d") + btns.pack(fill="x", padx=16, pady=10) + self.install_btn = tk.Button( + btns, text="Fehlendes installieren", + command=self.start_install, bg="#1d3a5f", fg="#dbe9ff", + activebackground="#2a4f7f", relief="flat", padx=14, pady=7) + self.install_btn.pack(side="left") + self.start_btn = tk.Button( + btns, text="▶ MediaEngine starten", + command=self.start_engine, bg="#1f4d2a", fg="#d8ffd8", + activebackground="#2a6a3a", relief="flat", padx=14, pady=7) + self.start_btn.pack(side="left", padx=8) + tk.Button(btns, text="Beenden", command=self.root.destroy, + bg="#262a33", fg="#c8ccd4", relief="flat", + padx=14, pady=7).pack(side="right") + + def on_log(self, text: str): + self.log_lines.append(text) + self.log.insert("end", text + "\n") + self.log.see("end") + self.root.update_idletasks() + + def on_progress(self, pct: int): + self.bar["value"] = pct + self.root.update_idletasks() + + def on_status(self, text: str): + self.status_var.set(text) + self.root.update_idletasks() + + # ---------- Status ---------- + + def refresh_status(self): + self.steps = [] + if IS_WIN: + self.steps.append(("Python 3.13", py_ok())) + gs = (os.environ.get("GSTREAMER_1_0_ROOT_MSVC_X86_64") + or "") + self.steps.append(("GStreamer 1.28", + bool(gs) or Path( + "C:\\gstreamer\\1.0\\msvc_x86_64" + ).exists())) + elif IS_MAC: + self.steps.append(("Homebrew", brew_path() is not None)) + self.steps.append(("Python 3 (mit tkinter)", mac_python_ok())) + self.steps.append(("GStreamer + PyGObject", gst_ok())) + else: + self.steps.append(("GStreamer (PyGObject)", gst_ok())) + + self.log.delete("1.0", "end") + for name, ok in self.steps: + mark = "✓" if ok else "✗" + self.on_log(f"{mark} {name}: " + + ("vorhanden" if ok else "fehlt – wird installiert")) + missing = [n for n, ok in self.steps if not ok] + if not missing: + self.on_status("Alles bereit – du kannst MediaEngine starten.") + self.install_btn.config(state="disabled") + self.start_btn.config(state="normal", bg="#1f4d2a") + else: + self.on_status("Fehlt: " + ", ".join(missing) + + " → Button links drücken.") + self.install_btn.config(state="normal") + self.start_btn.config(state="disabled", bg="#262a33") + + # ---------- Installation ---------- + + def start_install(self): + self.install_btn.config(state="disabled") + threading.Thread(target=self._install_worker, daemon=True).start() + + def _install_worker(self): + try: + if IS_MAC: + self._install_mac() + elif IS_WIN: + self._install_win() + self.on_log("") + self.on_log("Installation abgeschlossen.") + self.on_log("Falls gerade etwas installiert wurde, das Python " + "betrifft: dieses Fenster einmal schließen und " + "die App erneut starten.") + self.refresh_status() + except Exception as e: # noqa: BLE001 + self.on_log(f"FEHLER: {e}") + self.on_status("Installation fehlgeschlagen – siehe Log. " + "(Meldung an den Support reicht zum Fixen.)") + self.install_btn.config(state="normal") + + def _osascript_admin(self, script: str, prompt: str) -> str: + esc = script.replace('\\', '\\\\').replace('"', '\\"') + code = (f'do shell script "{esc}" ' + f'with administrator privileges ' + f'with prompt "{prompt}"') + r = subprocess.run(["osascript", "-e", code], + capture_output=True, text=True, timeout=3600) + if r.returncode != 0: + raise RuntimeError(r.stderr.strip() or "osascript fehlgeschlagen") + return r.stdout + + def _install_mac(self): + self.on_status("macOS: Installation läuft (Passwort-Dialog folgt)…") + lines = ["set -e"] + brew = brew_path() + if brew is None: + self.on_log("Homebrew fehlt – wird installiert (mehrere Minuten)…") + lines.append( + 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL ' + 'https://raw.githubusercontent.com/Homebrew/' + 'install/HEAD/install.sh)"') + # brew-Pfad robust im Skript auswaehlen (Apple Silicon vs. Intel); + # absolute Pfade machen ein eval/shellenv ueberfluessig + lines.append( + 'if [ -x /opt/homebrew/bin/brew ]; then ' + 'BREW=/opt/homebrew/bin/brew; ' + 'else BREW=/usr/local/bin/brew; fi') + lines.append('"$BREW" install ' + " ".join(BREW_PKGS)) + script = "\n".join(lines) + with tempfile.NamedTemporaryFile("w", suffix=".sh", + delete=False) as f: + f.write(script) + sh = f.name + os.chmod(sh, 0o700) + self.on_log("Starte Installation (macOS fragt nach deinem " + "Administrator-Passwort)…") + out = self._osascript_admin(f"/bin/bash {sh}", + "HMS MediaEngine: GStreamer und " + "Python-Komponenten installieren") + for line in (out or "").strip().splitlines()[-12:]: + self.on_log(" " + line) + Path(sh).unlink(missing_ok=True) + + def _install_win(self): + if not Path("C:\\gstreamer\\1.0\\msvc_x86_64").exists() \ + and not os.environ.get("GSTREAMER_1_0_ROOT_MSVC_X86_64"): + self.on_status("Windows: GStreamer-Installer (GUI) – bitte " + "mit 'Weiter/Fertig' bestätigen…") + dest = Path(tempfile.gettempdir()) / "gstreamer.msi" + self.on_log("Lade GStreamer-Installer herunter…") + dl(GST_MSI, dest) + self.on_log("Öffne GStreamer-Installation (Fenster erscheint)…") + r = subprocess.run(["msiexec", "/i", str(dest)], + capture_output=False, timeout=3600) + self.on_log(f"GStreamer-Installer beendet (Code {r.returncode}).") + if not py_ok(): + self.on_status("Windows: Python-Installer (GUI) – bitte " + "'Add python.exe to PATH' anhaken und installieren…") + dest = Path(tempfile.gettempdir()) / "python-setup.exe" + self.on_log("Lade Python-Installer herunter…") + dl(PY_EXE, dest) + self.on_log("Öffne Python-Installation (Fenster erscheint)…") + subprocess.run([str(dest)], timeout=3600) + self.on_log("Python-Installation abgeschlossen.") + self.on_log("WICHTIG: Falls PATH soeben geändert wurde, dieses " + "Fenster schließen und HMS-Start.vbs erneut " + "doppelklicken.") + + # ---------- Start ---------- + + def start_engine(self): + try: + if IS_WIN: + pyw = "pythonw" + subprocess.Popen( + [pyw if Path(sys.executable).stem == "python" + else sys.executable, str(ROOT / "run.py")], + cwd=str(ROOT), + creationflags=0x08000000, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + else: + subprocess.Popen( + ["python3", str(ROOT / "run.py")], cwd=str(ROOT), + stdout=open("/tmp/hms-mediaengine.log", "ab"), + stderr=subprocess.STDOUT, + start_new_session=True) + self.on_status("MediaEngine startet – Browser öffnet sich…") + self.root.after(2500, lambda: webbrowser.open( + "http://localhost:8080")) + self.root.after(4000, self.root.destroy) + except Exception as e: # noqa: BLE001 + messagebox.showerror("HMS MediaEngine", + f"Start fehlgeschlagen:\n{e}") + + def run(self): + self.root.mainloop() + + +if __name__ == "__main__": + app = InstallerApp() + app.run() diff --git a/launcher.pyw b/launcher.pyw new file mode 100644 index 0000000..613462d --- /dev/null +++ b/launcher.pyw @@ -0,0 +1,8 @@ +# Unsichtbarer Windows-Launcher (pythonw ohne Konsolenfenster). +# Wird von HMS-Start.vbs aufgerufen. +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from launcher_core import launch +sys.exit(launch()) diff --git a/launcher_core.py b/launcher_core.py new file mode 100644 index 0000000..57be452 --- /dev/null +++ b/launcher_core.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Gemeinsame Launcher-Logik fuer Windows-Starter und macOS. + +Unsichtbarer Ablauf: GStreamer vorhanden -> Engine im Hintergrund +starten, auf die Web-UI warten, Browser oeffnen. +Fehlt etwas -> grafischer Installer (installer_gui.py) oeffnen. +""" + +from __future__ import annotations + +import json +import subprocess +import sys +import time +import urllib.request +import webbrowser +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +IS_WIN = sys.platform == "win32" + + +def web_port() -> int: + try: + cfg = json.loads((ROOT / "config.json").read_text("utf-8")) + return int(cfg.get("web", {}).get("port", 8080)) + except Exception: # noqa: BLE001 + return 8080 + + +def gst_ok() -> bool: + try: + import gi # noqa: F401 + gi.require_version("Gst", "1.0") + from gi.repository import Gst + Gst.init(None) + return True + except Exception: # noqa: BLE001 + return False + + +def _spawn(py: str, target: str, hidden: bool) -> None: + if IS_WIN and hidden: + subprocess.Popen( + [py, str(ROOT / target)], cwd=str(ROOT), + creationflags=0x08000000, # CREATE_NO_WINDOW + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + subprocess.Popen( + [py, str(ROOT / target)], cwd=str(ROOT), + stdout=open("/tmp/hms-mediaengine.log", "ab"), + stderr=subprocess.STDOUT, start_new_session=not IS_WIN) + + +def wait_health(port: int, timeout_s: float = 25.0) -> bool: + deadline = time.monotonic() + timeout_s + while time.monotonic() < deadline: + try: + with urllib.request.urlopen( + f"http://localhost:{port}/api/health", + timeout=1.5) as r: + if json.loads(r.read().decode()).get("status") == "ok": + return True + except Exception: # noqa: BLE001 + time.sleep(0.4) + return False + + +def launch() -> int: + if gst_ok(): + _spawn(sys.executable, "run.py", hidden=True) + port = web_port() + if wait_health(port): + webbrowser.open(f"http://localhost:{port}") + return 0 + print("[Launcher] Engine antwortete nicht rechtzeitig – " + "oeffne Installer.", file=sys.stderr) + _spawn(sys.executable, "installer_gui.py", hidden=False) + return 1 + + +if __name__ == "__main__": + sys.exit(launch()) diff --git a/make_mac_app.py b/make_mac_app.py new file mode 100644 index 0000000..08e10e7 --- /dev/null +++ b/make_mac_app.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Erzeugt 'HMS MediaEngine.app' im Projektordner (macOS App-Bundle). + +Aufruf: python3 make_mac_app.py +Danach: 'HMS MediaEngine.app' per Doppelklick im Finder starten. +Der Launcher prueft unsichtbar: fehlt GStreamer, oeffnet sich der +grafische Installer; ist alles da, startet die Engine im Hintergrund +und der Browser oeffnet sich. +""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +APP = ROOT / "HMS MediaEngine.app" + +INFO_PLIST = """ + + + + CFBundleNameHMS MediaEngine + CFBundleDisplayNameHMS MediaEngine + CFBundleIdentifierde.hms.mediaengine + CFBundleVersion0.1.0 + CFBundleShortVersionString0.1.0 + CFBundlePackageTypeAPPL + CFBundleExecutableHMS-Launcher + LSMinimumSystemVersion11.0 + NSHighResolutionCapable + CFBundleInfoDictionaryVersion6.0 + + +""" + +LAUNCHER = r'''#!/bin/bash +# HMS MediaEngine App-Start (vom Finder, ohne sichtbares Terminal) +DIR="$(cd "$(dirname "$0")/../../.." && pwd)" +PY="" +for p in /opt/homebrew/bin/python3 /usr/local/bin/python3 python3; do + if command -v "$p" >/dev/null 2>&1; then PY="$p"; break; fi +done +if [ -z "$PY" ]; then + osascript -e 'display alert "Python nicht gefunden" message "Bitte HMS-Mac-Install.command im Projektordner einmal ausfuehren - es installiert alles Grafische." as critical' >/dev/null 2>&1 + exit 1 +fi +cd "$DIR" +if "$PY" -c "import gi; gi.require_version('Gst','1.0')" >/dev/null 2>&1; then + nohup "$PY" "$DIR/run.py" >>/tmp/hms-mediaengine.log 2>&1 & + PORT=8080 + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ + 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40; do + curl -s -o /dev/null "http://localhost:$PORT/api/health" && break + sleep 0.5 + done + open "http://localhost:$PORT" + exit 0 +else + nohup "$PY" "$DIR/installer_gui.py" >/tmp/hms-installer.log 2>&1 & + exit 0 +fi +''' + + +def build() -> None: + macos = APP / "Contents" / "MacOS" + macos.mkdir(parents=True, exist_ok=True) + (APP / "Contents" / "Info.plist").write_text(INFO_PLIST, "utf-8") + launcher = macos / "HMS-Launcher" + launcher.write_text(LAUNCHER, "utf-8") + launcher.chmod(0o755) + print(f"App erstellt: {APP}") + print("Doppelklick im Finder: 'HMS MediaEngine.app'") + + +if __name__ == "__main__": + build()