def _save_msl_data(self): """Save crypto keys and mastertoken to disk""" self._msl_data['tokens'] = {'mastertoken': self.mastertoken} self._msl_data.update(self._export_keys()) common.save_file(MSL_DATA_FILENAME, json.dumps(self._msl_data).encode('utf-8')) common.debug('Successfully saved MSL data to disk')
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEGDASH manifest""" seconds = manifest['duration'] / 1000 init_length = seconds / 2 * 12 + 20 * 1000 duration = "PT" + str(seconds) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) protection = _protection_info(manifest) for video_track in manifest['video_tracks']: _convert_video_track( video_track, period, init_length, protection) default_audio_language_index = _get_default_audio_language(manifest) for index, audio_track in enumerate(manifest['audio_tracks']): _convert_audio_track(audio_track, period, init_length, default=(index == default_audio_language_index)) default_subtitle_language_index = _get_default_subtitle_language(manifest) for index, text_track in enumerate(manifest['timedtexttracks']): if text_track['isNoneTrack']: continue _convert_text_track(text_track, period, default=(index == default_subtitle_language_index)) xml = ET.tostring(root, encoding='utf-8', method='xml') common.save_file('manifest.mpd', xml) return xml.replace('\n', '').replace('\r', '')
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEG-DASH manifest""" from xbmcaddon import Addon isa_version = Addon('inputstream.adaptive').getAddonInfo('version') has_drm_streams = manifest['hasDrmStreams'] protection_info = _get_protection_info(manifest) if has_drm_streams else None seconds = int(manifest['duration'] / 1000) init_length = int(seconds / 2 * 12 + 20 * 1000) duration = "PT" + str(seconds) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) for video_track in manifest['video_tracks']: _convert_video_track(video_track, period, init_length, protection_info, has_drm_streams) common.fix_locale_languages(manifest['audio_tracks']) common.fix_locale_languages(manifest['timedtexttracks']) default_audio_language_index = _get_default_audio_language(manifest) for index, audio_track in enumerate(manifest['audio_tracks']): _convert_audio_track(audio_track, period, init_length, (index == default_audio_language_index), has_drm_streams) default_subtitle_language_index = _get_default_subtitle_language(manifest) for index, text_track in enumerate(manifest['timedtexttracks']): if text_track['isNoneTrack']: continue _convert_text_track(text_track, period, (index == default_subtitle_language_index), isa_version) xml = ET.tostring(root, encoding='utf-8', method='xml') if common.is_debug_verbose(): common.save_file('manifest.mpd', xml) return xml.decode('utf-8').replace('\n', '').replace('\r', '').encode('utf-8')
def _load_manifest(self, viewable_id, esn): common.debug('Requesting manifest for {} with ESN {}' .format(viewable_id, esn)) profiles = enabled_profiles() import pprint common.debug('Requested profiles:\n{}' .format(pprint.pformat(profiles, indent=2))) manifest_request_data = { 'method': 'manifest', 'lookupType': 'PREPARE', 'viewableIds': [viewable_id], 'profiles': profiles, 'drmSystem': 'widevine', 'appId': '14673889385265', 'sessionParams': { 'pinCapableClient': False, 'uiplaycontext': 'null' }, 'sessionId': '14673889385265', 'trackId': 0, 'flavor': 'PRE_FETCH', 'secureUrls': False, 'supportPreviewContent': True, 'forceClearStreams': False, 'languages': ['de-DE'], 'clientVersion': '4.0004.899.011', 'uiVersion': 'akira' } manifest = self._chunked_request(ENDPOINTS['manifest'], manifest_request_data, esn) common.save_file('manifest.json', json.dumps(manifest)) return manifest['result']['viewables'][0]
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEGDASH manifest""" seconds = manifest['runtime'] / 1000 init_length = seconds / 2 * 12 + 20 * 1000 duration = "PT" + str(seconds) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) protection = _protection_info(manifest) for video_track in manifest['videoTracks']: _convert_video_track(video_track, period, init_length, protection) for index, audio_track in enumerate(manifest['audioTracks']): # Assume that first listed track is the default _convert_audio_track(audio_track, period, init_length, default=(index == 0)) for text_track in manifest.get('textTracks'): _convert_text_track(text_track, period) xml = ET.tostring(root, encoding='utf-8', method='xml') common.save_file('manifest.mpd', xml) return xml.replace('\n', '').replace('\r', '')
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEG-DASH manifest""" from xbmcaddon import Addon isa_version = g.remove_ver_suffix( g.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version'))) # If a CDN server has stability problems it may cause errors with streaming, # we allow users to select a different CDN server # (should be managed by ISA but is currently is not implemented) cdn_index = int(g.ADDON.getSettingString('cdn_server')[-1]) - 1 seconds = manifest['duration'] / 1000 init_length = int(seconds / 2 * 12 + 20 * 1000) duration = "PT" + str(int(seconds)) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) has_video_drm_streams = manifest['video_tracks'][0].get( 'hasDrmStreams', False) video_protection_info = _get_protection_info( manifest['video_tracks'][0]) if has_video_drm_streams else None for video_track in manifest['video_tracks']: _convert_video_track(video_track, period, init_length, video_protection_info, has_video_drm_streams, cdn_index) common.fix_locale_languages(manifest['audio_tracks']) common.fix_locale_languages(manifest['timedtexttracks']) has_audio_drm_streams = manifest['audio_tracks'][0].get( 'hasDrmStreams', False) default_audio_language_index = _get_default_audio_language(manifest) for index, audio_track in enumerate(manifest['audio_tracks']): _convert_audio_track(audio_track, period, init_length, (index == default_audio_language_index), has_audio_drm_streams, cdn_index) default_subtitle_language_index = _get_default_subtitle_language(manifest) for index, text_track in enumerate(manifest['timedtexttracks']): if text_track['isNoneTrack']: continue _convert_text_track(text_track, period, (index == default_subtitle_language_index), cdn_index, isa_version) xml = ET.tostring(root, encoding='utf-8', method='xml') if common.is_debug_verbose(): common.save_file('manifest.mpd', xml) return xml.decode('utf-8').replace('\n', '').replace('\r', '').encode('utf-8')
def _load_manifest(self, viewable_id, esn): cache_identifier = esn + '_' + unicode(viewable_id) try: # The manifest must be requested once and maintained for its entire duration manifest = g.CACHE.get(cache.CACHE_MANIFESTS, cache_identifier, False) common.debug('Manifest for {} with ESN {} obtained from the cache', viewable_id, esn) # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) return manifest except cache.CacheMiss: pass common.debug('Requesting manifest for {} with ESN {}', viewable_id, esn) profiles = enabled_profiles() import pprint common.info('Requested profiles:\n{}', pprint.pformat(profiles, indent=2)) ia_addon = xbmcaddon.Addon('inputstream.adaptive') hdcp = ia_addon is not None and ia_addon.getSetting('HDCPOVERRIDE') == 'true' # TODO: Future implementation when available, # request the HDCP version from Kodi through a function # in CryptoSession currently not implemented # so there will be no more need to use the HDCPOVERRIDE = true hdcp_version = [] if not g.ADDON.getSettingBool('enable_force_hdcp') and hdcp: hdcp_version = ['1.4'] if g.ADDON.getSettingBool('enable_force_hdcp') and hdcp: hdcp_version = ['2.2'] timestamp = int(time.time() * 10000) manifest_request_data = { 'version': 2, 'url': '/manifest', 'id': timestamp, 'languages': [g.LOCAL_DB.get_value('locale_id')], 'params': { 'type': 'standard', 'viewableId': [viewable_id], 'profiles': profiles, 'flavor': 'PRE_FETCH', 'drmType': 'widevine', 'drmVersion': 25, 'usePsshBox': True, 'isBranching': False, 'useHttpsStreams': False, 'imageSubtitleHeight': 1080, 'uiVersion': 'shakti-v93016808', 'uiPlatform': 'SHAKTI', 'clientVersion': '6.0016.426.011', 'desiredVmaf': 'plus_lts', # phone_plus_exp can be used to mobile, not tested 'supportsPreReleasePin': True, 'supportsWatermark': True, 'supportsUnequalizedDownloadables': True, 'showAllSubDubTracks': False, 'titleSpecificData': { viewable_id: { 'unletterboxed': True } }, 'videoOutputInfo': [{ 'type': 'DigitalVideoOutputDescriptor', 'outputType': 'unknown', 'supportedHdcpVersions': hdcp_version, 'isHdcpEngaged': hdcp }], 'preferAssistiveAudio': False, 'isNonMember': False }, 'echo': '' } # Get and check mastertoken validity mt_validity = self.check_mastertoken_validity() manifest = self._chunked_request(ENDPOINTS['manifest'], manifest_request_data, esn, mt_validity) # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) # Save the manifest to the cache to retrieve it during its validity expiration = int(manifest['expiration'] / 1000) g.CACHE.add(cache.CACHE_MANIFESTS, cache_identifier, manifest, eol=expiration) if 'result' in manifest: return manifest['result'] return manifest
def _save_system_info(): # Ask to save to a file filename = 'NFSystemInfo.txt' path = ui.show_browse_dialog( f'{common.get_local_string(30603)} - {filename}') if not path: return # This collect the main data to allow verification checks for problems data = f'Netflix add-on version: {G.VERSION}' data += f'\nDebug enabled: {LOG.is_enabled}' data += f'\nSystem platform: {common.get_system_platform()}' data += f'\nMachine architecture: {common.get_machine()}' data += f'\nUser agent string: {common.get_user_agent()}' data += '\n\n#### Widevine info ####\n' if common.get_system_platform() == 'android': data += f'\nSystem ID: {G.LOCAL_DB.get_value("drm_system_id", "--not obtained--", TABLE_SESSION)}' data += f'\nSecurity level: {G.LOCAL_DB.get_value("drm_security_level", "--not obtained--", TABLE_SESSION)}' data += f'\nHDCP level: {G.LOCAL_DB.get_value("drm_hdcp_level", "--not obtained--", TABLE_SESSION)}' wv_force_sec_lev = G.LOCAL_DB.get_value('widevine_force_seclev', WidevineForceSecLev.DISABLED, TABLE_SESSION) data += f'\nForced security level setting is: {wv_force_sec_lev}' else: try: from ctypes import (CDLL, c_char_p) cdm_lib_file_path = _get_cdm_file_path() try: lib = CDLL(cdm_lib_file_path) data += '\nLibrary status: Correctly loaded' try: lib.GetCdmVersion.restype = c_char_p data += f'\nVersion: {lib.GetCdmVersion().decode("utf-8")}' except Exception: # pylint: disable=broad-except # This can happen if the endpoint 'GetCdmVersion' is changed data += '\nVersion: Reading error' except Exception as exc: # pylint: disable=broad-except # This should not happen but currently InputStream Helper does not perform any verification checks on # downloaded and installed files, so if due to an problem it installs a CDM for a different architecture # or the files are corrupted, the user can no longer play videos and does not know what to do data += '\nLibrary status: Error loading failed' data += '\n>>> It is possible that is installed a CDM of a wrong architecture or is corrupted' data += '\n>>> Suggested solutions:' data += '\n>>> - Restore a previous version of Widevine library from InputStream Helper add-on settings' data += '\n>>> - Report the problem to the GitHub of InputStream Helper add-on' data += f'\n>>> Error details: {exc}' except Exception as exc: # pylint: disable=broad-except data += f'\nThe data could not be obtained. Error details: {exc}' data += '\n\n#### ESN ####\n' esn = get_esn() or '--not obtained--' data += f'\nUsed ESN: {common.censure(esn) if len(esn) > 50 else esn}' data += f'\nWebsite ESN: {get_website_esn() or "--not obtained--"}' data += f'\nAndroid generated ESN: {(generate_android_esn() or "--not obtained--")}' if common.get_system_platform() == 'android': data += '\n\n#### Device system info ####\n' try: import subprocess info = subprocess.check_output(['/system/bin/getprop' ]).decode('utf-8') data += f'\n{info}' except Exception as exc: # pylint: disable=broad-except data += f'\nThe data could not be obtained. Error: {exc}' data += '\n' try: common.save_file(common.join_folders_paths(path, filename), data.encode('utf-8')) ui.show_notification( f'{xbmc.getLocalizedString(35259)}: {filename}') # 35259=Saved except Exception as exc: # pylint: disable=broad-except LOG.error('save_file error: {}', exc) ui.show_notification('Error! Try another path')
def _load_manifest(self, viewable_id, esn): common.debug('Requesting manifest for {} with ESN {}'.format( viewable_id, esn)) profiles = enabled_profiles() import pprint common.debug('Requested profiles:\n{}'.format( pprint.pformat(profiles, indent=2))) ia_addon = xbmcaddon.Addon('inputstream.adaptive') hdcp = ia_addon is not None and ia_addon.getSetting( 'HDCPOVERRIDE') == 'true' # TODO: Future implementation when available, # request the HDCP version from Kodi through a function # in CryptoSession currently not implemented # so there will be no more need to use the HDCPOVERRIDE = true hdcp_version = [] if not g.ADDON.getSettingBool('enable_force_hdcp') and hdcp: hdcp_version = ['1.4'] if g.ADDON.getSettingBool('enable_force_hdcp') and hdcp: hdcp_version = ['2.2'] id = int(time.time() * 10000) manifest_request_data = { 'version': 2, 'url': '/manifest', 'id': id, 'esn': esn, 'languages': [g.LOCAL_DB.get_value('locale_id')], 'uiVersion': 'shakti-v5bca5cd3', 'clientVersion': '6.0013.315.051', 'params': { 'type': 'standard', 'viewableId': [viewable_id], 'profiles': profiles, 'flavor': 'PRE_FETCH', 'drmType': 'widevine', 'drmVersion': 25, 'usePsshBox': True, 'isBranching': False, 'useHttpsStreams': False, 'imageSubtitleHeight': 1080, 'uiVersion': 'shakti-v5bca5cd3', 'uiPlatform': 'SHAKTI', 'clientVersion': '6.0013.315.051', 'supportsPreReleasePin': True, 'supportsWatermark': True, 'showAllSubDubTracks': False, 'titleSpecificData': {}, 'videoOutputInfo': [{ 'type': 'DigitalVideoOutputDescriptor', 'outputType': 'unknown', 'supportedHdcpVersions': hdcp_version, 'isHdcpEngaged': hdcp }], 'preferAssistiveAudio': False, 'isNonMember': False } } manifest = self._chunked_request(ENDPOINTS['manifest'], manifest_request_data, esn) common.save_file('manifest.json', json.dumps(manifest)) if 'result' in manifest: return manifest['result'] return manifest
def _save_msl_data(self): """Save crypto keys and mastertoken to disk""" msl_data = {'tokens': {'mastertoken': self.mastertoken}} msl_data.update(self._export_keys()) common.save_file('msl_data.json', json.dumps(msl_data).encode('utf-8')) common.debug('Successfully saved MSL data to disk')
def _load_manifest(self, viewable_id, esn): cache_identifier = esn + '_' + unicode(viewable_id) try: # The manifest must be requested once and maintained for its entire duration manifest = g.CACHE.get(CACHE_MANIFESTS, cache_identifier) expiration = int(manifest['expiration'] / 1000) if (expiration - time.time()) < 14400: # Some devices remain active even longer than 48 hours, if the manifest is at the limit of the deadline # when requested by stream_continuity.py / events_handler.py will cause problems # if it is already expired, so we guarantee a minimum of safety ttl of 4h (14400s = 4 hours) raise CacheMiss() if common.is_debug_verbose(): common.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) return manifest except CacheMiss: pass isa_addon = xbmcaddon.Addon('inputstream.adaptive') hdcp_override = isa_addon is not None and isa_addon.getSettingBool( 'HDCPOVERRIDE') hdcp_4k_capable = common.is_device_4k_capable( ) or g.ADDON.getSettingBool('enable_force_hdcp') hdcp_version = [] if not hdcp_4k_capable and hdcp_override: hdcp_version = ['1.4'] if hdcp_4k_capable and hdcp_override: hdcp_version = ['2.2'] common.info('Requesting manifest for {} with ESN {} and HDCP {}', viewable_id, common.censure(esn) if g.ADDON.getSetting('esn') else esn, hdcp_version) profiles = enabled_profiles() from pprint import pformat common.info('Requested profiles:\n{}', pformat(profiles, indent=2)) params = { 'type': 'standard', 'viewableId': [viewable_id], 'profiles': profiles, 'flavor': 'PRE_FETCH', 'drmType': 'widevine', 'drmVersion': 25, 'usePsshBox': True, 'isBranching': False, 'isNonMember': False, 'isUIAutoPlay': False, 'useHttpsStreams': True, 'imageSubtitleHeight': 1080, 'uiVersion': 'shakti-v93016808', 'uiPlatform': 'SHAKTI', 'clientVersion': '6.0016.426.011', 'desiredVmaf': 'plus_lts', # phone_plus_exp can be used to mobile, not tested 'supportsPreReleasePin': True, 'supportsWatermark': True, 'supportsUnequalizedDownloadables': True, 'showAllSubDubTracks': False, 'titleSpecificData': { viewable_id: { 'unletterboxed': True } }, 'videoOutputInfo': [{ 'type': 'DigitalVideoOutputDescriptor', 'outputType': 'unknown', 'supportedHdcpVersions': hdcp_version, 'isHdcpEngaged': hdcp_override }], 'preferAssistiveAudio': False } manifest = self.msl_requests.chunked_request( ENDPOINTS['manifest'], self.msl_requests.build_request_data('/manifest', params), esn, disable_msl_switch=False) if common.is_debug_verbose(): # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) # Save the manifest to the cache to retrieve it during its validity expiration = int(manifest['expiration'] / 1000) g.CACHE.add(CACHE_MANIFESTS, cache_identifier, manifest, expires=expiration) if 'result' in manifest: return manifest['result'] return manifest
def _load_manifest(self, viewable_id, esn): cache_identifier = esn + '_' + unicode(viewable_id) try: # The manifest must be requested once and maintained for its entire duration manifest = g.CACHE.get(CACHE_MANIFESTS, cache_identifier) expiration = int(manifest['expiration'] / 1000) if (expiration - time.time()) < 14400: # Some devices remain active even longer than 48 hours, if the manifest is at the limit of the deadline # when requested by am_stream_continuity.py / events_handler.py will cause problems # if it is already expired, so we guarantee a minimum of safety ttl of 4h (14400s = 4 hours) raise CacheMiss() if common.is_debug_verbose(): common.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) return manifest except CacheMiss: pass isa_addon = xbmcaddon.Addon('inputstream.adaptive') hdcp_override = isa_addon.getSettingBool('HDCPOVERRIDE') hdcp_4k_capable = common.is_device_4k_capable( ) or g.ADDON.getSettingBool('enable_force_hdcp') hdcp_version = [] if not hdcp_4k_capable and hdcp_override: hdcp_version = ['1.4'] if hdcp_4k_capable and hdcp_override: hdcp_version = ['2.2'] common.info('Requesting manifest for {} with ESN {} and HDCP {}', viewable_id, common.censure(esn) if g.ADDON.getSetting('esn') else esn, hdcp_version) profiles = enabled_profiles() from pprint import pformat common.info('Requested profiles:\n{}', pformat(profiles, indent=2)) params = { 'type': 'standard', 'viewableId': [viewable_id], 'profiles': profiles, 'flavor': 'PRE_FETCH', 'drmType': 'widevine', 'drmVersion': 25, 'usePsshBox': True, 'isBranching': False, 'isNonMember': False, 'isUIAutoPlay': False, 'useHttpsStreams': True, 'imageSubtitleHeight': 1080, 'uiVersion': g.LOCAL_DB.get_value('ui_version', '', table=TABLE_SESSION), 'uiPlatform': 'SHAKTI', 'clientVersion': g.LOCAL_DB.get_value('client_version', '', table=TABLE_SESSION), 'desiredVmaf': 'plus_lts', # phone_plus_exp can be used to mobile, not tested 'supportsPreReleasePin': True, 'supportsWatermark': True, 'supportsUnequalizedDownloadables': True, 'showAllSubDubTracks': False, 'titleSpecificData': { unicode(viewable_id): { 'unletterboxed': True } }, 'videoOutputInfo': [{ 'type': 'DigitalVideoOutputDescriptor', 'outputType': 'unknown', 'supportedHdcpVersions': hdcp_version, 'isHdcpEngaged': hdcp_override }], 'preferAssistiveAudio': False } if 'linux' in common.get_system_platform( ) and 'arm' in common.get_machine(): # 24/06/2020 To get until to 1080P resolutions under arm devices (ChromeOS), android excluded, # is mandatory to add the widevine challenge data (key request) to the manifest request. # Is not possible get the key request from the default_crypto, is needed to implement # the wv crypto (used for android) but currently InputStreamAdaptive support this interface only # under android OS. # As workaround: Initially we pass an hardcoded challenge data needed to play the first video, # then when ISA perform the license callback we replace it with the fresh license challenge data. params['challenge'] = self.manifest_challenge endpoint_url = ENDPOINTS[ 'manifest'] + '?reqAttempt=1&reqPriority=0&reqName=prefetch/manifest' manifest = self.msl_requests.chunked_request( endpoint_url, self.msl_requests.build_request_data('/manifest', params), esn, disable_msl_switch=False) if common.is_debug_verbose(): # Save the manifest to disk as reference common.save_file('manifest.json', json.dumps(manifest).encode('utf-8')) # Save the manifest to the cache to retrieve it during its validity expiration = int(manifest['expiration'] / 1000) g.CACHE.add(CACHE_MANIFESTS, cache_identifier, manifest, expires=expiration) return manifest