Version 2.0.0

This commit is contained in:
Erik Thiele
2026-04-17 14:06:24 +02:00
parent bd99563892
commit caebdf8eb9
8 changed files with 702 additions and 0 deletions

281
app.py Normal file
View File

@@ -0,0 +1,281 @@
import os
import json
import socket
import hashlib
from datetime import datetime
from flask import (
Flask, render_template,
send_from_directory, redirect,
request, abort
)
from flask_login import (
LoginManager, login_user,
login_required, logout_user,
UserMixin
)
from werkzeug.utils import secure_filename
# -------------------------------------------------
# Grundkonfiguration
# -------------------------------------------------
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 = "2.0.0"
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
app = Flask(__name__)
app.secret_key = "CHANGE_THIS_SECRET"
login_manager = LoginManager(app)
login_manager.login_view = "login"
# -------------------------------------------------
# Config Helpers
# -------------------------------------------------
def load_config():
if not os.path.exists(CONFIG_FILE):
return {"admin": {}, "screens": {}}
with open(CONFIG_FILE) as f:
return json.load(f)
def save_config(cfg):
with open(CONFIG_FILE, "w") as f:
json.dump(cfg, f, indent=2)
f.flush()
os.fsync(f.fileno())
print("✅ config.json gespeichert")
# -------------------------------------------------
# Auth / Benutzer
# -------------------------------------------------
class Admin(UserMixin):
id = 1
@login_manager.user_loader
def load_user(user_id):
return Admin()
# -------------------------------------------------
# Login / Logout
# -------------------------------------------------
@app.route("/login", methods=["GET", "POST"])
def login():
config = load_config()
if request.method == "POST":
if (
request.form.get("username") == config["admin"].get("username")
and request.form.get("password") == config["admin"].get("password")
):
login_user(Admin())
return redirect("/admin")
return render_template("login.html")
@app.route("/logout")
def logout():
logout_user()
return redirect("/login")
# -------------------------------------------------
# Medien ausliefern
# -------------------------------------------------
@app.route("/media/<screen>/<path:filename>")
def media(screen, filename):
path = os.path.join(MEDIA_DIR, screen)
return send_from_directory(path, filename)
# -------------------------------------------------
# Player
# -------------------------------------------------
@app.route("/player/<screen>")
def player(screen):
config = load_config()
screens_cfg = config.setdefault("screens", {})
screen_cfg = screens_cfg.get(screen)
if not screen_cfg:
abort(404)
folder = os.path.join(MEDIA_DIR, screen)
playlist = screen_cfg.get("playlist", [])
show_images = screen_cfg.get("show_images", True)
show_videos = screen_cfg.get("show_videos", True)
def allowed_file(name):
if name.startswith("._"):
return False
ext = name.lower()
if ext.endswith((".jpg", ".jpeg", ".png")):
return show_images
if ext.endswith(".mp4"):
return show_videos
return False
ordered = []
# 1. Playlist-Reihenfolge
for name in playlist:
if os.path.exists(os.path.join(folder, name)) and allowed_file(name):
ordered.append(name)
# 2. Neue Dateien hintendran
for f in sorted(os.listdir(folder)):
if f not in ordered and allowed_file(f):
ordered.append(f)
return render_template(
"player.html",
screen=screen,
files=ordered,
interval=screen_cfg.get("interval", 10)
)
# -------------------------------------------------
# Hash für automatisches Player-Reload
# -------------------------------------------------
@app.route("/playlist/<screen>/hash")
def playlist_hash(screen):
screen_dir = os.path.join(MEDIA_DIR, screen)
if not os.path.isdir(screen_dir):
return ""
entries = []
for f in os.listdir(screen_dir):
if f.startswith("._"):
continue
p = os.path.join(screen_dir, f)
try:
entries.append(f"{f}:{int(os.path.getmtime(p))}")
except OSError:
pass
h = hashlib.md5("|".join(sorted(entries)).encode()).hexdigest()
return h
# -------------------------------------------------
# Admin Dashboard
# -------------------------------------------------
@app.route("/admin")
@login_required
def admin():
config = load_config()
screens_cfg = config.setdefault("screens", {})
screens = {}
media_files = {}
screen_status = {}
for screen in sorted(os.listdir(MEDIA_DIR)):
screen_dir = os.path.join(MEDIA_DIR, screen)
if not os.path.isdir(screen_dir):
continue
screens_cfg.setdefault(screen, {
"interval": 10,
"show_images": True,
"show_videos": True,
"playlist": []
})
screens[screen] = screens_cfg[screen]
files = []
for f in sorted(os.listdir(screen_dir)):
if f.startswith("._"):
continue
ext = os.path.splitext(f)[1].lower()
ftype = "video" if ext == ".mp4" else "image"
size = os.path.getsize(os.path.join(screen_dir, f)) // 1024
files.append({
"name": f,
"type": ftype,
"size": size
})
media_files[screen] = files
screen_status[screen] = "active" if files else "empty"
save_config(config)
return render_template(
"admin.html",
screens=screens,
media_files=media_files,
screen_status=screen_status,
hostname=socket.gethostname(),
version=APP_VERSION,
year=datetime.now().year
)
# -------------------------------------------------
# Admin: Screen-Einstellungen speichern
# -------------------------------------------------
@app.route("/admin/update/<screen>", methods=["POST"])
@login_required
def update_screen(screen):
config = load_config()
cfg = config["screens"][screen]
cfg["interval"] = int(request.form.get("interval", 10))
cfg["show_images"] = "show_images" in request.form
cfg["show_videos"] = "show_videos" in request.form
save_config(config)
return redirect("/admin")
# -------------------------------------------------
# Admin: Upload
# -------------------------------------------------
@app.route("/admin/upload/<screen>", methods=["POST"])
@login_required
def upload(screen):
file = request.files.get("file")
if not file:
return redirect("/admin")
ext = os.path.splitext(file.filename)[1].lower()
if ext not in UPLOAD_EXTENSIONS:
return "Ungültiger Dateityp", 400
filename = secure_filename(file.filename)
file.save(os.path.join(MEDIA_DIR, screen, filename))
return redirect("/admin")
# -------------------------------------------------
# Admin: Datei löschen
# -------------------------------------------------
@app.route("/admin/delete/<screen>/<filename>", methods=["POST"])
@login_required
def delete_file(screen, filename):
path = os.path.join(MEDIA_DIR, screen, filename)
if os.path.exists(path):
os.remove(path)
return redirect("/admin")
# -------------------------------------------------
# Admin: Playlist speichern
# -------------------------------------------------
@app.route("/admin/playlist/<screen>", methods=["POST"])
@login_required
def update_playlist(screen):
config = load_config()
cfg = config["screens"][screen]
data = request.get_json()
cfg["playlist"] = data.get("playlist", [])
save_config(config)
return "", 204
# -------------------------------------------------
# Main
# -------------------------------------------------
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5005)