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
{% for book in books %}
{% if book.has_cover %}
 }})
{% else %}

{% 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)