281 lines
7.8 KiB
Python
281 lines
7.8 KiB
Python
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) |