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 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 __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 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 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 __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
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 = "http://webapi.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.isPaid = result_set.get("is_premium", False) 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] """ items = [] needle = XbmcWrapper.show_key_board() if not needle: return [] Logger.debug("Searching for '%s'", needle) # convert to HTML needle = HtmlEntityHelper.url_encode(needle) # Search Programma's url = "https://search.rtl.nl/?typeRestriction=tvabstract&search={}&page=1&pageSize=99" search_url = url.format(needle) temp = MediaItem("Search", search_url) items += self.process_folder_list(temp) or [] # Search Afleveringen -> no dates given, so this makes little sense # url = "https://search.rtl.nl/?typeRestriction=videoobject&uitzending=true&search={}&page=1&pageSize=99" # search_url = url.format(needle) # temp = MediaItem("Search", search_url) # items += self.process_folder_list(temp) or [] return items
def create_category(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) title = HtmlEntityHelper.url_encode(result_set['title']) url = "http://m.schooltv.nl/api/v1/categorieen/%s/afleveringen.json?sort=Nieuwste&age_filter=&size=%s" % ( title, self.__PageSize) item = MediaItem(result_set['title'], url) item.thumb = result_set.get('image', self.noImage) item.description = "Totaal %(count)s videos" % result_set item.icon = self.icon return item
def set_input_stream_addon_input(strm, proxy=None, headers=None, addon="inputstream.adaptive", manifest_type=None, license_key=None, license_type=None, max_bit_rate=None, persist_storage=False, service_certificate=None, manifest_update=None): """ Parsers standard M3U8 lists and returns a list of tuples with streams and bitrates that can be used by other methods. :param strm: (MediaStream) the MediaStream to update :param proxy: (Proxy) The proxy to use for opening :param dict headers: Possible HTTP Headers :param str addon: Adaptive add-on to use :param str manifest_type: Type of manifest (hls/mpd) :param str license_key: The value of the license key request :param str license_type: The type of license key request used (see below) :param int max_bit_rate: The maximum bitrate to use (optional) :param bool persist_storage: Should we store certificates? And request server certificates? :param str service_certificate: Use the specified server certificate :param str manifest_update: How should the manifest be updated Can be used like this: part = item.create_new_empty_media_part() stream = part.append_media_stream(stream_url, 0) M3u8.set_input_stream_addon_input(stream, self.proxy, self.headers) item.complete = True if maxBitRate is not set, the bitrate will be configured via the normal generic Retrospect or channel settings. """ if manifest_type is None: raise ValueError("No manifest type set") strm.Adaptive = True # See https://github.com/peak3d/inputstream.adaptive/blob/master/inputstream.adaptive/addon.xml.in strm.add_property("inputstreamaddon", addon) strm.add_property("inputstream.adaptive.manifest_type", manifest_type) if license_key: strm.add_property("inputstream.adaptive.license_key", license_key) if license_type: strm.add_property("inputstream.adaptive.license_type", license_type) if max_bit_rate: strm.add_property("inputstream.adaptive.max_bandwidth", max_bit_rate * 1000) if persist_storage: strm.add_property("inputstream.adaptive.license_flags", "persistent_storage") if service_certificate is not None: strm.add_property("inputstream.adaptive.server_certificate", service_certificate) if manifest_update: strm.add_property("inputstream.adaptive.manifest_update_parameter", manifest_update) if headers: header = "" for k, v in headers.items(): header = "{0}&{1}={2}".format(header, k, HtmlEntityHelper.url_encode(v)) strm.add_property("inputstream.adaptive.stream_headers", header.strip("&")) return strm
def update_video_item(self, item): # NOSONAR """ 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) # we need to fetch the actual url as it might differ for single video items data, secure_url = UriHandler.header(item.url, proxy=self.proxy) # Get the MZID secure_url = secure_url.rstrip("/") secure_url = "%s.mssecurevideo.json" % (secure_url, ) data = UriHandler.open(secure_url, proxy=self.proxy, additional_headers=item.HttpHeaders) secure_data = JsonHelper(data, logger=Logger.instance()) mzid = secure_data.get_value( list(secure_data.json.keys())[0], "videoid") # region New URL retrieval with DRM protection # We need a player token token_data = UriHandler.open( "https://media-services-public.vrt.be/" "vualto-video-aggregator-web/rest/external/v1/tokens", data="", additional_headers={"Content-Type": "application/json"}) token = JsonHelper(token_data).get_value("vrtPlayerToken") asset_url = "https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/" \ "external/v1/videos/{0}?vrtPlayerToken={1}&client=vrtvideo"\ .format(HtmlEntityHelper.url_encode(mzid), HtmlEntityHelper.url_encode(token)) asset_data = UriHandler.open(asset_url, proxy=self.proxy, no_cache=True) asset_data = JsonHelper(asset_data) drm_key = asset_data.get_value("drm") drm_protected = drm_key is not None adaptive_available = AddonSettings.use_adaptive_stream_add_on( with_encryption=drm_protected) part = item.create_new_empty_media_part() srt = None # see if we prefer hls over dash hls_prio = 2 if self._get_setting("hls_over_dash", False) else 0 for target_url in asset_data.get_value("targetUrls"): video_type = target_url["type"] video_url = target_url["url"] if video_type == "hls_aes" and drm_protected and adaptive_available: # no difference in encrypted or not. Logger.debug("Found HLS AES encrypted stream and a DRM key") stream = part.append_media_stream(video_url, 0) M3u8.set_input_stream_addon_input(stream, self.proxy) elif video_type == "hls" and not drm_protected: # no difference in encrypted or not. if adaptive_available: Logger.debug( "Found standard HLS stream and without DRM protection") stream = part.append_media_stream(video_url, hls_prio) M3u8.set_input_stream_addon_input(stream, self.proxy) else: m3u8_data = UriHandler.open(video_url, self.proxy) for s, b, a in M3u8.get_streams_from_m3u8( video_url, self.proxy, play_list_data=m3u8_data, map_audio=True): item.complete = True if a: audio_part = a.rsplit("-", 1)[-1] audio_part = "-%s" % (audio_part, ) s = s.replace(".m3u8", audio_part) part.append_media_stream(s, b) srt = M3u8.get_subtitle(video_url, play_list_data=m3u8_data, proxy=self.proxy) if not srt: continue srt = srt.replace(".m3u8", ".vtt") part.Subtitle = SubtitleHelper.download_subtitle( srt, format="webvtt") elif video_type == "mpeg_dash" and adaptive_available: if not drm_protected: Logger.debug( "Found standard MPD stream and without DRM protection") stream = part.append_media_stream(video_url, 1) Mpd.set_input_stream_addon_input(stream, self.proxy) else: stream = part.append_media_stream(video_url, 1) encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'\ .format(drm_key) encryption_key = Mpd.get_license_key( key_url="https://widevine-proxy.drm.technology/proxy", key_type="D", key_value=encryption_json, key_headers={ "Content-Type": "text/plain;charset=UTF-8" }) Mpd.set_input_stream_addon_input( stream, self.proxy, license_key=encryption_key) if video_type.startswith("hls") and srt is None: srt = M3u8.get_subtitle(video_url, proxy=self.proxy) if srt: srt = srt.replace(".m3u8", ".vtt") part.Subtitle = SubtitleHelper.download_subtitle( srt, format="webvtt") item.complete = True # endregion return item
def get_kodi_play_list_data(self, bitrate, proxy=None): """ Returns the playlist items for this MediaItem :param int bitrate: The bitrate of the streams that should be in the playlist. Given in kbps. :param ProxyInfo|None proxy: The proxy to set :return: A list of ListItems that should be added to a playlist with their selected stream url :rtype: list[tuple[xbmcgui.ListItem, str]] """ Logger.info("Creating playlist items for Bitrate: %s kbps\n%s", bitrate, self) if not bool(bitrate): raise ValueError("Bitrate not specified") play_list_data = [] for part in self.MediaItemParts: if len(part.MediaStreams) == 0: Logger.warning("Ignoring empty MediaPart: %s", part) continue kodi_item = self.get_kodi_item() stream = part.get_media_stream_for_bitrate(bitrate) Logger.info("Selected Stream: %s", stream) if stream.Adaptive: Adaptive.set_max_bitrate(stream, max_bit_rate=bitrate) # Set the actual stream path kodi_item.setProperty("path", stream.Url) # properties of the Part for prop in part.Properties + stream.Properties: Logger.trace("Adding property: %s", prop) kodi_item.setProperty(prop[0], prop[1]) # TODO: Apparently if we use the InputStream Adaptive, using the setSubtitles() causes sync issues. if part.Subtitle and False: Logger.debug("Adding subtitle to ListItem: %s", part.Subtitle) kodi_item.setSubtitles([ part.Subtitle, ]) # Set any custom Header header_params = dict() # set proxy information if present self.__set_kodi_proxy_info(kodi_item, stream, stream.Url, header_params, proxy) # Now add the actual HTTP headers for k in part.HttpHeaders: header_params[k] = HtmlEntityHelper.url_encode( part.HttpHeaders[k]) stream_url = stream.Url if header_params: kodi_query_string = reduce( lambda x, y: "%s&%s=%s" % (x, y, header_params[y]), header_params.keys(), "") kodi_query_string = kodi_query_string.lstrip("&") Logger.debug("Adding Kodi Stream parameters: %s\n%s", header_params, kodi_query_string) stream_url = "%s|%s" % (stream.Url, kodi_query_string) play_list_data.append((kodi_item, stream_url)) return play_list_data
def create_video_item(self, result_set): """ Creates a MediaItem of type 'video' 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. If the item is completely processed an no further data needs to be fetched the self.complete property should be set to True. If not set to True, the self.update_video_item method is called if the item is focussed or selected for playback. :param list[str]|dict result_set: The result_set of the self.episodeItemRegex :return: A new MediaItem of type 'video' or 'audio' (despite the method's name). :rtype: MediaItem|None """ Logger.trace('starting FormatVideoItem for %s', self.channelName) # Logger.Trace(result_set) # the vmanProgramId (like 1019976) leads to http://anytime.tv4.se/webtv/metafileFlash.smil?p=1019976&bw=1000&emulate=true&sl=true program_id = result_set["id"] # Logger.Debug("ProgId = %s", programId) url = "https://playback-api.b17g.net/media/%s?service=tv4&device=browser&protocol=hls" % ( program_id, ) name = result_set["title"] item = MediaItem(name, url) item.description = result_set["description"] if item.description is None: item.description = item.name # premium_expire_date_time=2099-12-31T00:00:00+01:00 date = result_set["broadcast_date_time"] (date_part, time_part) = date.split("T") (year, month, day) = date_part.split("-") (hour, minutes, rest1, zone) = time_part.split(":") item.set_date(year, month, day, hour, minutes, 00) broadcast_date = datetime.datetime(int(year), int(month), int(day), int(hour), int(minutes)) thumb_url = result_set.get("image", result_set.get("program_image")) # some images need to come via a proxy: if thumb_url and "://img.b17g.net/" in thumb_url: item.thumb = "https://imageproxy.b17g.services/?format=jpg&shape=cut" \ "&quality=90&resize=520x293&source={}"\ .format(HtmlEntityHelper.url_encode(thumb_url)) else: item.thumb = thumb_url availability = result_set["availability"] # noinspection PyTypeChecker free_period = availability["availability_group_free"] # noinspection PyTypeChecker premium_period = availability["availability_group_premium"] now = datetime.datetime.now() if False and not premium_period == "0": # always premium free_expired = now - datetime.timedelta(days=99 * 365) elif free_period == "30+" or free_period is None: free_expired = broadcast_date + datetime.timedelta(days=99 * 365) else: free_expired = broadcast_date + datetime.timedelta( days=int(free_period)) Logger.trace( "Premium info for: %s\nPremium state: %s\nFree State: %s\nBroadcast %s vs Expired %s", name, premium_period, free_period, broadcast_date, free_expired) if now > free_expired: item.isPaid = True item.type = "video" item.complete = False item.icon = self.icon item.isGeoLocked = result_set["is_geo_restricted"] item.isDrmProtected = result_set["is_drm_protected"] item.isLive = result_set.get("is_live", False) if item.isLive: item.url = "{0}&is_live=true".format(item.url) return item
def update_video_for_mzid(self, item, mzid, live=False): # NOSONAR """ Updates a video item based on the MZID :param MediaItem item: the parent item :param str mzid: the MZID """ # region New URL retrieval with DRM protection # We need a player token token_data = UriHandler.open( "https://media-services-public.vrt.be/" "vualto-video-aggregator-web/rest/external/v1/tokens", data="", additional_headers={"Content-Type": "application/json"}) token = JsonHelper(token_data).get_value("vrtPlayerToken") asset_url = "https://media-services-public.vrt.be/vualto-video-aggregator-web/rest/" \ "external/v1/videos/{0}?vrtPlayerToken={1}&client=vrtvideo"\ .format(HtmlEntityHelper.url_encode(mzid), HtmlEntityHelper.url_encode(token)) asset_data = UriHandler.open(asset_url, proxy=self.proxy, no_cache=True) asset_data = JsonHelper(asset_data) drm_key = asset_data.get_value("drm") drm_protected = drm_key is not None adaptive_available = AddonSettings.use_adaptive_stream_add_on( with_encryption=drm_protected, channel=self) part = item.create_new_empty_media_part() srt = None # see if we prefer hls over dash hls_prio = 2 if self._get_setting("hls_over_dash", 'false') == 'true' else 0 for target_url in asset_data.get_value("targetUrls"): video_type = target_url["type"] video_url = target_url["url"] if video_type == "hls_aes" and drm_protected and adaptive_available: # no difference in encrypted or not. Logger.debug("Found HLS AES encrypted stream and a DRM key") stream = part.append_media_stream(video_url, hls_prio) M3u8.set_input_stream_addon_input(stream, self.proxy) elif video_type == "hls" and not drm_protected: # no difference in encrypted or not. if adaptive_available: Logger.debug( "Found standard HLS stream and without DRM protection") stream = part.append_media_stream(video_url, hls_prio) M3u8.set_input_stream_addon_input(stream, self.proxy) else: m3u8_data = UriHandler.open(video_url, self.proxy) for s, b, a in M3u8.get_streams_from_m3u8( video_url, self.proxy, play_list_data=m3u8_data, map_audio=True): item.complete = True if a: audio_part = a.rsplit("-", 1)[-1] audio_part = "-%s" % (audio_part, ) s = s.replace(".m3u8", audio_part) part.append_media_stream(s, b) srt = M3u8.get_subtitle(video_url, play_list_data=m3u8_data, proxy=self.proxy) if not srt or live: # If there is not SRT don't download it. If it a live stream with subs, # don't use it as it is not supported by Kodi continue srt = srt.replace(".m3u8", ".vtt") part.Subtitle = SubtitleHelper.download_subtitle( srt, format="webvtt") elif video_type == "mpeg_dash" and adaptive_available: if not drm_protected: Logger.debug( "Found standard MPD stream and without DRM protection") stream = part.append_media_stream(video_url, 1) Mpd.set_input_stream_addon_input(stream, self.proxy) else: stream = part.append_media_stream(video_url, 1) encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'\ .format(drm_key) encryption_key = Mpd.get_license_key( key_url="https://widevine-proxy.drm.technology/proxy", key_type="D", key_value=encryption_json, key_headers={ "Content-Type": "text/plain;charset=UTF-8" }) Mpd.set_input_stream_addon_input( stream, self.proxy, license_key=encryption_key) if video_type.startswith("hls") and srt is None: srt = M3u8.get_subtitle(video_url, proxy=self.proxy) if not srt or live: # If there is not SRT don't download it. If it a live stream with subs, # don't use it as it is not supported by Kodi continue srt = srt.replace(".m3u8", ".vtt") part.Subtitle = SubtitleHelper.download_subtitle( srt, format="webvtt") item.complete = True # endregion 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 """ Logger.debug('Starting update_video_item for %s (%s)', item.name, self.channelName) # 1 - get the overal config file guid_regex = 'http://[^:]+/mgid:[^"]+:([0-9a-f-]+)"' rtmp_regex = r'type="video/([^"]+)" bitrate="(\d+)">\W+<src>([^<]+)</src>' data = UriHandler.open(item.url, proxy=self.proxy) guids = Regexer.do_regex(guid_regex, data) item.MediaItemParts = [] for guid in guids: # get the info for this part Logger.debug("Processing part with GUID: %s", guid) # reset stuff part = None # http://www.southpark.nl/feeds/video-player/mediagen?uri=mgid%3Aarc%3Aepisode%3Acomedycentral.com%3Aeb2a53f7-e370-4049-a6a9-57c195367a92&suppressRegisterBeacon=true guid = HtmlEntityHelper.url_encode( "mgid:arc:episode:comedycentral.com:%s" % (guid, )) info_url = "%s/feeds/video-player/mediagen?uri=%s&suppressRegisterBeacon=true" % ( self.baseUrl, guid) # 2- Get the GUIDS for the different ACTS info_data = UriHandler.open(info_url, proxy=self.proxy) rtmp_streams = Regexer.do_regex(rtmp_regex, info_data) for rtmp_stream in rtmp_streams: # if this is the first stream for the part, create an new part if part is None: part = item.create_new_empty_media_part() part.append_media_stream( self.get_verifiable_video_url(rtmp_stream[2]), rtmp_stream[1]) item.complete = True Logger.trace("Media item updated: %s", item) return item