Files
script.service.ultimate/lib/streaming_providers/base/vod_operations.py
T
2026-03-19 12:46:47 +01:00

156 lines
5.7 KiB
Python

# streaming_providers/base/vod_operations.py
"""
VOD-related operations separated from core registry.
Follows the same pattern as ChannelOperations and EventOperations.
"""
from typing import Dict, List, Optional, Union
from .models.vod import VodCategory, VodItem
from .utils.logger import logger
class VodOperations:
"""Handles all VOD browsing and manifest operations."""
def __init__(self, registry):
self.registry = registry
logger.debug("VodOperations: Initialized")
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _get_provider(self, provider_name: str):
provider = self.registry.get_provider(provider_name)
if not provider:
raise ValueError(f"Provider '{provider_name}' not found or disabled")
return provider
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def get_vod_node(
self,
provider_name: str,
content_id: str = "",
cursor: Optional[str] = None,
page_size: int = 24,
**kwargs,
) -> Dict:
"""
Return the children of a VOD node, with optional paging support.
Args:
provider_name: Provider to query.
content_id: Opaque node identifier from a previous response
(VodCategory.content_id). Empty string → root.
cursor: Opaque continuation token from a previous response's
next_cursor field. None → first page.
Providers encode their own paging state (offset int,
next-page URL, scroll token, etc.) in this string.
page_size: Hint for how many entries to return per page.
Providers may ignore or clamp this value.
Returns:
{
"entries": List[VodCategory | VodItem],
"next_cursor": Optional[str], # None = no further pages
"total": Optional[int], # total count if known by provider
}
Providers that have not yet been updated to support paging may
return a plain list; this method normalises that into the dict
shape above with next_cursor=None so callers never need to
special-case the old return type.
Raises:
ValueError: Provider not found.
"""
provider = self._get_provider(provider_name)
label = content_id or "root"
logger.debug(
f"VodOperations: Fetching VOD node '{label}' from '{provider_name}' "
f"(cursor={cursor!r}, page_size={page_size})"
)
raw = provider.get_vod_category(
content_id=content_id,
cursor=cursor,
page_size=page_size,
**kwargs,
)
# Normalise: providers that have not yet adopted paging return a plain
# list. Wrap it so all callers always get the same dict shape.
if isinstance(raw, list):
result = {"entries": raw, "next_cursor": None, "total": None}
else:
result = raw
entries = result.get("entries", [])
next_cursor = result.get("next_cursor")
logger.info(
f"VodOperations: Retrieved {len(entries)} VOD entries from '{provider_name}' "
f"at '{label}'"
+ (f" — next_cursor present" if next_cursor else "")
)
return result
def get_vod_manifest(
self, provider_name: str, vod_id: str, **kwargs
) -> Optional[str]:
"""
Get manifest URL for a specific VOD item.
Delegates directly to provider.get_manifest(content_id) — identical
to channel and event manifest resolution.
"""
provider = self._get_provider(provider_name)
manifest_url = provider.get_manifest(content_id=vod_id, **kwargs)
if manifest_url:
logger.debug(
f"Retrieved manifest for VOD '{vod_id}' from '{provider_name}'"
)
return manifest_url
def get_vod_drm_configs(
self, provider_name: str, vod_id: str, **kwargs
):
"""
Get DRM configs for a specific VOD item.
Delegates directly to provider.get_drm(content_id).
"""
provider = self._get_provider(provider_name)
return provider.get_drm(content_id=vod_id, **kwargs)
def get_all_vod_roots(self) -> Dict[str, List[Union[VodCategory, VodItem]]]:
"""
Get root VOD entries from all enabled providers that implement VOD.
Providers that return implements_vod=False are silently skipped.
Only fetches the first page of root entries per provider — callers
that need subsequent pages should use get_vod_node directly.
"""
enabled = self.registry.get_enabled_providers()
logger.info(f"Fetching VOD roots from {len(enabled)} providers")
result = {}
total = 0
for name in enabled:
try:
provider = self.registry.get_provider(name)
if not getattr(provider, "implements_vod", False):
continue
node = self.get_vod_node(name, content_id="")
entries = node["entries"]
result[name] = entries
total += len(entries)
except Exception as e:
logger.error(f"Failed to get VOD root from '{name}': {e}")
result[name] = []
logger.info(f"Retrieved {total} total root VOD entries")
return result