CANCOM Simple Signage
Browserbasiertes Digital-Signage-System für interne Info-Screens.
Features
- 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 Standort konfigurierbares Hintergrundbild
- Pro Screen eigene Playlist mit Drag-&-Drop-Reihenfolge
- Auto-Reload bei Playlist-Änderungen
- Newsticker pro Screen
- Custom-URL-Button: pro Screen konfigurierbarer Aktions-Button mit frei wählbarer Position (9 Positionen) im Player (öffnet URL in iframe-Overlay mit Zurück-Button oder per Direkt-Weiterleitung)
- Digital Voice Agent: global konfigurierbarer Voice-Agent-Button pro Screen (9 Positionen, iframe-Overlay mit positionsgetreuem Zurück-Button, Typewriter-Tagline mit mehrsprachigen Wechseltexten, optionales Bild über dem Button)
- Stay-on-First: Screen kann auf erstem Playlist-Element stehen bleiben (kein automatischer Refresh)
- User-Verwaltung: Mehrere User mit Rollen (Admin / Superuser / User), E-Mail als Login, Passwort-Hashing (scrypt), Berechtigungen pro Standort
- Passwort-Workflow: First-Login-Änderung, Admin-Reset mit temporärem Passwort, Notfall-Login bei fehlendem Hash
- Admin Dashboard:
/admin/dashboard– Statistiken (Sites, Screens, User, Admins, Superuser) mit Sparkline-Charts + Trendanzeige, Aktivitätsverlauf - Aktivitätsverlauf: Alle Erstell-/Lösch-/Änderungsaktionen sowie Login/Logout werden in
history.jsonprotokolliert und im Dashboard angezeigt - Tab-basierte Admin-UI pro Screen: Playlist, Einstellungen, Aktionen, Digital Voice Agent, Medien (Tabler Tabs)
- Priority-Seite ebenfalls mit Tabs: Playlist und Medien
- Dark Mode (localStorage-persistiert)
- CI-konformes Admin-UI (CANCOM-Design:
brand-surface,nav-surfacerot)
Architektur
Browser (Player)
│
▼
Flask App (Server)
├── Admin UI /admin/<site>
├── Player UI /player/<site>/<screen>
├── Priority-Seite /admin/<site>/priority
├── config.json
├── users.json
├── history.json
├── media/
│ ├── <site>/
│ │ ├── <screen>/
│ │ │ ├── bild.jpg
│ │ │ ├── video.mp4
│ │ │ └── welcome.html
│ └── priority/
└── generate_welcome_page.py
- Server: Python 3 + Flask
- Player: Jeder moderne Browser (Chrome Kiosk, Edge, Firefox)
- State:
config.json+ Dateisystem - Frontend: Tabler Core + Tabler Icons + SortableJS (CDN)
Projektstruktur
signage/
├── app.py # Flask-App (alle Routen)
├── generate_welcome_page.py # Logo-Suche + Willkommensseite-Generierung
├── config.json # Persistente Konfiguration
├── users.json # User-Datenbank (gehashte Passwörter)
├── history.json # Aktivitätsverlauf (Dashboard)
├── media/
│ ├── <site>/
│ │ ├── lobby/
│ │ ├── casino/
│ │ └── videosysteme/
│ └── priority/
├── templates/
│ ├── admin.html # Admin-Dashboard (Übersicht)
│ ├── admin_dashboard.html # Admin Dashboard (Statistiken + Verlauf)
│ ├── priority.html # Priority-Playlist (eigene Seite)
│ ├── customer.html # Willkommensseite-Formular
│ ├── player.html # Player-Ansicht
│ ├── login.html
│ ├── change_password.html # Passwort ändern (First-Login / Reset)
│ ├── user_list.html # User-Liste (Admin)
│ ├── user_create.html # User anlegen (Admin)
│ ├── user_edit.html # User bearbeiten (Admin)
│ ├── _header.html # Gemeinsamer Header
│ ├── _footer.html # Gemeinsamer Footer
│ └── _styles.html # Zentrale CSS (Variablen, Dark Mode)
├── static/
│ ├── cancom.svg
│ ├── dva.png
│ └── wallpaper.png
└── AGENTS.md
Installation
Voraussetzungen
- Python ≥ 3.9
- pip
- ffmpeg (optional, für Videokonvertierung)
Setup
pip install -r requirements.txt
Starten
python app.py
Server läuft auf http://localhost:5005.
Docker
Empfohlenes docker-compose.yml mit Bind-Mounts für persistente Daten:
services:
signage:
container_name: signage
build: .
ports:
- "5005:5005"
restart: unless-stopped
volumes:
- ./media:/app/media
- ./config.json:/app/config.json
- ./users.json:/app/users.json
- ./history.json:/app/history.json
environment:
- TZ=Europe/Berlin
networks:
default:
driver: bridge
Der Container läuft als User mit UID 1000. Daher vor dem ersten Start die Berechtigungen setzen:
chown 1000:1000 config.json users.json history.json
chown -R 1000:1000 media/
Start:
docker compose up -d
Server läuft auf http://localhost:5005 (gunicorn mit 4 Workern, non-root User).
Routen
| Route | Beschreibung |
|---|---|
GET / |
Weiterleitung zum Login |
GET /login |
Admin-Login |
GET /logout |
Ausloggen |
GET /admin |
Redirect zum ersten konfigurierten Standort |
GET /admin/dashboard |
Admin Dashboard (Statistiken + Verlauf) |
GET /admin/<site> |
Admin-Dashboard für einen Standort |
GET /admin/<site>/priority |
Priority-Playlist (separate Seite) |
POST /admin/<site>/update/<screen> |
Allgemeine Screen-Einstellungen speichern (Intervall, Newsticker, Bilder/Videos, stay_on_first) |
POST /admin/<site>/update-actions/<screen> |
Aktionen-Einstellungen speichern (Custom-URL) |
POST /admin/<site>/update-voice/<screen> |
Voice-Agent-Einstellungen speichern (Enabled, Label, Target, Position, Bild) |
POST /admin/<site>/upload/<screen> |
Medien hochladen |
POST /admin/<site>/add-url/<screen> |
URL zur Playlist hinzufügen |
POST /admin/<site>/delete/<screen>/<filename> |
Datei löschen |
POST /admin/<site>/playlist/<screen> |
Playlist-Reihenfolge speichern (JSON) |
POST /admin/<site>/delete-screen/<screen> |
Screen + Medien löschen |
GET /admin/<site>/add-screen?name=<name> |
Neuen Screen anlegen |
GET /add-site?name=<name> |
Neuen Standort anlegen |
POST /admin/<site>/delete-site |
Standort + alle Screens/Medien löschen |
POST /admin/<site>/upload-background |
Hintergrundbild für Willkommensseite hochladen |
POST /admin/<site>/delete-background |
Hintergrundbild zurücksetzen |
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 /change-password |
Passwort ändern (First-Login / nach Admin-Reset) |
GET /admin/users |
User-Liste (Admin) |
GET /admin/users/create |
User anlegen (Admin) |
GET /admin/users/edit/<email> |
User bearbeiten (Admin) |
POST /admin/users/create |
User anlegen (Admin) |
POST /admin/users/delete/<email> |
User löschen (Admin, letzter Admin geschützt) |
POST /admin/users/reset-password/<email> |
Passwort-Reset mit temporärem Passwort (Admin) |
POST /admin/users/edit/<email> |
User bearbeiten speichern (Admin) |
GET /media/<site>/<screen>/<file> |
Medien-Datei ausliefern |
GET /media/priority/<file> |
Priority-Medien (global) |
GET /media/<site>/background/<filename> |
Hintergrundbild der Willkommensseite |
Admin Portal
http://localhost:5005/admin/<standort>
- Screens konfigurieren (Tab-basiert: Playlist, Einstellungen, Aktionen, Digital Voice Agent, Medien)
- Medien hochladen / löschen
- Playlist per Drag & Drop sortieren
- Priority-Playlist verwalten (ebenfalls mit Tabs)
- Willkommensseite generieren (bis zu 3 Kundenlogos, Hintergrundbild pro Standort)
- Custom-URL-Aktionsbutton pro Screen konfigurieren (Position, iframe-Overlay oder Weiterleitung)
- Digital Voice Agent pro Screen konfigurieren (Position, Bild ein/aus, Typewriter-Tagline)
- Stay-on-First-Modus pro Screen (kein automatischer Refresh)
- Standorte anlegen & löschen
- Userverwaltung: User anlegen, bearbeiten, löschen, Passwort-Reset (temporäres Passwort wird angezeigt)
Admin Dashboard
http://localhost:5005/admin/dashboard
- 5 Statistik-Cards (Standorte, Screens, User, Admins, Superuser) mit ApexCharts-Sparkline + Trendanzeige
- Aktivitätsverlauf (History) als Liste mit Zeitstempel, Aktion und User
- Alle Erstell-/Lösch-/Änderungsaktionen sowie Login/Logout werden protokolliert
Player-URL
http://localhost:5005/player/<standort>/<screen>
Beispiel:
http://localhost:5005/player/stuttgart/lobby
Willkommensseite
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. Pro Standort kann ein eigenes Hintergrundbild hochgeladen werden (Fallback auf static/wallpaper.png).
Globale Konfiguration (config.json)
| Feld | Typ | Beschreibung |
|---|---|---|
server_url |
string | Öffentliche Server-URL für Player-Links |
voice_agent_url |
string | URL des Digital Voice Agents (global, read-only im Admin) |
User-Rollen (users.json)
| Rolle | Zugriff | Userverwaltung |
|---|---|---|
| Admin | Alle Standorte | Ja (anlegen/bearbeiten/löschen/Reset) |
| Superuser | Alle Standorte | Nein |
| User | Nur freigegebene Standorte | Nein |
- Usernamen sind E-Mail-Adressen
- Passwörter werden als scrypt-Hash gespeichert
users.jsonwird beim ersten Start ausconfig.json.adminbefüllt- Notfall:
users.jsonlöschen → Server-Neustart erzeugt neuen Admin ausconfig.json
| Feld | Typ | Beschreibung |
|---|---|---|
server_url |
string | Öffentliche Server-URL für Player-Links |
voice_agent_url |
string | URL des Digital Voice Agents (global, read-only im Admin) |
Konfiguration (config.json)
{
"server_url": "http://signage.ccmake.de",
"voice_agent_url": "https://voice-agent.example.com",
"admin": { "username": "...", "password": "..." },
"sites": {
"stuttgart": {
"screens": {
"lobby": {
"playlist": [...],
"interval": 10,
"show_images": true,
"show_videos": true,
"newsticker_text": "...",
"newsticker_enabled": false,
"custom_url_enabled": false,
"custom_url": "https://...",
"custom_url_label": "Infos",
"custom_url_target": "overlay",
"custom_url_position": "top-left",
"stay_on_first": false,
"voice_agent_enabled": true,
"voice_agent_show_image": false,
"voice_agent_label": "Digitaler Assistent",
"voice_agent_target": "overlay",
"voice_agent_position": "top-left"
},
"casino": { "playlist": [...], "interval": 15 }
},
"welcome_data": {
"names": ["Firma A"],
"logo_urls": ["https://..."],
"background_url": null
}
}
},
"priority": {
"enabled": true,
"playlist": [...]
}
}
Screen-Konfiguration (pro Screen in config.json)
| Feld | Typ | Beschreibung |
|---|---|---|
playlist |
Array | Playlist-Einträge (Strings oder Dicts mit url/zoom) |
interval |
int | Anzeige-Intervall in Sekunden |
show_images |
bool | Bilder anzeigen |
show_videos |
bool | Videos anzeigen |
newsticker_text |
string | Text für Newsticker (max. 200 Zeichen) |
newsticker_enabled |
bool | Newsticker anzeigen |
custom_url_enabled |
bool | Custom-URL-Button im Player anzeigen |
custom_url |
string | URL des Aktions-Buttons |
custom_url_label |
string | Button-Beschriftung |
custom_url_target |
string | "overlay" (iframe) oder "redirect" |
custom_url_position |
string | Position des Buttons: top-left, top-center, top-right, middle-left, middle-center, middle-right, bottom-left, bottom-center, bottom-right |
stay_on_first |
bool | Player bleibt auf erstem Element stehen (kein Refresh) |
voice_agent_enabled |
bool | Voice-Agent-Button im Player anzeigen |
voice_agent_show_image |
bool | Bild (dva.png) über dem Button anzeigen |
voice_agent_label |
string | Button-Beschriftung (Default: "Digitaler Assistent") |
voice_agent_target |
string | "overlay" (iframe) oder "redirect" |
voice_agent_position |
string | Position des Buttons: top-left, top-center, top-right, middle-left, middle-center, middle-right, bottom-left, bottom-center, bottom-right |
Helper-Funktionen (app.py)
load_config()/save_config()– JSON lesen/schreibenget_site_list(cfg)– alle Standorte sortiertget_screen_config(cfg, site, screen)– Screen-Konfiguration mit Defaultsis_url(item)– prüft ob Item eine URL istnormalize_url(item)– normalisiert URL-Item zu{"url", "zoom"}playlist_item_name(item)– Name aus String oder Dict extrahierenplaylist_item_enabled(item)– Enabled-Status prüfenload_priority_files()– Priority-Playlist + Dateien ladenprio_redirect(site)– Redirect-Pfad zur Priority-Seiteget_background_url(site)– URL zum benutzerdefinierten Hintergrundbild oderNoneload_users()/save_users()/get_user()– User aususers.jsoninit_user_db()– initialen Admin ausconfig.jsonanlegenget_accessible_sites(cfg, user)– Standorte nach User-Berechtigung filternadmin_required/site_access_required– Zugriffs-Dekoratoren
Dark Mode
Wird über localStorage("signage-theme") persistiert. Umschalt-Button im Header. CSS-Variablen --ccm-* in _styles.html.
Sicherheit
- Admin-Bereich per Flask-Login geschützt (E-Mail + Passwort)
- Passwörter gehasht (scrypt) in
users.json - Drei Rollen: Admin (alle Standorte + Userverwaltung), Superuser (alle Standorte), User (nur zugewiesene Standorte)
- Zugriffskontrolle per Decorator (
@admin_required,@site_access_required) - Letzter Admin kann nicht gelöscht werden
- 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
ffmpeg -i input.mov -c:v libx264 -pix_fmt yuv420p -movflags +faststart output.mp4
Maintainer
CANCOM Simple Signage – Interne Lösung, nicht für externe Weitergabe bestimmt.