def set_bitrate(self): """ Sets the bitrate for the selected channel via a specific dialog. """ if self.channelObject is None: raise ValueError("Missing channel") # taken from the settings.xml bitrate_options = "Retrospect|100|250|500|750|1000|1500|2000|2500|4000|8000|20000"\ .split("|") current_bitrate = AddonSettings.get_max_channel_bitrate( self.channelObject) Logger.debug("Found bitrate for %s: %s", self.channelObject, current_bitrate) current_bitrate_index = 0 if current_bitrate not in bitrate_options \ else bitrate_options.index(current_bitrate) dialog = xbmcgui.Dialog() heading = LanguageHelper.get_localized_string( LanguageHelper.BitrateSelection) selected_bitrate = dialog.select(heading, bitrate_options, preselect=current_bitrate_index) if selected_bitrate < 0: return Logger.info("Changing bitrate for %s from %s to %s", self.channelObject, bitrate_options[current_bitrate_index], bitrate_options[selected_bitrate]) AddonSettings.set_max_channel_bitrate( self.channelObject, bitrate_options[selected_bitrate]) return
def show_country_settings(self): """ Shows the country settings page where channels can be shown/hidden based on the country of origin. """ if AddonSettings.is_min_version(18): AddonSettings.show_settings(-99) else: AddonSettings.show_settings(101) self.refresh()
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 __exit__(self, exc_type, exc_val, exc_tb): if exc_val: Logger.critical("Error in menu handling: %s", exc_val.message, exc_info=True) # make sure we leave no references behind AddonSettings.clear_cached_addon_settings_object() # close the log to prevent locking on next call Logger.instance().close_log() return False
def __configure_channel(self, channel_info): """ Shows the current channels settings dialog. :param ChannelInfo channel_info: The channel info for the channel """ if not channel_info: Logger.warning("Cannot configure channel without channel info") Logger.info("Configuring channel: %s", channel_info) AddonSettings.show_channel_settings(channel_info)
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 select_channels(self): """ Selects the channels that should be visible. @return: None """ valid_channels = ChannelIndex.get_register().get_channels( include_disabled=True) channels_to_show = [c for c in valid_channels if c.visible] # The old way # channels_to_show = filter(lambda c: c.visible, valid_channels) selected_channels = [c for c in channels_to_show if c.enabled] selected_indices = list( [channels_to_show.index(c) for c in selected_channels]) Logger.debug("Currently selected channels: %s", selected_indices) channel_to_show_names = [ HtmlEntityHelper.convert_html_entities(c.channelName) for c in channels_to_show ] # The old way # channel_to_show_names = list(map(lambda c: HtmlEntityHelper.convert_html_entities(c.channelName), channels_to_show)) dialog = xbmcgui.Dialog() heading = LanguageHelper.get_localized_string( LanguageHelper.ChannelSelection)[:-1] selected_channels = dialog.multiselect(heading, channel_to_show_names, preselect=selected_indices) if selected_channels is None: return selected_channels = list(selected_channels) Logger.debug("New selected channels: %s", selected_channels) indices_to_remove = [ i for i in selected_indices if i not in selected_channels ] indices_to_add = [ i for i in selected_channels if i not in selected_indices ] for i in indices_to_remove: Logger.info("Hiding channel: %s", channels_to_show[i]) AddonSettings.set_channel_visiblity(channels_to_show[i], False) for i in indices_to_add: Logger.info("Showing channel: %s", channels_to_show[i]) AddonSettings.set_channel_visiblity(channels_to_show[i], True) self.refresh() return
def GetDefaultCachePath(self): """ returns the default cache path for this channel""" # set the UZG path if AddonSettings.GetUzgCacheDuration() > 0: cachPath = AddonSettings.GetUzgCachePath() if cachPath: Logger.Trace("UZG Cache path resolved to: %s", cachPath) return cachPath cachePath = chn_class.Channel.GetDefaultCachePath(self) Logger.Trace("UZG Cache path resolved chn_class default: %s", cachePath) return cachePath
def __ShowFirstTimeMessage(self, channelInfo): # type: (ChannelInfo) -> None """ Checks if it is the first time a channel is executed and if a first time message is available it will be shown Arguments: channelName : string - Name of the channelfile that is loaded channelPath : string - Path of the channelfile Shows a message dialog if the message should be shown. Make sure that each line fits in a single line of a XBMC Dialog box (50 chars) """ hideFirstTime = AddonSettings.HideFirstTimeMessages() if channelInfo.firstTimeMessage: if not hideFirstTime: Logger.Info("Showing first time message '%s' for channel chn_%s.", channelInfo.firstTimeMessage, channelInfo.moduleName) title = LanguageHelper.GetLocalizedString(LanguageHelper.ChannelMessageId) XbmcWrapper.ShowDialog(title, channelInfo.firstTimeMessage.split("|")) else: Logger.Debug("Not showing first time message due to add-on setting set to '%s'.", hideFirstTime) return
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 get_verifiable_video_url(self, url): """ Creates an RTMP(E) url that can be verified using an SWF URL. Returns a new URL that includes the self.swfUrl in the form of "url --swfVfy|-W swfUrl". If self.swfUrl == "", the original URL is returned. :param str url: The URL that should be made verifiable. :return: A new URL that includes the self.swfUrl :rtype: str """ if self.swfUrl == "": return url # TODO: Kodi 17.x also accepts an SWF-url as swfvfy option (https://www.ffmpeg.org/ffmpeg-protocols.html#rtmp). # This option should be set via the XbmcListItem.setProperty, so within Retrospect via: # part.add_property("swfvfy", self.swfUrl) # Or as an URL parameter swfvfy where we add the full URL instead of just 1: # return "%s swfvfy=%s" % (url, self.swfUrl) if AddonSettings.is_min_version(17): Logger.debug("Using Kodi 17+ RTMP parameters") return "%s swfvfy=%s" % (url, self.swfUrl) else: Logger.debug("Using Legacy (Kodi 16 and older) RTMP parameters") return "%s swfurl=%s swfvfy=1" % (url, self.swfUrl)
def __get_title(self, name): """ Create the title based on the MediaItems name and type. :param str name: the name to update. :return: an updated name :rtype: str """ if not name: name = self.name if self.type == 'page': # We need to add the Page prefix to the item name = "%s %s" % (LanguageHelper.get_localized_string( LanguageHelper.Page), name) Logger.debug("MediaItem.__get_title :: Adding Page Prefix") elif self.__date != '' and not self.is_playable(): # not playable items should always show date name = "%s [COLOR=dimgray](%s)[/COLOR]" % (name, self.__date) folder_prefix = AddonSettings.get_folder_prefix() if self.type == "folder" and not folder_prefix == "": name = "%s %s" % (folder_prefix, name) return name
def update_live_item(self, item): """ Updates an existing live 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 """ part = item.create_new_empty_media_part() if AddonSettings.use_adaptive_stream_add_on(): stream = part.append_media_stream(item.url, 0) M3u8.set_input_stream_addon_input(stream, self.proxy) item.complete = True else: for s, b in M3u8.get_streams_from_m3u8(item.url, self.proxy): item.complete = True part.append_media_stream(s, b) return item
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 ShowCategories(self): """Displays the ShowCategories that are currently available in XOT as a directory listing. """ Logger.Info("Plugin::ShowCategories") channelRegister = ChannelImporter.GetRegister() categories = channelRegister.GetCategories() xbmcItems = [] icon = os.path.join(Config.rootDir, "icon.png") fanart = os.path.join(Config.rootDir, "fanart.jpg") for category in categories: name = LanguageHelper.GetLocalizedCategory(category) xbmcItem = xbmcgui.ListItem(name, name) # set art try: xbmcItem.setIconImage(icon) except: # it was deprecated pass xbmcItem.setArt({'thumb': icon, 'icon': icon}) if not AddonSettings.HideFanart(): xbmcItem.setArt({'fanart': fanart}) url = self.__CreateActionUrl(None, action=self.actionListCategory, category=category) xbmcItems.append((url, xbmcItem, True)) Logger.Trace(xbmcItems) ok = xbmcplugin.addDirectoryItems(self.handle, xbmcItems, len(xbmcItems)) xbmcplugin.addSortMethod(handle=self.handle, sortMethod=xbmcplugin.SORT_METHOD_LABEL) xbmcplugin.endOfDirectory(self.handle, ok) return ok
def GetXBMCItem(self): """ Creates an Xbmc ListItem object for this channel """ name = HtmlEntityHelper.ConvertHTMLEntities(self.channelName) description = HtmlEntityHelper.ConvertHTMLEntities(self.channelDescription) self.icon = self.__GetImagePath(self.icon) item = xbmcgui.ListItem(name, description) try: item.setIconImage(self.icon) except: # it was deprecated pass 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.GetFullLanguage(self.language), # "Tagline": description, "Plot": description}) if AddonSettings.HideFanart(): return item if self.fanart is not None: self.fanart = self.__GetImagePath(self.fanart) else: self.fanart = os.path.join(Config.rootDir, "fanart.jpg") item.setArt({'fanart': self.fanart}) return item
def AddLiveChannel(self, data): Logger.Info("Performing Pre-Processing") # if self.channelCode != "vtm": # return data, [] username = AddonSettings.GetSetting("mediaan_username") if not username: return data, [] items = [] if self.channelCode == "vtm": item = MediaItem("Live VTM", "#livestream") else: item = MediaItem("Live Q2", "#livestream") item.type = "video" item.isLive = True item.fanart = self.fanart item.thumb = self.noImage now = datetime.datetime.now() item.SetDate(now.year, now.month, now.day, now.hour, now.minute, now.second) items.append(item) if self.channelCode == "vtm": recent = MediaItem("\a.: Recent :.", "https://vtm.be/video/volledige-afleveringen/id") item.fanart = self.fanart item.thumb = self.noImage item.dontGroup = True items.append(recent) Logger.Debug("Pre-Processing finished") return data, items
def get_setting(self, setting_id): """ Retrieves an encrypted setting from the Kodi Add-on Settings. :param str setting_id: the ID for the setting to retrieve. :return: the decrypted value for the setting. :rtype: str """ Logger.info("Decrypting value for setting '%s'", setting_id) encrypted_value = AddonSettings.get_setting(setting_id) if not encrypted_value: return encrypted_value try: decrypted_value = self.__decrypt(encrypted_value, Vault.__Key) if not decrypted_value.startswith(setting_id): Logger.error("Invalid decrypted value for setting '%s'", setting_id) return None decrypted_value = decrypted_value[len(setting_id) + 1:] Logger.info("Successfully decrypted value for setting '%s'", setting_id) except UnicodeDecodeError: Logger.error( "Invalid Unicode data returned from decryption. Must be wrong data" ) return None return decrypted_value
def GetSetting(self, settingId): """ Retrieves an encrypted setting from the Kodi Add-on Settings. @param settingId: the ID for the setting to retrieve @return: the decrypted value for the setting """ Logger.Info("Decrypting value for setting '%s'", settingId) encryptedValue = AddonSettings.GetSetting(settingId) if not encryptedValue: return encryptedValue try: decryptedValue = self.__Decrypt(encryptedValue, Vault.__Key) if not decryptedValue.startswith(settingId): Logger.Error("Invalid decrypted value for setting '%s'", settingId) return None decryptedValue = decryptedValue[len(settingId) + 1:] Logger.Info("Successfully decrypted value for setting '%s'", settingId) except UnicodeDecodeError: Logger.Error("Invalid Unicode data returned from decryption. Must be wrong data") return None return decryptedValue
def __GetApplicationKey(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 """ applicationKeyEncrypted = AddonSettings.GetSetting(Vault.__APPLICATION_KEY_SETTING) if not applicationKeyEncrypted: return None vaultIncorrectPin = LanguageHelper.GetLocalizedString(LanguageHelper.VaultIncorrectPin) pin = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultInputPin), hidden=True) if not pin: XbmcWrapper.ShowNotification("", vaultIncorrectPin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") pinKey = self.__GetPBK(pin) applicationKey = self.__Decrypt(applicationKeyEncrypted, pinKey) if not applicationKey.startswith(Vault.__APPLICATION_KEY_SETTING): Logger.Critical("Invalid Retrospect PIN") XbmcWrapper.ShowNotification("", vaultIncorrectPin, XbmcWrapper.Error) raise RuntimeError("Incorrect Retrospect PIN specified") applicationKeyValue = applicationKey[len(Vault.__APPLICATION_KEY_SETTING) + 1:] Logger.Info("Successfully decrypted the ApplicationKey.") return applicationKeyValue
def __GetTitle(self, name): """ Create the title based on the MediaItems name and type. @param name: (string) the name to update @return: (string) an updated name """ if not name: name = self.name if self.type == 'page': # We need to add the Page prefix to the item name = "Page %s" % (name, ) Logger.Debug("GetXBMCItem :: Adding Page Prefix") elif self.__date != '' and not self.IsPlayable(): # not playable items should always show date name = "%s (%s)" % (name, self.__date) folderPrefix = AddonSettings.GetFolderPrefix() if self.type == "folder" and not folderPrefix == "": name = "%s %s" % (folderPrefix, name) return name
def update_json_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 """ headers = {} if self.localIP: headers.update(self.localIP) data = UriHandler.open(item.url, proxy=self.proxy, additional_headers=headers) video_data = JsonHelper(data) stream_data = video_data.get_value("mediaAssetsOnDemand") if not stream_data: return item use_adaptive = AddonSettings.use_adaptive_stream_add_on() stream_data = stream_data[0] part = item.create_new_empty_media_part() if "hlsUrl" in stream_data: hls_url = stream_data["hlsUrl"] if use_adaptive: stream = part.append_media_stream(hls_url, 0) M3u8.set_input_stream_addon_input(stream, self.proxy, headers=headers) item.complete = True else: for s, b in M3u8.get_streams_from_m3u8(hls_url, self.proxy, headers=headers): item.complete = True part.append_media_stream(s, b) if "timedTextSubtitlesUrl" in stream_data and stream_data[ "timedTextSubtitlesUrl"]: sub_url = stream_data["timedTextSubtitlesUrl"].replace( ".ttml", ".vtt") sub_url = HtmlEntityHelper.url_decode(sub_url) part.Subtitle = SubtitleHelper.download_subtitle(sub_url, format="webvtt") return item
def __IsNewVersionAvailable(self): """ Verifies that there is a new version available. It compares the addons.xml.md5 with the stored one. @return: True or False """ # first check if there is a "channel" folder channelPath = os.path.join(Config.rootDir, "channels") if not os.path.isdir(channelPath): Logger.Warning( "No Channels found at '%s', skipping updates for now.", channelPath) return False onlineData = UriHandler.Open(Config.UpdateUrl) self.__NewMd5 = onlineData.strip() self.__OldMd5 = AddonSettings.GetCurrentAddonXmlMd5() updated = self.__OldMd5 != self.__NewMd5 if updated: Logger.Info("New updates are available ('%s' vs '%s')", self.__OldMd5, self.__NewMd5) else: Logger.Info("No new updates available, MD5 hashes match") return updated
def LogOn(self): signatureSettings = "mediaan_signature" signatureSetting = AddonSettings.GetSetting(signatureSettings) # apiKey = "3_HZ0FtkMW_gOyKlqQzW5_0FHRC7Nd5XpXJZcDdXY4pk5eES2ZWmejRW5egwVm4ug-" # from VTM apiKey = "3_OEz9nzakKMkhPdUnz41EqSRfhJg5z9JXvS4wUORkqNf2M2c1wS81ilBgCewkot97" # from Stievie if signatureSetting and "|" not in signatureSetting: url = "https://accounts.eu1.gigya.com/accounts.getAccountInfo" data = "APIKey=%s" \ "&sdk=js_7.4.30" \ "&login_token=%s" % (apiKey, signatureSetting, ) logonData = UriHandler.Open(url, params=data, proxy=self.proxy, noCache=True) if self.__ExtractSessionData(logonData, signatureSettings): return True Logger.Warning("Failed to extend the VTM.be session.") Logger.Info("Logging onto VTM.be") v = Vault() password = v.GetSetting("mediaan_password") username = AddonSettings.GetSetting("mediaan_username") if not username or not password: XbmcWrapper.ShowDialog( title=None, lines=LanguageHelper.GetLocalizedString( LanguageHelper.MissingCredentials), # notificationType=XbmcWrapper.Error, # displayTime=5000 ) return False Logger.Debug("Using: %s / %s", username, "*" * len(password)) url = "https://accounts.eu1.gigya.com/accounts.login" data = "loginID=%s" \ "&password=%s" \ "&targetEnv=jssdk" \ "&APIKey=%s" \ "&includeSSOToken=true" \ "&authMode=cookie" % \ (HtmlEntityHelper.UrlEncode(username), HtmlEntityHelper.UrlEncode(password), apiKey) logonData = UriHandler.Open(url, params=data, proxy=self.proxy, noCache=True) return self.__ExtractSessionData(logonData, signatureSettings)
def __set_proxy(self, language, proxy_id, local_ip): """ Sets the proxy and local IP configuration for channels. :param str language: The language for what channels to update. :param int proxy_id: The proxy index to use. :param int local_ip: The local_ip index to use. If no proxy_id is specified (None) then the proxy_id will be determined based on language If no local_ip is specified (None) then the local_ip will be determined based on language """ languages = AddonSettings.get_available_countries( as_country_codes=True) if language is not None and language not in languages: Logger.warning("Missing language: %s", language) return if proxy_id is None: proxy_id = languages.index(language) else: # noinspection PyTypeChecker proxy_id = int(proxy_id) if local_ip is None: local_ip = languages.index(language) else: # noinspection PyTypeChecker local_ip = int(local_ip) channels = ChannelIndex.get_register().get_channels() Logger.info( "Setting proxy='%s' (%s) and local_ip='%s' (%s) for country '%s'", proxy_id, languages[proxy_id], local_ip, languages[local_ip], language) channels_in_country = [ c for c in channels if c.language == language or language is None ] for channel in channels_in_country: Logger.debug("Setting Proxy for: %s", channel) AddonSettings.set_proxy_id_for_channel(channel, proxy_id) if channel.localIPSupported: Logger.debug("Setting Local IP for: %s", channel) AddonSettings.set_local_ip_for_channel(channel, local_ip)
def DownloadVideoItem(self, item): """Downloads an existing MediaItem with more data. Arguments: item : MediaItem - the MediaItem that should be downloaded. Returns: The original item with more data added to it's properties. Used to download an <item>. If the item is not complete, the self.UpdateVideoItem method is called to update the item. The method downloads only the MediaStream with the bitrate that was set in the addon settings. After downloading the self.downloaded property is set. """ if not item.IsPlayable(): Logger.Error("Cannot download a folder item.") return item if item.IsPlayable(): if not item.complete: Logger.Info("Fetching MediaUrl for PlayableItem[%s]", item.type) item = self.ProcessVideoItem(item) if not item.complete or not item.HasMediaItemParts(): Logger.Error("Cannot download incomplete item or item without MediaItemParts") return item i = 1 bitrate = AddonSettings.GetMaxStreamBitrate() for mediaItemPart in item.MediaItemParts: Logger.Info("Trying to download %s", mediaItemPart) stream = mediaItemPart.GetMediaStreamForBitrate(bitrate) downloadUrl = stream.Url extension = UriHandler.GetExtensionFromUrl(downloadUrl) if len(item.MediaItemParts) > 1: saveFileName = "%s-Part_%s.%s" % (item.name, i, extension) else: saveFileName = "%s.%s" % (item.name, extension) Logger.Debug(saveFileName) # headers = item.HttpHeaders + mediaItemPart.HttpHeaders headers = item.HttpHeaders.copy() headers.update(mediaItemPart.HttpHeaders) progressDialog = XbmcDialogProgressWrapper("Downloading Item", item.name, stream.Url) folderName = XbmcWrapper.ShowFolderSelection('Select download destination for "%s"' % (saveFileName, )) UriHandler.Download(downloadUrl, saveFileName, folderName, progressDialog, proxy=self.proxy, additionalHeaders=headers) i += 1 item.downloaded = True return item
def _GetSetting(self, settingId, valueForNone=None): """ Retrieves channel specific settings. Just to prevent us from importing AddonSettings in all channels. @param settingId: the channels specific setting @return: the settings value from the Add-on using the Kodi settings API """ setting = AddonSettings.GetChannelSetting(self.guid, settingId, valueForNone) return setting
def ChangePin(self, applicationKey=None): # type: (str) -> bool """ Stores an existing ApplicationKey using a new PIN @param applicationKey: an existing ApplicationKey that will be stored. If none specified, the existing ApplicationKey of the Vault will be used. @return: indication of success """ Logger.Info("Updating the ApplicationKey with a new PIN") if self.__newKeyGeneratedInConstructor: Logger.Info("A key was just generated, no need to change PINs.") return True if applicationKey is None: Logger.Debug("Using the ApplicationKey from the vault.") applicationKey = Vault.__Key else: Logger.Debug("Using the ApplicationKey from the input parameter.") if not applicationKey: raise ValueError("No ApplicationKey specified.") # Now we get a new PIN and (re)encrypt pin = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultNewPin), hidden=True) if not pin: XbmcWrapper.ShowNotification( "", LanguageHelper.GetLocalizedString(LanguageHelper.VaultNoPin), XbmcWrapper.Error) return False pin2 = XbmcWrapper.ShowKeyBoard( heading=LanguageHelper.GetLocalizedString(LanguageHelper.VaultRepeatPin), hidden=True) if pin != pin2: Logger.Critical("Mismatch in PINs") XbmcWrapper.ShowNotification( "", LanguageHelper.GetLocalizedString(LanguageHelper.VaultPinsDontMatch), XbmcWrapper.Error) return False encryptedKey = "%s=%s" % (self.__APPLICATION_KEY_SETTING, applicationKey) # let's generate a pin using the scrypt password-based key derivation pinKey = self.__GetPBK(pin) encryptedKey = self.__Encrypt(encryptedKey, pinKey) AddonSettings.SetSetting(Vault.__APPLICATION_KEY_SETTING, encryptedKey) Logger.Info("Successfully updated the Retrospect PIN") return True
def __GetPBK(self, pin): salt = AddonSettings.GetClientId() pbk = pyscrypt.hash(password=pin, salt=salt, N=2 ** 7, # should be so that Raspberry Pi can handle it # N=1024, r=1, p=1, dkLen=32) Logger.Trace("Generated PBK with MD5: %s", hashlib.md5(pbk).hexdigest()) return pbk
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