Standortauswahl und Multi Logo und Action Button

This commit is contained in:
Erik Thiele
2026-06-05 12:55:01 +02:00
parent 4733c11832
commit d7bebf1226
13 changed files with 1562 additions and 761 deletions

239
README.md
View File

@@ -1,85 +1,95 @@
# 📺 CANCOM Simple Signage
# CANCOM Simple Signage
**CANCOM Simple Signage** ist ein leichtgewichtiges, browserbasiertes Digital-Signage-System auf Basis von **Python + Flask**.
Ziel ist eine **einfache, robuste und wartbare Lösung** für interne Info-Screens, ohne die Komplexität klassischer Digital-Signage-Plattformen.
Browserbasiertes Digital-Signage-System für interne Info-Screens.
---
## Features
## Features
- Browser-basierter Player (kein nativer Client erforderlich)
- Bilder & Videos (MP4, H.264)
- ✅ Pro Screen eine eigene Playlist
- ✅ Drag-&-Drop-Reihenfolge im Admin-Portal
- ✅ Automatischer Player-Reload bei Änderungen
- ✅ Upload & Löschen von Medien
- ✅ Screen-Status (aktiv / leer)
- ✅ CI-konformes Admin-UI (CANCOM)
- ✅ Läuft lokal, in VMs oder auf Proxmox
- Browserbasierter Player (Kiosk-Modus, Chromium, Firefox)
- Bilder, Videos (MP4/H.264), HTML-Seiten, URL-Playlist-Einträge
- **Multi-Standort**: Standorte (sites) gruppieren Screens
- **Priority-Playlist**: globale Inhalte wirken auf alle Player
- **Willkommensseite**: bis zu 3 Kundenlogos via OpenAI + Brandfetch
- Pro Screen eigene Playlist mit Drag-&-Drop-Reihenfolge
- Auto-Reload bei Playlist-Änderungen
- Newsticker pro Screen
- **Custom-URL-Button**: pro Screen konfigurierbarer Aktions-Button oben links im Player (öffnet URL in iframe-Overlay mit Zurück-Button oder per Direkt-Weiterleitung)
- Dark Mode (localStorage-persistiert)
- CI-konformes Admin-UI (CANCOM-Design: `brand-surface`, `nav-surface` rot)
---
## 🧱 Architektur
## Architektur
```
Browser (Player)
Flask App (Server)
├── Admin UI
├── Player UI
├── Admin UI /admin/<site>
├── Player UI /player/<site>/<screen>
├── Priority-Seite /admin/<site>/priority
├── config.json
── media/
── <screen-name>/
├── bild.jpg
── video.mp4
── media/
── <site>/
├── <screen>/
── bild.jpg
│ │ │ ├── video.mp4
│ │ │ └── welcome.html
│ └── priority/
└── generate_welcome_page.py
```
- **Server:** Python 3 + Flask
- **Player:** Jeder moderne Browser (Chrome, Edge, Firefox, Kiosk-Modus)
- **Player:** Jeder moderne Browser (Chrome Kiosk, Edge, Firefox)
- **State:** `config.json` + Dateisystem
- **Frontend:** Tabler + SortableJS (CDN)
- **Frontend:** Tabler Core + Tabler Icons + SortableJS (CDN)
---
## 📂 Projektstruktur
## Projektstruktur
```
signage/
├── app.py
├── config.json
├── app.py # Flask-App (alle Routen)
├── generate_welcome_page.py # Logo-Suche + Willkommensseite-Generierung
├── config.json # Persistente Konfiguration
├── media/
│ ├── lobby/
└── meetingroom/
│ ├── <site>/
│ ├── lobby/
│ │ ├── casino/
│ │ └── videosysteme/
│ └── priority/
├── templates/
│ ├── admin.html
│ ├── player.html
── login.html
│ ├── admin.html # Admin-Dashboard (Übersicht)
│ ├── priority.html # Priority-Playlist (eigene Seite)
── customer.html # Willkommensseite-Formular
│ ├── player.html # Player-Ansicht
│ ├── login.html
│ ├── _header.html # Gemeinsamer Header
│ ├── _footer.html # Gemeinsamer Footer
│ └── _styles.html # Zentrale CSS (Variablen, Dark Mode)
├── static/
└── README.md
│ ├── cancom.svg
│ └── wallpaper.png
└── AGENTS.md
```
---
## 🚀 Installation
## Installation
### Voraussetzungen
- Python ≥ 3.9
- pip
- ffmpeg
```bash
sudo apt install python3 python3-pip ffmpeg
```
- ffmpeg (optional, für Videokonvertierung)
### Setup
```bash
git clone <repository-url>
cd signage
pip install flask flask-login werkzeug
pip install flask flask-login werkzeug openai
```
### Starten
@@ -88,72 +98,134 @@ pip install flask flask-login werkzeug
python app.py
```
Server läuft auf:
Server läuft auf `http://localhost:5005`.
```
http://localhost:5005
### Docker
```bash
docker compose up -d
```
---
## 🛠️ Betrieb
## Routen
| Route | Beschreibung |
|-------|-------------|
| `GET /` | 404 kein Root-Endpunkt |
| `GET /login` | Admin-Login |
| `GET /logout` | Ausloggen |
| `GET /admin` | Redirect zum ersten konfigurierten Standort |
| `GET /admin/<site>` | Admin-Dashboard für einen Standort |
| `GET /admin/<site>/priority` | Priority-Playlist (separate Seite) |
| `GET /admin/<site>/update/<screen>` | Screen-Einstellungen speichern (POST) |
| `GET /admin/<site>/upload/<screen>` | Medien hochladen (POST) |
| `GET /admin/<site>/add-url/<screen>` | URL zur Playlist hinzufügen (POST) |
| `GET /admin/<site>/delete/<screen>/<filename>` | Datei löschen (POST) |
| `GET /admin/<site>/playlist/<screen>` | Playlist-Reihenfolge speichern (POST JSON) |
| `GET /player/<site>/<screen>` | Player-Ansicht |
| `GET /playlist/<site>/<screen>/hash` | Playlist-Checksumme (für Auto-Reload) |
| `GET /willkommen?site=<site>` | Willkommensseite-Formular (GET + POST) |
| `POST /api/customer` | API-Endpunkt für Willkommensseite (JSON) |
| `GET /media/<site>/<screen>/<file>` | Medien-Datei ausliefern |
| `GET /media/priority/<file>` | Priority-Medien (global) |
### Admin-Portal
```
/admin
http://localhost:5005/admin/<standort>
```
- Screens konfigurieren
- Medien hochladen / löschen
- Playlist per Drag & Drop sortieren
- Priority-Playlist verwalten
- Willkommensseite generieren (bis zu 3 Kundenlogos)
- Custom-URL-Aktionsbutton pro Screen konfigurieren (iframe-Overlay oder Weiterleitung)
### Player-URLs
### Player-URL
```
/player/<screen-name>
http://localhost:5005/player/<standort>/<screen>
```
Beispiel:
```
http://localhost:5005/player/stuttgart/lobby
```
### Willkommensseite
```
http://server:5000/player/lobby
http://localhost:5005/willkommen?site=stuttgart
```
Maximal 3 Kunden eingeben → Logos werden via OpenAI + Brandfetch gesucht → `welcome.html` wird in der Lobby-Playlist vorne eingefügt.
---
## Konfiguration (`config.json`)
```json
{
"admin": { "username": "...", "password": "..." },
"sites": {
"stuttgart": {
"screens": {
"lobby": { "playlist": [...], "interval": 10 },
"casino": { "playlist": [...], "interval": 15 }
}
}
},
"priority": {
"enabled": true,
"playlist": [...]
}
}
```
---
## 🔄 Automatischer Reload
## Helper-Funktionen (`app.py`)
Der Player aktualisiert sich selbst, wenn:
- Medien hinzugefügt oder gelöscht werden
- Playlist-Reihenfolge geändert wird
- Screen-Einstellungen angepasst werden
- `load_config()` / `save_config()` JSON lesen/schreiben
- `get_site_list(cfg)` alle Standorte sortiert
- `get_screen_config(cfg, site, screen)` Screen-Konfiguration mit Defaults
- `is_url(item)` prüft ob Item eine URL ist
- `normalize_url(item)` normalisiert URL-Item zu `{"url", "zoom"}`
- `playlist_item_name(item)` Name aus String oder Dict extrahieren
- `playlist_item_enabled(item)` Enabled-Status prüfen
- `load_priority_files()` Priority-Playlist + Dateien laden
- `prio_redirect(site)` Redirect-Pfad zur Priority-Seite
---
## ⚠️ Git & Medien
## Dark Mode
> **Dieses Repository ist für Code gedacht, nicht für Medien.**
Empfohlene `.gitignore`:
```gitignore
media/
*.mp4
*.mov
*.jpg
*.jpeg
*.png
```
Wird über `localStorage("signage-theme")` persistiert. Umschalt-Button im Header. CSS-Variablen `--ccm-*` in `_styles.html`.
---
## 🎞️ Video-Empfehlungen
## Sicherheit
- Format: MP4
- Codec: H.264
- Auflösung: max. 1920x1080
- Admin-Bereich per Flask-Login geschützt
- Player-Seiten Read-Only
- Externe Nutzung via Reverse Proxy + TLS empfohlen
---
## Git & Medien
> Das Repository ist für Code gedacht, nicht für Medien.
`media/` und Medien-Dateiendungen sind in `.gitignore` ausgeschlossen.
---
## Video-Empfehlungen
- Format: MP4 (H.264)
- Auflösung: max. 1920×1080
```bash
ffmpeg -i input.mov -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp4
@@ -161,25 +233,6 @@ ffmpeg -i input.mov -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp
---
## 🔐 Sicherheit
## Maintainer
- Admin-Bereich per Login geschützt
- Player-Seiten Read-Only
- Externe Nutzung via Reverse Proxy + TLS empfohlen
---
## 🧭 Roadmap
- Zeitsteuerung pro Playlist-Eintrag
- Vorschau im Admin-Portal
- Mehrere Admin-User
- SQLite statt config.json
- Docker-Compose Setup
---
## 👤 Maintainer
**CANCOM Simple Signage**
Interne Lösung nicht für externe Weitergabe bestimmt.
**CANCOM Simple Signage** Interne Lösung, nicht für externe Weitergabe bestimmt.