mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-16 22:22:27 +02:00
Add discovery
This commit is contained in:
@@ -24,7 +24,7 @@ class DiscoveryChannelManager:
|
||||
|
||||
Responsibilities:
|
||||
- Fetching and parsing distribution channels from the CMS /home route
|
||||
- Maintaining the ``_channels_cache`` (channel_id → DiscoveryChannel)
|
||||
- Maintaining the ``_channels_cache`` (edit_id → DiscoveryChannel)
|
||||
- Discovering and caching navigation routes from the /home response graph
|
||||
into ``_cms_routes`` (route_id → label)
|
||||
|
||||
@@ -163,6 +163,27 @@ class DiscoveryChannelManager:
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not discover CMS routes at init: {e}")
|
||||
|
||||
def get_by_distribution_id(
|
||||
self, distribution_id: str
|
||||
) -> Optional[DiscoveryChannel]:
|
||||
"""
|
||||
Look up a cached DiscoveryChannel by its CMS distribution UUID.
|
||||
|
||||
The cache is keyed by ``edit_id``, so this performs a linear scan
|
||||
using the ``distribution_id`` stored in ``raw_data``. The channel
|
||||
list is small (typically < 50), so this is acceptable.
|
||||
|
||||
Args:
|
||||
distribution_id: The distributionChannel UUID from the CMS.
|
||||
|
||||
Returns:
|
||||
Matching DiscoveryChannel, or None if not found.
|
||||
"""
|
||||
for channel in self._channels_cache.values():
|
||||
if channel.raw_data.get("distribution_id") == distribution_id:
|
||||
return channel
|
||||
return None
|
||||
|
||||
# =========================================================================
|
||||
# CMS route discovery
|
||||
# =========================================================================
|
||||
|
||||
@@ -278,17 +278,24 @@ class DiscoveryEventManager:
|
||||
relationships, now_utc, start_dt, end_dt
|
||||
)
|
||||
|
||||
# ---- channel lookup -----------------------------------------
|
||||
channel_name: Optional[str] = None
|
||||
# ---- edit_id from distributionChannel cache -----------------
|
||||
# The distributionChannel relationship carries the CMS UUID
|
||||
# (distribution_id). The cache is keyed by edit_id, so we
|
||||
# use the reverse map on the provider to resolve it.
|
||||
edit_id: Optional[str] = None
|
||||
dist_channel_ref = (
|
||||
relationships.get("distributionChannel", {}).get("data", {})
|
||||
)
|
||||
dist_channel_id = (
|
||||
dist_channel_ref.get("id") if dist_channel_ref else None
|
||||
)
|
||||
channel_name: Optional[str] = None
|
||||
if dist_channel_id:
|
||||
cached = self._channels_cache.get(dist_channel_id)
|
||||
cached = self._provider.channel_manager.get_by_distribution_id(
|
||||
dist_channel_id
|
||||
)
|
||||
if cached:
|
||||
edit_id = cached.channel_id # channel_id == edit_id
|
||||
channel_name = cached.name
|
||||
else:
|
||||
logger.debug(
|
||||
@@ -299,13 +306,13 @@ class DiscoveryEventManager:
|
||||
# ---- build Event --------------------------------------------
|
||||
event = Event(
|
||||
name=attributes.get("name", "Unknown Event"),
|
||||
content_id=airing.get("id", ""),
|
||||
content_id=edit_id or airing.get("id", ""),
|
||||
provider=self._provider.provider_name,
|
||||
logo_url=None,
|
||||
mode="live" if status == EventStatus.LIVE else "vod",
|
||||
session_manifest=False,
|
||||
manifest_script=None,
|
||||
cdm=None,
|
||||
session_manifest=True if edit_id else False,
|
||||
manifest_script=edit_id,
|
||||
cdm=edit_id,
|
||||
content_type="AIRING",
|
||||
description=attributes.get("description", ""),
|
||||
genre=None,
|
||||
@@ -613,7 +620,7 @@ class DiscoveryEventManager:
|
||||
|
||||
event = Event(
|
||||
name=attributes.get("name", "Unknown Event"),
|
||||
content_id=video_data.get("id", ""),
|
||||
content_id=edit_id or video_data.get("id", ""),
|
||||
provider=self._provider.provider_name,
|
||||
logo_url=logo_url,
|
||||
mode=(
|
||||
@@ -621,11 +628,9 @@ class DiscoveryEventManager:
|
||||
if attributes.get("videoType") == "LIVE"
|
||||
else "vod"
|
||||
),
|
||||
session_manifest=True,
|
||||
manifest_script=(
|
||||
f"editid={edit_id}" if edit_id else None
|
||||
),
|
||||
cdm=f"editid={edit_id}" if edit_id else None,
|
||||
session_manifest=True if edit_id else False,
|
||||
manifest_script=edit_id,
|
||||
cdm=edit_id,
|
||||
content_type="EVENT",
|
||||
description=attributes.get("description", ""),
|
||||
genre=genre,
|
||||
|
||||
@@ -109,24 +109,30 @@ class DiscoveryChannel:
|
||||
attributes = distribution_data.get("attributes", {})
|
||||
relationships = distribution_data.get("relationships", {})
|
||||
|
||||
# Get edit ID
|
||||
# edit_id is the playback identifier — used as content_id throughout
|
||||
edit_data = relationships.get("edit", {}).get("data", {})
|
||||
edit_id = edit_data.get("id")
|
||||
|
||||
# distribution_id is the CMS channel UUID — kept as metadata only
|
||||
distribution_id = distribution_data.get("id", "")
|
||||
|
||||
# Extract logo
|
||||
logo_url = cls._extract_logo_url(relationships, included_by_id)
|
||||
|
||||
raw = distribution_data.copy()
|
||||
raw["distribution_id"] = distribution_id
|
||||
|
||||
return cls(
|
||||
name=attributes.get("name", "Unknown Channel"),
|
||||
channel_id=distribution_data.get("id", ""),
|
||||
channel_id=edit_id or distribution_id, # edit_id is the primary key
|
||||
edit_id=edit_id,
|
||||
logo_url=logo_url,
|
||||
description=attributes.get("description", ""),
|
||||
mode=StreamingMode.LIVE.value,
|
||||
session_manifest=True,
|
||||
manifest_script=f"editid={edit_id}" if edit_id else None,
|
||||
cdm=f"editid={edit_id}" if edit_id else None,
|
||||
raw_data=distribution_data.copy(),
|
||||
manifest_script=distribution_id or None,
|
||||
cdm=distribution_id or None,
|
||||
raw_data=raw,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -150,23 +156,26 @@ class DiscoveryChannel:
|
||||
attributes = api_data.get("attributes", {})
|
||||
relationships = api_data.get("relationships", {})
|
||||
|
||||
# Get edit ID (for VOD, the ID itself is often the edit_id)
|
||||
# For VOD/events, the item ID itself is the edit_id (playback identifier)
|
||||
edit_id = api_data.get("id")
|
||||
|
||||
# Extract logo
|
||||
logo_url = cls._extract_logo_url(relationships, included_by_id)
|
||||
|
||||
raw = api_data.copy()
|
||||
raw["source_id"] = edit_id # preserve original CMS ID as metadata
|
||||
|
||||
channel = cls(
|
||||
name=attributes.get("name", "Unknown Event"),
|
||||
channel_id=api_data.get("id", ""),
|
||||
channel_id=edit_id or "", # edit_id is the primary key
|
||||
edit_id=edit_id,
|
||||
logo_url=logo_url,
|
||||
content_type="VOD",
|
||||
mode=StreamingMode.VOD.value,
|
||||
session_manifest=True,
|
||||
manifest_script=f"editid={edit_id}" if edit_id else None,
|
||||
cdm=f"editid={edit_id}" if edit_id else None,
|
||||
raw_data=api_data.copy(),
|
||||
manifest_script=edit_id or None,
|
||||
cdm=edit_id or None,
|
||||
raw_data=raw,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@@ -314,13 +323,9 @@ class DiscoveryChannel:
|
||||
# Transfer raw_data
|
||||
channel.raw_data = self.raw_data.copy()
|
||||
|
||||
# Store edit_id and timestamps in raw_data for later use
|
||||
if self.edit_id:
|
||||
channel.raw_data["edit_id"] = self.edit_id
|
||||
if self.start_time:
|
||||
channel.raw_data["start_time"] = self.start_time
|
||||
if self.end_time:
|
||||
channel.raw_data["end_time"] = self.end_time
|
||||
# Store distribution_id in raw_data for reference (channels only)
|
||||
if "distribution_id" in self.raw_data:
|
||||
channel.raw_data["distribution_id"] = self.raw_data["distribution_id"]
|
||||
|
||||
return channel
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ from ...base.utils.logger import logger
|
||||
|
||||
from .constants import PlatformOS, get_default_capabilities, get_default_device_info, get_drm_request_headers
|
||||
from .exceptions import ManifestFetchError, PlaybackRestrictedException
|
||||
from .models import DiscoveryChannel
|
||||
|
||||
|
||||
class DiscoveryPlaybackManager:
|
||||
@@ -43,11 +42,9 @@ class DiscoveryPlaybackManager:
|
||||
def __init__(
|
||||
self,
|
||||
provider, # DiscoveryProvider — avoid circular import
|
||||
channels_cache: Dict[str, DiscoveryChannel],
|
||||
playback_cache: Dict[str, tuple],
|
||||
):
|
||||
self._provider = provider
|
||||
self._channels_cache = channels_cache
|
||||
# {edit_id: (expiry_timestamp, playback_data)}
|
||||
self._playback_cache = playback_cache
|
||||
|
||||
@@ -63,9 +60,9 @@ class DiscoveryPlaybackManager:
|
||||
"""
|
||||
Populate streaming data for a list of StreamingChannel objects.
|
||||
|
||||
Looks up each channel's ``edit_id`` in the shared ``_channels_cache``,
|
||||
fetches (or returns cached) playback info, and attaches the manifest
|
||||
URL, streaming format, and DRM config to the channel object.
|
||||
``channel.channel_id`` is the ``edit_id`` (playback identifier), so
|
||||
it is passed directly to ``get_cached_playback_info`` — no cache
|
||||
indirection required.
|
||||
|
||||
Args:
|
||||
channels: StreamingChannel objects to populate.
|
||||
@@ -83,17 +80,10 @@ class DiscoveryPlaybackManager:
|
||||
|
||||
while retries < max_retries and not success and not is_restricted:
|
||||
try:
|
||||
disco_channel = self._channels_cache.get(channel.channel_id)
|
||||
if not disco_channel:
|
||||
logger.warning(
|
||||
f"Channel {channel.name} not in cache, skipping"
|
||||
)
|
||||
break
|
||||
|
||||
edit_id = disco_channel.edit_id
|
||||
edit_id = channel.channel_id
|
||||
if not edit_id:
|
||||
logger.warning(
|
||||
f"No edit_id for channel {channel.name}, skipping"
|
||||
f"No edit_id (channel_id) for {channel.name}, skipping"
|
||||
)
|
||||
break
|
||||
|
||||
@@ -120,11 +110,6 @@ class DiscoveryPlaybackManager:
|
||||
channel.license_url = streaming_data["license_url"]
|
||||
channel.cdm_type = streaming_data["drm_system"]
|
||||
|
||||
if streaming_data["drm_auth"]:
|
||||
disco_channel.raw_data["drm_auth"] = (
|
||||
streaming_data["drm_auth"]
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Streaming data populated for: {channel.name}"
|
||||
)
|
||||
@@ -319,7 +304,7 @@ class DiscoveryPlaybackManager:
|
||||
"applicationSessionId": str(uuid.uuid4()),
|
||||
"userPreferences": {
|
||||
"videoQuality": "best",
|
||||
"uiLanguage": f"{self._provider.country}-DE".upper(),
|
||||
"uiLanguage": f"{self._provider.country.lower()}-{self._provider.country.upper()}",
|
||||
},
|
||||
"features": ["mlp"],
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ from .constants import (
|
||||
get_user_agent,
|
||||
)
|
||||
from .event_manager import DiscoveryEventManager
|
||||
from .exceptions import ChannelNotFoundError, ManifestFetchError
|
||||
from .exceptions import ManifestFetchError
|
||||
from .models import DiscoveryChannel
|
||||
from .playback_manager import DiscoveryPlaybackManager
|
||||
|
||||
@@ -116,7 +116,7 @@ class DiscoveryProvider(StreamingProvider):
|
||||
# ------------------------------------------------------------------
|
||||
# Shared caches (owned here; passed by reference into each manager)
|
||||
# ------------------------------------------------------------------
|
||||
# DiscoveryChannel objects keyed by channel_id
|
||||
# DiscoveryChannel objects keyed by edit_id (the playback identifier)
|
||||
self._channels_cache: Dict[str, DiscoveryChannel] = {}
|
||||
# Navigation routes discovered from /home: { route_id: label }
|
||||
self._cms_routes: Dict[str, str] = {}
|
||||
@@ -236,7 +236,6 @@ class DiscoveryProvider(StreamingProvider):
|
||||
)
|
||||
self.playback_manager = DiscoveryPlaybackManager(
|
||||
provider=self,
|
||||
channels_cache=self._channels_cache,
|
||||
playback_cache=self._playback_cache,
|
||||
)
|
||||
|
||||
@@ -408,70 +407,48 @@ class DiscoveryProvider(StreamingProvider):
|
||||
|
||||
def get_manifest(self, content_id: str, **kwargs) -> Optional[str]:
|
||||
"""
|
||||
Get manifest URL for a channel by ID.
|
||||
Get manifest URL for a channel or event by ID.
|
||||
|
||||
Raises:
|
||||
ChannelNotFoundError: If channel not in cache.
|
||||
``content_id`` is the ``edit_id`` (playback identifier), so it is
|
||||
passed directly to the playback manager — no cache indirection needed.
|
||||
"""
|
||||
try:
|
||||
disco_channel = self._channels_cache.get(content_id)
|
||||
if not disco_channel:
|
||||
raise ChannelNotFoundError(content_id)
|
||||
|
||||
edit_id = disco_channel.edit_id
|
||||
if not edit_id:
|
||||
raise ManifestFetchError(
|
||||
f"No edit_id for channel {content_id}"
|
||||
)
|
||||
|
||||
playback_data = self.playback_manager.get_cached_playback_info(
|
||||
edit_id=edit_id
|
||||
edit_id=content_id
|
||||
)
|
||||
streaming_data = self.playback_manager.extract_streaming_data(
|
||||
playback_data
|
||||
)
|
||||
return streaming_data.get("manifest_url")
|
||||
|
||||
except (ChannelNotFoundError, ManifestFetchError):
|
||||
except ManifestFetchError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting manifest for channel {content_id}: {e}"
|
||||
f"Error getting manifest for {content_id}: {e}"
|
||||
)
|
||||
return None
|
||||
|
||||
def get_drm(self, content_id: str, **kwargs) -> List[DRMConfig]:
|
||||
"""Get all DRM configurations for a channel by ID."""
|
||||
"""
|
||||
Get all DRM configurations for a channel or event by ID.
|
||||
|
||||
``content_id`` is the ``edit_id`` (playback identifier), so it is
|
||||
passed directly to the playback manager — no cache indirection needed.
|
||||
"""
|
||||
try:
|
||||
disco_channel = self._channels_cache.get(content_id)
|
||||
if not disco_channel:
|
||||
logger.warning(
|
||||
f"Channel {content_id} not in cache for DRM"
|
||||
)
|
||||
return []
|
||||
|
||||
edit_id = disco_channel.edit_id
|
||||
if not edit_id:
|
||||
return []
|
||||
|
||||
playback_data = self.playback_manager.get_cached_playback_info(
|
||||
edit_id=edit_id
|
||||
edit_id=content_id
|
||||
)
|
||||
streaming_data = self.playback_manager.extract_streaming_data(
|
||||
playback_data
|
||||
)
|
||||
|
||||
if not streaming_data["license_url"]:
|
||||
return []
|
||||
|
||||
drm_config = self.playback_manager.build_drm_config(
|
||||
streaming_data
|
||||
)
|
||||
drm_config = self.playback_manager.build_drm_config(streaming_data)
|
||||
return [drm_config] if drm_config else []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting DRM configs for channel {content_id}: {e}"
|
||||
f"Error getting DRM configs for {content_id}: {e}"
|
||||
)
|
||||
return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user