def migrate_library(): # Migrate the Kodi library to the new format of STRM path # - Old STRM: '/play/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used before ver 1.7.0) # - New STRM: '/play_strm/show/xxxxxxxx/season/xxxxxxxx/episode/xxxxxxxx/' (used from ver 1.7.0) folders = get_library_subfolders( FOLDER_NAME_MOVIES) + get_library_subfolders(FOLDER_NAME_SHOWS) if not folders: return LOG.debug('Start migrating STRM files') try: with ui.ProgressDialog(True, title='Migrating library to new format', max_value=len(folders)) as progress_bar: for folder_path in folders: folder_name = os.path.basename( G.py2_decode(xbmc.translatePath(folder_path))) progress_bar.set_message('PLEASE WAIT - Migrating: ' + folder_name) _migrate_strm_files(folder_path) except Exception as exc: # pylint: disable=broad-except LOG.error('Migrating failed: {}', exc) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) ui.show_ok_dialog('Migrating library to new format', ( 'Library migration has failed.[CR]' 'Before try play a Netflix video from library, you must run manually the library migration, ' 'otherwise you will have add-on malfunctions.[CR][CR]' 'Open add-on settings on "Library" section, and select "Import existing library".' ))
def parse_profiles(data): """Parse profile information from Netflix response""" profiles_list = jgraph_get_list('profilesList', data) try: if not profiles_list: raise InvalidProfilesError('It has not been possible to obtain the list of profiles.') sort_order = 0 current_guids = [] for index, profile_data in iteritems(profiles_list): # pylint: disable=unused-variable summary = jgraph_get('summary', profile_data) guid = summary['guid'] current_guids.append(guid) LOG.debug('Parsing profile {}', summary['guid']) avatar_url = _get_avatar(profile_data, data, guid) is_active = summary.pop('isActive') G.LOCAL_DB.set_profile(guid, is_active, sort_order) G.SHARED_DB.set_profile(guid, sort_order) # Add profile language description translated from locale summary['language_desc'] = G.py2_decode(xbmc.convertLanguage(summary['language'][:2], xbmc.ENGLISH_NAME)) if LOG.level == LOG.LEVEL_VERBOSE: for key, value in iteritems(summary): if key in PROFILE_DEBUG_INFO: LOG.debug('Profile info {}', {key: value}) # Translate the profile name, is coded as HTML summary['profileName'] = parse_html(summary['profileName']) summary['avatar'] = avatar_url G.LOCAL_DB.insert_profile_configs(summary, guid) sort_order += 1 _delete_non_existing_profiles(current_guids) except Exception as exc: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) LOG.error('Profile list data: {}', profiles_list) raise_from(InvalidProfilesError, exc)
def load(account_hash): """Load cookies for a given account and check them for validity""" filename = cookie_filename(account_hash) if not xbmcvfs.exists(filename): common.debug('Cookies file does not exist') raise MissingCookiesError() common.debug('Loading cookies from {}', G.py2_decode(filename)) cookie_file = xbmcvfs.File(filename, 'rb') try: if G.PY_IS_VER2: # pickle.loads on py2 wants string cookie_jar = pickle.loads(cookie_file.read()) else: cookie_jar = pickle.loads(cookie_file.readBytes()) except Exception as exc: # pylint: disable=broad-except import traceback common.error('Failed to load cookies from file: {exc}', exc=exc) common.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise MissingCookiesError() finally: cookie_file.close() # Clear flwssn cookie if present, as it is trouble with early expiration if 'flwssn' in cookie_jar: cookie_jar.clear(domain='.netflix.com', path='/', name='flwssn') log_cookie(cookie_jar) return cookie_jar
def rename_cookie_file(): # The file "COOKIE_xxxxxx..." will be renamed to "COOKIES" list_files = list_dir(G.DATA_PATH)[1] for filename in list_files: if 'COOKIE_' in G.py2_decode(filename): copy_file(join_folders_paths(G.DATA_PATH, G.py2_decode(filename)), join_folders_paths(G.DATA_PATH, 'COOKIES')) xbmc.sleep(80) delete_file(G.py2_decode(filename))
def delete_cache_folder(): # Delete cache folder in the add-on userdata (no more needed with the new cache management) cache_path = os.path.join(G.DATA_PATH, 'cache') if not os.path.exists(G.py2_decode(xbmc.translatePath(cache_path))): return LOG.debug('Deleting the cache folder from add-on userdata folder') try: delete_folder_contents(cache_path, True) xbmc.sleep(80) xbmcvfs.rmdir(cache_path) except Exception: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
def get_upnext_info(videoid, videoid_next_episode, info_data, metadata, is_played_from_strm): """Get the data to send to Up Next add-on""" upnext_info = { 'current_episode': _upnext_info(videoid, *info_data[videoid.value]), 'next_episode': _upnext_info(videoid_next_episode, *info_data[videoid_next_episode.value]) } if is_played_from_strm: # The current video played is a STRM, then generate the path of next STRM file file_path = G.SHARED_DB.get_episode_filepath( videoid_next_episode.tvshowid, videoid_next_episode.seasonid, videoid_next_episode.episodeid) url = G.py2_decode(translatePath(file_path)) else: url = common.build_url( videoid=videoid_next_episode, mode=G.MODE_PLAY, params={'profile_guid': G.LOCAL_DB.get_active_profile_guid()}) upnext_info['play_url'] = url if 'creditsOffset' in metadata[0]: upnext_info['notification_offset'] = metadata[0]['creditsOffset'] return upnext_info
def get_library_subfolders(folder_name): """Returns all the subfolders contained in a folder of library path""" section_path = common.join_folders_paths(get_library_path(), folder_name) return [ common.join_folders_paths(section_path, G.py2_decode(folder)) for folder in common.list_dir(section_path)[0] ]
def _load_msl_data(self, msl_data): try: self.crypto.load_msl_data(msl_data) self.crypto.load_crypto_session(msl_data) except Exception: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1'))
def get_local_db_path(db_filename): # First ensure database folder exists db_folder = G.py2_decode( xbmc.translatePath(os.path.join(G.DATA_PATH, 'database'))) if not folder_exists(db_folder): xbmcvfs.mkdirs(db_folder) return os.path.join(db_folder, db_filename)
def login(self, credentials=None): """Perform account login""" try: # First we get the authentication url without logging in, required for login API call react_context = website.extract_json(self.get('login'), 'reactContext') auth_url = website.extract_api_data(react_context)['auth_url'] LOG.debug('Logging in...') login_response = self.post( 'login', headers={'Accept-Language': _get_accept_language_string(react_context)}, data=_login_payload(credentials or common.get_credentials(), auth_url, react_context)) website.extract_session_data(login_response, validate=True, update_profiles=True) if credentials: # Save credentials only when login has succeeded common.set_credentials(credentials) LOG.info('Login successful') ui.show_notification(common.get_local_string(30109)) cookies.save(self.account_hash, self.session.cookies) return True except LoginValidateError as exc: self.session.cookies.clear() common.purge_credentials() raise_from(LoginError(unicode(exc)), exc) except (MbrStatusNeverMemberError, MbrStatusFormerMemberError) as exc: self.session.cookies.clear() LOG.warn('Membership status {} not valid for login', exc) raise_from(LoginError(common.get_local_string(30180)), exc) except Exception: # pylint: disable=broad-except self.session.cookies.clear() import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise
def make_http_call_cache(callname, params, data): """Make an IPC call via HTTP and wait for it to return. The contents of data will be expanded to kwargs and passed into the target function.""" try: # Python 3 from urllib.request import build_opener, install_opener, ProxyHandler, HTTPError, URLError, Request, urlopen except ImportError: # Python 2 from urllib2 import build_opener, install_opener, ProxyHandler, HTTPError, URLError, Request, urlopen import json # debug('Handling HTTP IPC call to {}'.format(callname)) # Note: On python 3, using 'localhost' slowdown the call (Windows OS is affected) not sure if it is an urllib issue url = 'http://127.0.0.1:{}/{}'.format(G.LOCAL_DB.get_value('cache_service_port', 8002), callname) install_opener(build_opener(ProxyHandler({}))) # don't use proxy for localhost r = Request(url=url, data=data, headers={'Params': json.dumps(params)}) try: result = urlopen(r, timeout=IPC_TIMEOUT_SECS).read() except HTTPError as exc: if exc.reason in exceptions.__dict__: raise_from(exceptions.__dict__[exc.reason], exc) raise_from(Exception('The service has returned: {}'.format(exc.reason)), exc) except URLError as exc: # On PY2 the exception message have to be decoded with latin-1 for system with symbolic characters err_msg = G.py2_decode(str(exc), 'latin-1') if '10049' in err_msg: err_msg += '\r\nPossible cause is wrong localhost settings in your operative system.' LOG.error(err_msg) raise_from(exceptions.BackendNotReady(G.py2_encode(err_msg, encoding='latin-1')), exc) return result
def validate_login(react_context): path_code_list = PAGE_ITEM_ERROR_CODE_LIST.split('\\') path_error_code = PAGE_ITEM_ERROR_CODE.split('/') if common.check_path_exists(path_error_code, react_context): # If the path exists, a login error occurs try: error_code_list = common.get_path(path_code_list, react_context) error_code = common.get_path(path_error_code, react_context) LOG.error('Login not valid, error code {}', error_code) error_description = common.get_local_string(30102) + error_code if error_code in error_code_list: error_description = error_code_list[error_code] if 'email_' + error_code in error_code_list: error_description = error_code_list['email_' + error_code] if 'login_' + error_code in error_code_list: error_description = error_code_list['login_' + error_code] raise LoginValidateError(common.remove_html_tags(error_description)) except (AttributeError, KeyError) as exc: import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) error_msg = ( 'Something is wrong in PAGE_ITEM_ERROR_CODE or PAGE_ITEM_ERROR_CODE_LIST paths.' 'react_context data may have changed.') LOG.error(error_msg) raise_from(WebsiteParsingError(error_msg), exc)
def delete_folder_contents(path, delete_subfolders=False): """ Delete all files in a folder :param path: Path to perform delete contents :param delete_subfolders: If True delete also all subfolders """ directories, files = list_dir(xbmc.translatePath(path)) for filename in files: xbmcvfs.delete(os.path.join(path, G.py2_decode(filename))) if not delete_subfolders: return for directory in directories: delete_folder_contents(os.path.join(path, G.py2_decode(directory)), True) # Give time because the system performs previous op. otherwise it can't delete the folder xbmc.sleep(80) xbmcvfs.rmdir(os.path.join(path, G.py2_decode(directory)))
def make_http_call(callname, data): """Make an IPC call via HTTP and wait for it to return. The contents of data will be expanded to kwargs and passed into the target function.""" from collections import OrderedDict try: # Python 3 from urllib.request import build_opener, install_opener, ProxyHandler, HTTPError, URLError, urlopen except ImportError: # Python 2 from urllib2 import build_opener, install_opener, ProxyHandler, HTTPError, URLError, urlopen import json LOG.debug('Handling HTTP IPC call to {}'.format(callname)) # Note: On python 3, using 'localhost' slowdown the call (Windows OS is affected) not sure if it is an urllib issue url = 'http://127.0.0.1:{}/{}'.format(G.LOCAL_DB.get_value('ns_service_port', 8001), callname) install_opener(build_opener(ProxyHandler({}))) # don't use proxy for localhost try: result = json.loads( urlopen(url=url, data=json.dumps(data).encode('utf-8'), timeout=IPC_TIMEOUT_SECS).read(), object_pairs_hook=OrderedDict) except HTTPError as exc: result = json.loads(exc.reason) except URLError as exc: # On PY2 the exception message have to be decoded with latin-1 for system with symbolic characters err_msg = G.py2_decode(str(exc), 'latin-1') if '10049' in err_msg: err_msg += '\r\nPossible cause is wrong localhost settings in your operative system.' LOG.error(err_msg) raise_from(exceptions.BackendNotReady(G.py2_encode(err_msg, encoding='latin-1')), exc) _raise_for_error(result) return result
def extract_json(content, name): """Extract json from netflix content page""" LOG.debug('Extracting {} JSON', name) json_str = None try: json_array = recompile(JSON_REGEX.format(name), DOTALL).findall(content.decode('utf-8')) json_str = json_array[0] json_str_replace = json_str.replace('\\"', '\\\\"') # Escape double-quotes json_str_replace = json_str_replace.replace('\\s', '\\\\s') # Escape \s json_str_replace = json_str_replace.replace( '\\n', '\\\\n') # Escape line feed json_str_replace = json_str_replace.replace('\\t', '\\\\t') # Escape tab json_str_replace = json_str_replace.encode().decode( 'unicode_escape') # Decode the string as unicode json_str_replace = sub( r'\\(?!["])', r'\\\\', json_str_replace ) # Escape backslash (only when is not followed by double quotation marks \") return json.loads(json_str_replace) except Exception as exc: # pylint: disable=broad-except if json_str: LOG.error('JSON string trying to load: {}', json_str) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) raise_from(WebsiteParsingError('Unable to extract {}'.format(name)), exc)
def show_browse_dialog(title, browse_type=0, default_path=None, multi_selection=False, extensions=None): """ Show a browse dialog to select files or folders :param title: The window title :param browse_type: Type of dialog as int value (0 = ShowAndGetDirectory, 1 = ShowAndGetFile, ..see doc) :param default_path: The initial path :param multi_selection: Allow multi selection :param extensions: extensions allowed e.g. '.jpg|.png' :return: The selected path as string (or tuple of selected items) if user pressed 'Ok', else None """ ret = G.py2_decode(xbmcgui.Dialog().browse(browse_type, title, shares='local', useThumbs=False, treatAsFolder=False, defaultt=default_path, enableMultiple=multi_selection, mask=extensions)) # Note: when defaultt is set and the user cancel the action (when enableMultiple is False), # will be returned the defaultt value again, so we avoid this strange behavior... return None if not ret or ret == default_path else ret
def autoselect_profile_set(self, pathitems): # pylint: disable=unused-argument """Save the GUID for profile auto-selection""" G.LOCAL_DB.set_value('autoselect_profile_guid', self.params['profile_guid']) G.settings_monitor_suspend(True) G.ADDON.setSetting('autoselect_profile_name', self.params['profile_name']) G.ADDON.setSettingBool('autoselect_profile_enabled', True) G.settings_monitor_suspend(False) ui.show_notification(common.get_local_string(30058).format(G.py2_decode(self.params['profile_name'])))
def remove_item(self, job_data, library_home=None): # pylint: disable=unused-argument """Remove an item from the Kodi library, delete it from disk, remove add-on database references""" videoid = job_data['videoid'] LOG.debug('Removing {} ({}) from add-on library', videoid, job_data['title']) try: # Remove the STRM file exported exported_file_path = G.py2_decode( xbmc.translatePath(job_data['file_path'])) common.delete_file_safe(exported_file_path) parent_folder = G.py2_decode( xbmc.translatePath(os.path.dirname(exported_file_path))) # Remove the NFO file of the related STRM file nfo_file = os.path.splitext(exported_file_path)[0] + '.nfo' common.delete_file_safe(nfo_file) dirs, files = common.list_dir(parent_folder) # Remove the tvshow NFO file (only when it is the last file in the folder) tvshow_nfo_file = common.join_folders_paths( parent_folder, 'tvshow.nfo') # (users have the option of removing even single seasons) if xbmcvfs.exists(tvshow_nfo_file) and not dirs and len( files) == 1: xbmcvfs.delete(tvshow_nfo_file) # Delete parent folder xbmcvfs.rmdir(parent_folder) # Delete parent folder when empty if not dirs and not files: xbmcvfs.rmdir(parent_folder) # Remove videoid records from add-on database remove_videoid_from_db(videoid) except ItemNotFound: LOG.warn( 'The videoid {} not exists in the add-on library database', videoid) except Exception as exc: # pylint: disable=broad-except import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) ui.show_addon_error_info(exc)
def _subtitle_profiles(): from xbmcaddon import Addon isa_version = G.remove_ver_suffix( G.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version'))) subtitle_profile = ['webvtt-lssdh-ios8'] if G.ADDON.getSettingBool('disable_webvtt_subtitle') \ or not common.is_minimum_version(isa_version, '2.3.8'): subtitle_profile = ['simplesdh'] return subtitle_profile
def _perform_ipc_return_call(func, data, func_name=None): try: result = _call(func, data) except Exception as exc: # pylint: disable=broad-except if exc.__class__.__name__ not in ['CacheMiss', 'MetadataNotAvailable']: LOG.error('IPC callback raised exception: {exc}', exc=exc) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) result = ipc_convert_exc_to_json(exc) return _execute_addonsignals_return_call(result, func_name)
def callback_event_video_queue(self, data=None): """Callback to add a video event""" try: self.add_event_to_queue(data['event_type'], data['event_data'], data['player_state']) except Exception as exc: # pylint: disable=broad-except import traceback from resources.lib.kodi.ui import show_addon_error_info common.error(G.py2_decode(traceback.format_exc(), 'latin-1')) show_addon_error_info(exc)
def import_library(self, path): """ Imports an already existing exported STRM library into the add-on library database, allows you to restore an existing library, by avoiding to recreate it from scratch. This operations also update the missing tv shows seasons and episodes, and automatically converts old STRM format type from add-on version 0.13.x or before 1.7.0 to new format. """ # If set ask to user if want to export NFO files nfo_settings = nfo.NFOSettings() nfo_settings.show_export_dialog() LOG.info('Start importing Kodi library') remove_folders = [ ] # List of failed imports paths to be optionally removed remove_titles = [ ] # List of failed imports titles to be optionally removed # Start importing STRM files folders = get_library_subfolders(FOLDER_NAME_MOVIES, path) + get_library_subfolders( FOLDER_NAME_SHOWS, path) with ui.ProgressDialog(True, max_value=len(folders)) as progress_bar: for folder_path in folders: folder_name = os.path.basename( G.py2_decode(translatePath(folder_path))) progress_bar.set_message(folder_name) try: videoid = self.import_videoid_from_existing_strm( folder_path, folder_name) if videoid is None: # Failed to import, add folder to remove list remove_folders.append(folder_path) remove_titles.append(folder_name) continue # Successfully imported, Execute the task for index, total_tasks, title in self.execute_library_task( videoid, self.export_item, nfo_settings=nfo_settings, notify_errors=True): label_partial_op = ' ({}/{})'.format( index + 1, total_tasks) if total_tasks > 1 else '' progress_bar.set_message(title + label_partial_op) if progress_bar.is_cancelled(): LOG.warn('Import library interrupted by User') return if self.monitor.abortRequested(): LOG.warn('Import library interrupted by Kodi') return except ImportWarning: # Ignore it, something was wrong in STRM file (see _import_videoid in library_jobs.py) pass progress_bar.perform_step() progress_bar.set_wait_message() delay_anti_ban() ret = self._import_library_remove(remove_titles, remove_folders) request_kodi_library_update(scan=True, clean=ret)
def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" from requests import exceptions try: self.auth_url = website.extract_session_data( self.get('browse'))['auth_url'] cookies.save(self.session.cookies) LOG.debug('Successfully refreshed session data') return True except MbrStatusError: raise except (WebsiteParsingError, MbrStatusAnonymousError) as exc: import traceback LOG.warn( 'Failed to refresh session data, login can be expired or the password has been changed ({})', type(exc).__name__) LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if isinstance(exc, MbrStatusAnonymousError): # This prevent the MSL error: No entity association record found for the user common.send_signal(signal=common.Signals.CLEAR_USER_ID_TOKENS) # Needed to do a new login common.purge_credentials() ui.show_notification(common.get_local_string(30008)) raise_from(NotLoggedInError, exc) except exceptions.RequestException: import traceback LOG.warn( 'Failed to refresh session data, request error (RequestException)' ) LOG.warn(G.py2_decode(traceback.format_exc(), 'latin-1')) if raise_exception: raise except Exception: # pylint: disable=broad-except import traceback LOG.warn( 'Failed to refresh session data, login expired (Exception)') LOG.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if raise_exception: raise return False
def _get_item_details_from_kodi(mediatype, file_path): """Get a Kodi library item with details (from Kodi database) by searching with the file path""" # To ensure compatibility with previously exported items, make the filename legal file_path = makeLegalFilename(file_path) dir_path = os.path.dirname(G.py2_decode(xbmc.translatePath(file_path))) filename = os.path.basename(G.py2_decode(xbmc.translatePath(file_path))) # We get the data from Kodi library using filters, this is much faster than loading all episodes in memory. if file_path[:10] == 'special://': # If the path is special, search with real directory path and also special path special_dir_path = os.path.dirname(G.py2_decode(file_path)) path_filter = { 'or': [{ 'field': 'path', 'operator': 'startswith', 'value': dir_path }, { 'field': 'path', 'operator': 'startswith', 'value': special_dir_path }] } else: path_filter = { 'field': 'path', 'operator': 'startswith', 'value': dir_path } # Now build the all request and call the json-rpc function through get_library_items library_items = get_library_items( mediatype, { 'and': [ path_filter, { 'field': 'filename', 'operator': 'is', 'value': filename } ] }) if not library_items: raise ItemNotFound return get_library_item_details(mediatype, library_items[0][mediatype + 'id'])
def wrapper(*args, **kwargs): # pylint: disable=broad-except, ungrouped-imports success = False try: func(*args, **kwargs) success = True except BackendNotReady as exc_bnr: from resources.lib.kodi.ui import show_backend_not_ready show_backend_not_ready(G.py2_decode(str(exc_bnr), 'latin-1')) except InputStreamHelperError as exc: from resources.lib.kodi.ui import show_ok_dialog show_ok_dialog('InputStream Helper Add-on error', ( 'The operation has been cancelled.\r\n' 'InputStream Helper has generated an internal error:\r\n{}\r\n\r\n' 'Please report it to InputStream Helper github.'.format(exc))) except ( HttpError401, HttpErrorTimeout ) as exc: # HTTP error 401 Client Error: Unauthorized for url ... # HttpError401: This is a generic error, can happen when the http request for some reason has failed. # Known causes: # - Possible change of data format or wrong data in the http request (also in headers/params) # - Some current nf session data are not more valid (authURL/cookies/...) # HttpErrorTimeout: This error is raised by Requests ReadTimeout error, unknown causes from resources.lib.kodi.ui import show_ok_dialog show_ok_dialog( get_local_string(30105), ('There was a communication problem with Netflix.[CR]' 'You can try the operation again or exit.[CR]' '(Error code: {})').format(exc.__class__.__name__)) except (MbrStatusNeverMemberError, MbrStatusFormerMemberError): from resources.lib.kodi.ui import show_error_info show_error_info(get_local_string(30008), get_local_string(30180), False, True) except Exception as exc: import traceback from resources.lib.kodi.ui import show_addon_error_info LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) show_addon_error_info(exc) finally: if not success: from xbmcplugin import endOfDirectory endOfDirectory(handle=G.PLUGIN_HANDLE, succeeded=False)
def _tick_and_wait_for_abort(self): try: self.controller.on_service_tick() self.library_updater.on_service_tick() G.CACHE_MANAGEMENT.on_service_tick() except Exception as exc: # pylint: disable=broad-except import traceback from resources.lib.kodi.ui import show_notification LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) show_notification(': '.join((exc.__class__.__name__, unicode(exc)))) return self.controller.waitForAbort(1)
def ask_credentials(): """ Show some dialogs and ask the user for account credentials """ email = G.py2_decode( xbmcgui.Dialog().input(heading=common.get_local_string(30005), type=xbmcgui.INPUT_ALPHANUM)) or None common.verify_credentials(email) password = ask_for_password() common.verify_credentials(password) return {'email': email.strip(), 'password': password.strip()}
def convert_to_dash(manifest): """Convert a Netflix style manifest to MPEG-DASH manifest""" from xbmcaddon import Addon isa_version = G.remove_ver_suffix( G.py2_decode(Addon('inputstream.adaptive').getAddonInfo('version'))) # If a CDN server has stability problems it may cause errors with streaming, # we allow users to select a different CDN server # (should be managed by ISA but is currently is not implemented) cdn_index = int(G.ADDON.getSettingString('cdn_server')[-1]) - 1 seconds = manifest['duration'] / 1000 init_length = int(seconds / 2 * 12 + 20 * 1000) duration = "PT" + str(int(seconds)) + ".00S" root = _mpd_manifest_root(duration) period = ET.SubElement(root, 'Period', start='PT0S', duration=duration) has_video_drm_streams = manifest['video_tracks'][0].get( 'hasDrmStreams', False) video_protection_info = _get_protection_info( manifest['video_tracks'][0]) if has_video_drm_streams else None for video_track in manifest['video_tracks']: _convert_video_track(video_track, period, init_length, video_protection_info, has_video_drm_streams, cdn_index) common.fix_locale_languages(manifest['audio_tracks']) common.fix_locale_languages(manifest['timedtexttracks']) has_audio_drm_streams = manifest['audio_tracks'][0].get( 'hasDrmStreams', False) default_audio_language_index = _get_default_audio_language(manifest) for index, audio_track in enumerate(manifest['audio_tracks']): _convert_audio_track(audio_track, period, init_length, (index == default_audio_language_index), has_audio_drm_streams, cdn_index) default_subtitle_language_index = _get_default_subtitle_language(manifest) for index, text_track in enumerate(manifest['timedtexttracks']): if text_track['isNoneTrack']: continue _convert_text_track(text_track, period, (index == default_subtitle_language_index), cdn_index, isa_version) xml = ET.tostring(root, encoding='utf-8', method='xml') if LOG.level == LOG.LEVEL_VERBOSE: common.save_file_def('manifest.mpd', xml) return xml.decode('utf-8').replace('\n', '').replace('\r', '').encode('utf-8')
def try_refresh_session_data(self, raise_exception=False): """Refresh session data from the Netflix website""" from requests import exceptions try: self.auth_url = website.extract_session_data( self.get('browse'))['auth_url'] cookies.save(self.account_hash, self.session.cookies) common.debug('Successfully refreshed session data') return True except MbrStatusError: raise except (WebsiteParsingError, MbrStatusAnonymousError) as exc: import traceback common.warn( 'Failed to refresh session data, login can be expired or the password has been changed ({})', type(exc).__name__) common.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if isinstance(exc, MbrStatusAnonymousError): # This prevent the MSL error: No entity association record found for the user common.send_signal(signal=common.Signals.CLEAR_USER_ID_TOKENS) return self.external_func_login(modal_error_message=False) # pylint: disable=not-callable except exceptions.RequestException: import traceback common.warn( 'Failed to refresh session data, request error (RequestException)' ) common.warn(G.py2_decode(traceback.format_exc(), 'latin-1')) if raise_exception: raise except Exception: # pylint: disable=broad-except import traceback common.warn( 'Failed to refresh session data, login expired (Exception)') common.debug(G.py2_decode(traceback.format_exc(), 'latin-1')) self.session.cookies.clear() if raise_exception: raise return False
def _notify_managers(manager, notification, data): notify_method = getattr(manager, notification.__name__) try: if data is not None: notify_method(data) else: notify_method() except Exception as exc: # pylint: disable=broad-except manager.enabled = False msg = '{} disabled due to exception: {}'.format(manager.name, exc) import traceback LOG.error(G.py2_decode(traceback.format_exc(), 'latin-1')) ui.show_notification(title=common.get_local_string(30105), msg=msg)