def toggle_cloak(self): """ Toggles the cloaking (showing/hiding) of the selected folder. """ item = self._pickler.de_pickle_media_item( self.params[self.keywordPickle]) Logger.info("Cloaking current item: %s", item) c = Cloaker(self.channelObject, AddonSettings.store(LOCAL), logger=Logger.instance()) if c.is_cloaked(item.url): c.un_cloak(item.url) self.refresh() return first_time = c.cloak(item.url) if first_time: XbmcWrapper.show_dialog( LanguageHelper.get_localized_string( LanguageHelper.CloakFirstTime), LanguageHelper.get_localized_string( LanguageHelper.CloakMessage)) del c self.refresh()
def __show_first_time_message(self, channel_info): """ Checks if it is the first time a channel is executed and if a first time message is available it will be shown. Shows a message dialog if the message should be shown. Make sure that each line fits in a single line of a Kodi Dialog box (50 chars). :param ChannelInfo channel_info: The ChannelInfo to show a message for. """ hide_first_time = AddonSettings.hide_first_time_messages() if channel_info.firstTimeMessage: if not hide_first_time: Logger.info( "Showing first time message '%s' for channel chn_%s.", channel_info.firstTimeMessage, channel_info.moduleName) title = LanguageHelper.get_localized_string( LanguageHelper.ChannelMessageId) XbmcWrapper.show_dialog( title, channel_info.firstTimeMessage.split("|")) else: Logger.debug( "Not showing first time message due to add-on setting set to '%s'.", hide_first_time) return
def __send_log(self): """ Send log files via Pastbin or Gist. """ from resources.lib.helpers.logsender import LogSender sender_mode = 'hastebin' log_sender = LogSender(Config.logSenderApi, logger=Logger.instance(), mode=sender_mode) try: title = LanguageHelper.get_localized_string( LanguageHelper.LogPostSuccessTitle) url_text = LanguageHelper.get_localized_string( LanguageHelper.LogPostLogUrl) files_to_send = [ Logger.instance().logFileName, Logger.instance().logFileName.replace(".log", ".old.log") ] if sender_mode != "gist": paste_url = log_sender.send_file(Config.logFileNameAddon, files_to_send[0]) else: paste_url = log_sender.send_files(Config.logFileNameAddon, files_to_send) XbmcWrapper.show_dialog(title, url_text % (paste_url, )) except Exception as e: Logger.error("Error sending %s", Config.logFileNameAddon, exc_info=True) title = LanguageHelper.get_localized_string( LanguageHelper.LogPostErrorTitle) error_text = LanguageHelper.get_localized_string( LanguageHelper.LogPostError) error = error_text % (str(e), ) XbmcWrapper.show_dialog(title, error.strip(": "))
def log_on(self): """ Logs on to a website, using an url. First checks if the channel requires log on. If so and it's not already logged on, it should handle the log on. That part should be implemented by the specific channel. More arguments can be passed on, but must be handled by custom code. After a successful log on the self.loggedOn property is set to True and True is returned. :return: indication if the login was successful. :rtype: bool """ if self.__idToken: return True # check if there is a refresh token # refresh token: viervijfzes_refresh_token refresh_token = AddonSettings.get_setting("viervijfzes_refresh_token") client = AwsIdp("eu-west-1_dViSsKM5Y", "6s1h851s8uplco5h6mqh1jac8m", proxy=self.proxy, logger=Logger.instance()) if refresh_token: id_token = client.renew_token(refresh_token) if id_token: self.__idToken = id_token return True else: Logger.info("Extending token for VierVijfZes failed.") # username: viervijfzes_username username = AddonSettings.get_setting("viervijfzes_username") # password: viervijfzes_password v = Vault() password = v.get_setting("viervijfzes_password") if not username or not password: XbmcWrapper.show_dialog( title=None, lines=LanguageHelper.get_localized_string( LanguageHelper.MissingCredentials), ) return False id_token, refresh_token = client.authenticate(username, password) if not id_token or not refresh_token: Logger.error("Error getting a new token. Wrong password?") return False self.__idToken = id_token AddonSettings.set_setting("viervijfzes_refresh_token", refresh_token) return True
def __show_empty_information(self, items, favs=False): """ Adds an empty item to a list or just shows a message. @type favs: boolean @param items: :param list[MediaItem] items: The list of items. :param bool favs: Indicating that we are dealing with favourites. :return: boolean indicating to report the listing as succes or not. :rtype: ok """ if favs: title = LanguageHelper.get_localized_string(LanguageHelper.NoFavsId) else: title = LanguageHelper.get_localized_string(LanguageHelper.ErrorNoEpisodes) behaviour = AddonSettings.get_empty_list_behaviour() Logger.debug("Showing empty info for mode (favs=%s): [%s]", favs, behaviour) if behaviour == "error": # show error ok = False elif behaviour == "dummy" and not favs: # We should add a dummy items, but not for favs empty_list_item = MediaItem("- %s -" % (title.strip("."), ), "", type='video') if self.channelObject: empty_list_item.icon = self.channelObject.icon empty_list_item.thumb = self.channelObject.noImage empty_list_item.fanart = self.channelObject.fanart else: icon = Config.icon fanart = Config.fanart empty_list_item.icon = icon empty_list_item.thumb = fanart empty_list_item.fanart = fanart empty_list_item.dontGroup = True empty_list_item.description = "This listing was left empty intentionally." empty_list_item.complete = True # add funny stream here? # part = empty_list_item.create_new_empty_media_part() # for s, b in YouTube.get_streams_from_you_tube("", self.channelObject.proxy): # part.append_media_stream(s, b) # if we add one, set OK to True ok = True items.append(empty_list_item) else: ok = True XbmcWrapper.show_notification(LanguageHelper.get_localized_string(LanguageHelper.ErrorId), title, XbmcWrapper.Error, 2500) return ok
def __get_application_key(self): """ Gets the decrypted application key that is used for all the encryption. :return: The decrypted application key that is used for all the encryption. :rtype: bytes """ application_key_encrypted = AddonSettings.get_setting( Vault.__APPLICATION_KEY_SETTING, store=LOCAL) # The key was never in the local store the value was None. It was "" if it was reset. if application_key_encrypted is None: application_key_encrypted = AddonSettings.get_setting( Vault.__APPLICATION_KEY_SETTING, store=KODI) if not application_key_encrypted: return None Logger.info("Moved ApplicationKey to local storage") AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING, application_key_encrypted, store=LOCAL) # Still no application key? Then there was no key! if application_key_encrypted == "" or application_key_encrypted is None: return None vault_incorrect_pin = LanguageHelper.get_localized_string( LanguageHelper.VaultIncorrectPin) pin = XbmcWrapper.show_key_board( heading=LanguageHelper.get_localized_string( LanguageHelper.VaultInputPin), hidden=True) if not pin: XbmcWrapper.show_notification("", vault_incorrect_pin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") pin_key = self.__get_pbk(pin) application_key = self.__decrypt(application_key_encrypted, pin_key) if not application_key.startswith(Vault.__APPLICATION_KEY_SETTING): Logger.critical("Invalid Retrospect PIN") XbmcWrapper.show_notification("", vault_incorrect_pin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") application_key_value = application_key[ len(Vault.__APPLICATION_KEY_SETTING) + 1:] Logger.info("Successfully decrypted the ApplicationKey.") if PY2: return application_key_value # We return bytes on Python 3 return application_key_value.encode()
def update_video_item(self, item): """ Updates an existing MediaItem with more data. Used to update none complete MediaItems (self.complete = False). This could include opening the item's URL to fetch more data and then process that data or retrieve it's real media-URL. The method should at least: * cache the thumbnail to disk (use self.noImage if no thumb is available). * set at least one MediaItemPart with a single MediaStream. * set self.complete = True. if the returned item does not have a MediaItemPart then the self.complete flag will automatically be set back to False. :param MediaItem item: the original MediaItem that needs updating. :return: The original item with more data added to it's properties. :rtype: MediaItem """ Logger.debug('Starting update_video_item for %s (%s)', item.name, self.channelName) if "index.html" in item.url: data = UriHandler.open( item.url, additional_headers={"Referer": "http://www.{}/".format(self.__country_id)} ) json_data = JsonHelper(data) guid = json_data.get_value("feed", "items", 0, "guid") url = "https://media-utils.mtvnservices.com/services/MediaGenerator/" \ "{}?arcStage=live&format=json&acceptMethods=hls&clang=nl" \ "&https=true".format(guid) else: url = item.url data = UriHandler.open(url) json_data = JsonHelper(data) url = json_data.get_value("package", "video", "item", 0, "rendition", 0, "src") if not url: error = json_data.get_value("package", "video", "item", 0, "text") Logger.error("Error resolving url: %s", error) XbmcWrapper.show_dialog(LanguageHelper.ErrorId, error) return item item.MediaItemParts = [] part = item.create_new_empty_media_part() part.append_media_stream(url, 0) item.complete = True return item
def __update_video_from_mpd(self, item, mpd_info, use_adaptive_with_encryption): """ Updates an existing MediaItem with more data based on an MPD stream. :param dict[str,str] mpd_info: Stream info retrieved from the stream json. :param bool use_adaptive_with_encryption: Do we use the Adaptive InputStream add-on? :param MediaItem item: The original MediaItem that needs updating. :return: The original item with more data added to it's properties. :rtype: MediaItem """ Logger.debug("Updating streams using BrightCove data.") part = item.create_new_empty_media_part() mpd_manifest_url = "https:{0}".format(mpd_info["mediaLocator"]) mpd_data = UriHandler.open(mpd_manifest_url, proxy=self.proxy) subtitles = Regexer.do_regex(r'<BaseURL>([^<]+\.vtt)</BaseURL>', mpd_data) if subtitles: Logger.debug("Found subtitle: %s", subtitles[0]) subtitle = SubtitleHelper.download_subtitle(subtitles[0], proxy=self.proxy, format="webvtt") part.Subtitle = subtitle if use_adaptive_with_encryption: # We can use the adaptive add-on with encryption Logger.info("Using MPD InputStreamAddon") license_url = Regexer.do_regex('licenseUrl="([^"]+)"', mpd_data)[0] token = "Bearer {0}".format(mpd_info["playToken"]) key_headers = {"Authorization": token} license_key = Mpd.get_license_key(license_url, key_headers=key_headers) stream = part.append_media_stream(mpd_manifest_url, 0) Mpd.set_input_stream_addon_input(stream, self.proxy, license_key=license_key) item.complete = True else: XbmcWrapper.show_dialog( LanguageHelper.get_localized_string(LanguageHelper.DrmTitle), LanguageHelper.get_localized_string( LanguageHelper.WidevineLeiaRequired)) return item
def search_site(self, url=None): """ Creates an list of items by searching the site. This method is called when the URL of an item is "searchSite". The channel calling this should implement the search functionality. This could also include showing of an input keyboard and following actions. The %s the url will be replaced with an URL encoded representation of the text to search for. :param str|None url: Url to use to search with a %s for the search parameters. :return: A list with search results as MediaItems. :rtype: list[MediaItem] """ items = [] if url is None: item = MediaItem("Search Not Implented", "", type='video') items.append(item) else: items = [] needle = XbmcWrapper.show_key_board() if needle: Logger.debug("Searching for '%s'", needle) # convert to HTML needle = HtmlEntityHelper.url_encode(needle) search_url = url % (needle, ) temp = MediaItem("Search", search_url) return self.process_folder_list(temp) return items
def execute(self): title = LanguageHelper.get_localized_string( LanguageHelper.CleanupCache)[:-1] clean = \ XbmcWrapper.show_yes_no(title, LanguageHelper.CleanupConfirmation) if not clean: Logger.warning("Clean-up cancelled") return files_to_remove = { "channelindex.json": "Cleaning: Channel Index", "cookiejar.dat": "Cleaning: Cookies in cookiejar.dat", "xot.session.lock": "Cleaning: Session lock" } for file_name, log_line in files_to_remove.items(): Logger.info(log_line) files_to_remove = os.path.join(Config.profileDir, file_name) if os.path.isfile(files_to_remove): os.remove(files_to_remove) Logger.info("Cleaning: PickeStore objects") self.parameter_parser.pickler.purge_store(Config.addonId, age=0) Logger.info("Cleaning: Cache objects in cache folder") env_ctrl = EnvController(Logger.instance()) env_ctrl.cache_clean_up(Config.cacheDir, 0)
def __show_empty_information(self, items, favs=False): """ Adds an empty item to a list or just shows a message. @type favs: boolean @param items: :param list[MediaItem] items: The list of items. :param bool favs: Indicating that we are dealing with favourites. :return: boolean indicating to report the listing as succes or not. :rtype: ok """ if favs: title = LanguageHelper.get_localized_string( LanguageHelper.NoFavsId) else: title = LanguageHelper.get_localized_string( LanguageHelper.ErrorNoEpisodes) behaviour = AddonSettings.get_empty_list_behaviour() Logger.debug("Showing empty info for mode (favs=%s): [%s]", favs, behaviour) if behaviour == "error": # show error ok = False elif behaviour == "dummy" and not favs: # We should add a dummy items, but not for favs empty_list_item = MediaItem("- %s -" % (title.strip("."), ), "", type='video') empty_list_item.dontGroup = True empty_list_item.complete = True # if we add one, set OK to True ok = True items.append(empty_list_item) else: ok = True XbmcWrapper.show_notification( LanguageHelper.get_localized_string(LanguageHelper.ErrorId), title, XbmcWrapper.Error, 2500) return ok
def __show_warnings(self, media_item): """ Show playback warnings for this MediaItem :param MediaItem media_item: The current MediaItem that will be played. """ if (media_item.isDrmProtected or media_item.isPaid) and AddonSettings.show_drm_paid_warning(): if media_item.isDrmProtected: Logger.debug("Showing DRM Warning message") title = LanguageHelper.get_localized_string(LanguageHelper.DrmTitle) message = LanguageHelper.get_localized_string(LanguageHelper.DrmText) XbmcWrapper.show_dialog(title, message) elif media_item.isPaid: Logger.debug("Showing Paid Warning message") title = LanguageHelper.get_localized_string(LanguageHelper.PaidTitle) message = LanguageHelper.get_localized_string(LanguageHelper.PaidText) XbmcWrapper.show_dialog(title, message)
def __update_title_and_description_with_limitations(self): """ Updates the title/name and description with the symbols for DRM, GEO and Paid. :return: (tuple) name postfix, description postfix :rtype: tuple[str,str] """ geo_lock = "º" # º drm_lock = "^" # ^ paid = "ª" # ª cloaked = "¨" # ¨ description_prefix = [] title_postfix = [] description = "" title = "" if self.__expires_datetime is not None: expires = "{}: {}".format(MediaItem.ExpiresAt, self.__expires_datetime.strftime("%Y-%m-%d %H:%M")) description_prefix.append(expires) if self.isDrmProtected: title_postfix.append(drm_lock) description_prefix.append( LanguageHelper.get_localized_string(LanguageHelper.DrmProtected)) if self.isGeoLocked: title_postfix.append(geo_lock) description_prefix.append( LanguageHelper.get_localized_string(LanguageHelper.GeoLockedId)) if self.isPaid: title_postfix.append(paid) description_prefix.append( LanguageHelper.get_localized_string(LanguageHelper.PremiumPaid)) if self.isCloaked: title_postfix.append(cloaked) description_prefix.append( LanguageHelper.get_localized_string(LanguageHelper.HiddenItem)) if self.uses_external_addon: from resources.lib.xbmcwrapper import XbmcWrapper external = XbmcWrapper.get_external_add_on_label(self.url) title_postfix.append(external) # actually update it if description_prefix: description_prefix = "\n".join(description_prefix) description = "[COLOR gold][I]%s[/I][/COLOR]" % (description_prefix.rstrip(), ) if title_postfix: title = "".join(title_postfix) title = "[COLOR gold]%s[/COLOR]" % (title.lstrip(), ) return title, description
def process_video_item(self, item): """ Process a video item using the required dataparsers :param MediaItem item: The Item to update :return: An updated item. :rtype: MediaItem """ data_parsers = self.__get_data_parsers(item.url) if not data_parsers: Logger.error("No dataparsers found cannot update item.") return item data_parsers = [d for d in data_parsers if d.Updater is not None] if len(data_parsers) < 1: Logger.warning("No DataParsers with Updaters found.") return item if len(data_parsers) > 1: Logger.warning( "More than 2 DataParsers with Updaters found. Only using first one." ) data_parser = data_parsers[0] if not data_parser.Updater: Logger.error("No videoupdater found cannot update item.") return item if data_parser.LogOnRequired: Logger.info("One or more dataparsers require logging in.") self.loggedOn = self.log_on() if not self.loggedOn: Logger.warning("Could not log on for: %s", self) title = LanguageHelper.get_localized_string( LanguageHelper.LoginErrorTitle) text = LanguageHelper.get_localized_string( LanguageHelper.LoginErrorText) XbmcWrapper.show_dialog(title, text) Logger.debug("Processing Updater from %s", data_parser) return data_parser.Updater(item)
def search_site(self, url=None): """ Creates an list of items by searching the site. This method is called when the URL of an item is "searchSite". The channel calling this should implement the search functionality. This could also include showing of an input keyboard and following actions. The %s the url will be replaced with an URL encoded representation of the text to search for. :param str url: Url to use to search with a %s for the search parameters. :return: A list with search results as MediaItems. :rtype: list[MediaItem] """ if self.primaryChannelId: shows_url = "https://{0}/content/shows?" \ "include=genres%%2Cimages%%2CprimaryChannel.images&" \ "filter%%5BprimaryChannel.id%%5D={1}&" \ "page%%5Bsize%%5D={2}&query=%s"\ .format(self.baseUrlApi, self.primaryChannelId or "", self.programPageSize) videos_url = "https://{0}/content/videos?decorators=viewingHistory&" \ "include=images%%2CprimaryChannel%%2Cshow&" \ "filter%%5BprimaryChannel.id%%5D={1}&" \ "page%%5Bsize%%5D={2}&query=%s"\ .format(self.baseUrlApi, self.primaryChannelId or "", self.videoPageSize) else: shows_url = "https://{0}/content/shows?" \ "include=genres%%2Cimages%%2CprimaryChannel.images&" \ "page%%5Bsize%%5D={1}&query=%s" \ .format(self.baseUrlApi, self.programPageSize) videos_url = "https://{0}/content/videos?decorators=viewingHistory&" \ "include=images%%2CprimaryChannel%%2Cshow&" \ "page%%5Bsize%%5D={1}&query=%s" \ .format(self.baseUrlApi, self.videoPageSize) needle = XbmcWrapper.show_key_board() if needle: Logger.debug("Searching for '%s'", needle) needle = HtmlEntityHelper.url_encode(needle) search_url = videos_url % (needle, ) temp = MediaItem("Search", search_url) episodes = self.process_folder_list(temp) search_url = shows_url % (needle, ) temp = MediaItem("Search", search_url) shows = self.process_folder_list(temp) return shows + episodes return []
def __update_dash_video(self, item, stream_info): """ :param MediaItem item: The item that was updated :param JsonHelper stream_info: The stream info """ if not AddonSettings.use_adaptive_stream_add_on(with_encryption=True): XbmcWrapper.show_dialog( LanguageHelper.get_localized_string(LanguageHelper.DrmTitle), LanguageHelper.get_localized_string( LanguageHelper.WidevineLeiaRequired)) return item playback_item = stream_info.get_value("playbackItem") stream_url = playback_item["manifestUrl"] part = item.create_new_empty_media_part() stream = part.append_media_stream(stream_url, 0) license_info = playback_item.get("license", None) if license_info is not None: license_key_token = license_info["token"] auth_token = license_info["castlabsToken"] header = { "x-dt-auth-token": auth_token, "content-type": "application/octstream" } license_url = license_info["castlabsServer"] license_key = Mpd.get_license_key(license_url, key_value=license_key_token, key_headers=header) Mpd.set_input_stream_addon_input(stream, proxy=self.proxy, license_key=license_key) item.isDrmProtected = False else: Mpd.set_input_stream_addon_input(stream, proxy=self.proxy) item.complete = True return item
def _purge_kodi_cache(self, channel_texture_path): """ Class the JSON RPC within Kodi that removes all changed items which paths contain the value given in channelTexturePath @param channel_texture_path: string - The """ json_cmd = '{' \ '"jsonrpc": "2.0", ' \ '"method": "Textures.GetTextures", ' \ '"params": {' \ '"filter": {"operator": "contains", "field": "url", "value": "%s"}, ' \ '"properties": ["url"]' \ '}, ' \ '"id": "libTextures"' \ '}' % (channel_texture_path,) json_results = XbmcWrapper.execute_json_rpc(json_cmd, self._logger) results = JsonHelper(json_results, logger=self._logger) if "error" in results.json or "result" not in results.json: self._logger.error( "Error retreiving textures:\nCmd : %s\nResult: %s", json_cmd, results.json) return results = results.get_value("result", "textures", fallback=[]) for result in results: texture_id = result["textureid"] texture_url = result["url"] self._logger.debug("Going to remove texture: %d - %s", texture_id, texture_url) json_cmd = '{' \ '"jsonrpc": "2.0", ' \ '"method": "Textures.RemoveTexture", ' \ '"params": {' \ '"textureid": %s' \ '}' \ '}' % (texture_id,) XbmcWrapper.execute_json_rpc(json_cmd, self._logger) return
def __show_howto(self, force=False): """ Shows the Vault howto if it was not already shown. :param bool force: Force the howto to show :returns: indicator if the howto was shown :rtype: bool """ if not force: vault_shown = AddonSettings.store(LOCAL).get_boolean_setting( Vault.__VAULT_HOWTO_SETTING, default=False) if vault_shown: return False XbmcWrapper.show_text(LanguageHelper.VaultHowToTitle, LanguageHelper.VaultHowToText) AddonSettings.store(LOCAL).set_setting(Vault.__VAULT_HOWTO_SETTING, True) return True
def get_kodi_item(self): """ Creates an Kodi ListItem object for this channel :return: a Kodi ListItem with all required properties set. :rtype: xbmcgui.ListItem """ name = HtmlEntityHelper.convert_html_entities(self.channelName) description = HtmlEntityHelper.convert_html_entities( self.channelDescription) if self.uses_external_addon: from resources.lib.xbmcwrapper import XbmcWrapper name = "{} {}".format( name, XbmcWrapper.get_external_add_on_label(self.addonUrl)) self.icon = self.__get_image_path(self.icon) item = kodifactory.list_item(name, description) item.setArt({'thumb': self.icon, 'icon': self.icon}) # http://mirrors.kodi.tv/docs/python-docs/14.x-helix/xbmcgui.html#ListItem-setInfo item.setInfo( "video", { "Title": name, # "Count": self.sortOrderPerCountry, # "TrackNumber": self.sortOrder, "Genre": LanguageHelper.get_full_language(self.language), # "Tagline": description, "Plot": description }) if self.poster is not None: self.poster = self.__get_image_path(self.poster) item.setArt({'poster': self.poster}) if AddonSettings.hide_fanart(): return item if self.fanart is not None: self.fanart = self.__get_image_path(self.fanart) else: self.fanart = Config.fanart item.setArt({'fanart': self.fanart}) return item
def set_setting(self, setting_id, setting_name=None, setting_action_id=None): """ Reads a value for a setting from the keyboard and encrypts it in the Kodi Add-on settings. The settingActionId defaults to <settingId>_set :param str setting_id: The ID for the Kodi Add-on setting to set. :param str setting_name: The name to display in the keyboard. :param str setting_action_id: The name of setting that shows the ***** if an value was encrypted. :rtype: None """ Logger.info("Encrypting value for setting '%s'", setting_id) input_value = XbmcWrapper.show_key_board( "", LanguageHelper.get_localized_string( LanguageHelper.VaultSpecifySetting) % (setting_name or setting_id, )) if input_value is None: Logger.debug("Setting of encrypted value cancelled.") return value = "%s=%s" % (setting_id, input_value) encrypted_value = self.__encrypt(value, Vault.__Key) if setting_action_id is None: setting_action_id = "%s_set" % (setting_id, ) Logger.debug("Updating '%s' and '%s'", setting_id, setting_action_id) AddonSettings.set_setting(setting_id, encrypted_value) if input_value: AddonSettings.set_setting(setting_action_id, "******") else: AddonSettings.set_setting(setting_action_id, "") Logger.info("Successfully encrypted value for setting '%s'", setting_id) return
def reset(): """ Resets the Vault and Retrospect Machine key, making all encrypted values useless. :rtype: None """ ok = XbmcWrapper.show_yes_no(LanguageHelper.get_localized_string(LanguageHelper.VaultReset), LanguageHelper.get_localized_string(LanguageHelper.VaultResetConfirm)) if not ok: Logger.debug("Aborting Reset Vault") return Logger.info("Resetting the vault to a new initial state.") AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING, "", store=LOCAL) # create a vault instance so we initialize a new one with a new PIN. Vault() return
def search_site(self, url=None): """ Creates an list of items by searching the site. This method is called when the URL of an item is "searchSite". The channel calling this should implement the search functionality. This could also include showing of an input keyboard and following actions. The %s the url will be replaced with an URL encoded representation of the text to search for. :param str url: Url to use to search with a %s for the search parameters. :return: A list with search results as MediaItems. :rtype: list[MediaItem] """ items = [] needle = XbmcWrapper.show_key_board() if not needle: return [] Logger.debug("Searching for '%s'", needle) # convert to HTML needle = HtmlEntityHelper.url_encode(needle) # Search Programma's url = "https://search.rtl.nl/?typeRestriction=tvabstract&search={}&page=1&pageSize=99" search_url = url.format(needle) temp = MediaItem("Search", search_url) items += self.process_folder_list(temp) or [] # Search Afleveringen -> no dates given, so this makes little sense # url = "https://search.rtl.nl/?typeRestriction=videoobject&uitzending=true&search={}&page=1&pageSize=99" # search_url = url.format(needle) # temp = MediaItem("Search", search_url) # items += self.process_folder_list(temp) or [] return items
def __update_embedded_video(self, item): """ Updates video items that are encrypted. This could be the default for Krypton! :param MediaItem item: The item to update. :return: An updated item. :rtype: MediaItem """ data = UriHandler.open(item.url, proxy=self.proxy) if UriHandler.instance().status.code == 404: title, message = Regexer.do_regex( r'<h1>([^<]+)</h1>\W+<p>([^<]+)<', data)[0] XbmcWrapper.show_dialog(title, message) return item start_needle = "var playerConfig =" start_data = data.index(start_needle) + len(start_needle) end_data = data.index("var talpaPlayer") data = data[start_data:end_data].strip().rstrip(";") json = JsonHelper(data) has_drm_only = True adaptive_available = AddonSettings.use_adaptive_stream_add_on( with_encryption=False, channel=self) adaptive_available_encrypted = AddonSettings.use_adaptive_stream_add_on( with_encryption=True, channel=self) for play_list_entry in json.get_value("playlist"): part = item.create_new_empty_media_part() for source in play_list_entry["sources"]: stream_type = source["type"] stream_url = source["file"] stream_drm = source.get("drm") if not stream_drm: has_drm_only = False if stream_type == "m3u8": Logger.debug("Found non-encrypted M3u8 stream: %s", stream_url) M3u8.update_part_with_m3u8_streams(part, stream_url, proxy=self.proxy, channel=self) item.complete = True elif stream_type == "dash" and adaptive_available: Logger.debug("Found non-encrypted Dash stream: %s", stream_url) stream = part.append_media_stream(stream_url, 1) Mpd.set_input_stream_addon_input(stream, proxy=self.proxy) item.complete = True else: Logger.debug("Unknown stream source: %s", source) else: compatible_drm = "widevine" if compatible_drm not in stream_drm or stream_type != "dash": Logger.debug("Found encrypted %s stream: %s", stream_type, stream_url) continue Logger.debug("Found Widevine encrypted Dash stream: %s", stream_url) license_url = stream_drm[compatible_drm]["url"] pid = stream_drm[compatible_drm]["releasePid"] encryption_json = '{"getRawWidevineLicense":' \ '{"releasePid":"%s", "widevineChallenge":"b{SSM}"}' \ '}' % (pid,) headers = { "Content-Type": "application/json", "Origin": "https://embed.kijk.nl", "Referer": stream_url } encryption_key = Mpd.get_license_key( license_url, key_type=None, key_value=encryption_json, key_headers=headers) stream = part.append_media_stream(stream_url, 0) Mpd.set_input_stream_addon_input( stream, proxy=self.proxy, license_key=encryption_key) item.complete = True subs = [ s['file'] for s in play_list_entry.get("tracks", []) if s.get('kind') == "captions" ] if subs: subtitle = SubtitleHelper.download_subtitle(subs[0], format="webvtt") part.Subtitle = subtitle if has_drm_only and not adaptive_available_encrypted: XbmcWrapper.show_dialog( LanguageHelper.get_localized_string(LanguageHelper.DrmTitle), LanguageHelper.get_localized_string( LanguageHelper.WidevineLeiaRequired)) return item
def play_video_item(self): """ Starts the videoitem using a playlist. """ from resources.lib import player Logger.debug("Playing videoitem using PlayListMethod") try: media_item = self.media_item if not media_item.complete: media_item = self.channelObject.process_video_item(media_item) # Any warning to show self.__show_warnings(media_item) # validated the updated media_item if not media_item.complete or not media_item.has_media_item_parts( ): Logger.warning( "process_video_item returned an MediaItem that had MediaItem.complete = False:\n%s", media_item) if not media_item.has_media_item_parts(): # the update failed or no items where found. Don't play XbmcWrapper.show_notification( LanguageHelper.get_localized_string( LanguageHelper.ErrorId), LanguageHelper.get_localized_string( LanguageHelper.NoStreamsId), XbmcWrapper.Error) Logger.warning( "Could not start playback due to missing streams. Item:\n%s", media_item) xbmcplugin.endOfDirectory(self.handle, False) return kodi_items = media_item.get_kodi_play_list_data( AddonSettings.get_max_stream_bitrate(self.channelObject), self.channelObject.proxy) Logger.debug("Continuing playback in plugin.py") if not bool(kodi_items): Logger.warning("play_video_item did not return valid playdata") xbmcplugin.endOfDirectory(self.handle, False) return # Now we force the busy dialog to close, else the video will not play and the # setResolved will not work. LockWithDialog.close_busy_dialog() # Append it to the Kodi playlist in a smart way. start_url = self.__append_kodi_play_list(kodi_items) # Set the mode (if the InputStream Adaptive add-on is used, we also need to set it) show_subs = AddonSettings.show_subtitles() # TODO: Apparently if we use the InputStream Adaptive, using the setSubtitles() causes sync issues. available_subs = [p.Subtitle for p in media_item.MediaItemParts] # Get the Kodi Player instance (let Kodi decide what player, see # http://forum.kodi.tv/showthread.php?tid=173887&pid=1516662#pid1516662) kodi_player = player.Player(show_subs=show_subs, subs=available_subs) kodi_player.waitForPlayBack(url=start_url, time_out=10) xbmcplugin.endOfDirectory(self.handle, True) except: XbmcWrapper.show_notification( LanguageHelper.get_localized_string(LanguageHelper.ErrorId), LanguageHelper.get_localized_string( LanguageHelper.NoPlaybackId), XbmcWrapper.Error) Logger.critical("Could not playback the url", exc_info=True) # We need to single Kodi that it failed and it should not wait longer. Either using a # `raise` or with `xbmcplugin.endOfDirectory`. Doing the latter for now although we are # not really playing. xbmcplugin.endOfDirectory(self.handle, False) return
def process_folder_list(self, favorites=None): """Wraps the channel.process_folder_list :param list[MediaItem]|None favorites: """ Logger.info("Plugin::process_folder_list Doing process_folder_list") try: ok = True # read the item from the parameters selected_item = self.media_item # determine the parent guid parent_guid = self._get_parent_guid(self.channelObject, selected_item) if favorites is None: watcher = StopWatch("Plugin process_folder_list", Logger.instance()) media_items = self.channelObject.process_folder_list( selected_item) watcher.lap("Class process_folder_list finished") else: watcher = StopWatch("Plugin process_folder_list With Items", Logger.instance()) media_items = favorites if len(media_items) == 0: Logger.warning("process_folder_list returned %s items", len(media_items)) ok = self.__show_empty_information(media_items, favs=favorites is not None) else: Logger.debug("process_folder_list returned %s items", len(media_items)) kodi_items = [] for media_item in media_items: # type: MediaItem self.__update_artwork(media_item, self.channelObject) if media_item.type == 'folder' or media_item.type == 'append' or media_item.type == "page": action = self.actionListFolder folder = True elif media_item.is_playable(): action = self.actionPlayVideo folder = False else: Logger.critical( "Plugin::process_folder_list: Cannot determine what to add" ) continue # Get the Kodi item kodi_item = media_item.get_kodi_item() self.__set_kodi_properties(kodi_item, media_item, folder, is_favourite=favorites is not None) # Get the context menu items context_menu_items = self.__get_context_menu_items( self.channelObject, item=media_item) kodi_item.addContextMenuItems(context_menu_items) # Get the action URL url = media_item.actionUrl if url is None: url = self._create_action_url(self.channelObject, action=action, item=media_item, store_id=parent_guid) # Add them to the list of Kodi items kodi_items.append((url, kodi_item, folder)) watcher.lap("Kodi Items generated") # add items but if OK was False, keep it like that ok = ok and xbmcplugin.addDirectoryItems(self.handle, kodi_items, len(kodi_items)) watcher.lap("items send to Kodi") if ok and parent_guid is not None: self._pickler.store_media_items(parent_guid, selected_item, media_items) watcher.stop() self.__add_sort_method_to_handle(self.handle, media_items) self.__add_breadcrumb(self.handle, self.channelObject, selected_item) # set the content. It needs to be "episodes" to make the MediaItem.set_season_info() work xbmcplugin.setContent(handle=self.handle, content="episodes") xbmcplugin.endOfDirectory(self.handle, ok) except Exception: Logger.error("Plugin::Error Processing FolderList", exc_info=True) XbmcWrapper.show_notification( LanguageHelper.get_localized_string(LanguageHelper.ErrorId), LanguageHelper.get_localized_string(LanguageHelper.ErrorList), XbmcWrapper.Error, 4000) xbmcplugin.endOfDirectory(self.handle, False)
def __init__(self, addon_name, params, handle=0): # NOSONAR complexity """ Initialises the plugin with given arguments. :param str addon_name: The add-on name. :param str params: The input parameters from the query string. :param int handle: The Kodi directory handle. """ Logger.info("******** Starting %s add-on version %s/repo *********", Config.appName, Config.version) # noinspection PyTypeChecker super(Plugin, self).__init__(addon_name, handle, params) Logger.debug(self) # Container Properties self.propertyRetrospect = "Retrospect" self.propertyRetrospectChannel = "RetrospectChannel" self.propertyRetrospectChannelSetting = "RetrospectChannelSettings" self.propertyRetrospectFolder = "RetrospectFolder" self.propertyRetrospectVideo = "RetrospectVideo" self.propertyRetrospectCloaked = "RetrospectCloaked" self.propertyRetrospectCategory = "RetrospectCategory" self.propertyRetrospectFavorite = "RetrospectFavorite" self.propertyRetrospectAdaptive = "RetrospectAdaptive" # channel objects self.channelObject = None self.channelFile = "" self.channelCode = None self.methodContainer = dict( ) # : storage for the inspect.getmembers(channel) method. Improves performance # are we in session? session_active = SessionHelper.is_session_active(Logger.instance()) # fetch some environment settings env_ctrl = envcontroller.EnvController(Logger.instance()) if not session_active: # do add-on start stuff Logger.info("Add-On start detected. Performing startup actions.") # print the folder structure env_ctrl.print_retrospect_settings_and_folders( Config, AddonSettings) # show notification XbmcWrapper.show_notification(None, LanguageHelper.get_localized_string( LanguageHelper.StartingAddonId) % (Config.appName, ), fallback=False, logger=Logger) # check for updates. Using local import for performance from resources.lib.updater import Updater up = Updater(Config.updateUrl, Config.version, UriHandler.instance(), Logger.instance(), AddonSettings.get_release_track()) if up.is_new_version_available(): Logger.info("Found new version online: %s vs %s", up.currentVersion, up.onlineVersion) notification = LanguageHelper.get_localized_string( LanguageHelper.NewVersion2Id) notification = notification % (Config.appName, up.onlineVersion) XbmcWrapper.show_notification(None, lines=notification, display_time=20000) # check for cache folder env_ctrl.cache_check() # do some cache cleanup env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime) # empty picklestore self._pickler.purge_store(Config.addonId) # create a session SessionHelper.create_session(Logger.instance()) #=============================================================================== # Start the plugin version of progwindow #=============================================================================== if len(self.params) == 0: # Show initial start if not in a session # now show the list if AddonSettings.show_categories(): self.show_categories() else: self.show_channel_list() #=============================================================================== # Start the plugin verion of the episode window #=============================================================================== else: # Determine what stage we are in. Check that there are more than 2 Parameters if len(self.params) > 1 and self.keywordChannel in self.params: # retrieve channel characteristics self.channelFile = os.path.splitext( self.params[self.keywordChannel])[0] self.channelCode = self.params[self.keywordChannelCode] Logger.debug( "Found Channel data in URL: channel='%s', code='%s'", self.channelFile, self.channelCode) # import the channel channel_register = ChannelIndex.get_register() channel = channel_register.get_channel(self.channelFile, self.channelCode) if channel is not None: self.channelObject = channel else: Logger.critical( "None or more than one channels were found, unable to continue." ) return # init the channel as plugin self.channelObject.init_channel() Logger.info("Loaded: %s", self.channelObject.channelName) elif self.keywordCategory in self.params \ or self.keywordAction in self.params and ( self.params[self.keywordAction] == self.actionAllFavourites or self.params[self.keywordAction] == self.actionRemoveFavourite): # no channel needed for these favourites actions. pass # =============================================================================== # Vault Actions # =============================================================================== elif self.keywordAction in self.params and \ self.params[self.keywordAction] in \ ( self.actionSetEncryptedValue, self.actionSetEncryptionPin, self.actionResetVault ): try: # Import vault here, as it is only used here or in a channel # that supports it from resources.lib.vault import Vault action = self.params[self.keywordAction] if action == self.actionResetVault: Vault.reset() return v = Vault() if action == self.actionSetEncryptionPin: v.change_pin() elif action == self.actionSetEncryptedValue: v.set_setting( self.params[self.keywordSettingId], self.params.get(self.keywordSettingName, ""), self.params.get(self.keywordSettingActionId, None)) finally: if self.keywordSettingTabFocus in self.params: AddonSettings.show_settings( self.params[self.keywordSettingTabFocus], self.params.get(self.keywordSettingSettingFocus, None)) return elif self.keywordAction in self.params and \ self.actionPostLog in self.params[self.keywordAction]: self.__send_log() return elif self.keywordAction in self.params and \ self.actionProxy in self.params[self.keywordAction]: # do this here to not close the busy dialog on the SetProxy when # a confirm box is shown title = LanguageHelper.get_localized_string( LanguageHelper.ProxyChangeConfirmTitle) content = LanguageHelper.get_localized_string( LanguageHelper.ProxyChangeConfirm) if not XbmcWrapper.show_yes_no(title, content): Logger.warning( "Stopping proxy update due to user intervention") return language = self.params.get(self.keywordLanguage, None) proxy_id = self.params.get(self.keywordProxy, None) local_ip = self.params.get(self.keywordLocalIP, None) self.__set_proxy(language, proxy_id, local_ip) return else: Logger.critical("Error determining Plugin action") return #=============================================================================== # See what needs to be done. #=============================================================================== if self.keywordAction not in self.params: Logger.critical( "Action parameters missing from request. Parameters=%s", self.params) return elif self.params[self.keywordAction] == self.actionListCategory: self.show_channel_list(self.params[self.keywordCategory]) elif self.params[ self.keywordAction] == self.actionConfigureChannel: self.__configure_channel(self.channelObject) elif self.params[self.keywordAction] == self.actionFavourites: # we should show the favourites self.show_favourites(self.channelObject) elif self.params[self.keywordAction] == self.actionAllFavourites: self.show_favourites(None) elif self.params[self.keywordAction] == self.actionListFolder: # channelName and URL is present, Parse the folder self.process_folder_list() elif self.params[self.keywordAction] == self.actionPlayVideo: self.play_video_item() elif not self.params[self.keywordAction] == "": self.on_action_from_context_menu( self.params[self.keywordAction]) else: Logger.warning( "Number of parameters (%s) or parameter (%s) values not implemented", len(self.params), self.params) self.__fetch_textures() return
def get_channels(self, include_disabled=False, **kwargs): # NOSONAR """ Retrieves all enabled channels within Retrospect. If updated channels are found, the those channels are indexed and the channel index is rebuild. :param bool include_disabled: Boolean to indicate if we should include those channels that are explicitly disabled from the settings. :param dict kwargs: Here for backward compatibility. :return: a list of ChannelInfo objects of enabled channels. :rtype: list[ChannelInfo] """ sw = StopWatch("ChannelIndex.get_channels Importer", Logger.instance()) Logger.info("Fetching all enabled channels.") self.__allChannels = [] valid_channels = [] channels_updated = False country_visibility = {} channel_path = os.path.join(Config.rootDir, self.__INTERNAL_CHANNEL_PATH) for channel_pack in os.listdir(channel_path): if not channel_pack.startswith("channel."): continue for channel_set in os.listdir(os.path.join(channel_path, channel_pack)): channel_set_path = os.path.join(channel_path, channel_pack, channel_set) if not os.path.isdir(channel_set_path): continue channel_set_info_path = os.path.join(channel_set_path, "chn_{}.json".format(channel_set)) channel_infos = ChannelInfo.from_json(channel_set_info_path) # Check if the channel was updated if self.__is_channel_set_updated(channel_infos[0]): if not channels_updated: # this was the first update found (otherwise channelsUpdated was True) show a message: title = LanguageHelper.get_localized_string(LanguageHelper.InitChannelTitle) text = LanguageHelper.get_localized_string(LanguageHelper.InitChannelText) XbmcWrapper.show_notification(title, text, display_time=15000, logger=Logger.instance()) channels_updated |= True # Initialise the channelset. self.__initialise_channel_set(channel_infos[0]) # And perform all first actions for the included channels in the set for channel_info in channel_infos: self.__initialise_channel(channel_info) # Check the channel validity for channel_info in channel_infos: if not self.__channel_is_correct(channel_info): continue self.__allChannels.append(channel_info) if channel_info.ignore: Logger.warning("Not loading: %s -> ignored in the channel set", channel_info) continue valid_channels.append(channel_info) # was the channel hidden based on language settings? We do some caching to speed # things up. if channel_info.language not in country_visibility: country_visibility[channel_info.language] = AddonSettings.show_channel_with_language(channel_info.language) channel_info.visible = country_visibility[channel_info.language] # was the channel explicitly disabled from the settings? channel_info.enabled = AddonSettings.get_channel_visibility(channel_info) Logger.debug("Found channel: %s", channel_info) if channels_updated: Logger.info("New or updated channels found. Updating add-on configuration for all channels and user agent.") AddonSettings.update_add_on_settings_with_channels(valid_channels, Config) AddonSettings.update_user_agent() else: Logger.debug("No channel changes found. Skipping add-on configuration for channels.") # TODO: perhaps we should check that the settings.xml is correct and not broken? valid_channels.sort(key=lambda c: c.sort_key) visible_channels = [ci for ci in valid_channels if ci.visible and ci.enabled] Logger.info("Fetch a total of %d channels of which %d are visible.", len(valid_channels), len(visible_channels)) sw.stop() if include_disabled: return valid_channels return visible_channels
def update_video_item(self, item): """ Updates an existing MediaItem with more data. Used to update none complete MediaItems (self.complete = False). This could include opening the item's URL to fetch more data and then process that data or retrieve it's real media-URL. The method should at least: * cache the thumbnail to disk (use self.noImage if no thumb is available). * set at least one MediaItemPart with a single MediaStream. * set self.complete = True. if the returned item does not have a MediaItemPart then the self.complete flag will automatically be set back to False. :param MediaItem item: the original MediaItem that needs updating. :return: The original item with more data added to it's properties. :rtype: MediaItem """ if item.metaData.get(self.__REQUIRES_LOGIN, False): logged_in = self.log_on() if not logged_in: XbmcWrapper.show_dialog(LanguageHelper.LoginErrorTitle, LanguageHelper.LoginErrorText) return item video_data = UriHandler.open(item.url, proxy=self.proxy, additional_headers=self.localIP) if not video_data: return item video_data = JsonHelper(video_data) video_info = video_data.get_value("data", "attributes") errors = video_data.get_value("errors") Logger.error("Error updating items: %s", errors) if errors: return item part = item.create_new_empty_media_part() m3u8url = video_info["streaming"]["hls"]["url"] m3u8data = UriHandler.open(m3u8url, self.proxy) if AddonSettings.use_adaptive_stream_add_on(): stream = part.append_media_stream(m3u8url, 0) item.complete = True M3u8.set_input_stream_addon_input(stream, self.proxy) else: # user agent for all sub m3u8 and ts requests needs to be the same part.HttpHeaders[ "user-agent"] = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)" for s, b, a in M3u8.get_streams_from_m3u8( m3u8url, self.proxy, append_query_string=False, map_audio=True, play_list_data=m3u8data): item.complete = True if a: audio_part = a.split("-prog_index.m3u8", 1)[0] audio_id = audio_part.rsplit("/", 1)[-1] s = s.replace("-prog_index.m3u8", "-{0}-prog_index.m3u8".format(audio_id)) part.append_media_stream(s, b) if self.language == "se": vtt_url = M3u8.get_subtitle(m3u8url, self.proxy, m3u8data, language="sv") elif self.language == "dk": vtt_url = M3u8.get_subtitle(m3u8url, self.proxy, m3u8data, language="da") else: vtt_url = M3u8.get_subtitle(m3u8url, self.proxy, m3u8data) # https://dplaynordics-vod-80.akamaized.net/dplaydni/259/0/hls/243241001/1112635959-prog_index.m3u8?version_hash=bb753129&hdnts=st=1518218118~exp=1518304518~acl=/*~hmac=bdeefe0ec880f8614e14af4d4a5ca4d3260bf2eaa8559e1eb8ba788645f2087a vtt_url = vtt_url.replace("-prog_index.m3u8", "-0.vtt") part.Subtitle = SubtitleHelper.download_subtitle(vtt_url, format='srt', proxy=self.proxy) # if the user has premium, don't show any warnings if self.__has_premium: item.isPaid = False return item
def __init__(self, addon_name, params, handle=0): """ Initialises the plugin with given arguments. :param str addon_name: The add-on name. :param str params: The input parameters from the query string. :param int|str handle: The Kodi directory handle. """ Logger.info("******** Starting %s add-on version %s/repo *********", Config.appName, Config.version) # noinspection PyTypeChecker super(Plugin, self).__init__(addon_name, handle, params) Logger.debug(self) # are we in session? session_active = SessionHelper.is_session_active(Logger.instance()) # fetch some environment settings env_ctrl = envcontroller.EnvController(Logger.instance()) if not session_active: # do add-on start stuff Logger.info("Add-On start detected. Performing startup actions.") # print the folder structure env_ctrl.print_retrospect_settings_and_folders( Config, AddonSettings) # show notification XbmcWrapper.show_notification(None, LanguageHelper.get_localized_string( LanguageHelper.StartingAddonId) % (Config.appName, ), fallback=False, logger=Logger) # check for updates. Using local import for performance from resources.lib.updater import Updater up = Updater(Config.updateUrl, Config.version, UriHandler.instance(), Logger.instance(), AddonSettings.get_release_track()) if up.is_new_version_available(): Logger.info("Found new version online: %s vs %s", up.currentVersion, up.onlineVersion) notification = LanguageHelper.get_localized_string( LanguageHelper.NewVersion2Id) notification = notification % (Config.appName, up.onlineVersion) XbmcWrapper.show_notification(None, lines=notification, display_time=20000) # check for cache folder env_ctrl.cache_check() # do some cache cleanup env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime) # empty picklestore self.pickler.purge_store(Config.addonId) # create a session SessionHelper.create_session(Logger.instance())
def get_channels(self, include_disabled=False, **kwargs): # NOSONAR """ Retrieves all enabled channels within Retrospect. If updated channels are found, the those channels are indexed and the channel index is rebuild. :param bool include_disabled: Boolean to indicate if we should include those channels that are explicitly disabled from the settings. :param dict kwargs: Here for backward compatibility. :return: a list of ChannelInfo objects of enabled channels. :rtype: list[ChannelInfo] """ sw = StopWatch("ChannelIndex.get_channels Importer", Logger.instance()) Logger.info("Fetching all enabled channels.") self.__allChannels = [] valid_channels = [] # What platform are we platform = envcontroller.EnvController.get_platform() channels_updated = False country_visibility = {} for channel_set in self.__channelIndex[self.__CHANNEL_INDEX_CHANNEL_KEY]: channel_set = self.__channelIndex[self.__CHANNEL_INDEX_CHANNEL_KEY][channel_set] channel_set_info_path = channel_set[self.__CHANNEL_INDEX_CHANNEL_INFO_KEY] channel_set_version = channel_set[self.__CHANNEL_INDEX_CHANNEL_VERSION_KEY] # Check if file exists. If not, rebuild index if not os.path.isfile(channel_set_info_path) and not self.__reindexed: Logger.warning("Missing channelSet file: %s.", channel_set_info_path) self.__rebuild_index() return self.get_channels() channel_infos = ChannelInfo.from_json(channel_set_info_path, channel_set_version) # Check if the channel was updated if self.__is_channel_set_updated(channel_infos[0]): # let's see if the index has already been updated this section, of not, do it and # restart the ChannelRetrieval. if not self.__reindexed: # rebuild and restart Logger.warning("Re-index channel index due to channelSet update: %s.", channel_set_info_path) self.__rebuild_index() return self.get_channels() else: Logger.warning("Found updated channelSet: %s.", channel_set_info_path) if not channels_updated: # this was the first update found (otherwise channelsUpdated was True) show a message: title = LanguageHelper.get_localized_string(LanguageHelper.InitChannelTitle) text = LanguageHelper.get_localized_string(LanguageHelper.InitChannelText) XbmcWrapper.show_notification(title, text, display_time=15000, logger=Logger.instance()) channels_updated |= True # Initialise the channelset. self.__initialise_channel_set(channel_infos[0]) # And perform all first actions for the included channels in the set for channel_info in channel_infos: self.__initialise_channel(channel_info) # Check the channel validity for channel_info in channel_infos: if not self.__channel_is_correct(channel_info): continue self.__allChannels.append(channel_info) # valid channel for this platform ? if not channel_info.compatiblePlatforms & platform == platform: Logger.warning("Not loading: %s -> platform '%s' is not compatible.", channel_info, Environments.name(platform)) continue valid_channels.append(channel_info) # was the channel hidden based on language settings? We do some caching to speed # things up. if channel_info.language not in country_visibility: country_visibility[channel_info.language] = AddonSettings.show_channel_with_language(channel_info.language) channel_info.visible = country_visibility[channel_info.language] # was the channel explicitly disabled from the settings? channel_info.enabled = AddonSettings.get_channel_visibility(channel_info) Logger.debug("Found channel: %s", channel_info) if channels_updated: Logger.info("New or updated channels found. Updating add-on configuration for all channels and user agent.") AddonSettings.update_add_on_settings_with_channels(valid_channels, Config) AddonSettings.update_user_agent() else: Logger.debug("No channel changes found. Skipping add-on configuration for channels.") # TODO: perhaps we should check that the settings.xml is correct and not broken? valid_channels.sort(key=lambda c: c.sort_key) visible_channels = [ci for ci in valid_channels if ci.visible and ci.enabled] Logger.info("Fetch a total of %d channels of which %d are visible.", len(valid_channels), len(visible_channels)) sw.stop() if include_disabled: return valid_channels return visible_channels