Files
script.service.ultimate/lib/streaming_providers/base/managers/vod.py
T
2026-10-03 14:35:57 +02:00

130 lines
4.4 KiB
Python

# streaming_providers/base/managers/vod.py
"""
VodManager ABC.
Public interface
----------------
handles_content_id(content_id) -> bool [concrete]
get_vod_category(content_id="", cursor=None, page_size=24, **kw)
-> VodPage [abstract]
get_vod_manifest(content_id, **kw) -> Optional[str] [abstract]
search_vod(query, cursor=None, page_size=24, **kw) -> VodPage [concrete]
get_vod_manifest_headers(content_id, **kw) -> Dict[str, str] [concrete]
get_vod_drm(content_id, **kw) -> List[DRMConfig] [concrete]
content_id grammar
------------------
content_id is an opaque token. Each provider picks its own grammar and
documents it here. The base class does not parse content_id. Examples:
RTL+ "folder_<id>", "program_<id>", "clip_<id>", ...
HRTi "catalogue_<id>", "series_<sid>--<ssid>", ...
Discovery "/sports", "/sports/alpine-skiing", ...
MoveTV ["root", "Film", "123"] encoded into a string
Constructor contract
--------------------
Four required keyword-only collaborators. No **kwargs. Subclasses accept
extra keyword-only args explicitly and call super().__init__ with only
the four required. A typo at a call site becomes an immediate TypeError.
Return-value conventions
------------------------
get_vod_manifest / get_vod_drm return None / [] when this manager does not
handle content_id. Auth / geo / entitlement / rate-limit / server failures
propagate as exceptions from base.errors.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from ..models import DRMConfig
from ..protocols import AuthProtocol
from ..utils.logger import logger
from ..vod import VodPage
class VodManager(ABC):
"""Abstract base for provider VOD managers."""
def __init__(
self,
*,
http_manager: Any,
auth: AuthProtocol,
country: str,
config: Any,
) -> None:
if not isinstance(auth, AuthProtocol):
logger.warning(
f"{self.__class__.__name__}: auth does not match AuthProtocol "
f"(missing one of get_access_token / build_headers / "
f"invalidate). Got {type(auth).__name__}."
)
self.http_manager = http_manager
self.auth = auth
self.country = country
self.config = config
# ------------------------------------------------------------------
# Routing
# ------------------------------------------------------------------
def handles_content_id(self, content_id: str) -> bool:
"""
True if this manager handles the given content_id.
Default: True. Override in providers whose VOD content_ids have a
distinguishable grammar (e.g. start with "details_" or "clip_").
"""
return True
# ------------------------------------------------------------------
# Abstract
# ------------------------------------------------------------------
@abstractmethod
def get_vod_category(
self,
content_id: str = "",
cursor: Optional[str] = None,
page_size: int = 24,
**kw: Any,
) -> VodPage:
"""Return children of a VOD node. Empty content_id is the root."""
raise NotImplementedError
@abstractmethod
def get_vod_manifest(
self, content_id: str, **kw: Any
) -> Optional[str]:
"""Return the manifest URL, or None if not handled by this manager."""
raise NotImplementedError
# ------------------------------------------------------------------
# Concrete
# ------------------------------------------------------------------
def search_vod(
self,
query: str,
cursor: Optional[str] = None,
page_size: int = 24,
**kw: Any,
) -> VodPage:
"""Search the catalogue. Default: no results. Override if supported."""
return VodPage()
def get_vod_manifest_headers(
self, content_id: str, **kw: Any
) -> Dict[str, str]:
"""Headers for the manifest request. Default: auth headers."""
return self.auth.build_headers()
def get_vod_drm(
self, content_id: str, **kw: Any
) -> List[DRMConfig]:
"""DRM for a VOD item. Default: no DRM."""
return []