diff --git a/AGENTS.md b/AGENTS.md index 8d3627b..a6c9f47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -121,7 +121,8 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens. - Gemeinsame HTML-Bausteine: `_header.html`, `_footer.html`, `_styles.html` (CSS-Variablen `--ccm-*`, Dark Mode, Card-Border-Radius 1rem). - `add_customer_to_playlists` (früher `add_customer_to_lobby_playlist`) entfernt `welcome.html` als String *und* als Dict aus **allen** Screens des Standorts und fügt sie danach nur in den Zielscreens an Position 0 wieder ein – ein abgewählter Screen bleibt so nicht mit einer alten Begrüßung stehen. Eingefügt wird ein Dict, kein String, damit `enabled` und `schedule` mitgeführt werden können. - `welcome_target_screens(config, site)` fällt nur dann auf `lobby` zurück, wenn **kein** Screen des Standorts den Schlüssel `welcome_enabled` besitzt. Wurde er überall bewusst auf `False` gesetzt, ist die leere Liste das gewollte Ergebnis. -- **Logo-Upload**: Pro Kunde kann im Willkommensseiten-Formular eine Bilddatei hochgeladen werden (`logo_file_1..3`); ein Upload hat Vorrang vor `search_customer_logo`. Ablage als `media//logos/logo_.png`, Auslieferung über `/media//logos/`. Die URL traegt einen `?v=`, damit Player kein altes Logo aus dem Cache zeigen. +- **Logo-Upload**: Pro Kunde kann im Willkommensseiten-Formular eine Bilddatei hochgeladen werden (`logo_file_1..3`); ein Upload hat Vorrang vor `search_customer_logo`. Ablage als `media//logo_.png`, Auslieferung über `/media//logos/` (die Route liest aus dem Standort-Verzeichnis). +- **Keine neuen Unterordner unter `media//` anlegen.** `admin()` behandelt jeden Unterordner dort als Screen und persistiert ihn ueber `get_screen_config()` in der Konfiguration - ein Ordner `logos` erschien dadurch als Phantom-Screen, bekam eine `welcome.html` und liess sich ueber die Admin-UI bespielen und loeschen. Deshalb liegen Logos wie das Hintergrundbild als Dateien direkt im Standort-Verzeichnis. Der Name `logos` ist in der Screen-Erkennung zusaetzlich ausgenommen, damit Altbestaende nicht wieder auftauchen. Die URL traegt einen `?v=`, damit Player kein altes Logo aus dem Cache zeigen. - Nach dem Freistellen schneidet `trim_transparent_border()` die durchsichtigen Raender weg. Das ist kein Schoenheitsdetail: `.customer-logo` skaliert mit `object-fit: contain` auf 120px Hoehe, ein breiter Rand zaehlt dabei mit und laesst ein hochgeladenes Logo deutlich kleiner erscheinen als ein eng beschnittenes Brandfetch-Logo (gemessen: 30px statt 120px). Der Schnitt greift auch bei bereits transparenten PNGs, aber nicht bei abgewaehltem Freistellen - dort gibt es keinen transparenten Rand. - `remove_logo_background()` arbeitet mit Pillow (Flood Fill von den Raendern, weiche Kante). **Bewusst kein ML-Modell** wie rembg: der Container laeuft mit read-only Root-FS ohne Modell-Cache. Flood Fill statt globalem Farbvergleich ist wesentlich - sonst verschwindet auch Weiss *innerhalb* des Logos. Bereits transparente Bilder werden unveraendert durchgereicht; bei unruhigem Bildrand bleibt das Bild unangetastet und die Funktion gibt einen Hinweis zurueck. - `welcome_data` in der Site-Config wird **feldweise** gespeichert (Slot 1-3, auch leere), nicht nur die erfolgreichen Kunden - sonst verschiebt sich die Zuordnung von Name, Logo und Ansprechpartner beim naechsten Aufruf. Ein zuvor hochgeladenes Logo wird beim erneuten Generieren wiederverwendet, solange der Kundenname im selben Feld unveraendert ist. diff --git a/README.md b/README.md index 44e923e..9007d5a 100755 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ Maximal 3 Kunden eingeben → Logos werden via OpenAI + Brandfetch gesucht → ` **Der Schalter wirkt sofort:** Beim Einschalten wird eine bereits vorhandene Willkommensseite unmittelbar in die Playlist des Screens gesetzt, beim Ausschalten wieder entfernt (samt Datei) – ohne erneutes Generieren. Die Seite ist je Standort identisch, ein zweiter Handgriff wäre also nur Aufwand ohne anderes Ergebnis. Beim Generieren wird zusätzlich eine Standort-Kopie unter `media//welcome.html` abgelegt, die nie gelöscht wird; dadurch findet der Schalter auch dann noch eine Quelle, wenn die Seite zwischenzeitlich auf allen Screens abgeschaltet war. -**Logo-Upload:** Findet die automatische Suche kein Logo (oder passt es nicht), kann pro Kunde eine Bilddatei hochgeladen werden. Ein Upload hat Vorrang vor der Suche. Die Datei wird als `media//logos/logo_.png` abgelegt und über `/media//logos/` ausgeliefert. +**Logo-Upload:** Findet die automatische Suche kein Logo (oder passt es nicht), kann pro Kunde eine Bilddatei hochgeladen werden. Ein Upload hat Vorrang vor der Suche. Die Datei wird als `media//logo_.png` abgelegt und über `/media//logos/` ausgeliefert. **Bewusst als Datei im Standort-Verzeichnis, nicht in einem Unterordner:** `admin()` behandelt jeden Unterordner von `media//` als Screen und schreibt ihn dauerhaft in die Konfiguration – ein Ordner `logos` würde also als Phantom-Screen in der Oberfläche auftauchen. Das Hintergrundbild liegt aus demselben Grund als `media//background.*`. Der Ordnername `logos` ist zusätzlich reserviert und wird bei der Screen-Erkennung übersprungen. **Freistellen und Zuschneiden:** Beim Upload wird der Hintergrund automatisch entfernt, damit das Logo nicht in einem weissen Kasten auf dem Hintergrundbild steht. Anschliessend werden die durchsichtigen Ränder weggeschnitten (`trim_transparent_border`). Ohne diesen Schnitt erscheint ein hochgeladenes Logo kleiner als ein automatisch gefundenes: die Willkommensseite skaliert mit `object-fit: contain` auf eine feste Höhe von 120px, und ein breiter Rand zählt dabei mit – Brandfetch-Logos sind bereits eng beschnitten. Verfahren: Flood Fill von den Bildrändern her (bewusst kein globaler Farbvergleich – so bleibt Weiss *innerhalb* des Logos erhalten), weiche Kante gegen Treppeneffekte, Speicherung immer als PNG. Bereits transparente Bilder werden unverändert übernommen; ist der Bildrand nicht einfarbig, bleibt das Bild unangetastet und die Oberfläche weist darauf hin. Mit der Checkbox „Hintergrund behalten" lässt sich das Freistellen pro Kunde abschalten. Umgesetzt mit Pillow – bewusst ohne ML-Modell, da der Container mit read-only Root-FS läuft und kein Modell-Cache zur Verfügung steht. diff --git a/app.py b/app.py index 863d32a..cca2d78 100755 --- a/app.py +++ b/app.py @@ -496,8 +496,10 @@ def media_background(site, filename): @app.route("/media//logos/") def media_logo(site, filename): """Liefert ein manuell hochgeladenes Kundenlogo aus. - Ohne Login erreichbar wie die übrigen Medien - der Player ist nicht angemeldet.""" - path = os.path.join(MEDIA_DIR, site, generate_welcome_page.LOGO_DIRNAME) + Ohne Login erreichbar wie die übrigen Medien - der Player ist nicht angemeldet. + Die Dateien liegen direkt im Standort-Verzeichnis, damit kein Unterordner + entsteht, den admin() als Screen interpretieren wuerde.""" + path = os.path.join(MEDIA_DIR, site) return send_from_directory(path, filename) @@ -950,7 +952,7 @@ def add_customer(): frueher = load_config().get("sites", {}).get(site, {}).get("welcome_data", {}) frueher_namen = frueher.get("names", []) frueher_urls = frueher.get("logo_urls", []) - logo_prefix = f"/media/{site}/{generate_welcome_page.LOGO_DIRNAME}/" + logo_prefix = f"/media/{site}/{generate_welcome_page.LOGO_URL_PREFIX}/" found_logos = [None, None, None] for i, name in enumerate(customer_names): @@ -1401,11 +1403,16 @@ def admin(site): site_cfg = cfg.setdefault("sites", {}).setdefault(current_site, {"screens": {}}) config_screens = site_cfg.setdefault("screens", {}) - # Ordner im Medienverzeichnis = Screens + # Ordner im Medienverzeichnis = Screens. + # "logos" ist reserviert: aeltere Versionen legten dort hochgeladene + # Kundenlogos ab, und ein solcher Ordner wuerde sonst dauerhaft als Screen + # in der Konfiguration landen. if os.path.isdir(site_media_base): for entry in os.listdir(site_media_base): entry_path = os.path.join(site_media_base, entry) - if os.path.isdir(entry_path) and not entry.startswith("._"): + if (os.path.isdir(entry_path) + and not entry.startswith("._") + and entry != generate_welcome_page.LOGO_URL_PREFIX): known_screens.add(entry) # Screens aus Config ergänzen (auch ohne Ordner) diff --git a/generate_welcome_page.py b/generate_welcome_page.py index 8dda753..9ec408c 100644 --- a/generate_welcome_page.py +++ b/generate_welcome_page.py @@ -161,7 +161,12 @@ def remove_logo_background(image, tolerance=LOGO_TOLERANCE): return img, None -LOGO_DIRNAME = "logos" +# Logos liegen als Dateien direkt im Standort-Verzeichnis, nicht in einem +# Unterordner: admin() behandelt jeden Unterordner von media// als Screen +# und legt ihn dauerhaft in der Konfiguration an. Das Hintergrundbild wird aus +# demselben Grund als media//background.* abgelegt. +LOGO_URL_PREFIX = "logos" # nur der Pfadbestandteil in der URL +LOGO_FILENAME = "logo_{slot}.png" LOGO_EXTENSIONS = (".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp") @@ -198,12 +203,12 @@ def save_uploaded_logo(file_storage, site, slot, freistellen=True): if img.size != vorher: print(f"✅ Generate - Logo zugeschnitten: {vorher[0]}x{vorher[1]} → {img.size[0]}x{img.size[1]}") - logo_dir = os.path.join(MEDIA_DIR, site, LOGO_DIRNAME) - os.makedirs(logo_dir, exist_ok=True) - dateiname = f"logo_{slot}.png" - img.save(os.path.join(logo_dir, dateiname), "PNG") + site_dir = os.path.join(MEDIA_DIR, site) + os.makedirs(site_dir, exist_ok=True) + dateiname = LOGO_FILENAME.format(slot=slot) + img.save(os.path.join(site_dir, dateiname), "PNG") - url = f"/media/{site}/{LOGO_DIRNAME}/{dateiname}?v={int(time.time())}" + url = f"/media/{site}/{LOGO_URL_PREFIX}/{dateiname}?v={int(time.time())}" print(f"✅ Generate - Logo hochgeladen: {url}" + (f" (Hinweis: {hinweis})" if hinweis else "")) return url, hinweis