From 0fa3c00319449c5b430c65feac6fe774dbab3b20 Mon Sep 17 00:00:00 2001 From: Erik Thiele Date: Mon, 27 Apr 2026 13:03:45 +0200 Subject: [PATCH] Version 3.6 mit URL Zoom Funktion Co-authored-by: Copilot --- .DS_Store | Bin 12292 -> 12292 bytes app.py | 161 ++++++++++++++++++++++++++++-------------- config.json | 16 +++-- templates/admin.html | 26 ++++--- templates/player.html | 28 +++++++- 5 files changed, 162 insertions(+), 69 deletions(-) diff --git a/.DS_Store b/.DS_Store index 5bdf022a4c77e88b7dec3cc2a72b4f25d380a81e..51e553f11809e3cd77d74c3c1b460846d21527eb 100644 GIT binary patch delta 1165 zcmcJNUu+ab9LMLoAe{{xnd`OW+G%@74`~Awumu%NO-n0(`X|Sq*4`DQ-F62UdflbB z+foEQ9vVUnDq#}{@I<0SjZqUMX!M~XC4ie&nkpv$2^J??~!rbja0?)D!v-ADU z{N^|F`+lYdrv|^@5vev_<-PCqB~yL<1B{KkVv|Ulo}3WvrYBtgQFsX-U~}BzjFV1p za9rt0$4({Xuw!qQ9|-?iWWnrybFxQFdIdceDX-M^Z|6j+YZok9wqn&gZEHIdITk7n zmz5ia-(0aIn->+8oF~VJ5=Ke?74Q81&hx{5K5w?k+hR8GDtFHUzSXfW8^gXhWtt{kl)C4 z@+Y}V9*}>?W6Z!Tlz<|HN>rg5HK;>97Ge?JLMv9I4GAQX!WL|WiCq}MC~Uacj}MW> z0UX3H*ZyN@isUX#}xvENDVbmC{y3R;pXIr~ECyyL`3Esb8{mm@-i`*d( z$$!fG*_!x)r2|mSf xoWRL~^JnlSzQWfyiyv?f=P`{7xQI*m9e>~|ZsE29;9rHPKQ=dZ_E63K&=YDjBpCnz delta 34 qcmZokXi3*5MjUWK*HVp#+ diff --git a/app.py b/app.py index 1691273..1a61da7 100755 --- a/app.py +++ b/app.py @@ -23,7 +23,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MEDIA_DIR = os.path.join(BASE_DIR, "media") CONFIG_FILE = os.path.join(BASE_DIR, "config.json") -APP_VERSION = "3.5.0" +APP_VERSION = "3.6.0" UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"} app = Flask(__name__) @@ -50,10 +50,21 @@ def save_config(cfg): def is_url(value): - return ( - isinstance(value, str) - and value.lower().startswith(("http://", "https://")) - ) + """Check if value is a URL (string or dict with 'url' key)""" + if isinstance(value, str): + return value.lower().startswith(("http://", "https://")) + if isinstance(value, dict) and "url" in value: + url = value.get("url", "") + return isinstance(url, str) and url.lower().startswith(("http://", "https://")) + return False + +def normalize_url(value): + """Convert URL string to dict with zoom factor, or return existing dict""" + if isinstance(value, dict) and "url" in value: + return value + if isinstance(value, str) and value.lower().startswith(("http://", "https://")): + return {"url": value, "zoom": 1.0} + return None # ------------------------------------------------- @@ -133,13 +144,15 @@ def player(screen): normal_files = [] # 1. Playlist-Reihenfolge - for name in playlist: - if is_url(name): - normal_files.append({"kind": "url", "url": name}) + for item in playlist: + if is_url(item): + url_data = normalize_url(item) + if url_data: + normal_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0)}) continue - if os.path.exists(os.path.join(folder, name)) and allowed_file(name): - normal_files.append({"kind": "file", "name": name}) + if os.path.exists(os.path.join(folder, item)) and allowed_file(item): + normal_files.append({"kind": "file", "name": item}) existing_file_names = { item["name"] for item in normal_files if item["kind"] == "file" @@ -160,14 +173,16 @@ def player(screen): if priority_cfg.get("enabled", False): prio_files = [] - for name in priority_cfg.get("playlist", []): - if is_url(name): - prio_files.append({"kind": "url", "url": name}) + for item in priority_cfg.get("playlist", []): + if is_url(item): + url_data = normalize_url(item) + if url_data: + prio_files.append({"kind": "url", "url": url_data["url"], "zoom": url_data.get("zoom", 1.0)}) continue - file_path = os.path.join(MEDIA_DIR, "priority", name) + file_path = os.path.join(MEDIA_DIR, "priority", item) if os.path.exists(file_path): - prio_files.append({"kind": "file", "name": name}) + prio_files.append({"kind": "file", "name": item}) else: prio_files = [] @@ -234,25 +249,28 @@ def admin(): screen_cfg["newsticker_enabled"] = screen_cfg.get("newsticker_enabled", False) # 1️⃣ Playlist-Reihenfolge - for name in playlist: - if is_url(name): - files.append({ - "name": name, - "type": "url", - "size": "URL" - }) + for item in playlist: + if is_url(item): + url_data = normalize_url(item) + if url_data: + files.append({ + "name": url_data["url"], + "type": "url", + "size": "URL", + "zoom": url_data.get("zoom", 1.0) + }) continue - file_path = os.path.join(path, name) + file_path = os.path.join(path, item) if not os.path.exists(file_path): continue - ext = os.path.splitext(name)[1].lower() + ext = os.path.splitext(item)[1].lower() ftype = "video" if ext == ".mp4" else "image" size = os.path.getsize(file_path) // 1024 files.append({ - "name": name, + "name": item, "type": ftype, "size": size }) @@ -286,25 +304,28 @@ def admin(): prio_playlist = cfg["priority"].get("playlist", []) # 1️⃣ Playlist-Reihenfolge - for name in prio_playlist: - if is_url(name): - priority_files.append({ - "name": name, - "type": "url", - "size": "URL" - }) + for item in prio_playlist: + if is_url(item): + url_data = normalize_url(item) + if url_data: + priority_files.append({ + "name": url_data["url"], + "type": "url", + "size": "URL", + "zoom": url_data.get("zoom", 1.0) + }) continue - file_path = os.path.join(prio_dir, name) + file_path = os.path.join(prio_dir, item) if not os.path.exists(file_path): continue - ext = os.path.splitext(name)[1].lower() + ext = os.path.splitext(item)[1].lower() ftype = "video" if ext == ".mp4" else "image" size = os.path.getsize(file_path) // 1024 priority_files.append({ - "name": name, + "name": item, "type": ftype, "size": size }) @@ -388,12 +409,24 @@ def upload(screen): @login_required def add_url(screen): url = request.form.get("url", "").strip() + zoom = request.form.get("zoom", "1.0").strip() + if not is_url(url): return redirect("/admin") + + try: + zoom_factor = float(zoom) + if zoom_factor <= 0: + zoom_factor = 1.0 + except (ValueError, TypeError): + zoom_factor = 1.0 + # Store as object with zoom factor + url_data = {"url": url, "zoom": zoom_factor} + config = load_config() if screen == "priority": - config.setdefault("priority", {}).setdefault("playlist", []).append(url) + config.setdefault("priority", {}).setdefault("playlist", []).append(url_data) else: screen_cfg = config.setdefault("screens", {}).setdefault(screen, { "interval": 10, @@ -401,7 +434,7 @@ def add_url(screen): "show_videos": True, "playlist": [] }) - screen_cfg.setdefault("playlist", []).append(url) + screen_cfg.setdefault("playlist", []).append(url_data) save_config(config) return redirect("/admin") @@ -424,12 +457,18 @@ def delete_file(screen, filename): # --- Playlist bereinigen --- if screen == "priority": playlist = cfg.get("priority", {}).get("playlist", []) - if filename in playlist: - playlist.remove(filename) + # Remove both string URLs and dict-based URLs + playlist[:] = [item for item in playlist if not ( + (isinstance(item, str) and item == filename) or + (isinstance(item, dict) and item.get("url") == filename) + )] else: playlist = cfg["screens"].get(screen, {}).get("playlist", []) - if filename in playlist: - playlist.remove(filename) + # Remove both string URLs and dict-based URLs + playlist[:] = [item for item in playlist if not ( + (isinstance(item, str) and item == filename) or + (isinstance(item, dict) and item.get("url") == filename) + )] save_config(cfg) return redirect("/admin") @@ -444,20 +483,36 @@ def delete_file(screen, filename): def save_playlist(screen): cfg = load_config() data = request.get_json() - + new_order = data["playlist"] # List of filenames/URLs as strings + + # Get the current playlist with all data (including zoom factors) if screen == "priority": - cfg.setdefault("priority", {}).setdefault("playlist", []) - cfg["priority"]["playlist"] = data["playlist"] + old_playlist = cfg.get("priority", {}).get("playlist", []) else: - screen_cfg = cfg.setdefault("screens", {}).setdefault(screen, { - "interval": 10, - "show_images": True, - "show_videos": True, - "playlist": [], - "newsticker_text": "", - "newsticker_enabled": False - }) - screen_cfg["playlist"] = data["playlist"] + old_playlist = cfg.get("screens", {}).get(screen, {}).get("playlist", []) + + # Create a mapping: filename/url -> full item (preserves zoom factor) + item_map = {} + for item in old_playlist: + if isinstance(item, dict) and "url" in item: + key = item["url"] # URL as key + else: + key = item # Filename as key + item_map[key] = item + + # Build new playlist: use the mapping to preserve zoom factors + new_playlist = [] + for key in new_order: + if key in item_map: + new_playlist.append(item_map[key]) + else: + # Item not found in mapping - add as is + new_playlist.append(key) + + if screen == "priority": + cfg.setdefault("priority", {})["playlist"] = new_playlist + else: + cfg.setdefault("screens", {}).setdefault(screen, {})["playlist"] = new_playlist save_config(cfg) return "", 204 diff --git a/config.json b/config.json index f4a5c2c..b4ff958 100755 --- a/config.json +++ b/config.json @@ -2,7 +2,10 @@ "priority": { "enabled": true, "playlist": [ - "https://www.meteoblue.com/en/meteotv/d7b0fd" + { + "url": "https://www.meteoblue.com/en/meteotv/d7b0fd", + "zoom": 1.0 + } ] }, "screens": { @@ -11,13 +14,16 @@ "show_images": true, "show_videos": false, "playlist": [ - "https://wbxroompresence.cancom.io/standort?find=Stuttgart", "Hilfe_KI.jpg", "e00a687f-d82a-446d-b8f3-07895fbc7309.png", - "Video_CANCOM_LIVE_2025_Stuttgart.MP4" + "Video_CANCOM_LIVE_2025_Stuttgart.MP4", + { + "url": "https://wbxroompresence.cancom.io/standort?find=Stuttgart", + "zoom": 0.8 + } ], - "newsticker_text": "Hallo dies ist der Newsticker", - "newsticker_enabled": false + "newsticker_text": "Herzlich willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste - wir w\u00fcnschen ihnen einen sch\u00f6nen Tag", + "newsticker_enabled": true }, "casino": { "interval": 10, diff --git a/templates/admin.html b/templates/admin.html index a8dfd21..e1857bc 100755 --- a/templates/admin.html +++ b/templates/admin.html @@ -177,11 +177,16 @@
-
+
+ -
+
+ + +
+ @@ -199,9 +204,9 @@ title="Vorschau" {% elif file.type == "url" %} data-bs-toggle="tooltip" - title="URL: {{ file.name }}" + title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}" {% endif %}> - {{ file.name }} + {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %} {% if file.type == "image" %} @@ -326,11 +331,16 @@
-
+
+ -
+
+ + +
+
@@ -350,9 +360,9 @@ title="Vorschau" {% elif file.type == "url" %} data-bs-toggle="tooltip" - title="URL: {{ file.name }}" + title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}" {% endif %}> - {{ file.name }} + {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} [{{ file.zoom }}x]{% endif %} {% if file.type == "image" %} diff --git a/templates/player.html b/templates/player.html index 31cf358..350ec14 100755 --- a/templates/player.html +++ b/templates/player.html @@ -22,8 +22,9 @@ background: black; display: none; position: fixed; - top: 0; - left: 0; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); z-index: 1; } @@ -122,10 +123,14 @@ function normalizeItem(item) { if (typeof item === "string") { const lower = item.toLowerCase(); if (lower.startsWith("http://") || lower.startsWith("https://")) { - return { kind: "url", url: item }; + return { kind: "url", url: item, zoom: 1.0 }; } return { kind: "file", name: item }; } + // Item is already an object (from backend) - ensure zoom is set + if (item.kind === "url" && !("zoom" in item)) { + item.zoom = 1.0; + } return item; } @@ -198,10 +203,27 @@ function playNext() { vid.pause(); vid.src = ""; iframe.src = ""; + iframe.style.transform = "scale(1)"; // Reset zoom if (item.kind === "url") { iframe.style.display = "block"; iframe.src = item.url; + // Apply zoom factor with browser-like behavior + // Zoom < 1.0: mehr Inhalt sichtbar (wie Browser herauszoomen) + // Zoom > 1.0: weniger Inhalt sichtbar (wie Browser reinzoomen) + const zoom = item.zoom || 1.0; + + if (zoom !== 1.0) { + // Adjust iframe size and scale to simulate real browser zoom + const scale = 1 / zoom; // Inverted for proper zoom behavior + iframe.style.width = `${100 * scale}vw`; + iframe.style.height = `${100 * scale}vh`; + iframe.style.transform = `translate(-50%, -50%) scale(${zoom})`; + } else { + iframe.style.width = `100vw`; + iframe.style.height = `100vh`; + iframe.style.transform = `translate(-50%, -50%)`; + } setTimeout(playNext, interval); return; }