Version 3.6 mit URL Zoom Funktion
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
147
app.py
147
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):
|
||||
for item in playlist:
|
||||
if is_url(item):
|
||||
url_data = normalize_url(item)
|
||||
if url_data:
|
||||
files.append({
|
||||
"name": name,
|
||||
"name": url_data["url"],
|
||||
"type": "url",
|
||||
"size": "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):
|
||||
for item in prio_playlist:
|
||||
if is_url(item):
|
||||
url_data = normalize_url(item)
|
||||
if url_data:
|
||||
priority_files.append({
|
||||
"name": name,
|
||||
"name": url_data["url"],
|
||||
"type": "url",
|
||||
"size": "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":
|
||||
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":
|
||||
cfg.setdefault("priority", {}).setdefault("playlist", [])
|
||||
cfg["priority"]["playlist"] = data["playlist"]
|
||||
cfg.setdefault("priority", {})["playlist"] = new_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"]
|
||||
cfg.setdefault("screens", {}).setdefault(screen, {})["playlist"] = new_playlist
|
||||
|
||||
save_config(cfg)
|
||||
return "", 204
|
||||
|
||||
16
config.json
16
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,
|
||||
|
||||
@@ -177,11 +177,16 @@
|
||||
</form>
|
||||
|
||||
<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"
|
||||
placeholder="https://example.com" required>
|
||||
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
|
||||
</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>
|
||||
|
||||
<!-- 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'>"
|
||||
{% 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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
|
||||
</span>
|
||||
|
||||
{% if file.type == "image" %}
|
||||
@@ -326,11 +331,16 @@
|
||||
</form>
|
||||
|
||||
<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"
|
||||
placeholder="https://example.com" required>
|
||||
<button class="btn btn-secondary" type="submit">URL hinzufügen</button>
|
||||
</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>
|
||||
|
||||
<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'>"
|
||||
{% 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 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}
|
||||
</span>
|
||||
|
||||
{% if file.type == "image" %}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user