From 1cb260fa80c1d2f3311d5777e5a38c742aa15376 Mon Sep 17 00:00:00 2001 From: Erik Thiele Date: Sun, 3 May 2026 18:20:21 +0200 Subject: [PATCH] =?UTF-8?q?Automatische=20Willkommensseite=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 12292 -> 12292 bytes .gitignore | 1 + Dockerfile | 12 ++ app.py | 96 +++++++++++++++- config.json | 39 ++++--- docker-compose.yml | 17 +++ env.sh | 2 + generate_welcome_page.py | 241 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + templates/admin.html | 5 +- templates/customer.html | 133 +++++++++++++++++++++ templates/player.html | 11 ++ 12 files changed, 543 insertions(+), 17 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 env.sh create mode 100644 generate_welcome_page.py create mode 100644 templates/customer.html diff --git a/.DS_Store b/.DS_Store index 51e553f11809e3cd77d74c3c1b460846d21527eb..d11ac64da3f3aaf5d793a97778a04b34b66d4a3e 100644 GIT binary patch delta 1708 zcmeH|TTC2P7{|~5RLX2%!daHG%M|RumLjlHnk{X?+CrgLE`>Dgc41pB19K{)Fv!ku zQB1Z*Y#WjWORO4F3p1G59K)dCls2*&e5Gc(&7wnx;4#;wkLa6KM#7K0ngPmvogH_RcU zgcF?@MANOI1wZ{Y=gDL8Lar1I{Y`3nR7geZuS?;S^NZSzxTCRPKuPrF_b27UNo_jD zr|n+jLGJt+sB_fPLgyG;=UicX(&6S?yuE#M1);KvO||t6O}pFL_a9hd{=$GP7Y7rQ zhGh(n@P5r2;-+qx_r#|R-5yRFIyY_OsKHY{pCA61sg3fbfWMHHDC?EJi9ut0!muBZ zSXlD36t&zZu{RFun$7o*>29W;&MYa4S}93~+=bQ@7k53IQC%Z4w3sC-&(>47+2>^{Tv-cjV}R>{Zo_=q;hM?5uoN28ePOic2WWsI5G$=i2HqRdF&(AemxDBQ|Srxj&S zt0b{^?{+uu(k#2@er{&>SCoBsinWiE*07f1JyRx6d0NW8E=fAV3NrI<(q7%L$5NT* zyA@UGcg0$Co$Hd?CcZkK9YrG6?DzOQhQI9Q%q8aVz zKqnFyfQCWnFpz=;8{>WTPv9H!iZX@GAda0!Yyi4W@`k9zD({mwN7S`yr4@Dd zXWX?hfhkPmVLT!nK8_Q30#Cug(|86i;S65RxIBk>;qrOBg}3oe6E5OCEaDSV0xK>4T99CMf@^|^)vj4heG8rKnvBgniiDTw3%lLKEufADT@e&^)8zHg#$qVMqr;05&~ zxlT%_YlGohzr1jzs)r^#WRM&>-&r0{sc0-A4f(?FQ-fk-;lH^&*^TgLV64jFMpSTA*qYQv_UFa*+)4{i~iQfGf69%p-+LO!-#YLI$z3+z$MRNFDl$gMD&&+TG4 zN0&LM3~O!24Q-guhnuAa+EwZaNOCLBAIjA>sH&z~bN5LHq+UXFV^mKmDK`8d`$kNj z%+YDbLG3&DgIfTHVl0OpWpKfb3ao|?l?Y%ncA*iw(Tx2#f*$l@5OEkdh9pj53}2^44olM zEEK(=I)Rx)3KlU?&k%VKw{8=dUBpwk$mI=;ISahr#wONhmfm*6B5jD^FuK|KeTZTR zF(yF5IzNrmIKw13j|;enOSp_1xQSbs!n7sBeM^QJJi~Lm#9O?>dwjqre8wE@afaxb fwI^|b*0`NntovO^pOA4nRW|Tn5Vx~HHT(YnGKd@^ diff --git a/.gitignore b/.gitignore index c6967ae..cbec4df 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ test* *.old .venv/ +.DS_Store media/ *.mp4 *.mov diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1bb8819 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 5005 + +CMD ["python", "app.py"] diff --git a/app.py b/app.py index 1a61da7..2893fdc 100755 --- a/app.py +++ b/app.py @@ -1,7 +1,9 @@ import os import json -import socket import hashlib +# Import welcome page functions +import generate_welcome_page + from datetime import datetime from flask import ( @@ -16,6 +18,7 @@ from flask_login import ( ) from werkzeug.utils import secure_filename + # ------------------------------------------------- # Grundkonfiguration # ------------------------------------------------- @@ -101,6 +104,87 @@ def logout(): logout_user() return redirect("/login") +# ------------------------------------------------- +# Customer / Willkommensseite +# ------------------------------------------------- +@app.route("/customer", methods=["GET", "POST"]) +@login_required +def add_customer(): + """Add new customer with logo search and welcome page""" + error = None + success = None + logo_url = None + + if request.method == "POST": + customer_name = request.form.get("customer_name", "").strip() + + if not customer_name: + error = "Kundenname erforderlich" + else: + try: + # Search for logo + logo_url = generate_welcome_page.search_customer_logo(customer_name) + + if not logo_url: + error = "Logo konnte nicht gefunden werden" + else: + # Generate and save welcome page + html_filename = generate_welcome_page.save_welcome_page(customer_name, logo_url) + + if html_filename: + # Add to lobby playlist + if generate_welcome_page.add_customer_to_lobby_playlist(html_filename): + success = f"✅ Kunde '{customer_name}' erfolgreich hinzugefügt!" + else: + error = "Fehler beim Hinzufügen zur Playliste" + else: + error = "Fehler beim Speichern der Willkommensseite" + except ValueError as e: + error = f"Konfigurationsfehler: {str(e)}" + except Exception as e: + error = f"Fehler: {str(e)}" + + return render_template("customer.html", error=error, success=success, logo_url=logo_url) + +@app.route("/api/customer", methods=["POST"]) +@login_required +def api_add_customer(): + """API endpoint for customer creation""" + try: + data = request.get_json() + customer_name = data.get("customer_name", "").strip() + + if not customer_name: + return jsonify({"error": "Customer name required"}), 400 + + # Search for logo + logo_url = generate_welcome_page.search_customer_logo(customer_name) + + if not logo_url: + return jsonify({"error": "Logo not found"}), 400 + + # Generate and save welcome page + html_filename = generate_welcome_page.save_welcome_page(customer_name, logo_url) + + if not html_filename: + return jsonify({"error": "Failed to create welcome page"}), 500 + + # Add to lobby playlist + if generate_welcome_page.add_customer_to_lobby_playlist(html_filename): + return jsonify({ + "success": True, + "message": f"Customer '{customer_name}' added successfully", + "welcome_page": html_filename, + "logo_url": logo_url + }), 201 + else: + return jsonify({"error": "Failed to add to playlist"}), 500 + + except ValueError as e: + return jsonify({"error": str(e)}), 400 + except Exception as e: + return jsonify({"error": str(e)}), 500 + # ------------------------------------------------- # Medien ausliefern # ------------------------------------------------- @@ -136,6 +220,8 @@ def player(screen): return show_images if ext.endswith(".mp4"): return show_videos + if ext.endswith((".html", ".htm")): + return True return False # ------------------------------ @@ -266,7 +352,13 @@ def admin(): continue ext = os.path.splitext(item)[1].lower() - ftype = "video" if ext == ".mp4" else "image" + if ext == ".mp4": + ftype = "video" + if ext in (".jpg", ".jpeg", ".png"): + ftype = "image" + if ext == ".html": + ftype = "html" + size = os.path.getsize(file_path) // 1024 files.append({ diff --git a/config.json b/config.json index b4ff958..9648a9a 100755 --- a/config.json +++ b/config.json @@ -3,36 +3,49 @@ "enabled": true, "playlist": [ { - "url": "https://www.meteoblue.com/en/meteotv/d7b0fd", - "zoom": 1.0 + "url": "https://wbxroompresence.cancom.io/standort?find=Stuttgart", + "zoom": 0.8 } ] }, "screens": { "lobby": { - "interval": 15, + "interval": 10, "show_images": true, "show_videos": false, "playlist": [ - "Hilfe_KI.jpg", - "e00a687f-d82a-446d-b8f3-07895fbc7309.png", - "Video_CANCOM_LIVE_2025_Stuttgart.MP4", + "welcome.html", { - "url": "https://wbxroompresence.cancom.io/standort?find=Stuttgart", - "zoom": 0.8 - } + "url": "https://www.meteoblue.com/en/meteotv/d7b0fd", + "zoom": 1.0 + }, + "Cancom_Leitsatz.JPG" ], - "newsticker_text": "Herzlich willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste - wir w\u00fcnschen ihnen einen sch\u00f6nen Tag", + "newsticker_text": "Herzlich Willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste und w\u00fcnschen Ihnen einen angenehmen Tag! !", "newsticker_enabled": true }, "casino": { - "interval": 10, + "interval": 15, "show_images": true, "show_videos": true, "playlist": [ - "https://wbxroompresence.cancom.io/standort?find=Stuttgart" + "https://wbxroompresence.cancom.io/standort?find=Stuttgart", + { + "url": "https://www.meteoblue.com/en/meteotv/d7b0fd", + "zoom": 1.0 + }, + "Video_CANCOM_Jahresruckblick_2025.MP4", + "Video_CANCOM_LIVE_2025_Stuttgart.MP4" ], - "newsticker_text": "Hallo dies ist der Newsticker", + "newsticker_text": "Herzlich willkommen bei der CANCOM - wir w\u00fcnschen ihnen einen sch\u00f6nen Tag", + "newsticker_enabled": true + }, + "videosysteme": { + "interval": 20, + "show_images": false, + "show_videos": false, + "playlist": [], + "newsticker_text": "Hallo dies ist ein Test f\u00fcr Michael", "newsticker_enabled": false } }, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b68ed57 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.9" + +services: + signage: + container_name: signage + build: + context: . + dockerfile: Dockerfile + platforms: + - linux/amd64 + image: gitea.teamthiele.de/ethiele/signage:latest + ports: + - "5005:5005" + restart: unless-stopped + environment: + - OPENAI_API_KEY= "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" + - BRANDFETCH_API_KEY = "eBwCQW_DcQ_jvdqZRNph0JBtRA36XoMTufaaU4AoirOBnqXX9fMqHDw7uYfiz8kSFYMNKGMZtuHxrmud9hn0WQ" diff --git a/env.sh b/env.sh new file mode 100755 index 0000000..a6a8bb6 --- /dev/null +++ b/env.sh @@ -0,0 +1,2 @@ +export OPENAI_API_KEY="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" +export BRANDFETCH_API_KEY="eBwCQW_DcQ_jvdqZRNph0JBtRA36XoMTufaaU4AoirOBnqXX9fMqHDw7uYfiz8kSFYMNKGMZtuHxrmud9hn0WQ" \ No newline at end of file diff --git a/generate_welcome_page.py b/generate_welcome_page.py new file mode 100644 index 0000000..460a936 --- /dev/null +++ b/generate_welcome_page.py @@ -0,0 +1,241 @@ +import os +import json +from openai import OpenAI + +# ------------------------------------------------- +# Customer / Willkommensseite +# ------------------------------------------------- +WELCOME_DIR = os.path.join(os.path.dirname(__file__), "media", "lobby") + +os.makedirs(WELCOME_DIR, exist_ok=True) + +WELCOME_FILENAME = "welcome.html" + +def get_openai_client(): + """Initialize OpenAI client from environment variable""" + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + raise ValueError("OPENAI_API_KEY environment variable not set") + return OpenAI(api_key=api_key) + +def get_brandfetch_logo(domain): + """Try to fetch a logo URL from Brandfetch CDN.""" + print(f"🔍 Versuche Brandfetch für Domain: {domain}") + # Direct CDN URL construction - Brandfetch provides logos via cdn.brandfetch.io + logo_url = f"https://cdn.brandfetch.io/{domain}?c=1idyd4Tpb2nKaXIIc8T" + + # if validate_logo_url(logo_url): + print(f"✅ Brandfetch Logo gefunden: {logo_url}") + return logo_url + + # return None + + +def search_customer_logo(customer_name): + """Search for customer logo URL using OpenAI and web search""" + try: + client = get_openai_client() + + response = client.chat.completions.create( + model="gpt-4", + messages=[ + { + "role": "system", + "content": """Du bist ein Experte für die Suche nach Domain Namen. Du findest anhand des Firmennamens die richtige Domain dazu + Gib nur den Domain Namen zurück, keine weiteren Informationen. + FALLBACK: Wenn du kein direkt Aufrufbare Domain findest, antworte mit "FALLBACK".""" + }, + { + "role": "user", + "content": f"Finde die Domain für dasUnternehmen: {customer_name}" + } + ], + temperature=0.1, + max_tokens=100 + ) + + logo_url = response.choices[0].message.content.strip() + print (f"🔍 OpenAI Logo-URL: {logo_url}") + if logo_url.upper() == "FALLBACK": + logo_url = None + + if logo_url: + brandfetch_url = get_brandfetch_logo(logo_url) + if brandfetch_url: + return brandfetch_url + + return None + except Exception as e: + print(f"❌ Logo search error: {e}") + return None + + +def generate_welcome_html(customer_name, logo_url): + """Generate a welcome page HTML with full-screen design""" + # CANCOM SVG logo inline + cancom_svg = '''''' + + html_content = f""" + + + + + Willkommen bei CANCOM + + + + + + + + + +
+
+ +
meets
+ {f'' if logo_url else ''} +
+
+
+ Herzlich Willkommen
+ in unserer
+ Niederlassung Stuttgart! +
+
+
+ +""" + return html_content + + +def save_welcome_page(customer_name, logo_url): + """Save welcome page and return filename""" + try: + filepath = os.path.join(os.path.abspath(WELCOME_DIR), WELCOME_FILENAME) + html_content = generate_welcome_html(customer_name, logo_url) + + with open(filepath, "w", encoding="utf-8") as f: + f.write(html_content) + if os.path.getsize(filepath) == 0: + raise IOError("File not written") + print(f"✅ Willkommensseite generiert für: {customer_name}") + print(f"✅ Willkommensseite gespeichert in: {filepath}") + print(f"✅ Willkommensseite gespeichert: {WELCOME_FILENAME}") + return WELCOME_FILENAME + except Exception as e: + print(f"❌ Error saving welcome page: {e}") + return None + + +def add_customer_to_lobby_playlist(html_filename): + """Add customer welcome page to lobby playlist""" + try: + # Import here to avoid circular imports + from app import load_config, save_config + + config = load_config() + screens = config.setdefault("screens", {}) + lobby = screens.setdefault("lobby", {}) + playlist = lobby.setdefault("playlist", []) + + full_filename = f"{html_filename}" + if full_filename in playlist: + playlist.remove(full_filename) + playlist.insert(0, full_filename) + save_config(config) + print(f"✅ Willkommensseite in Lobby-Playliste platziert") + return True + except Exception as e: + print(f"❌ Error adding to playlist: {e}") + return False \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 24bb47b..80dc3bf 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ flask flask-login werkzeug +openai +requests +urllib3 diff --git a/templates/admin.html b/templates/admin.html index e1857bc..c7f10e1 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -136,6 +136,7 @@ @@ -211,7 +212,7 @@ {% if file.type == "image" %} Bild - {% elif file.type == "url" %} + {% elif file.type == "url" or file.type == "html" %} URL {% else %} Video @@ -367,7 +368,7 @@ {% if file.type == "image" %} Bild - {% elif file.type == "url" %} + {% elif file.type == "url" or file.type == "html" %} URL {% else %} Video diff --git a/templates/customer.html b/templates/customer.html new file mode 100644 index 0000000..86ded99 --- /dev/null +++ b/templates/customer.html @@ -0,0 +1,133 @@ + + + + + + Kunde hinzufügen + + + + + + + + +
+ + +
+
+
+
+
+
+
Willkommensseite
+

Kundeninformationen eingeben

+
+
+
+
+ {% if error %} +
+ {{ error }} +
+ {% endif %} + {% if success %} +
+ {{ success }} +
+ {% endif %} + + {% if logo_url %} +
+
+ Logo Vorschau +
Wenn das Logo erfolgreich gefunden wurde, wird es hier angezeigt.
+
+ Logo Vorschau +
+ {% endif %} + +
+
+ + +
+ +
+

Das System sucht automatisch nach dem Kundenlogo

+

und erstellt eine angepasste Willkommensseite für die Lobby-Playlist.

+
+ + +
+
+
+
+
+
+ + diff --git a/templates/player.html b/templates/player.html index 350ec14..1b43470 100755 --- a/templates/player.html +++ b/templates/player.html @@ -142,6 +142,10 @@ function isImage(item) { return item.kind === "file" && /\.(jpg|jpeg|png)$/i.test(item.name); } +function isHtml(item) { + return item.kind === "file" && /\.(html?|htm)$/i.test(item.name); +} + function getNextItem() { const normalizedNormal = normalFiles.map(normalizeItem); const normalizedPrio = prioFiles.map(normalizeItem); @@ -234,6 +238,13 @@ function playNext() { vid.src = src; vid.onended = playNext; vid.play(); + } else if (isHtml(item)) { + iframe.style.display = "block"; + iframe.src = src; + iframe.style.width = `100vw`; + iframe.style.height = `100vh`; + iframe.style.transform = `translate(-50%, -50%)`; + setTimeout(playNext, interval); } else { img.style.display = "block"; img.src = src;