def __encrypt(self, data, key): """ Encrypt string data (not bytes) based on the given encryption key (bytes). :param str data: The data to encrypt. :param bytes key: The key to use for encryption. :return: The encrypted base64 encoded value. :rtype: str """ Logger.debug("Encrypting with keysize: %s", len(key)) aes = pyaes.AESModeOfOperationCTR(key) if PY2: return base64.b64encode(aes.encrypt(data)) return base64.b64encode(aes.encrypt(data)).decode()
def init_channel(self): """Initializes the channel and will call some post processing stuff. This method is called for each add-on call and can be used to do some channel initialisation. """ Logger.debug("Initializing channel (init_channel): %s", self) # Make sure all images are from the correct absolute location self.noImage = TextureHandler.instance().get_texture_uri( self, self.noImage) self.poster = TextureHandler.instance().get_texture_uri( self, self.poster) return
def update_video_item_javascript(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 """ url_parts = item.url.rsplit("/", 3) if url_parts[-3] == "aflevering": video_id = url_parts[-2] else: video_id = url_parts[-1] Logger.debug("Found videoId '%s' for '%s'", video_id, item.url) url = "https://omroepzeeland.bbvms.com/p/regiogrid/q/sourceid_string:{}*.js".format( video_id) data = UriHandler.open(url, proxy=self.proxy) json_data = Regexer.do_regex(r'var opts\s*=\s*({.+});\W*//window', data) Logger.debug("Found jsondata with size: %s", len(json_data[0])) json_data = JsonHelper(json_data[0]) clip_data = json_data.get_value("clipData", "assets") server = json_data.get_value("publicationData", "defaultMediaAssetPath") part = item.create_new_empty_media_part() for clip in clip_data: part.append_media_stream("{}{}".format(server, clip["src"]), int(clip["bandwidth"])) item.complete = True return item
def __get_kodi_favourites(self, addon_id): """ Retrieves the PickleStore ID's corresponding to Kodi Favourites using the json RPC :return: A set of PickleStore ID's :rtype: set(str) """ import json import xbmc # Use a set() for performance favourite_pickle_stores = set() # Do the RPC req = { "jsonrpc": "2.0", "method": "Favourites.GetFavourites", "params": [None, ["path", "windowparameter"]], "id": 1 } rpc_result = xbmc.executeJSONRPC(json.dumps(req)) Logger.trace("PickleStore: Received favourites '%s'", rpc_result) rpc_result_data = json.loads(rpc_result) favourites = rpc_result_data.get("result", {}).get("favourites") if not favourites: return favourite_pickle_stores # Start of the add-on url addon_url = "plugin://{}".format(addon_id) for fav in favourites: fav_name = fav.get("title", "") fav_path = fav.get("path", fav.get("windowparameter")) or "" if not fav_path.startswith(addon_url): continue # Is it a favourite with a PickleStore ID? pickle_store_id = Regexer.do_regex( r"pickle=([^&]+){}[^&]+".format(Pickler.__store_separator), fav_path) if not pickle_store_id: continue Logger.debug("PickleStore: Found favourite: %s (%s)", fav_name, fav_path) favourite_pickle_stores.add(pickle_store_id[0].lower()) return favourite_pickle_stores
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) # Get the authentication part right. token = self.__authenticator.get_authentication_token() headers = { "Authorization": "Bearer {}".format(token) } video_data = UriHandler.open(item.url, additional_headers=headers) video_json = JsonHelper(video_data) license_url = video_json.get_value("licenseUrl") video_manifest = video_json.get_value("manifest") token = video_json.get_value("token") key_headers = { "Authorization": "Bearer {0}".format(token), "content-type": "application/octet-stream" } part = item.create_new_empty_media_part() stream = part.append_media_stream(video_manifest, 0) from resources.lib.streams.mpd import Mpd license_key = Mpd.get_license_key(license_url, key_headers=key_headers, key_type="A") Mpd.set_input_stream_addon_input(stream, license_key=license_key) item.complete = True 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 result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'folder'. :rtype: MediaItem|None """ # Logger.Trace(result_set) json = result_set title = json["name"] program_id = json["nid"] program_id = HtmlEntityHelper.url_encode(program_id) url = "https://api.tv4play.se/play/video_assets" \ "?platform=tablet&per_page=%s&is_live=false&type=episode&" \ "page=1&node_nids=%s&start=0" % (self.maxPageSize, program_id, ) if "channel" in json and json["channel"]: # noinspection PyTypeChecker channel_id = json["channel"]["nid"] Logger.trace("ChannelId found: %s", channel_id) else: channel_id = "tv4" Logger.warning("ChannelId NOT found. Assuming %s", channel_id) # match the exact channel or put them in TV4 is_match_for_channel = channel_id.startswith(self.__channelId) is_match_for_channel |= self.channelCode == "tv4se" and not channel_id.startswith( "sjuan") and not channel_id.startswith("tv12") if not is_match_for_channel: Logger.debug("Channel mismatch for '%s': %s vs %s", title, channel_id, self.channelCode) return None item = MediaItem(title, url) item.icon = self.icon item.thumb = result_set.get("program_image", self.noImage) item.fanart = result_set.get("program_image", self.fanart) item.isPaid = result_set.get("is_premium", False) return item
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 """ from resources.lib.streams.m3u8 import M3u8 Logger.debug('Starting update_video_item for %s (%s)', item.name, self.channelName) meta_data = UriHandler.open(item.url, referer=self.baseUrl) meta = JsonHelper(meta_data) stream_parts = meta.get_value("feed", "items") for stream_part in stream_parts: stream_url = stream_part["group"]["content"] stream_url = stream_url.replace("&device={device}", "") stream_url = "%s&format=json&acceptMethods=hls" % (stream_url, ) stream_data = UriHandler.open(stream_url) stream = JsonHelper(stream_data) # subUrls = stream.get_value("package", "video", "item", 0, "transcript", 0, "typographic") # NOSONAR part = item.create_new_empty_media_part() hls_streams = stream.get_value("package", "video", "item", 0, "rendition") for hls_stream in hls_streams: hls_url = hls_stream["src"] item.complete |= M3u8.update_part_with_m3u8_streams(part, hls_url) item.complete = True Logger.trace("Media url: %s", item) 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 result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'page'. :rtype: MediaItem|None """ if "totalPages" not in result_set: return None Logger.debug("Starting create_page_item") # current page? page_uri_part = "page%5Bnumber%5D=" if page_uri_part not in self.parentItem.url: page = 1 url_format = "{0}&page%5Bnumber%5D={{0:d}}".format(self.parentItem.url) else: base_url, page_part = self.parentItem.url.rsplit(page_uri_part, 1) next_part = page_part.find("&") if next_part < 0: # end page = int(page_part) url_format = "{0}&page%5Bnumber%5D={{0:d}}".format(base_url) else: page = int(page_part[0:next_part]) url_format = "{0}&page%5Bnumber%5D={{0:d}}&{1}".format(base_url, page_part[next_part:]) max_pages = result_set.get("totalPages", 0) Logger.trace("Current Page: %d of %d (%s)", page, max_pages, self.parentItem.url) if page + 1 > max_pages: return None title = LanguageHelper.get_localized_string(LanguageHelper.MorePages) url = url_format.format(page + 1) item = MediaItem(title, url) item.fanart = self.parentItem.fanart item.thumb = self.parentItem.thumb return item
def get_media_stream_for_bitrate(self, bitrate): """Returns the MediaStream for the requested bitrate. Arguments: bitrate : integer - The bitrate of the stream in kbps Returns: The url of the stream with the requested bitrate. If bitrate is not specified the highest bitrate stream will be used. """ # order the items by bitrate self.MediaStreams.sort(key=lambda s: s.Bitrate) best_stream = None best_distance = None if bitrate == 0: # return the highest one Logger.debug("Returning the higest bitrate stream") return self.MediaStreams[-1] for stream in self.MediaStreams: if stream.Bitrate is None: # no bitrate set, see if others are available continue # this is the bitrate-as-max-limit-method if stream.Bitrate > bitrate: # if the bitrate is higher, continue for more continue # if commented ^^ , we get the closest-match-method # determine the distance till the bitrate distance = abs(bitrate - stream.Bitrate) if best_distance is None or best_distance > distance: # this stream is better, so store it. best_distance = distance best_stream = stream if best_stream is None: # no match, take the lowest bitrate return self.MediaStreams[0] return best_stream
def __init__(self, cache_dir=None, web_time_out=30, cookie_jar=None, ignore_ssl_errors=False): """ Initialises the UriHandler class Keyword Arguments: :param str cache_dir: A path for http caching. If specified, caching will be used. :param int web_time_out: Timeout for requests in seconds :param str cookie_jar: The path to the cookie jar (in case of file storage) :param ignore_ssl_errors: Ignore any SSL certificate errors. """ self.id = int(time.time()) if cookie_jar: self.cookieJar = MozillaCookieJar(cookie_jar) if not os.path.isfile(cookie_jar): self.cookieJar.save() self.cookieJar.load() self.cookieJarFile = True else: self.cookieJar = CookieJar() self.cookieJarFile = False self.cacheDir = cache_dir self.cacheStore = None if cache_dir: self.cacheStore = StreamCache(cache_dir) Logger.debug("Opened %s", self.cacheStore) else: Logger.debug("No cache-store provided. Cached disabled.") self.userAgent = "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)" self.webTimeOut = web_time_out # max duration of request self.ignoreSslErrors = ignore_ssl_errors # ignore SSL errors if self.ignoreSslErrors: Logger.warning("Ignoring all SSL errors in Python") # status of the most recent call self.status = UriStatus(code=0, url=None, error=False, reason=None) # for download animation self.__animationIndex = -1
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) data = UriHandler.open(item.url, additional_headers=item.HttpHeaders) json = JsonHelper(data) part = item.create_new_empty_media_part() part.Subtitle = NpoStream.get_subtitle(json.get_value("mid")) for stream in json.get_value("videoStreams"): if not stream["url"].startswith("odi"): part.append_media_stream(stream["url"], stream["bitrate"] / 1000) item.complete = True if item.has_media_item_parts(): return item for s, b in NpoStream.get_streams_from_npo(None, json.get_value("mid")): item.complete = True part.append_media_stream(s, b) return item
def get_mms_from_asx(url): """Opens a URL with an ASX playlist and returns the first found stream in the ASX file. Only searches for mms://url. Arguments: url : string - the URL to an ASX playlist. Returns: The first found stream in an ASX playlist. If the <url> ends with .mms it is assumed to already be a single stream. In that case the URL is returned. Example: <asx version="3.0"> <title>Example.com Live Stream</title> <entry> <title>Short Announcement to Play Before Main Stream</title> <ref href="http://example.com/announcement.wma" /> <param name="aParameterName" value="aParameterValue" /> </entry> <entry> <title>Example radio</title> <ref href="mms://example.com:8080" /> <author>Example.com</author> <copyright>2005 Example.com</copyright> </entry> </asx> Will return: mms://example.com:8080 because it is the first MMS stream """ if url.find(".mms") > 0: Logger.info("MMS found in url: %s", url) return url Logger.debug("Parsing %s to find MMS", url) data = UriHandler.open(url) urls = Regexer.do_regex(r'[Rr]ef href\W*=\W*"mms://([^"]+)"', data) if len(urls) > 0: return "mms://%s" % (urls[0], ) else: return url
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 get_streams_from_f4m(url, proxy=None, headers=None): """ Parsers standard F4m lists and returns a list of tuples with streams and bitrates that can be used by other methods :param ProxyInfo proxy: The proxy to use for opening. :param str url: The url to download. :param dict[str,str] headers: Possible HTTP Headers. Can be used like this: part = item.create_new_empty_media_part() for s, b in F4m.get_streams_from_f4m(url, self.proxy): item.complete = True # s = self.get_verifiable_video_url(s) part.append_media_stream(s, b) """ streams = [] data = UriHandler.open(url, proxy, additional_headers=headers) Logger.trace(data) Logger.debug("Processing F4M Streams: %s", url) needle = '<media href="([^"]+)"[^>]*bitrate="([^"]+)"' needles = Regexer.do_regex(needle, data) base_url_logged = False base_url = url[:url.rindex("/")] for n in needles: # see if we need to append a server path Logger.trace(n) if "://" not in n[0]: if not base_url_logged: Logger.trace("Using base_url %s for F4M", base_url) base_url_logged = True stream = "%s/%s" % (base_url, n[0]) else: if not base_url_logged: Logger.trace("Full url found in F4M") base_url_logged = True stream = n[0] bitrate = int(n[1]) streams.append((stream, bitrate)) Logger.debug("Found %s substreams in F4M", len(streams)) return streams
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) 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], 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, 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 add_days(self, data): """ Performs pre-process actions for data processing. Accepts an data from the process_folder_list method, BEFORE the items are processed. Allows setting of parameters (like title etc) for the channel. Inside this method the <data> could be changed and additional items can be created. The return values should always be instantiated in at least ("", []). :param str data: The retrieve data that was loaded for the current item and URL. :return: A tuple of the data and a list of MediaItems that were generated. :rtype: tuple[str|JsonHelper,list[MediaItem]] """ items = [] now = datetime.datetime.now() from_date = now - datetime.timedelta(6) Logger.debug( "Showing dates starting from %02d%02d%02d to %02d%02d%02d", from_date.year, from_date.month, from_date.day, now.year, now.month, now.day) current = from_date while current <= now: url = "https://api.538.nl/api/v1/schedule/station/radio-538" \ "?since=%s-%s-%sT00%%3A00%%3A00%%2B01%%3A00" \ "&until=%s-%s-%sT23%%3A59%%3A59%%2B01%%3A00" % \ (current.year, current.month, current.day, current.year, current.month, current.day) # "&_=1483280915489%%02d%%02d%%02d" title = "Afleveringen van %02d-%02d-%02d" % ( current.year, current.month, current.day) date_item = MediaItem(title, url) date_item.icon = self.icon date_item.thumb = self.noImage date_item.complete = True items.append(date_item) current = current + datetime.timedelta(1) return data, items
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 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) # now the mediaurl is derived. First we try WMV data = UriHandler.open(item.url) urls = Regexer.do_regex( '<a href="([^"]+.(?:wmv|mp4))">(High|Medium|Mid|Low|MP4)', data) media_part = item.create_new_empty_media_part() 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 media_part.append_media_stream( HtmlEntityHelper.convert_html_entities(url[0]), bitrate) item.complete = True return item
def store_media_items(self, store_guid, parent, children): """ Store the MediaItems in the given store path :param str store_guid: The guid used for storage :param MediaItem parent: The parent item :param list[MediaItem] children: The child items :rtype: str """ if self.__pickle_store_path is None: raise ValueError("Cannot find pickle store path") if store_guid is None: raise ValueError("No parent and not channel guid specified") children = children or [] # The path is constructed like this for abcdef01-xxxx-xxxx-xxxx-xxxxxxxxxxxx: # <storepath>/ab/cd/abcdef01-xxxx-xxxx-xxxx-xxxxxxxxxxxx pickles_dir, pickles_path = self.__get_pickle_path(store_guid) Logger.debug("PickleStore: Write to '%s'", pickles_path) if not os.path.isdir(pickles_dir): os.makedirs(pickles_dir) content = { "parent": parent, "children": {item.guid: item for item in children} } if self.__compress: pickle_content = pickle.dumps(content, protocol=pickle.HIGHEST_PROTOCOL) import zlib with io.open(pickles_path, 'wb+') as fp: fp.write(zlib.compress(pickle_content, zlib.Z_BEST_COMPRESSION)) else: with io.open(pickles_path, "wb+") as fp: pickle.dump(content, fp, protocol=pickle.HIGHEST_PROTOCOL) return
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) url = item.url data = UriHandler.open(url, proxy=self.proxy) renditions_url = Regexer.do_regex( r'<media:content[^>]+url=\W([^\'"]+)\W', data)[0] renditions_url = HtmlEntityHelper.strip_amp(renditions_url) rendition_data = UriHandler.open(renditions_url, proxy=self.proxy) video_items = Regexer.do_regex( r'<rendition[^>]+bitrate="(\d+)"[^>]*>\W+<src>([^<]+)<', rendition_data) item.MediaItemParts = [] part = item.create_new_empty_media_part() for video_item in video_items: media_url = self.get_verifiable_video_url(video_item[1].replace( "rtmpe", "rtmp")) part.append_media_stream(media_url, video_item[0]) item.complete = True 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 result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'folder'. :rtype: MediaItem|None """ Logger.trace(result_set) if "/sk=" in self.parentItem.url: return None abstract_key = result_set["abstract_key"] abstract_data = self.abstracts.get(abstract_key, None) if not abstract_data: Logger.warning("Could not find abstract data for key: %s", abstract_key) return None Logger.debug("Found Abstract Data: %s", abstract_data) abstract_name = abstract_data.get("name", "") title = result_set["name"] if abstract_name: title = "%s - %s" % (abstract_name, title) description = result_set.get("synopsis", None) key_value = result_set["key"] url = "http://www.rtl.nl/system/s4m/vfd/version=1/d=pc/output=json/ak=%s/sk=%s/pg=1" % ( abstract_key, key_value) item = MediaItem(title.title(), url) item.description = description item.thumb = "%s/%s.png" % ( self.posterBase, key_value, ) item.complete = True return item
def pre_process_folder_list(self, data): """ Performs pre-process actions for data processing. Accepts an data from the process_folder_list method, BEFORE the items are processed. Allows setting of parameters (like title etc) for the channel. Inside this method the <data> could be changed and additional items can be created. The return values should always be instantiated in at least ("", []). :param str data: The retrieve data that was loaded for the current item and URL. :return: A tuple of the data and a list of MediaItems that were generated. :rtype: tuple[str|JsonHelper,list[MediaItem]] """ Logger.info("Performing Pre-Processing") items = [] if '>Populair<' in data: data = data[data.index('>Populair<'):] if '>L1-kanalen<' in data: data = data[:data.index('>L1-kanalen<')] Logger.debug("Pre-Processing finished") # add live items title = LanguageHelper.get_localized_string( LanguageHelper.LiveStreamTitleId) item = MediaItem("\a.: {} :.".format(title), "") item.type = "folder" items.append(item) live_item = MediaItem("L1VE TV".format(title), "#livetv") live_item.type = "video" live_item.isLive = True item.items.append(live_item) live_item = MediaItem("L1VE Radio".format(title), "#liveradio") live_item.type = "video" live_item.isLive = True item.items.append(live_item) return data, items
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 get_video_section(self, data): """ Finds the part in the HTML that contains the videos for a show. The return values should always be instantiated in at least ("", []). :param str data: The retrieve data that was loaded for the current item and URL. :return: A tuple of the data and a list of MediaItems that were generated. :rtype: tuple[str|JsonHelper,list[MediaItem]] """ Logger.info("Performing Pre-Processing") items = [] data = data[:data.find('<h2>Relaterade</h2>')] Logger.debug("Pre-Processing finished") return data, items
def __decrypt(self, data, key): """ Decrypts string data (not bytes) using the given encryption key (bytes). The decrypted string is returned. :param str data: The data to decrypt. :param bytes key: The key to use for encryption. :return: Decrypted value. :rtype: str """ Logger.debug("Decrypting with keysize: %s", len(key)) aes = pyaes.AESModeOfOperationCTR(key) if PY2: return aes.decrypt(base64.b64decode(data)) return aes.decrypt(base64.b64decode(data)).decode()
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 add_clips(self, data): """ Add an items that lists clips. The return values should always be instantiated in at least ("", []). :param str data: The retrieve data that was loaded for the current item and URL. :return: A tuple of the data and a list of MediaItems that were generated. :rtype: tuple[str|JsonHelper,list[MediaItem]] """ Logger.info("Adding Clips Pre-Processing") items = [] # if the main list was retrieve using json, are the current data is json, just determine # the clip URL clip_url = None if data.lstrip().startswith("{"): if self.parentItem.url.endswith("type=program"): # http://playapi.mtgx.tv/v3/videos?format=6723&order=-airdate&type=program # http://playapi.mtgx.tv/v3/videos?format=6723&order=-updated&type=clip" % (data_id,) clip_url = self.parentItem.url.replace("type=program", "type=clip") else: # now we determine the ID and load the json data data_id = Regexer.do_regex(r'data-format-id="(\d+)"', data)[-1] Logger.debug("Found FormatId = %s", data_id) program_url = \ "http://playapi.mtgx.tv/v3/videos?format=%s&order=-airdate&type=program" % (data_id,) data = UriHandler.open(program_url, proxy=self.proxy) clip_url = \ "http://playapi.mtgx.tv/v3/videos?format=%s&order=-updated&type=clip" % (data_id,) if clip_url is not None: clip_title = LanguageHelper.get_localized_string( LanguageHelper.Clips) clip_item = MediaItem("\a.: %s :." % (clip_title, ), clip_url) clip_item.thumb = self.parentItem.thumb clip_item.fanart = self.parentItem.fanart items.append(clip_item) Logger.debug("Pre-Processing finished") return data, items
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 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) data = UriHandler.open(item.url, proxy=self.proxy, additional_headers=item.HttpHeaders) url = Regexer.do_regex(self.mediaUrlRegex, data)[-1] part = MediaItemPart(item.name, url) item.MediaItemParts.append(part) Logger.info('finishing update_video_item. MediaItems are %s', item) if not item.thumb and self.noImage: # no thumb was set yet and no url Logger.debug("Setting thumb to %s", item.thumb) if not item.has_media_item_parts(): item.complete = False else: item.complete = True return item
def update_live_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 """ # http://services.vrt.be/videoplayer/r/live.json?_1466364209811= channel_data = UriHandler.open( "http://services.vrt.be/videoplayer/r/live.json", proxy=self.proxy) channel_data = JsonHelper(channel_data) url = None for channel_id in channel_data.json: if channel_id not in item.url: continue else: url = channel_data.json[channel_id].get("hls") if url is None: Logger.error("Could not find stream for live channel: %s", item.url) return item Logger.debug("Found stream url for %s: %s", item, url) part = item.create_new_empty_media_part() for s, b in M3u8.get_streams_from_m3u8(url, self.proxy): item.complete = True part.append_media_stream(s, b) return item