dev: gruppierte Suchergebnisse mit Quellenauswahl (Issue #2)

- _show_search_results() gruppiert Treffer über alle Plugins nach Titel
- Titel in einem Plugin: direkt zur Staffel-Ansicht (kein Plugin-Suffix)
- Titel in mehreren Plugins: Zwischenstufe 'Quelle wählen'
- Neue Funktion _show_choose_source() und Route 'choose_source'
This commit is contained in:
2026-03-01 18:49:18 +01:00
parent 7b60b00c8b
commit 1f0e627721

View File

@@ -1958,7 +1958,8 @@ def _show_search_results(query: str) -> None:
xbmcgui.Dialog().notification("Suche", "Keine Quellen gefunden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcgui.Dialog().notification("Suche", "Keine Quellen gefunden.", xbmcgui.NOTIFICATION_INFO, 3000)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
list_items: list[dict[str, object]] = [] # grouped: casefold-key → Liste der Plugin-Einträge für diesen Titel
grouped: dict[str, list[dict[str, object]]] = {}
canceled = False canceled = False
plugin_entries = list(plugins.items()) plugin_entries = list(plugins.items())
total_plugins = max(1, len(plugin_entries)) total_plugins = max(1, len(plugin_entries))
@@ -2029,31 +2030,61 @@ def _show_search_results(query: str) -> None:
merged_info = _apply_playstate_to_info(dict(info_labels), playstate) merged_info = _apply_playstate_to_info(dict(info_labels), playstate)
label = _label_with_duration(title, info_labels) label = _label_with_duration(title, info_labels)
label = _label_with_playstate(label, playstate) label = _label_with_playstate(label, playstate)
label = f"{label} [{plugin_name}]"
direct_play = bool( direct_play = bool(
plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False) plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False)
) )
extra_params = _series_url_params(plugin, title) extra_params = _series_url_params(plugin, title)
list_items.append( key = title.casefold()
{ grouped.setdefault(key, []).append({
"label": label, "title": title,
"action": "play_movie" if direct_play else "seasons", "plugin_name": plugin_name,
"params": {"plugin": plugin_name, "title": title, **extra_params}, "extra_params": extra_params,
"is_folder": (not direct_play), "label_base": label,
"direct_play": direct_play,
"info_labels": merged_info, "info_labels": merged_info,
"art": art, "art": art,
"cast": cast, "cast": cast,
} })
)
if canceled: if canceled:
break break
if not canceled: if not canceled:
progress(100, "Suche fertig") progress(100, "Suche fertig")
if canceled and not list_items: if canceled and not grouped:
xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500) xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500)
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
return return
# Gruppierte Einträge alphabetisch ausgeben
list_items: list[dict[str, object]] = []
for key in sorted(grouped):
entries = grouped[key]
first = entries[0]
canonical_title = str(first["title"])
if len(entries) == 1:
# Nur ein Plugin → direkt zur Staffel-Ansicht
direct_play = bool(first["direct_play"])
list_items.append({
"label": first["label_base"],
"action": "play_movie" if direct_play else "seasons",
"params": {"plugin": first["plugin_name"], "title": canonical_title, **dict(first["extra_params"])},
"is_folder": not direct_play,
"info_labels": first["info_labels"],
"art": first["art"],
"cast": first["cast"],
})
else:
# Mehrere Plugins → Zwischenstufe "Quelle wählen"
plugin_list = ",".join(str(e["plugin_name"]) for e in entries)
list_items.append({
"label": first["label_base"],
"action": "choose_source",
"params": {"title": canonical_title, "plugins": plugin_list},
"is_folder": True,
"info_labels": first["info_labels"],
"art": first["art"],
"cast": first["cast"],
})
for item in list_items: for item in list_items:
_add_directory_item( _add_directory_item(
handle, handle,
@@ -2068,6 +2099,31 @@ def _show_search_results(query: str) -> None:
xbmcplugin.endOfDirectory(handle) xbmcplugin.endOfDirectory(handle)
def _show_choose_source(title: str, plugins_str: str) -> None:
"""Zeigt Quellenauswahl wenn ein Titel in mehreren Plugins verfügbar ist."""
handle = _get_handle()
title = (title or "").strip()
plugin_names = [p.strip() for p in (plugins_str or "").split(",") if p.strip()]
all_plugins = _discover_plugins()
xbmcplugin.setPluginCategory(handle, title)
_set_content(handle, "tvshows")
for plugin_name in plugin_names:
plugin = all_plugins.get(plugin_name)
if not plugin:
continue
extra_params = _series_url_params(plugin, title)
_add_directory_item(
handle,
plugin_name,
"seasons",
{"plugin": plugin_name, "title": title, **extra_params},
is_folder=True,
)
xbmcplugin.endOfDirectory(handle)
def _movie_seed_for_title(plugin: BasisPlugin, title: str, seasons: list[str]) -> tuple[str, str] | None: 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.""" """Ermittelt ein Film-Seed (Season/Episode), um direkt Provider anzeigen zu können."""
if not seasons or len(seasons) != 1: if not seasons or len(seasons) != 1:
@@ -4728,6 +4784,11 @@ def _route_install_resolveurl(params: dict[str, str]) -> None:
_ensure_resolveurl_installed(force=True, silent=False) _ensure_resolveurl_installed(force=True, silent=False)
@_router.route("choose_source")
def _route_choose_source(params: dict[str, str]) -> None:
_show_choose_source(params.get("title", ""), params.get("plugins", ""))
@_router.route("seasons") @_router.route("seasons")
def _route_seasons(params: dict[str, str]) -> None: def _route_seasons(params: dict[str, str]) -> None:
_show_seasons(params.get("plugin", ""), params.get("title", ""), params.get("series_url", "")) _show_seasons(params.get("plugin", ""), params.get("title", ""), params.get("series_url", ""))