mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-20 16:12:15 +02:00
256 lines
9.3 KiB
Python
256 lines
9.3 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 search_vod(
|
|
self,
|
|
provider_name: str,
|
|
query: str,
|
|
cursor: Optional[str] = None,
|
|
page_size: int = 24,
|
|
**kwargs,
|
|
) -> Dict:
|
|
"""
|
|
Search the VOD catalogue of a single provider.
|
|
|
|
Args:
|
|
provider_name: Provider to query.
|
|
query: Free-text search string entered by the user.
|
|
cursor: Opaque continuation token from a previous response's
|
|
next_cursor field. None → first page.
|
|
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 do not implement search return an empty entries list
|
|
with next_cursor=None.
|
|
|
|
Raises:
|
|
ValueError: Provider not found.
|
|
"""
|
|
provider = self._get_provider(provider_name)
|
|
logger.debug(
|
|
f"VodOperations: Searching VOD in '{provider_name}' "
|
|
f"(query={query!r}, cursor={cursor!r}, page_size={page_size})"
|
|
)
|
|
|
|
raw = provider.search_vod(
|
|
query=query,
|
|
cursor=cursor,
|
|
page_size=page_size,
|
|
**kwargs,
|
|
)
|
|
|
|
# Normalise: providers may return a plain list or the paged 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: Search '{query}' in '{provider_name}' returned "
|
|
f"{len(entries)} entries"
|
|
+ (f" — next_cursor present" if next_cursor else "")
|
|
)
|
|
return result
|
|
|
|
def search_all_vod(
|
|
self, query: str, **kwargs
|
|
) -> Dict[str, List[Union[VodCategory, VodItem]]]:
|
|
"""
|
|
Search the VOD catalogue across all enabled providers that implement VOD.
|
|
|
|
Providers that return implements_vod=False are silently skipped.
|
|
Only fetches the first page of results per provider — callers that
|
|
need subsequent pages should use search_vod directly.
|
|
|
|
Args:
|
|
query: Free-text search string entered by the user.
|
|
|
|
Returns:
|
|
Dict mapping provider name → list of matching VodCategory/VodItem
|
|
objects. Providers that fail or return no results are included
|
|
with an empty list so the caller always gets a complete map of
|
|
enabled VOD providers.
|
|
"""
|
|
enabled = self.registry.get_enabled_providers()
|
|
logger.info(f"Searching VOD across {len(enabled)} providers (query={query!r})")
|
|
|
|
result = {}
|
|
total = 0
|
|
|
|
for name in enabled:
|
|
try:
|
|
provider = self.registry.get_provider(name)
|
|
if not getattr(provider, "implements_vod", False):
|
|
continue
|
|
node = self.search_vod(name, query=query, **kwargs)
|
|
entries = node["entries"]
|
|
result[name] = entries
|
|
total += len(entries)
|
|
except Exception as e:
|
|
logger.error(f"Failed to search VOD in '{name}': {e}")
|
|
result[name] = []
|
|
|
|
logger.info(f"VOD search '{query}' returned {total} total entries")
|
|
return result
|
|
|
|
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 |