From ff11575d4c4545c3cc897e9fd479690f97c186bf Mon Sep 17 00:00:00 2001 From: root Date: Tue, 17 Mar 2026 15:49:24 +0700 Subject: [PATCH] Save.TV: Film-Download auf pve-hetzner Festplatte (HD, werbefrei, /mnt/savetv Bind-Mount) --- homelab-ai-bot/tools/savetv.py | 106 ++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/homelab-ai-bot/tools/savetv.py b/homelab-ai-bot/tools/savetv.py index c2158adf..86a70019 100644 --- a/homelab-ai-bot/tools/savetv.py +++ b/homelab-ai-bot/tools/savetv.py @@ -1,15 +1,17 @@ -"""Save.TV Online-Videorecorder — EPG Scanner + Film-Tipps + Aufnahme-Steuerung. +"""Save.TV Online-Videorecorder — EPG Scanner + Film-Tipps + Aufnahme + Download. Architektur: - EPG-Daten von Save.TV: TvProgrammFilm.cfm (3 Tage) + TvProgrammFilmHighlights.cfm (4 Wochen) - Nur TVCATEGORYID 1 (Spielfilm), Spam-Genres rausgefiltert - Seen-Cache: Nur neue Filme werden gemeldet (nicht erneut bei jedem Scan) - Aufnahmen per tcJWriteRecord.cfm +- Downloads per croGetDownloadUrl2.cfm -> /mnt/savetv/ (Bind-Mount auf 2.7 TB Platte) """ import re import json import logging +import subprocess import requests from datetime import datetime, timedelta from pathlib import Path @@ -34,6 +36,10 @@ EPG_PAGES = [ SEEN_CACHE = Path("/tmp/savetv_seen_ids.json") SEEN_MAX_AGE_DAYS = 30 +DOWNLOAD_DIR = Path("/mnt/savetv") +DOWNLOAD_FORMAT_HD = 6 +DOWNLOAD_FORMAT_SD = 5 + AUTO_RECORD_SCORE = 80 SUGGEST_SCORE = 60 @@ -94,18 +100,38 @@ TOOLS = [ }, }, }, + { + "type": "function", + "function": { + "name": "savetv_download", + "description": "Save.TV Film downloaden auf Hetzner-Festplatte (HD, werbefrei). " + "Nutze wenn User sagt 'download', 'runterladen', 'sichern', 'speichern'. " + "Filme landen auf pve-hetzner /var/lib/vz/savetv/ (2.7 TB frei).", + "parameters": { + "type": "object", + "properties": { + "telecast_id": {"type": "number", "description": "TelecastId der Sendung"}, + "title": {"type": "string", "description": "Filmtitel fuer den Dateinamen"}, + }, + "required": ["telecast_id"], + }, + }, + }, ] SYSTEM_PROMPT_EXTRA = """TV / Save.TV Tools: - get_savetv_tipps: Zeigt sehenswerte Spielfilme der naechsten Tage/Wochen - get_savetv_archive_filme: Bewertet alle fertigen Aufnahmen im Archiv nach Qualitaet - savetv_record: Nimmt einen Film per TelecastId auf +- savetv_download: Laedt fertigen Film auf pve-hetzner Festplatte (HD, werbefrei, 2.7 TB frei) - get_savetv_status: Zeigt Archiv und geplante Aufnahmen Wenn der User nach Archiv-Filmen/Bewertung fragt, nutze get_savetv_archive_filme. WICHTIG bei Archiv-Bewertung: Das Tool liefert KINO-HIGHLIGHTS (echte Kinofilme, Klassiker, preisgekroente Filme) getrennt von deutschem Fernsehprogramm. Praesentiere dem User die KINO-HIGHLIGHTS zuerst und erklaere kurz warum jeder Film sehenswert ist (Regisseur, Preise, Stars). Hebe DRINGEND ablaufende Kino-Highlights besonders hervor — die muss er schnell sichern. +Download: Wenn User einen Film sichern/downloaden will, nutze savetv_download mit der TelecastId. +Dateien landen auf pve-hetzner (/var/lib/vz/savetv/) und koennen spaeter manuell geholt werden. """ @@ -364,6 +390,54 @@ def _record_telecast(telecast_id): return "Fehler: " + str(e) +def _get_download_url(telecast_id, fmt=DOWNLOAD_FORMAT_HD, adfree=True): + """Holt die temporaere Download-URL fuer eine fertige Aufnahme.""" + s = _get_session() + if not s: + return None, "Login fehlgeschlagen" + try: + r = s.get( + SAVETV_URL + "/STV/M/obj/cRecordOrder/croGetDownloadUrl2.cfm", + params={ + "TelecastId": telecast_id, + "iFormat": fmt, + "bAdFree": str(adfree).lower(), + }, + headers={"X-Requested-With": "XMLHttpRequest"}, + timeout=15, + ) + data = r.json() + if data.get("SUCCESS"): + return data["DOWNLOADURL"], None + return None, data.get("ERROR", "Unbekannter Fehler") + except Exception as e: + return None, str(e) + + +def _download_film(telecast_id, title="film"): + """Startet HD-Download im Hintergrund nach /mnt/savetv/.""" + url, err = _get_download_url(telecast_id) + if err: + url, err = _get_download_url(telecast_id, fmt=DOWNLOAD_FORMAT_SD) + if err: + return None, f"Download-URL Fehler: {err}" + + DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True) + safe_title = re.sub(r'[^\w\-.]', '_', title)[:80] + filename = f"{safe_title}_{telecast_id}.mp4" + target = DOWNLOAD_DIR / filename + + try: + subprocess.Popen( + ["wget", "-q", "-O", str(target), url], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return filename, None + except Exception as e: + return None, f"wget Fehler: {e}" + + def _format_film(f, with_tid=True): """Formatiert einen Film als Text.""" title = f.get("STITLE", "?") @@ -669,9 +743,39 @@ def handle_savetv_record(telecast_id=0, **kw): return "Save.TV: " + result + "\nSendung: " + title +def handle_savetv_download(telecast_id=0, title="", **kw): + """Film von Save.TV auf pve-hetzner Festplatte downloaden.""" + if not telecast_id: + return "Keine TelecastId angegeben." + tid = int(telecast_id) + + if not title: + entries = _get_full_archive() + for e in entries: + tc = e.get("STRTELECASTENTRY", {}) + if int(tc.get("ITELECASTID", 0)) == tid: + title = tc.get("STITLE", f"film_{tid}") + break + if not title: + title = f"film_{tid}" + + filename, err = _download_film(tid, title) + if err: + return f"Download fehlgeschlagen: {err}" + + return ( + f"Download gestartet: {title}\n" + f"Datei: /var/lib/vz/savetv/{filename}\n" + f"Format: H.264 HD (werbefrei)\n" + f"Server: pve-hetzner (2.7 TB frei)\n" + f"Hinweis: Download laeuft im Hintergrund (1-3 GB, ca. 5-15 Min)." + ) + + HANDLERS = { "get_savetv_status": handle_get_savetv_status, "get_savetv_tipps": handle_get_savetv_tipps, "get_savetv_archive_filme": handle_get_savetv_archive_filme, "savetv_record": handle_savetv_record, + "savetv_download": handle_savetv_download, }