vielen Dank für Deine Arbeit, läuft top, vor allem mit den Magenta Daten.
Muss man für Waipu noch irgendwas einstellen, damit auch Sendungsbilder geladen werden?
Bei mir überhaupt keine da über Waipu (über Magenta schon).
vielen Dank für Deine Arbeit, läuft top, vor allem mit den Magenta Daten.
Muss man für Waipu noch irgendwas einstellen, damit auch Sendungsbilder geladen werden?
Bei mir überhaupt keine da über Waipu (über Magenta schon).
Mir ist beim Sichten des Codes auch noch etwas aufgefallen, bitte die epg.db löschen und den Code updaten, dann sollten die fehlenden Daten nach einem Neustart des Grabbers auftauchen.
[…]
Jetzt sind die Bilder da, super, vielen Dank!
Jetzt hab ich die Qual der Wahl, ob ich Magenta- oder Waipu-Daten besser finde
Ich freue mich sehr, dass easyepg zumindest noch basale Infos von TMS beziehen kann (danke!). Wenn man Sender nutzt, für die es keine anderen Daten gibt, ist dies wirklich sehr nützlich. Ich habe noch zwei Anregungen hierzu:
Für den Eigenbedarf habe ich mir gerade eine Python-Lösung mit Hilfe von Bing Copilot gestrickt, die nachträglich die generierte xmltv-Datei durch Daten von TMDB anreichert. Voraussetzung ist, dass die Sendung mindestens 85 Minuten ist (sonst eher kein Spielfilm) und entweder eine Beschreibung oder ein Rating (Stars in TVHeadend) fehlt.
Unten der Code. Vielleicht will hilft es auch anderen oder jemand will es als Grundlage für etwas besseres nutzen:
import xml.etree.ElementTree as ET
import requests
import time
import json
import os
import sys
TMDB_API_KEY = 'DEIN_TMDB_API_KEY_HIER'
TMDB_SEARCH_URL = 'https://api.themoviedb.org/3/search/movie'
TMDB_DETAILS_URL = 'https://api.themoviedb.org/3/movie/{}'
TMDB_RELEASE_URL = 'https://api.themoviedb.org/3/movie/{}/release_dates'
CACHE_FILE = 'tmdb_cache.json'
if len(sys.argv) != 3:
print("Usage: python xmltv-aufwerten.py <input.xml> <output.xml>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
cache = json.load(f)
else:
cache = {}
MAX_REQUESTS_PER_SECOND = 25
request_timestamps = []
def rate_limit():
now = time.time()
request_timestamps[:] = [t for t in request_timestamps if now - t < 1]
if len(request_timestamps) >= MAX_REQUESTS_PER_SECOND:
time.sleep(1 - (now - request_timestamps[0]))
request_timestamps.append(time.time())
def get_tmdb_info(title):
if title in cache:
return cache[title]
rate_limit()
params = {'api_key': TMDB_API_KEY, 'query': title, 'language': 'de'}
response = requests.get(TMDB_SEARCH_URL, params=params)
results = response.json().get('results')
if not results:
cache[title] = {}
return {}
movie_id = results[0]['id']
rate_limit()
details = requests.get(TMDB_DETAILS_URL.format(movie_id), params={'api_key': TMDB_API_KEY, 'language': 'de'}).json()
rate_limit()
release_data = requests.get(TMDB_RELEASE_URL.format(movie_id), params={'api_key': TMDB_API_KEY}).json()
fsk = ''
for entry in release_data.get('results', []):
if entry.get('iso_3166_1') == 'DE':
for rd in entry.get('release_dates', []):
if rd.get('certification'):
fsk = rd['certification']
break
if fsk:
break
genres = details.get('genres', [])
genre_names = [g['name'] for g in genres]
info = {
'overview': details.get('overview', ''),
'rating': details.get('vote_average', ''),
'poster': f"https://image.tmdb.org/t/p/w500{details.get('poster_path')}" if details.get('poster_path') else '',
'genres': genre_names,
'fsk': fsk,
'type': 'Movie' if details.get('release_date') else 'TV',
'season': details.get('season_number', ''),
'episode': details.get('episode_count', '')
}
cache[title] = info
return info
# Genre → Content Type Mapping
GENRE_TO_TYPE = {
'Dokumentation': 'Documentary',
'Animation': 'Children',
'Krimi': 'Crime',
'Thriller': 'Crime',
'Komödie': 'Comedy',
'Drama': 'Drama',
'Science Fiction': 'Sci-Fi',
'Fantasy': 'Sci-Fi',
'Abenteuer': 'Adventure',
'Horror': 'Horror',
'Musik': 'Music',
'Romantik': 'Romance'
}
def enrich_xmltv(xmltv_path, output_path):
tree = ET.parse(xmltv_path)
root = tree.getroot()
for programme in root.findall('programme'):
title_elem = programme.find('title')
if title_elem is None or not title_elem.text:
continue
title = title_elem.text.strip()
start = programme.get('start')
stop = programme.get('stop')
if not start or not stop or len(start) < 12 or len(stop) < 12:
continue
try:
start_time = time.strptime(start[:14], "%Y%m%d%H%M%S")
stop_time = time.strptime(stop[:14], "%Y%m%d%H%M%S")
duration_minutes = (time.mktime(stop_time) - time.mktime(start_time)) / 60
except Exception:
continue
if duration_minutes <= 85:
continue
desc_elem = programme.find('desc')
rating_elem = programme.find('star-rating')
needs_desc = desc_elem is None or not desc_elem.text or len(desc_elem.text.strip()) < 20
needs_rating = rating_elem is None
if not (needs_desc or needs_rating):
continue
info = get_tmdb_info(title)
if not info:
continue
# Beschreibung
if needs_desc and info['overview']:
if desc_elem is None:
desc_elem = ET.SubElement(programme, 'desc')
desc_elem.text = info['overview']
# Bewertung
if needs_rating and info['rating']:
percent = round(float(info['rating']) * 10)
star_elem = ET.SubElement(programme, 'star-rating')
star_elem.set('system', 'IMDb')
value_elem = ET.SubElement(star_elem, 'value')
value_elem.text = f"{percent}/100"
# Altersfreigabe
if info.get('fsk'):
fsk_elem = ET.SubElement(programme, 'rating')
fsk_elem.set('system', 'FSK')
value_elem = ET.SubElement(fsk_elem, 'value')
value_elem.text = info['fsk']
# Poster
if programme.find('icon') is None and info['poster']:
icon_elem = ET.SubElement(programme, 'icon')
icon_elem.set('src', info['poster'])
# Genre & Content Type
existing_categories = [c.text for c in programme.findall('category') if c.text]
for genre in info.get('genres', []):
if genre not in existing_categories:
cat_elem = ET.SubElement(programme, 'category')
cat_elem.text = genre
# Content Type Simulation
mapped_type = GENRE_TO_TYPE.get(genre)
if mapped_type and mapped_type not in existing_categories:
cat_elem = ET.SubElement(programme, 'category')
cat_elem.text = mapped_type
# Episode
if info.get('season') and info.get('episode'):
try:
season = int(info['season']) - 1
episode = int(info['episode']) - 1
ep_elem = ET.SubElement(programme, 'episode-num')
ep_elem.set('system', 'xmltv_ns')
ep_elem.text = f"{season}.{episode}.0"
except:
pass
tree.write(output_path, encoding='utf-8', xml_declaration=True)
with open(CACHE_FILE, 'w', encoding='utf-8') as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
enrich_xmltv(input_file, output_file)
Alles anzeigen
Habe dann Sender über die Web Option versucht hinzuzufügen, das geht extrem langsam ca. 15s für einen Sender.
Ja, das ist mir auch aufgefallen. Wenn man auf das + klickt, dauert es eine Weile bis angezeigt wird, das der Sender hinzugefügt wurde.
Bei mir war das bei Waipu.
Ich habe hier einen Raspberry Pi 3 mit 64bit als Testreferenz:
Linux raspberrypi 6.6.20+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.20-1+rpt1 (2024-03-07) aarch64 GNU/Linux
Ich kann die genannte Verzögerung nicht nachvollziehen, zumindest nicht, wenn es um das Hinzufügen eines einzelnen Kanals geht. Der Browser zeigt mir eine Zeit von 400-500ms an - vom Klick auf das "+"-Symbol bis zum Erscheinen der Notification, dass der Kanal hinzugefügt wurde. Ich bin hier weit von den 15s entfernt.
Nur das Hinzufügen mehrerer (aller) Kanäle habe ich soeben optimiert, dort betrug die Ladezeit abhängig von der Listengröße ggf. mehrere Sekunden.
Geil wäre dann noch das EPG von Zattoo irgendwann wenn Zeit ist, dann stell ich alles auf WEB um und gut ist. TMS Lizenz mindestens 1200 Euro im Jahr
dass man auch neue hinzufügen kann
hier haste nen backup was ich vor ne weile mal angelegt habe DACH mit sender infos, GB nur callid'S
dupes: 22148
new: 0
channels: 4509
Wegen des API-Keys, warum generiert euch euch im Dev Protal von Gracenote nicht einfach selbst einen? Funktioniert hervorragend.
Gerade die neusten Commits geladen: Status: File created successfully! Zwar "nur" rudimentäre Infos aber besser als nichts.
Danke easy4me für deine super Arbeit.
Naja, der selbst generierte Key funktioniert in Wirklichkeit gar nicht, daher gibt es nur die rudimentären Infos.
Hast Du mehr rudimentäre Infos als ich (siehe Bild = ist auf einen Kanal von zattoo eingestellt / EPG bekomme ich von Gracenote mittels easyepg-lite)
Das was Du da siehst ist ohne API-Key erstellt (bzw. es steht noch der invalide Key drin). Dank der Anpassung von easy4me funktioniert das.
Naja, der selbst generierte Key funktioniert in Wirklichkeit gar nicht, daher gibt es nur die rudimentären Infos.
Okay, dann hätte ich mir das ja sparen können.
Das was Du da siehst ist ohne API-Key erstellt (bzw. es steht noch der invalide Key drin). Dank der Anpassung von easy4me funktioniert das.
Dürfte identisch sein wie bei dir.
Edit: Ich habe es gerade mal mit den Magenta Daten versucht, die sehen wieder richtig gut aus.
Edit2: Ich habe gerade versucht neue Sender per TMS hinzuzufügen, also das die API Abfrage hochkommt, aber sowohl meiner als auch der von hier werden als invalid angezeigt.
Man kann momentan nur mit einem gültigen Key neue TMS-Kanäle hinzufügen und die erweiterten Daten von dort beziehen.
Ich kann ggf. eine Möglichkeit einbauen, die IDs ohne Key hinzuzufügen, man muss aber die IDs zu den Kanälen kennen. Eine Liste der Stations/Lineups wurde bereits hier als ZIP gepostet.
Und ggf. könnte man auch erweiterte TMS-Daten laden. Das Problem ist aber leider das Rate-Limit der alternativen Quelle, man könnte TMS ohne Key dann nur noch in Ausnahmefällen nutzen, da das Laden sehr lange dauern würde - dafür aber wieder mit erweiterten Daten. Aktuell geht das Laden recht zügig mit rudimentären Infos.
Danke dir für die Zusammenfassung.
Das Laden aktuell geht auf jeden Fall recht fix und solange man für die Mainstream Sender alles übers Web via Magenta bekommen kann und rudimentären für die bereits vorhandenen Sender, kann man damit erst mal leben denke ich.
War sowieso erstaunt, dass das ganze jetzt 3 Jahre so super lief.
Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!