Version 3.6 mit URL Zoom Funktion

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Erik Thiele
2026-04-27 13:03:45 +02:00
parent 4cd170c9df
commit 0fa3c00319
5 changed files with 162 additions and 69 deletions

BIN
.DS_Store vendored

Binary file not shown.

147
app.py
View File

@@ -23,7 +23,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MEDIA_DIR = os.path.join(BASE_DIR, "media") MEDIA_DIR = os.path.join(BASE_DIR, "media")
CONFIG_FILE = os.path.join(BASE_DIR, "config.json") 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"} UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
app = Flask(__name__) app = Flask(__name__)
@@ -50,10 +50,21 @@ def save_config(cfg):
def is_url(value): def is_url(value):
return ( """Check if value is a URL (string or dict with 'url' key)"""
isinstance(value, str) if isinstance(value, str):
and value.lower().startswith(("http://", "https://")) 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 = [] normal_files = []
# 1. Playlist-Reihenfolge # 1. Playlist-Reihenfolge
for name in playlist: for item in playlist:
if is_url(name): if is_url(item):
normal_files.append({"kind": "url", "url": name}) 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 continue
if os.path.exists(os.path.join(folder, name)) and allowed_file(name): if os.path.exists(os.path.join(folder, item)) and allowed_file(item):
normal_files.append({"kind": "file", "name": name}) normal_files.append({"kind": "file", "name": item})
existing_file_names = { existing_file_names = {
item["name"] for item in normal_files if item["kind"] == "file" item["name"] for item in normal_files if item["kind"] == "file"
@@ -160,14 +173,16 @@ def player(screen):
if priority_cfg.get("enabled", False): if priority_cfg.get("enabled", False):
prio_files = [] prio_files = []
for name in priority_cfg.get("playlist", []): for item in priority_cfg.get("playlist", []):
if is_url(name): if is_url(item):
prio_files.append({"kind": "url", "url": name}) 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 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): if os.path.exists(file_path):
prio_files.append({"kind": "file", "name": name}) prio_files.append({"kind": "file", "name": item})
else: else:
prio_files = [] prio_files = []
@@ -234,25 +249,28 @@ def admin():
screen_cfg["newsticker_enabled"] = screen_cfg.get("newsticker_enabled", False) screen_cfg["newsticker_enabled"] = screen_cfg.get("newsticker_enabled", False)
# 1⃣ Playlist-Reihenfolge # 1⃣ Playlist-Reihenfolge
for name in playlist: for item in playlist:
if is_url(name): if is_url(item):
url_data = normalize_url(item)
if url_data:
files.append({ files.append({
"name": name, "name": url_data["url"],
"type": "url", "type": "url",
"size": "URL" "size": "URL",
"zoom": url_data.get("zoom", 1.0)
}) })
continue continue
file_path = os.path.join(path, name) file_path = os.path.join(path, item)
if not os.path.exists(file_path): if not os.path.exists(file_path):
continue continue
ext = os.path.splitext(name)[1].lower() ext = os.path.splitext(item)[1].lower()
ftype = "video" if ext == ".mp4" else "image" ftype = "video" if ext == ".mp4" else "image"
size = os.path.getsize(file_path) // 1024 size = os.path.getsize(file_path) // 1024
files.append({ files.append({
"name": name, "name": item,
"type": ftype, "type": ftype,
"size": size "size": size
}) })
@@ -286,25 +304,28 @@ def admin():
prio_playlist = cfg["priority"].get("playlist", []) prio_playlist = cfg["priority"].get("playlist", [])
# 1⃣ Playlist-Reihenfolge # 1⃣ Playlist-Reihenfolge
for name in prio_playlist: for item in prio_playlist:
if is_url(name): if is_url(item):
url_data = normalize_url(item)
if url_data:
priority_files.append({ priority_files.append({
"name": name, "name": url_data["url"],
"type": "url", "type": "url",
"size": "URL" "size": "URL",
"zoom": url_data.get("zoom", 1.0)
}) })
continue continue
file_path = os.path.join(prio_dir, name) file_path = os.path.join(prio_dir, item)
if not os.path.exists(file_path): if not os.path.exists(file_path):
continue continue
ext = os.path.splitext(name)[1].lower() ext = os.path.splitext(item)[1].lower()
ftype = "video" if ext == ".mp4" else "image" ftype = "video" if ext == ".mp4" else "image"
size = os.path.getsize(file_path) // 1024 size = os.path.getsize(file_path) // 1024
priority_files.append({ priority_files.append({
"name": name, "name": item,
"type": ftype, "type": ftype,
"size": size "size": size
}) })
@@ -388,12 +409,24 @@ def upload(screen):
@login_required @login_required
def add_url(screen): def add_url(screen):
url = request.form.get("url", "").strip() url = request.form.get("url", "").strip()
zoom = request.form.get("zoom", "1.0").strip()
if not is_url(url): if not is_url(url):
return redirect("/admin") 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() config = load_config()
if screen == "priority": if screen == "priority":
config.setdefault("priority", {}).setdefault("playlist", []).append(url) config.setdefault("priority", {}).setdefault("playlist", []).append(url_data)
else: else:
screen_cfg = config.setdefault("screens", {}).setdefault(screen, { screen_cfg = config.setdefault("screens", {}).setdefault(screen, {
"interval": 10, "interval": 10,
@@ -401,7 +434,7 @@ def add_url(screen):
"show_videos": True, "show_videos": True,
"playlist": [] "playlist": []
}) })
screen_cfg.setdefault("playlist", []).append(url) screen_cfg.setdefault("playlist", []).append(url_data)
save_config(config) save_config(config)
return redirect("/admin") return redirect("/admin")
@@ -424,12 +457,18 @@ def delete_file(screen, filename):
# --- Playlist bereinigen --- # --- Playlist bereinigen ---
if screen == "priority": if screen == "priority":
playlist = cfg.get("priority", {}).get("playlist", []) playlist = cfg.get("priority", {}).get("playlist", [])
if filename in playlist: # Remove both string URLs and dict-based URLs
playlist.remove(filename) playlist[:] = [item for item in playlist if not (
(isinstance(item, str) and item == filename) or
(isinstance(item, dict) and item.get("url") == filename)
)]
else: else:
playlist = cfg["screens"].get(screen, {}).get("playlist", []) playlist = cfg["screens"].get(screen, {}).get("playlist", [])
if filename in playlist: # Remove both string URLs and dict-based URLs
playlist.remove(filename) 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) save_config(cfg)
return redirect("/admin") return redirect("/admin")
@@ -444,20 +483,36 @@ def delete_file(screen, filename):
def save_playlist(screen): def save_playlist(screen):
cfg = load_config() cfg = load_config()
data = request.get_json() 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":
old_playlist = cfg.get("priority", {}).get("playlist", [])
else:
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": if screen == "priority":
cfg.setdefault("priority", {}).setdefault("playlist", []) cfg.setdefault("priority", {})["playlist"] = new_playlist
cfg["priority"]["playlist"] = data["playlist"]
else: else:
screen_cfg = cfg.setdefault("screens", {}).setdefault(screen, { cfg.setdefault("screens", {}).setdefault(screen, {})["playlist"] = new_playlist
"interval": 10,
"show_images": True,
"show_videos": True,
"playlist": [],
"newsticker_text": "",
"newsticker_enabled": False
})
screen_cfg["playlist"] = data["playlist"]
save_config(cfg) save_config(cfg)
return "", 204 return "", 204

View File

@@ -2,7 +2,10 @@
"priority": { "priority": {
"enabled": true, "enabled": true,
"playlist": [ "playlist": [
"https://www.meteoblue.com/en/meteotv/d7b0fd" {
"url": "https://www.meteoblue.com/en/meteotv/d7b0fd",
"zoom": 1.0
}
] ]
}, },
"screens": { "screens": {
@@ -11,13 +14,16 @@
"show_images": true, "show_images": true,
"show_videos": false, "show_videos": false,
"playlist": [ "playlist": [
"https://wbxroompresence.cancom.io/standort?find=Stuttgart",
"Hilfe_KI.jpg", "Hilfe_KI.jpg",
"e00a687f-d82a-446d-b8f3-07895fbc7309.png", "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_text": "Herzlich willkommen bei der CANCOM - wir begr\u00fc\u00dfen unsere G\u00e4ste - wir w\u00fcnschen ihnen einen sch\u00f6nen Tag",
"newsticker_enabled": false "newsticker_enabled": true
}, },
"casino": { "casino": {
"interval": 10, "interval": 10,

View File

@@ -177,11 +177,16 @@
</form> </form>
<form action="/admin/add-url/priority" method="post" class="mb-3"> <form action="/admin/add-url/priority" method="post" class="mb-3">
<div class="input-group"> <div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control" <input type="url" name="url" class="form-control"
placeholder="https://example.com" required> placeholder="https://example.com" required>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</div> </div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</form> </form>
<!-- Playlist --> <!-- Playlist -->
@@ -199,9 +204,9 @@
title="<img src='/media/priority/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>" title="<img src='/media/priority/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %} {% elif file.type == "url" %}
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
title="URL: {{ file.name }}" title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}"
{% endif %}> {% endif %}>
{{ file.name }} {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span> </span>
{% if file.type == "image" %} {% if file.type == "image" %}
@@ -326,11 +331,16 @@
</form> </form>
<form action="/admin/add-url/{{ screen }}" method="post" class="mb-4"> <form action="/admin/add-url/{{ screen }}" method="post" class="mb-4">
<div class="input-group"> <div class="mb-2">
<label class="form-label">URL</label>
<input type="url" name="url" class="form-control" <input type="url" name="url" class="form-control"
placeholder="https://example.com" required> placeholder="https://example.com" required>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</div> </div>
<div class="mb-2">
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
</div>
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
</form> </form>
<hr> <hr>
@@ -350,9 +360,9 @@
title="<img src='/media/{{ screen }}/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>" title="<img src='/media/{{ screen }}/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
{% elif file.type == "url" %} {% elif file.type == "url" %}
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
title="URL: {{ file.name }}" title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}"
{% endif %}> {% endif %}>
{{ file.name }} {{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
</span> </span>
{% if file.type == "image" %} {% if file.type == "image" %}

View File

@@ -22,8 +22,9 @@
background: black; background: black;
display: none; display: none;
position: fixed; position: fixed;
top: 0; top: 50%;
left: 0; left: 50%;
transform: translate(-50%, -50%);
z-index: 1; z-index: 1;
} }
@@ -122,10 +123,14 @@ function normalizeItem(item) {
if (typeof item === "string") { if (typeof item === "string") {
const lower = item.toLowerCase(); const lower = item.toLowerCase();
if (lower.startsWith("http://") || lower.startsWith("https://")) { 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 }; 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; return item;
} }
@@ -198,10 +203,27 @@ function playNext() {
vid.pause(); vid.pause();
vid.src = ""; vid.src = "";
iframe.src = ""; iframe.src = "";
iframe.style.transform = "scale(1)"; // Reset zoom
if (item.kind === "url") { if (item.kind === "url") {
iframe.style.display = "block"; iframe.style.display = "block";
iframe.src = item.url; 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); setTimeout(playNext, interval);
return; return;
} }