dev: YouTube Fixes, Trakt Credentials fest, Upcoming Ansicht, Watchlist Kontextmenue

This commit is contained in:
2026-03-14 12:50:39 +01:00
parent 4b9ba6a01a
commit d51505e004
6 changed files with 276 additions and 55 deletions

View File

@@ -18,7 +18,14 @@ except ImportError:
requests = None # type: ignore
from plugin_interface import BasisPlugin
from plugin_helpers import log_error
try:
import xbmc # type: ignore
def _log(msg: str) -> None:
xbmc.log(f"[ViewIt][YouTube] {msg}", xbmc.LOGWARNING)
except ImportError:
def _log(msg: str) -> None:
pass
# ---------------------------------------------------------------------------
# Konstanten
@@ -121,13 +128,15 @@ def _videos_from_search_data(data: dict) -> List[str]:
if title and video_id:
results.append(_encode(title, video_id))
except Exception as exc:
log_error(f"[YouTube] _videos_from_search_data Fehler: {exc}")
_log(f"[YouTube] _videos_from_search_data Fehler: {exc}")
return results
def _search_with_ytdlp(query: str, count: int = 20) -> List[str]:
"""Sucht YouTube-Videos via yt-dlp ytsearch-Extraktor."""
if not _ensure_ytdlp_in_path():
return []
try:
from yt_dlp import YoutubeDL # type: ignore
except ImportError:
@@ -144,7 +153,7 @@ def _search_with_ytdlp(query: str, count: int = 20) -> List[str]:
if e.get("id") and e.get("title")
]
except Exception as exc:
log_error(f"[YouTube] yt-dlp Suche Fehler: {exc}")
_log(f"[YouTube] yt-dlp Suche Fehler: {exc}")
return []
@@ -161,16 +170,60 @@ def _fetch_search_videos(url: str) -> List[str]:
return []
return _videos_from_search_data(data)
except Exception as exc:
log_error(f"[YouTube] _fetch_search_videos ({url}): {exc}")
_log(f"[YouTube] _fetch_search_videos ({url}): {exc}")
return []
def _fix_strptime() -> None:
"""Kodi-Workaround: datetime.strptime ist manchmal None."""
import datetime as _dt
import time as _time
if not callable(getattr(_dt.datetime, "strptime", None)):
_dt.datetime.strptime = lambda s, f: _dt.datetime(*(_time.strptime(s, f)[0:6]))
def _ensure_ytdlp_in_path() -> bool:
"""Fuegt script.module.yt-dlp/lib zum sys.path hinzu falls noetig."""
_fix_strptime()
try:
import yt_dlp # type: ignore # noqa: F401
return True
except ImportError:
pass
try:
import sys, os
import xbmcvfs # type: ignore
lib_path = xbmcvfs.translatePath("special://home/addons/script.module.yt-dlp/lib")
if lib_path and os.path.isdir(lib_path) and lib_path not in sys.path:
sys.path.insert(0, lib_path)
import yt_dlp # type: ignore # noqa: F401
return True
except Exception:
pass
return False
def _get_quality_format() -> str:
"""Liest YouTube-Qualitaet aus den Addon-Einstellungen."""
_QUALITY_MAP = {
"0": "best[ext=mp4]/best",
"1": "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]/best",
"2": "bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best",
"3": "bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best",
"4": "bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360][ext=mp4]/best",
}
try:
import xbmcaddon # type: ignore
val = xbmcaddon.Addon().getSetting("youtube_quality") or "0"
return _QUALITY_MAP.get(val, _QUALITY_MAP["0"])
except Exception:
return _QUALITY_MAP["0"]
def _resolve_with_ytdlp(video_id: str) -> Optional[str]:
"""Loest Video-ID via yt-dlp zu direkter Stream-URL auf."""
try:
from yt_dlp import YoutubeDL # type: ignore
except ImportError:
log_error("[YouTube] yt-dlp nicht verfuegbar (script.module.yt-dlp fehlt)")
if not _ensure_ytdlp_in_path():
_log("[YouTube] yt-dlp nicht verfuegbar (script.module.yt-dlp fehlt)")
try:
import xbmcgui
xbmcgui.Dialog().notification(
@@ -182,9 +235,14 @@ def _resolve_with_ytdlp(video_id: str) -> Optional[str]:
except Exception:
pass
return None
try:
from yt_dlp import YoutubeDL # type: ignore
except ImportError:
return None
url = f"https://www.youtube.com/watch?v={video_id}"
fmt = _get_quality_format()
ydl_opts: Dict[str, Any] = {
"format": "best[ext=mp4]/best",
"format": fmt,
"quiet": True,
"no_warnings": True,
"extract_flat": False,
@@ -203,7 +261,7 @@ def _resolve_with_ytdlp(video_id: str) -> Optional[str]:
if formats:
return formats[-1].get("url")
except Exception as exc:
log_error(f"[YouTube] yt-dlp Fehler fuer {video_id}: {exc}")
_log(f"[YouTube] yt-dlp Fehler fuer {video_id}: {exc}")
return None
@@ -214,8 +272,7 @@ def _resolve_with_ytdlp(video_id: str) -> Optional[str]:
class YoutubePlugin(BasisPlugin):
name = "YouTube"
# Pseudo-Staffeln: nur Suche Browse-Endpunkte erfordern Login
_SEASONS = ["Suche"]
_SEASONS = ["Stream"]
def capabilities(self) -> Set[str]:
return set()
@@ -241,8 +298,7 @@ class YoutubePlugin(BasisPlugin):
return list(self._SEASONS)
def episodes_for(self, title: str, season: str) -> List[str]:
if season == "Suche":
# Titel ist bereits ein kodierter Eintrag aus der Suche
if season == "Stream":
return [title]
return []