Analysiert und dokumentiert: - plugin.video.xstream: Startup-Sequenz, URL-Routing, Site-Plugin-Konvention, Browsing- und Hoster-Auflösungsflow, globale Suche - script.module.xstreamscraper: automatische TMDB-basierte Stream-Suche - HTTP-Request-Handler: Caching, Cookie-Verwaltung, Anti-Bot-Mechanismen - Plugin-Handler, GUI-Schicht, Einstellungsreferenz, Abhängigkeiten Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
18 KiB
xStream – Technische Dokumentation
Version: plugin.video.xstream 2026.02.11 / script.module.xstreamscraper 2025.08.14
Erstellt: 2026-08-17
1. Übersicht
Das Projekt besteht aus zwei voneinander abhängigen Kodi-Addons:
| Addon | Zweck |
|---|---|
plugin.video.xstream |
Vollständiges Video-Plugin: Browser-GUI, Site-Plugins, Hoster-Auflösung, Download |
script.module.xstreamscraper |
Companion-Modul: automatische Suche und Stream-Auflösung via TMDB-ID, für externe Aufrufer |
Zusammenfassung der Funktion: xStream ist eine Kodi-Suchmaschine für Filme und Serien. Es lädt Inhalte von verschiedenen Streaming-Webseiten (sogenannte „Site-Plugins"), löst die gefundenen Hoster-Links über das resolveurl-Addon auf und spielt den Stream in Kodi ab. Das Scraper-Modul ergänzt dies um eine vollautomatische Suche, die auf TMDB-IDs basiert.
2. Komponentenstruktur
Xstream-Standalone/
├── plugin.video.xstream/
│ ├── addon.xml # Addon-Metadaten und Abhängigkeiten
│ ├── default.py # Einstiegspunkt (Kodi ruft dies auf)
│ ├── service.py # Kodi-Service (läuft beim Kodi-Start)
│ ├── xstream.py # URL-Router und Hauptlogik
│ ├── sites/ # Site-Plugins (eine Datei pro Webseite)
│ │ ├── kinox.py
│ │ ├── aniworld.py
│ │ ├── burningseries.py
│ │ └── ...
│ └── resources/
│ └── lib/
│ ├── config.py # Addon-Einstellungen (Wrapper um xbmcaddon)
│ ├── tools.py # Logger, Parser, Cache-Hilfsfunktionen
│ ├── gui/
│ │ ├── gui.py # Kodi-GUI-Abstraktion (Listeneinträge, Ansichten)
│ │ ├── guiElement.py # Einzelner Listeneintrag (Titel, Thumbnail, Metadaten)
│ │ ├── hoster.py # Hoster-GUI: Abspielen, Download, Weiterleiten
│ │ └── contextElement.py# Kontextmenü-Einträge
│ └── handler/
│ ├── pluginHandler.py # Erkennung und Verwaltung der Site-Plugins
│ ├── requestHandler.py# HTTP-Client mit Cache, Cookies, Anti-Bot
│ ├── ParameterHandler.py # URL-Parameter lesen/schreiben
│ ├── jdownloaderHandler.py
│ ├── jdownloader2Handler.py
│ ├── myjdownloaderHandler.py
│ └── pyLoadHandler.py
└── script.module.xstreamscraper/
├── addon.xml
├── main.py # Einstiegspunkt des Scraper-Moduls
├── service.py # Service des Scraper-Moduls
└── resources/lib/
├── scraper.py # Kernlogik: Suche, Hoster-Auflösung, Wiedergabe
├── settings.py # Globaler Zustand (collectMode, aDirectory)
├── tools.py
├── gui/ # GUI-Klassen (Subset von plugin.video.xstream)
└── handler/ # Request- und Parameter-Handler (Subset)
3. Ablauf: plugin.video.xstream
3.1 Startup-Sequenz (Service)
service.py wird von Kodi beim Start als separater Service-Prozess ausgeführt.
Kodi-Start
└─► service.py: main()
1. cCache: setze '{addon_id}_main' = 'running'
2. Resolver-Update von GitHub prüfen/ausführen (updateManager.resolverUpdate)
3. Abhängigkeiten prüfen (checkDependence) – fehlende Addons installieren
4. Domain-Prüfung aller Site-Plugins parallel (cPluginHandler.checkDomain)
→ HTTP-Request an domain des jeweiligen Site-Plugins
→ Status 200/3xx: Domain aktiv, globale Suche aktiviert
→ Status 403–503: globale Suche deaktiviert, Link gesperrt
5. PluginDB aktualisieren (falls neue Einstellungen vorhanden)
6. cCache: setze '{addon_id}_main' = 'finished'
7. Changelog-Popup anzeigen (falls konfiguriert)
8. Alten HTML-Cache löschen (alle N Tage, konfigurierbar)
showMainMenu() in xstream.py wartet aktiv (max. 60 Sekunden / 0,5-Sekunden-Schleifen) auf das Signal 'finished', bevor das Hauptmenü aufgebaut wird.
3.2 Browsing-Flow (Plugin-Aufruf)
Kodi ruft default.py mit einer Plugin-URL der Form
plugin://plugin.video.xstream/?site=kinox&function=showMovieMenu&... auf.
default.py: main()
└─► Systempfade einrichten (sites/, lib/, art/sites/)
└─► xstream.py: parseUrl()
├── ParameterHandler: URL-Parameter lesen
├── Sonderfunktionen: clearCache, viewInfo, searchAlter, searchTMDB, ...
├── Kein 'site'-Parameter → showMainMenu()
│ └── alle aktivierten Site-Plugins als Listeneinträge hinzufügen
├── site = 'cHosterGui' → showHosterGui()
├── site = 'globalSearch' → searchGlobal()
├── site = 'xStream' → xStream-Einstellungen öffnen
├── site = 'resolver' → Resolver-Einstellungen öffnen
└── site = '<plugin-name>'
└── plugin = __import__('<plugin-name>')
└── function = getattr(plugin, sFunction)
└── function() ← Site-Plugin übernimmt
3.3 Site-Plugin-Konvention
Jede Datei in sites/ ist ein eigenständiges Python-Modul. Es muss folgende Modul-Variablen und Funktionen bereitstellen:
| Name | Pflicht | Beschreibung |
|---|---|---|
SITE_IDENTIFIER |
Ja | Eindeutiger Bezeichner (= Dateiname ohne .py) |
SITE_NAME |
Ja | Anzeigename im Menü |
SITE_ICON |
Nein | Dateiname des Icons in resources/art/sites/ |
DOMAIN |
Nein | Standarddomain; kann per Einstellung überschrieben werden |
SITE_GLOBAL_SEARCH |
Nein | True/False; steuert globale Suche |
load() |
Ja | Einstiegspunkt – baut Hauptmenü des Sites auf |
_search(oGui, sSearchText) |
Nein | Globale Suche; befüllt oGui mit Ergebnissen |
showHosters() |
Typisch | Gibt Hoster-Liste zurück (list of dicts + Funktionsname am Ende) |
getHosterUrl(sUrl) |
Typisch | Löst einen Hoster-Link zur Stream-URL auf |
Beispiel (kinox.py, vereinfacht):
SITE_IDENTIFIER = 'kinox'
SITE_NAME = 'KinoX'
DOMAIN = 'ww22.kinoz.to'
def load(): # Hauptmenü: News, Filme, Serien, Dokumentationen, Suche
def showMovieMenu(): # Untermenü: Kino, A-Z, Genre, Beliebt, Neu
def showHosters(): # Gibt Liste der Hoster-Dicts + 'getHosterUrl' zurück
def getHosterUrl(): # Löst Hoster-URL auf, gibt {'streamUrl':..., 'resolved':...} zurück
def _search(oGui, sSearchText): # Globale Suche, befüllt oGui
3.4 Hoster-Auflösungsflow
User klickt auf Film/Episode
└─► xstream.py: parseUrl() erkennt 'playMode'
├── hosterSelect = 'Auto' → cHosterGui.streamAuto()
└── sonst → cHosterGui.stream()
cHosterGui.stream(playMode, siteName, function):
1. Site-Plugin laden, Hoster-Funktion aufrufen → Hoster-Liste
2. Hoster nach Priorität sortieren (__getPriorities)
→ resolveurl.HostedMediaFile(url=...).get_resolvers()
→ Priorität nach Resolver-Ranking + Qualität + Sprache
3. Auswahl:
├── hosterSelect = 'List' → Hoster als Ordner anzeigen (showHosterFolder)
├── Mehrere Hoster → Dialog zur Auswahl (_chooseHoster)
└── Genau ein Hoster → direkt weiter
4. getHosterUrl(hoster['link']) aufrufen → {'streamUrl': ..., 'resolved': bool}
5. cHosterGui.play / download / addToPlaylist / sendToJD / ...
cHosterGui.play(siteResult):
1. resolveurl.resolve(streamUrl) → direkter Stream-Link
2. xbmcgui.ListItem mit Link erstellen
3. m3u8/mpd → inputstream.adaptive aktivieren
4. xbmcplugin.setResolvedUrl(handle, True, listItem)
3.5 Globale Suche
searchGlobal(sSearchText):
1. cGui im collectMode = True (Ergebnisse werden im Speicher gesammelt)
2. ThreadPoolExecutor (max. 10 Workers):
→ alle aktivierten Plugins mit globalsearch != 'false'
→ plugin._search(oGui, sSearchText) parallel aufrufen
3. Fortschrittsdialog aktualisieren (0–50 %)
4. collectMode = False
5. Ergebnisse nach Site-Name sortiert als Listeneinträge hinzufügen (50–100 %)
searchAlter (Kontextmenü „Weitere Quellen"):
- Wie searchGlobal, aber mit Titel-Normalisierung (Jahr, Staffelkennung entfernen)
- Ergebnisse werden nach Titel, Jahr und IMDB-ID gefiltert
searchTMDB (aus TMDB-Kontext aufgerufen):
- Wie searchGlobal, ohne zusätzliche Filterung
4. HTTP-Request-Handler (requestHandler.py)
cRequestHandler ist der zentrale HTTP-Client des Plugins.
Initialisierung (wichtige Parameter)
| Parameter | Standard | Bedeutung |
|---|---|---|
caching |
True |
HTML-Cache aktivieren |
ignoreErrors |
False |
HTTP-Fehler still ignorieren |
method |
'GET' |
HTTP-Methode |
compression |
True |
gzip/deflate anfordern |
bypass_dns |
False |
DNS-over-HTTPS verwenden |
Caching
Zwei Cache-Ebenen, konfigurierbar per Einstellung:
| Ebene | Speicherort | Aktivierung |
|---|---|---|
| Persistent | {profile}/htmlcache/{md5(url)} |
Standard |
| Volatil (In-Memory) | cCache (Wörterbuch) |
Einstellung volatileHtmlCache = true |
Cache-Lebensdauer: konfigurierbar, Standard 6 Stunden (360 min × 60 s).
Anti-Bot-Mechanismen
| Schutzmechanismus | Erkennung | Behandlung |
|---|---|---|
| Cloudflare | cloudflare im Response-Header |
Fehlermeldung an Nutzer, kein Retry |
| DDOS-Guard | DDOS-GUARD im Response-Body |
Automatischer Cookie-Challenge via check.ddos-guard.net |
| Blazingfast | lazingfast im Response-Body |
AES-Entschlüsselung via pyaes, Cookie-Setzung |
| DNS-Sperre (CUII) | Redirect zu notice.cuii.info |
Fehlerdialog; Bypass via DoH (Cloudflare) wenn aktiviert |
DNS-over-HTTPS (DoH)
Wenn bypass_dns = True und Einstellung bypassDNSlock = true:
→ DNS-Auflösung via https://cloudflare-dns.com/dns-query statt System-DNS
→ IP-Adresse wird direkt für TCP-Verbindung genutzt; SNI-Header enthält weiterhin den Hostnamen
→ IP-Ergebnis wird im volatilen Cache gespeichert
Cookie-Verwaltung
Pro Domain wird eine eigene Cookie-Datei im LWP-Format gespeichert:
{profile}/cookies/{domain_mit_unterstrichen}.txt
5. Plugin-Handler (pluginHandler.py)
Verwaltet die Erkennung und den Zustand der Site-Plugins.
PluginDB
Eine JSON-Datei unter {profile}/pluginDB. Sie enthält für jedes Site-Plugin:
{
"kinox": {
"name": "KinoX",
"identifier": "kinox",
"icon": "kinox.png",
"domain": "ww22.kinoz.to",
"globalsearch": "true",
"modified": 1700000000.0
}
}
Die DB wird aktualisiert, wenn:
- Eine neue
.py-Datei imsites/-Ordner gefunden wird - Eine vorhandene Datei seit dem letzten Einlesen geändert wurde
- Die globale Sucheinstellung für ein Plugin geändert wurde
Domain-Prüfung (checkDomain)
Wird beim Kodi-Start vom Service aufgerufen. Für jedes aktivierte Plugin:
- HTTP-Request an
http://{domain}/ - Status-Code wird in der Einstellung
plugin_{id}_statusgespeichert - Bei Redirect (3xx): neue Domain aus dem
Location-Header übernehmen - Bei 403–503: globale Suche für das Plugin deaktivieren
6. GUI-Schicht (gui.py, guiElement.py)
cGui
Abstraktion über Kodis xbmcplugin-API. Wesentliche Konzepte:
collectMode: Wenn True, werden Listeneinträge nicht an Kodi gesendet, sondern in self.searchResults gesammelt. Wird für die globale Suche verwendet, damit alle Plugins ihre Ergebnisse in eine gemeinsame Liste schreiben können.
TMDB-Metadaten: Wenn die Einstellung TMDBMETA = true, wird für jeden Eintrag automatisch guiElement.getMeta() aufgerufen (via TMDB-API), bevor er angezeigt wird.
Kontext-Menü-Einträge (automatisch für alle Einträge):
- Trailer (wenn Addon
script.module.xstream.trailerinstalliert) - Erweiterte Info (TMDB-Infobox)
- Weitere Quellen (searchAlter)
- Playlist hinzufügen
- Download
- An JDownloader / JDownloader2 / My.JDownloader / pyLoad senden
- Manueller Hoster-Auswahldialog (wenn Auto-Modus aktiv)
cGuiElement
Repräsentiert einen einzelnen Listeneintrag. Wesentliche Felder:
| Feld | Methode | Beschreibung |
|---|---|---|
| Titel | setTitle |
Anzeigename |
| Site-Name | setSiteName |
Zugehöriges Site-Plugin |
| Funktion | setFunction |
Aufzurufende Funktion beim Klick |
| Thumbnail | setThumbnail |
Vorschaubild-URL |
| Fanart | setFanart |
Hintergrundbild-URL |
| Medientyp | setMediaType |
movie, tvshow, season, episode |
| Sprache | setLanguage |
Sprach-Code (z. B. DE, EN) |
| Qualität | setQuality |
z. B. 1080p, 720p |
| Jahr | setYear |
Erscheinungsjahr |
| Staffel/Episode | setSeason/setEpisode |
Serieninformationen |
7. script.module.xstreamscraper
Das Scraper-Modul ist für den Fall gedacht, dass externe Tools (z. B. über Kodi-Addons, die TMDB-IDs kennen) einen Film oder eine Serie automatisch abspielen wollen, ohne durch das xStream-Menü navigieren zu müssen.
Haupt-API: scraper.play(_type, _id, season, episode)
play("movie", "123456", None, None)
play("tv", "78910", "2", "5") # Staffel 2, Episode 5
Ablauf:
play(_type, _id, season, episode):
1. TMDB-API: Film-/Seriendaten laden (Name, Erscheinungsdatum, alternative Titel)
2. Optional: vavoo-Integration prüfen (direkter Stream ohne Scraping)
3. searchGlobal(name, searchtitles, isSerie, ...) aufrufen
→ alle aktivierten Site-Plugins parallel durchsuchen (ThreadPoolExecutor)
→ Ergebnisse filtern:
- Sprache muss Deutsch sein
- Medientyp muss passen (Film ≠ Serie)
- Bei Serien: Staffelnummer prüfen
- Jahresabgleich (±0 bei Filmen)
- Titelabgleich (normalisiert: Sonderzeichen entfernt)
4. get_hosters(sources, isSerie, season, episode) aufrufen
→ Bei Serien: get_episodes() navigiert zu korrekter Episode
→ site-plugin.getHosterUrl() für jeden Treffer
5. Jeden Hoster der Reihe nach testen:
→ resolveurl.resolve(streamUrl) aufrufen
→ HTTP-HEAD-Request prüfen (Content-Type muss Video sein)
→ Erster gültiger Link → _play(url)
6. _play(url):
→ xbmcgui.ListItem mit URL
→ m3u8 → inputstream.adaptive
→ xbmcplugin.setResolvedUrl()
Unterschied zur manuellen Nutzung via plugin.video.xstream
| Aspekt | plugin.video.xstream | script.module.xstreamscraper |
|---|---|---|
| Einstieg | Nutzer-Navigation | TMDB-ID als Parameter |
| Hoster-Auswahl | Manuell oder nach Priorität | Vollautomatisch, erster funktionierender |
| Stream-Validierung | Keiner | HTTP-HEAD-Request vor Wiedergabe |
| Sprach-/Jahresfilter | Keine automatische Filterung | Automatisch (nur Deutsch, korrektes Jahr) |
| Zielgruppe | Endnutzer in Kodi-GUI | Andere Addons / Automatisierung |
8. Einstellungen (Auszug)
Alle Einstellungen befinden sich in resources/settings.xml und werden über cConfig gelesen.
| Einstellung | Bedeutung |
|---|---|
plugin_{id} |
Site-Plugin aktiviert (true/false) |
plugin_{id}.domain |
Überschreibt Standard-Domain des Plugins |
plugin_{id}_status |
Letzter HTTP-Status-Code der Domain |
plugin_{id}_checkdomain |
Domain-Prüfung für dieses Plugin aktiviert |
global_search_{id} |
Globale Suche für dieses Plugin aktiviert |
hosterSelect |
Auto (automatisch) oder List (manuelle Auswahl) |
cacheTime |
HTML-Cache-Lebensdauer in Minuten (Standard: 360) |
requestTimeout |
HTTP-Timeout in Sekunden (Standard: 10) |
volatileHtmlCache |
In-Memory-Cache aktivieren |
bypassDNSlock |
DNS-over-HTTPS für gesperrte Domains |
TMDBMETA |
TMDB-Metadaten für Listeneinträge laden |
metaOverwrite |
TMDB-Daten ersetzen statt ergänzen |
prefLanguage |
Bevorzugte Sprache (1 = Deutsch, 2 = Englisch) |
preferedQuality |
Bevorzugte Videoqualität für Hoster-Sortierung |
presortHoster |
Hoster nach Resolver-Priorität vorsortieren |
maxHoster |
Maximale Anzahl Hoster in der Liste |
jd_enabled / jd2_enabled / myjd_enabled / pyload_enabled |
Download-Manager aktivieren |
githubUpdateResolver |
Resolver bei Kodi-Start aktualisieren |
cacheDeltaDay |
HTML-Cache alle N Tage beim Start löschen |
popup.update.notification |
Changelog-Popup beim Start anzeigen |
9. Abhängigkeiten
plugin.video.xstream
| Addon | Pflicht | Zweck |
|---|---|---|
script.module.requests |
Ja | HTTP (via scraper-Modul) |
script.module.resolveurl ≥ 5.1.173 |
Ja | Hoster-Auflösung |
script.module.six ≥ 1.11.0 |
Ja | Python-2/3-Kompatibilität |
script.module.pyaes |
Ja | AES-Entschlüsselung (Blazingfast-Bypass) |
script.module.inputstreamhelper ≥ 0.3.3 |
Optional | HLS/DASH über inputstream.adaptive |
repository.resolveurl |
Optional | Resolver-Repository |
script.module.pydevd / script.module.web-pdb |
Optional | Debugging |
script.module.xstreamscraper
Zusätzlich zu den obigen:
| Addon | Pflicht | Zweck |
|---|---|---|
plugin.video.xstream |
Ja | Nutzt dessen Site-Plugins direkt |
plugin.video.vavooto |
Ja | Vavoo-Stream-Integration |
10. Bekannte Einschränkungen
- Cloudflare: Seiten mit aktivem Cloudflare-Schutz werden nicht automatisch umgangen. Der Nutzer erhält eine Fehlermeldung.
- Domain-Wechsel: Wenn eine Streaming-Seite ihre Domain ändert, muss entweder der Kodi-Start abgewartet werden (Domain-Prüfung aktualisiert die Einstellung automatisch via Redirect-Erkennung) oder die Domain muss manuell in den xStream-Einstellungen angepasst werden.
- Thread-Safety in Global Search: Die Site-Plugins werden parallel in Threads ausgeführt. Plugins, die globalen Zustand modifizieren, können Race-Conditions verursachen.
- scraper.py
plugin_Funktion: Der Scraper importiert Site-Plugins dynamisch und setztsettings.urlparamsals globalen Zustand – dies ist nicht thread-sicher und kann bei paralleler Nutzung zu Problemen führen. - Kodi-Version: Der Code enthält explizite Versionsprüfungen (
kodi_version[:2] < '20'usw.). Unter Kodi 19 wirdsetInfostattgetVideoInfoTag()verwendet.