Files
script.service.ultimate/lib/streaming_providers/base/vod_operations.py
T

156 lines
5.7 KiB
Python
Raw Normal View History

2026-03-03 15:24:56 +01:00
# 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
2026-03-18 19:14:50 +01:00
from .models.vod import VodCategory, VodItem
2026-03-03 15:24:56 +01:00
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,
2026-03-18 19:14:50 +01:00
content_id: str = "",
2026-03-19 12:46:47 +01:00
cursor: Optional[str] = None,
page_size: int = 24,
2026-03-17 20:09:46 +01:00
**kwargs,
2026-03-19 12:46:47 +01:00
) -> Dict:
2026-03-03 15:24:56 +01:00
"""
2026-03-19 12:46:47 +01:00
Return the children of a VOD node, with optional paging support.
2026-03-03 15:24:56 +01:00
Args:
provider_name: Provider to query.
2026-03-18 19:14:50 +01:00
content_id: Opaque node identifier from a previous response
(VodCategory.content_id). Empty string → root.
2026-03-19 12:46:47 +01:00
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.
2026-03-03 15:24:56 +01:00
Returns:
2026-03-19 12:46:47 +01:00
{
"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.
2026-03-03 15:24:56 +01:00
Raises:
2026-03-18 19:14:50 +01:00
ValueError: Provider not found.
2026-03-03 15:24:56 +01:00
"""
provider = self._get_provider(provider_name)
2026-03-18 19:14:50 +01:00
label = content_id or "root"
2026-03-19 12:46:47 +01:00
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")
2026-03-03 15:24:56 +01:00
logger.info(
2026-03-19 12:46:47 +01:00
f"VodOperations: Retrieved {len(entries)} VOD entries from '{provider_name}' "
2026-03-18 19:14:50 +01:00
f"at '{label}'"
2026-03-19 12:46:47 +01:00
+ (f" — next_cursor present" if next_cursor else "")
2026-03-03 15:24:56 +01:00
)
2026-03-19 12:46:47 +01:00
return result
2026-03-03 15:24:56 +01:00
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
2026-03-04 11:41:00 +01:00
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)
2026-03-03 15:24:56 +01:00
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.
2026-03-19 12:46:47 +01:00
Only fetches the first page of root entries per provider — callers
that need subsequent pages should use get_vod_node directly.
2026-03-03 15:24:56 +01:00
"""
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
2026-03-19 12:46:47 +01:00
node = self.get_vod_node(name, content_id="")
entries = node["entries"]
2026-03-03 15:24:56 +01:00
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