mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-16 06:02:35 +02:00
Final route consolidation
This commit is contained in:
+15
-96
@@ -19,12 +19,11 @@ helpers:
|
||||
Returns the fetched DRMConfig list (or [] on failure) so callers that
|
||||
also need the configs don't have to fetch them a second time.
|
||||
|
||||
_resolve_stream(content_type, provider, content_id, ...)
|
||||
_resolve_stream_unified(content_type, provider, content_id, ...)
|
||||
The single place that understands how to turn (type, provider, id) into a
|
||||
manifest response — redirect, proxied rewrite, or decrypted rewrite.
|
||||
|
||||
_resolve_decrypted_stream(content_type, provider, content_id, ...)
|
||||
Returns a manifest with server-side decryption (ClearKey keys injected).
|
||||
receiver_side selects who decrypts (client vs. server); every content
|
||||
type's route handler calls this directly with an explicit value.
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -33,7 +32,7 @@ import re
|
||||
import time
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from bottle import HTTPResponse, redirect, request, response
|
||||
from bottle import redirect, request, response
|
||||
from streaming_providers.base.utils import logger
|
||||
from streaming_providers.base.utils.manifest_utils import ManifestUtils
|
||||
|
||||
@@ -252,8 +251,8 @@ def make_helpers(manager, service):
|
||||
|
||||
Returns:
|
||||
The list of DRMConfig objects that were fetched (or [] on failure).
|
||||
Callers that also need the raw configs (e.g. _resolve_stream) can
|
||||
reuse this instead of fetching them again.
|
||||
Callers that also need the raw configs (e.g. _resolve_stream_unified)
|
||||
can reuse this instead of fetching them again.
|
||||
"""
|
||||
try:
|
||||
if is_catchup and content_type == CONTENT_TYPE_CHANNEL:
|
||||
@@ -418,10 +417,11 @@ def make_helpers(manager, service):
|
||||
doesn't support catchup or the window has been exceeded; None if valid.
|
||||
|
||||
Extracted from channels.py's _handle_channel_stream so
|
||||
_resolve_decrypted_stream can apply the same check — previously it
|
||||
skipped this validation entirely, letting catchup requests outside the
|
||||
provider's DVR window through to the decrypted-stream path when the
|
||||
standard path would reject them with a 400.
|
||||
_resolve_stream_unified can apply the same check for every
|
||||
receiver_side value — previously the decrypted-stream path skipped
|
||||
this validation entirely, letting catchup requests outside the
|
||||
provider's DVR window through when the standard path would reject
|
||||
them with a 400.
|
||||
"""
|
||||
channels = manager.get_channels(provider_name=provider, fetch_manifests=False)
|
||||
channel_obj = next((c for c in channels if c.channel_id == content_id), None)
|
||||
@@ -534,8 +534,10 @@ def make_helpers(manager, service):
|
||||
"""
|
||||
Single resolver for every stream endpoint — replaces the former
|
||||
_resolve_stream / _resolve_decrypted_stream split (and the later
|
||||
mode="auto"/"playable" split). Both wrapper names still exist below
|
||||
for existing call sites.
|
||||
mode="auto"/"playable" split before that). Every content type's
|
||||
route handler (channels.py, events.py, vod.py, recordings.py) calls
|
||||
this directly with an explicit receiver_side; the two wrapper
|
||||
functions this replaced have been removed.
|
||||
|
||||
Routing is entirely data-driven: whenever content has ClearKey DRM and
|
||||
a media proxy is configured, the proxy is used to inject correct
|
||||
@@ -857,87 +859,6 @@ def make_helpers(manager, service):
|
||||
)
|
||||
}
|
||||
|
||||
def _resolve_stream(
|
||||
content_type: str,
|
||||
provider: str,
|
||||
content_id: str,
|
||||
country=None,
|
||||
is_catchup: bool = False,
|
||||
start_time: int = None,
|
||||
end_time: int = None,
|
||||
epg_id: str = None,
|
||||
drm_variant: str = "auto",
|
||||
no_proxy: bool = False,
|
||||
):
|
||||
"""
|
||||
Deprecated: thin wrapper around _resolve_stream_unified, kept so
|
||||
existing call sites (channels.py, events.py, vod.py, recordings.py)
|
||||
don't need to change. New code should call _resolve_stream_unified
|
||||
directly with an explicit receiver_side.
|
||||
"""
|
||||
return _resolve_stream_unified(
|
||||
content_type, provider, content_id,
|
||||
country=country,
|
||||
is_catchup=is_catchup,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
epg_id=epg_id,
|
||||
drm_variant=drm_variant,
|
||||
receiver_side=True,
|
||||
no_proxy=no_proxy,
|
||||
)
|
||||
|
||||
def _resolve_decrypted_stream(
|
||||
content_type: str,
|
||||
provider: str,
|
||||
content_id: str,
|
||||
highest_quality_only: bool = False,
|
||||
):
|
||||
"""
|
||||
Deprecated: thin wrapper around _resolve_stream_unified, kept so
|
||||
existing call sites (channels.py's /stream/proxied/ routes) don't need
|
||||
to change. Reads start_time/end_time/epg_id/country from the request
|
||||
query string itself, matching the original function's contract — those
|
||||
routes never passed catchup args explicitly. New code should call
|
||||
_resolve_stream_unified directly with receiver_side=False.
|
||||
"""
|
||||
try:
|
||||
country = request.query.get("country")
|
||||
start_time = request.query.get("start_time")
|
||||
end_time = request.query.get("end_time")
|
||||
epg_id = request.query.get("epg_id")
|
||||
is_catchup = bool(start_time and end_time and content_type == CONTENT_TYPE_CHANNEL)
|
||||
|
||||
if is_catchup:
|
||||
try:
|
||||
start_time = int(start_time)
|
||||
end_time = int(end_time)
|
||||
except (ValueError, TypeError):
|
||||
response.status = 400
|
||||
return {"error": "Invalid start_time or end_time format"}
|
||||
|
||||
return _resolve_stream_unified(
|
||||
content_type, provider, content_id,
|
||||
country=country,
|
||||
is_catchup=is_catchup,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
epg_id=epg_id,
|
||||
receiver_side=False,
|
||||
highest_quality_only=highest_quality_only,
|
||||
)
|
||||
|
||||
except HTTPResponse:
|
||||
raise
|
||||
except ValueError as e:
|
||||
logger.error(f"API Error in decrypted {content_type} stream: {e}")
|
||||
response.status = 404
|
||||
return {"error": str(e)}
|
||||
except Exception as e:
|
||||
logger.error(f"API Error in decrypted {content_type} stream: {e}")
|
||||
response.status = 500
|
||||
return {"error": f"Internal server error: {str(e)}"}
|
||||
|
||||
# Return all helpers as a dict for submodules to use
|
||||
return {
|
||||
"CONTENT_TYPE_CHANNEL": CONTENT_TYPE_CHANNEL,
|
||||
@@ -951,8 +872,6 @@ def make_helpers(manager, service):
|
||||
"_stream_needs_headers": _stream_needs_headers,
|
||||
"_inject_base_url": _inject_base_url,
|
||||
"_validate_catchup_window": _validate_catchup_window,
|
||||
"_resolve_stream": _resolve_stream,
|
||||
"_resolve_decrypted_stream": _resolve_decrypted_stream,
|
||||
"_resolve_stream_unified": _resolve_stream_unified,
|
||||
"_serialize_drm_configs": _serialize_drm_configs,
|
||||
}
|
||||
+41
-54
@@ -836,6 +836,42 @@ class UltimateService:
|
||||
stream_url = f"{base_url}/api/providers/{provider_name}/channels/{channel_id}/{stream_path}"
|
||||
return header + f"{stream_url}\n"
|
||||
|
||||
def _iter_m3u_provider_channels(self, providers_to_process):
|
||||
"""
|
||||
Yield (provider_name, provider_label, channels) for each provider in
|
||||
providers_to_process — sorted channels, with provider_label resolved
|
||||
(falling back to provider_name on lookup failure).
|
||||
|
||||
Extracted from the four _generate_m3u_* generators, which duplicated
|
||||
this exact prefix. Structurally identical to what each of them did
|
||||
inline: channels-fetch and provider_label lookup share one
|
||||
try/except Exception (log-and-skip-this-provider), with the label
|
||||
lookup nested inside on its own narrower
|
||||
(AttributeError, KeyError, ValueError) fallback — same nesting as
|
||||
before, just here once instead of four times. Each caller still
|
||||
wraps its own per-channel body in its own try/except Exception,
|
||||
log-and-continue-to-next-provider, exactly as it did before this
|
||||
was split out — this generator doesn't change where or how
|
||||
failures are caught, only removes the duplicated lookup code ahead
|
||||
of that point.
|
||||
"""
|
||||
for provider_name in providers_to_process:
|
||||
try:
|
||||
channels = self._sort_channels(
|
||||
self.manager.get_channels(provider_name=provider_name, fetch_manifests=False)
|
||||
)
|
||||
try:
|
||||
provider_instance = self.manager.get_provider(provider_name)
|
||||
provider_label = provider_instance.provider_label
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
provider_label = provider_name
|
||||
except Exception as provider_err:
|
||||
logger.warning(
|
||||
f"Failed to process provider '{provider_name}': {str(provider_err)}"
|
||||
)
|
||||
continue
|
||||
yield provider_name, provider_label, channels
|
||||
|
||||
def _generate_m3u_proxied_fast(self, providers=None):
|
||||
"""
|
||||
Fast generation of decrypted M3U content for specified providers.
|
||||
@@ -873,20 +909,8 @@ class UltimateService:
|
||||
|
||||
channels_included = 0
|
||||
|
||||
for provider_name in providers_to_process:
|
||||
for provider_name, provider_label, channels in self._iter_m3u_provider_channels(providers_to_process):
|
||||
try:
|
||||
# Get channels for this provider
|
||||
channels = self._sort_channels(
|
||||
self.manager.get_channels(provider_name=provider_name, fetch_manifests=False)
|
||||
)
|
||||
|
||||
# Get provider label
|
||||
try:
|
||||
provider_instance = self.manager.get_provider(provider_name)
|
||||
provider_label = provider_instance.provider_label
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
provider_label = provider_name
|
||||
|
||||
# Process each channel - no DRM checks (all channels are
|
||||
# already routed through the media proxy at playback time)
|
||||
for channel in channels:
|
||||
@@ -963,20 +987,8 @@ class UltimateService:
|
||||
|
||||
channels_included = 0
|
||||
|
||||
for provider_name in providers_to_process:
|
||||
for provider_name, provider_label, channels in self._iter_m3u_provider_channels(providers_to_process):
|
||||
try:
|
||||
# Get channels for this provider
|
||||
channels = self._sort_channels(
|
||||
self.manager.get_channels(provider_name=provider_name, fetch_manifests=False)
|
||||
)
|
||||
|
||||
# Get provider label
|
||||
try:
|
||||
provider_instance = self.manager.get_provider(provider_name)
|
||||
provider_label = provider_instance.provider_label
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
provider_label = provider_name
|
||||
|
||||
# Process each channel - no DRM checks
|
||||
for channel in channels:
|
||||
channel_id = channel.channel_id
|
||||
@@ -1075,20 +1087,8 @@ class UltimateService:
|
||||
channels_included = 0
|
||||
channels_skipped = 0
|
||||
|
||||
for provider_name in providers_to_process:
|
||||
for provider_name, provider_label, channels in self._iter_m3u_provider_channels(providers_to_process):
|
||||
try:
|
||||
# Get channels for this provider
|
||||
channels = self._sort_channels(
|
||||
self.manager.get_channels(provider_name=provider_name, fetch_manifests=False)
|
||||
)
|
||||
|
||||
# Get provider instance for label
|
||||
try:
|
||||
provider_instance = self.manager.get_provider(provider_name)
|
||||
provider_label = provider_instance.provider_label
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
provider_label = provider_name
|
||||
|
||||
# Process each channel
|
||||
for channel in channels:
|
||||
channel_id = channel.channel_id
|
||||
@@ -1228,21 +1228,8 @@ class UltimateService:
|
||||
# every entry here would silently mismatch its own KODIPROP directives.
|
||||
stream_path = "stream/index.mpd?client_drm=true&no_proxy=true" if no_proxy else "stream/index.mpd?client_drm=true"
|
||||
|
||||
for provider_name in providers_to_process:
|
||||
for provider_name, provider_label, channels in self._iter_m3u_provider_channels(providers_to_process):
|
||||
try:
|
||||
# Get channels for this provider
|
||||
channels = self._sort_channels(
|
||||
self.manager.get_channels(provider_name=provider_name, fetch_manifests=False)
|
||||
)
|
||||
|
||||
# Resolve provider_label once per provider instead of once per
|
||||
# channel (previously done inside _generate_m3u_channel_entry
|
||||
# on every call — same result, redundant lookups).
|
||||
try:
|
||||
provider_label = self.manager.get_provider(provider_name).provider_label
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
provider_label = provider_name
|
||||
|
||||
# Add each channel to M3U
|
||||
for channel in channels:
|
||||
m3u_content += self._generate_m3u_entry(
|
||||
@@ -1253,7 +1240,7 @@ class UltimateService:
|
||||
|
||||
except Exception as provider_err:
|
||||
logger.warning(
|
||||
f"Failed to get channels for provider '{provider_name}': {str(provider_err)}"
|
||||
f"Failed to process provider '{provider_name}': {str(provider_err)}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user