dev: YouTube HD via inputstream.adaptive, DokuStreams Suche fix
This commit is contained in:
203
addon/default.py
203
addon/default.py
@@ -3965,6 +3965,190 @@ def _resolve_stream_with_retry(plugin: BasisPlugin, link: str) -> str | None:
|
||||
return final_link
|
||||
|
||||
|
||||
def _is_inputstream_adaptive_available() -> bool:
|
||||
"""Prueft ob inputstream.adaptive in Kodi installiert ist."""
|
||||
try:
|
||||
import xbmcaddon # type: ignore
|
||||
xbmcaddon.Addon("inputstream.adaptive")
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lokaler MPD-Manifest-Server fuer inputstream.adaptive
|
||||
# ---------------------------------------------------------------------------
|
||||
_mpd_server_instance = None
|
||||
_mpd_server_port = 0
|
||||
|
||||
|
||||
def _ensure_mpd_server() -> int:
|
||||
"""Startet einen lokalen HTTP-Server der MPD-Manifeste serviert.
|
||||
|
||||
Gibt den Port zurueck. Der Server laeuft in einem Daemon-Thread.
|
||||
"""
|
||||
global _mpd_server_instance, _mpd_server_port
|
||||
if _mpd_server_instance is not None:
|
||||
return _mpd_server_port
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import threading
|
||||
|
||||
_pending_manifests: dict[str, str] = {}
|
||||
|
||||
class _ManifestHandler(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self) -> None:
|
||||
if "/manifest" in self.path:
|
||||
key = self.path.split("key=")[-1].split("&")[0] if "key=" in self.path else ""
|
||||
content = _pending_manifests.pop(key, "")
|
||||
if content:
|
||||
data = content.encode("utf-8")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/dash+xml")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
return
|
||||
self.send_error(404)
|
||||
|
||||
def log_message(self, *_args: object) -> None:
|
||||
pass # kein Logging
|
||||
|
||||
server = socketserver.TCPServer(("127.0.0.1", 0), _ManifestHandler)
|
||||
_mpd_server_port = server.server_address[1]
|
||||
_mpd_server_instance = server
|
||||
|
||||
# pending_manifests als Attribut am Server speichern
|
||||
server._pending_manifests = _pending_manifests # type: ignore[attr-defined]
|
||||
|
||||
t = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
t.start()
|
||||
_log(f"MPD-Server gestartet auf Port {_mpd_server_port}", xbmc.LOGDEBUG)
|
||||
return _mpd_server_port
|
||||
|
||||
|
||||
def _register_mpd_manifest(mpd_xml: str) -> str:
|
||||
"""Registriert ein MPD-Manifest und gibt die lokale URL zurueck."""
|
||||
import hashlib
|
||||
port = _ensure_mpd_server()
|
||||
key = hashlib.md5(mpd_xml.encode()).hexdigest()[:12]
|
||||
if _mpd_server_instance is not None:
|
||||
_mpd_server_instance._pending_manifests[key] = mpd_xml # type: ignore[attr-defined]
|
||||
return f"http://127.0.0.1:{port}/plugin.video.viewit/manifest?key={key}"
|
||||
|
||||
|
||||
def _play_dual_stream(
|
||||
video_url: str,
|
||||
audio_url: str,
|
||||
*,
|
||||
meta: dict[str, str] | None = None,
|
||||
display_title: str | None = None,
|
||||
info_labels: dict[str, str] | None = None,
|
||||
art: dict[str, str] | None = None,
|
||||
cast: list[TmdbCastMember] | None = None,
|
||||
resolve_handle: int | None = None,
|
||||
trakt_media: dict[str, object] | None = None,
|
||||
) -> None:
|
||||
"""Spielt getrennte Video+Audio-Streams via inputstream.adaptive.
|
||||
|
||||
Startet einen lokalen HTTP-Server der ein generiertes MPD-Manifest
|
||||
serviert (gem. inputstream.adaptive Wiki: Integration + Custom Manifest).
|
||||
Fallback auf Video-only wenn inputstream.adaptive nicht installiert.
|
||||
"""
|
||||
if not _is_inputstream_adaptive_available():
|
||||
_log("inputstream.adaptive nicht verfuegbar – Video-only Wiedergabe", xbmc.LOGWARNING)
|
||||
_play_final_link(
|
||||
video_url, display_title=display_title, info_labels=info_labels,
|
||||
art=art, cast=cast, resolve_handle=resolve_handle, trakt_media=trakt_media,
|
||||
)
|
||||
return
|
||||
|
||||
from xml.sax.saxutils import escape as xml_escape
|
||||
|
||||
m = meta or {}
|
||||
vcodec = m.get("vc", "avc1.640028")
|
||||
acodec = m.get("ac", "mp4a.40.2")
|
||||
w = m.get("w", "1920")
|
||||
h = m.get("h", "1080")
|
||||
fps = m.get("fps", "25")
|
||||
vbr = m.get("vbr", "5000000")
|
||||
abr = m.get("abr", "128000")
|
||||
asr = m.get("asr", "44100")
|
||||
ach = m.get("ach", "2")
|
||||
dur = m.get("dur", "0")
|
||||
|
||||
dur_attr = ""
|
||||
if dur and dur != "0":
|
||||
dur_attr = f' mediaPresentationDuration="PT{dur}S"'
|
||||
|
||||
mpd_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static"'
|
||||
' minBufferTime="PT2S"'
|
||||
' profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"'
|
||||
+ dur_attr + '>'
|
||||
'<Period>'
|
||||
'<AdaptationSet mimeType="video/mp4" contentType="video" subsegmentAlignment="true">'
|
||||
'<Representation id="video" bandwidth="' + vbr + '"'
|
||||
' codecs="' + xml_escape(vcodec) + '"'
|
||||
' width="' + w + '" height="' + h + '"'
|
||||
' frameRate="' + fps + '">'
|
||||
'<BaseURL>' + xml_escape(video_url) + '</BaseURL>'
|
||||
'</Representation>'
|
||||
'</AdaptationSet>'
|
||||
'<AdaptationSet mimeType="audio/mp4" contentType="audio" subsegmentAlignment="true">'
|
||||
'<Representation id="audio" bandwidth="' + abr + '"'
|
||||
' codecs="' + xml_escape(acodec) + '"'
|
||||
' audioSamplingRate="' + asr + '">'
|
||||
'<AudioChannelConfiguration'
|
||||
' schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"'
|
||||
' value="' + ach + '"/>'
|
||||
'<BaseURL>' + xml_escape(audio_url) + '</BaseURL>'
|
||||
'</Representation>'
|
||||
'</AdaptationSet>'
|
||||
'</Period>'
|
||||
'</MPD>'
|
||||
)
|
||||
|
||||
mpd_url = _register_mpd_manifest(mpd_xml)
|
||||
_log(f"MPD-Manifest URL: {mpd_url}", xbmc.LOGDEBUG)
|
||||
|
||||
list_item = xbmcgui.ListItem(label=display_title or "", path=mpd_url)
|
||||
list_item.setMimeType("application/dash+xml")
|
||||
list_item.setContentLookup(False)
|
||||
list_item.setProperty("inputstream", "inputstream.adaptive")
|
||||
list_item.setProperty("inputstream.adaptive.manifest_type", "mpd")
|
||||
|
||||
merged_info: dict[str, object] = dict(info_labels or {})
|
||||
if display_title:
|
||||
merged_info["title"] = display_title
|
||||
_apply_video_info(list_item, merged_info, cast)
|
||||
if art:
|
||||
setter = getattr(list_item, "setArt", None)
|
||||
if callable(setter):
|
||||
try:
|
||||
setter(art)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
resolved = False
|
||||
if resolve_handle is not None:
|
||||
resolver = getattr(xbmcplugin, "setResolvedUrl", None)
|
||||
if callable(resolver):
|
||||
try:
|
||||
resolver(resolve_handle, True, list_item)
|
||||
resolved = True
|
||||
except Exception:
|
||||
pass
|
||||
if not resolved:
|
||||
xbmc.Player().play(item=mpd_url, listitem=list_item)
|
||||
|
||||
if trakt_media and _get_setting_bool("trakt_enabled", default=False):
|
||||
_trakt_scrobble_start_async(trakt_media)
|
||||
_trakt_monitor_playback(trakt_media)
|
||||
|
||||
|
||||
def _play_final_link(
|
||||
link: str,
|
||||
*,
|
||||
@@ -3975,6 +4159,25 @@ def _play_final_link(
|
||||
resolve_handle: int | None = None,
|
||||
trakt_media: dict[str, object] | None = None,
|
||||
) -> None:
|
||||
# Getrennte Video+Audio-Streams (yt-dlp): via inputstream.adaptive abspielen
|
||||
audio_url = None
|
||||
meta: dict[str, str] = {}
|
||||
try:
|
||||
from ytdlp_helper import split_video_audio
|
||||
link, audio_url, meta = split_video_audio(link)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if audio_url:
|
||||
_play_dual_stream(
|
||||
link, audio_url,
|
||||
meta=meta,
|
||||
display_title=display_title, info_labels=info_labels,
|
||||
art=art, cast=cast, resolve_handle=resolve_handle,
|
||||
trakt_media=trakt_media,
|
||||
)
|
||||
return
|
||||
|
||||
list_item = xbmcgui.ListItem(label=display_title or "", path=link)
|
||||
try:
|
||||
list_item.setProperty("IsPlayable", "true")
|
||||
|
||||
Reference in New Issue
Block a user