import os import json import io import zipfile from flask import Flask, render_template_string, request, session, redirect, url_for, send_from_directory, send_file, jsonify from flask_cors import CORS # Konfiguration PASSWORD = 'toniessindteuer' BASE_DIR = '/mnt/NAS/Toniebox/' app = Flask(__name__) app.config['SECRET_KEY'] = 'tonieflix-super-secret-key' CORS(app, supports_credentials=True, allow_headers="*") # --------------------------------------------------------- # Hilfsfunktionen # --------------------------------------------------------- def get_books(single_book=None): books = [] if os.path.exists(BASE_DIR): items_to_check = [single_book] if single_book else sorted(os.listdir(BASE_DIR)) for item in items_to_check: item_path = os.path.join(BASE_DIR, item) if os.path.isdir(item_path): files = os.listdir(item_path) tracks = sorted([f for f in files if f.lower().endswith(('.mp3', '.ogg'))]) has_cover = any(f.lower() == 'cover.jpg' for f in files) if tracks: books.append({ 'name': item, 'tracks': tracks, 'has_cover': has_cover }) return books # --------------------------------------------------------- # HTML & JS Template (Frontend) # --------------------------------------------------------- TEMPLATE = """ Tonieflix Abmelden

🎬 Tonieflix

{% if single_mode %} 📚 Gesamte Bibliothek laden {% else %} {% endif %}
{% for book in books %}
{% if book.has_cover %} Cover {% else %} Kein Cover {% endif %}
{{ book.name }}
{% else %}

Keine Hörbücher gefunden.

{% endfor %}
▶️

Tippe irgendwo auf den Bildschirm, um zu starten

""" LOGIN_TEMPLATE = """ Login - Tonieflix

🎬 Tonieflix

""" # --------------------------------------------------------- # Backend Logik & Routen # --------------------------------------------------------- @app.before_request def require_login(): if request.method == 'OPTIONS': return None if request.path.startswith('/api/'): return None if request.args.get('pw') == PASSWORD: return None if request.endpoint not in ['login', 'static'] and not session.get('logged_in'): return redirect(url_for('login', next=request.full_path if request.full_path != '/' else None)) @app.route('/login', methods=['GET', 'POST']) def login(): next_url = request.args.get('next') or url_for('index') if request.method == 'POST': if request.form.get('password') == PASSWORD: session['logged_in'] = True return redirect(next_url) else: return "Falsches Passwort!", 403 return render_template_string(LOGIN_TEMPLATE) @app.route('/logout') def logout(): session.clear() return redirect(url_for('login')) @app.route('/') def index(): return render_template_string(TEMPLATE, books=get_books(), auto_play="", single_mode=False) @app.route('/play/') def play_book(book_name): return render_template_string(TEMPLATE, books=get_books(single_book=book_name), auto_play=book_name, single_mode=True) @app.route('/cover/') def serve_cover(book_name): book_path = os.path.join(BASE_DIR, book_name) return send_from_directory(book_path, 'cover.jpg') @app.route('/stream//') def serve_audio(book_name, track_name): book_path = os.path.join(BASE_DIR, book_name) return send_from_directory(book_path, track_name) @app.route('/download/') def download_book(book_name): book_path = os.path.join(BASE_DIR, book_name) if not os.path.isdir(book_path): return "Hörbuch nicht gefunden", 404 memory_file = io.BytesIO() with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_STORED) as zf: for root, dirs, files in os.walk(book_path): for file in files: file_path = os.path.join(root, file) zf.write(file_path, arcname=file) memory_file.seek(0) return send_file( memory_file, download_name=f"{book_name}.zip", as_attachment=True, mimetype='application/zip' ) @app.route('/api/books') def api_books(): client_password = request.headers.get('X-Password') if client_password != PASSWORD: return jsonify({"error": "Falsches Passwort"}), 403 return jsonify(get_books()) if __name__ == '__main__': app.run(host='0.0.0.0', port=5005)