mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-06 07:52:26 +02:00
185 lines
6.6 KiB
Python
185 lines
6.6 KiB
Python
# streaming_providers/base/managers/catchup.py
|
|
"""
|
|
CatchupManager ABC.
|
|
|
|
Public interface
|
|
----------------
|
|
catchup_window_hours -> int [concrete]
|
|
supports_catchup -> bool [concrete]
|
|
get_catchup_manifest(content_id, start_time, end_time=None, ...)
|
|
-> Optional[str] [abstract]
|
|
get_catchup_manifest_headers(...) -> Dict[str,str] [concrete]
|
|
get_catchup_drm(...) -> List[DRMConfig] [concrete]
|
|
|
|
Constructor contract
|
|
--------------------
|
|
Four required keyword-only collaborators. Catchup usually needs
|
|
additional collaborators at construction -- the channel manager (for
|
|
live manifest lookup) and/or the EPG manager (for EPG-based manifest
|
|
resolution, as MoveTV does). Those are explicit keyword-only extras
|
|
in the subclass.
|
|
|
|
Return-value conventions
|
|
------------------------
|
|
get_catchup_manifest returns None when the provider cannot resolve a
|
|
catchup manifest for the given content and window. It does NOT raise
|
|
NotFoundError -- "no catchup for this content" is a valid result, and
|
|
callers fall back to the live manifest.
|
|
|
|
get_catchup_drm returns [] when catchup shares DRM with live (the
|
|
common case), or a provider-specific list when catchup uses different
|
|
DRM.
|
|
|
|
Return the manifest of the live stream
|
|
--------------------------------------
|
|
The catchup manifest is a *modified* live manifest URL (with time
|
|
parameters) for providers like Magenta and MoveTV, or a distinct URL
|
|
for providers whose catchup is served from a different origin. This
|
|
ABC does not constrain the shape; it just names the entry point.
|
|
|
|
Do NOT silently fall back to the live manifest URL from within
|
|
get_catchup_manifest. Callers that want the live manifest on failure
|
|
should call provider.get_manifest() themselves. Silently returning a
|
|
live URL as a "catchup" URL would cause the DRM pipeline to extract
|
|
PSSH from the live stream, which may differ from the catchup stream's
|
|
encryption context.
|
|
|
|
end_time is optional
|
|
--------------------
|
|
The ABC accepts end_time as Optional[int] because some providers'
|
|
catchup APIs only take a start timestamp (simpliTV, for example --
|
|
it appends a single "start" parameter to the manifest URL and does
|
|
not consume an end bound). Providers whose API does use both bounds
|
|
should still declare and use end_time; providers whose API does not
|
|
should accept it for signature compatibility and document that it is
|
|
ignored.
|
|
|
|
Do NOT pass a sentinel value (0, or start_time, or start_time + 1800)
|
|
when the ABC accepts None. Pass None.
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
class CatchupManager(ABC):
|
|
"""Abstract base for provider catchup 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
|
|
|
|
# ------------------------------------------------------------------
|
|
# Capability
|
|
# ------------------------------------------------------------------
|
|
|
|
@property
|
|
def catchup_window_hours(self) -> int:
|
|
"""
|
|
Return the catchup window in hours.
|
|
|
|
Default 0 means no catchup. Providers override.
|
|
"""
|
|
return 0
|
|
|
|
@property
|
|
def supports_catchup(self) -> bool:
|
|
"""True when catchup_window_hours > 0."""
|
|
return self.catchup_window_hours > 0
|
|
|
|
# ------------------------------------------------------------------
|
|
# Abstract
|
|
# ------------------------------------------------------------------
|
|
|
|
@abstractmethod
|
|
def get_catchup_manifest(
|
|
self,
|
|
content_id: str,
|
|
start_time: int,
|
|
end_time: Optional[int] = None,
|
|
epg_id: Optional[str] = None,
|
|
**kw: Any,
|
|
) -> Optional[str]:
|
|
"""
|
|
Return the catchup manifest URL for the given content and window.
|
|
|
|
Args:
|
|
content_id: Channel identifier.
|
|
start_time: Window start as Unix timestamp (seconds).
|
|
end_time: Window end as Unix timestamp (seconds), or None
|
|
when the provider's API does not use an end bound
|
|
(or when the caller does not know it). Providers
|
|
that need both bounds should require the caller
|
|
to pass end_time and raise BadRequestError on
|
|
None; providers that don't should accept None
|
|
and ignore it.
|
|
epg_id: Optional EPG event id, for providers that need it.
|
|
|
|
Return None when the provider cannot resolve catchup for the
|
|
content or window. Do not raise NotFoundError -- "no catchup" is
|
|
a valid result.
|
|
|
|
Do NOT fall back to the live manifest URL here. See the module
|
|
docstring.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# ------------------------------------------------------------------
|
|
# Concrete
|
|
# ------------------------------------------------------------------
|
|
|
|
def get_catchup_manifest_headers(
|
|
self,
|
|
content_id: str,
|
|
start_time: int,
|
|
end_time: Optional[int] = None,
|
|
epg_id: Optional[str] = None,
|
|
**kw: Any,
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Headers for the catchup manifest request.
|
|
|
|
Default: the auth headers, which is correct for most providers.
|
|
Override when catchup requires additional or different headers.
|
|
"""
|
|
return self.auth.build_headers()
|
|
|
|
def get_catchup_drm(
|
|
self,
|
|
content_id: str,
|
|
start_time: int,
|
|
end_time: Optional[int] = None,
|
|
epg_id: Optional[str] = None,
|
|
**kw: Any,
|
|
) -> List[DRMConfig]:
|
|
"""
|
|
DRM for catchup content.
|
|
|
|
Default: []. Most providers' catchup shares DRM with live (the
|
|
caller falls back to the channel manager's DRM), or has no DRM.
|
|
|
|
Providers whose catchup uses a distinct DRM configuration
|
|
(different license URL, different PSSH) override this.
|
|
"""
|
|
return [] |