fix: hochgeladene Logos nicht mehr in einem Screen-Ordner ablegen
Hochgeladene Logos landeten unter media/<site>/logos/. admin() behandelt aber jeden Unterordner von media/<site>/ als Screen und schreibt ihn per get_screen_config() dauerhaft in die Konfiguration. Der Logo-Ordner erschien dadurch als Screen in der Oberflaeche, bekam beim Generieren eine eigene welcome.html und liess sich wie ein Screen bespielen - und die darin liegenden Logos wie Screen-Medien loeschen. Genau so ist eines der hochgeladenen Logos verschwunden. Logos liegen jetzt als media/<site>/logo_<slot>.png direkt im Standort-Verzeichnis, so wie das Hintergrundbild als media/<site>/background.* - aus demselben Grund. Die URL bleibt /media/<site>/logos/<datei>, die Route liest aus dem Standort-Verzeichnis; bestehende welcome.html funktionieren damit unveraendert weiter. Zusaetzlich wird der Ordnername "logos" bei der Screen-Erkennung uebersprungen, damit Altbestaende nicht erneut als Screen auftauchen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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/<site>/logos/logo_<slot>.png`, Auslieferung über `/media/<site>/logos/<datei>`. Die URL traegt einen `?v=<timestamp>`, 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/<site>/logo_<slot>.png`, Auslieferung über `/media/<site>/logos/<datei>` (die Route liest aus dem Standort-Verzeichnis).
|
||||
- **Keine neuen Unterordner unter `media/<site>/` 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=<timestamp>`, 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.
|
||||
|
||||
@@ -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/<site>/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/<site>/logos/logo_<slot>.png` abgelegt und über `/media/<site>/logos/<datei>` 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/<site>/logo_<slot>.png` abgelegt und über `/media/<site>/logos/<datei>` ausgeliefert. **Bewusst als Datei im Standort-Verzeichnis, nicht in einem Unterordner:** `admin()` behandelt jeden Unterordner von `media/<site>/` 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/<site>/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.
|
||||
|
||||
|
||||
17
app.py
17
app.py
@@ -496,8 +496,10 @@ def media_background(site, filename):
|
||||
@app.route("/media/<site>/logos/<path:filename>")
|
||||
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)
|
||||
|
||||
@@ -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/<site>/ als Screen
|
||||
# und legt ihn dauerhaft in der Konfiguration an. Das Hintergrundbild wird aus
|
||||
# demselben Grund als media/<site>/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
|
||||
|
||||
Reference in New Issue
Block a user