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: Architektur:
- EPG-Daten von Save.TV: TvProgrammFilm.cfm (3 Tage) + TvProgrammFilmHighlights.cfm (4 Wochen) - EPG-Daten von Save.TV: TvProgrammFilm.cfm (3 Tage) + TvProgrammFilmHighlights.cfm (4 Wochen)
- Nur TVCATEGORYID 1 (Spielfilm), Spam-Genres rausgefiltert - Nur TVCATEGORYID 1 (Spielfilm), Spam-Genres rausgefiltert
- Seen-Cache: Nur neue Filme werden gemeldet (nicht erneut bei jedem Scan) - Seen-Cache: Nur neue Filme werden gemeldet (nicht erneut bei jedem Scan)
- Aufnahmen per tcJWriteRecord.cfm - Aufnahmen per tcJWriteRecord.cfm
- Downloads per croGetDownloadUrl2.cfm -> /mnt/savetv/ (Bind-Mount auf 2.7 TB Platte)
""" """
import re import re
import json import json
import logging import logging
import subprocess
import requests import requests
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
@ -34,6 +36,10 @@ EPG_PAGES = [
SEEN_CACHE = Path("/tmp/savetv_seen_ids.json") SEEN_CACHE = Path("/tmp/savetv_seen_ids.json")
SEEN_MAX_AGE_DAYS = 30 SEEN_MAX_AGE_DAYS = 30
DOWNLOAD_DIR = Path("/mnt/savetv")
DOWNLOAD_FORMAT_HD = 6
DOWNLOAD_FORMAT_SD = 5
AUTO_RECORD_SCORE = 80 AUTO_RECORD_SCORE = 80
SUGGEST_SCORE = 60 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: SYSTEM_PROMPT_EXTRA = """TV / Save.TV Tools:
- get_savetv_tipps: Zeigt sehenswerte Spielfilme der naechsten Tage/Wochen - get_savetv_tipps: Zeigt sehenswerte Spielfilme der naechsten Tage/Wochen
- get_savetv_archive_filme: Bewertet alle fertigen Aufnahmen im Archiv nach Qualitaet - get_savetv_archive_filme: Bewertet alle fertigen Aufnahmen im Archiv nach Qualitaet
- savetv_record: Nimmt einen Film per TelecastId auf - 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 - get_savetv_status: Zeigt Archiv und geplante Aufnahmen
Wenn der User nach Archiv-Filmen/Bewertung fragt, nutze get_savetv_archive_filme. 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, WICHTIG bei Archiv-Bewertung: Das Tool liefert KINO-HIGHLIGHTS (echte Kinofilme, Klassiker,
preisgekroente Filme) getrennt von deutschem Fernsehprogramm. Praesentiere dem User die preisgekroente Filme) getrennt von deutschem Fernsehprogramm. Praesentiere dem User die
KINO-HIGHLIGHTS zuerst und erklaere kurz warum jeder Film sehenswert ist (Regisseur, Preise, 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. 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) 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): def _format_film(f, with_tid=True):
"""Formatiert einen Film als Text.""" """Formatiert einen Film als Text."""
title = f.get("STITLE", "?") title = f.get("STITLE", "?")
@ -669,9 +743,39 @@ def handle_savetv_record(telecast_id=0, **kw):
return "Save.TV: " + result + "\nSendung: " + title 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 = { HANDLERS = {
"get_savetv_status": handle_get_savetv_status, "get_savetv_status": handle_get_savetv_status,
"get_savetv_tipps": handle_get_savetv_tipps, "get_savetv_tipps": handle_get_savetv_tipps,
"get_savetv_archive_filme": handle_get_savetv_archive_filme, "get_savetv_archive_filme": handle_get_savetv_archive_filme,
"savetv_record": handle_savetv_record, "savetv_record": handle_savetv_record,
"savetv_download": handle_savetv_download,
} }