diff --git a/arakava-news/STATE.md b/arakava-news/STATE.md index 7a770a3ff..311d40ad3 100644 --- a/arakava-news/STATE.md +++ b/arakava-news/STATE.md @@ -1,5 +1,5 @@ # Arakava News — Live State -> Auto-generiert: 2026-04-11 14:30 +> Auto-generiert: 2026-04-11 14:45 ## Service Status | Service | CT | Status | diff --git a/homelab-ai-bot/savetv_auto_download.py b/homelab-ai-bot/savetv_auto_download.py new file mode 100755 index 000000000..c2eef7829 --- /dev/null +++ b/homelab-ai-bot/savetv_auto_download.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +"""SaveTV Auto-Download — Lädt fertige Aufnahmen automatisch herunter. + +Cronjob: */30 * * * * (alle 30 Minuten) +Prüft das Save.TV-Archiv auf fertige Aufnahmen die noch nicht auf Hetzner liegen +und startet den Download im Hintergrund. +""" + +import json +import logging +import os +import re +import sys +import time +from pathlib import Path + +sys.path.insert(0, os.path.dirname(__file__)) +sys.path.insert(0, "/opt") + +from tools import savetv, savetv_country_filter + +log = logging.getLogger("savetv_auto_download") +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + datefmt="%H:%M:%S", +) + +DOWNLOAD_DIR = Path("/mnt/savetv") +DOWNLOAD_LOG = DOWNLOAD_DIR / ".download_log.json" +MAX_PARALLEL = 3 +MIN_SCORE = 80 + + +def _load_download_log(): + if DOWNLOAD_LOG.exists(): + try: + return json.loads(DOWNLOAD_LOG.read_text()) + except Exception: + pass + return {} + + +def _save_download_log(dl_log): + try: + DOWNLOAD_LOG.write_text(json.dumps(dl_log, ensure_ascii=False, indent=2)) + except Exception: + pass + + +def _normalize(s): + return re.sub(r"\s+", " ", re.sub(r"[^\w\s]", " ", s)).strip().lower() + + +def _already_stored(title): + norm = _normalize(title) + for fp in DOWNLOAD_DIR.iterdir(): + if fp.suffix == ".mp4" and fp.stat().st_size > 1_000_000: + raw = fp.stem.rsplit(" (", 1)[0] + if _normalize(raw) == norm: + return True + return False + + +def run(): + log.info("Starte Auto-Download Check...") + + entries = savetv._get_full_archive() + if not entries: + log.info("Kein Archiv-Zugriff oder leeres Archiv") + return + + dl_log = _load_download_log() + filminfo_cache = savetv_country_filter.load_filminfo_cache() + started = 0 + + for e in entries: + tc = e.get("STRTELECASTENTRY", {}) + tid = str(int(float(tc.get("ITELECASTID", 0)))) + title = tc.get("STITLE", "") + + if not title or not tid or tid == "0": + continue + + if tc.get("SFOLGE", ""): + continue + + if savetv._is_excluded(title): + continue + + if savetv_country_filter.should_exclude_production_country(title, filminfo_cache): + continue + + if tid in dl_log: + continue + + if _already_stored(title): + dl_log[tid] = "done" + continue + + if started >= MAX_PARALLEL: + break + + log.info(f"Starte Download: {title} (TID {tid})") + filename, err = savetv._download_film(int(float(tid)), title) + if err: + log.warning(f"Download-Fehler {title}: {err}") + continue + + dl_log[tid] = "done" + started += 1 + time.sleep(2) + + _save_download_log(dl_log) + log.info(f"Fertig: {started} neue Downloads gestartet") + + +if __name__ == "__main__": + run() diff --git a/infrastructure/STATE.md b/infrastructure/STATE.md index 6919eb839..5d556f84b 100644 --- a/infrastructure/STATE.md +++ b/infrastructure/STATE.md @@ -1,5 +1,5 @@ # Infrastruktur — Live State -> Auto-generiert: 2026-04-11 14:30 +> Auto-generiert: 2026-04-11 14:45 ## pve-hetzner Disk | Mount | Belegt | diff --git a/smart-home/STATE.md b/smart-home/STATE.md index 821d6f16d..f28745fce 100644 --- a/smart-home/STATE.md +++ b/smart-home/STATE.md @@ -1,5 +1,5 @@ # Smart Home Muldenstein — Live State -> Auto-generiert: 2026-04-11 14:30 +> Auto-generiert: 2026-04-11 14:45 ## Backup-Status - Letztes Backup: 666MB, 2026-04-11 04:01