def _select_hls_substreams(self, master_hls_url, protocol): """Select HLS substreams to speed up Kodi player start, workaround for slower Kodi selection""" hls_variant_url = None subtitle_url = None hls_audio_id = None hls_subtitle_id = None hls_base_url = master_hls_url.split('.m3u8')[0] try: response = open_url(master_hls_url, raise_errors=[415]) except HTTPError as exc: self._handle_bad_stream_error(protocol, exc.code, exc.reason) return None if response is None: return None hls_playlist = to_unicode(response.read()) max_bandwidth = get_max_bandwidth() stream_bandwidth = None # Get hls variant url based on max_bandwidth setting import re hls_variant_regex = re.compile(r'#EXT-X-STREAM-INF:[\w\-.,=\"]*?BANDWIDTH=(?P<BANDWIDTH>\d+),' r'[\w\-.,=\"]+\d,(?:AUDIO=\"(?P<AUDIO>[\w\-]+)\",)?(?:SUBTITLES=\"' r'(?P<SUBTITLES>\w+)\",)?[\w\-.,=\"]+?[\r\n](?P<URI>[\w:\/\-.=?&]+)') # reverse sort by bandwidth for match in sorted(re.finditer(hls_variant_regex, hls_playlist), key=lambda m: int(m.group('BANDWIDTH')), reverse=True): stream_bandwidth = int(match.group('BANDWIDTH')) // 1000 if max_bandwidth == 0 or stream_bandwidth < max_bandwidth: if match.group('URI').startswith('http'): hls_variant_url = match.group('URI') else: hls_variant_url = hls_base_url + match.group('URI') hls_audio_id = match.group('AUDIO') hls_subtitle_id = match.group('SUBTITLES') break if stream_bandwidth > max_bandwidth and not hls_variant_url: message = localize(30057, max=max_bandwidth, min=stream_bandwidth) ok_dialog(message=message) open_settings() # Get audio url if hls_audio_id: audio_regex = re.compile(r'#EXT-X-MEDIA:TYPE=AUDIO[\w\-=,\.\"\/]+?GROUP-ID=\"' + hls_audio_id + '' r'\"[\w\-=,\.\"\/]+?URI=\"(?P<AUDIO_URI>[\w\-=]+)\.m3u8\"') match_audio = re.search(audio_regex, hls_playlist) if match_audio: hls_variant_url = hls_base_url + match_audio.group('AUDIO_URI') + '-' + hls_variant_url.split('-')[-1] # Get subtitle url, works only for on demand streams if get_setting_bool('showsubtitles', default=True) and '/live/' not in master_hls_url and hls_subtitle_id: subtitle_regex = re.compile(r'#EXT-X-MEDIA:TYPE=SUBTITLES[\w\-=,\.\"\/]+?GROUP-ID=\"' + hls_subtitle_id + '' r'\"[\w\-=,\.\"\/]+URI=\"(?P<SUBTITLE_URI>[\w\-=]+)\.m3u8\"') match_subtitle = re.search(subtitle_regex, hls_playlist) if match_subtitle: subtitle_url = hls_base_url + match_subtitle.group('SUBTITLE_URI') + '.webvtt' return StreamURLS(hls_variant_url, subtitle_url)
def get_stream(self, video, roaming=False, api_data=None): ''' Main streamservice function ''' if not api_data: api_data = self._get_api_data(video) stream_json = self._get_stream_json(api_data, roaming) if not stream_json: # Roaming token failed if roaming: message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message) # X-VRT-Token failed message = localize( 30963) # You need a VRT NU account to play this stream. return self._handle_stream_api_error(message) if 'targetUrls' in stream_json: # DRM support for ketnet junior/uplynk streaming service uplynk = 'uplynk.com' in stream_json.get('targetUrls')[0].get( 'url') vudrm_token = stream_json.get('drm') drm_stream = (vudrm_token or uplynk) # Select streaming protocol if not drm_stream and has_inputstream_adaptive(): protocol = 'mpeg_dash' elif drm_stream and self._can_play_drm: protocol = 'mpeg_dash' elif vudrm_token: protocol = 'hls_aes' else: protocol = 'hls' # Get stream manifest url manifest_url = next( stream.get('url') for stream in stream_json.get('targetUrls') if stream.get('type') == protocol) # External virtual subclip, live-to-VOD from past 24 hours archived livestream (airdate feature) if video.get('start_date') and video.get('end_date'): manifest_url += '?t=' + video.get( 'start_date') + '-' + video.get('end_date') # Fix virtual subclip from datetime import timedelta duration = timedelta(milliseconds=stream_json.get('duration', 0)) manifest_url = self._fix_virtualsubclip(manifest_url, duration) # Prepare stream for Kodi player if protocol == 'mpeg_dash' and drm_stream: log(2, 'Protocol: mpeg_dash drm') if vudrm_token: if self._vualto_license_url is None: self._get_vualto_license_url() encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'.format( vudrm_token) license_key = self._get_license_key( key_url=self._vualto_license_url, key_type='D', key_value=encryption_json, key_headers={ 'Content-Type': 'text/plain;charset=UTF-8' }) else: license_key = self._get_license_key( key_url=self._UPLYNK_LICENSE_URL, key_type='R') stream = StreamURLS(manifest_url, license_key=license_key, use_inputstream_adaptive=True) elif protocol == 'mpeg_dash': log(2, 'Protocol: mpeg_dash') stream = StreamURLS(manifest_url, use_inputstream_adaptive=True) else: log(2, 'Protocol: {protocol}', protocol=protocol) # Fix 720p quality for HLS livestreams manifest_url += '?hd' if '.m3u8?' not in manifest_url else '&hd' stream = self._select_hls_substreams(manifest_url, protocol) return stream # VRT Geoblock: failed to get stream, now try again with roaming enabled if stream_json.get('code') in self._GEOBLOCK_ERROR_CODES: log(2, 'VRT Geoblock: {msg}', msg=stream_json.get('message')) if not roaming: return self.get_stream(video, roaming=True, api_data=api_data) if stream_json.get('code') == self._INVALID_LOCATION: message = localize( 30965 ) # Geoblock error: Blocked on your geographical location based on your IP address return self._handle_stream_api_error(message, stream_json) message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message, stream_json) # Failed to get stream, handle error message = localize(30954) # Whoops something went wrong return self._handle_stream_api_error(message, stream_json)
def get_stream(self, video, roaming=False, api_data=None): """Main streamservice function""" if not api_data: api_data = self._get_api_data(video) stream_json = self._get_stream_json(api_data, roaming) if not stream_json: # Roaming token failed if roaming: message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message) # X-VRT-Token failed message = localize( 30963) # You need a VRT NU account to play this stream. return self._handle_stream_api_error(message) if 'targetUrls' in stream_json: # DRM support for ketnet junior/uplynk streaming service uplynk = 'uplynk.com' in stream_json.get('targetUrls')[0].get( 'url') vudrm_token = stream_json.get('drm') vualto_license_url = stream_json.get( 'vualto_license_url') or self._get_vualto_license_url() drm_stream = (vudrm_token or uplynk) # Select streaming protocol if not drm_stream and has_inputstream_adaptive(): protocol = 'mpeg_dash' elif drm_stream and self._can_play_drm: protocol = 'mpeg_dash' elif vudrm_token: protocol = 'hls_aes' else: protocol = 'hls' # Get stream manifest url manifest_url = next((stream.get('url') for stream in stream_json.get('targetUrls') if stream.get('type') == protocol), None) # Failed to get compatible manifest url, ask user to toggle "Use Widevine DRM" if manifest_url is None: available_protocols = [ stream.get('type') for stream in stream_json.get('targetUrls') ] if protocol not in available_protocols: error_json = { 'message': '%s is not available for this stream, please try toggling the "Use Widevine DRM" setting' % protocol } message = localize( 30989) # Failed to load a compatible stream return self._handle_stream_api_error(message, error_json) else: # External virtual subclip, live-to-VOD from past 24 hours archived livestream (airdate feature) if video.get('start_date') and video.get('end_date'): manifest_url += '?t=' + video.get( 'start_date') + '-' + video.get('end_date') # Fix virtual subclip from datetime import timedelta duration = timedelta( milliseconds=stream_json.get('duration', 0)) manifest_url = self._fix_virtualsubclip(manifest_url, duration) # Prepare stream for Kodi player if protocol == 'mpeg_dash' and drm_stream: log(2, 'Protocol: mpeg_dash drm') if vudrm_token: encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'.format( vudrm_token) license_key = self._get_license_key( key_url=vualto_license_url, key_type='D', key_value=encryption_json, key_headers={ 'Content-Type': 'text/plain;charset=UTF-8' }) else: license_key = self._get_license_key( key_url=self._UPLYNK_LICENSE_URL, key_type='R') stream = StreamURLS(manifest_url, license_key=license_key, use_inputstream_adaptive=True) elif protocol == 'mpeg_dash': log(2, 'Protocol: mpeg_dash') stream = StreamURLS(manifest_url, use_inputstream_adaptive=True) else: log(2, 'Protocol: {protocol}', protocol=protocol) # Fix 720p quality for HLS livestreams manifest_url = manifest_url.replace( '.m3u8?', '.m3u8?hd&' ) if '.m3u8?' in manifest_url else manifest_url + '?hd' # Play HLS directly in Kodi Player on Kodi 17 if kodi_version_major( ) < 18 or not has_inputstream_adaptive(): stream = self._select_hls_substreams( manifest_url, protocol) else: stream = StreamURLS(manifest_url, use_inputstream_adaptive=True) return stream # VRT Geoblock: failed to get stream, now try again with roaming enabled if stream_json.get('code') in self._GEOBLOCK_ERROR_CODES: log_error('VRT Geoblock: {msg}', msg=stream_json.get('message')) if not roaming: return self.get_stream(video, roaming=True, api_data=api_data) if stream_json.get('code') == self._INVALID_LOCATION: message = localize( 30965 ) # Geoblock error: Blocked on your geographical location based on your IP address return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == self._BELGIUM_ONLY: message = localize( 30973 ) # Geoblock error: This program can only be played from EU return self._handle_stream_api_error(message, stream_json) message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == 'VIDEO_NOT_FOUND': # Refresh listing invalidate_caches('*.json') container_reload() message = localize(30987) # No stream found return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == 'ERROR_AGE_RESTRICTED': message = localize( 30990 ) # Cannot be played, VRT NU account not allowed to access 12+ content return self._handle_stream_api_error(message, stream_json) # Failed to get stream, handle error message = localize(30954) # Whoops something went wrong return self._handle_stream_api_error(message, stream_json)