mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-23 17:42:19 +02:00
Add movetv
This commit is contained in:
@@ -452,7 +452,8 @@ class DRMOperations:
|
||||
if manifest_url:
|
||||
logger.debug(f"Phase 2: PSSH cache miss for {cache_key}, extracting from manifest")
|
||||
pssh_data_list = self._extract_pssh_from_manifest(
|
||||
manifest_url, manifest_headers, provider_name
|
||||
manifest_url, manifest_headers, provider_name,
|
||||
channel_id=channel_id,
|
||||
)
|
||||
if pssh_data_list:
|
||||
self.pssh_cache.set(cache_key, pssh_data_list)
|
||||
@@ -582,7 +583,10 @@ class DRMOperations:
|
||||
logger.debug(f"GENERIC plugin: Using pre-fetched manifest URL for '{channel_id}'")
|
||||
|
||||
if manifest_url:
|
||||
pssh_data_list = self._extract_pssh_from_manifest(manifest_url, manifest_headers, provider_name)
|
||||
pssh_data_list = self._extract_pssh_from_manifest(
|
||||
manifest_url, manifest_headers, provider_name,
|
||||
channel_id=channel_id,
|
||||
)
|
||||
if pssh_data_list:
|
||||
self.pssh_cache.set(cache_key, pssh_data_list)
|
||||
|
||||
@@ -602,7 +606,10 @@ class DRMOperations:
|
||||
logger.error(f"GENERIC plugin: Cannot get manifest URL for init segment extraction")
|
||||
return None, pssh_data_list
|
||||
|
||||
real_pssh = self._extract_pssh_from_manifest(manifest_url, manifest_headers, provider_name)
|
||||
real_pssh = self._extract_pssh_from_manifest(
|
||||
manifest_url, manifest_headers, provider_name,
|
||||
channel_id=channel_id,
|
||||
)
|
||||
|
||||
if real_pssh and not self._has_stub_pssh(real_pssh):
|
||||
logger.info(
|
||||
@@ -666,8 +673,26 @@ class DRMOperations:
|
||||
}
|
||||
return bool(config_systems & plugin_systems)
|
||||
|
||||
def _extract_pssh_from_manifest(self, manifest_url: str, manifest_headers: dict[str, str], provider_name: Optional[str] = None) -> List:
|
||||
"""Extract PSSH data from manifest using the provider's HTTPManager."""
|
||||
def _extract_pssh_from_manifest(
|
||||
self,
|
||||
manifest_url: str,
|
||||
manifest_headers: Dict[str, str],
|
||||
provider_name: Optional[str] = None,
|
||||
channel_id: Optional[str] = None,
|
||||
) -> List:
|
||||
"""
|
||||
Extract PSSH data from manifest, falling back to the init segment if
|
||||
the manifest itself carries only stub ContentProtection entries.
|
||||
|
||||
Args:
|
||||
manifest_url: URL of the manifest to fetch.
|
||||
manifest_headers: HTTP headers to use when fetching the manifest.
|
||||
provider_name: Used to resolve the provider's HTTPManager and
|
||||
segment headers. If None, a plain HTTPManager is used.
|
||||
channel_id: Passed to provider.get_segment_headers() so providers
|
||||
that require per-channel auth on segments supply the
|
||||
correct headers. If None, segment headers default to {}.
|
||||
"""
|
||||
from .utils.drm_extractor import DRMExtractor
|
||||
from .network import HTTPManager
|
||||
|
||||
@@ -677,19 +702,28 @@ class DRMOperations:
|
||||
return []
|
||||
|
||||
try:
|
||||
# 2. Resolve the correct HTTP manager
|
||||
# 2. Resolve the correct HTTP manager and segment headers
|
||||
http = None
|
||||
segment_headers = {}
|
||||
|
||||
if provider_name:
|
||||
provider = self.registry.get_provider(provider_name)
|
||||
if provider:
|
||||
http = provider.http_manager
|
||||
logger.debug(f"Using configured HTTPManager for provider: {provider_name}")
|
||||
|
||||
if channel_id:
|
||||
segment_headers = provider.get_segment_headers(channel_id)
|
||||
logger.debug(
|
||||
f"Resolved segment headers for '{provider_name}/{channel_id}': "
|
||||
f"{list(segment_headers.keys())}"
|
||||
)
|
||||
|
||||
if not http:
|
||||
logger.debug("No provider manager found; using default HTTPManager")
|
||||
http = HTTPManager()
|
||||
|
||||
# 3. Perform the request
|
||||
# 3. Perform the manifest request
|
||||
response = http.get(manifest_url, headers=manifest_headers, timeout=10, operation="api")
|
||||
response.raise_for_status()
|
||||
manifest_content = response.text
|
||||
@@ -712,7 +746,8 @@ class DRMOperations:
|
||||
if init_segment_url:
|
||||
segment_pssh = DRMExtractor._extract_from_single_segment(
|
||||
init_segment_url,
|
||||
[p.system_id for p in pssh_list] if pssh_list else []
|
||||
[p.system_id for p in pssh_list] if pssh_list else [],
|
||||
headers=segment_headers,
|
||||
)
|
||||
|
||||
if segment_pssh:
|
||||
|
||||
@@ -6,7 +6,7 @@ Separated from general manifest parsing to maintain clear separation of concerns
|
||||
|
||||
import base64
|
||||
import re
|
||||
from typing import List
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ..models.drm import PSSHData
|
||||
from .logger import logger
|
||||
@@ -43,6 +43,8 @@ class DRMExtractor:
|
||||
if fallback_to_segments and segment_urls:
|
||||
incomplete_pssh = [p for p in pssh_list if not p.pssh_box or not p.key_ids]
|
||||
if incomplete_pssh:
|
||||
# No headers available in this deprecated path — callers that need
|
||||
# auth on segment requests should use _extract_from_single_segment directly.
|
||||
segment_pssh = DRMExtractor._extract_from_single_segment(
|
||||
segment_urls[0], [p.system_id for p in incomplete_pssh]
|
||||
)
|
||||
@@ -92,13 +94,27 @@ class DRMExtractor:
|
||||
@staticmethod
|
||||
def _extract_from_single_segment(
|
||||
segment_url: str,
|
||||
expected_system_ids: List[str] = None
|
||||
expected_system_ids: List[str] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> List[PSSHData]:
|
||||
"""Extract PSSH from a single segment URL."""
|
||||
"""
|
||||
Extract PSSH from a single segment URL.
|
||||
|
||||
Args:
|
||||
segment_url: URL of the init segment to fetch
|
||||
expected_system_ids: If provided, filter results to these DRM system IDs.
|
||||
Falls back to returning all PSSH if no matches found.
|
||||
headers: HTTP headers to use when fetching the segment (e.g. Authorization).
|
||||
Providers that require auth on segment requests should supply these
|
||||
via StreamingProvider.get_segment_headers().
|
||||
"""
|
||||
from .mp4_pssh_extractor import MP4PSSHExtractor
|
||||
|
||||
try:
|
||||
pssh_from_segment = MP4PSSHExtractor.extract_from_url(segment_url)
|
||||
pssh_from_segment = MP4PSSHExtractor.extract_from_url(
|
||||
segment_url,
|
||||
headers=headers or {},
|
||||
)
|
||||
|
||||
if expected_system_ids:
|
||||
# Normalize expected IDs using the model
|
||||
|
||||
@@ -41,6 +41,7 @@ class MPDRewriter:
|
||||
channel: Optional[str] = None,
|
||||
blocklist_path: str = "representation_blocklist.json",
|
||||
clearkey_receiver_side: bool = False,
|
||||
segment_headers: Optional[Dict[str, str]] = None,
|
||||
):
|
||||
self.media_proxy_url = media_proxy_url.rstrip("/")
|
||||
self.provider_proxy_url = provider_proxy_url
|
||||
@@ -61,6 +62,12 @@ class MPDRewriter:
|
||||
self._static_params = {}
|
||||
if self.provider_proxy_url:
|
||||
self._static_params["proxy"] = self.provider_proxy_url
|
||||
if segment_headers:
|
||||
import json
|
||||
encoded = base64.urlsafe_b64encode(
|
||||
json.dumps(segment_headers).encode()
|
||||
).decode().rstrip("=")
|
||||
self._static_params["headers"] = encoded
|
||||
|
||||
@staticmethod
|
||||
def encode_url(url: str) -> str:
|
||||
|
||||
+161
-215
@@ -332,6 +332,58 @@ class UltimateService:
|
||||
# Fallback to environment manager config
|
||||
return self.env_manager.get_config(setting_id, default)
|
||||
|
||||
def _fetch_manifest_for_rewriter(
|
||||
self,
|
||||
provider: str,
|
||||
channel_id: str,
|
||||
manifest_url: str,
|
||||
) -> tuple:
|
||||
"""
|
||||
Fetch a manifest and collect everything MPDRewriter needs.
|
||||
|
||||
Returns:
|
||||
(manifest_text, ttl, provider_proxy_url, segment_headers)
|
||||
|
||||
Raises:
|
||||
ValueError: if the provider HTTP manager is not available
|
||||
requests.HTTPError: if the manifest fetch fails
|
||||
"""
|
||||
http_manager = self.manager.get_provider_http_manager(provider)
|
||||
if not http_manager:
|
||||
raise ValueError(f'Provider "{provider}" not configured properly (no HTTP manager)')
|
||||
|
||||
# Resolve manifest headers from the provider so auth tokens etc. are sent
|
||||
provider_instance = self.manager.get_provider(provider)
|
||||
manifest_headers = (
|
||||
provider_instance.get_manifest_headers(channel_id)
|
||||
if provider_instance else {}
|
||||
)
|
||||
segment_headers = (
|
||||
provider_instance.get_segment_headers(channel_id)
|
||||
if provider_instance else {}
|
||||
)
|
||||
|
||||
manifest_response = http_manager.get(
|
||||
manifest_url, headers=manifest_headers, operation="manifest"
|
||||
)
|
||||
manifest_response.raise_for_status()
|
||||
|
||||
# Cache TTL: prefer HTTP Cache-Control/Expires, fall back to MPD minimumUpdatePeriod
|
||||
ttl = MPDRewriter.extract_cache_ttl(manifest_response.headers)
|
||||
mpd_ttl = MPDRewriter.extract_mpd_update_period(manifest_response.text)
|
||||
if mpd_ttl and mpd_ttl < ttl:
|
||||
ttl = mpd_ttl
|
||||
|
||||
# Derive provider-side proxy URL for the media proxy to use when forwarding
|
||||
provider_proxy_url = None
|
||||
if http_manager.config.proxy_config:
|
||||
proxy_cfg = http_manager.config.proxy_config
|
||||
provider_proxy_url = (
|
||||
f"{proxy_cfg.proxy_type.value.lower()}://{proxy_cfg.host}:{proxy_cfg.port}"
|
||||
)
|
||||
|
||||
return manifest_response.text, ttl, provider_proxy_url, segment_headers
|
||||
|
||||
def get_decrypted_manifest(
|
||||
self, provider: str, channel_id: str, keyids: dict,
|
||||
highest_quality_only: bool = False, receiver_side: bool = False
|
||||
@@ -345,6 +397,7 @@ class UltimateService:
|
||||
channel_id: Channel ID
|
||||
keyids: Dictionary of kid:key pairs
|
||||
highest_quality_only: If True, keep only highest quality video representation
|
||||
receiver_side: If True, inject ClearKey signaling for receiver-side decryption
|
||||
|
||||
Returns:
|
||||
Rewritten MPD content as string
|
||||
@@ -354,48 +407,24 @@ class UltimateService:
|
||||
# Note: We don't cache decrypted manifests as they contain keys
|
||||
logger.info(
|
||||
f"Generating {'receiver-side clearkey' if receiver_side else 'decrypted'} manifest "
|
||||
f"for {provider}/{channel_id} (highest_quality_only={highest_quality_only})")
|
||||
f"for {provider}/{channel_id} (highest_quality_only={highest_quality_only})"
|
||||
)
|
||||
|
||||
# Get original manifest URL
|
||||
manifest_url = self.manager.get_channel_manifest(
|
||||
provider_name=provider, channel_id=channel_id, country=country
|
||||
)
|
||||
|
||||
if not manifest_url:
|
||||
response.status = 404
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{
|
||||
"error": f'Manifest not available for channel "{channel_id}" from provider "{provider}"'
|
||||
}
|
||||
{"error": f'Manifest not available for channel "{channel_id}" from provider "{provider}"'}
|
||||
)
|
||||
|
||||
# Get provider's HTTP manager
|
||||
http_manager = self.manager.get_provider_http_manager(provider)
|
||||
if not http_manager:
|
||||
logger.error(f"No HTTP manager found for provider '{provider}'")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{"error": f'Provider "{provider}" not configured properly'}
|
||||
)
|
||||
|
||||
# Fetch manifest
|
||||
try:
|
||||
logger.debug(f"Fetching manifest for decryption: {manifest_url}")
|
||||
manifest_response = http_manager.get(manifest_url, operation="manifest")
|
||||
manifest_text, _ttl, provider_proxy_url, segment_headers = self._fetch_manifest_for_rewriter(
|
||||
provider, channel_id, manifest_url
|
||||
)
|
||||
|
||||
# Get provider proxy URL if configured
|
||||
provider_proxy_url = None
|
||||
if http_manager.config.proxy_config:
|
||||
proxy_cfg = http_manager.config.proxy_config
|
||||
provider_proxy_url = (
|
||||
f"{proxy_cfg.proxy_type.value.lower()}://{proxy_cfg.host}:{proxy_cfg.port}"
|
||||
)
|
||||
logger.debug(f"Provider has proxy configured: {provider_proxy_url}")
|
||||
|
||||
# Rewrite MPD URLs to point to media proxy decrypt endpoint with keys
|
||||
# CHANGED: Added provider and channel_id parameters for blocklist filtering
|
||||
rewriter = MPDRewriter(
|
||||
self.media_proxy_url,
|
||||
provider_proxy_url,
|
||||
@@ -404,127 +433,153 @@ class UltimateService:
|
||||
provider=provider,
|
||||
channel=channel_id,
|
||||
clearkey_receiver_side=receiver_side,
|
||||
segment_headers=segment_headers,
|
||||
)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_response.text, manifest_url)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_text, manifest_url)
|
||||
|
||||
# Return rewritten MPD
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return rewritten_mpd
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(str(e))
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": str(e)})
|
||||
except Exception as fetch_err:
|
||||
logger.error(f"Failed to fetch manifest for decryption: {fetch_err}")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": f"Failed to fetch manifest: {str(fetch_err)}"})
|
||||
|
||||
def get_proxied_catchup_manifest(
|
||||
self,
|
||||
provider: str,
|
||||
channel_id: str,
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
epg_id: str = None,
|
||||
country: str = None,
|
||||
) -> str:
|
||||
"""
|
||||
Get proxied and rewritten MPD manifest for catchup content using media proxy.
|
||||
Similar to get_proxied_manifest but for catchup streams.
|
||||
"""
|
||||
cache_key = f"{channel_id}_catchup_{start_time}_{end_time}"
|
||||
|
||||
cached_mpd = self.mpd_cache.get(provider, cache_key)
|
||||
if cached_mpd:
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return cached_mpd
|
||||
|
||||
logger.info(f"Cache miss for catchup {provider}/{channel_id}, fetching manifest")
|
||||
|
||||
if not self.media_proxy_url:
|
||||
response.status = 503
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": "Media proxy not configured (MEDIA_PROXY_URL not set)"})
|
||||
|
||||
manifest_url = self.manager.get_catchup_manifest(
|
||||
provider_name=provider,
|
||||
channel_id=channel_id,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
epg_id=epg_id,
|
||||
country=country,
|
||||
)
|
||||
if not manifest_url:
|
||||
response.status = 404
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": "Catchup manifest not available"})
|
||||
|
||||
try:
|
||||
manifest_text, ttl, provider_proxy_url, segment_headers = self._fetch_manifest_for_rewriter(
|
||||
provider, channel_id, manifest_url
|
||||
)
|
||||
|
||||
rewriter = MPDRewriter(
|
||||
self.media_proxy_url,
|
||||
provider_proxy_url,
|
||||
None, # No keyids for catchup streams
|
||||
False, # highest_quality_only — not needed for catchup
|
||||
provider=provider,
|
||||
channel=channel_id,
|
||||
segment_headers=segment_headers,
|
||||
)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_text, manifest_url)
|
||||
|
||||
self.mpd_cache.set(
|
||||
provider=provider,
|
||||
channel_id=cache_key,
|
||||
mpd_content=rewritten_mpd,
|
||||
ttl=ttl,
|
||||
original_url=manifest_url,
|
||||
)
|
||||
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return rewritten_mpd
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(str(e))
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": str(e)})
|
||||
except Exception as fetch_err:
|
||||
logger.error(f"Failed to fetch catchup manifest: {fetch_err}")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": f"Failed to fetch manifest: {str(fetch_err)}"})
|
||||
|
||||
def get_proxied_manifest(self, provider: str, channel_id: str, highest_quality_only: bool = False) -> str:
|
||||
"""
|
||||
Get proxied and rewritten MPD manifest for a channel using media proxy.
|
||||
Uses cache when available and valid.
|
||||
|
||||
Args:
|
||||
provider: Provider name
|
||||
channel_id: Channel ID
|
||||
highest_quality_only: If True, keep only highest quality video representation
|
||||
|
||||
Returns:
|
||||
Rewritten MPD content as string
|
||||
"""
|
||||
country = request.query.get("country")
|
||||
|
||||
# Try cache first (only if not using highest_quality_only, as that changes output)
|
||||
if not highest_quality_only:
|
||||
cached_mpd = self.mpd_cache.get(provider, channel_id)
|
||||
if cached_mpd:
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return cached_mpd
|
||||
|
||||
# Cache miss or highest_quality_only enabled - fetch and rewrite
|
||||
logger.info(
|
||||
f"Cache miss for {provider}/{channel_id}, fetching manifest (highest_quality_only={highest_quality_only})")
|
||||
|
||||
# Check if media proxy is configured
|
||||
if not self.media_proxy_url:
|
||||
response.status = 503
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{"error": "Media proxy not configured (MEDIA_PROXY_URL not set)"}
|
||||
)
|
||||
return json.dumps({"error": "Media proxy not configured (MEDIA_PROXY_URL not set)"})
|
||||
|
||||
# Get original manifest URL
|
||||
manifest_url = self.manager.get_channel_manifest(
|
||||
provider_name=provider, channel_id=channel_id, country=country
|
||||
)
|
||||
|
||||
if not manifest_url:
|
||||
response.status = 404
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{
|
||||
"error": f'Manifest not available for channel "{channel_id}" from provider "{provider}"'
|
||||
}
|
||||
)
|
||||
{"error": f'Manifest not available for channel "{channel_id}" from provider "{provider}"'})
|
||||
|
||||
# Get provider's HTTP manager to fetch manifest and check proxy config
|
||||
http_manager = self.manager.get_provider_http_manager(provider)
|
||||
if not http_manager:
|
||||
logger.error(f"No HTTP manager found for provider '{provider}'")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{"error": f'Provider "{provider}" not configured properly'}
|
||||
)
|
||||
|
||||
# Fetch manifest via provider's HTTP manager
|
||||
try:
|
||||
logger.debug(f"Fetching manifest: {manifest_url}")
|
||||
manifest_response = http_manager.get(manifest_url, operation="manifest")
|
||||
manifest_text, ttl, provider_proxy_url, segment_headers = self._fetch_manifest_for_rewriter(
|
||||
provider, channel_id, manifest_url
|
||||
)
|
||||
|
||||
# Extract cache TTL from response headers
|
||||
ttl = MPDRewriter.extract_cache_ttl(manifest_response.headers)
|
||||
|
||||
# Also check MPD's own update period as fallback
|
||||
mpd_ttl = MPDRewriter.extract_mpd_update_period(manifest_response.text)
|
||||
if mpd_ttl and mpd_ttl < ttl:
|
||||
ttl = mpd_ttl
|
||||
logger.debug(f"Using MPD minimumUpdatePeriod as TTL: {ttl}s")
|
||||
|
||||
# Get provider proxy URL if configured
|
||||
provider_proxy_url = None
|
||||
if http_manager.config.proxy_config:
|
||||
# Build proxy URL from config
|
||||
proxy_cfg = http_manager.config.proxy_config
|
||||
provider_proxy_url = (
|
||||
f"{proxy_cfg.proxy_type.value.lower()}://{proxy_cfg.host}:{proxy_cfg.port}"
|
||||
)
|
||||
logger.debug(f"Provider has proxy configured: {provider_proxy_url}")
|
||||
|
||||
# Rewrite MPD URLs to point to media proxy
|
||||
# CHANGED: Added provider and channel_id parameters for blocklist filtering
|
||||
rewriter = MPDRewriter(
|
||||
self.media_proxy_url,
|
||||
provider_proxy_url,
|
||||
None, # No keyids for proxied (unencrypted) streams
|
||||
None,
|
||||
highest_quality_only,
|
||||
provider=provider, # NEW: Enable blocklist filtering
|
||||
channel=channel_id # NEW: Enable blocklist filtering
|
||||
provider=provider,
|
||||
channel=channel_id,
|
||||
segment_headers=segment_headers,
|
||||
)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_response.text, manifest_url)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_text, manifest_url)
|
||||
|
||||
# Cache the rewritten MPD (only if not using highest_quality_only)
|
||||
if not highest_quality_only:
|
||||
self.mpd_cache.set(
|
||||
provider=provider,
|
||||
channel_id=channel_id,
|
||||
mpd_content=rewritten_mpd,
|
||||
ttl=ttl,
|
||||
original_url=manifest_url,
|
||||
)
|
||||
self.mpd_cache.set(provider=provider, channel_id=channel_id,
|
||||
mpd_content=rewritten_mpd, ttl=ttl, original_url=manifest_url)
|
||||
|
||||
# Return rewritten MPD
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return rewritten_mpd
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(str(e))
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": str(e)})
|
||||
except Exception as fetch_err:
|
||||
logger.error(f"Failed to fetch manifest: {fetch_err}")
|
||||
response.status = 502
|
||||
@@ -1497,115 +1552,6 @@ class UltimateService:
|
||||
|
||||
return m3u_content
|
||||
|
||||
def get_proxied_catchup_manifest(
|
||||
self,
|
||||
provider: str,
|
||||
channel_id: str,
|
||||
start_time: int,
|
||||
end_time: int,
|
||||
epg_id: str = None,
|
||||
country: str = None,
|
||||
) -> str:
|
||||
"""
|
||||
Get proxied and rewritten MPD manifest for catchup content using media proxy.
|
||||
Similar to get_proxied_manifest but for catchup streams.
|
||||
"""
|
||||
# Generate cache key that includes time parameters
|
||||
cache_key = f"{channel_id}_catchup_{start_time}_{end_time}"
|
||||
|
||||
# Try cache first (with catchup-specific key)
|
||||
cached_mpd = self.mpd_cache.get(provider, cache_key)
|
||||
if cached_mpd:
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return cached_mpd
|
||||
|
||||
logger.info(
|
||||
f"Cache miss for catchup {provider}/{channel_id}, fetching manifest"
|
||||
)
|
||||
|
||||
# Check if media proxy is configured
|
||||
if not self.media_proxy_url:
|
||||
response.status = 503
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{"error": "Media proxy not configured (MEDIA_PROXY_URL not set)"}
|
||||
)
|
||||
|
||||
# Get catchup manifest URL
|
||||
manifest_url = self.manager.get_catchup_manifest(
|
||||
provider_name=provider,
|
||||
channel_id=channel_id,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
epg_id=epg_id,
|
||||
country=country,
|
||||
)
|
||||
|
||||
if not manifest_url:
|
||||
response.status = 404
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": f"Catchup manifest not available"})
|
||||
|
||||
# Get provider's HTTP manager
|
||||
http_manager = self.manager.get_provider_http_manager(provider)
|
||||
if not http_manager:
|
||||
logger.error(f"No HTTP manager found for provider '{provider}'")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps(
|
||||
{"error": f'Provider "{provider}" not configured properly'}
|
||||
)
|
||||
|
||||
# Fetch manifest
|
||||
try:
|
||||
logger.debug(f"Fetching catchup manifest: {manifest_url}")
|
||||
manifest_response = http_manager.get(manifest_url, operation="manifest")
|
||||
|
||||
# Extract cache TTL
|
||||
ttl = MPDRewriter.extract_cache_ttl(manifest_response.headers)
|
||||
mpd_ttl = MPDRewriter.extract_mpd_update_period(manifest_response.text)
|
||||
if mpd_ttl and mpd_ttl < ttl:
|
||||
ttl = mpd_ttl
|
||||
|
||||
# Get provider proxy URL if configured
|
||||
provider_proxy_url = None
|
||||
if http_manager.config.proxy_config:
|
||||
proxy_cfg = http_manager.config.proxy_config
|
||||
provider_proxy_url = (
|
||||
f"{proxy_cfg.proxy_type.value.lower()}://{proxy_cfg.host}:{proxy_cfg.port}"
|
||||
)
|
||||
logger.debug(f"Provider has proxy configured: {provider_proxy_url}")
|
||||
|
||||
# Rewrite MPD URLs to point to media proxy
|
||||
# CHANGED: Added provider and channel_id parameters for blocklist filtering
|
||||
rewriter = MPDRewriter(
|
||||
self.media_proxy_url,
|
||||
provider_proxy_url,
|
||||
None, # No keyids for catchup streams
|
||||
False, # highest_quality_only - usually not needed for catchup
|
||||
provider=provider, # NEW: Enable blocklist filtering
|
||||
channel=channel_id # NEW: Enable blocklist filtering
|
||||
)
|
||||
rewritten_mpd = rewriter.rewrite_mpd(manifest_response.text, manifest_url)
|
||||
|
||||
# Cache the rewritten MPD with catchup-specific key
|
||||
self.mpd_cache.set(
|
||||
provider=provider,
|
||||
channel_id=cache_key, # Use catchup-specific cache key
|
||||
mpd_content=rewritten_mpd,
|
||||
ttl=ttl,
|
||||
original_url=manifest_url,
|
||||
)
|
||||
|
||||
response.content_type = "application/dash+xml; charset=utf-8"
|
||||
return rewritten_mpd
|
||||
|
||||
except Exception as fetch_err:
|
||||
logger.error(f"Failed to fetch catchup manifest: {fetch_err}")
|
||||
response.status = 502
|
||||
response.content_type = "application/json"
|
||||
return json.dumps({"error": f"Failed to fetch manifest: {str(fetch_err)}"})
|
||||
|
||||
@staticmethod
|
||||
def get_settings_manager():
|
||||
"""Simple helper to get SettingsManager"""
|
||||
|
||||
Reference in New Issue
Block a user