def run_addon_configuration(show_end_msg=False): """ Add-on configuration wizard, automatically configures profiles and add-ons dependencies, based on user-supplied data and device characteristics """ system = get_system_platform() debug('Running add-on configuration wizard ({})', system) G.settings_monitor_suspend(True, False) is_4k_capable = is_device_4k_capable() _set_profiles(system, is_4k_capable) _set_kodi_settings(system) _set_isa_addon_settings(is_4k_capable, system == 'android') # This settings for now used only with android devices and it should remain disabled (keep it for test), # in the future it may be useful for other platforms or it may be removed G.ADDON.setSettingBool('enable_force_hdcp', False) # Enable UpNext if it is installed and enabled G.ADDON.setSettingBool( 'UpNextNotifier_enabled', getCondVisibility('System.AddonIsEnabled(service.upnext)')) G.settings_monitor_suspend(False) if show_end_msg: show_ok_dialog(get_local_string(30154), get_local_string(30157))
def _perform_service_changes(previous_ver, current_ver): """Perform actions for an version bump""" LOG.debug( 'Initialize add-on service upgrade operations, from version {} to {}', previous_ver, current_ver) # Clear cache (prevents problems when netflix change data structures) G.CACHE.clear() if not previous_ver: return # Delete all stream continuity data - if user has upgraded from Kodi 18 to Kodi 19 if CmpVersion(previous_ver) < '1.13': # There is no way to determine if the user has migrated from Kodi 18 to Kodi 19, # then we assume that add-on versions prior to 1.13 was on Kodi 18 # The am_stream_continuity.py on Kodi 18 works differently and the existing data can not be used on Kodi 19 G.SHARED_DB.clear_stream_continuity() # Disable enable_hevc_profiles if has been wrongly enabled by the user and it is unsupported by the systems with G.SETTINGS_MONITOR.ignore_events(1): is_4k_capable = is_device_4k_capable() G.ADDON.setSettingBool('enable_hevc_profiles', is_4k_capable) if CmpVersion(previous_ver) < '1.9.0': # In the version 1.9.0 has been changed the COOKIE_ filename with a static filename from resources.lib.upgrade_actions import rename_cookie_file rename_cookie_file() if CmpVersion(previous_ver) < '1.12.0': # In the version 1.13.0: # - 'force_widevine' on setting.xml has been moved # as 'widevine_force_seclev' in TABLE_SESSION with different values: # force_widevine = G.ADDON.getSettingString('force_widevine') # # Old values: Disabled|Widevine L3|Widevine L3 (ID-4445) # # New values: Disabled|L3|L3 (ID 4445) # if force_widevine == 'Widevine L3': # G.LOCAL_DB.set_value('widevine_force_seclev', 'L3', table=TABLE_SESSION) # elif force_widevine == 'Widevine L3 (ID-4445)': # G.LOCAL_DB.set_value('widevine_force_seclev', 'L3 (ID 4445)', table=TABLE_SESSION) # # - 'esn' on setting.xml is not more used but if was set the value need to be copied on 'esn' on TABLE_SESSION: # esn = G.ADDON.getSettingString('esn') # if esn: # from resources.lib.utils.esn import set_esn # set_esn(esn) # - 'suspend_settings_monitor' is not more used G.LOCAL_DB.delete_key('suspend_settings_monitor') # In the version 1.14.0 the new settings.xml format has been introduced # the migration of the settings (commented above) from this version is no more possible from resources.lib.kodi import ui ui.show_ok_dialog( 'Netflix add-on upgrade', 'This add-on upgrade has reset your ESN code, if you had set an ESN code manually ' 'you must re-enter it again in the Expert settings, otherwise simply ignore this message.' ) if CmpVersion(previous_ver) < '1.16.0': # In the version 1.16.0 the watched status sync setting has been enabled by default, # therefore to be able to keep the user's setting even when it has never been changed, # we have done a new setting (ProgressManager_enabled >> to >> sync_watched_status) with G.SETTINGS_MONITOR.ignore_events(1): G.ADDON.setSettingBool( 'sync_watched_status', G.ADDON.getSettingBool('ProgressManager_enabled')) if CmpVersion(previous_ver) < '1.18.3': # In the version 1.18.3 content_profiles_int has been replaced by manifest_settings_status G.LOCAL_DB.delete_key('content_profiles_int')
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 LOG.level == LOG.LEVEL_VERBOSE: LOG.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file_def('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'] LOG.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 LOG.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 LOG.level == LOG.LEVEL_VERBOSE: # Save the manifest to disk as reference common.save_file_def('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
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 _get_manifest(self, viewable_id, esn, challenge, sid): from pprint import pformat cache_identifier = f'{esn}_{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 LOG.is_enabled: LOG.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file_def('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'] manifest_ver = G.ADDON.getSettingString('msl_manifest_version') profiles = enabled_profiles() LOG.info( 'Requesting manifest (version {}) for\nVIDEO ID: {}\nESN: {}\nHDCP: {}\nPROFILES:\n{}', manifest_ver, viewable_id, common.censure(esn) if len(esn) > 50 else esn, hdcp_version, pformat(profiles, indent=2)) if manifest_ver == 'v1': endpoint_url, request_data = self._build_manifest_v1( viewable_id=viewable_id, hdcp_version=hdcp_version, hdcp_override=hdcp_override, profiles=profiles, challenge=challenge) else: # Default - most recent version endpoint_url, request_data = self._build_manifest_v2( viewable_id=viewable_id, hdcp_version=hdcp_version, hdcp_override=hdcp_override, profiles=profiles, challenge=challenge, sid=sid) manifest = self.msl_requests.chunked_request(endpoint_url, request_data, esn, disable_msl_switch=False) if LOG.is_enabled: # Save the manifest to disk as reference common.save_file_def('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
def _get_manifest(self, viewable_id, esn, challenge, sid): cache_identifier = f'{esn}_{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 LOG.is_enabled: LOG.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file_def('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'] LOG.info('Requesting licensed manifest for {} with ESN {} and HDCP {}', viewable_id, common.censure(esn) if len(esn) > 50 else esn, hdcp_version) profiles = enabled_profiles() from pprint import pformat LOG.info('Requested profiles:\n{}', pformat(profiles, indent=2)) params = { 'type': 'standard', 'manifestVersion': 'v2', 'viewableId': viewable_id, 'profiles': profiles, 'flavor': 'PRE_FETCH', 'drmType': 'widevine', 'drmVersion': 25, 'usePsshBox': True, 'isBranching': False, 'useHttpsStreams': True, 'supportsUnequalizedDownloadables': 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), 'supportsPreReleasePin': True, 'supportsWatermark': True, 'showAllSubDubTracks': False, 'videoOutputInfo': [{ 'type': 'DigitalVideoOutputDescriptor', 'outputType': 'unknown', 'supportedHdcpVersions': hdcp_version, 'isHdcpEngaged': hdcp_override }], 'titleSpecificData': { str(viewable_id): { 'unletterboxed': True } }, 'preferAssistiveAudio': False, 'isUIAutoPlay': False, 'isNonMember': False, 'desiredVmaf': 'plus_lts', # phone_plus_exp can be used to mobile, not tested 'desiredSegmentVmaf': 'plus_lts', 'requestSegmentVmaf': False, 'supportsPartialHydration': False, 'contentPlaygraph': [], 'profileGroups': [{ 'name': 'default', 'profiles': profiles }], 'licenseType': 'limited' # standard / limited } if challenge and sid: params['challenge'] = challenge params['challenges'] = { 'default': [{ 'drmSessionId': sid, 'clientTime': int(time.time()), 'challengeBase64': challenge }] } endpoint_url = ENDPOINTS['manifest'] + create_req_params( 0, 'licensedManifest') manifest = self.msl_requests.chunked_request( endpoint_url, self.msl_requests.build_request_data('licensedManifest', params), esn, disable_msl_switch=False) if LOG.is_enabled: # Save the manifest to disk as reference common.save_file_def('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
def _get_manifest(self, viewable_id, esn, challenge, sid): if common.get_system_platform() != 'android' and (not challenge or not sid): LOG.error('DRM session data not valid (Session ID: {}, Challenge: {})', challenge, sid) from pprint import pformat cache_identifier = f'{esn}_{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 LOG.is_enabled: LOG.debug('Manifest for {} obtained from the cache', viewable_id) # Save the manifest to disk as reference common.save_file_def('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'] manifest_ver = G.ADDON.getSettingString('msl_manifest_version') profiles = enabled_profiles() LOG.info('Requesting manifest (version {}) for\nVIDEO ID: {}\nESN: {}\nHDCP: {}\nPROFILES:\n{}', manifest_ver, viewable_id, common.censure(esn) if len(esn) > 50 else esn, hdcp_version, pformat(profiles, indent=2)) xid = None # On non-Android systems, we pre-initialize the DRM with default PSSH/KID, this allows to obtain Challenge/SID # to achieve 1080p resolution. # On Android, pre-initialize DRM is possible but cannot keep the same DRM session, will result in an error # because the manifest license data do not match the current DRM session, then we do not use it and # we still make the license requests. if manifest_ver == 'v1': endpoint_url, request_data = self._build_manifest_v1(viewable_id=viewable_id, hdcp_version=hdcp_version, hdcp_override=hdcp_override, profiles=profiles, challenge=challenge) else: # Default - most recent version endpoint_url, request_data, xid = self._build_manifest_v2(viewable_id=viewable_id, hdcp_version=hdcp_version, hdcp_override=hdcp_override, profiles=profiles, challenge=challenge, sid=sid) manifest = self.msl_requests.chunked_request(endpoint_url, request_data, esn, disable_msl_switch=False) if manifest_ver == 'default' and 'license' in manifest['video_tracks'][0]: self.needs_license_request = False # This xid must be used also for each future Event request, until playback stops G.LOCAL_DB.set_value('xid', xid, TABLE_SESSION) self.licenses_xid.insert(0, xid) self.licenses_session_id.insert(0, manifest['video_tracks'][0]['license']['drmSessionId']) self.licenses_release_url.insert(0, manifest['video_tracks'][0]['license']['links']['releaseLicense']['href']) self.licenses_response = manifest['video_tracks'][0]['license']['licenseResponseBase64'] else: self.needs_license_request = True if LOG.is_enabled: # Save the manifest to disk as reference common.save_file_def('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