Files
hms-mediaengine/_entwicklung/tools/fixture_generator/fixture_generator.py
T
HMS MediaEngine Agent 362e089be0 AUFGERAUMT: Root auf 10 sichtbare Elemente reduziert
Der Nutzer hat recht: Der Ordner war voller Entwicklungs-Muell.
Jetzt ist sauber getrennt:

ROOT (was der Nutzer sieht und braucht):
- run.py                     = das Programm
- hms_app/                   = der Anwendungscode
- HMS MediaEngine.app        = macOS Doppelklick-Starter
- HMS-Start.vbs              = Windows Doppelklick-Starter
- HMS-Install.vbs             = Windows Erst-Installation
- HMS-Mac-Install.command     = macOS Homebrew-Installation
- HMS-Portable-Install.command = macOS Portable-Installation (16GB-Fix)
- installer_gui.py           = grafischer Installer
- launcher.pyw + launcher_core.py = interne Start-Logik
- LIESMICH.txt               = 10-Zeilen-Kurzanleitung
- .gitignore

_entwicklung/ (alles andere, NICHT benoetigt):
- packages/ apps/ native/ plugins/ tools/ schemas/ tests/ docs/
  build/ fixture_profiles/
- PLAN.md STATUS.md ERRORS.md TEST_REPORT.md CHANGELOG.md README.md
- pyproject.toml uv.lock setup_*.sh/ps1 make_mac_app.py

Diese Trennung gilt ab sofort fuer alle Commits. Der Nutzer kann
_entwicklung/ loeschen wenn er Platz braucht - die App laeuft ohne.

Verifiziert: App startet nach Aufraeumen unveraendert (Health 200).
2026-09-11 23:44:06 +02:00

149 lines
6.0 KiB
Python

"""Fixture-Generator (PLAN.md §16.3, §16.4, §16.7).
Erzeugt menschenlesbare CSV-Kanallisten für die V1-Personalities:
- HMS MediaEngine Master 32ch
- HMS MediaEngine Layer 64ch
GDTF-/pultspezifische Personality-Dateien folgen in Phase 4; die hier
erzeugten Listen tragen bereits dieselbe Fixture-Schema-Version (§16.7).
"""
from __future__ import annotations
import argparse
import csv
from pathlib import Path
FIXTURE_SCHEMA_VERSION = 1
MASTER32: list[tuple[int, str, str]] = [
(1, "Master Intensity (MSB)", "16 Bit (mit Kanal 2)"),
(2, "Master Intensity (LSB)", "16 Bit"),
(3, "Blackout", "Trigger/Schalter, höchste Priorität"),
(4, "Freeze Output", "Schalter"),
(5, "Preset Bank", "8 Bit"),
(6, "Preset Index (MSB)", "16 Bit (mit Kanal 7)"),
(7, "Preset Index (LSB)", "16 Bit"),
(8, "Preset Recall", "steigende Flanke, direkter Abruf ohne Cue-GO-Logik"),
(9, "Transition Type", "Enum"),
(10, "Transition Duration (MSB)", "16 Bit, konfigurierter Maximalwert"),
(11, "Transition Duration (LSB)", "16 Bit"),
(12, "Global Speed (MSB)", "16 Bit"),
(13, "Global Speed (LSB)", "16 Bit"),
(14, "BPM (MSB)", "16 Bit"),
(15, "BPM (LSB)", "16 Bit"),
(16, "Tap Tempo", "steigende Flanke"),
(17, "reserviert (Cue/Timeline-Erweiterung)", "im MVP neutral ignorieren"),
(18, "reserviert (Cue/Timeline-Erweiterung)", "im MVP neutral ignorieren"),
(19, "reserviert (Cue/Timeline-Erweiterung)", "im MVP neutral ignorieren"),
(20, "reserviert (Cue/Timeline-Erweiterung)", "im MVP neutral ignorieren"),
(21, "reserviert (Cue/Timeline-Erweiterung)", "im MVP neutral ignorieren"),
(22, "Audio Reactive Enable", "Schalter"),
(23, "Audio Master Gain", "8 Bit"),
(24, "Automation/AI Enable", "nur Freigabe, keine Sicherheitsumgehung"),
(25, "Output Test Pattern", "Enum"),
(26, "Preview Enable", "Schalter"),
(27, "Global Hue", "8 Bit"),
(28, "Global Saturation", "8 Bit"),
(29, "Fallback Preset", "8 Bit"),
(30, "Release Manual Overrides", "Trigger mit Schutzlogik"),
(31, "reserviert", "muss neutral ignoriert werden"),
(32, "reserviert", "muss neutral ignoriert werden"),
]
def _layer64() -> list[tuple[int, str, str]]:
rows: list[tuple[int, str, str]] = [
(1, "Layer Enable", "Schalter"),
(2, "Opacity (MSB)", "16 Bit"),
(3, "Opacity (LSB)", "16 Bit"),
(4, "Source Type", "Enum: Media/Generator/Live/Solid"),
(5, "Media Bank", "8 Bit"),
(6, "Media Folder", "8 Bit"),
(7, "Media/Plugin Index (MSB)", "16 Bit"),
(8, "Media/Plugin Index (LSB)", "16 Bit"),
(9, "Load/Commit Selection", "steigende Flanke"),
(10, "Transport", "Enum: Stop/Play/Pause/Retrigger"),
(11, "Loop Mode", "Enum"),
(12, "Playback Direction/Mode", "Enum"),
(13, "Playback Speed (MSB)", "16 Bit, signed Mapping"),
(14, "Playback Speed (LSB)", "16 Bit, signed Mapping"),
(15, "Playback Position (MSB)", "16 Bit, normalisiert"),
(16, "Playback Position (LSB)", "16 Bit, normalisiert"),
(17, "In Point (MSB)", "16 Bit, normalisiert"),
(18, "In Point (LSB)", "16 Bit, normalisiert"),
(19, "Out Point (MSB)", "16 Bit, normalisiert"),
(20, "Out Point (LSB)", "16 Bit, normalisiert"),
(21, "Blend Mode", "Enum"),
(22, "Transform Mode/Anchor", "Enum"),
(23, "Position X (MSB)", "16 Bit, signed"),
(24, "Position X (LSB)", "16 Bit, signed"),
(25, "Position Y (MSB)", "16 Bit, signed"),
(26, "Position Y (LSB)", "16 Bit, signed"),
(27, "Scale X (MSB)", "16 Bit"),
(28, "Scale X (LSB)", "16 Bit"),
(29, "Scale Y (MSB)", "16 Bit"),
(30, "Scale Y (LSB)", "16 Bit"),
(31, "Rotation (MSB)", "16 Bit"),
(32, "Rotation (LSB)", "16 Bit"),
(33, "Crop Left", "8 Bit"),
(34, "Crop Right", "8 Bit"),
(35, "Crop Top", "8 Bit"),
(36, "Crop Bottom", "8 Bit"),
(37, "Hue", "8 Bit"),
(38, "Saturation", "8 Bit"),
(39, "Brightness", "8 Bit"),
(40, "Contrast", "8 Bit"),
(41, "FX1 Enable", "Schalter"),
(42, "FX1 Plugin Select", "8 Bit, Show-Registry"),
(43, "FX1 Mix", "8 Bit"),
]
for slot in range(1, 9):
rows.append((43 + slot, f"FX1 Parameter P{slot}", "8 Bit oder manifestgebundene Paare"))
rows.extend(
[
(52, "FX2 Enable", "Schalter"),
(53, "FX2 Plugin Select", "8 Bit, Show-Registry"),
(54, "FX2 Mix", "8 Bit"),
]
)
for slot in range(1, 9):
rows.append((54 + slot, f"FX2 Parameter P{slot}", "8 Bit oder manifestgebundene Paare"))
rows.append((63, "Layer Retrigger/Reset", "steigende Flanke"))
rows.append((64, "reserviert", "muss neutral ignoriert werden"))
return rows
LAYER64: list[tuple[int, str, str]] = _layer64()
_HEADER = ["channel", "parameter", "resolution_behavior"]
def write_csv(rows: list[tuple[int, str, str]], path: Path) -> Path:
"""Schreibt eine Kanalliste als CSV; Elternordner werden angelegt."""
if [r[0] for r in rows] != list(range(1, len(rows) + 1)):
raise ValueError("channel numbers must be contiguous starting at 1")
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", newline="", encoding="utf-8") as fh:
writer = csv.writer(fh)
writer.writerow(_HEADER)
writer.writerows(rows)
return path
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(prog="fixture_generator")
parser.add_argument("--out-dir", default="fixture_profiles", help="Zielordner")
args = parser.parse_args(argv)
out = Path(args.out_dir)
master = write_csv(MASTER32, out / "master32" / "master32.csv")
layer = write_csv(LAYER64, out / "layer64" / "layer64.csv")
print(f"master32: {master} ({len(MASTER32)} Kanäle)")
print(f"layer64: {layer} ({len(LAYER64)} Kanäle)")
print(f"fixture_schema_version: {FIXTURE_SCHEMA_VERSION}")
return 0
if __name__ == "__main__":
raise SystemExit(main())