From caebdf8eb91029a92c9f883af18e2e8377e9d5a9 Mon Sep 17 00:00:00 2001 From: Erik Thiele Date: Fri, 17 Apr 2026 14:06:24 +0200 Subject: [PATCH] Version 2.0.0 --- .DS_Store | Bin 0 -> 10244 bytes .gitignore | 10 ++ app.py | 281 ++++++++++++++++++++++++++++++++++++++++++ config.json | 32 +++++ requirements.txt | 3 + templates/admin.html | 265 +++++++++++++++++++++++++++++++++++++++ templates/login.html | 27 ++++ templates/player.html | 84 +++++++++++++ 8 files changed, 702 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 app.py create mode 100644 config.json create mode 100644 requirements.txt create mode 100644 templates/admin.html create mode 100644 templates/login.html create mode 100644 templates/player.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9ce562472bc2cdfb6743625a14839d2d60a7edec GIT binary patch literal 10244 zcmeHMZ)_Ar6rZ;Rx+^Sf`P&7K9vmoOfwq)FLEze6`D1|s{Y&|C?A@+(!`^PWy|zfL zRQaNUF%dP2Mn4%*6F-TfiT|Pm_@>aH$QNUb3GoAJq92S#-|Xxid)F%n1|nf6nR_#D z-ptOtd2i-7?+77K+pU%p5+sE1m?@P;BO5ElNWF=+(>x>-$moV#cKF~f=>|ASbUq1&mlvJ1c!j2Jnpuuv!*UAVKU)9P(X zDXDI|R!MHBwPwpQV(GFHYt^;hs5G(8GE<6fYDuSqu4*WxjqWuPrga!6hY9EcXc8I8 z{2n11QE@XA)s7o6oE#M#NfK#xM$j+#8t^sXYrxmQ-KGKe>fm$k`@2mI{(5~4_!`Kg z0eU_JF;g0L=>Vh7(Sbh{Leq8%p`C`LjDX}4-g8);M; zXX871ecUoPCpFsM7PpMN=x{uC|Kj`5^r7@I9y zrz37_PYu`9-eFtGRGl+!RFb-FZL5aE>OEQn2e;dG{5l#e~^_ZbGn@U0}f>J>k zoKQ4glBdekc`3xVcgOU7-FoU_XRH||vbaOVNo=%KI+`k-;0328R3gjUX-i#F!(Wx> z9rk^5WwF`Dxk`#FSh_$IoA+XHL9+2vbb{p-qTFIAG0l)vfpx1|D#~p=OyyF8%Um%@ zl-qH06Sx%H<*g3O@-F(>4T_y=?9xn}(i!*L5p3(QJC&r?*o(mpm%9$R&MqRKcB)tP zltmkzRhP({czzolutrri)tTSsEwqQEn>{)PQ*i58Ohr1!^L!{gmu*5Qy%(WR4OYz3 zlMo7*xe`1nA(dne*+gO(=r~B8Ajin-_WSgYXzU4u{|fJPXgk^Y8+^25-Qd@D{uc z@4`p$F?<4N;A^-D-@+yM0j|K$@C*FGjp0OYEH{NK=439!m2#`O8m^YB<09Nfu9b^& z2A5!S=iGaZY)`M&<@X@X^|LPKXWXe_{4B6;{kjJm#Eo~NpIscsJZtil;+fRls#n!! zER&gX+Iqb{;W$E;SvZd1cwUYX56V-gO%KcvgK787b=wy7AZ(yA8pWy$0*iR`R*7?}Vnv`*K%bNfSam^QnZQfHDl5d50(z%3f>mdTHG&`! zrxJ(+*6`?I(k(FkAITN+8~Fj3+M{p z!DaXr{&sO1$r$*i90M0}4Lo5*B5w4RPg;kT!`ag{LuJ(K%5-&J`@-sJN}7gc?!mFC z^9dOL)$rKpB8C$O)`;!B3-dJRxe@b7d6#o^^`R#l6Mn5E6uZc-qyPi%!re{VCE`} zVdrD|v~-SCfgXCy=Ml~1fEhWM4QB9sbis@q%nN4Zc?`_(lEMT_#f1#a(DRak z8F8@-W&}xf!Hl>p17-xNfiYKzlZS#Cxh5CP(6iPVH-c?>zzlMS2QzXo515g+<^wZ4 zMKiYs%m8u0%)|_s31z@c{SYu?X28rq2Fx7IfSKb%z|1$-1vBBB05jQM=0h3!_W4gx zMl8udnOvuJp$v7`G?Wo%&*9O3(oja6Hy^7FX($s|xCp)ZL>kHj7FQy6QPNN*u+Rf# z#0OR)cCph?MlA6_nLzy-9OlgJgffjTl-ZeqG8DZtDDy}L${fx>nPWqs%zHzi%(#q4$p>ltzkc}t|8H/") +def media(screen, filename): + path = os.path.join(MEDIA_DIR, screen) + return send_from_directory(path, filename) + +# ------------------------------------------------- +# Player +# ------------------------------------------------- +@app.route("/player/") +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//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/", 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/", 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//", 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/", 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) \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..4cbce4c --- /dev/null +++ b/config.json @@ -0,0 +1,32 @@ +{ + "screens": { + "lobby": { + "interval": 10, + "show_images": true, + "show_videos": true, + "playlist": [ + "QRCode.png" + ] + }, + "casino": { + "interval": 10, + "show_images": true, + "show_videos": true, + "playlist": [ + "HilfeKI.jpg", + "IMG_6730.jpeg", + "JET-Design.JPG" + ] + }, + "Erik": { + "interval": 10, + "show_images": true, + "show_videos": true, + "playlist": [] + } + }, + "admin": { + "username": "admin", + "password": "wooper-01" + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..24bb47b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +flask-login +werkzeug diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..c8c9c29 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,265 @@ + + + + +CANCOM Simple Signage Admin + + + + + + + + + + + + + + +
+ + + + + +
+
+ + {% for screen, cfg in screens.items() %} +
+ + +
+ Screen: {{ screen }} + {% if screen_status[screen] == "active" %} + Aktiv + {% else %} + Leer + {% endif %} +
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + +
Medien hochladen
+
+
+ + +
+
+ + +
Playlist Reihenfolge
+ +
    + {% for file in media_files[screen] %} +
  • + + {{ file.name }} + + {% if file.type == "image" %} + Bild + {% else %} + Video + {% endif %} + + {{ file.size }} KB + + +
    + +
    + + + +
  • + {% endfor %} +
+ + + +
+
+ {% endfor %} + +
+
+ + +
+
+ © {{ year }} CANCOM · Version {{ version }} · {{ hostname }} +
+
+ +
+ + + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..a5e7545 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,27 @@ + + + + +Signage Login + + + + +
+
+

Signage Admin

+
+
+ + +
+
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/templates/player.html b/templates/player.html new file mode 100644 index 0000000..20c8099 --- /dev/null +++ b/templates/player.html @@ -0,0 +1,84 @@ + + + + +CANCOM Simple Signage Player + + + + + + + + + + + + + +