🔴 Dringend
0
laufen bald ab
🎬 Kino-Highlights
0
📺 Deutsche TV-Filme
0
"""Save.TV Web-UI — Film-Archiv durchsuchen und downloaden. Läuft auf Port 8765 in CT 116. Erreichbar via Tailscale: http://100.123.47.7:8765 """ import os import re as _re import subprocess import sys import json import threading import urllib.request from datetime import datetime from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, '/opt') from flask import Flask, jsonify, render_template_string, request from tools import savetv app = Flask(__name__) DOWNLOAD_LOG = Path("/mnt/savetv/.download_log.json") # ── Archiv-Cache (Hintergrund-Refresh) ────────────────────────────────────── _ARCHIVE_CACHE_FILE = Path("/mnt/savetv/.archive_cache.json") _ARCHIVE_CACHE_TTL = 1800 # 30 min _archive_lock = threading.Lock() _archive_refreshing = False def _load_archive_cache(): """Aus Datei-Cache lesen (sofort, kein Netzwerk).""" try: if _ARCHIVE_CACHE_FILE.exists(): data = json.loads(_ARCHIVE_CACHE_FILE.read_text()) return data.get("entries", []), float(data.get("ts", 0)) except Exception: pass return [], 0.0 def _save_archive_cache(entries): try: _ARCHIVE_CACHE_FILE.write_text( json.dumps({"ts": datetime.now().timestamp(), "entries": entries}, ensure_ascii=False)) except Exception: pass def _refresh_archive_bg(): """Save.TV-Archiv im Hintergrund aktualisieren.""" global _archive_refreshing with _archive_lock: if _archive_refreshing: return _archive_refreshing = True try: entries = savetv._get_full_archive() if entries: _save_archive_cache(entries) except Exception: pass finally: _archive_refreshing = False def _get_archive_cached(): """Sofort aus Cache zurückgeben, Hintergrund-Refresh anstoßen wenn nötig.""" entries, ts = _load_archive_cache() age = datetime.now().timestamp() - ts if age > _ARCHIVE_CACHE_TTL or not entries: t = threading.Thread(target=_refresh_archive_bg, daemon=True) t.start() return entries def _load_download_log(): if DOWNLOAD_LOG.exists(): try: return json.loads(DOWNLOAD_LOG.read_text()) except Exception: pass return {} def _save_download_log(log): try: DOWNLOAD_LOG.write_text(json.dumps(log, ensure_ascii=False, indent=2)) except Exception: pass DOWNLOAD_PROGRESS = Path("/mnt/savetv/.download_progress.json") DOWNLOAD_DIR = Path("/mnt/savetv") _PROGRESS_LOCK = threading.Lock() def _load_progress_raw(): """Liest Progress-Datei ohne Lock (nur innerhalb von _PROGRESS_LOCK aufrufen).""" if DOWNLOAD_PROGRESS.exists(): try: return json.loads(DOWNLOAD_PROGRESS.read_text()) except Exception: pass return {} def _save_progress_raw(prog): """Schreibt Progress-Datei ohne Lock (nur innerhalb von _PROGRESS_LOCK aufrufen).""" DOWNLOAD_PROGRESS.write_text(json.dumps(prog, ensure_ascii=False, indent=2)) def _load_progress(): with _PROGRESS_LOCK: return _load_progress_raw() def _save_progress(prog): with _PROGRESS_LOCK: try: _save_progress_raw(prog) except Exception: pass def _head_content_length(url): try: req = urllib.request.Request(url, method='HEAD') with urllib.request.urlopen(req, timeout=10) as resp: return int(resp.headers.get('Content-Length', 0)) except Exception: return 0 HTML = r"""