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.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.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 __get_api_persisted_url(self, operation, hash_value,
                                variables):  # NOSONAR
        """ Generates a GraphQL url

        :param str operation:   The operation to use
        :param str hash_value:  The hash of the Query
        :param dict variables:  Any variables to pass

        :return: A GraphQL string
        :rtype: str

        """

        extensions = {
            "persistedQuery": {
                "version": 1,
                "sha256Hash": hash_value
            }
        }
        extensions = HtmlEntityHelper.url_encode(
            JsonHelper.dump(extensions, pretty_print=False))

        variables = HtmlEntityHelper.url_encode(
            JsonHelper.dump(variables, pretty_print=False))

        url = "https://graph.kijk.nl/graphql?" \
              "operationName={}&" \
              "variables={}&" \
              "extensions={}".format(operation, variables, extensions)
        return url
Beispiel #3
0
    def create_episode_item_api(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] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'folder'.
        :rtype: MediaItem|None

        """

        if not isinstance(result_set, dict):
            json_data = result_set[1].replace("&quot;", "\"")
            result_set = JsonHelper(json_data)
            result_set = result_set.json

        brand = result_set["brand"]
        if brand != self.__channel_brand:
            return None

        title = result_set["title"]
        url = "{}{}".format(self.baseUrl, result_set["link"])
        item = MediaItem(title, url)
        item.description = result_set["description"]
        item.isGeoLocked = True

        images = result_set["images"]
        item.poster = HtmlEntityHelper.convert_html_entities(images.get("poster"))
        item.thumb = HtmlEntityHelper.convert_html_entities(images.get("teaser"))
        return item
    def __get_api_url(self, operation, hash_value, variables=None):
        """ Generates a GraphQL url

        :param str operation:   The operation to use
        :param str hash_value:  The hash of the Query
        :param dict variables:  Any variables to pass

        :return: A GraphQL string
        :rtype: str

        """

        extensions = {
            "persistedQuery": {
                "version": 1,
                "sha256Hash": hash_value
            }
        }
        extensions = HtmlEntityHelper.url_encode(
            JsonHelper.dump(extensions, pretty_print=False))

        final_vars = {"order_by": "NAME", "per_page": 1000}
        if variables:
            final_vars = variables
        final_vars = HtmlEntityHelper.url_encode(
            JsonHelper.dump(final_vars, pretty_print=False))

        url = "https://graphql.tv4play.se/graphql?" \
              "operationName={}&" \
              "variables={}&" \
              "extensions={}".format(operation, final_vars, extensions)
        return url
Beispiel #5
0
    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 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.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[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)

        genres = result_set[0]
        if self.__genre and self.__genre not in genres:
            Logger.debug("Item '%s' filtered due to genre: %s", result_set[2], genres)
            return None

        url = result_set[1]
        if "&" in url:
            url = HtmlEntityHelper.convert_html_entities(url)

        if not url.startswith("http:"):
            url = "%s%s" % (self.baseUrl, url)

        # get the ajax page for less bandwidth
        url = "%s?sida=1&amp;sort=tid_stigande&embed=true" % (url, )

        item = MediaItem(result_set[2], url)
        item.complete = True
        item.isGeoLocked = 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')
            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 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.HttpHeaders = self.httpHeaders

        Logger.debug("Created '%s' for url %s", item.name, item.url)
        return item
Beispiel #11
0
    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
Beispiel #12
0
    def __init__(self, realm, api_key):
        """ Initializes a handler for the authentication provider

        :param str api_key:     The API key to use
        :param str realm:       The realm for this handler

        """

        if not api_key:
            raise ValueError("API Key required for RTL XL via Gigya")

        super(RtlXlHandler, self).__init__(realm, device_id=None)

        self.api_key = api_key
        self.__setting_signature = "{}:signature".format(realm)

        # internal data
        self.__signature = None
        self.__user_id = None
        self.__signature_timestamp = None
        self.__common_param_dict = {
            "APIKey": self.api_key,
            "authMode": "cookie"
        }
        self.__common_params = \
            "APIKey={}&authMode=cookie".format(HtmlEntityHelper.url_encode(self.api_key))
    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 = "https://api.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
Beispiel #14
0
    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 create_api_swipefolder_type(self, result_set):
        """ Creates a new MediaItem for a folder listing

        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 dict result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'folder'.
        :rtype: MediaItem|None

        """

        title = result_set["title"]

        if title == "Sista chansen":
            title = LanguageHelper.get_localized_string(LanguageHelper.LastChance)
        elif title == "Mest sedda programmen":
            title = LanguageHelper.get_localized_string(LanguageHelper.MostViewedEpisodes)
        elif title.startswith("Popul"):
            title = LanguageHelper.get_localized_string(LanguageHelper.Popular)
        elif title.startswith("Nyheter"):
            title = LanguageHelper.get_localized_string(LanguageHelper.LatestNews)

        item = MediaItem(title, "swipe://{}".format(HtmlEntityHelper.url_encode(title)))
        for card in result_set["cards"]:
            child = self.create_api_typed_item(card)
            if not child:
                continue

            item.items.append(child)
        return item
Beispiel #16
0
    def get_kodi_item(self):
        """ Creates an Kodi ListItem object for this channel
        
        :return: a Kodi ListItem with all required properties set.
        :rtype: xbmcgui.ListItem

        """

        name = HtmlEntityHelper.convert_html_entities(self.channelName)
        description = HtmlEntityHelper.convert_html_entities(
            self.channelDescription)

        if self.uses_external_addon:
            from resources.lib.xbmcwrapper import XbmcWrapper
            name = "{} {}".format(
                name, XbmcWrapper.get_external_add_on_label(self.addonUrl))

        self.icon = self.__get_image_path(self.icon)
        item = kodifactory.list_item(name, description)
        item.setArt({'thumb': self.icon, 'icon': self.icon})

        # http://mirrors.kodi.tv/docs/python-docs/14.x-helix/xbmcgui.html#ListItem-setInfo
        item.setInfo(
            "video",
            {
                "Title": name,
                # "Count": self.sortOrderPerCountry,
                # "TrackNumber": self.sortOrder,
                "Genre": LanguageHelper.get_full_language(self.language),
                # "Tagline": description,
                "Plot": description
            })

        if self.poster is not None:
            self.poster = self.__get_image_path(self.poster)
            item.setArt({'poster': self.poster})

        if AddonSettings.hide_fanart():
            return item

        if self.fanart is not None:
            self.fanart = self.__get_image_path(self.fanart)
        else:
            self.fanart = Config.fanart
        item.setArt({'fanart': self.fanart})
        return item
    def __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
Beispiel #18
0
    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
    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)
Beispiel #20
0
    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 create_api_program_type(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,)

        item = MediaItem(title, url)
        item.description = result_set.get("description", None)

        item.thumb = result_set.get("image")
        if item.thumb is not None:
            item.thumb = "https://imageproxy.b17g.services/?format=jpg&shape=cut" \
                         "&quality=70&resize=520x293&source={}"\
                .format(HtmlEntityHelper.url_encode(item.thumb))

        item.fanart = result_set.get("image")
        if item.fanart is not None:
            item.fanart = "https://imageproxy.b17g.services/?format=jpg&shape=cut" \
                         "&quality=70&resize=1280x720&source={}" \
                .format(HtmlEntityHelper.url_encode(item.fanart))

        item.isPaid = result_set.get("is_premium", False)
        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
Beispiel #24
0
    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 create_api_program_type(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

        """

        json = result_set
        title = json["name"]

        # https://graphql.tv4play.se/graphql?operationName=cdp&variables={"nid":"100-penisar"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"255449d35b5679b2cb5a9b85e63afd532c68d50268ae2740ae82f24d83a84774"}}
        program_id = json["nid"]
        url = self.__get_api_query('{program(nid:"%s"){name,description,videoPanels{id,name,subheading,assetType}}}' % (program_id,))

        item = MediaItem(title, url)
        item.description = result_set.get("description", None)

        item.thumb = result_set.get("image")
        if item.thumb is not None:
            item.thumb = "https://imageproxy.b17g.services/?format=jpg&shape=cut" \
                         "&quality=70&resize=520x293&source={}"\
                .format(HtmlEntityHelper.url_encode(item.thumb))

        item.fanart = result_set.get("image")
        if item.fanart is not None:
            item.fanart = "https://imageproxy.b17g.services/?format=jpg&shape=cut" \
                         "&quality=70&resize=1280x720&source={}" \
                .format(HtmlEntityHelper.url_encode(item.fanart))

        item.isPaid = result_set.get("is_premium", False)

        return item
    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 = "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
Beispiel #28
0
    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
Beispiel #29
0
    def update_live_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)
        part = item.create_new_empty_media_part()

        hls_url = media_info.get_value("streamUrl")
        if hls_url is not None and "m3u8" in hls_url:
            Logger.debug("Found HLS url for %s: %s",
                         media_info.json["streamName"], hls_url)

            for s, b in M3u8.get_streams_from_m3u8(hls_url, self.proxy):
                part.append_media_stream(s, b)
                item.complete = True
        else:
            Logger.debug("No HLS url found for %s. Fetching RTMP Token.",
                         media_info.json["streamName"])
            # fetch the token:
            token_url = "%s/api/media/streaming?streamname=%s" \
                        % (self.baseUrl, media_info.json["streamName"])

            token_data = UriHandler.open(token_url,
                                         proxy=self.proxy,
                                         additional_headers=item.HttpHeaders,
                                         no_cache=True)

            token_data = JsonHelper(token_data)
            token = token_data.get_value("token")
            Logger.debug("Found token '%s' for '%s'", token,
                         media_info.json["streamName"])

            rtmp_url = "rtmp://rtmp.rtbf.be/livecast/%s?%s pageUrl=%s tcUrl=rtmp://rtmp.rtbf.be/livecast" \
                       % (media_info.json["streamName"], token, self.baseUrl)
            rtmp_url = self.get_verifiable_video_url(rtmp_url)
            part.append_media_stream(rtmp_url, 0)
            item.complete = True

        item.isGeoLocked = not media_info.get_value(
            "geoLocRestriction", fallback="world") == "world"
        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)

        # 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