Prepare RTLPlus for new streaming prov

This commit is contained in:
Nirvana
2026-04-26 20:55:08 +02:00
parent 996e276784
commit ff9152de1f
3 changed files with 107 additions and 44 deletions
@@ -67,8 +67,8 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
@property
def oauth_redirect_uri(self) -> str:
# Used in the authorize step
return f"{self.config.beta_website}tv-programm"
# Used in authorize step
return "https://beta.plus.rtl.de/tv-programm"
@property
def oauth_token_redirect_uri(self) -> str:
@@ -177,17 +177,26 @@ class RTLPlusAuthenticator(BaseOAuth2Authenticator):
self._oauth_state = fixed_state
return fixed_state
def _build_token_exchange_payload(
self, authorization_code: str, code_verifier: str, state: str = None, **kwargs
) -> Dict[str, Any]:
def _build_token_exchange_payload(self, authorization_code, code_verifier, state=None, **kwargs):
token_redirect = "https://beta.plus.rtl.de/silent-sso-iframe.html"
logger.debug(f"Token exchange redirect_uri: {token_redirect}")
return {
"grant_type": "authorization_code",
"client_id": self.oauth_client_id,
"code": authorization_code,
"redirect_uri": f"{self.config.beta_website}silent-sso-iframe.html",
"redirect_uri": token_redirect,
"code_verifier": code_verifier,
}
def _get_token_exchange_headers(self, **kwargs) -> Dict[str, str]:
headers = self._get_auth_headers()
headers.update({
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "https://beta.plus.rtl.de",
"Referer": "https://beta.plus.rtl.de/",
})
return headers
def _get_client_id(self) -> str:
if self._client_id:
return self._client_id
@@ -12,11 +12,13 @@ Handles all linear TV (live channel) functionality:
from typing import List, Optional, Dict, Any
import json
import urllib.parse
import time
from datetime import date
from ...base.models import Channel, DRMConfig, DRMSystem, LicenseConfig
from ...base.utils.logger import logger
from .constants import RTLPlusDefaults
class RTLPlusChannelManager:
@@ -401,61 +403,107 @@ class RTLPlusChannelManager:
preferred_quality: str = None,
) -> List[DRMConfig]:
"""
Get DRM configuration for a linear TV channel (Widevine only for now).
Get DRM configuration for a linear TV channel.
Supports both Widevine and PlayReady.
"""
# Normalize the channel identifier to SEO name
channel_seo = self._normalize_channel_identifier(channel_id)
assets = self.extract_stream_assets(channel_seo)
quality = preferred_quality or next(iter(self.cfg.preferred_qualities), "hd")
drm_configs = []
# Process DASH (Widevine) - this is the primary DRM
# Process all assets
for asset in assets:
if asset.get("format") == "dashcenc" and asset.get("quality") == quality:
drm_info = asset.get("drm", {})
drm_config = drm_info.get("config", {})
content_id = drm_config.get("contentId")
asset_format = asset.get("format", "")
asset_quality = asset.get("quality", "")
if not content_id:
logger.warning(f"No contentId in DRM config for {channel_seo}")
if asset_quality != quality:
continue
drm_info = asset.get("drm", {})
drm_config = drm_info.get("config", {})
content_id = drm_config.get("contentId")
if not content_id:
continue
try:
uid = self.auth.get_user_id_from_token()
if not uid:
logger.error(f"No user ID available for DRM on {channel_seo}")
continue
try:
uid = self.auth.get_user_id_from_token()
if not uid:
logger.error(f"No user ID available for DRM on {channel_seo}")
continue
# Get upfront token (same for both DRM systems)
upfront_token = self.auth.get_upfront_token(
content_id=content_id,
uid=uid,
)
# Get upfront token - no extra parameters needed
upfront_token = self.auth.get_upfront_token(
content_id=content_id,
uid=uid,
)
# Handle Widevine (DASH CENC)
if asset_format == "dashcenc":
wv_config = self._get_widevine_config(upfront_token)
if wv_config:
drm_configs.append(wv_config)
logger.debug(f"Added Widevine DRM for {channel_seo}")
license_url = self.cfg.drmtoday_license_url
headers = self.cfg.get_drm_license_headers(upfront_token)
# Handle PlayReady (hlsfp or playready format)
elif asset_format in ["hlsfp", "playready"]:
pr_config = self._get_playready_config(upfront_token)
if pr_config:
drm_configs.append(pr_config)
logger.debug(f"Added PlayReady DRM for {channel_seo}")
drm_configs.append(DRMConfig(
system=DRMSystem.WIDEVINE,
priority=3,
license=LicenseConfig(
server_url=license_url,
req_headers=json.dumps(headers),
req_data="{CHA-RAW}",
use_http_get_request=False,
),
))
logger.debug(f"Added Widevine DRM for {channel_seo}")
except Exception as e:
logger.error(f"Failed to get Widevine DRM for {channel_seo}: {e}")
break # Only need one DASH asset
except Exception as e:
logger.error(f"Failed to get DRM for {channel_seo}: {e}")
continue
logger.info(f"Built {len(drm_configs)} DRM configs for {channel_seo}")
return drm_configs
def _get_widevine_config(self, upfront_token: str) -> Optional[DRMConfig]:
"""Get Widevine DRM configuration using existing headers from constants."""
if not upfront_token:
return None
# Use the existing method from RTLPlusConfig
headers = self.cfg.get_drm_license_headers(upfront_token)
# URL-encode headers for req_headers parameter
req_headers = urllib.parse.urlencode(headers)
return DRMConfig(
system=DRMSystem.WIDEVINE,
priority=3,
license=LicenseConfig(
server_url=self.cfg.drmtoday_license_url,
req_headers=req_headers,
req_data="{CHA-RAW}",
use_http_get_request=False,
),
)
def _get_playready_config(self, upfront_token: str) -> Optional[DRMConfig]:
"""Get PlayReady DRM configuration using existing headers from constants."""
if not upfront_token:
return None
# Use the existing method from RTLPlusConfig
headers = self.cfg.get_playready_license_headers(upfront_token)
# URL-encode headers for req_headers parameter
req_headers = urllib.parse.urlencode(headers)
return DRMConfig(
system=DRMSystem.PLAYREADY,
priority=2, # Slightly lower priority than Widevine (fallback)
license=LicenseConfig(
server_url=RTLPlusDefaults.DRMTODAY_PLAYREADY_URL,
req_headers=req_headers,
req_data="{CHA-RAW}", # PlayReady expects raw challenge
use_http_get_request=False,
),
)
# --------------------------------------------------------------------------
# Channel Info Helpers
# --------------------------------------------------------------------------
@@ -59,6 +59,7 @@ class RTLPlusDefaults:
# 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"
# Direct CDN
ORIGIN_CDN_BASE = "https://origin.live.rtlde.bedrock.tech"
@@ -525,4 +526,9 @@ class RTLPlusConfig:
return RTLPlusHeaders.get_playready_drm_headers(
access_token=access_token,
device_id=self.device_id,
)
)
@staticmethod
def get_playready_license_headers(upfront_token: str) -> dict:
"""Get headers for PlayReady license request to DRMToday."""
return RTLPlusHeaders.get_playready_drm_headers(upfront_token)