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)
Ejemplo n.º 2
0
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'])
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
 def __init__(self, params):
     common.debug('Initializing "LibraryActionExecutor" with params: {}',
                  params)
     self.params = params
Ejemplo n.º 5
0
 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)
Ejemplo n.º 7
0
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']]]))
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 12
0
 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')
Ejemplo n.º 13
0
 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()
Ejemplo n.º 14
0
 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)
Ejemplo n.º 15
0
    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
Ejemplo n.º 17
0
 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)
Ejemplo n.º 18
0
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
Ejemplo n.º 20
0
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)
Ejemplo n.º 21
0
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)
Ejemplo n.º 23
0
    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()
Ejemplo n.º 25
0
    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
Ejemplo n.º 26
0
 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
Ejemplo n.º 27
0
    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
Ejemplo n.º 28
0
    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
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
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)
Ejemplo n.º 31
0
 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)