nightly: fix movie search flow and add source metadata fallbacks

This commit is contained in:
2026-02-23 17:52:44 +01:00
parent d414fac022
commit d5a1125e03
4 changed files with 445 additions and 62 deletions

View File

@@ -1214,7 +1214,7 @@ def _show_plugin_search_results(plugin_name: str, query: str) -> None:
except Exception:
pass
raise
results = [str(t).strip() for t in (results or []) if t and str(t).strip()]
results = _clean_search_titles([str(t).strip() for t in (results or []) if t and str(t).strip()])
results.sort(key=lambda value: value.casefold())
use_source, show_tmdb, prefer_source = _metadata_policy(
@@ -1406,6 +1406,33 @@ def _series_url_params(plugin: BasisPlugin, title: str) -> dict[str, str]:
return {"series_url": series_url} if series_url else {}
def _clean_search_titles(values: list[str]) -> list[str]:
"""Filtert offensichtliche Platzhalter und dedupliziert Treffer."""
blocked = {
"stream",
"streams",
"film",
"movie",
"play",
"details",
"details/play",
}
cleaned: list[str] = []
seen: set[str] = set()
for raw in values:
title = (raw or "").strip()
if not title:
continue
key = title.casefold()
if key in blocked:
continue
if key in seen:
continue
seen.add(key)
cleaned.append(title)
return cleaned
def _show_search() -> None:
_log("Suche gestartet.")
dialog = xbmcgui.Dialog()
@@ -1453,7 +1480,7 @@ def _show_search_results(query: str) -> None:
pass
_log(f"Suche fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
continue
results = [str(t).strip() for t in (results or []) if t and str(t).strip()]
results = _clean_search_titles([str(t).strip() for t in (results or []) if t and str(t).strip()])
_log(f"Treffer ({plugin_name}): {len(results)}", xbmc.LOGDEBUG)
use_source, show_tmdb, prefer_source = _metadata_policy(
plugin_name, plugin, allow_tmdb=_tmdb_enabled()
@@ -1537,6 +1564,73 @@ def _show_search_results(query: str) -> None:
xbmcplugin.endOfDirectory(handle)
def _movie_seed_for_title(plugin: BasisPlugin, title: str, seasons: list[str]) -> tuple[str, str] | None:
"""Ermittelt ein Film-Seed (Season/Episode), um direkt Provider anzeigen zu können."""
if not seasons or len(seasons) != 1:
return None
season = str(seasons[0] or "").strip()
if not season:
return None
try:
episodes = [str(value or "").strip() for value in (plugin.episodes_for(title, season) or [])]
except Exception:
return None
episodes = [value for value in episodes if value]
if len(episodes) != 1:
return None
episode = episodes[0]
season_key = season.casefold()
episode_key = episode.casefold()
title_key = (title or "").strip().casefold()
generic_seasons = {"film", "movie", "stream"}
generic_episodes = {"stream", "film", "play", title_key}
if season_key in generic_seasons and episode_key in generic_episodes:
return (season, episode)
return None
def _show_movie_streams(
plugin_name: str,
title: str,
season: str,
episode: str,
*,
series_url: str = "",
) -> None:
handle = _get_handle()
plugin = _discover_plugins().get(plugin_name)
if plugin is None:
xbmcgui.Dialog().notification("Streams", "Quelle nicht gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle)
return
if series_url:
remember_series_url = getattr(plugin, "remember_series_url", None)
if callable(remember_series_url):
try:
remember_series_url(title, series_url)
except Exception:
pass
xbmcplugin.setPluginCategory(handle, f"{title} - Streams")
_set_content(handle, "videos")
base_params = {"plugin": plugin_name, "title": title, "season": season, "episode": episode}
if series_url:
base_params["series_url"] = series_url
# Hoster bleiben im Auswahldialog der Wiedergabe (wie bisher).
_add_directory_item(
handle,
title,
"play_episode",
dict(base_params),
is_folder=False,
info_labels={"title": title, "mediatype": "movie"},
)
xbmcplugin.endOfDirectory(handle)
def _show_seasons(plugin_name: str, title: str, series_url: str = "") -> None:
handle = _get_handle()
_log(f"Staffeln laden: {plugin_name} / {title}")
@@ -1553,60 +1647,6 @@ def _show_seasons(plugin_name: str, title: str, series_url: str = "") -> None:
except Exception:
pass
# Einschalten liefert Filme. Für Playback soll nach dem Öffnen des Titels direkt ein
# einzelnes abspielbares Item angezeigt werden: <Titel> -> (<Titel> abspielbar).
# Wichtig: ohne zusätzliche Netzwerkanfragen (sonst bleibt Kodi ggf. im Busy-Spinner hängen).
if (plugin_name or "").casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False):
xbmcplugin.setPluginCategory(handle, title)
_set_content(handle, "movies")
playstate = _title_playstate(plugin_name, title)
info_labels: dict[str, object] = {"title": title, "mediatype": "movie"}
info_labels = _apply_playstate_to_info(info_labels, playstate)
display_label = _label_with_playstate(title, playstate)
movie_params = {"plugin": plugin_name, "title": title}
if series_url:
movie_params["series_url"] = series_url
_add_directory_item(
handle,
display_label,
"play_movie",
movie_params,
is_folder=False,
info_labels=info_labels,
)
xbmcplugin.endOfDirectory(handle)
return
# Optional: Plugins können schnell (ohne Detail-Request) sagen, ob ein Titel ein Film ist.
# Dann zeigen wir direkt ein einzelnes abspielbares Item: <Titel> -> (<Titel>).
is_movie = getattr(plugin, "is_movie", None)
if callable(is_movie):
try:
if bool(is_movie(title)):
xbmcplugin.setPluginCategory(handle, title)
_set_content(handle, "movies")
playstate = _title_playstate(plugin_name, title)
info_labels: dict[str, object] = {"title": title, "mediatype": "movie"}
info_labels = _apply_playstate_to_info(info_labels, playstate)
display_label = _label_with_playstate(title, playstate)
movie_params = {"plugin": plugin_name, "title": title}
if series_url:
movie_params["series_url"] = series_url
else:
movie_params.update(_series_url_params(plugin, title))
_add_directory_item(
handle,
display_label,
"play_movie",
movie_params,
is_folder=False,
info_labels=info_labels,
)
xbmcplugin.endOfDirectory(handle)
return
except Exception:
pass
use_source, show_tmdb, _prefer_source = _metadata_policy(
plugin_name, plugin, allow_tmdb=_tmdb_enabled()
)
@@ -1636,6 +1676,26 @@ def _show_seasons(plugin_name: str, title: str, series_url: str = "") -> None:
xbmcplugin.endOfDirectory(handle)
return
movie_seed = _movie_seed_for_title(plugin, title, seasons)
if movie_seed is not None:
# Dieser Action-Pfad wurde als Verzeichnis aufgerufen. Ohne endOfDirectory()
# bleibt Kodi im Busy-Zustand, auch wenn wir direkt in die Wiedergabe springen.
try:
xbmcplugin.endOfDirectory(handle, succeeded=False)
except Exception:
try:
xbmcplugin.endOfDirectory(handle)
except Exception:
pass
_play_episode(
plugin_name,
title,
movie_seed[0],
movie_seed[1],
series_url=series_url,
)
return
count = len(seasons)
suffix = "Staffel" if count == 1 else "Staffeln"
xbmcplugin.setPluginCategory(handle, f"{title} ({count} {suffix})")
@@ -3210,12 +3270,29 @@ def _track_playback_and_update_state(key: str) -> None:
pass
def _track_playback_and_update_state_async(key: str) -> None:
"""Startet Playstate-Tracking im Hintergrund, damit die UI nicht blockiert."""
key = (key or "").strip()
if not key:
return
def _worker() -> None:
try:
_track_playback_and_update_state(key)
except Exception:
pass
worker = threading.Thread(target=_worker, name="viewit-playstate-tracker", daemon=True)
worker.start()
def _play_episode(
plugin_name: str,
title: str,
season: str,
episode: str,
*,
forced_hoster: str = "",
episode_url: str = "",
series_url: str = "",
resolve_handle: int | None = None,
@@ -3260,10 +3337,16 @@ def _play_episode(
_log(f"Hoster laden fehlgeschlagen ({plugin_name}): {exc}", xbmc.LOGWARNING)
selected_hoster: str | None = None
forced_hoster = (forced_hoster or "").strip()
if available_hosters:
if len(available_hosters) == 1:
if forced_hoster:
for hoster in available_hosters:
if hoster.casefold() == forced_hoster.casefold():
selected_hoster = hoster
break
if selected_hoster is None and len(available_hosters) == 1:
selected_hoster = available_hosters[0]
else:
elif selected_hoster is None:
selected_index = xbmcgui.Dialog().select("Hoster waehlen", available_hosters)
if selected_index is None or selected_index < 0:
_log("Play abgebrochen (kein Hoster gewählt).", xbmc.LOGDEBUG)
@@ -3308,7 +3391,7 @@ def _play_episode(
cast=cast,
resolve_handle=resolve_handle,
)
_track_playback_and_update_state(
_track_playback_and_update_state_async(
_playstate_key(plugin_name=plugin_name, title=title, season=season, episode=episode)
)
@@ -3396,7 +3479,7 @@ def _play_episode_url(
cast=cast,
resolve_handle=resolve_handle,
)
_track_playback_and_update_state(
_track_playback_and_update_state_async(
_playstate_key(plugin_name=plugin_name, title=title, season=season_label, episode=episode_label)
)
@@ -3496,6 +3579,7 @@ def run() -> None:
params.get("title", ""),
params.get("season", ""),
params.get("episode", ""),
forced_hoster=params.get("hoster", ""),
episode_url=params.get("url", ""),
series_url=params.get("series_url", ""),
resolve_handle=_get_handle(),