Save.TV: Film-Download auf pve-hetzner Festplatte (HD, werbefrei, /mnt/savetv Bind-Mount)

This commit is contained in:
root 2026-03-17 15:49:24 +07:00
parent 6c7872bb96
commit ff11575d4c

View file

@ -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,
}