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 create_folder_item(self, result_set): """ Creates a MediaItem of type 'folder' using the result_set from the regex. This method creates a new MediaItem from the Regular Expression or Json results <result_set>. The method should be implemented by derived classes and are specific to the channel. :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'folder'. :rtype: MediaItem|None """ if len(result_set) > 3 and result_set[3] != "": Logger.debug("Sub category folder found.") url = parse.urljoin(self.baseUrl, HtmlEntityHelper.convert_html_entities(result_set[3])) name = "\a.: %s :." % (result_set[4],) item = MediaItem(name, url) item.thumb = self.noImage item.complete = True item.type = "folder" return item url = parse.urljoin(self.baseUrl, HtmlEntityHelper.convert_html_entities(result_set[0])) name = HtmlEntityHelper.convert_html_entities(result_set[1]) helper = HtmlHelper(result_set[2]) description = helper.get_tag_content("div", {'class': 'description'}) item = MediaItem(name, "%s/RSS" % (url,)) item.thumb = self.noImage item.type = 'folder' item.description = description.strip() date = helper.get_tag_content("div", {'class': 'date'}) if date == "": date = helper.get_tag_content("span", {'class': 'lastPublishedDate'}) if not date == "": date_parts = Regexer.do_regex(r"(\w+) (\d+)[^<]+, (\d+)", date) if len(date_parts) > 0: date_parts = date_parts[0] month_part = date_parts[0].lower() day_part = date_parts[1] year_part = date_parts[2] try: month = DateHelper.get_month_from_name(month_part, "en") item.set_date(year_part, month, day_part) except: Logger.error("Error matching month: %s", month_part, exc_info=True) item.complete = True return item
def CreatePageItem(self, resultSet): item = chn_class.Channel.CreatePageItem(self, resultSet) url = "%s/auvio/archives%s%s" % (self.baseUrl, HtmlEntityHelper.UrlDecode( resultSet[0]), resultSet[1]) item.url = url return item
def UpdateVideoItem(self, item): data = UriHandler.Open(item.url, proxy=self.proxy, additionalHeaders=item.HttpHeaders) mediaRegex = 'data-media="([^"]+)"' mediaInfo = Regexer.DoRegex(mediaRegex, data)[0] mediaInfo = HtmlEntityHelper.ConvertHTMLEntities(mediaInfo) mediaInfo = JsonHelper(mediaInfo) Logger.Trace(mediaInfo) # sources part = item.CreateNewEmptyMediaPart() # high, web, mobile, url mediaSources = mediaInfo.json.get("sources", {}) for quality in mediaSources: url = mediaSources[quality] if quality == "high": bitrate = 2000 elif quality == "web": bitrate = 800 elif quality == "mobile": bitrate = 400 else: bitrate = 0 part.AppendMediaStream(url, bitrate) # geoLocRestriction item.isGeoLocked = not mediaInfo.GetValue("geoLocRestriction", fallback="world") == "world" item.complete = True return item
def create_category_item(self, result_set): """ Creates a MediaItem of type 'folder' using the result_set from the regex. This method creates a new MediaItem from the Regular Expression or Json results <result_set>. The method should be implemented by derived classes and are specific to the channel. :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'folder'. :rtype: MediaItem|None """ Logger.trace(result_set) cat = HtmlEntityHelper.url_encode(result_set['nid']) url = "http://webapi.tv4play.se/play/programs?platform=tablet&category=%s" \ "&fl=nid,name,program_image,category,logo,is_premium" \ "&per_page=1000&is_active=true&start=0" % (cat, ) item = MediaItem(result_set['name'], url) item.thumb = self.noImage item.type = 'folder' item.complete = True return item
def update_video_item(self, item): data = UriHandler.open(item.url, proxy=self.proxy, additional_headers=item.HttpHeaders) media_regex = 'data-media="([^"]+)"' media_info = Regexer.do_regex(media_regex, data)[0] media_info = HtmlEntityHelper.convert_html_entities(media_info) media_info = JsonHelper(media_info) Logger.trace(media_info) # sources part = item.create_new_empty_media_part() # high, web, mobile, url media_sources = media_info.json.get("sources", {}) for quality in media_sources: url = media_sources[quality] if quality == "high": bitrate = 2000 elif quality == "web": bitrate = 800 elif quality == "mobile": bitrate = 400 else: bitrate = 0 part.append_media_stream(url, bitrate) # geoLocRestriction item.isGeoLocked = not media_info.get_value( "geoLocRestriction", fallback="world") == "world" item.complete = True return item
def CreatePageItem(self, resultSet): """Creates a MediaItem of type 'page' using the resultSet from the regex. Arguments: resultSet : tuple(string) - the resultSet of the self.pageNavigationRegex Returns: A new MediaItem of type 'page' This method creates a new MediaItem from the Regular Expression results <resultSet>. The method should be implemented by derived classes and are specific to the channel. """ url = urlparse.urljoin( self.baseUrl, HtmlEntityHelper.ConvertHTMLEntities(resultSet[0])) item = mediaitem.MediaItem(resultSet[self.pageNavigationRegexIndex], url) item.type = "page" item.complete = True item.SetDate(2022, 1, 1, text="") Logger.Trace("Created '%s' for url %s", item.name, item.url) return item
def CreateFolderItem(self, resultSet): """Creates a MediaItem of type 'folder' using the resultSet from the regex. Arguments: resultSet : tuple(strig) - the resultSet of the self.folderItemRegex Returns: A new MediaItem of type 'folder' This method creates a new MediaItem from the Regular Expression or Json results <resultSet>. The method should be implemented by derived classes and are specific to the channel. """ Logger.Trace(resultSet) cat = HtmlEntityHelper.UrlEncode(resultSet['nid']) url = "http://webapi.tv4play.se/play/programs?platform=tablet&category=%s" \ "&fl=nid,name,program_image,category,logo,is_premium" \ "&per_page=%s&is_active=true&start=0" % (cat, self.maxPageSize) item = mediaitem.MediaItem(resultSet['name'], url) item.thumb = self.noImage item.type = 'folder' item.complete = True return item
def __add_breadcrumb(self, handle, channel, selected_item, last_only=False): """ Updates the Kodi category with a breadcrumb to the current parent item :param int handle: The Kodi file handle :param ChannelInfo|Channel channel: The channel to which the item belongs :param MediaItem selected_item: The item from which to show the breadcrumbs :param bool last_only: Show only the last item """ bread_crumb = None if selected_item is not None: bread_crumb = selected_item.name elif self.channelObject is not None: bread_crumb = channel.channelName if not bread_crumb: return bread_crumb = HtmlEntityHelper.convert_html_entities(bread_crumb) xbmcplugin.setPluginCategory(handle=handle, category=bread_crumb)
def SearchSite(self, url=None): """Creates an list of items by searching the site Keyword Arguments: url : String - Url to use to search with a %s for the search parameters Returns: A list of MediaItems that should be displayed. 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. """ if self.useOldParsing: url = "%s?term=%%s" % (self.__GetSearchUrl(), ) else: # we need to do some ugly stuff to get the %s in the URL-Encoded query. query = '{"term":"tttt","limit":2000,"columns":"formats,episodes,clips","with":"format"}' query = HtmlEntityHelper.UrlEncode(query).replace("%", "%%").replace( "tttt", "%s") baseUrl = self.baseUrl.rsplit('/', 1)[0] url = "%s/api/playClient;isColumn=true;query=%s;resource=search?returnMeta=true" % ( baseUrl, query) Logger.Debug("Using %s search url: %s", "old" if self.useOldParsing else "new", url) return chn_class.Channel.SearchSite(self, url)
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 create_page_item(self, result_set): """ Creates a MediaItem of type 'page' using the result_set from the regex. This method creates a new MediaItem from the Regular Expression or Json results <result_set>. The method should be implemented by derived classes and are specific to the channel. :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'page'. :rtype: MediaItem|None """ Logger.debug("Starting create_page_item") total = '' for result in result_set: total = "%s%s" % (total, result) total = HtmlEntityHelper.strip_amp(total) if not self.pageNavigationRegexIndex == '': item = MediaItem(result_set[self.pageNavigationRegexIndex], parse.urljoin(self.baseUrl, total)) else: item = MediaItem("0", "") item.type = "page" item.fanart = self.fanart item.HttpHeaders = self.httpHeaders Logger.debug("Created '%s' for url %s", item.name, item.url) return item
def create_episode_item(self, result_set): """ Creates a new MediaItem for an episode. This method creates a new MediaItem from the Regular Expression or Json results <result_set>. The method should be implemented by derived classes and are specific to the channel. :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'folder'. :rtype: MediaItem|None """ url = parse.urljoin( self.baseUrl, HtmlEntityHelper.convert_html_entities(result_set[0])) name = result_set[1] if name == "Tags": return None if name == "Authors": return None if name == "Most Viewed": return None if name == "Top Rated": name = "Recent" url = "http://channel9.msdn.com/Feeds/RSS" else: url = "%s?sort=atoz" % (url, ) item = MediaItem(name, url) item.icon = self.icon item.complete = True 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') item.icon = self.icon 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 CreateEpisodeItem(self, resultSet): """ Accepts an arraylist of results. It returns an item. """ url = urlparse.urljoin( self.baseUrl, HtmlEntityHelper.ConvertHTMLEntities(resultSet[0])) name = resultSet[1] if name == "Tags": return None if name == "Authors": return None if name == "Most Viewed": return None if name == "Top Rated": name = "Recent" url = "http://channel9.msdn.com/Feeds/RSS" else: url = "%s?sort=atoz" % (url, ) item = mediaitem.MediaItem(name, url) item.icon = self.icon item.complete = True return item
def UpdateVideoItem(self, item): """ Accepts an item. It returns an updated item. Usually retrieves the MediaURL and the Thumb! It should return a completed item. """ Logger.Debug('Starting UpdateVideoItem for %s (%s)', item.name, self.channelName) # now the mediaurl is derived. First we try WMV data = UriHandler.Open(item.url) urls = Regexer.DoRegex( '<a href="([^"]+.(?:wmv|mp4))">(High|Medium|Mid|Low|MP4)', data) mediaPart = mediaitem.MediaItemPart(item.name) for url in urls: if url[1].lower() == "high": bitrate = 2000 elif url[1].lower() == "medium" or url[1].lower() == "mid": bitrate = 1200 elif url[1].lower() == "low" or url[1].lower() == "mp4": bitrate = 200 else: bitrate = 0 mediaPart.AppendMediaStream( HtmlEntityHelper.ConvertHTMLEntities(url[0]), bitrate) item.MediaItemParts.append(mediaPart) #images = Regexer.DoRegex('<link type="image/jpeg" rel="videothumbnail" href="([^"]+)"/>', data) #for image in images: # thumbUrl = htmlentityhelper.HtmlEntityHelper.ConvertHTMLEntities(image) # break item.complete = True return item
def __convert_ttml_to_srt(ttml): """Converts sami format into SRT format: Arguments: ttml : string - TTML (Timed Text Markup Language) subtitle format Returns: SRT formatted subtitle: Example: 1 00:00:20,000 --> 00:00:24,400 text """ pars_regex = r'<p[^>]+begin="([^"]+)\.(\d+)"[^>]+end="([^"]+)\.(\d+)"[^>]*>([\w\W]+?)</p>' subs = Regexer.do_regex(pars_regex, ttml) srt = "" i = 1 for sub in subs: try: start = "%s,%03d" % (sub[0], int(sub[1])) end = "%s,%03d" % (sub[2], int(sub[3])) text = sub[4].replace("<br />", "\n") text = HtmlEntityHelper.convert_html_entities(text) text = text.replace("\r\n", "") srt = "%s\n%s\n%s --> %s\n%s\n" % (srt, i, start, end, text.strip()) i += 1 except: Logger.error("Error parsing subtitle: %s", sub[1], exc_info=True) return srt
def CreateEpisodeItem(self, resultSet): """Creates a new MediaItem for an episode Arguments: resultSet : list[string] - the resultSet of the self.episodeItemRegex Returns: A new MediaItem of type 'folder' This method creates a new MediaItem from the Regular Expression results <resultSet>. The method should be implemented by derived classes and are specific to the channel. """ Logger.Trace(resultSet) url = resultSet[0] if "&" in url: url = HtmlEntityHelper.ConvertHTMLEntities(url) if not url.startswith("http:"): url = "%s%s" % (self.baseUrl, url) # get the ajax page for less bandwidth url = "%s?sida=1&sort=tid_stigande&embed=true" % (url, ) item = mediaitem.MediaItem(resultSet[1], url) item.icon = self.icon item.thumb = self.noImage item.complete = True return item
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 __send_paste_bin(self, name, code, expire='1M', paste_format=None, user_key=None): """ Send a file to pastebin.com :param str|unicode name: Name of the logfile paste/gist. :param str code: The content to post. :param str|unicode expire: Expiration time. :param str|unicode paste_format: The format for the file. :param str|unicode user_key: The user API key. :return: The result of the upload. :rtype: any """ if not name: raise ValueError("Name missing") if not code: raise ValueError("No code data specified") params = { 'api_option': 'paste', 'api_paste_private': 1, # 0=public 1=unlisted 2=private 'api_paste_name': name, 'api_paste_expire_date': expire, 'api_dev_key': self.__apiKey, 'api_paste_code': code, } if paste_format: params['api_paste_format'] = paste_format if user_key: params['api_user_key'] = user_key post_params = "" for k in params.keys(): post_params = "{0}&{1}={2}".format( post_params, k, HtmlEntityHelper.url_encode(str(params[k]))) post_params = post_params.lstrip("&") if self.__logger: self.__logger.debug("Posting %d chars to pastebin.com", len(code)) data = UriHandler.open("http://pastebin.com/api/api_post.php", params=post_params, proxy=self.__proxy) if "pastebin.com" not in data: raise IOError(data) if self.__logger: self.__logger.info("PasteBin: %s", data) return data
def __GetToken(self): """ Requests a playback token """ if not self.loggedOn: self.loggedOn = self.LogOn() if not self.loggedOn: Logger.Warning("Cannot log on") return None # Q2: https://user.medialaan.io/user/v1/gigya/request_token?uid=897b786c46e3462eac81549453680c0d&signature=SM7b5ciP09Z0gbcaCoZ%2B7r4b3uk%3D×tamp=1484691251&apikey=q2-html5-NNSMRSQSwGMDAjWKexV4e5Vm6eSPtupk&database=q2-sso&_=1484691247493 # VTM: https://user.medialaan.io/user/v1/gigya/request_token?uid=897b786c46e3462eac81549453680c0d&signature=Ak10FWFpuF2cSXfmGnNIBsJV4ss%3D×tamp=1481233821&apikey=vtm-b7sJGrKwMJj0VhdZvqLDFvgkJF5NLjNY&database=vtm-sso url = "https://user.medialaan.io/user/v1/gigya/request_token?uid=%s&signature=%s×tamp=%s&apikey=%s&database=%s" % ( self.__userId, HtmlEntityHelper.UrlEncode( self.__signature), self.__signatureTimeStamp, HtmlEntityHelper.UrlEncode(self.__apiKey), self.__sso) data = UriHandler.Open(url, proxy=self.proxy, noCache=True) jsonData = JsonHelper(data) return jsonData.GetValue("response")
def get_license_key(key_url, key_type="R", key_headers=None, key_value=None, json_filter=""): """ Generates a propery license key value # A{SSM} -> not implemented # R{SSM} -> raw format # B{SSM} -> base64 format URL encoded (b{ssmm} will not URL encode) # D{SSM} -> decimal format The generic format for a LicenseKey is: |<url>|<headers>|<key with placeholders>|<optional json filter> The Widevine Decryption Key Identifier (KID) can be inserted via the placeholder {KID} :param str key_url: The URL where the license key can be obtained. :param str|None key_type: The key type (A, R, B or D). :param dict[str,str] key_headers: A dictionary that contains the HTTP headers to pass. :param str key_value: The value that is beging passed on as the key value. :param str json_filter: If specified selects that json element to extract the key response. :return: A formated license string that can be passed to the adaptive input add-on. :rtype: str """ header = "" if key_headers: for k, v in key_headers.items(): header = "{0}&{1}={2}".format(header, k, HtmlEntityHelper.url_encode(v)) if key_type in ("A", "R", "B"): key_value = "{0}{{SSM}}".format(key_type) elif key_type == "D": if "D{SSM}" not in key_value: raise ValueError("Missing D{SSM} placeholder") key_value = HtmlEntityHelper.url_encode(key_value) return "{0}|{1}|{2}|{3}".format(key_url, header.strip("&"), key_value, json_filter)
def SearchSite(self, url=None): """Creates an list of items by searching the site Keyword Arguments: url : String - Url to use to search with a %s for the search parameters Returns: A list of MediaItems that should be displayed. 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. """ if self.primaryChannelId: shows_url = "https://disco-api.dplay.se/content/shows?" \ "include=genres%%2Cimages%%2CprimaryChannel.images&" \ "filter%%5BprimaryChannel.id%%5D={0}&" \ "page%%5Bsize%%5D={1}&query=%s"\ .format(self.primaryChannelId or "", self.programPageSize) videos_url = "https://disco-api.dplay.se/content/videos?decorators=viewingHistory&" \ "include=images%%2CprimaryChannel%%2Cshow&" \ "filter%%5BprimaryChannel.id%%5D={0}&" \ "page%%5Bsize%%5D={1}&query=%s"\ .format(self.primaryChannelId or "", self.videoPageSize) else: shows_url = "https://disco-api.dplay.se/content/shows?" \ "include=genres%%2Cimages%%2CprimaryChannel.images&" \ "page%%5Bsize%%5D={0}&query=%s" \ .format(self.programPageSize) videos_url = "https://disco-api.dplay.se/content/videos?decorators=viewingHistory&" \ "include=images%%2CprimaryChannel%%2Cshow&" \ "page%%5Bsize%%5D={0}&query=%s" \ .format(self.videoPageSize) needle = XbmcWrapper.ShowKeyBoard() if needle: Logger.Debug("Searching for '%s'", needle) needle = HtmlEntityHelper.UrlEncode(needle) searchUrl = videos_url % (needle, ) temp = mediaitem.MediaItem("Search", searchUrl) episodes = self.ProcessFolderList(temp) searchUrl = shows_url % (needle, ) temp = mediaitem.MediaItem("Search", searchUrl) shows = self.ProcessFolderList(temp) return shows + episodes return []
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: other = LanguageHelper.get_localized_string( LanguageHelper.OtherAddon) name = "{0} {1} [COLOR gold]{2}[/COLOR]".format( name, unichr(187), other) self.icon = self.__get_image_path(self.icon) item = xbmcgui.ListItem(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 AddonSettings.hide_fanart(): return item if self.fanart is not None: self.fanart = self.__get_image_path(self.fanart) else: self.fanart = os.path.join(Config.rootDir, "fanart.jpg") item.setArt({'fanart': self.fanart}) 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 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 CreateEpisodeItem(self, resultSet): """ Accepts an arraylist of results. It returns an item. """ #<a class='nArrow' href='([^']+)' title='[^']*'>([^<]+)</a> # 0 1 item = mediaitem.MediaItem(resultSet[1], HtmlEntityHelper.StripAmp("%s%s" % (self.baseUrl, resultSet[0]))) item.icon = self.icon Logger.Trace("%s (%s)", item.name, item.url) return item
def __update_m3u8(self, url, part, headers, use_kodi_hls): """ Update a video that has M3u8 streams. :param str url: The URL for the stream. :param MediaItemPart part: The new part that needs updating. :param dict[str,str] headers: The URL headers to use. :param bool use_kodi_hls: Should we use the InputStream Adaptive add-on? """ # first see if there are streams in this file, else check the second location. for s, b in M3u8.get_streams_from_m3u8(url, self.proxy, headers=headers): if use_kodi_hls: strm = part.append_media_stream(url, 0) M3u8.set_input_stream_addon_input(strm, headers=headers) # Only the main M3u8 is needed break else: part.append_media_stream(s, b) if not part.MediaStreams and "manifest.m3u8" in url: Logger.warning( "No streams found in %s, trying alternative with 'master.m3u8'", url) url = url.replace("manifest.m3u8", "master.m3u8") for s, b in M3u8.get_streams_from_m3u8(url, self.proxy, headers=headers): if use_kodi_hls: strm = part.append_media_stream(url, 0) M3u8.set_input_stream_addon_input(strm, headers=headers) # Only the main M3u8 is needed break else: part.append_media_stream(s, b) # check for subs # https://mtgxse01-vh.akamaihd.net/i/201703/13/DCjOLN_1489416462884_427ff3d3_,48,260,460,900,1800,2800,.mp4.csmil/master.m3u8?__b__=300&hdnts=st=1489687185~exp=3637170832~acl=/*~hmac=d0e12e62c219d96798e5b5ef31b11fa848724516b255897efe9808c8a499308b&cc1=name=Svenska%20f%C3%B6r%20h%C3%B6rselskadade~default=no~forced=no~lang=sv~uri=https%3A%2F%2Fsubstitch.play.mtgx.tv%2Fsubtitle%2Fconvert%2Fxml%3Fsource%3Dhttps%3A%2F%2Fcdn-subtitles-mtgx-tv.akamaized.net%2Fpitcher%2F20xxxxxx%2F2039xxxx%2F203969xx%2F20396967%2F20396967-swt.xml%26output%3Dm3u8 # https://cdn-subtitles-mtgx-tv.akamaized.net/pitcher/20xxxxxx/2039xxxx/203969xx/20396967/20396967-swt.xml&output=m3u8 if "uri=" in url and not part.Subtitle: Logger.debug("Extracting subs from M3u8") sub_url = url.rsplit("uri=")[-1] sub_url = HtmlEntityHelper.url_decode(sub_url) sub_data = UriHandler.open(sub_url, proxy=self.proxy) subs = [ line for line in sub_data.split("\n") if line.startswith("http") ] if subs: part.Subtitle = SubtitleHelper.download_subtitle( subs[0], format='webvtt', proxy=self.proxy) return
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 LogOn(self): tokenCookie = UriHandler.GetCookie("X-VRT-Token", ".vrt.be") if tokenCookie is not None: return True username = self._GetSetting("username") if not username: return None v = Vault() password = v.GetChannelSetting(self.guid, "password") if not password: Logger.Warning("Found empty password for VRT user") Logger.Debug("Using: %s / %s", username, "*" * len(password)) url = "https://accounts.eu1.gigya.com/accounts.login" data = "loginID=%s" \ "&password=%s" \ "&targetEnv=jssdk" \ "&APIKey=3_qhEcPa5JGFROVwu5SWKqJ4mVOIkwlFNMSKwzPDAh8QZOtHqu6L4nD5Q7lk0eXOOG" \ "&includeSSOToken=true" \ "&authMode=cookie" % \ (HtmlEntityHelper.UrlEncode(username), HtmlEntityHelper.UrlEncode(password)) logonData = UriHandler.Open(url, params=data, proxy=self.proxy, noCache=True) sig, uid, timestamp = self.__ExtractSessionData(logonData) if sig is None and uid is None and timestamp is None: return False url = "https://token.vrt.be/" tokenData = '{"uid": "%s", ' \ '"uidsig": "%s", ' \ '"ts": "%s", ' \ '"fn": "VRT", "ln": "NU", ' \ '"email": "%s"}' % (uid, sig, timestamp, username) headers = {"Content-Type": "application/json", "Referer": "https://www.vrt.be/vrtnu/"} UriHandler.Open(url, params=tokenData, proxy=self.proxy, additionalHeaders=headers) return True
def __get_video_streams(self, video_id, part): """ Fetches the video stream for a given videoId @param video_id: (integer) the videoId @param part: (MediaPart) the mediapart to add the streams to @return: (bool) indicating a successfull retrieval """ # hardcoded for now as it does not seem top matter dscgeo = '{"countryCode":"%s","expiry":1446917369986}' % ( self.language.upper(), ) dscgeo = HtmlEntityHelper.url_encode(dscgeo) headers = {"Cookie": "dsc-geo=%s" % (dscgeo, )} # send the data http, nothing, host, other = self.baseUrl.split("/", 3) subdomain, domain = host.split(".", 1) url = "https://secure.%s/secure/api/v2/user/authorization/stream/%s?stream_type=hls" \ % (domain, video_id,) data = UriHandler.open(url, proxy=self.proxy, additional_headers=headers, no_cache=True) json = JsonHelper(data) url = json.get_value("hls") if url is None: return False streams_found = False if "?" in url: qs = url.split("?")[-1] else: qs = None for s, b in M3u8.get_streams_from_m3u8(url, self.proxy): # and we need to append the original QueryString if "X-I-FRAME-STREAM" in s: continue streams_found = True if qs is not None: if "?" in s: s = "%s&%s" % (s, qs) else: s = "%s?%s" % (s, qs) part.append_media_stream(s, b) return streams_found