This commit is contained in:
Nirvana
2025-11-29 20:27:42 +01:00
parent c41acc8c0f
commit a2e3dede28
3 changed files with 141 additions and 27 deletions
+64 -7
View File
@@ -464,22 +464,39 @@ class HRTiAuthenticator(BaseAuthenticator):
self._user_id = self._user_id or ''
def authorize_session(self, content_type: str, content_ref_id: str,
channel_id: str = None, **kwargs) -> Optional[Dict[str, Any]]:
content_drm_id: str = None,
video_store_ids: list = None,
channel_id: str = None,
start_time: str = None,
end_time: str = None) -> Optional[Dict[str, Any]]:
"""Authorize a playback session"""
try:
bearer_token = self._current_token.access_token if self._current_token else ''
headers = self._get_api_headers(bearer_token)
# Set referer based on content type
if channel_id is None:
headers['referer'] = f'{self.config.base_website}/videostore'
else:
if content_type == "tlive":
headers['referer'] = f'{self.config.base_website}/live/tv?channel={channel_id}'
elif content_type == "rlive":
headers['referer'] = f'{self.config.base_website}/live/radio'
else:
headers['referer'] = f'{self.config.base_website}/live/'
payload = {
"ContentType": content_type,
"ContentReferenceId": content_ref_id,
"ContentDrmId": f"{content_ref_id}_drm",
"VideostoreReferenceIds": kwargs.get('video_store_ids', []),
"ContentDrmId": content_drm_id or f"{content_ref_id}_drm",
"VideostoreReferenceIds": video_store_ids,
"ChannelReferenceId": channel_id,
"StartTime": kwargs.get('start_time'),
"EndTime": kwargs.get('end_time')
"StartTime": start_time,
"EndTime": end_time
}
logger.debug(f"Authorizing session - type: {content_type}, ref: {content_ref_id}, drm: {content_drm_id}")
response = self.http_manager.post(
self.config.api_endpoints['authorize_session'],
operation='api',
@@ -490,7 +507,9 @@ class HRTiAuthenticator(BaseAuthenticator):
result = response.json()
if 'Result' in result:
logger.debug("HRTi session authorization successful")
authorized = result['Result'].get('Authorized', False)
session_id = result['Result'].get('SessionId') or result['Result'].get('DrmId')
logger.debug(f"Session authorization result - authorized: {authorized}, session_id: {session_id}")
return result['Result']
else:
logger.warning("No result in session authorization response")
@@ -500,8 +519,40 @@ class HRTiAuthenticator(BaseAuthenticator):
logger.error(f"HRTi session authorization failed: {e}")
return None
def report_session_event(self, session_id: str, channel_id: str = None) -> bool:
"""Report session event (like play start)"""
try:
bearer_token = self._current_token.access_token if self._current_token else ''
headers = self._get_api_headers(bearer_token)
# Set referer based on channel
if channel_id is None:
headers['referer'] = f'{self.config.base_website}/videostore'
else:
headers['referer'] = f'{self.config.base_website}/live/tv?channel={channel_id}'
payload = {
"SessionEventId": 1, # 1 = play start
"SessionId": session_id
}
response = self.http_manager.post(
self.config.api_endpoints['report_session'],
operation='api',
headers=headers,
data=json.dumps(payload)
)
response.raise_for_status()
logger.debug(f"Session event reported for session {session_id}")
return True
except Exception as e:
logger.warning(f"Session event reporting failed: {e}")
return False
def get_license_data(self, session_id: str) -> str:
"""Generate license data for DRM"""
"""Generate license data for DRM - returns base64 encoded string"""
try:
drm_license = {
'userId': self._user_id or '',
@@ -509,8 +560,14 @@ class HRTiAuthenticator(BaseAuthenticator):
'merchant': self.config.merchant
}
logger.debug(
f"Creating license data - userId: {self._user_id}, sessionId: {session_id}, merchant: {self.config.merchant}")
# Encode to JSON then to base64
license_bytes = json.dumps(drm_license).encode('utf-8')
license_b64 = base64.b64encode(license_bytes).decode('utf-8')
logger.debug(f"License data (base64, first 30 chars): {license_b64[:30]}...")
return license_b64
except Exception as e:
@@ -29,6 +29,7 @@ class HRTiDefaults:
'channels': f'{BASE_URL}/api/api/ott/GetChannels',
'programme': f'{BASE_URL}/api/api/ott/GetProgramme',
'authorize_session': f'{BASE_URL}/api/api/ott/AuthorizeSession',
'report_session': f'{BASE_URL}/api/api/ott/ReportSessionEvent',
'register_device': f'{HSAPI_BASE_URL}/RegisterDevice',
'content_ratings': f'{HSAPI_BASE_URL}/ContentRatingsGet',
'profiles': f'{HSAPI_BASE_URL}/ProfilesGet'
@@ -217,34 +217,90 @@ class HRTiProvider(StreamingProvider):
def enrich_channel_data(self, channel: StreamingChannel, **kwargs) -> Optional[StreamingChannel]:
"""
Enrich channel with manifest URL and additional data
Enrich channel with manifest URL and DRM configuration
"""
try:
# For HRTi, we need to authorize a session to get the manifest
manifest_url = self.get_manifest(channel.channel_id, **kwargs)
logger.debug(f"Enriching channel: {channel.name} ({channel.channel_id})")
# For live channels, we need to authorize a session first
content_type = "rlive" if channel.content_type == "AUDIO" else "tlive"
# Parse the streaming URL to get content DRM ID
from urllib.parse import urlparse
parts = urlparse(channel.manifest_script)
path_parts = parts.path.strip('/').split('/')
# Content DRM ID format: directory1_directory2
# Example: /cdn1oiv/hrtliveorigin/... -> cdn1oiv_hrtliveorigin
content_drm_id = None
if len(path_parts) >= 2:
content_drm_id = f"{path_parts[0]}_{path_parts[1]}"
logger.debug(f"Content DRM ID for {channel.name}: {content_drm_id}")
# Authorize session
session_data = self.auth.authorize_session(
content_type=content_type,
content_ref_id=channel.channel_id,
content_drm_id=content_drm_id,
video_store_ids=None,
channel_id=channel.channel_id,
start_time=None,
end_time=None
)
if not session_data:
logger.warning(f"Failed to authorize session for channel {channel.name}")
return channel
# Check if authorized
if not session_data.get('Authorized', False):
logger.warning(f"Session not authorized for channel {channel.name}")
return channel
logger.debug(f"Session authorized for {channel.name}")
# Report session event (play start)
session_id = session_data.get('SessionId') or session_data.get('DrmId')
if session_id:
self.auth.report_session_event(session_id, channel.channel_id)
# Set the manifest URL - use the streaming URL from channel data
manifest_url = channel.manifest_script
if manifest_url:
# HRTi manifests are dynamic and session-based
channel.set_dynamic_manifest(manifest_url)
logger.debug(f"Set manifest for {channel.name}: {manifest_url}")
# Set DRM configuration
drm_configs = self.get_drm(channel.channel_id, **kwargs)
if drm_configs:
channel.use_cdm = True
channel.cdm_type = "widevine"
# Set license URL from first Widevine config
for config in drm_configs:
if config.system == DRMSystem.WIDEVINE:
channel.license_url = config.license.server_url
break
else:
channel.use_cdm = False
channel.cdm_type = None
# Set DRM configuration with session data
drm_configs = self.get_drm(channel.channel_id, session_data, **kwargs)
if drm_configs:
channel.use_cdm = True
channel.cdm_type = "widevine"
return channel
# Set license URL from the first Widevine config
for config in drm_configs:
if config.system == DRMSystem.WIDEVINE:
channel.license_url = config.license.server_url
# Build the complete license key for inputstream.adaptive
# Format: server_url|req_headers|req_data|response_format
license_key_parts = [
config.license.server_url,
config.license.req_headers,
'R{SSM}', # Placeholder - inputstream will replace with actual challenge
'JBlicense' # Response is JSON, extract 'license' field
]
channel.license_key = '|'.join(license_key_parts)
logger.debug(f"Set DRM config for {channel.name}")
logger.debug(f"License URL: {config.license.server_url}")
break
else:
logger.warning(f"Could not fetch manifest for channel {channel.name} ({channel.channel_id})")
return channel
logger.warning(f"No DRM config for {channel.name}")
channel.use_cdm = False
channel.cdm_type = None
return channel
except Exception as e:
logger.error(f"Error enriching channel data for {channel.name}: {e}")