def _send(payload): if not settings.allow_ga: return data = payload.copy() data.update(_common) for k, v in data.items(): if type(v) is unicode: data[k] = v.encode('utf8') data_str = urlencode(data) try: common.debug('GA: %s' % json.dumps(data, indent=4)) req = urlopen(_ga_url, data_str) code = req.getcode() if code > 399: common.error('GA status code %s' % code) else: common.debug('Successful GA') except Exception as e: common.error('Failed to request GA: %s' % e)
def play_callback(data): """Callback function used for upnext integration""" common.debug('Received signal from Up Next. Playing next episode...') common.stop_playback() common.play_media(data['play_path'])
def _on_change(self): common.reset_log_level_global_var() common.debug( 'SettingsMonitor: settings have been changed, started checks') reboot_addon = False clean_cache = False use_mysql = g.ADDON.getSettingBool('use_mysql') use_mysql_old = g.LOCAL_DB.get_value('use_mysql', False, TABLE_SETTINGS_MONITOR) use_mysql_turned_on = use_mysql and not use_mysql_old common.debug('SettingsMonitor: Reinitialization of global settings') g.init_globals(sys.argv, reboot_addon) # Check the MySQL connection status after reinitialization of global settings use_mysql_after = g.ADDON.getSettingBool('use_mysql') if use_mysql_turned_on and use_mysql_after: g.LOCAL_DB.set_value('use_mysql', True, TABLE_SETTINGS_MONITOR) ui.show_notification(g.ADDON.getLocalizedString(30202)) if not use_mysql_after and use_mysql_old: g.LOCAL_DB.set_value('use_mysql', False, TABLE_SETTINGS_MONITOR) # Check if the custom esn is changed custom_esn = g.ADDON.getSetting('esn') custom_esn_old = g.LOCAL_DB.get_value('custom_esn', '', TABLE_SETTINGS_MONITOR) if custom_esn != custom_esn_old: g.LOCAL_DB.set_value('custom_esn', custom_esn, TABLE_SETTINGS_MONITOR) common.send_signal(signal=common.Signals.ESN_CHANGED, data=g.get_esn()) # Check menu settings changes for menu_id, menu_data in iteritems(g.MAIN_MENU_ITEMS): # Check settings changes in show menu show_menu_new_setting = bool( g.ADDON.getSettingBool('_'.join(('show_menu', menu_id)))) show_menu_old_setting = g.LOCAL_DB.get_value( 'menu_{}_show'.format(menu_id), True, TABLE_SETTINGS_MONITOR) if show_menu_new_setting != show_menu_old_setting: g.LOCAL_DB.set_value('menu_{}_show'.format(menu_id), show_menu_new_setting, TABLE_SETTINGS_MONITOR) reboot_addon = True # Check settings changes in sort order of menu if menu_data.get('request_context_name'): menu_sortorder_new_setting = int( g.ADDON.getSettingInt('_'.join( ('menu_sortorder', menu_data['path'][1])))) menu_sortorder_old_setting = g.LOCAL_DB.get_value( 'menu_{}_sortorder'.format(menu_id), 0, TABLE_SETTINGS_MONITOR) if menu_sortorder_new_setting != menu_sortorder_old_setting: g.LOCAL_DB.set_value('menu_{}_sortorder'.format(menu_id), menu_sortorder_new_setting, TABLE_SETTINGS_MONITOR) # We remove the cache to allow get the new results in the chosen order clean_cache = True # Check changes on content profiles # This is necessary because it is possible that some manifests # could be cached using the previous settings (see msl_handler - load_manifest) menu_keys = [ 'enable_dolby_sound', 'enable_vp9_profiles', 'enable_hevc_profiles', 'enable_hdr_profiles', 'enable_dolbyvision_profiles', 'enable_force_hdcp', 'disable_webvtt_subtitle' ] collect_int = '' for menu_key in menu_keys: collect_int += unicode(int(g.ADDON.getSettingBool(menu_key))) collect_int_old = g.LOCAL_DB.get_value('content_profiles_int', '', TABLE_SETTINGS_MONITOR) if collect_int != collect_int_old: g.LOCAL_DB.set_value('content_profiles_int', collect_int, TABLE_SETTINGS_MONITOR) clean_cache = True if clean_cache: common.run_plugin( 'plugin://plugin.video.netflix/action/purge_cache/' '?on_disk=True&no_notification=True') if reboot_addon: common.debug('SettingsMonitor: addon will be rebooted') url = 'plugin://plugin.video.netflix/directory/root' xbmc.executebuiltin( 'XBMC.Container.Update(path,replace)') # Clean path history xbmc.executebuiltin( 'Container.Update({})'.format(url)) # Open root page
def __init__(self, params): common.debug('Initializing "LibraryActionExecutor" with params: {}', params) self.params = params
def __init__(self, params): common.debug('Initializing AddonActionExecutor: {}'.format(params)) self.params = params
def on_playback_started(self, player_state): # pylint: disable=unused-argument common.debug('Sending initialization signal to Up Next Add-on') common.send_signal(common.Signals.UPNEXT_ADDON_INIT, self.upnext_info, non_blocking=True)
def subgenre(genre_id): """Retrieve subgenres for the given genre""" common.debug('Requesting subgenres for genre {}', genre_id) return SubgenreList(common.make_call( 'path_request', [['genres', genre_id, 'subgenres', {'from': 0, 'to': 47}, ['id', 'name']]]))
def _save_changed_stream(self, stype, stream): common.debug('Save changed stream {} for {}', stream, stype) self.sc_settings[stype] = stream g.SHARED_DB.set_stream_continuity(g.LOCAL_DB.get_active_profile_guid(), self.current_videoid.value, self.sc_settings)
def play(videoid): """Play an episode or movie as specified by the path""" common.info('Playing {}', videoid) is_up_next_enabled = g.ADDON.getSettingBool('UpNextNotifier_enabled') metadata = [{}, {}] try: metadata = api.metadata(videoid) common.debug('Metadata is {}', metadata) except MetadataNotAvailable: common.warn('Metadata not available for {}', videoid) # Parental control PIN pin_result = _verify_pin(metadata[0].get('requiresPin', False)) if not pin_result: if pin_result is not None: ui.show_notification(common.get_local_string(30106), time=10000) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return list_item = get_inputstream_listitem(videoid) infos, art = infolabels.add_info_for_playback( videoid, list_item, skip_set_progress_status=True) resume_position = None event_data = {} if g.IS_ADDON_EXTERNAL_CALL: # Workaround for resuming strm files from library resume_position = ( infolabels.get_resume_info_from_library(videoid).get('position') if g.ADDON.getSettingBool('ResumeManager_enabled') else None) if resume_position: index_selected = (ui.ask_for_resume(resume_position) if g.ADDON.getSettingBool('ResumeManager_dialog') else None) if index_selected == -1: xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=False, listitem=list_item) return if index_selected == 1: resume_position = None elif (g.ADDON.getSettingBool('ProgressManager_enabled') and videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.EPISODE]): # To now we have this limits: # - enabled only with items played inside the addon then not Kodi library, need impl. JSON-RPC lib update code event_data = _get_event_data(videoid) event_data['videoid'] = videoid.to_dict() event_data['is_played_by_library'] = g.IS_ADDON_EXTERNAL_CALL # Todo: UpNext addon is incompatible with netflix watched status sync feature # Problems: # - Need to modify the cache (to update the watched status) on every played item # - Modifying the cache after the stop, is wrong due to below problems # - The new fast play, 'play_url' method, cause problems with the Player callbacks, after press play next is missing the Stop event in the controller! # - To verify possibility problems of data mixing of controller._get_player_state() # - The call of next video from UpNext is recognized as Skin call, because it's an external addon call, so it causes several operating problems is_up_next_enabled = False if 'raspberrypi' in common.get_system_platform( ) and '18' in common.GetKodiVersion().version: # OMX Player is not compatible with netflix video streams # Only Kodi 18 has this property, on Kodi 19 Omx Player has been removed value = common.json_rpc('Settings.GetSettingValue', {'setting': 'videoplayer.useomxplayer'}) if value.get('value'): common.json_rpc('Settings.SetSettingValue', { 'setting': 'videoplayer.useomxplayer', 'value': False }) xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=True, listitem=list_item) upnext_info = get_upnext_info(videoid, (infos, art), metadata) if is_up_next_enabled else None g.LOCAL_DB.set_value('last_videoid_played', videoid.to_dict(), table=TABLE_SESSION) common.debug('Sending initialization signal') common.send_signal(common.Signals.PLAYBACK_INITIATED, { 'videoid': videoid.to_dict(), 'infos': infos, 'art': art, 'timeline_markers': get_timeline_markers(metadata[0]), 'upnext_info': upnext_info, 'resume_position': resume_position, 'event_data': event_data }, non_blocking=True)
def extract_session_data(content, validate=False, update_profiles=False): """ Call all the parsers we need to extract all the session relevant data from the HTML page """ common.debug('Extracting session data...') react_context = extract_json(content, 'reactContext') if validate: validate_login(react_context) user_data = extract_userdata(react_context) if user_data.get('membershipStatus') == 'ANONYMOUS': # Possible known causes: # -Login password has been changed # -In the login request, 'Content-Type' specified is not compliant with data passed or no more supported # -Expired profiles cookies!? (not verified) # In these cases it is mandatory to login again raise InvalidMembershipStatusAnonymous if user_data.get('membershipStatus') != 'CURRENT_MEMBER': # When NEVER_MEMBER it is possible that the account has not been confirmed or renewed common.error('Can not login, the Membership status is {}', user_data.get('membershipStatus')) raise InvalidMembershipStatusError(user_data.get('membershipStatus')) api_data = extract_api_data(react_context) # Note: Falcor cache does not exist if membershipStatus is not CURRENT_MEMBER falcor_cache = extract_json(content, 'falcorCache') if update_profiles: parse_profiles(falcor_cache) # Extract lolomo root id lolomo_root = falcor_cache['lolomo']['value'][1] g.LOCAL_DB.set_value('lolomo_root_id', lolomo_root, TABLE_SESSION) # Check if current 'profile session' is still active # What means 'profile session': # In web browser, after you select a profile and then you close the browse page, # when you reopen it you will not be asked to select a profile again, this means that the same profile session # still active, and the lolomo root id (and child contexts id's) are still the same. # Here one way to understand this, is checking if there is an 'summary' entry in the lolomos dictionary. is_profile_session_active = 'summary' in falcor_cache['lolomos'][ lolomo_root] # Extract lolomo continueWatching id and index cw_list_data = jgraph_get('continueWatching', falcor_cache['lolomos'][lolomo_root], falcor_cache) if cw_list_data: context_index = falcor_cache['lolomos'][lolomo_root][ 'continueWatching']['value'][2] g.LOCAL_DB.set_value('lolomo_continuewatching_index', context_index, TABLE_SESSION) g.LOCAL_DB.set_value('lolomo_continuewatching_id', jgraph_get('id', cw_list_data), TABLE_SESSION) elif is_profile_session_active: # Todo: In the new profiles, there is no 'continueWatching' context # How get or generate the continueWatching context? # (needed to update lolomo list for watched state sync, see update_lolomo_context in api_requests.py) cur_profile = jgraph_get_path(['profilesList', 'current'], falcor_cache) common.warn( 'Context continueWatching not found in lolomos for profile guid {}.', jgraph_get('summary', cur_profile)['guid']) g.LOCAL_DB.set_value('lolomo_continuewatching_index', '', TABLE_SESSION) g.LOCAL_DB.set_value('lolomo_continuewatching_id', '', TABLE_SESSION) else: common.warn( 'Is not possible to find the context continueWatching, the profile session is no more active' ) # Save only some info of the current profile from user data g.LOCAL_DB.set_value('build_identifier', user_data.get('BUILD_IDENTIFIER'), TABLE_SESSION) if not g.LOCAL_DB.get_value('esn', table=TABLE_SESSION): g.LOCAL_DB.set_value('esn', generate_esn(user_data), TABLE_SESSION) g.LOCAL_DB.set_value('locale_id', user_data.get('preferredLocale').get('id', 'en-US')) # Save api urls for key, path in list(api_data.items()): g.LOCAL_DB.set_value(key, path, TABLE_SESSION) api_data['is_profile_session_active'] = is_profile_session_active return api_data
def _notify_all(self, notification, data=None): common.debug('Notifying all action managers of {} (data={})', notification.__name__, data) for manager in self.action_managers: _notify_managers(manager, notification, data)
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)) common.debug('Successfully saved MSL data to disk')
def compare_mastertoken(self, mastertoken): """Check if the new mastertoken is different from current due to renew""" if not self._mastertoken_is_newer_that(mastertoken): common.debug('MSL mastertoken is changed due to renew') self._set_mastertoken(mastertoken) self._save_msl_data()
def call_initialize(self, data): """ Initialize the manager with data when the addon initiates a playback. """ self._call_if_enabled(self.initialize, data=data) common.debug('Initialized {}: {}', self.name, self)
def initial_addon_configuration(self): """ Initial addon configuration, helps users to automatically configure addon parameters for proper viewing of videos """ run_initial_config = self.ADDON.getSettingBool( 'run_init_configuration') if run_initial_config: import resources.lib.common as common import resources.lib.kodi.ui as ui self.settings_monitor_suspended(True) system = common.get_system_platform() common.debug( 'Running initial addon configuration dialogs on system: {}'. format(system)) if system in ['osx', 'ios', 'xbox']: self.ADDON.setSettingBool('enable_vp9_profiles', False) self.ADDON.setSettingBool('enable_hevc_profiles', True) elif system == 'windows': # Currently inputstream does not support hardware video acceleration on windows, # there is no guarantee that we will get 4K without video hardware acceleration, # so no 4K configuration self.ADDON.setSettingBool('enable_vp9_profiles', True) self.ADDON.setSettingBool('enable_hevc_profiles', False) elif system == 'android': ultrahd_capable_device = False premium_account = ui.ask_for_confirmation( common.get_local_string(30154), common.get_local_string(30155)) if premium_account: ultrahd_capable_device = ui.ask_for_confirmation( common.get_local_string(30154), common.get_local_string(30156)) if ultrahd_capable_device: ui.show_ok_dialog(common.get_local_string(30154), common.get_local_string(30157)) ia_enabled = xbmc.getCondVisibility( 'System.HasAddon(inputstream.adaptive)') if ia_enabled: xbmc.executebuiltin( 'Addon.OpenSettings(inputstream.adaptive)') else: ui.show_ok_dialog(common.get_local_string(30154), common.get_local_string(30046)) self.ADDON.setSettingBool('enable_vp9_profiles', False) self.ADDON.setSettingBool('enable_hevc_profiles', True) else: # VP9 should have better performance since there is no need for 4k self.ADDON.setSettingBool('enable_vp9_profiles', True) self.ADDON.setSettingBool('enable_hevc_profiles', False) self.ADDON.setSettingBool('enable_force_hdcp', ultrahd_capable_device) elif system == 'linux': # Too many different linux systems, we can not predict all the behaviors # Some linux distributions have encountered problems with VP9, # OMSC users complain that hevc creates problems self.ADDON.setSettingBool('enable_vp9_profiles', False) self.ADDON.setSettingBool('enable_hevc_profiles', False) else: self.ADDON.setSettingBool('enable_vp9_profiles', False) self.ADDON.setSettingBool('enable_hevc_profiles', False) self.ADDON.setSettingBool('run_init_configuration', False) self.settings_monitor_suspended(False)
def _on_change(self): common.reset_log_level_global_var() common.debug( 'SettingsMonitor: settings have been changed, started checks') reboot_addon = False clean_cache = False use_mysql = g.ADDON.getSettingBool('use_mysql') use_mysql_old = g.LOCAL_DB.get_value('use_mysql', False, TABLE_SETTINGS_MONITOR) use_mysql_turned_on = use_mysql and not use_mysql_old common.debug( 'SettingsMonitor: Reinitialization of service global settings') g.init_globals(sys.argv, use_mysql != use_mysql_old) # Check the MySQL connection status after reinitialization of service global settings use_mysql_after = g.ADDON.getSettingBool('use_mysql') if use_mysql_turned_on and use_mysql_after: g.LOCAL_DB.set_value('use_mysql', True, TABLE_SETTINGS_MONITOR) ui.show_notification(g.ADDON.getLocalizedString(30202)) if not use_mysql_after and use_mysql_old: g.LOCAL_DB.set_value('use_mysql', False, TABLE_SETTINGS_MONITOR) # Check if the custom esn is changed custom_esn = g.ADDON.getSetting('esn') custom_esn_old = g.LOCAL_DB.get_value('custom_esn', '', TABLE_SETTINGS_MONITOR) if custom_esn != custom_esn_old: g.LOCAL_DB.set_value('custom_esn', custom_esn, TABLE_SETTINGS_MONITOR) common.send_signal(signal=common.Signals.ESN_CHANGED, data=g.get_esn()) # Check menu settings changes for menu_id, menu_data in iteritems(g.MAIN_MENU_ITEMS): # Check settings changes in show menu show_menu_new_setting = bool( g.ADDON.getSettingBool('_'.join(('show_menu', menu_id)))) show_menu_old_setting = g.LOCAL_DB.get_value( 'menu_{}_show'.format(menu_id), True, TABLE_SETTINGS_MONITOR) if show_menu_new_setting != show_menu_old_setting: g.LOCAL_DB.set_value('menu_{}_show'.format(menu_id), show_menu_new_setting, TABLE_SETTINGS_MONITOR) reboot_addon = True # Check settings changes in sort order of menu if menu_data.get('request_context_name'): menu_sortorder_new_setting = int( g.ADDON.getSettingInt('menu_sortorder_' + menu_data['path'][1])) menu_sortorder_old_setting = g.LOCAL_DB.get_value( 'menu_{}_sortorder'.format(menu_id), 0, TABLE_SETTINGS_MONITOR) if menu_sortorder_new_setting != menu_sortorder_old_setting: g.LOCAL_DB.set_value('menu_{}_sortorder'.format(menu_id), menu_sortorder_new_setting, TABLE_SETTINGS_MONITOR) # We remove the cache to allow get the new results in the chosen order g.CACHE.clear([CACHE_COMMON, CACHE_MYLIST, CACHE_SEARCH]) # Check changes on content profiles # This is necessary because it is possible that some manifests # could be cached using the previous settings (see msl_handler - load_manifest) menu_keys = [ 'enable_dolby_sound', 'enable_vp9_profiles', 'enable_hevc_profiles', 'enable_hdr_profiles', 'enable_dolbyvision_profiles', 'enable_force_hdcp', 'disable_webvtt_subtitle' ] collect_int = '' for menu_key in menu_keys: collect_int += unicode(int(g.ADDON.getSettingBool(menu_key))) collect_int_old = g.LOCAL_DB.get_value('content_profiles_int', '', TABLE_SETTINGS_MONITOR) if collect_int != collect_int_old: g.LOCAL_DB.set_value('content_profiles_int', collect_int, TABLE_SETTINGS_MONITOR) g.CACHE.clear([CACHE_MANIFESTS]) # Check if Progress Manager settings is changed progress_manager_enabled = g.ADDON.getSettingBool( 'ProgressManager_enabled') progress_manager_enabled_old = g.LOCAL_DB.get_value( 'progress_manager_enabled', False, TABLE_SETTINGS_MONITOR) if progress_manager_enabled != progress_manager_enabled_old: g.LOCAL_DB.set_value('progress_manager_enabled', progress_manager_enabled, TABLE_SETTINGS_MONITOR) common.send_signal(signal=common.Signals.SWITCH_EVENTS_HANDLER, data=progress_manager_enabled) # Get or reset the lolomo data common.send_signal(signal='update_lolomo_data') # Avoid perform these operations when the add-on is installed from scratch and there are no credentials if (clean_cache or reboot_addon) and not common.check_credentials(): reboot_addon = False if reboot_addon: common.debug('SettingsMonitor: addon will be rebooted') url = 'plugin://plugin.video.netflix/directory/root' # Open root page xbmc.executebuiltin( 'Container.Update({})'.format(url)) # replace=reset history
def profiles(self, pathitems=None): # pylint: disable=unused-argument """Show profiles listing""" common.debug('Showing profiles listing') list_data, extra_data = common.make_call('get_profiles', {'request_update': True}) self._profiles(list_data, extra_data)
def _play(videoid, is_played_from_strm=False): """Play an episode or movie as specified by the path""" is_upnext_enabled = G.ADDON.getSettingBool('UpNextNotifier_enabled') common.info('Playing {}{}{}', videoid, ' [STRM file]' if is_played_from_strm else '', ' [external call]' if G.IS_ADDON_EXTERNAL_CALL else '') # Profile switch when playing from a STRM file (library) if is_played_from_strm: if not _profile_switch(): xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) return # Get metadata of videoid try: metadata = api.get_metadata(videoid) common.debug('Metadata is {}', metadata) except MetadataNotAvailable: common.warn('Metadata not available for {}', videoid) metadata = [{}, {}] # Check parental control PIN pin_result = _verify_pin(metadata[0].get('requiresPin', False)) if not pin_result: if pin_result is not None: ui.show_notification(common.get_local_string(30106), time=8000) xbmcplugin.endOfDirectory(G.PLUGIN_HANDLE, succeeded=False) return # Generate the xbmcgui.ListItem to be played list_item = get_inputstream_listitem(videoid) # STRM file resume workaround (Kodi library) resume_position = _strm_resume_workaroud(is_played_from_strm, videoid) if resume_position == '': xbmcplugin.setResolvedUrl(handle=G.PLUGIN_HANDLE, succeeded=False, listitem=list_item) return info_data = None event_data = {} videoid_next_episode = None # Get Infolabels and Arts for the videoid to be played, and for the next video if it is an episode (for UpNext) if is_played_from_strm or is_upnext_enabled or G.IS_ADDON_EXTERNAL_CALL: if is_upnext_enabled and videoid.mediatype == common.VideoId.EPISODE: # When UpNext is enabled, get the next episode to play videoid_next_episode = _upnext_get_next_episode_videoid( videoid, metadata) info_data = infolabels.get_info_from_netflix( [videoid, videoid_next_episode] if videoid_next_episode else [videoid]) info, arts = info_data[videoid.value] # When a item is played from Kodi library or Up Next add-on is needed set info and art to list_item list_item.setInfo('video', info) list_item.setArt(arts) # Get event data for videoid to be played (needed for sync of watched status with Netflix) if (G.ADDON.getSettingBool('ProgressManager_enabled') and videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.EPISODE]): if not is_played_from_strm or is_played_from_strm and G.ADDON.getSettingBool( 'sync_watched_status_library'): event_data = _get_event_data(videoid) event_data['videoid'] = videoid.to_dict() event_data['is_played_by_library'] = is_played_from_strm if 'raspberrypi' in common.get_system_platform(): _raspberry_disable_omxplayer() # Start and initialize the action controller (see action_controller.py) common.debug('Sending initialization signal') common.send_signal(common.Signals.PLAYBACK_INITIATED, { 'videoid': videoid.to_dict(), 'videoid_next_episode': videoid_next_episode.to_dict() if videoid_next_episode else None, 'metadata': metadata, 'info_data': info_data, 'is_played_from_strm': is_played_from_strm, 'resume_position': resume_position, 'event_data': event_data }, non_blocking=True) # Send callback after send the initialization signal # to give a bit of more time to the action controller (see note in initialize_playback of action_controller.py) xbmcplugin.setResolvedUrl(handle=G.PLUGIN_HANDLE, succeeded=True, listitem=list_item)
def _on_change(self): common.debug( 'SettingsMonitor: settings have been changed, started checks') reboot_addon = False use_mysql = g.ADDON.getSettingBool('use_mysql') use_mysql_old = g.LOCAL_DB.get_value('use_mysql', False, TABLE_SETTINGS_MONITOR) use_mysql_turned_on = use_mysql and not use_mysql_old common.debug('SettingsMonitor: Reinitialization of global settings') g.init_globals(sys.argv, reboot_addon) # Check the MySQL connection status after reinitialization of global settings use_mysql_after = g.ADDON.getSettingBool('use_mysql') if use_mysql_turned_on and use_mysql_after: g.LOCAL_DB.set_value('use_mysql', True, TABLE_SETTINGS_MONITOR) ui.show_notification(g.ADDON.getLocalizedString(30202)) if not use_mysql_after and use_mysql_old: g.LOCAL_DB.set_value('use_mysql', False, TABLE_SETTINGS_MONITOR) # Check if the custom esn is changed custom_esn = g.ADDON.getSetting('esn') custom_esn_old = g.LOCAL_DB.get_value('custom_esn', '', TABLE_SETTINGS_MONITOR) if custom_esn != custom_esn_old: g.LOCAL_DB.set_value('custom_esn', custom_esn, TABLE_SETTINGS_MONITOR) common.send_signal(signal=common.Signals.ESN_CHANGED, data=g.get_esn()) # Check menu settings changes sort_order_type_changed = False for menu_id, menu_data in g.MAIN_MENU_ITEMS.iteritems(): # Check settings changes in show menu show_menu_new_setting = bool( g.ADDON.getSettingBool('_'.join(('show_menu', menu_id)))) show_menu_old_setting = g.LOCAL_DB.get_value( 'menu_{}_show'.format(menu_id), True, TABLE_SETTINGS_MONITOR) if show_menu_new_setting != show_menu_old_setting: g.LOCAL_DB.set_value('menu_{}_show'.format(menu_id), show_menu_new_setting, TABLE_SETTINGS_MONITOR) reboot_addon = True # Check settings changes in sort order of menu if menu_data.get('request_context_name'): menu_sortorder_new_setting = int( g.ADDON.getSettingInt('_'.join( ('menu_sortorder', menu_data['path'][1])))) menu_sortorder_old_setting = g.LOCAL_DB.get_value( 'menu_{}_sortorder'.format(menu_id), 0, TABLE_SETTINGS_MONITOR) if menu_sortorder_new_setting != menu_sortorder_old_setting: g.LOCAL_DB.set_value('menu_{}_sortorder'.format(menu_id), menu_sortorder_new_setting, TABLE_SETTINGS_MONITOR) sort_order_type_changed = True if sort_order_type_changed: # We remove the cache to allow get the new results in the chosen order common.run_plugin( 'plugin://plugin.video.netflix/action/purge_cache/' '?on_disk=True&no_notification=True') if reboot_addon: common.debug('SettingsMonitor: addon will be rebooted') url = 'plugin://plugin.video.netflix/directory/root' xbmc.executebuiltin( 'XBMC.Container.Update(path,replace)') # Clean path history xbmc.executebuiltin( 'Container.Update({})'.format(url)) # Open root page
def play(videoid): """Play an episode or movie as specified by the path""" is_upnext_enabled = g.ADDON.getSettingBool('UpNextNotifier_enabled') # For db settings 'upnext_play_callback_received' and 'upnext_play_callback_file_type' see controller.py is_upnext_callback_received = g.LOCAL_DB.get_value( 'upnext_play_callback_received', False) is_upnext_callback_file_type_strm = g.LOCAL_DB.get_value( 'upnext_play_callback_file_type', '') == 'strm' # This is the only way found to know if the played item come from the add-on itself or from Kodi library # also when Up Next Add-on is used is_played_from_addon = not g.IS_ADDON_EXTERNAL_CALL or ( g.IS_ADDON_EXTERNAL_CALL and is_upnext_callback_received and not is_upnext_callback_file_type_strm) common.info('Playing {} from {} (Is Up Next Add-on call: {})', videoid, 'add-on' if is_played_from_addon else 'external call', is_upnext_callback_received) metadata = [{}, {}] try: metadata = api.get_metadata(videoid) common.debug('Metadata is {}', metadata) except MetadataNotAvailable: common.warn('Metadata not available for {}', videoid) # Parental control PIN pin_result = _verify_pin(metadata[0].get('requiresPin', False)) if not pin_result: if pin_result is not None: ui.show_notification(common.get_local_string(30106), time=8000) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return list_item = get_inputstream_listitem(videoid) resume_position = None info_data = None event_data = {} videoid_next_episode = None if not is_played_from_addon or is_upnext_enabled: if is_upnext_enabled: # When UpNext is enabled, get the next episode to play videoid_next_episode = _upnext_get_next_episode_videoid( videoid, metadata) info_data = infolabels.get_info_from_netflix( [videoid, videoid_next_episode] if videoid_next_episode else [videoid]) info, arts = info_data[videoid.value] # When a item is played from Kodi library or Up Next add-on is needed set info and art to list_item list_item.setInfo('video', info) list_item.setArt(arts) if not is_played_from_addon: # Workaround for resuming strm files from library resume_position = ( infolabels.get_resume_info_from_library(videoid).get('position') if g.ADDON.getSettingBool('ResumeManager_enabled') else None) if resume_position: index_selected = (ui.ask_for_resume(resume_position) if g.ADDON.getSettingBool('ResumeManager_dialog') else None) if index_selected == -1: xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=False, listitem=list_item) return if index_selected == 1: resume_position = None if (g.ADDON.getSettingBool('ProgressManager_enabled') and videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.EPISODE] and is_played_from_addon): # Enable the progress manager only when: # - It is not an add-on external call # - It is an external call, but the played item is not a STRM file # Todo: # in theory to enable in Kodi library need implement the update watched status code for items of Kodi library # by using JSON RPC Files.SetFileDetails https://github.com/xbmc/xbmc/pull/17202 # that can be used only on Kodi 19.x event_data = _get_event_data(videoid) event_data['videoid'] = videoid.to_dict() event_data['is_played_by_library'] = not is_played_from_addon if 'raspberrypi' in common.get_system_platform( ) and g.KODI_VERSION.is_major_ver('18'): # OMX Player is not compatible with netflix video streams # Only Kodi 18 has this property, on Kodi 19 Omx Player has been removed value = common.json_rpc('Settings.GetSettingValue', {'setting': 'videoplayer.useomxplayer'}) if value.get('value'): common.json_rpc('Settings.SetSettingValue', { 'setting': 'videoplayer.useomxplayer', 'value': False }) xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=True, listitem=list_item) g.LOCAL_DB.set_value('last_videoid_played', videoid.to_dict(), table=TABLE_SESSION) common.debug('Sending initialization signal') common.send_signal(common.Signals.PLAYBACK_INITIATED, { 'videoid': videoid.to_dict(), 'videoid_next_episode': videoid_next_episode.to_dict() if videoid_next_episode else None, 'metadata': metadata, 'info_data': info_data, 'is_played_from_addon': is_played_from_addon, 'resume_position': resume_position, 'event_data': event_data, 'is_upnext_callback_received': is_upnext_callback_received }, non_blocking=True)
def _parse_profile(profile, falkor_cache): _profile = profile['summary']['value'] common.debug('Parsing profile {}'.format(_profile['guid'])) _profile['avatar'] = _get_avatar(falkor_cache, profile) return _profile, _profile['isActive']
def _skip_section(self, section): common.debug('Entered section {}', section) if self.auto_skip: self._auto_skip(section) else: self._ask_to_skip(section)
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) if 'result' in manifest: return manifest['result'] return manifest
def create_database(config): """Create a new database""" db_name = config.pop('database', None) common.debug( 'The MySQL database {} does not exist, creating a new one'.format( db_name)) conn = mysql.connector.connect(**config) cur = conn.cursor() schema = ('CREATE DATABASE netflix_addon ' 'CHARACTER SET utf8mb4 ' 'COLLATE utf8mb4_unicode_ci;') cur.execute(schema) table = ( 'CREATE TABLE netflix_addon.profiles (' 'ID INT(11) NOT NULL AUTO_INCREMENT,' 'Guid VARCHAR(50) NOT NULL,' 'SortOrder INT(11) NOT NULL,' 'PRIMARY KEY (ID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') alter_tbl = ('ALTER TABLE netflix_addon.profiles ' 'ADD UNIQUE INDEX Guid(Guid);') cur.execute(table) cur.execute(alter_tbl) table = ( 'CREATE TABLE netflix_addon.shared_app_config (' 'ID INT(11) NOT NULL AUTO_INCREMENT,' 'Name VARCHAR(100) NOT NULL,' 'Value TEXT DEFAULT NULL,' 'PRIMARY KEY (ID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') alter_tbl = ('ALTER TABLE netflix_addon.shared_app_config ' 'ADD UNIQUE INDEX Name_UNIQUE(Name);') cur.execute(table) cur.execute(alter_tbl) table = ( 'CREATE TABLE netflix_addon.stream_continuity (' 'ProfileGuid VARCHAR(50) NOT NULL,' 'VideoID INT(11) NOT NULL,' 'Value TEXT DEFAULT NULL,' 'DateLastModified VARCHAR(50) NOT NULL,' 'PRIMARY KEY (ProfileGuid, VideoID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') alter_tbl = ( 'ALTER TABLE netflix_addon.stream_continuity ' 'ADD CONSTRAINT FK_streamcontinuity_ProfileGuid FOREIGN KEY (ProfileGuid)' 'REFERENCES netflix_addon.profiles(Guid) ON DELETE CASCADE ON UPDATE CASCADE;' ) cur.execute(table) cur.execute(alter_tbl) table = ( 'CREATE TABLE netflix_addon.video_lib_episodes (' 'EpisodeID INT(11) NOT NULL,' 'SeasonID INT(11) NOT NULL,' 'FilePath TEXT DEFAULT NULL,' 'PRIMARY KEY (EpisodeID, SeasonID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') cur.execute(table) table = ( 'CREATE TABLE netflix_addon.video_lib_movies (' 'MovieID INT(11) NOT NULL,' 'FilePath TEXT DEFAULT NULL,' 'NfoExport VARCHAR(5) NOT NULL DEFAULT \'False\',' 'PRIMARY KEY (MovieID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') cur.execute(table) table = ( 'CREATE TABLE netflix_addon.video_lib_seasons (' 'TvShowID INT(11) NOT NULL,' 'SeasonID INT(11) NOT NULL,' 'PRIMARY KEY (TvShowID, SeasonID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') cur.execute(table) table = ( 'CREATE TABLE netflix_addon.video_lib_tvshows (' 'TvShowID INT(11) NOT NULL,' 'ExcludeUpdate VARCHAR(5) NOT NULL DEFAULT \'False\',' 'NfoExport VARCHAR(5) NOT NULL DEFAULT \'False\',' 'PRIMARY KEY (TvShowID))' 'ENGINE = INNODB, CHARACTER SET utf8mb4, COLLATE utf8mb4_unicode_ci;') alter_tbl = ('ALTER TABLE netflix_addon.video_lib_tvshows ' 'ADD UNIQUE INDEX UK_videolibtvshows_TvShowID(TvShowID);') cur.execute(table) cur.execute(alter_tbl) if conn and conn.is_connected(): conn.close()
def _load_manifest(self, viewable_id, esn): cache_identifier = g.LOCAL_DB.get_active_profile_guid( ) + '_' + 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) if common.is_debug_verbose(): # 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'] 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 }], '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.CACHE_MANIFESTS, cache_identifier, manifest, eol=expiration) if 'result' in manifest: return manifest['result'] return manifest
def set_response(self, response): self.response_data = response common.debug('EVENT [{}] - Request response: {}', self.event_type, response) # Seem that malformed requests are ignored without returning errors # self.status = self.STATUS_ERROR self.status = self.STATUS_SUCCESS
def perpetual_path_request(self, paths, length_params, perpetual_range_start=None): """Perform a perpetual path request against the Shakti API to retrieve a possibly large video list. If the requested video list's size is larger than MAX_PATH_REQUEST_SIZE, multiple path requests will be executed with forward shifting range selectors and the results will be combined into one path response.""" response_type, length_args = length_params context_name = length_args[0] response_length = apipaths.LENGTH_ATTRIBUTES[response_type] request_size = apipaths.MAX_PATH_REQUEST_SIZE response_size = request_size + 1 # Note: when the request is made with 'genres' context, # the response strangely does not respect the number of objects # requested, returning 1 more item, i couldn't understand why if context_name == 'genres': response_size += 1 number_of_requests = 2 perpetual_range_start = int( perpetual_range_start) if perpetual_range_start else 0 range_start = perpetual_range_start range_end = range_start + request_size merged_response = {} for n_req in range(number_of_requests): path_response = self._path_request( _set_range_selector(paths, range_start, range_end)) if len(path_response) != 0: common.merge_dicts(path_response, merged_response) response_count = response_length(path_response, *length_args) if response_count >= response_size: range_start += response_size if n_req == (number_of_requests - 1): merged_response['_perpetual_range_selector'] = { 'next_start': range_start } common.debug( '{} has other elements, added _perpetual_range_selector item' .format(response_type)) else: range_end = range_start + request_size else: #There are no other elements to request break else: break if perpetual_range_start > 0: previous_start = perpetual_range_start - (response_size * number_of_requests) if '_perpetual_range_selector' in merged_response: merged_response['_perpetual_range_selector'][ 'previous_start'] = previous_start else: merged_response['_perpetual_range_selector'] = { 'previous_start': previous_start } return merged_response
def _perpetual_path_request(self, paths, length_params, perpetual_range_start=None, no_limit_req=False): """Perform a perpetual path request against the Shakti API to retrieve a possibly large video list. If the requested video list's size is larger than MAX_PATH_REQUEST_SIZE, multiple path requests will be executed with forward shifting range selectors and the results will be combined into one path response.""" response_type, length_args = length_params context_name = length_args[0] response_length = apipaths.LENGTH_ATTRIBUTES[response_type] request_size = apipaths.MAX_PATH_REQUEST_SIZE response_size = request_size + 1 # Note: when the request is made with 'genres' or 'seasons' context, # the response strangely does not respect the number of objects # requested, returning 1 more item, i couldn't understand why if context_name in ['genres', 'seasons']: response_size += 1 number_of_requests = 100 if no_limit_req else 2 perpetual_range_start = int( perpetual_range_start) if perpetual_range_start else 0 range_start = perpetual_range_start range_end = range_start + request_size merged_response = {} for n_req in range(number_of_requests): path_response = self._path_request( _set_range_selector(paths, range_start, range_end)) if not path_response: break if not common.check_path_exists(length_args, path_response): # It may happen that the number of items to be received # is equal to the number of the response_size # so a second round will be performed, which will return an empty list break common.merge_dicts(path_response, merged_response) response_count = response_length(path_response, *length_args) if response_count < response_size: # There are no other elements to request break range_start += response_size if n_req == (number_of_requests - 1): merged_response['_perpetual_range_selector'] = { 'next_start': range_start } common.debug( '{} has other elements, added _perpetual_range_selector item', response_type) else: range_end = range_start + request_size if perpetual_range_start > 0: previous_start = perpetual_range_start - (response_size * number_of_requests) if '_perpetual_range_selector' in merged_response: merged_response['_perpetual_range_selector'][ 'previous_start'] = previous_start else: merged_response['_perpetual_range_selector'] = { 'previous_start': previous_start } return merged_response
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.PERSISTENT_STORAGE['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 play(videoid): """Play an episode or movie as specified by the path""" is_upnext_enabled = g.ADDON.getSettingBool('UpNextNotifier_enabled') # For db settings 'upnext_play_callback_received' and 'upnext_play_callback_file_type' see action_controller.py is_upnext_callback_received = g.LOCAL_DB.get_value('upnext_play_callback_received', False) is_upnext_callback_file_type_strm = g.LOCAL_DB.get_value('upnext_play_callback_file_type', '') == 'strm' # This is the only way found to know if the played item come from the add-on itself or from Kodi library # also when Up Next Add-on is used is_played_from_addon = not g.IS_ADDON_EXTERNAL_CALL or (g.IS_ADDON_EXTERNAL_CALL and is_upnext_callback_received and not is_upnext_callback_file_type_strm) common.info('Playing {} from {} (Is Up Next Add-on call: {})', videoid, 'add-on' if is_played_from_addon else 'external call', is_upnext_callback_received) # Profile switch when playing from Kodi library if not is_played_from_addon: if not _profile_switch(): xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return # Get metadata of videoid try: metadata = api.get_metadata(videoid) common.debug('Metadata is {}', metadata) except MetadataNotAvailable: common.warn('Metadata not available for {}', videoid) metadata = [{}, {}] # Check parental control PIN pin_result = _verify_pin(metadata[0].get('requiresPin', False)) if not pin_result: if pin_result is not None: ui.show_notification(common.get_local_string(30106), time=8000) xbmcplugin.endOfDirectory(g.PLUGIN_HANDLE, succeeded=False) return # Generate the xbmcgui.ListItem to be played list_item = get_inputstream_listitem(videoid) # STRM file resume workaround (Kodi library) resume_position = _strm_resume_workaroud(is_played_from_addon, videoid) if resume_position == '': xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=False, listitem=list_item) return info_data = None event_data = {} videoid_next_episode = None # Get Infolabels and Arts for the videoid to be played, and for the next video if it is an episode (for UpNext) if not is_played_from_addon or is_upnext_enabled: if is_upnext_enabled and videoid.mediatype == common.VideoId.EPISODE: # When UpNext is enabled, get the next episode to play videoid_next_episode = _upnext_get_next_episode_videoid(videoid, metadata) info_data = infolabels.get_info_from_netflix( [videoid, videoid_next_episode] if videoid_next_episode else [videoid]) info, arts = info_data[videoid.value] # When a item is played from Kodi library or Up Next add-on is needed set info and art to list_item list_item.setInfo('video', info) list_item.setArt(arts) # Get event data for videoid to be played (needed for sync of watched status with Netflix) if (g.ADDON.getSettingBool('ProgressManager_enabled') and videoid.mediatype in [common.VideoId.MOVIE, common.VideoId.EPISODE] and is_played_from_addon): # Enable the progress manager only when: # - It is not an add-on external call # - It is an external call, but the played item is not a STRM file # Todo: # in theory to enable in Kodi library need implement the update watched status code for items of Kodi library # by using JSON RPC Files.SetFileDetails https://github.com/xbmc/xbmc/pull/17202 # that can be used only on Kodi 19.x event_data = _get_event_data(videoid) event_data['videoid'] = videoid.to_dict() event_data['is_played_by_library'] = not is_played_from_addon if 'raspberrypi' in common.get_system_platform(): _raspberry_disable_omxplayer() xbmcplugin.setResolvedUrl(handle=g.PLUGIN_HANDLE, succeeded=True, listitem=list_item) g.LOCAL_DB.set_value('last_videoid_played', videoid.to_dict(), table=TABLE_SESSION) # Start and initialize the action controller (see action_controller.py) common.debug('Sending initialization signal') common.send_signal(common.Signals.PLAYBACK_INITIATED, { 'videoid': videoid.to_dict(), 'videoid_next_episode': videoid_next_episode.to_dict() if videoid_next_episode else None, 'metadata': metadata, 'info_data': info_data, 'is_played_from_addon': is_played_from_addon, 'resume_position': resume_position, 'event_data': event_data, 'is_upnext_callback_received': is_upnext_callback_received}, non_blocking=True)
def home(self, pathitems=None, cache_to_disc=True): """Show home listing""" # pylint: disable=unused-argument common.debug('Showing root video lists') listings.build_main_menu_listing(api.root_lists()) _handle_endofdirectory(False, cache_to_disc)