mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-21 00:22:30 +02:00
Prepare RTLPlus for new streaming prov
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# streaming_providers/base/lib_drmtoday.py
|
||||
"""
|
||||
DRMToday License Server Integration
|
||||
|
||||
Shared utilities for providers using DRMToday as their DRM license provider.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from ..base.models.drm import (
|
||||
DRMConfig,
|
||||
DRMSystem,
|
||||
LicenseConfig,
|
||||
LicenseUnwrapperParams,
|
||||
)
|
||||
|
||||
|
||||
class DRMTodayConfig:
|
||||
"""Default DRMToday configuration constants"""
|
||||
|
||||
# Default license server URLs
|
||||
DEFAULT_WIDEVINE_URL = "https://lic.drmtoday.com/license-proxy-widevine/cenc/"
|
||||
DEFAULT_PLAYREADY_URL = "https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx"
|
||||
|
||||
# SOAP action for PlayReady
|
||||
PLAYREADY_SOAP_ACTION = "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"
|
||||
|
||||
# Common unwrapper configuration for DRMToday
|
||||
UNWRAPPER = "json,base64"
|
||||
UNWRAPPER_PARAMS = {"path_data": "license"}
|
||||
|
||||
|
||||
def create_drmtoday_widevine_config(
|
||||
upfront_token: str,
|
||||
origin: str,
|
||||
referer: str,
|
||||
user_agent: str,
|
||||
license_url: Optional[str] = None,
|
||||
priority: int = 3,
|
||||
) -> DRMConfig:
|
||||
"""
|
||||
Create Widevine DRM configuration for DRMToday.
|
||||
|
||||
Args:
|
||||
upfront_token: DRMToday upfront token (x-dt-auth-token)
|
||||
origin: Origin header value (e.g., "https://plus.rtl.de")
|
||||
referer: Referer header value
|
||||
user_agent: User-Agent string
|
||||
license_url: Optional custom license URL (uses default if not provided)
|
||||
priority: DRM priority (default 3)
|
||||
|
||||
Returns:
|
||||
DRMConfig ready for use in ISA
|
||||
"""
|
||||
license_url = license_url or DRMTodayConfig.DEFAULT_WIDEVINE_URL
|
||||
|
||||
headers = {
|
||||
"user-agent": user_agent,
|
||||
"origin": origin,
|
||||
"referer": referer,
|
||||
"x-dt-auth-token": upfront_token,
|
||||
}
|
||||
|
||||
license_config = LicenseConfig(
|
||||
server_url=license_url,
|
||||
req_headers=headers, # LicenseConfig will normalize this to URL-encoded
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
unwrapper=DRMTodayConfig.UNWRAPPER,
|
||||
unwrapper_params=LicenseUnwrapperParams(**DRMTodayConfig.UNWRAPPER_PARAMS),
|
||||
)
|
||||
|
||||
return DRMConfig(
|
||||
system=DRMSystem.WIDEVINE,
|
||||
priority=priority,
|
||||
license=license_config,
|
||||
)
|
||||
|
||||
|
||||
def create_drmtoday_playready_config(
|
||||
upfront_token: str,
|
||||
origin: str,
|
||||
referer: str,
|
||||
user_agent: str,
|
||||
license_url: Optional[str] = None,
|
||||
priority: int = 2,
|
||||
) -> DRMConfig:
|
||||
"""
|
||||
Create PlayReady DRM configuration for DRMToday.
|
||||
|
||||
Args:
|
||||
upfront_token: DRMToday upfront token (x-dt-auth-token)
|
||||
origin: Origin header value (e.g., "https://plus.rtl.de")
|
||||
referer: Referer header value
|
||||
user_agent: User-Agent string (often Edge for PlayReady)
|
||||
license_url: Optional custom license URL (uses default if not provided)
|
||||
priority: DRM priority (default 2)
|
||||
|
||||
Returns:
|
||||
DRMConfig ready for use in ISA
|
||||
"""
|
||||
license_url = license_url or DRMTodayConfig.DEFAULT_PLAYREADY_URL
|
||||
|
||||
headers = {
|
||||
"Content-Type": "text/xml; charset=UTF-8",
|
||||
"SOAPAction": DRMTodayConfig.PLAYREADY_SOAP_ACTION,
|
||||
"User-Agent": user_agent,
|
||||
"origin": origin,
|
||||
"referer": referer,
|
||||
"X-Dt-Auth-Token": upfront_token,
|
||||
}
|
||||
|
||||
license_config = LicenseConfig(
|
||||
server_url=license_url,
|
||||
req_headers=headers,
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
)
|
||||
|
||||
return DRMConfig(
|
||||
system=DRMSystem.PLAYREADY,
|
||||
priority=priority,
|
||||
license=license_config,
|
||||
)
|
||||
|
||||
|
||||
def create_drmtoday_configs(
|
||||
upfront_token: str,
|
||||
origin: str,
|
||||
referer: str,
|
||||
user_agent: str,
|
||||
playready_user_agent: Optional[str] = None,
|
||||
widevine_url: Optional[str] = None,
|
||||
playready_url: Optional[str] = None,
|
||||
widevine_priority: int = 3,
|
||||
playready_priority: int = 2,
|
||||
) -> list[DRMConfig]:
|
||||
"""
|
||||
Create both Widevine and PlayReady DRM configurations for DRMToday.
|
||||
|
||||
This is the most common use case - return both DRM systems so ISA can choose.
|
||||
|
||||
Args:
|
||||
upfront_token: DRMToday upfront token
|
||||
origin: Origin header value
|
||||
referer: Referer header value
|
||||
user_agent: User-Agent for Widevine requests
|
||||
playready_user_agent: User-Agent for PlayReady (uses user_agent if None)
|
||||
widevine_url: Optional custom Widevine license URL
|
||||
playready_url: Optional custom PlayReady license URL
|
||||
widevine_priority: Priority for Widevine (lower = higher priority)
|
||||
playready_priority: Priority for PlayReady
|
||||
|
||||
Returns:
|
||||
List of DRMConfig objects (Widevine then PlayReady)
|
||||
"""
|
||||
drm_configs = []
|
||||
|
||||
# Widevine config
|
||||
wv_config = create_drmtoday_widevine_config(
|
||||
upfront_token=upfront_token,
|
||||
origin=origin,
|
||||
referer=referer,
|
||||
user_agent=user_agent,
|
||||
license_url=widevine_url,
|
||||
priority=widevine_priority,
|
||||
)
|
||||
drm_configs.append(wv_config)
|
||||
|
||||
# PlayReady config (use provided UA or fallback to Widevine UA)
|
||||
pr_user_agent = playready_user_agent or user_agent
|
||||
pr_config = create_drmtoday_playready_config(
|
||||
upfront_token=upfront_token,
|
||||
origin=origin,
|
||||
referer=referer,
|
||||
user_agent=pr_user_agent,
|
||||
license_url=playready_url,
|
||||
priority=playready_priority,
|
||||
)
|
||||
drm_configs.append(pr_config)
|
||||
|
||||
return drm_configs
|
||||
@@ -29,9 +29,6 @@ class RTLPlusDefaults:
|
||||
USER_AGENT = get_user_agent("windows", "chrome")
|
||||
PLAYREADY_USER_AGENT = get_user_agent("windows", "edge")
|
||||
|
||||
# PlayReady SOAP action
|
||||
PLAYREADY_SOAP_ACTION = "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"
|
||||
|
||||
# Supported platform identifiers
|
||||
PLATFORM_WEB = "web"
|
||||
PLATFORM_DEFAULT = PLATFORM_WEB
|
||||
@@ -60,14 +57,6 @@ class RTLPlusDefaults:
|
||||
PROFILES_PATH = "/v2/platforms/m6group_web/users/{user_id}/profiles"
|
||||
VERSION_ENDPOINT = "https://beta.plus.rtl.de/version.json"
|
||||
|
||||
# DRM license server
|
||||
DRMTODAY_LICENSE_URL = "https://lic.drmtoday.com/license-proxy-widevine/cenc/"
|
||||
DRMTODAY_PLAYREADY_URL = "https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx"
|
||||
|
||||
# DRM License Response Parsing
|
||||
DRM_LICENSE_UNWRAPPER = "json,base64"
|
||||
DRM_LICENSE_UNWRAPPER_PARAMS = {"path_data": "license"}
|
||||
|
||||
# DRM Request Headers (common for all DRM types)
|
||||
DRM_COMMON_HEADERS = {
|
||||
"origin": BETA_WEBSITE.rstrip("/"),
|
||||
@@ -394,30 +383,6 @@ class RTLPlusHeaders:
|
||||
})
|
||||
return headers
|
||||
|
||||
@staticmethod
|
||||
def get_drm_license_headers(upfront_token: str, user_agent: str) -> dict:
|
||||
"""Headers for DRM license request to DRMToday."""
|
||||
headers = {
|
||||
"content-type": "application/json",
|
||||
"user-agent": user_agent,
|
||||
"x-dt-auth-token": upfront_token,
|
||||
}
|
||||
# Add common DRM headers
|
||||
headers.update(RTLPlusDefaults.DRM_COMMON_HEADERS)
|
||||
return headers
|
||||
|
||||
@staticmethod
|
||||
def get_playready_license_headers(upfront_token: str, user_agent: str = None) -> dict:
|
||||
"""Get headers for PlayReady license request to DRMToday."""
|
||||
headers = {
|
||||
"Content-Type": "text/xml; charset=UTF-8",
|
||||
"SOAPAction": RTLPlusDefaults.PLAYREADY_SOAP_ACTION,
|
||||
"User-Agent": user_agent or RTLPlusDefaults.PLAYREADY_USER_AGENT,
|
||||
"X-Dt-Auth-Token": upfront_token, # Note: Different case for PlayReady?
|
||||
}
|
||||
headers.update(RTLPlusDefaults.DRM_COMMON_HEADERS)
|
||||
return headers
|
||||
|
||||
@staticmethod
|
||||
def get_drm_headers(access_token: str, device_id: str = None, user_agent: str = None) -> dict:
|
||||
return {
|
||||
@@ -429,19 +394,6 @@ class RTLPlusHeaders:
|
||||
"X-Device-Name": RTLPlusDefaults.DEVICE_NAME,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_playready_drm_headers(access_token: str, device_id: str = None) -> dict:
|
||||
return {
|
||||
"Content-Type": "text/xml; charset=UTF-8",
|
||||
"Origin": RTLPlusDefaults.BASE_WEBSITE.rstrip("/"),
|
||||
"Referer": RTLPlusDefaults.BASE_WEBSITE,
|
||||
"SOAPAction": RTLPlusDefaults.PLAYREADY_SOAP_ACTION,
|
||||
"User-Agent": RTLPlusDefaults.PLAYREADY_USER_AGENT,
|
||||
"X-Auth-Token": access_token,
|
||||
"X-Device-Id": device_id or RTLPlusDefaults.DEVICE_ID,
|
||||
"X-Device-Name": RTLPlusDefaults.PLAYREADY_DEVICE_NAME,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_profiles_headers(
|
||||
oauth_token: str,
|
||||
@@ -497,9 +449,6 @@ class RTLPlusConfig:
|
||||
self.users_endpoint = config.get("users_endpoint", RTLPlusDefaults.USERS_ENDPOINT)
|
||||
self.profiles_path = config.get("profiles_path", RTLPlusDefaults.PROFILES_PATH)
|
||||
|
||||
# DRM license server
|
||||
self.drmtoday_license_url = config.get("drmtoday_license_url", RTLPlusDefaults.DRMTODAY_LICENSE_URL)
|
||||
|
||||
# Quality presets for linear TV
|
||||
self.preferred_qualities = config.get("preferred_qualities", RTLPlusDefaults.LINEAR_TV_PREFERRED_QUALITIES)
|
||||
self.preferred_formats = config.get("preferred_formats", RTLPlusDefaults.LINEAR_TV_PREFERRED_FORMATS)
|
||||
@@ -588,38 +537,6 @@ class RTLPlusConfig:
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
def get_drm_license_headers(self, upfront_token: str) -> dict:
|
||||
return RTLPlusHeaders.get_drm_license_headers(
|
||||
upfront_token=upfront_token,
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
def get_playready_license_headers(self, upfront_token: str) -> dict:
|
||||
return RTLPlusHeaders.get_playready_license_headers(
|
||||
upfront_token=upfront_token,
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_drm_unwrapper_config() -> tuple:
|
||||
"""Get unwrapper configuration for DRM license responses."""
|
||||
return (
|
||||
RTLPlusDefaults.DRM_LICENSE_UNWRAPPER,
|
||||
RTLPlusDefaults.DRM_LICENSE_UNWRAPPER_PARAMS,
|
||||
)
|
||||
def get_drm_headers(self, access_token: str) -> dict:
|
||||
return RTLPlusHeaders.get_drm_headers(
|
||||
access_token=access_token,
|
||||
device_id=self.device_id,
|
||||
user_agent=self.user_agent,
|
||||
)
|
||||
|
||||
def get_playready_drm_headers(self, access_token: str) -> dict:
|
||||
return RTLPlusHeaders.get_playready_drm_headers(
|
||||
access_token=access_token,
|
||||
device_id=self.device_id,
|
||||
)
|
||||
|
||||
def get_layout_url(self, layout_type: str, content_id: str) -> str:
|
||||
"""Get layout URL by type: 'live', 'video', 'folder', 'program', 'block'"""
|
||||
path_map = {
|
||||
|
||||
@@ -3,12 +3,11 @@ import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import ClassVar, Dict, List, Optional, Tuple
|
||||
import urllib.parse
|
||||
|
||||
from ..lib_drmtoday import create_drmtoday_configs
|
||||
|
||||
import requests
|
||||
|
||||
from ...base.models import DRMConfig, DRMSystem, LicenseConfig, StreamingChannel, Event, LicenseUnwrapperParams
|
||||
from ...base.models import DRMConfig, StreamingChannel, Event
|
||||
from ...base.models.proxy_models import ProxyConfig
|
||||
from ...base.provider import StreamingProvider
|
||||
from ...base.utils import logger
|
||||
@@ -484,7 +483,23 @@ class RTLPlusProvider(StreamingProvider):
|
||||
logger.error(f"RTL+ Manifest Unexpected Error: {str(e)}")
|
||||
return None
|
||||
|
||||
# Add to RTLPlusProvider class
|
||||
def _get_drm_vod_or_event(self, content_id: str, **kwargs) -> List[DRMConfig]:
|
||||
"""
|
||||
Get DRM configuration for VOD/event content using layout extraction.
|
||||
"""
|
||||
# Fetch layout for the content
|
||||
layout = self.fetch_layout(
|
||||
layout_type="video",
|
||||
content_id=content_id,
|
||||
location=f"{self.rtl_config.beta_website}{content_id}"
|
||||
)
|
||||
|
||||
if not layout:
|
||||
logger.error(f"Failed to fetch layout for VOD/event content_id: {content_id}")
|
||||
return []
|
||||
|
||||
# Use the common DRM extraction method
|
||||
return self.get_drm_for_content(layout)
|
||||
|
||||
def get_drm_for_content(self, layout_data: Dict) -> List[DRMConfig]:
|
||||
"""
|
||||
@@ -492,12 +507,6 @@ class RTLPlusProvider(StreamingProvider):
|
||||
|
||||
This method works for any layout type (live channel, event folder, VOD)
|
||||
that contains video assets with DRM configuration.
|
||||
|
||||
Args:
|
||||
layout_data: Layout JSON from fetch_layout()
|
||||
|
||||
Returns:
|
||||
List of DRMConfig objects
|
||||
"""
|
||||
assets = self.extract_video_assets(layout_data)
|
||||
|
||||
@@ -562,19 +571,14 @@ class RTLPlusProvider(StreamingProvider):
|
||||
logger.error(f"Failed to get upfront token for {content_id}")
|
||||
return []
|
||||
|
||||
drm_configs = []
|
||||
|
||||
# Build Widevine config
|
||||
wv_config = self._get_widevine_config(upfront_token)
|
||||
if wv_config:
|
||||
drm_configs.append(wv_config)
|
||||
logger.debug("Added Widevine DRM")
|
||||
|
||||
# Build PlayReady config
|
||||
pr_config = self._get_playready_config(upfront_token)
|
||||
if pr_config:
|
||||
drm_configs.append(pr_config)
|
||||
logger.debug("Added PlayReady DRM")
|
||||
# Use the shared DRMToday factory to create both configs
|
||||
drm_configs = create_drmtoday_configs(
|
||||
upfront_token=upfront_token,
|
||||
origin=self.rtl_config.beta_website.rstrip("/"),
|
||||
referer=self.rtl_config.beta_website,
|
||||
user_agent=self.rtl_config.user_agent,
|
||||
playready_user_agent=RTLPlusDefaults.PLAYREADY_USER_AGENT, # Edge for PlayReady
|
||||
)
|
||||
|
||||
logger.info(f"Built {len(drm_configs)} DRM configs (Widevine + PlayReady)")
|
||||
return drm_configs
|
||||
@@ -583,55 +587,13 @@ class RTLPlusProvider(StreamingProvider):
|
||||
logger.error(f"Failed to get DRM: {e}")
|
||||
return []
|
||||
|
||||
def _get_widevine_config(self, upfront_token: str) -> Optional[DRMConfig]:
|
||||
"""Get Widevine DRM configuration."""
|
||||
if not upfront_token:
|
||||
return None
|
||||
|
||||
headers = self.rtl_config.get_drm_license_headers(upfront_token)
|
||||
req_headers = urllib.parse.urlencode(headers)
|
||||
|
||||
license_config = LicenseConfig(
|
||||
server_url=self.rtl_config.drmtoday_license_url,
|
||||
req_headers=req_headers,
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
unwrapper="json,base64",
|
||||
unwrapper_params=LicenseUnwrapperParams(path_data="license"),
|
||||
)
|
||||
|
||||
return DRMConfig(
|
||||
system=DRMSystem.WIDEVINE,
|
||||
priority=3,
|
||||
license=license_config,
|
||||
)
|
||||
|
||||
def _get_playready_config(self, upfront_token: str) -> Optional[DRMConfig]:
|
||||
"""Get PlayReady DRM configuration."""
|
||||
if not upfront_token:
|
||||
return None
|
||||
|
||||
headers = self.rtl_config.get_playready_license_headers(upfront_token)
|
||||
req_headers = urllib.parse.urlencode(headers)
|
||||
|
||||
return DRMConfig(
|
||||
system=DRMSystem.PLAYREADY,
|
||||
priority=2,
|
||||
license=LicenseConfig(
|
||||
server_url=RTLPlusDefaults.DRMTODAY_PLAYREADY_URL,
|
||||
req_headers=req_headers,
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
),
|
||||
)
|
||||
|
||||
def get_drm(self, content_id: str, **kwargs) -> List[DRMConfig]:
|
||||
"""
|
||||
Get DRM configuration for content.
|
||||
|
||||
Supports:
|
||||
- Linear TV channels (via channel_manager)
|
||||
- VOD clips (via manifest extraction)
|
||||
- VOD clips (via layout extraction)
|
||||
- Events (via event_manager)
|
||||
"""
|
||||
# Try linear TV channel first
|
||||
@@ -644,57 +606,16 @@ class RTLPlusProvider(StreamingProvider):
|
||||
if drm_configs:
|
||||
return drm_configs
|
||||
|
||||
# Fall back to VOD/event DRM extraction
|
||||
return self._get_drm_vod_or_event(content_id, **kwargs)
|
||||
|
||||
def _get_drm_vod_or_event(self, content_id: str, **kwargs) -> List[DRMConfig]:
|
||||
"""Original DRM logic for VOD/events."""
|
||||
try:
|
||||
manifest_data = self._fetch_manifest_data(content_id)
|
||||
if manifest_data is None:
|
||||
return []
|
||||
|
||||
drm_configs = []
|
||||
access_token = self.authenticator.get_bearer_token()
|
||||
|
||||
for stream in manifest_data:
|
||||
if stream.get("name") == "dashhd" and "licenses" in stream:
|
||||
licenses = stream.get("licenses", [])
|
||||
|
||||
for license_info in licenses:
|
||||
license_url = license_info.get("uri", {}).get("href")
|
||||
if not license_url:
|
||||
continue
|
||||
|
||||
if license_info.get("type") == "WIDEVINE":
|
||||
drm_configs.append(DRMConfig(
|
||||
system=DRMSystem.WIDEVINE,
|
||||
priority=3,
|
||||
license=LicenseConfig(
|
||||
server_url=license_url,
|
||||
req_headers=json.dumps(self.rtl_config.get_drm_headers(access_token)),
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
),
|
||||
))
|
||||
elif license_info.get("type") == "PLAYREADY":
|
||||
drm_configs.append(DRMConfig(
|
||||
system=DRMSystem.PLAYREADY,
|
||||
priority=2,
|
||||
license=LicenseConfig(
|
||||
server_url=license_url,
|
||||
req_headers=json.dumps(self.rtl_config.get_playready_drm_headers(access_token)),
|
||||
req_data="{CHA-RAW}",
|
||||
use_http_get_request=False,
|
||||
),
|
||||
))
|
||||
break
|
||||
|
||||
# Fall back to VOD DRM extraction
|
||||
# This will also handle VOD clips and any other content types
|
||||
drm_configs = self._get_drm_vod_or_event(content_id, **kwargs)
|
||||
if drm_configs:
|
||||
return drm_configs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching DRM configs for RTL+ content {content_id}: {e}")
|
||||
return []
|
||||
# If we get here, no DRM config could be found for any content type
|
||||
logger.error(
|
||||
f"No DRM configuration found for content_id: {content_id} (not a valid channel, event, or VOD item)")
|
||||
return []
|
||||
|
||||
def _fetch_manifest_data(self, content_id: str) -> Optional[list]:
|
||||
"""Fetch raw manifest data for VOD/events, with cache."""
|
||||
|
||||
Reference in New Issue
Block a user