Nightly: deterministic plugin loading and docs refresh
This commit is contained in:
@@ -1230,11 +1230,16 @@ def _discover_plugins() -> dict[str, BasisPlugin]:
|
||||
except Exception as exc:
|
||||
xbmc.log(f"Plugin-Datei {file_path.name} konnte nicht geladen werden: {exc}", xbmc.LOGWARNING)
|
||||
continue
|
||||
preferred = getattr(module, "Plugin", None)
|
||||
if inspect.isclass(preferred) and issubclass(preferred, BasisPlugin) and preferred is not BasisPlugin:
|
||||
plugin_classes = [preferred]
|
||||
else:
|
||||
plugin_classes = [
|
||||
obj
|
||||
for obj in module.__dict__.values()
|
||||
if inspect.isclass(obj) and issubclass(obj, BasisPlugin) and obj is not BasisPlugin
|
||||
]
|
||||
plugin_classes.sort(key=lambda cls: cls.__name__.casefold())
|
||||
for cls in plugin_classes:
|
||||
try:
|
||||
instance = cls()
|
||||
@@ -1245,7 +1250,21 @@ def _discover_plugins() -> dict[str, BasisPlugin]:
|
||||
reason = getattr(instance, "unavailable_reason", "Nicht verfuegbar.")
|
||||
xbmc.log(f"Plugin {cls.__name__} deaktiviert: {reason}", xbmc.LOGWARNING)
|
||||
continue
|
||||
plugins[instance.name] = instance
|
||||
plugin_name = str(getattr(instance, "name", "") or "").strip()
|
||||
if not plugin_name:
|
||||
xbmc.log(
|
||||
f"Plugin {cls.__name__} wurde ohne Name registriert und wird uebersprungen.",
|
||||
xbmc.LOGWARNING,
|
||||
)
|
||||
continue
|
||||
if plugin_name in plugins:
|
||||
xbmc.log(
|
||||
f"Plugin-Name doppelt ({plugin_name}), {cls.__name__} wird uebersprungen.",
|
||||
xbmc.LOGWARNING,
|
||||
)
|
||||
continue
|
||||
plugins[plugin_name] = instance
|
||||
plugins = dict(sorted(plugins.items(), key=lambda item: item[0].casefold()))
|
||||
_PLUGIN_CACHE = plugins
|
||||
return plugins
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional, Set
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
|
||||
class BasisPlugin(ABC):
|
||||
@@ -12,6 +12,7 @@ class BasisPlugin(ABC):
|
||||
|
||||
name: str
|
||||
version: str = "0.0.0"
|
||||
prefer_source_metadata: bool = False
|
||||
|
||||
@abstractmethod
|
||||
async def search_titles(self, query: str) -> List[str]:
|
||||
@@ -29,6 +30,10 @@ class BasisPlugin(ABC):
|
||||
"""Optional: Liefert den Stream-Link fuer eine konkrete Folge."""
|
||||
return None
|
||||
|
||||
def metadata_for(self, title: str) -> Tuple[Dict[str, str], Dict[str, str], Optional[List[Any]]]:
|
||||
"""Optional: Liefert Info-Labels, Art und Cast fuer einen Titel."""
|
||||
return {}, {}, None
|
||||
|
||||
def resolve_stream_link(self, link: str) -> Optional[str]:
|
||||
"""Optional: Folgt einem Stream-Link und liefert die finale URL."""
|
||||
return None
|
||||
|
||||
@@ -10,7 +10,7 @@ Dieses Dokument beschreibt den Einstiegspunkt des Addons und die zentrale Steuer
|
||||
- startet die Wiedergabe und verwaltet Playstate/Resume.
|
||||
|
||||
## Ablauf (high level)
|
||||
1. **Plugin‑Discovery**: Lädt alle `addon/plugins/*.py` (ohne `_`‑Prefix) und instanziiert Klassen, die von `BasisPlugin` erben.
|
||||
1. **Plugin‑Discovery**: Lädt alle `addon/plugins/*.py` (ohne `_`‑Prefix). Bevorzugt `Plugin = <Klasse>`, sonst werden `BasisPlugin`‑Subklassen deterministisch instanziiert.
|
||||
2. **Navigation**: Baut Kodi‑Listen (Serien/Staffeln/Episoden) auf Basis der Plugin‑Antworten.
|
||||
3. **Playback**: Holt Stream‑Links aus dem Plugin und startet die Wiedergabe.
|
||||
4. **Playstate**: Speichert Resume‑Daten lokal (`playstate.json`) und setzt `playcount`/Resume‑Infos.
|
||||
@@ -35,6 +35,7 @@ Die genaue Aktion wird aus den Query‑Parametern gelesen und an das entsprechen
|
||||
- **Plugin‑Loader**: findet & instanziiert Plugins.
|
||||
- **UI‑Helper**: setzt Content‑Type, baut Verzeichnisseinträge.
|
||||
- **Playstate‑Helper**: `_load_playstate`, `_save_playstate`, `_apply_playstate_to_info`.
|
||||
- **Metadata‑Merge**: Plugin‑Metadaten können TMDB übersteuern, TMDB dient als Fallback.
|
||||
|
||||
## Fehlerbehandlung
|
||||
- Plugin‑Importfehler werden isoliert behandelt, damit das Addon nicht komplett ausfällt.
|
||||
|
||||
@@ -6,6 +6,7 @@ Diese Doku beschreibt, wie Plugins im ViewIT‑Addon aufgebaut sind und wie neue
|
||||
- Jedes Plugin ist eine einzelne Datei unter `addon/plugins/`.
|
||||
- Dateinamen **ohne** `_`‑Prefix werden automatisch geladen.
|
||||
- Jede Datei enthält eine Klasse, die von `BasisPlugin` erbt.
|
||||
- Optional: `Plugin = <Klasse>` als expliziter Einstiegspunkt (bevorzugt vom Loader).
|
||||
|
||||
## Pflicht‑Methoden (BasisPlugin)
|
||||
Jedes Plugin muss diese Methoden implementieren:
|
||||
@@ -22,6 +23,7 @@ Wesentliche Rückgaben an die Hauptlogik:
|
||||
- `episodes_for(...)` → Liste von Episoden-Labels
|
||||
- `stream_link_for(...)` → Hoster-/Player-Link (nicht zwingend finale Media-URL)
|
||||
- `resolve_stream_link(...)` → finale/spielbare URL nach Redirect/Resolver
|
||||
- `metadata_for(...)` → Info-Labels/Art (Plot/Poster) aus der Quelle
|
||||
- Optional `available_hosters_for(...)` → auswählbare Hoster-Namen im Dialog
|
||||
- Optional `series_url_for_title(...)` → stabile Detail-URL pro Titel für Folgeaufrufe
|
||||
- Optional `remember_series_url(...)` → Übernahme einer bereits bekannten Detail-URL
|
||||
@@ -35,6 +37,9 @@ Standard für Film-Provider (ohne echte Staffeln):
|
||||
- `popular_series` → `popular_series()`
|
||||
- `genres` → `genres()` + `titles_for_genre(genre)`
|
||||
- `latest_episodes` → `latest_episodes(page=1)`
|
||||
- `new_titles` → `new_titles_page(page=1)`
|
||||
- `alpha` → `alpha_index()` + `titles_for_alpha_page(letter, page)`
|
||||
- `series_catalog` → `series_catalog_page(page=1)`
|
||||
|
||||
## Empfohlene Struktur
|
||||
- Konstanten für URLs/Endpoints (BASE_URL, Pfade, Templates)
|
||||
@@ -60,6 +65,7 @@ Standard: `*_base_url` (Domain / BASE_URL)
|
||||
- `einschalten_base_url`
|
||||
- `topstream_base_url`
|
||||
- `filmpalast_base_url`
|
||||
- `doku_streams_base_url`
|
||||
|
||||
## Playback
|
||||
- `stream_link_for(...)` implementieren (liefert bevorzugten Hoster-Link).
|
||||
@@ -75,7 +81,8 @@ Standard: `*_base_url` (Domain / BASE_URL)
|
||||
2. **Navigation**: `series_url_for_title`/`remember_series_url` unterstützen, damit URLs zwischen Aufrufen stabil bleiben.
|
||||
3. **Auswahl Hoster**: Hoster-Namen aus der Detailseite extrahieren und anbieten.
|
||||
4. **Playback**: Hoster-Link liefern, danach konsistent über `resolve_stream_link` finalisieren.
|
||||
5. **Fallbacks**: bei Layout-Unterschieden defensiv parsen und Logging aktivierbar halten.
|
||||
5. **Metadaten**: `metadata_for` nutzen, Plot/Poster aus der Quelle zurückgeben.
|
||||
6. **Fallbacks**: bei Layout-Unterschieden defensiv parsen und Logging aktivierbar halten.
|
||||
|
||||
## Debugging
|
||||
Global gesteuert über Settings:
|
||||
|
||||
@@ -17,6 +17,7 @@ Weitere Details:
|
||||
- `einschalten_plugin.py` – Einschalten
|
||||
- `aniworld_plugin.py` – Aniworld
|
||||
- `filmpalast_plugin.py` – Filmpalast
|
||||
- `dokustreams_plugin.py` – Doku-Streams
|
||||
- `_template_plugin.py` – Vorlage für neue Plugins
|
||||
|
||||
### Plugin-Discovery (Ladeprozess)
|
||||
@@ -26,8 +27,9 @@ Der Loader in `addon/default.py`:
|
||||
1. Sucht alle `*.py` in `addon/plugins/`
|
||||
2. Überspringt Dateien, die mit `_` beginnen
|
||||
3. Lädt Module dynamisch
|
||||
4. Instanziert Klassen, die von `BasisPlugin` erben
|
||||
5. Ignoriert Plugins mit `is_available = False`
|
||||
4. Nutzt `Plugin = <Klasse>` als bevorzugten Einstiegspunkt (falls vorhanden)
|
||||
5. Fallback: instanziert Klassen, die von `BasisPlugin` erben (deterministisch sortiert)
|
||||
6. Ignoriert Plugins mit `is_available = False`
|
||||
|
||||
Damit bleiben fehlerhafte Plugins isoliert und blockieren nicht das gesamte Add-on.
|
||||
|
||||
@@ -38,6 +40,7 @@ Definiert in `addon/plugin_interface.py`:
|
||||
- `async search_titles(query: str) -> list[str]`
|
||||
- `seasons_for(title: str) -> list[str]`
|
||||
- `episodes_for(title: str, season: str) -> list[str]`
|
||||
- optional `metadata_for(title: str) -> (info_labels, art, cast)`
|
||||
|
||||
### Optionale Features (Capabilities)
|
||||
|
||||
@@ -47,10 +50,19 @@ Plugins können zusätzliche Features anbieten:
|
||||
- `popular_series`: liefert beliebte Serien
|
||||
- `genres`: Genre-Liste verfügbar
|
||||
- `latest_episodes`: neue Episoden verfügbar
|
||||
- `new_titles`: neue Titel verfügbar
|
||||
- `alpha`: A-Z Index verfügbar
|
||||
- `series_catalog`: Serienkatalog verfügbar
|
||||
- `popular_series() -> list[str]`
|
||||
- `genres() -> list[str]`
|
||||
- `titles_for_genre(genre: str) -> list[str]`
|
||||
- `latest_episodes(page: int = 1) -> list[LatestEpisode]` (wenn angeboten)
|
||||
- `new_titles_page(page: int = 1) -> list[str]` (wenn angeboten)
|
||||
- `alpha_index() -> list[str]` (wenn angeboten)
|
||||
- `series_catalog_page(page: int = 1) -> list[str]` (wenn angeboten)
|
||||
|
||||
Metadaten:
|
||||
- `prefer_source_metadata = True` bedeutet: Plugin-Metadaten gehen vor TMDB, TMDB dient nur als Fallback.
|
||||
|
||||
ViewIt zeigt im UI nur die Features an, die ein Plugin tatsächlich liefert.
|
||||
|
||||
@@ -62,6 +74,7 @@ Eine Integration sollte typischerweise bieten:
|
||||
- `search_titles()` mit Provider-Suche
|
||||
- `seasons_for()` und `episodes_for()` mit HTML-Parsing
|
||||
- `stream_link_for()` optional für direkte Playback-Links
|
||||
- `metadata_for()` optional für Plot/Poster aus der Quelle
|
||||
- Optional: `available_hosters_for()` oder Provider-spezifische Helfer
|
||||
|
||||
Als Startpunkt dient `addon/plugins/_template_plugin.py`.
|
||||
@@ -79,8 +92,9 @@ Als Startpunkt dient `addon/plugins/_template_plugin.py`.
|
||||
- Keine Netzwerkzugriffe im Import-Top-Level
|
||||
- Netzwerkzugriffe nur in Methoden (z. B. `search_titles`)
|
||||
- Fehler sauber abfangen und verständliche Fehlermeldungen liefern
|
||||
- Kein globaler Zustand, der across instances überrascht
|
||||
- Kein globaler Zustand, der über Instanzen hinweg überrascht
|
||||
- Provider-spezifische Parser in Helper-Funktionen kapseln
|
||||
- Reproduzierbare Reihenfolge: `Plugin`-Alias nutzen oder Klassenname eindeutig halten
|
||||
|
||||
### Debugging & Logs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user