dev: bump to 0.1.71-dev – Trakt History direkt abspielen, Metadaten + Plugin-Bugfixes

- Trakt History: Episoden starten direkt (kein Staffel-Dialog mehr)
- Trakt History: Episodentitel, Plot und Artwork bereits in der Übersicht
- TraktItem um episode_title, episode_overview, episode_thumb, show_poster, show_fanart erweitert
- get_history() nutzt jetzt ?extended=full,images
- Slash-Commands /check und /deploy angelegt
- build_install_addon.sh deployt jetzt auch nach ~/.kodi/addons/
- filmpalast_plugin: return-Tuple-Bug gefixt (return "", "", "")
- dokustreams_plugin: Regex-Escaping für clean_name() korrigiert
- aniworld_plugin: raise_for_status() in resolve_redirect() ergänzt
- serienstream_plugin: Toter Code und unnötigen Regex-Backslash entfernt
This commit is contained in:
2026-03-01 22:56:51 +01:00
parent 95e14583e0
commit ff30548811
7 changed files with 298 additions and 19 deletions

View File

@@ -131,6 +131,7 @@ _MEDIA_TYPE_CACHE: dict[str, str] = {}
_TMDB_SEASON_CACHE: dict[tuple[int, int, str, str], dict[int, tuple[dict[str, str], dict[str, str]]]] = {}
_TMDB_SEASON_SUMMARY_CACHE: dict[tuple[int, int, str, str], tuple[dict[str, str], dict[str, str]]] = {}
_TMDB_EPISODE_CAST_CACHE: dict[tuple[int, int, int, str], list[TmdbCastMember]] = {}
_TRAKT_SEASON_META_CACHE: dict = {}
_TMDB_LOG_PATH: str | None = None
_GENRE_TITLES_CACHE: dict[tuple[str, str], list[str]] = {}
_ADDON_INSTANCE = None
@@ -987,7 +988,9 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
_tmdb_labels_and_art(title)
tmdb_id = _tmdb_cache_get(_TMDB_ID_CACHE, title_key)
if not tmdb_id:
return {"title": episode_label}, {}
return _trakt_episode_labels_and_art(
title=title, season_label=season_label, episode_label=episode_label
)
season_number = _extract_first_int(season_label)
episode_number = _extract_first_int(episode_label)
@@ -1003,7 +1006,9 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
if cached_season is None:
api_key = _get_setting_string("tmdb_api_key").strip()
if not api_key or api_key == "None":
return {"title": episode_label}, {}
return _trakt_episode_labels_and_art(
title=title, season_label=season_label, episode_label=episode_label
)
log_requests = _get_setting_bool("tmdb_log_requests", default=False)
log_responses = _get_setting_bool("tmdb_log_responses", default=False)
log_fn = _tmdb_file_log if (log_requests or log_responses) else None
@@ -1038,6 +1043,66 @@ def _tmdb_episode_labels_and_art(*, title: str, season_label: str, episode_label
return cached_season.get(episode_number, ({"title": episode_label}, {}))
def _trakt_episode_labels_and_art(
*, title: str, season_label: str, episode_label: str
) -> tuple[dict[str, str], dict[str, str]]:
"""Trakt-Fallback für Episoden-Metadaten wenn TMDB nicht verfügbar.
Lädt Staffel-Episodendaten per Batch (extended=full,images) und optionale
deutsche Übersetzung per Episode (Translations-Endpunkt).
"""
client = _trakt_get_client()
if not client:
return {"title": episode_label}, {}
season_number = _extract_first_int(season_label)
episode_number = _extract_first_int(episode_label)
if season_number is None or episode_number is None:
return {"title": episode_label}, {}
cache_key = (title.strip().casefold(), season_number)
cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, cache_key)
if cached is None:
slug = client.search_show(title)
if not slug:
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, cache_key, {})
return {"title": episode_label}, {}
meta = client.lookup_tv_season(slug, season_number)
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, cache_key, {"slug": slug, "episodes": meta or {}})
cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, cache_key)
slug = (cached or {}).get("slug", "")
episodes: dict = (cached or {}).get("episodes", {})
ep = episodes.get(episode_number)
if not ep:
return {"title": episode_label}, {}
ep_title = ep.title or episode_label
ep_overview = ep.overview
language = _get_setting_string("tmdb_language").strip() or "de-DE"
lang_code = language[:2]
if slug and lang_code and lang_code != "en":
trans_key = (cache_key, episode_number, lang_code)
trans_cached = _tmdb_cache_get(_TRAKT_SEASON_META_CACHE, trans_key)
if trans_cached is None:
t_title, t_overview = client.get_episode_translation(slug, season_number, episode_number, lang_code)
trans_cached = {"title": t_title, "overview": t_overview}
_tmdb_cache_set(_TRAKT_SEASON_META_CACHE, trans_key, trans_cached)
if trans_cached.get("title"):
ep_title = trans_cached["title"]
if trans_cached.get("overview"):
ep_overview = trans_cached["overview"]
info: dict[str, str] = {"title": ep_title}
if ep_overview:
info["plot"] = ep_overview
if ep.runtime_minutes:
info["duration"] = str(ep.runtime_minutes * 60)
art: dict[str, str] = {}
if ep.thumb:
art["thumb"] = ep.thumb
return info, art
def _tmdb_episode_cast(*, title: str, season_label: str, episode_label: str) -> list[TmdbCastMember]:
if not _tmdb_enabled():
return []
@@ -4526,12 +4591,29 @@ def _show_trakt_watchlist(media_type: str = "") -> None:
xbmcplugin.endOfDirectory(handle)
return
_set_content(handle, "tvshows")
items = client.get_watchlist(token, media_type=media_type)
for item in items:
label = f"{item.title}"
label = f"{item.title} ({item.year})" if item.year else item.title
tmdb_info, art, _ = _tmdb_labels_and_art(item.title)
info_labels: dict[str, object] = dict(tmdb_info)
info_labels["title"] = label
info_labels["tvshowtitle"] = item.title
if item.year:
label = f"{item.title} ({item.year})"
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True)
info_labels["year"] = item.year
info_labels["mediatype"] = "tvshow"
match = _trakt_find_in_plugins(item.title)
if match:
plugin_name, matched_title = match
action = "seasons"
params: dict[str, str] = {"plugin": plugin_name, "title": matched_title}
else:
action = "search"
params = {"query": item.title}
_add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art)
if not items:
xbmcgui.Dialog().notification("Trakt", "Watchlist ist leer.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle)
@@ -4546,14 +4628,66 @@ def _show_trakt_history(page: int = 1) -> None:
xbmcplugin.endOfDirectory(handle)
return
xbmcplugin.setPluginCategory(handle, "Trakt: Zuletzt gesehen")
_set_content(handle, "episodes")
items = client.get_history(token, page=page, limit=LIST_PAGE_SIZE)
for item in items:
label = item.title
if item.media_type == "episode" and item.season and item.episode:
label = f"{item.title} - S{item.season:02d}E{item.episode:02d}"
is_episode = item.media_type == "episode" and item.season and item.episode
# Label mit Episodentitel wenn vorhanden
if is_episode:
ep_title = item.episode_title or f"Episode {item.episode}"
label = f"{item.title} S{item.season:02d}E{item.episode:02d}: {ep_title}"
elif item.year:
label = f"{item.title} ({item.year})"
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True)
else:
label = item.title
# Artwork: Trakt-Bilder als Basis, TMDB ergänzt fehlende Keys
art: dict[str, str] = {}
if item.episode_thumb:
art["thumb"] = item.episode_thumb
if item.show_fanart:
art["fanart"] = item.show_fanart
if item.show_poster:
art["poster"] = item.show_poster
_, tmdb_art, _ = _tmdb_labels_and_art(item.title)
for _k, _v in tmdb_art.items():
art.setdefault(_k, _v)
# Info-Labels
info_labels: dict[str, object] = {}
info_labels["title"] = item.episode_title if is_episode and item.episode_title else label
info_labels["tvshowtitle"] = item.title
if item.year:
info_labels["year"] = item.year
if is_episode:
info_labels["season"] = item.season
info_labels["episode"] = item.episode
if item.episode_overview:
info_labels["plot"] = item.episode_overview
info_labels["mediatype"] = "episode" if is_episode else "tvshow"
# Navigation: Episoden direkt abspielen, Serien zur Staffelauswahl
match = _trakt_find_in_plugins(item.title)
if match:
plugin_name, matched_title = match
if is_episode:
action = "play_episode"
params: dict[str, str] = {
"plugin": plugin_name,
"title": matched_title,
"season": f"Staffel {item.season}",
"episode": f"Episode {item.episode}",
}
_add_directory_item(handle, label, action, params, is_folder=False, info_labels=info_labels, art=art)
else:
action = "seasons"
params = {"plugin": plugin_name, "title": matched_title}
_add_directory_item(handle, label, action, params, is_folder=True, info_labels=info_labels, art=art)
else:
_add_directory_item(handle, label, "search", {"query": item.title}, is_folder=True, info_labels=info_labels, art=art)
if len(items) >= LIST_PAGE_SIZE:
_add_directory_item(handle, "Naechste Seite >>", "trakt_history", {"page": str(page + 1)}, is_folder=True)
@@ -4613,8 +4747,20 @@ def _show_trakt_upcoming() -> None:
}
if item.show_year:
info_labels["year"] = item.show_year
if item.episode_overview:
info_labels["plot"] = item.episode_overview
_, art, _ = _tmdb_labels_and_art(item.show_title)
# Artwork: Trakt-Bilder als Basis, TMDB ergänzt fehlende Keys
art: dict[str, str] = {}
if item.episode_thumb:
art["thumb"] = item.episode_thumb
if item.show_fanart:
art["fanart"] = item.show_fanart
if item.show_poster:
art["poster"] = item.show_poster
_, tmdb_art, _ = _tmdb_labels_and_art(item.show_title)
for _k, _v in tmdb_art.items():
art.setdefault(_k, _v)
match = _trakt_find_in_plugins(item.show_title)
if match:
@@ -4736,7 +4882,11 @@ def _show_trakt_continue_watching() -> None:
@_router.route("search")
def _route_search(params: dict[str, str]) -> None:
_show_search()
query = params.get("query", "").strip()
if query:
_show_search_results(query)
else:
_show_search()
@_router.route("plugin_menu")