コード例 #1
0
class MediaItem:
    """Main class that represent items that are retrieved in XOT. They are used
    to fill the lists and have MediaItemParts which have MediaStreams in this
    hierarchy:

    MediaItem
        +- MediaItemPart
        |    +- MediaStream
        |    +- MediaStream
        |    +- MediaStream
        +- MediaItemPart
        |    +- MediaStream
        |    +- MediaStream
        |    +- MediaStream

    """

    LabelTrackNumber = "TrackNumber"
    LabelDuration = "Duration"
    ExpiresAt = LanguageHelper.get_localized_string(LanguageHelper.ExpiresAt)

    def __dir__(self):
        """ Required in order for the Pickler().Validate to work! """
        return [
            "name", "url", "actionUrl", "MediaItemParts", "description",
            "thumb", "fanart", "icon", "__date", "__timestamp", "type",
            "dontGroup", "isLive", "isGeoLocked", "isDrmProtected", "isPaid",
            "__infoLabels", "complete", "items", "HttpHeaders", "guid",
            "guidValue"
        ]

    #noinspection PyShadowingBuiltins
    def __init__(self, title, url, type="folder"):
        """ Creates a new MediaItem.

        The `url` can contain an url to a site more info about the item can be
        retrieved, for instance for a video item to retrieve the media url, or
        in case of a folder where child items can be retrieved.

        Essential is that no encoding (like UTF8) is specified in the title of
        the item. This is all taken care of when creating Kodi items in the
        different methods.

        :param str|unicode title:   The title of the item, used for appearance in lists.
        :param str|unicode url:     Url that used for further information retrieval.
        :param str type:            Type of MediaItem (folder, video, audio). Defaults to 'folder'.

        """

        name = title.strip()

        self.name = name
        self.url = url
        self.actionUrl = None
        self.MediaItemParts = []
        self.description = ""
        self.thumb = ""  # : The local or remote image for the thumbnail of episode
        self.fanart = ""  # : The fanart url
        self.icon = ""  # : low quality icon for list

        self.__date = ""  # : value show in interface
        self.__timestamp = datetime.min  # : value for sorting, this one is set to minimum so if non is set, it's shown at the bottom
        self.__expires_datetime = None  # : datetime value of the expire time

        self.type = type  # : video, audio, folder, append, page, playlist
        self.dontGroup = False  # : if set to True this item will not be auto grouped.
        self.isLive = False  # : if set to True, the item will have a random QuerySting param
        self.isGeoLocked = False  # : if set to True, the item is GeoLocked to the channels language (o)
        self.isDrmProtected = False  # : if set to True, the item is DRM protected and cannot be played (^)
        self.isPaid = False  # : if set to True, the item is a Paid item and cannot be played (*)
        self.__infoLabels = dict()  # : Additional Kodi InfoLabels

        self.complete = False
        self.items = []
        self.HttpHeaders = dict()  # : http headers for the item data retrieval

        # Items that are not essential for pickled
        self.isCloaked = False
        self.metaData = dict(
        )  # : Additional data that is for internal / routing use only

        # GUID used for identifcation of the object. Do not set from script, MD5 needed
        # to prevent UTF8 issues
        try:
            self.guid = "%s%s" % (EncodingHelper.encode_md5(title),
                                  EncodingHelper.encode_md5(url or ""))
        except:
            Logger.error(
                "Error setting GUID for title:'%s' and url:'%s'. Falling back to UUID",
                title,
                url,
                exc_info=True)
            self.guid = self.__get_uuid()
        self.guidValue = int("0x%s" % (self.guid, ), 0)

    def append_single_stream(self, url, bitrate=0, subtitle=None):
        """ Appends a single stream to a new MediaPart of this MediaItem.

        This methods creates a new MediaPart item and adds the provided
        stream to its MediaStreams collection. The newly created MediaPart
        is then added to the MediaItem's MediaParts collection.

        :param str url:         Url of the stream.
        :param int bitrate:     Bitrate of the stream (default = 0).
        :param str subtitle:    Url of the subtitle of the mediapart.

        :return: A reference to the created MediaPart.
        :rtype: MediaItemPart

        """

        new_part = MediaItemPart(self.name, url, bitrate, subtitle)
        self.MediaItemParts.append(new_part)
        return new_part

    def create_new_empty_media_part(self):
        """ Adds an empty MediaPart to the MediaItem.

        This method is used to create an empty MediaPart that can be used to
        add new stream to. The newly created MediaPart is appended to the
        MediaItem.MediaParts list.

        :return: The new MediaPart object (as a reference) that was appended.
        :rtype: MediaItemPart

        """

        new_part = MediaItemPart(self.name)
        self.MediaItemParts.append(new_part)
        return new_part

    def has_media_item_parts(self):
        """ Return True if there are any MediaItemParts present with streams for
        this MediaItem

        :return: True if there are any MediaItemParts present with streams for
                 this MediaItem
        :rtype: bool

        """

        for part in self.MediaItemParts:
            if len(part.MediaStreams) > 0:
                return True

        return False

    def is_playable(self):
        """ Returns True if the item can be played in a Media Player.

        At this moment it returns True for:
        * type = 'video'
        * type = 'audio'

        :return: Returns true if this is a playable MediaItem
        :rtype: bool

        """

        return self.type.lower() in ('video', 'audio', 'playlist')

    def has_track(self):
        """ Does this MediaItem have a TrackNumber InfoLabel

        :return: if the track was set.
        :rtype: bool
        """

        return MediaItem.LabelTrackNumber in self.__infoLabels

    def has_date(self):
        """ Returns if a date was set

        :return: True if a date was set.
        :rtype: bool

        """

        return self.__timestamp > datetime.min

    def clear_date(self):
        """ Resets the date (used for favourites for example). """

        self.__timestamp = datetime.min
        self.__date = ""

    def has_info(self):
        """ Indicator to show that this item has additional InfoLabels

        :return: whether or not there are infolabels
        :rtype: bool

        """
        return bool(self.__infoLabels)

    def set_info_label(self, label, value):
        """ Set a Kodi InfoLabel and its value.

        See http://kodi.wiki/view/InfoLabels
        :param str label: the name of the label
        :param Any value: the value to assign

        """

        self.__infoLabels[label] = value

    def set_season_info(self, season, episode):
        """ Set season and episode information

        :param str|int season:  The Season Number
        :param str|int episode: The Episode Number

        """

        if season is None or episode is None:
            Logger.warning("Cannot set EpisodeInfo without season and episode")
            return

        self.__infoLabels["Episode"] = int(episode)
        self.__infoLabels["Season"] = int(season)
        return

    def set_expire_datetime(self,
                            timestamp,
                            year=0,
                            month=0,
                            day=0,
                            hour=0,
                            minutes=0,
                            seconds=0):
        """ Sets the datetime value until when the item can be streamed.

        :param datetime|None timestamp: A full datetime object.
        :param int|str year:            The year of the datetime.
        :param int|str month:           The month of the datetime.
        :param int|str day:             The day of the datetime.
        :param int|str|None hour:       The hour of the datetime (Optional)
        :param int|str|None minutes:    The minutes of the datetime (Optional)
        :param int|str|None seconds:    The seconds of the datetime (Optional)

        """

        if timestamp is not None:
            self.__expires_datetime = timestamp
            return

        self.__expires_datetime = datetime(int(year), int(month), int(day),
                                           int(hour), int(minutes),
                                           int(seconds))

    def set_date(self,
                 year,
                 month,
                 day,
                 hour=None,
                 minutes=None,
                 seconds=None,
                 only_if_newer=False,
                 text=None):
        """ Sets the datetime of the MediaItem.

        Sets the datetime of the MediaItem in the self.__date and the
        corresponding text representation of that datetime.

        `hour`, `minutes` and `seconds` can be optional and will be set to 0 in
        that case. They must all be set or none of them. Not just one or two of
        them.

        If `only_if_newer` is set to True, the update will only occur if the set
        datetime is newer then the currently set datetime.

        The text representation can be overwritten by setting the `text` keyword
        to a specific value. In that case the timestamp is set to the given time
        values but the text representation will be overwritten.

        If the values form an invalid datetime value, the datetime value will be
        reset to their default values.

        :param int|str year:            The year of the datetime.
        :param int|str month:           The month of the datetime.
        :param int|str day:             The day of the datetime.
        :param int|str|none hour:       The hour of the datetime (Optional)
        :param int|str|none minutes:    The minutes of the datetime (Optional)
        :param int|str|none seconds:     The seconds of the datetime (Optional)
        :param bool only_if_newer:      Update only if the new date is more recent then the
                                        currently set one
        :param str text:                If set it will overwrite the text in the date label the
                                        datetime is also set.

        :return: The datetime that was set.
        :rtype: datetime

        """

        # date_format = xbmc.getRegion('dateshort')
        # correct a small bug in Kodi
        # date_format = date_format[1:].replace("D-M-", "%D-%M")
        # dateFormatLong = xbmc.getRegion('datelong')
        # timeFormat = xbmc.getRegion('time')
        # date_time_format = "%s %s" % (date_format, timeFormat)

        try:
            date_format = "%Y-%m-%d"  # "%x"
            date_time_format = date_format + " %H:%M"

            if hour is None and minutes is None and seconds is None:
                time_stamp = datetime(int(year), int(month), int(day))
                date = time_stamp.strftime(date_format)
            else:
                time_stamp = datetime(int(year), int(month), int(day),
                                      int(hour), int(minutes), int(seconds))
                date = time_stamp.strftime(date_time_format)

            if only_if_newer and self.__timestamp > time_stamp:
                return

            self.__timestamp = time_stamp
            if text is None:
                self.__date = date
            else:
                self.__date = text

        except ValueError:
            Logger.error(
                "Error setting date: Year=%s, Month=%s, Day=%s, Hour=%s, Minutes=%s, Seconds=%s",
                year,
                month,
                day,
                hour,
                minutes,
                seconds,
                exc_info=True)
            self.__timestamp = datetime.min
            self.__date = ""

        return self.__timestamp

    def get_kodi_item(self, name=None):
        """Creates a Kodi item with the same data is the MediaItem.

        This item is used for displaying purposes only and changes to it will
        not be passed on to the MediaItem.

        :param str|unicode name:    Overwrites the name of the Kodi item.

        :return: a complete Kodi ListItem
        :rtype: xbmcgui.ListItem

        """

        # Update name and descriptions
        name_post_fix, description_pre_fix = self.__update_title_and_description_with_limitations(
        )

        name = self.__get_title(name)
        name = "%s %s" % (name, name_post_fix)
        name = self.__full_decode_text(name)

        if self.description is None:
            self.description = ''

        if description_pre_fix != "":
            description = "%s\n\n%s" % (description_pre_fix, self.description)
        else:
            description = self.description

        description = self.__full_decode_text(description)
        if description is None:
            description = ""

        # the Kodi ListItem date
        # date: string (%d.%m.%Y / 01.01.2009) - file date
        if self.__timestamp > datetime.min:
            kodi_date = self.__timestamp.strftime("%d.%m.%Y")
            kodi_year = self.__timestamp.year
        else:
            kodi_date = ""
            kodi_year = 0

        # Get all the info labels starting with the ones set and then add the specific ones
        info_labels = self.__infoLabels.copy()
        info_labels["Title"] = name
        if kodi_date:
            info_labels["Date"] = kodi_date
            info_labels["Year"] = kodi_year
            info_labels["Aired"] = kodi_date
        if self.type != "audio":
            info_labels["Plot"] = description

        # now create the Kodi item
        item = xbmcgui.ListItem(name or "<unknown>", self.__date)
        item.setLabel(name)
        item.setLabel2(self.__date)

        # set a flag to indicate it is a item that can be used with setResolveUrl.
        if self.is_playable():
            Logger.trace("Setting IsPlayable to True")
            item.setProperty("IsPlayable", "true")

        # specific items
        Logger.trace("Setting InfoLabels: %s", info_labels)
        if self.type == "audio":
            item.setInfo(type="music", infoLabels=info_labels)
        else:
            item.setInfo(type="video", infoLabels=info_labels)

        # now set all the art to prevent duplicate calls to Kodi
        if self.fanart and not AddonSettings.hide_fanart():
            item.setArt({
                'thumb': self.thumb,
                'icon': self.icon,
                'fanart': self.fanart
            })
        else:
            item.setArt({'thumb': self.thumb, 'icon': self.icon})

        # Set Artwork
        # art = dict()
        # for l in ("thumb", "poster", "banner", "fanart", "clearart", "clearlogo", "landscape"):
        #     art[l] = self.thumb
        # item.setArt(art)

        # We never set the content resolving, Retrospect does this. And if we do, then the custom
        # headers are removed from the URL when opening the resolved URL.
        try:
            item.setContentLookup(False)
        except:
            # apparently not yet supported on this Kodi version3
            pass
        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

    @property
    def uses_external_addon(self):
        return self.url is not None and self.url.startswith("plugin://")

    def __set_kodi_proxy_info(self, kodi_item, stream, stream_url, kodi_params,
                              proxy):
        """ Updates a Kodi ListItem with the correct Proxy configuration taken from the ProxyInfo
        object.

        :param xbmcgui.ListItem kodi_item:          The current Kodi ListItem.
        :param MediaStream stream:                  The current Stream object.
        :param str stream_url:                      The current Url for the Stream object (might have
                                                    been changed in the mean time by other calls)
        :param dict[str|unicode,str] kodi_params:   A dictionary of Kodi Parameters.
        :param ProxyInfo proxy:                     The ProxyInfo object

        """
        if not proxy:
            return

        if proxy.Scheme.startswith(
                "http") and not stream.Url.startswith("http"):
            Logger.debug("Not adding proxy due to scheme mismatch")
        elif proxy.Scheme == "dns":
            Logger.debug("Not adding DNS proxy for Kodi streams")
        elif not proxy.use_proxy_for_url(stream_url):
            Logger.debug("Not adding proxy due to filter mismatch")
        else:
            if AddonSettings.is_min_version(17):
                # See ffmpeg proxy in https://github.com/xbmc/xbmc/commit/60b21973060488febfdc562a415e11cb23eb9764
                kodi_item.setProperty("proxy.host", proxy.Proxy)
                kodi_item.setProperty("proxy.port", str(proxy.Port))
                kodi_item.setProperty("proxy.type", proxy.Scheme)
                if proxy.Username:
                    kodi_item.setProperty("proxy.user", proxy.Username)
                if proxy.Password:
                    kodi_item.setProperty("proxy.password", proxy.Password)
                Logger.debug("Adding (Krypton) %s", proxy)
            else:
                kodi_params["HttpProxy"] = proxy.get_proxy_address()
                Logger.debug("Adding (Pre-Krypton) %s", proxy)
        return

    def __get_uuid(self):
        """ Generates a Unique Identifier based on Time and Random Integers """

        return binascii.hexlify(os.urandom(16)).upper()

    def __full_decode_text(self, string_value):
        """ Decodes a byte encoded string with HTML content into Unicode String

        Arguments:
        stringValue : string - The byte encoded string to decode

        Returns:
        An Unicode String with all HTML entities replaced by their UTF8 characters

        The decoding is done by first decode the string to UTF8 and then replace
        the HTML entities to their UTF8 characters.

        """

        if string_value is None:
            return None

        if string_value == "":
            return ""

        # then get rid of the HTML entities
        string_value = HtmlEntityHelper.convert_html_entities(string_value)
        return string_value

    def __str__(self):
        """ String representation 

        :return: The String representation
        :rtype: str

        """

        value = self.name

        if self.is_playable():
            if len(self.MediaItemParts) > 0:
                value = "MediaItem: %s [Type=%s, Complete=%s, IsLive=%s, Date=%s, Geo/DRM=%s/%s]" % \
                        (value, self.type, self.complete, self.isLive, self.__date,
                         self.isGeoLocked, self.isDrmProtected)
                for media_part in self.MediaItemParts:
                    value = "%s\n%s" % (value, media_part)
                value = "%s" % (value, )
            else:
                value = "%s [Type=%s, Complete=%s, unknown urls, IsLive=%s, Date=%s, Geo/DRM=%s/%s]" \
                        % (value, self.type, self.complete, self.isLive, self.__date,
                           self.isGeoLocked, self.isDrmProtected)
        else:
            value = "%s [Type=%s, Url=%s, Date=%s, IsLive=%s, Geo/DRM=%s/%s]" \
                    % (value, self.type, self.url, self.__date, self.isLive, self.isGeoLocked, self.isDrmProtected)

        return value

    def __eq__(self, item):
        """ checks 2 items for Equality

        Arguments:
        item : MediaItem - The item to check for equality.

        Returns:
        the output of self.__equals(item).

        """
        return self.__equals(item)

    def __ne__(self, item):
        """ returns NOT Equal

        Arguments:
        item : MediaItem - The item to check for equality.

        Returns:
        the output of not self.__equals(item).

        """

        return not self.__equals(item)

    def __hash__(self):
        """ returns the hash value """

        return hash(self.guidValue)

    def __equals(self, other):
        """ Checks two MediaItems for equality

        :param MediaItem other: The other item.

        :return: whether the objects are equal (if the item's GUID's match).
        :rtype: bool

        """

        if not other:
            return False

        return self.guidValue == other.guidValue

    def __update_title_and_description_with_limitations(self):
        """ Updates the title/name and description with the symbols for DRM, GEO and Paid.

        :return:            (tuple) name postfix, description postfix
        :rtype: tuple[str,str]

        """

        geo_lock = "&ordm;"  # º
        drm_lock = "^"  # ^
        paid = "&ordf;"  # ª
        cloaked = "&uml;"  # ¨
        description_prefix = []
        title_postfix = []

        description = ""
        title = ""

        if self.__expires_datetime is not None:
            expires = "{}: {}".format(
                MediaItem.ExpiresAt,
                self.__expires_datetime.strftime("%Y-%m-%d %H:%M"))
            description_prefix.append(expires)

        if self.isDrmProtected:
            title_postfix.append(drm_lock)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.DrmProtected))

        if self.isGeoLocked:
            title_postfix.append(geo_lock)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.GeoLockedId))

        if self.isPaid:
            title_postfix.append(paid)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.PremiumPaid))

        if self.isCloaked:
            title_postfix.append(cloaked)
            description_prefix.append(
                LanguageHelper.get_localized_string(LanguageHelper.HiddenItem))

        if self.uses_external_addon:
            from resources.lib.xbmcwrapper import XbmcWrapper
            external = XbmcWrapper.get_external_add_on_label(self.url)
            title_postfix.append(external)

        # actually update it
        if description_prefix:
            description_prefix = "\n".join(description_prefix)
            description = "[COLOR gold][I]%s[/I][/COLOR]" % (
                description_prefix.rstrip(), )

        if title_postfix:
            title = "".join(title_postfix)
            title = "[COLOR gold]%s[/COLOR]" % (title.lstrip(), )

        return title, description

    def __get_title(self, name):
        """ Create the title based on the MediaItems name and type.

        :param str name: the name to update.

        :return: an updated name
        :rtype: str

        """

        if not name:
            name = self.name

        if self.type == 'page':
            # We need to add the Page prefix to the item
            name = "%s %s" % (LanguageHelper.get_localized_string(
                LanguageHelper.Page), name)
            Logger.debug("MediaItem.__get_title :: Adding Page Prefix")

        elif self.__date != '' and not self.is_playable(
        ) and not AddonSettings.is_min_version(18):
            # not playable items should always show date
            name = "%s [COLOR=dimgray](%s)[/COLOR]" % (name, self.__date)

        folder_prefix = AddonSettings.get_folder_prefix()
        if self.type == "folder" and not folder_prefix == "":
            name = "%s %s" % (folder_prefix, name)

        return name

    def __setstate__(self, state):
        """ Sets the current MediaItem's state based on the pickled value. However, it also adds
        newly added class variables so old items won't brake.

        @param state: a default Pickle __dict__
        """

        # creating a new MediaItem here should not cause too much performance issues, as not very many
        # will be depickled.

        m = MediaItem("", "")
        self.__dict__ = m.__dict__
        self.__dict__.update(state)
コード例 #2
0
    def get_channels(self, include_disabled=False, **kwargs):  # NOSONAR
        """ Retrieves all enabled channels within Retrospect.

        If updated channels are found, the those channels are indexed and the
        channel index is rebuild.

        :param bool include_disabled:   Boolean to indicate if we should include those channels
                                        that are explicitly disabled from the settings.
        :param dict kwargs:             Here for backward compatibility.

        :return: a list of ChannelInfo objects of enabled channels.
        :rtype: list[ChannelInfo]

        """

        sw = StopWatch("ChannelIndex.get_channels Importer", Logger.instance())
        Logger.info("Fetching all enabled channels.")

        self.__allChannels = []
        valid_channels = []

        # What platform are we
        platform = envcontroller.EnvController.get_platform()

        channels_updated = False
        country_visibility = {}

        for channel_set in self.__channelIndex[
                self.__CHANNEL_INDEX_CHANNEL_KEY]:
            channel_set = self.__channelIndex[
                self.__CHANNEL_INDEX_CHANNEL_KEY][channel_set]
            channel_set_info_path = channel_set[
                self.__CHANNEL_INDEX_CHANNEL_INFO_KEY]
            channel_set_version = channel_set[
                self.__CHANNEL_INDEX_CHANNEL_VERSION_KEY]

            # Check if file exists. If not, rebuild index
            if not os.path.isfile(
                    channel_set_info_path) and not self.__reindexed:
                Logger.warning("Missing channelSet file: %s.",
                               channel_set_info_path)
                self.__rebuild_index()
                return self.get_channels()

            channel_infos = ChannelInfo.from_json(channel_set_info_path,
                                                  channel_set_version)

            # Check if the channel was updated
            if self.__is_channel_set_updated(channel_infos[0]):
                # let's see if the index has already been updated this section, of not, do it and
                # restart the ChannelRetrieval.
                if not self.__reindexed:
                    # rebuild and restart
                    Logger.warning(
                        "Re-index channel index due to channelSet update: %s.",
                        channel_set_info_path)
                    self.__rebuild_index()
                    return self.get_channels()
                else:
                    Logger.warning("Found updated channelSet: %s.",
                                   channel_set_info_path)

                if not channels_updated:
                    # this was the first update found (otherwise channelsUpdated was True) show a message:
                    title = LanguageHelper.get_localized_string(
                        LanguageHelper.InitChannelTitle)
                    text = LanguageHelper.get_localized_string(
                        LanguageHelper.InitChannelText)
                    XbmcWrapper.show_notification(title,
                                                  text,
                                                  display_time=15000,
                                                  logger=Logger.instance())
                channels_updated |= True

                # Initialise the channelset.
                self.__initialise_channel_set(channel_infos[0])

                # And perform all first actions for the included channels in the set
                for channel_info in channel_infos:
                    self.__initialise_channel(channel_info)

            # Check the channel validity
            for channel_info in channel_infos:
                if not self.__channel_is_correct(channel_info):
                    continue
                self.__allChannels.append(channel_info)

                # valid channel for this platform ?
                if not channel_info.compatiblePlatforms & platform == platform:
                    Logger.warning(
                        "Not loading: %s -> platform '%s' is not compatible.",
                        channel_info, Environments.name(platform))
                    continue
                valid_channels.append(channel_info)

                # was the channel hidden based on language settings? We do some caching to speed
                # things up.
                if channel_info.language not in country_visibility:
                    country_visibility[
                        channel_info.
                        language] = AddonSettings.show_channel_with_language(
                            channel_info.language)
                channel_info.visible = country_visibility[
                    channel_info.language]

                # was the channel explicitly disabled from the settings?
                channel_info.enabled = AddonSettings.get_channel_visibility(
                    channel_info)

                Logger.debug("Found channel: %s", channel_info)

        if channels_updated:
            Logger.info(
                "New or updated channels found. Updating add-on configuration for all channels and user agent."
            )
            AddonSettings.update_add_on_settings_with_channels(
                valid_channels, Config)
            AddonSettings.update_user_agent()
        else:
            Logger.debug(
                "No channel changes found. Skipping add-on configuration for channels."
            )
            # TODO: perhaps we should check that the settings.xml is correct and not broken?

        valid_channels.sort(key=lambda c: c.sort_key)
        visible_channels = [
            ci for ci in valid_channels if ci.visible and ci.enabled
        ]
        Logger.info("Fetch a total of %d channels of which %d are visible.",
                    len(valid_channels), len(visible_channels))

        sw.stop()

        if include_disabled:
            return valid_channels

        return visible_channels
コード例 #3
0
    def add_live_items_and_genres(self, data):
        """ Adds the Live items, Channels and Last Episodes to the listing.

        :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 = []

        extra_items = {
            LanguageHelper.get_localized_string(LanguageHelper.LiveTv):
            "https://www.svtplay.se/kanaler",
            LanguageHelper.get_localized_string(LanguageHelper.CurrentlyPlayingEpisodes):
            self.__get_api_url(
                "GridPage",
                "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                variables={"selectionId": "live"}),
            LanguageHelper.get_localized_string(LanguageHelper.Search):
            "searchSite",
            LanguageHelper.get_localized_string(LanguageHelper.Recent):
            self.__get_api_url(
                "GridPage",
                "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                variables={"selectionId": "latest"}),
            LanguageHelper.get_localized_string(LanguageHelper.LastChance):
            self.__get_api_url(
                "GridPage",
                "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                variables={"selectionId": "lastchance"}),
            LanguageHelper.get_localized_string(LanguageHelper.MostViewedEpisodes):
            self.__get_api_url(
                "GridPage",
                "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                variables={"selectionId": "popular"}),
        }

        for title, url in extra_items.items():
            new_item = MediaItem("\a.: %s :." % (title, ), url)
            new_item.complete = True
            new_item.thumb = self.noImage
            new_item.dontGroup = True
            items.append(new_item)

        genre_tags = "\a.: {}/{} :.".format(
            LanguageHelper.get_localized_string(LanguageHelper.Genres),
            LanguageHelper.get_localized_string(LanguageHelper.Tags).lower())

        genre_url = self.__get_api_url(
            "AllGenres",
            "6bef51146d05b427fba78f326453127f7601188e46038c9a5c7b9c2649d4719c",
            {})
        genre_item = MediaItem(genre_tags, genre_url)
        genre_item.complete = True
        genre_item.thumb = self.noImage
        genre_item.dontGroup = True
        items.append(genre_item)

        category_items = {
            "Drama":
            ("drama",
             "https://www.svtstatic.se/play/play5/images/categories/posters/drama-d75cd2da2eecde36b3d60fad6b92ad42.jpg"
             ),
            "Dokumentär":
            ("dokumentar",
             "https://www.svtstatic.se/play/play5/images/categories/posters/dokumentar-00599af62aa8009dbc13577eff894b8e.jpg"
             ),
            "Humor":
            ("humor",
             "https://www.svtstatic.se/play/play5/images/categories/posters/humor-abc329317eedf789d2cca76151213188.jpg"
             ),
            "Livsstil":
            ("livsstil",
             "https://www.svtstatic.se/play/play5/images/categories/posters/livsstil-2d9cd77d86c086fb8908ce4905b488b7.jpg"
             ),
            "Underhållning":
            ("underhallning",
             "https://www.svtstatic.se/play/play5/images/categories/posters/underhallning-a60da5125e715d74500a200bd4416841.jpg"
             ),
            "Kultur":
            ("kultur",
             "https://www.svtstatic.se/play/play5/images/categories/posters/kultur-93dca50ed1d6f25d316ac1621393851a.jpg"
             ),
            "Samhälle & Fakta":
            ("samhalle-och-fakta",
             "https://www.svtstatic.se/play/play5/images/categories/posters/samhalle-och-fakta-3750657f72529a572f3698e01452f348.jpg"
             ),
            "Film":
            ("film",
             "https://www.svtstatic.se/image/medium/480/20888292/1548755428"),
            "Barn":
            ("barn",
             "https://www.svtstatic.se/play/play5/images/categories/posters/barn-c17302a6f7a9a458e0043b58bbe8ab79.jpg"
             ),
            "Nyheter":
            ("nyheter",
             "https://www.svtstatic.se/play/play6/images/categories/posters/nyheter.e67ff1b5770152af4690ad188546f9e9.jpg"
             ),
            "Sport":
            ("sport",
             "https://www.svtstatic.se/play/play6/images/categories/posters/sport.98b65f6627e4addbc4177542035ea504.jpg"
             )
        }

        category_title = "\a.: {} :.".format(
            LanguageHelper.get_localized_string(LanguageHelper.Categories))
        new_item = MediaItem(category_title, "https://www.svtplay.se/genre")
        new_item.complete = True
        new_item.thumb = self.noImage
        new_item.dontGroup = True
        for title, (category_id, thumb) in category_items.items():
            # https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=GenreProgramsAO&variables={"genre": ["action-och-aventyr"]}&extensions={"persistedQuery": {"version": 1, "sha256Hash": "189b3613ec93e869feace9a379cca47d8b68b97b3f53c04163769dcffa509318"}}
            cat_item = MediaItem(title, "#genre_item")
            cat_item.complete = True
            cat_item.thumb = thumb or self.noImage
            cat_item.dontGroup = True
            cat_item.metaData[self.__genre_id] = category_id
            new_item.items.append(cat_item)
        items.append(new_item)

        return data, items
コード例 #4
0
    def __update_title_and_description_with_limitations(self):
        """ Updates the title/name and description with the symbols for DRM, GEO and Paid.

        :return:            (tuple) name postfix, description postfix
        :rtype: tuple[str,str]

        """

        geo_lock = "&ordm;"  # º
        drm_lock = "^"  # ^
        paid = "&ordf;"  # ª
        cloaked = "&uml;"  # ¨
        description_prefix = []
        title_postfix = []

        description = ""
        title = ""

        if self.__expires_datetime is not None:
            expires = "{}: {}".format(
                MediaItem.ExpiresAt,
                self.__expires_datetime.strftime("%Y-%m-%d %H:%M"))
            description_prefix.append(("gold", expires))

        if self.isDrmProtected:
            title_postfix.append(("gold", drm_lock))
            description_prefix.append(("gold",
                                       LanguageHelper.get_localized_string(
                                           LanguageHelper.DrmProtected)))

        if self.isGeoLocked:
            title_postfix.append(("aqua", geo_lock))
            description_prefix.append(("aqua",
                                       LanguageHelper.get_localized_string(
                                           LanguageHelper.GeoLockedId)))

        if self.isPaid:
            title_postfix.append(("gold", paid))
            description_prefix.append(("gold",
                                       LanguageHelper.get_localized_string(
                                           LanguageHelper.PremiumPaid)))

        if self.isCloaked:
            title_postfix.append(("gold", cloaked))
            description_prefix.append(("gold",
                                       LanguageHelper.get_localized_string(
                                           LanguageHelper.HiddenItem)))

        if self.uses_external_addon:
            from resources.lib.xbmcwrapper import XbmcWrapper
            external = XbmcWrapper.get_external_add_on_label(self.url)
            title_postfix.append(("gold", external))

        def __color_text(texts, text_format="[COLOR {}]{}[/COLOR]"):
            """

            :param list[tuple[str, str]] texts:     The color and text (in tuple)
            :param str text_format:                 The format used for filling

            :return: A Kodi compatible color coded string.
            :rtype: str

            See https://forum.kodi.tv/showthread.php?tid=210837
            """

            return "".join([
                text_format.format(clr, text.lstrip()) for clr, text in texts
            ]).strip()

        # actually update it
        if description_prefix:
            description = __color_text(description_prefix,
                                       text_format="[COLOR {}]{}[/COLOR]\n")

        if title_postfix:
            title = __color_text(title_postfix)

        return title, description
コード例 #5
0
    def change_pin(self, application_key=None):
        """ Stores an existing ApplicationKey using a new PIN.

        :param bytes application_key: an existing ApplicationKey that will be stored. If none
                                      specified, the existing ApplicationKey of the Vault will
                                      be used.

        :return: Indication of success.
        :rtype: bool

        """

        Logger.info("Updating the ApplicationKey with a new PIN")

        if self.__newKeyGeneratedInConstructor:
            Logger.info("A key was just generated, no need to change PINs.")
            return True

        if application_key is None:
            Logger.debug("Using the ApplicationKey from the vault.")
            application_key = Vault.__Key
        else:
            Logger.debug("Using the ApplicationKey from the input parameter.")

        if not application_key:
            raise ValueError("No ApplicationKey specified.")

        # Now we get a new PIN and (re)encrypt

        pin = XbmcWrapper.show_key_board(
            heading=LanguageHelper.get_localized_string(
                LanguageHelper.VaultNewPin),
            hidden=True)
        if not pin:
            XbmcWrapper.show_notification(
                "",
                LanguageHelper.get_localized_string(LanguageHelper.VaultNoPin),
                XbmcWrapper.Error)
            return False

        pin2 = XbmcWrapper.show_key_board(
            heading=LanguageHelper.get_localized_string(
                LanguageHelper.VaultRepeatPin),
            hidden=True)
        if pin != pin2:
            Logger.critical("Mismatch in PINs")
            XbmcWrapper.show_notification(
                "",
                LanguageHelper.get_localized_string(
                    LanguageHelper.VaultPinsDontMatch), XbmcWrapper.Error)
            return False

        if PY2:
            encrypted_key = "%s=%s" % (self.__APPLICATION_KEY_SETTING,
                                       application_key)
        else:
            # make it text to store
            encrypted_key = "%s=%s" % (self.__APPLICATION_KEY_SETTING,
                                       application_key.decode())

        # let's generate a pin using the scrypt password-based key derivation
        pin_key = self.__get_pbk(pin)
        encrypted_key = self.__encrypt(encrypted_key, pin_key)
        AddonSettings.set_setting(Vault.__APPLICATION_KEY_SETTING,
                                  encrypted_key,
                                  store=LOCAL)
        Logger.info("Successfully updated the Retrospect PIN")
        return True
コード例 #6
0
    def load_programs(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 = []

        # fetch al pages
        p = 1
        url_format = "https://{0}/content/shows?" \
                     "include=images" \
                     "&page%5Bsize%5D=100&page%5Bnumber%5D={{0}}".format(self.baseUrlApi)
        # "include=images%2CprimaryChannel" \
        url = url_format.format(p)
        data = UriHandler.open(url)
        json = JsonHelper(data)
        pages = json.get_value("meta", "totalPages")
        programs = json.get_value("data") or []

        # extract the images
        self.__update_image_lookup(json)

        for p in range(2, pages + 1, 1):
            url = url_format.format(p)
            Logger.debug("Loading: %s", url)

            data = UriHandler.open(url)
            json = JsonHelper(data)
            programs += json.get_value("data") or []

            # extract the images
            self.__update_image_lookup(json)

        Logger.debug("Found a total of %s items over %s pages", len(programs),
                     pages)

        for p in programs:
            item = self.create_program_item(p)
            if item is not None:
                items.append(item)

        if self.recentUrl:
            recent_text = LanguageHelper.get_localized_string(
                LanguageHelper.Recent)
            recent = MediaItem("\b.: {} :.".format(recent_text),
                               self.recentUrl)
            recent.dontGroup = True
            items.append(recent)

        # live items
        if self.liveUrl:
            live = MediaItem("\b.: Live :.", self.liveUrl)
            live.type = "video"
            live.dontGroup = True
            live.isGeoLocked = True
            live.isLive = True
            items.append(live)

        search = MediaItem("\a.: S&ouml;k :.", "searchSite")
        search.type = "folder"
        search.dontGroup = True
        items.append(search)

        return data, items
コード例 #7
0
    def add_categories_and_specials(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 = []

        extras = {
            LanguageHelper.get_localized_string(LanguageHelper.Search):
            ("searchSite", None, False),
            LanguageHelper.get_localized_string(LanguageHelper.TvShows):
            ("https://api.tv4play.se/play/programs?is_active=true&platform=tablet"
             "&per_page=1000&fl=nid,name,program_image,is_premium,updated_at,channel&start=0",
             None, False)
        }

        # Channel 4 specific items
        if self.channelCode == "tv4se":
            extras.update({
                LanguageHelper.get_localized_string(LanguageHelper.Categories):
                ("https://api.tv4play.se/play/categories.json", None, False),
                LanguageHelper.get_localized_string(LanguageHelper.MostViewedEpisodes):
                ("https://api.tv4play.se/play/video_assets/most_viewed?type=episode"
                 "&platform=tablet&is_live=false&per_page=%s&start=0" %
                 (self.maxPageSize, ), None, False),
            })

            today = datetime.datetime.now()
            days = [
                LanguageHelper.get_localized_string(LanguageHelper.Monday),
                LanguageHelper.get_localized_string(LanguageHelper.Tuesday),
                LanguageHelper.get_localized_string(LanguageHelper.Wednesday),
                LanguageHelper.get_localized_string(LanguageHelper.Thursday),
                LanguageHelper.get_localized_string(LanguageHelper.Friday),
                LanguageHelper.get_localized_string(LanguageHelper.Saturday),
                LanguageHelper.get_localized_string(LanguageHelper.Sunday)
            ]
            for i in range(0, 7, 1):
                start_date = today - datetime.timedelta(i)
                end_date = start_date + datetime.timedelta(1)

                day = days[start_date.weekday()]
                if i == 0:
                    day = LanguageHelper.get_localized_string(
                        LanguageHelper.Today)
                elif i == 1:
                    day = LanguageHelper.get_localized_string(
                        LanguageHelper.Yesterday)

                Logger.trace("Adding item for: %s - %s", start_date, end_date)
                # Old URL:
                # url = "https://api.tv4play.se/play/video_assets?exclude_node_nids=" \
                #       "nyheterna,v%C3%A4der,ekonomi,lotto,sporten,nyheterna-blekinge,nyheterna-bor%C3%A5s," \
                #       "nyheterna-dalarna,nyheterna-g%C3%A4vle,nyheterna-g%C3%B6teborg,nyheterna-halland," \
                #       "nyheterna-helsingborg,nyheterna-j%C3%B6nk%C3%B6ping,nyheterna-kalmar,nyheterna-link%C3%B6ping," \
                #       "nyheterna-lule%C3%A5,nyheterna-malm%C3%B6,nyheterna-norrk%C3%B6ping,nyheterna-skaraborg," \
                #       "nyheterna-skellefte%C3%A5,nyheterna-stockholm,nyheterna-sundsvall,nyheterna-ume%C3%A5," \
                #       "nyheterna-uppsala,nyheterna-v%C3%A4rmland,nyheterna-v%C3%A4st,nyheterna-v%C3%A4ster%C3%A5s," \
                #       "nyheterna-v%C3%A4xj%C3%B6,nyheterna-%C3%B6rebro,nyheterna-%C3%B6stersund,tv4-tolken," \
                #       "fotbollskanalen-europa" \
                #       "&platform=tablet&per_page=32&is_live=false&product_groups=2&type=episode&per_page=100"
                url = "https://api.tv4play.se/play/video_assets?exclude_node_nids=" \
                      "&platform=tablet&per_page=32&is_live=false&product_groups=2&type=episode&per_page=100"
                url = "%s&broadcast_from=%s&broadcast_to=%s&" % (
                    url, start_date.strftime("%Y%m%d"),
                    end_date.strftime("%Y%m%d"))
                extras[day] = (url, start_date, False)

        extras[LanguageHelper.get_localized_string(
            LanguageHelper.CurrentlyPlayingEpisodes
        )] = (
            "https://api.tv4play.se/play/video_assets?exclude_node_nids=&platform=tablet&"
            "per_page=32&is_live=true&product_groups=2&type=episode&per_page=100",
            None, False)

        for name in extras:
            title = name
            url, date, is_live = extras[name]
            item = MediaItem(title, url)
            item.dontGroup = True
            item.complete = True
            item.thumb = self.noImage
            item.HttpHeaders = self.httpHeaders
            item.isLive = is_live

            if date is not None:
                item.set_date(date.year,
                              date.month,
                              date.day,
                              0,
                              0,
                              0,
                              text=date.strftime("%Y-%m-%d"))

            items.append(item)

        if not self.channelCode == "tv4se":
            return data, items

        # Add Live TV
        # live = MediaItem("\a.: Live-TV :.",
        #                            "http://tv4events1-lh.akamaihd.net/i/EXTRAEVENT5_1@324055/master.m3u8",
        #                            type="video")
        # live.dontGroup = True
        # # live.isDrmProtected = True
        # live.isGeoLocked = True
        # live.isLive = True
        # items.append(live)

        Logger.debug("Pre-Processing finished")
        return data, items
コード例 #8
0
    def __update_embedded_video(self, item):
        """ Updates video items that are encrypted. This could be the default for Krypton!

        :param MediaItem item: The item to update.

        :return: An updated item.
        :rtype: MediaItem

        """

        data = UriHandler.open(item.url, proxy=self.proxy)
        start_needle = "var playerConfig ="
        start_data = data.index(start_needle) + len(start_needle)
        end_data = data.index("var talpaPlayer")
        data = data[start_data:end_data].strip().rstrip(";")

        json = JsonHelper(data)
        has_drm_only = True
        adaptive_available = AddonSettings.use_adaptive_stream_add_on(
            with_encryption=False, channel=self)
        adaptive_available_encrypted = AddonSettings.use_adaptive_stream_add_on(
            with_encryption=True, channel=self)

        for play_list_entry in json.get_value("playlist"):
            part = item.create_new_empty_media_part()
            for source in play_list_entry["sources"]:
                stream_type = source["type"]
                stream_url = source["file"]
                stream_drm = source.get("drm")

                if not stream_drm:
                    has_drm_only = False
                    if stream_type == "m3u8":
                        Logger.debug("Found non-encrypted M3u8 stream: %s",
                                     stream_url)
                        M3u8.update_part_with_m3u8_streams(part,
                                                           stream_url,
                                                           proxy=self.proxy,
                                                           channel=self)
                        item.complete = True
                    elif stream_type == "dash" and adaptive_available:
                        Logger.debug("Found non-encrypted Dash stream: %s",
                                     stream_url)
                        stream = part.append_media_stream(stream_url, 1)
                        Mpd.set_input_stream_addon_input(stream,
                                                         proxy=self.proxy)
                        item.complete = True
                    else:
                        Logger.debug("Unknown stream source: %s", source)

                else:
                    compatible_drm = "widevine"
                    if compatible_drm not in stream_drm or stream_type != "dash":
                        Logger.debug("Found encrypted %s stream: %s",
                                     stream_type, stream_url)
                        continue

                    Logger.debug("Found Widevine encrypted Dash stream: %s",
                                 stream_url)
                    license_url = stream_drm[compatible_drm]["url"]
                    pid = stream_drm[compatible_drm]["releasePid"]
                    encryption_json = '{"getRawWidevineLicense":' \
                                      '{"releasePid":"%s", "widevineChallenge":"b{SSM}"}' \
                                      '}' % (pid,)

                    headers = {
                        "Content-Type": "application/json",
                        "Origin": "https://embed.kijk.nl",
                        "Referer": stream_url
                    }

                    encryption_key = Mpd.get_license_key(
                        license_url,
                        key_type=None,
                        key_value=encryption_json,
                        key_headers=headers)

                    stream = part.append_media_stream(stream_url, 0)
                    Mpd.set_input_stream_addon_input(
                        stream, proxy=self.proxy, license_key=encryption_key)
                    item.complete = True

            subs = [
                s['file'] for s in play_list_entry.get("tracks", [])
                if s.get('kind') == "captions"
            ]
            if subs:
                subtitle = SubtitleHelper.download_subtitle(subs[0],
                                                            format="webvtt")
                part.Subtitle = subtitle

        if has_drm_only and not adaptive_available_encrypted:
            XbmcWrapper.show_dialog(
                LanguageHelper.get_localized_string(LanguageHelper.DrmTitle),
                LanguageHelper.get_localized_string(
                    LanguageHelper.WidevineLeiaRequired))
        return item
コード例 #9
0
    def execute(self):
        Logger.info("Plugin::process_folder_list Doing process_folder_list")
        try:
            ok = True

            # read the item from the parameters
            selected_item = self.__media_item

            # determine the parent guid
            parent_guid = self.parameter_parser.get_parent_guid(
                self.__channel, selected_item)

            if self.__favorites is None:
                watcher = StopWatch("Plugin process_folder_list",
                                    Logger.instance())
                media_items = self.__channel.process_folder_list(selected_item)
                watcher.lap("Class process_folder_list finished")
            else:
                parent_guid = "{}.fav".format(parent_guid)
                watcher = StopWatch("Plugin process_folder_list With Items",
                                    Logger.instance())
                media_items = self.__favorites

            if len(media_items) == 0:
                Logger.warning("process_folder_list returned %s items",
                               len(media_items))
                ok = self.__show_empty_information(media_items,
                                                   favs=self.__favorites
                                                   is not None)
            else:
                Logger.debug("process_folder_list returned %s items",
                             len(media_items))

            kodi_items = []

            for media_item in media_items:  # type: MediaItem
                self.__update_artwork(media_item, self.__channel)

                if media_item.type == 'folder' or media_item.type == 'append' or media_item.type == "page":
                    action_value = action.LIST_FOLDER
                    folder = True
                elif media_item.is_playable():
                    action_value = action.PLAY_VIDEO
                    folder = False
                else:
                    Logger.critical(
                        "Plugin::process_folder_list: Cannot determine what to add"
                    )
                    continue

                # Get the Kodi item
                kodi_item = media_item.get_kodi_item()
                self.__set_kodi_properties(kodi_item,
                                           media_item,
                                           folder,
                                           is_favourite=self.__favorites
                                           is not None)

                # Get the context menu items
                context_menu_items = self._get_context_menu_items(
                    self.__channel, item=media_item)
                kodi_item.addContextMenuItems(context_menu_items)

                # Get the action URL
                url = media_item.actionUrl
                if url is None:
                    url = self.parameter_parser.create_action_url(
                        self.__channel,
                        action=action_value,
                        item=media_item,
                        store_id=parent_guid)

                # Add them to the list of Kodi items
                kodi_items.append((url, kodi_item, folder))

            watcher.lap("Kodi Items generated")

            # add items but if OK was False, keep it like that
            ok = ok and xbmcplugin.addDirectoryItems(self.handle, kodi_items,
                                                     len(kodi_items))
            watcher.lap("items send to Kodi")

            if ok and parent_guid is not None:
                self.parameter_parser.pickler.store_media_items(
                    parent_guid, selected_item, media_items)

            watcher.stop()

            self.__add_sort_method_to_handle(self.handle, media_items)
            self.__add_breadcrumb(self.handle, self.__channel, selected_item)

            # set the content. It needs to be "episodes" to make the MediaItem.set_season_info() work
            xbmcplugin.setContent(handle=self.handle, content="episodes")

            xbmcplugin.endOfDirectory(self.handle, ok)
        except Exception:
            Logger.error("Plugin::Error Processing FolderList", exc_info=True)
            XbmcWrapper.show_notification(
                LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                LanguageHelper.get_localized_string(LanguageHelper.ErrorList),
                XbmcWrapper.Error, 4000)
            xbmcplugin.endOfDirectory(self.handle, False)
コード例 #10
0
    def __init__(self, channel_info):
        """ Initialisation of the class.

        All class variables should be instantiated here and this method should not
        be overridden by any derived classes.

        :param ChannelInfo channel_info: The channel info object to base this channel on.

        """

        chn_class.Channel.__init__(self, channel_info)

        # ============== Actual channel setup STARTS here and should be overwritten from derived classes ===============
        self.__channelId = "tv4"
        if self.channelCode == "tv4se":
            self.noImage = "tv4image.png"
            self.__channelId = "tv4"
        elif self.channelCode == "tv7se":
            self.noImage = "tv7image.png"
            self.__channelId = "sjuan"
        elif self.channelCode == "tv12se":
            self.noImage = "tv12image.png"
            self.__channelId = "tv12"
        else:
            raise Exception("Invalid channel code")

        # setup the urls
        # self.mainListUri = "https://api.tv4play.se/play/programs?is_active=true&platform=tablet&per_page=1000" \
        #                    "&fl=nid,name,program_image&start=0"

        self.mainListUri = "#mainlisting"

        self.baseUrl = "http://www.tv4play.se"
        self.swfUrl = "http://www.tv4play.se/flash/tv4playflashlets.swf"

        self._add_data_parser(self.mainListUri,
                              preprocessor=self.add_categories_and_specials)

        self.episodeItemJson = [
            "results",
        ]
        self._add_data_parser(
            "https://api.tv4play.se/play/programs?",
            json=True,
            # No longer used:  requiresLogon=True,
            parser=self.episodeItemJson,
            creator=self.create_episode_item)

        self._add_data_parser("https://api.tv4play.se/play/categories.json",
                              json=True,
                              match_type=ParserData.MatchExact,
                              parser=[],
                              creator=self.create_category_item)
        self._add_data_parser(
            "https://api.tv4play.se/play/programs?platform=tablet&category=",
            json=True,
            parser=self.episodeItemJson,
            creator=self.create_episode_item)

        self._add_data_parser("http://tv4live-i.akamaihd.net/hls/live/",
                              updater=self.update_live_item)
        self._add_data_parser(
            "http://tv4events1-lh.akamaihd.net/i/EXTRAEVENT5_1",
            updater=self.update_live_item)

        self.videoItemJson = [
            "results",
        ]
        self._add_data_parser("*",
                              preprocessor=self.pre_process_folder_list,
                              json=True,
                              parser=self.videoItemJson,
                              creator=self.create_video_item,
                              updater=self.update_video_item)

        #===============================================================================================================
        # non standard items
        self.maxPageSize = 25  # The Android app uses a page size of 20
        self.__expires_text = LanguageHelper.get_localized_string(
            LanguageHelper.ExpiresAt)

        #===============================================================================================================
        # Test cases:
        #   Batman - WideVine
        #   Antikdeckarna - Clips

        # ====================================== Actual channel setup STOPS here =======================================
        return
コード例 #11
0
    def process_folder_list(self, favorites=None):
        """Wraps the channel.process_folder_list

        :param list[MediaItem]|None favorites:

        """

        Logger.info("Plugin::process_folder_list Doing process_folder_list")
        try:
            ok = True

            selected_item = None
            if self.keywordPickle in self.params:
                selected_item = self._pickler.de_pickle_media_item(self.params[self.keywordPickle])

            if favorites is None:
                watcher = StopWatch("Plugin process_folder_list", Logger.instance())
                media_items = self.channelObject.process_folder_list(selected_item)
                watcher.lap("Class process_folder_list finished")
            else:
                watcher = StopWatch("Plugin process_folder_list With Items", Logger.instance())
                media_items = favorites

            if len(media_items) == 0:
                Logger.warning("process_folder_list returned %s items", len(media_items))
                ok = self.__show_empty_information(media_items, favs=favorites is not None)
            else:
                Logger.debug("process_folder_list returned %s items", len(media_items))

            kodi_items = []

            for media_item in media_items:  # type: MediaItem
                self.__update_artwork(media_item, self.channelObject)

                if media_item.type == 'folder' or media_item.type == 'append' or media_item.type == "page":
                    action = self.actionListFolder
                    folder = True
                elif media_item.is_playable():
                    action = self.actionPlayVideo
                    folder = False
                else:
                    Logger.critical("Plugin::process_folder_list: Cannot determine what to add")
                    continue

                # Get the Kodi item
                kodi_item = media_item.get_kodi_item()
                self.__set_kodi_properties(kodi_item, media_item, folder,
                                           is_favourite=favorites is not None)

                # Get the context menu items
                context_menu_items = self.__get_context_menu_items(self.channelObject, item=media_item)
                kodi_item.addContextMenuItems(context_menu_items)

                # Get the action URL
                url = media_item.actionUrl
                if url is None:
                    url = self._create_action_url(self.channelObject, action=action, item=media_item)

                # Add them to the list of Kodi items
                kodi_items.append((url, kodi_item, folder))

            watcher.lap("Kodi Items generated")
            # add items but if OK was False, keep it like that
            ok = ok and xbmcplugin.addDirectoryItems(self.handle, kodi_items, len(kodi_items))
            watcher.lap("items send to Kodi")

            if selected_item is None and self.channelObject is not None:
                # mainlist item register channel.
                watcher.lap("Statistics send")

            watcher.stop()

            self.__add_sort_method_to_handle(self.handle, media_items)
            self.__add_breadcrumb(self.handle, self.channelObject, selected_item)

            # set the content. It needs to be "episodes" to make the MediaItem.set_season_info() work
            xbmcplugin.setContent(handle=self.handle, content="episodes")

            xbmcplugin.endOfDirectory(self.handle, ok)
        except Exception:
            Logger.error("Plugin::Error Processing FolderList", exc_info=True)
            XbmcWrapper.show_notification(LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                                          LanguageHelper.get_localized_string(LanguageHelper.ErrorList),
                                          XbmcWrapper.Error, 4000)
            xbmcplugin.endOfDirectory(self.handle, False)
コード例 #12
0
    def __init__(self, addon_name, params, handle=0):  # NOSONAR complexity
        """ Initialises the plugin with given arguments.

        :param str addon_name:      The add-on name.
        :param str params:          The input parameters from the query string.
        :param int handle:          The Kodi directory handle.

        """

        Logger.info("******** Starting %s add-on version %s/repo *********", Config.appName, Config.version)
        # noinspection PyTypeChecker
        self.handle = int(handle)

        super(Plugin, self).__init__(addon_name, params)
        Logger.debug("Plugin Params: %s (%s)\n"
                     "Handle:      %s\n"
                     "Name:        %s\n"
                     "Query:       %s", self.params, len(self.params),
                     self.handle, self.pluginName, params)

        # channel objects
        self.channelObject = None
        self.channelFile = ""
        self.channelCode = None

        self.methodContainer = dict()   # : storage for the inspect.getmembers(channel) method. Improves performance

        # are we in session?
        session_active = SessionHelper.is_session_active(Logger.instance())

        # fetch some environment settings
        env_ctrl = envcontroller.EnvController(Logger.instance())

        if not session_active:
            # do add-on start stuff
            Logger.info("Add-On start detected. Performing startup actions.")

            # print the folder structure
            env_ctrl.print_retrospect_settings_and_folders(Config, AddonSettings)

            # show notification
            XbmcWrapper.show_notification(None, LanguageHelper.get_localized_string(LanguageHelper.StartingAddonId) % (
                Config.appName,), fallback=False, logger=Logger)

            # check for updates. Using local import for performance
            from resources.lib.updater import Updater
            up = Updater(Config.updateUrl, Config.version,
                         UriHandler.instance(), Logger.instance(),
                         AddonSettings.get_release_track())

            if up.is_new_version_available():
                Logger.info("Found new version online: %s vs %s", up.currentVersion, up.onlineVersion)
                notification = LanguageHelper.get_localized_string(LanguageHelper.NewVersion2Id)
                notification = notification % (Config.appName, up.onlineVersion)
                XbmcWrapper.show_notification(None, lines=notification, display_time=20000)

            # check for cache folder
            env_ctrl.cache_check()

            # do some cache cleanup
            env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime)

        # create a session
        SessionHelper.create_session(Logger.instance())

        #===============================================================================
        #        Start the plugin version of progwindow
        #===============================================================================
        if len(self.params) == 0:

            # Show initial start if not in a session
            # now show the list
            if AddonSettings.show_categories():
                self.show_categories()
            else:
                self.show_channel_list()

        #===============================================================================
        #        Start the plugin verion of the episode window
        #===============================================================================
        else:
            # Determine what stage we are in. Check that there are more than 2 Parameters
            if len(self.params) > 1 and self.keywordChannel in self.params:
                # retrieve channel characteristics
                self.channelFile = os.path.splitext(self.params[self.keywordChannel])[0]
                self.channelCode = self.params[self.keywordChannelCode]
                Logger.debug("Found Channel data in URL: channel='%s', code='%s'", self.channelFile,
                             self.channelCode)

                # import the channel
                channel_register = ChannelIndex.get_register()
                channel = channel_register.get_channel(self.channelFile, self.channelCode)

                if channel is not None:
                    self.channelObject = channel
                else:
                    Logger.critical("None or more than one channels were found, unable to continue.")
                    return

                # init the channel as plugin
                self.channelObject.init_channel()
                Logger.info("Loaded: %s", self.channelObject.channelName)

            elif self.keywordCategory in self.params \
                    or self.keywordAction in self.params and (
                        self.params[self.keywordAction] == self.actionAllFavourites or
                        self.params[self.keywordAction] == self.actionRemoveFavourite):
                # no channel needed for these favourites actions.
                pass

            # ===============================================================================
            # Vault Actions
            # ===============================================================================
            elif self.keywordAction in self.params and \
                    self.params[self.keywordAction] in \
                    (
                        self.actionSetEncryptedValue,
                        self.actionSetEncryptionPin,
                        self.actionResetVault
                    ):
                try:
                    # Import vault here, as it is only used here or in a channel
                    # that supports it
                    from resources.lib.vault import Vault

                    action = self.params[self.keywordAction]
                    if action == self.actionResetVault:
                        Vault.reset()
                        return

                    v = Vault()
                    if action == self.actionSetEncryptionPin:
                        v.change_pin()
                    elif action == self.actionSetEncryptedValue:
                        v.set_setting(self.params[self.keywordSettingId],
                                      self.params.get(self.keywordSettingName, ""),
                                      self.params.get(self.keywordSettingActionId, None))
                finally:
                    if self.keywordSettingTabFocus in self.params:
                        AddonSettings.show_settings(self.params[self.keywordSettingTabFocus],
                                                    self.params.get(
                                                       self.keywordSettingSettingFocus, None))
                return

            elif self.keywordAction in self.params and \
                    self.actionPostLog in self.params[self.keywordAction]:
                self.__send_log()
                return

            elif self.keywordAction in self.params and \
                    self.actionProxy in self.params[self.keywordAction]:

                # do this here to not close the busy dialog on the SetProxy when
                # a confirm box is shown
                title = LanguageHelper.get_localized_string(LanguageHelper.ProxyChangeConfirmTitle)
                content = LanguageHelper.get_localized_string(LanguageHelper.ProxyChangeConfirm)
                if not XbmcWrapper.show_yes_no(title, content):
                    Logger.warning("Stopping proxy update due to user intervention")
                    return

                language = self.params.get(self.keywordLanguage, None)
                proxy_id = self.params.get(self.keywordProxy, None)
                local_ip = self.params.get(self.keywordLocalIP, None)
                self.__set_proxy(language, proxy_id, local_ip)
                return

            else:
                Logger.critical("Error determining Plugin action")
                return

            #===============================================================================
            # See what needs to be done.
            #===============================================================================
            if self.keywordAction not in self.params:
                Logger.critical("Action parameters missing from request. Parameters=%s", self.params)
                return

            elif self.params[self.keywordAction] == self.actionListCategory:
                self.show_channel_list(self.params[self.keywordCategory])

            elif self.params[self.keywordAction] == self.actionConfigureChannel:
                self.__configure_channel(self.channelObject)

            elif self.params[self.keywordAction] == self.actionFavourites:
                # we should show the favourites
                self.show_favourites(self.channelObject)

            elif self.params[self.keywordAction] == self.actionAllFavourites:
                self.show_favourites(None)

            elif self.params[self.keywordAction] == self.actionListFolder:
                # channelName and URL is present, Parse the folder
                self.process_folder_list()

            elif self.params[self.keywordAction] == self.actionPlayVideo:
                self.play_video_item()

            elif not self.params[self.keywordAction] == "":
                self.on_action_from_context_menu(self.params[self.keywordAction])

            else:
                Logger.warning("Number of parameters (%s) or parameter (%s) values not implemented",
                               len(self.params), self.params)

        self.__fetch_textures()
        return
コード例 #13
0
    def extract_hero_data(self, data):
        """ Extacts the Hero json data

        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[JsonHelper,list[MediaItem]]

        """

        Logger.info("Performing Pre-Processing")
        items = []

        hero_data = Regexer.do_regex(r'data-hero="([^"]+)', data)[0]
        hero_data = HtmlEntityHelper.convert_html_entities(hero_data)
        Logger.trace(hero_data)
        hero_json = JsonHelper(hero_data)
        hero_playlists = hero_json.get_value("data", "playlists")
        if not hero_playlists:
            # set an empty object
            hero_json.json = {}

        current = self.parentItem.metaData.get("current_playlist", None)
        if current == "clips":
            Logger.debug("Found 'clips' metadata, only listing clips")
            hero_json.json = {}
            return hero_json, items

        if current is None:
            # Add clips folder
            clip_title = LanguageHelper.get_localized_string(LanguageHelper.Clips)
            clips = MediaItem("\a.: %s :." % (clip_title,), self.parentItem.url)
            clips.metaData[self.__meta_playlist] = "clips"
            self.__no_clips = True
            items.append(clips)

        # See if there are seasons to show
        if len(hero_playlists) == 1:
            # first items, list all, except if there is only a single season
            Logger.debug("Only one folder playlist found. Listing that one")
            return hero_json, items

        if current is None:
            # list all folders
            for playlist in hero_playlists:
                folder = self.create_folder_item(playlist)
                items.append(folder)
            # clear the json item to prevent further listing
            hero_json.json = {}
            return hero_json, items

        # list the correct folder
        current_list = [lst for lst in hero_playlists if lst["id"] == current]
        if current_list:
            # we are listing a subfolder, put that one on index 0 and then also
            hero_playlists.insert(0, current_list[0])
            self.__no_clips = True

        Logger.debug("Pre-Processing finished")
        return hero_json, items
コード例 #14
0
    def add_categories_and_specials(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 = []

        # TV4 Group specific items
        query = 'query{programSearch(per_page:1000){__typename,programs' \
                '%s,' \
                'totalHits}}' % (self.__program_fields,)
        query = HtmlEntityHelper.url_encode(query)
        tv_shows_url = "https://graphql.tv4play.se/graphql?query={}".format(
            query)

        extras = {
            LanguageHelper.get_localized_string(LanguageHelper.Search):
            ("searchSite", None, False),
            LanguageHelper.get_localized_string(LanguageHelper.TvShows):
            (tv_shows_url, None, False),
            LanguageHelper.get_localized_string(LanguageHelper.Categories):
            ("https://graphql.tv4play.se/graphql?query=query%7Btags%7D", None,
             False),
            LanguageHelper.get_localized_string(LanguageHelper.MostViewedEpisodes):
            ("https://api.tv4play.se/play/video_assets/most_viewed?type=episode"
             "&platform=tablet&is_live=false&per_page=%s&start=0" %
             (self.__maxPageSize, ), None, False),
        }

        today = datetime.datetime.now()
        days = [
            LanguageHelper.get_localized_string(LanguageHelper.Monday),
            LanguageHelper.get_localized_string(LanguageHelper.Tuesday),
            LanguageHelper.get_localized_string(LanguageHelper.Wednesday),
            LanguageHelper.get_localized_string(LanguageHelper.Thursday),
            LanguageHelper.get_localized_string(LanguageHelper.Friday),
            LanguageHelper.get_localized_string(LanguageHelper.Saturday),
            LanguageHelper.get_localized_string(LanguageHelper.Sunday)
        ]
        for i in range(0, 7, 1):
            start_date = today - datetime.timedelta(i)
            end_date = start_date + datetime.timedelta(1)

            day = days[start_date.weekday()]
            if i == 0:
                day = LanguageHelper.get_localized_string(LanguageHelper.Today)
            elif i == 1:
                day = LanguageHelper.get_localized_string(
                    LanguageHelper.Yesterday)

            Logger.trace("Adding item for: %s - %s", start_date, end_date)
            url = "https://api.tv4play.se/play/video_assets?exclude_node_nids=" \
                  "&platform=tablet&is_live=false&product_groups=2&type=episode&per_page=100"
            url = "%s&broadcast_from=%s&broadcast_to=%s&" % (
                url, start_date.strftime("%Y%m%d"),
                end_date.strftime("%Y%m%d"))
            extras[day] = (url, start_date, False)

        extras[LanguageHelper.get_localized_string(
            LanguageHelper.CurrentlyPlayingEpisodes
        )] = (
            "https://api.tv4play.se/play/video_assets?exclude_node_nids=&platform=tablet&"
            "is_live=true&product_groups=2&type=episode&per_page=100", None,
            False)

        # Actually add the extra items
        for name in extras:
            title = name
            url, date, is_live = extras[name]
            item = MediaItem(title, url)
            item.dontGroup = True
            item.complete = True
            item.HttpHeaders = self.httpHeaders
            item.isLive = is_live

            if date is not None:
                item.set_date(date.year,
                              date.month,
                              date.day,
                              0,
                              0,
                              0,
                              text=date.strftime("%Y-%m-%d"))

            items.append(item)

        Logger.debug("Pre-Processing finished")
        return data, items
コード例 #15
0
    def show_notification(title,
                          lines,
                          notification_type=Info,
                          display_time=1500,
                          fallback=True,
                          logger=None):
        """ Shows an Kodi Notification

        :param str|int|None title:      The title to show or its language ID.
        :param str|int|list[str] lines: The content to show or its language ID.
        :param str notification_type:   The type of notification: info, error, warning.
        :param int display_time:        Time to display the notification. Defaults to 1500 ms.
        :param bool fallback:           Should we fallback on XbmcWrapper.show_dialog on error?
        :param any logger:              A possible `Logger` object.

        """

        if isinstance(title, int):
            title = LanguageHelper.get_localized_string(title)

        # check for a title
        if title:
            notification_title = "%s - %s" % (Config.appName, title)
        else:
            notification_title = Config.appName

        # check for content and merge multiple lines. This is to stay compatible
        # with the LanguageHelper.get_localized_string that returns strings as arrays
        # if they are multiple lines (this is because XbmcWrapper.show_dialog needs
        # this for multi-line dialog boxes.
        if not lines:
            notification_content = ""
        else:
            if isinstance(lines, int):
                notification_content = LanguageHelper.get_localized_string(
                    lines)
            elif isinstance(lines, (tuple, list)):
                notification_content = " ".join(lines)
            else:
                notification_content = lines

        # determine the duration
        notification_type = notification_type.lower()
        if notification_type == XbmcWrapper.Warning and display_time < 2500:
            display_time = 2500
        elif notification_type == XbmcWrapper.Info and display_time < 5000:
            display_time = 5000
        elif display_time < 1500:
            # cannot be smaller then 1500 (API limit)
            display_time = 1500

        # Get an icon
        notification_icon = Config.icon
        if os.path.exists(notification_icon):
            # change the separators
            notification_icon = notification_icon.replace("\\", "/")
        else:
            notification_icon = notification_type

        if notification_type not in AddonSettings.get_notification_level():
            return

        if logger:
            logger.debug("Showing notification: %s - %s", notification_title,
                         notification_content)

        try:
            xbmcgui.Dialog().notification(notification_title,
                                          notification_content,
                                          icon=notification_icon,
                                          time=display_time)
            return
        except:
            if fallback:
                XbmcWrapper.show_dialog(title or "", lines or "")
            # no reason to worry if this does not work on older XBMC's
            return
コード例 #16
0
    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|unicode 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 = []

        # Add a klip folder only on the first page and only if it is not already a clip page
        if "type=clip" not in self.parentItem.url \
                and "&page=1&" in self.parentItem.url \
                and "node_nids=" in self.parentItem.url:
            # get the category ID
            cat_start = self.parentItem.url.rfind("node_nids=")
            cat_id = self.parentItem.url[cat_start + 10:]
            Logger.debug("Currently doing CatId: '%s'", cat_id)

            url = "https://api.tv4play.se/play/video_assets?platform=tablet&per_page=%s&" \
                  "type=clip&page=1&node_nids=%s&start=0" % (self.__maxPageSize, cat_id,)
            clips_title = LanguageHelper.get_localized_string(
                LanguageHelper.Clips)
            clips = MediaItem(clips_title, url)
            clips.complete = True
            items.append(clips)

        # find the max number of items ("total_hits":2724)
        total_items = int(Regexer.do_regex(r'total_hits\W+(\d+)', data)[-1])
        Logger.debug("Found total of %s items. Only showing %s.", total_items,
                     self.__maxPageSize)
        if total_items > self.__maxPageSize and "&page=1&" in self.parentItem.url:
            # create a group item
            more_title = LanguageHelper.get_localized_string(
                LanguageHelper.MorePages)
            more = MediaItem(more_title, "")
            more.complete = True
            items.append(more)

            # what are the total number of pages?
            current_page = 1
            # noinspection PyTypeChecker
            total_pages = int(math.ceil(1.0 * total_items /
                                        self.__maxPageSize))

            current_url = self.parentItem.url
            needle = "&page="
            while current_page < total_pages:
                # what is the current page
                current_page += 1

                url = current_url.replace("%s1" % (needle, ),
                                          "%s%s" % (needle, current_page))
                Logger.debug("Adding next page: %s\n%s", current_page, url)
                page = MediaItem(str(current_page), url)
                page.type = "page"
                page.complete = True

                if total_pages == 2:
                    items = [page]
                    break
                else:
                    more.items.append(page)

        Logger.debug("Pre-Processing finished")
        return data, items
コード例 #17
0
    def __init__(self, channel_info):
        """ Initialisation of the class.

        All class variables should be instantiated here and this method should not
        be overridden by any derived classes.

        :param ChannelInfo channel_info: The channel info object to base this channel on.

        """

        chn_class.Channel.__init__(self, channel_info)

        # ==== Actual channel setup STARTS here and should be overwritten from derived classes ====
        self.noImage = "urplayimage.png"

        # setup the urls
        self.mainListUri = "https://urplay.se/api/bff/v1/search?product_type=series&rows=10000&start=0"
        self.baseUrl = "https://urplay.se"
        self.swfUrl = "https://urplay.se/assets/jwplayer-6.12-17973009ab259c1dea1258b04bde6e53.swf"

        # Match the "series" API -> shows TV Shows
        self._add_data_parser(self.mainListUri,
                              json=True,
                              name="Show parser with categories",
                              match_type=ParserData.MatchExact,
                              preprocessor=self.add_categories_and_search,
                              parser=["results"],
                              creator=self.create_episode_json_item)

        # Match Videos (programs)
        self._add_data_parser(
            "https://urplay.se/api/bff/v1/search?product_type=program",
            name="Most viewed",
            json=True,
            parser=["results"],
            creator=self.create_video_item_json)

        self._add_data_parser("*",
                              json=True,
                              name="Json based video parser",
                              preprocessor=self.extract_json_data,
                              parser=["currentProduct", "series", "programs"],
                              creator=self.create_video_item_json)

        self._add_data_parser("*", updater=self.update_video_item)

        # Categories
        cat_reg = r'<a[^>]+href="(?<url>/blad[^"]+/(?<slug>[^"]+))"[^>]*>(?<title>[^<]+)<'
        cat_reg = Regexer.from_expresso(cat_reg)
        self._add_data_parser("https://urplay.se/",
                              name="Category parser",
                              match_type=ParserData.MatchExact,
                              parser=cat_reg,
                              creator=self.create_category_item)

        self._add_data_parsers([
            "https://urplay.se/api/bff/v1/search?play_category",
            "https://urplay.se/api/bff/v1/search?response_type=category"
        ],
                               name="Category content",
                               json=True,
                               parser=["results"],
                               creator=self.create_json_item)

        # Searching
        self._add_data_parser("https://urplay.se/search/json",
                              json=True,
                              parser=["programs"],
                              creator=self.create_search_result_program)
        self._add_data_parser("https://urplay.se/search/json",
                              json=True,
                              parser=["series"],
                              creator=self.create_search_result_serie)

        self.mediaUrlRegex = r"urPlayer.init\(([^<]+)\);"

        #===========================================================================================
        # non standard items
        self.__videoItemFound = False
        self.__cateogory_slugs = {
            "dokumentarfilmer": "dokument%C3%A4rfilmer",
            "forelasningar": "f%C3%B6rel%C3%A4sningar",
            "kultur-och-historia": "kultur%20och%20historia",
            "reality-och-livsstil": "reality%20och%20livsstil",
            "samhalle": "samh%C3%A4lle"
        }
        self.__cateogory_urls = {
            "radio":
            "https://urplay.se/api/bff/v1/search?response_type=category"
            "&singles_and_series=true"
            "&rows=1000&start=0"
            "&type=programradio&view=title",
            "syntolkat":
            "https://urplay.se/api/bff/v1/search?response_type=category"
            "&singles_and_series=true"
            "&rows=1000&start=0"
            "&view=title"
            "&with_audio_description=true",
            "teckensprak":
            "https://urplay.se/api/bff/v1/search?response_type=category"
            "&language=sgn-SWE"
            "&rows=1000&start=0"
            "&view=title"
            "&singles_and_series=true&view=title"
        }

        self.__timezone = pytz.timezone("Europe/Amsterdam")
        self.__episode_text = LanguageHelper.get_localized_string(
            LanguageHelper.EpisodeId)
        #===========================================================================================
        # Test cases:
        #   Anaconda Auf Deutch : RTMP, Subtitles

        # ====================================== Actual channel setup STOPS here ===================
        return
コード例 #18
0
    def execute(self):
        from resources.lib import player

        Logger.debug("Playing videoitem using PlayListMethod")

        try:
            media_item = self.__media_item

            if not media_item.complete:
                media_item = self.__channel.process_video_item(media_item)

            # Any warning to show
            self.__show_warnings(media_item)

            # validated the updated media_item
            if not media_item.complete or not media_item.has_media_item_parts(
            ):
                Logger.warning(
                    "process_video_item returned an MediaItem that had MediaItem.complete = False:\n%s",
                    media_item)

            if not media_item.has_media_item_parts():
                # the update failed or no items where found. Don't play
                XbmcWrapper.show_notification(
                    LanguageHelper.get_localized_string(
                        LanguageHelper.ErrorId),
                    LanguageHelper.get_localized_string(
                        LanguageHelper.NoStreamsId), XbmcWrapper.Error)
                Logger.warning(
                    "Could not start playback due to missing streams. Item:\n%s",
                    media_item)
                xbmcplugin.endOfDirectory(self.handle, False)
                return

            kodi_items = media_item.get_kodi_play_list_data(
                AddonSettings.get_max_stream_bitrate(self.__channel))

            Logger.debug("Continuing playback in plugin.py")
            if not bool(kodi_items):
                Logger.warning("play_video_item did not return valid playdata")
                xbmcplugin.endOfDirectory(self.handle, False)
                return

            # Now we force the busy dialog to close, else the video will not play and the
            # setResolved will not work.
            LockWithDialog.close_busy_dialog()

            # Append it to the Kodi playlist in a smart way.
            start_url = self.__append_kodi_play_list(kodi_items)

            # Set the mode (if the InputStream Adaptive add-on is used, we also need to set it)
            show_subs = AddonSettings.show_subtitles()

            # TODO: Apparently if we use the InputStream Adaptive, using the setSubtitles() causes sync issues.
            available_subs = [p.Subtitle for p in media_item.MediaItemParts]

            # Get the Kodi Player instance (let Kodi decide what player, see
            # http://forum.kodi.tv/showthread.php?tid=173887&pid=1516662#pid1516662)
            kodi_player = player.Player(show_subs=show_subs,
                                        subs=available_subs)
            kodi_player.waitForPlayBack(url=start_url, time_out=10)

            xbmcplugin.endOfDirectory(self.handle, True)
        except:
            XbmcWrapper.show_notification(
                LanguageHelper.get_localized_string(LanguageHelper.ErrorId),
                LanguageHelper.get_localized_string(
                    LanguageHelper.NoPlaybackId), XbmcWrapper.Error)
            Logger.critical("Could not playback the url", exc_info=True)

            # We need to single Kodi that it failed and it should not wait longer. Either using a
            # `raise` or with `xbmcplugin.endOfDirectory`. Doing the latter for now although we are
            # not really playing.
            xbmcplugin.endOfDirectory(self.handle, False)

        return
コード例 #19
0
    def process_live_items(self, data):  # NOSONAR
        """ Performs pre-process actions that either return multiple live channels that are present
        in the live url or an actual list item if a single live stream is present.

        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 = []

        Logger.info("Adding Live Streams")

        if self.liveUrl.endswith(".m3u8"):
            # We actually have a single stream.
            title = "{} - {}".format(
                self.channelName,
                LanguageHelper.get_localized_string(
                    LanguageHelper.LiveStreamTitleId))
            live_item = MediaItem(title, self.liveUrl)
            live_item.type = 'video'
            live_item.isLive = True
            if self.channelCode == "rtvdrenthe":
                # RTV Drenthe actually has a buggy M3u8 without master index.
                live_item.append_single_stream(live_item.url, 0)
                live_item.complete = True

            items.append(live_item)
            return "", items

        # we basically will check for live channels
        json_data = JsonHelper(data, logger=Logger.instance())
        live_streams = json_data.get_value()

        Logger.trace(live_streams)
        if "videos" in live_streams:
            Logger.debug("Multiple streams found")
            live_streams = live_streams["videos"]
        elif not isinstance(live_streams, (list, tuple)):
            Logger.debug("Single streams found")
            live_streams = (live_streams, )
        else:
            Logger.debug("List of stream found")

        live_stream_value = None
        for streams in live_streams:
            Logger.debug("Adding live stream")
            title = streams.get(
                'name') or "%s - Live TV" % (self.channelName, )

            live_item = MediaItem(title, self.liveUrl)
            live_item.type = 'video'
            live_item.complete = True
            live_item.isLive = True
            part = live_item.create_new_empty_media_part()
            for stream in streams:
                Logger.trace(stream)
                bitrate = None

                # used in Omrop Fryslan
                if stream == "android" or stream == "iPhone":
                    bitrate = 250
                    url = streams[stream]["videoLink"]
                elif stream == "iPad":
                    bitrate = 1000
                    url = streams[stream]["videoLink"]

                # used in RTV Utrecht
                elif stream == "androidLink" or stream == "iphoneLink":
                    bitrate = 250
                    url = streams[stream]
                elif stream == "ipadLink":
                    bitrate = 1000
                    url = streams[stream]
                elif stream == "tabletLink":
                    bitrate = 300
                    url = streams[stream]

                # These windows stream won't work
                # elif stream == "windowsLink":
                #     bitrate = 1200
                #     url = streams[stream]
                # elif stream == "wpLink":
                #     bitrate = 1200
                #     url = streams[stream]

                elif stream == "name":
                    Logger.trace("Ignoring stream '%s'", stream)
                else:
                    Logger.warning("No url found for type '%s'", stream)

                # noinspection PyUnboundLocalVariable
                if "livestreams.omroep.nl/live/" in url and url.endswith(
                        "m3u8"):
                    Logger.info("Found NPO Stream, adding ?protection=url")
                    url = "%s?protection=url" % (url, )

                if bitrate:
                    part.append_media_stream(url, bitrate)

                    if url == live_stream_value and ".m3u8" in url:
                        # if it was equal to the previous one, assume we have a m3u8. Reset the others.
                        Logger.info(
                            "Found same M3u8 stream for all streams for this Live channel, using that one: %s",
                            url)
                        live_item.MediaItemParts = []
                        live_item.url = url
                        live_item.complete = False
                        break
                    elif "playlist.m3u8" in url:
                        # if we have a playlist, use that one. Reset the others.
                        Logger.info(
                            "Found M3u8 playlist for this Live channel, using that one: %s",
                            url)
                        live_item.MediaItemParts = []
                        live_item.url = url
                        live_item.complete = False
                        break
                    else:
                        # add it to the possibilities
                        live_stream_value = url
            items.append(live_item)
        return "", items
コード例 #20
0
    def execute(self):
        if self.category:
            Logger.info("Plugin::show_channel_list for %s", self.category)
        else:
            Logger.info("Plugin::show_channel_list")
        try:
            # only display channels
            channel_register = ChannelIndex.get_register()
            channels = channel_register.get_channels()

            xbmc_items = []

            # Should we show the "All Favourites"?
            if AddonSettings.show_show_favourites_in_channel_list():
                icon = Config.icon
                fanart = Config.fanart
                name = LanguageHelper.get_localized_string(
                    LanguageHelper.AllFavouritesId)
                kodi_item = xbmcgui.ListItem(name, name)

                # set art
                try:
                    kodi_item.setIconImage(icon)
                except:
                    # it was deprecated
                    pass
                kodi_item.setArt({'thumb': icon, 'icon': icon})
                kodi_item.setProperty(self._propertyRetrospect, "true")
                kodi_item.setProperty(self._propertyRetrospectCategory, "true")

                if not AddonSettings.hide_fanart():
                    kodi_item.setArt({'fanart': fanart})

                url = self.parameter_parser.create_action_url(
                    None, action=action.ALL_FAVOURITES)
                xbmc_items.append((url, kodi_item, True))

            for channel in channels:
                if self.category and channel.category != self.category:
                    Logger.debug("Skipping %s (%s) due to category filter",
                                 channel.channelName, channel.category)
                    continue

                # Get the Kodi item
                item = channel.get_kodi_item()
                item.setProperty(self._propertyRetrospect, "true")
                item.setProperty(self._propertyRetrospectChannel, "true")
                if channel.settings:
                    item.setProperty(self._propertyRetrospectChannelSetting,
                                     "true")
                if channel.adaptiveAddonSelectable:
                    item.setProperty(self._propertyRetrospectAdaptive, "true")

                # Get the context menu items
                context_menu_items = self._get_context_menu_items(channel)
                item.addContextMenuItems(context_menu_items)
                # Get the URL for the item
                url = self.parameter_parser.create_action_url(
                    channel, action=action.LIST_FOLDER)

                # Append to the list of Kodi Items
                xbmc_items.append((url, item, True))

            # Add the items
            ok = xbmcplugin.addDirectoryItems(self.handle, xbmc_items,
                                              len(xbmc_items))

            # Just let Kodi display the order we give.
            xbmcplugin.addSortMethod(
                handle=self.handle, sortMethod=xbmcplugin.SORT_METHOD_UNSORTED)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_TITLE)
            xbmcplugin.addSortMethod(handle=self.handle,
                                     sortMethod=xbmcplugin.SORT_METHOD_GENRE)
            xbmcplugin.setContent(handle=self.handle, content="tvshows")
            xbmcplugin.endOfDirectory(self.handle, ok)
        except:
            xbmcplugin.endOfDirectory(self.handle, False)
            Logger.critical("Error fetching channels for plugin",
                            exc_info=True)
コード例 #21
0
    def _get_context_menu_items(self, channel, item=None):
        """ Retrieves the custom context menu items to display.

        :param Channel|None channel:    The channel from which to get the context menu items.
                                         The channel might be None in case of some actions that
                                         do not require a channel.
        :param MediaItem|None item:     The item to which the context menu belongs.

        :return: A list of context menu names and their commands.
        :rtype: list[tuple[str,str]]

        """

        context_menu_items = []

        # Generic, none-Python menu items that would normally cause an unwanted reload of the
        # Python interpreter instance within Kodi.
        refresh = LanguageHelper.get_localized_string(LanguageHelper.RefreshListId)
        context_menu_items.append((refresh, 'Container.Refresh()'))

        if item is None:
            return context_menu_items

        # if it was a favourites list, don't add the channel methods as they might be from a different channel
        if channel is None:
            return context_menu_items

        if item.has_info():
            info_action = LanguageHelper.get_localized_string(LanguageHelper.ItemInfo)
            context_menu_items.append((info_action, 'Action(info)'))

        # now we process the other items
        possible_methods = self.__get_members(channel)
        # Logger.Debug(possible_methods)

        for menu_item in channel.contextMenuItems:
            # Logger.Debug(menu_item)
            if menu_item.itemTypes is None or item.type in menu_item.itemTypes:
                # We don't care for complete here!
                # if menu_item.completeStatus == None or menu_item.completeStatus == item.complete:

                # see if the method is available
                method_available = False

                for method in possible_methods:
                    if method == menu_item.functionName:
                        method_available = True
                        # break from the method loop
                        break

                if not method_available:
                    Logger.warning("No method for: %s", menu_item)
                    continue

                cmd_url = self.parameter_parser.create_action_url(
                    channel, action=menu_item.functionName, item=item)

                cmd = "RunPlugin(%s)" % (cmd_url,)
                title = "Retro: %s" % (menu_item.label,)
                Logger.trace("Adding command: %s | %s", title, cmd)
                context_menu_items.append((title, cmd))

        return context_menu_items
コード例 #22
0
    def __init__(self, channel_info):
        """ Initialisation of the class.

        All class variables should be instantiated here and this method should not
        be overridden by any derived classes.

        :param ChannelInfo channel_info: The channel info object to base this channel on.

        """

        chn_class.Channel.__init__(self, channel_info)

        # ============== Actual channel setup STARTS here and should be overwritten from derived classes ===============
        # The following data was taken from http://playapi.mtgx.tv/v3/channels
        self.channelId = None
        if self.channelCode == "se3":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "tv3seimage.png"
            self.channelId = (
                1209,  # TV4
                6000,  # MTV
                6001,  # Comedy Central
                7000,  # Online Only ???
            )

        elif self.channelCode == "se6":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "tv6seimage.png"
            self.channelId = (959, )

        elif self.channelCode == "se8":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "tv8seimage.png"
            self.channelId = (801, )

        elif self.channelCode == "se10":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "tv10seimage.png"
            self.channelId = (5462, )

        elif self.channelCode == "ngse":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "ngnoimage.jpg"
            self.channelId = (7300, )

        elif self.channelCode == "mtvse":
            self.mainListUri = "https://www.viafree.se/program"
            self.noImage = "mtvimage.png"
            self.channelId = (6000, )

        elif self.channelCode == "viafreese":
            self.mainListUri = "https://www.viafree.se/program/"
            self.noImage = "viafreeimage.png"
            self.channelId = None

        elif self.channelCode == "sesport":
            raise NotImplementedError('ViaSat sport is not in this channel anymore.')

        # Danish channels
        elif self.channelCode == "tv3dk":
            self.mainListUri = "http://www.viafree.dk/programmer"
            self.noImage = "tv3noimage.png"
            # self.channelId = (3687, 6200, 6201) -> show all for now

        # Norwegian Channels
        elif self.channelCode == "no3":
            self.mainListUri = "https://www.viafree.no/programmer"
            self.noImage = "tv3noimage.png"
            self.channelId = (1550, 6100, 6101)

        elif self.channelCode == "no4":
            self.mainListUri = "https://www.viafree.no/programmer"
            self.noImage = "viasat4noimage.png"
            self.channelId = (935,)

        elif self.channelCode == "no6":
            self.mainListUri = "https://www.viafree.no/programmer"
            self.noImage = "viasat4noimage.png"
            self.channelId = (1337,)

        self.baseUrl = self.mainListUri.rsplit("/", 1)[0]
        self.searchInfo = {
            "se": ["sok", "S&ouml;k"],
            "ee": ["otsing", "Otsi"],
            "dk": ["sog", "S&oslash;g"],
            "no": ["sok", "S&oslash;k"],
            "lt": ["paieska", "Paie&scaron;ka"],
            "lv": ["meklet", "Mekl&#275;t"]
        }

        # setup the urls
        self.swfUrl = "http://flvplayer.viastream.viasat.tv/flvplayer/play/swf/MTGXPlayer-1.8.swf"

        # New JSON page data
        self._add_data_parser(self.mainListUri, preprocessor=self.extract_json_data,
                              match_type=ParserData.MatchExact)
        self._add_data_parser(self.mainListUri, preprocessor=self.extract_categories_and_add_search,
                              json=True, match_type=ParserData.MatchExact,
                              parser=["page", "blocks", 0, "_embedded", "programs"],
                              creator=self.create_json_episode_item)

        # This is the new way, but more complex and some channels have items with missing
        # category slugs and is not compatible with the old method channels.
        self.useNewPages = False
        if self.useNewPages:
            self._add_data_parser("*", preprocessor=self.extract_json_data)
            self._add_data_parser("*", json=True, preprocessor=self.merge_season_data,
                                  # parser=["context", "dispatcher", "stores", "ContentPageProgramStore", "format", "videos", "0", "program"),
                                  # creator=self.create_json_video_item
                                  )

            self._add_data_parser("http://playapi.mtgx.tv/", updater=self.update_video_item)
        else:
            self._add_data_parser("*", parser=['_embedded', 'videos'], json=True, preprocessor=self.add_clips,
                                  creator=self.create_video_item, updater=self.update_video_item)
            self.pageNavigationJson = ["_links", "next"]
            self.pageNavigationJsonIndex = 0
            self._add_data_parser("*", json=True,
                                  parser=self.pageNavigationJson, creator=self.create_page_item)

        self._add_data_parser("https://playapi.mtgx.tv/v3/search?term=", json=True,
                              parser=["_embedded", "formats"], creator=self.create_json_search_item)

        self._add_data_parser("/api/playClient;isColumn=true;query=", json=True,
                              match_type=ParserData.MatchContains,
                              parser=["data", "formats"], creator=self.create_json_episode_item)
        self._add_data_parser("/api/playClient;isColumn=true;query=", json=True,
                              match_type=ParserData.MatchContains,
                              parser=["data", "clips"], creator=self.create_json_video_item)
        self._add_data_parser("/api/playClient;isColumn=true;query=", json=True,
                              match_type=ParserData.MatchContains,
                              parser=["data", "episodes"], creator=self.create_json_video_item)
        # ===============================================================================================================
        # non standard items
        self.episodeLabel = LanguageHelper.get_localized_string(LanguageHelper.EpisodeId)
        self.seasonLabel = LanguageHelper.get_localized_string(LanguageHelper.SeasonId)
        self.__categories = {}

        # ===============================================================================================================
        # Test Cases
        #  No GEO Lock: Extra Extra
        #  GEO Lock:
        #  Multi Bitrate: Glamourama

        # ====================================== Actual channel setup STOPS here =======================================
        return
コード例 #23
0
    def add_live_items_and_genres(self, data):
        """ Adds the Live items, Channels and Last Episodes to the listing.

        :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 = []

        # Specify the name, url and whether or not to filter out some subheadings:
        extra_items = {
            LanguageHelper.get_localized_string(LanguageHelper.LiveTv): (
                self.__get_api_url("ChannelsQuery", "65ceeccf67cc8334bc14eb495eb921cffebf34300562900076958856e1a58d37", {}),
                False),

            LanguageHelper.get_localized_string(LanguageHelper.CurrentlyPlayingEpisodes): (
                self.__get_api_url("GridPage",
                                   "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                                   variables={"selectionId": "live"}),
                True),

            LanguageHelper.get_localized_string(LanguageHelper.Search): (
                "searchSite", False),

            LanguageHelper.get_localized_string(LanguageHelper.Recent): (
                self.__get_api_url("GridPage",
                                   "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                                   variables={"selectionId": "latest"}),
                False),

            LanguageHelper.get_localized_string(LanguageHelper.LastChance): (
                self.__get_api_url("GridPage",
                                   "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                                   variables={"selectionId": "lastchance"}),
                False),

            LanguageHelper.get_localized_string(LanguageHelper.MostViewedEpisodes): (
                self.__get_api_url("GridPage",
                                   "265677a2465d93d39b536545cdc3664d97e3843ce5e34f145b2a45813b85007b",
                                   variables={"selectionId": "popular"}),
                False)
        }

        for title, (url, include_subheading) in extra_items.items():
            new_item = MediaItem("\a.: %s :." % (title, ), url)
            new_item.complete = True
            new_item.dontGroup = True
            new_item.metaData[self.__filter_subheading] = include_subheading
            items.append(new_item)

        genre_tags = "\a.: {}/{} :.".format(
            LanguageHelper.get_localized_string(LanguageHelper.Genres),
            LanguageHelper.get_localized_string(LanguageHelper.Tags).lower()
        )

        genre_url = self.__get_api_url("AllGenres", "6bef51146d05b427fba78f326453127f7601188e46038c9a5c7b9c2649d4719c", {})
        genre_item = MediaItem(genre_tags, genre_url)
        genre_item.complete = True
        genre_item.dontGroup = True
        items.append(genre_item)

        category_items = {
            "Drama": (
                "drama",
                "https://www.svtstatic.se/image/medium/480/7166155/1458037803"
            ),
            "Dokumentär": (
                "dokumentar",
                "https://www.svtstatic.se/image/medium/480/7166209/1458037873"
            ),
            "Humor": (
                "humor",
                "https://www.svtstatic.se/image/medium/480/7166065/1458037609"
            ),
            "Livsstil": (
                "livsstil",
                "https://www.svtstatic.se/image/medium/480/7166101/1458037687"
            ),
            "Underhållning": (
                "underhallning",
                "https://www.svtstatic.se/image/medium/480/7166041/1458037574"
            ),
            "Kultur": (
                "kultur",
                "https://www.svtstatic.se/image/medium/480/7166119/1458037729"
            ),
            "Samhälle & Fakta": (
                "samhalle-och-fakta",
                "https://www.svtstatic.se/image/medium/480/7166173/1458037837"
            ),
            "Filmer": (
                "filmer",
                "https://www.svtstatic.se/image/medium/480/20888292/1548755428"
            ),
            "Barn": (
                "barn",
                "https://www.svtstatic.se/image/medium/480/22702778/1560934663"
            ),
            "Nyheter": (
                "nyheter",
                "https://www.svtstatic.se/image/medium/480/7166089/1458037651"
            ),
            "Sport": (
                "sport",
                "https://www.svtstatic.se/image/medium/480/7166143/1458037766"
            ),
            "Serier": (
                "serier",
                "https://www.svtstatic.se/image/medium/480/20888260/1548755402"
            ),
            "Reality": (
                "reality",
                "https://www.svtstatic.se/image/medium/480/21866138/1555059667"
            ),
            "Ung": (
                "ung-i-play",
                "https://www.svtstatic.se/image/medium/480/20888300/1548755484"
            ),
            "Musik": (
                "musik",
                "https://www.svtstatic.se/image/medium/480/19417384/1537791920"
            )
        }

        category_title = "\a.: {} :.".format(
            LanguageHelper.get_localized_string(LanguageHelper.Categories))
        new_item = MediaItem(category_title, "https://www.svtplay.se/genre")
        new_item.complete = True
        new_item.dontGroup = True
        for title, (category_id, thumb) in category_items.items():
            # https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=GenreProgramsAO&variables={"genre": ["action-och-aventyr"]}&extensions={"persistedQuery": {"version": 1, "sha256Hash": "189b3613ec93e869feace9a379cca47d8b68b97b3f53c04163769dcffa509318"}}
            cat_item = MediaItem(title, "#genre_item")
            cat_item.complete = True
            cat_item.thumb = thumb or self.noImage
            cat_item.fanart = thumb or self.fanart
            cat_item.dontGroup = True
            cat_item.metaData[self.__genre_id] = category_id
            new_item.items.append(cat_item)
        items.append(new_item)

        progs = MediaItem(
            LanguageHelper.get_localized_string(LanguageHelper.TvShows),
            self.__program_url)
        items.append(progs)

        if self.__show_program_folder:
            clips = MediaItem(
                "\a.: {} :.".format(LanguageHelper.get_localized_string(LanguageHelper.SingleEpisodes)),
                self.__program_url
            )
            items.append(clips)

            # split the item types
            clips.metaData["list_type"] = "videos"
            progs.metaData["list_type"] = "folders"

        # Clean up the titles
        for item in items:
            item.name = item.name.strip("\a.: ")

        return data, items
コード例 #24
0
    def __init__(self, channel_info):
        """ Initialisation of the class.

        All class variables should be instantiated here and this method should not
        be overridden by any derived classes.

        :param ChannelInfo channel_info: The channel info object to base this channel on.

        """

        chn_class.Channel.__init__(self, channel_info)

        # ============== Actual channel setup STARTS here and should be overwritten from derived classes ===============
        self.noImage = "svtimage.png"

        # Setup the urls

        self.mainListUri = self.__get_api_url(
            "ProgramsListing",
            "1eeb0fb08078393c17658c1a22e7eea3fbaa34bd2667cec91bbc4db8d778580f",
            {})

        self.baseUrl = "https://www.svtplay.se"
        self.swfUrl = "https://media.svt.se/swf/video/svtplayer-2016.01.swf"

        # In case we use the All Titles and Singles with the API
        self._add_data_parser(
            "https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=ProgramsListing",
            match_type=ParserData.MatchStart,
            json=True,
            preprocessor=self.add_live_items_and_genres,
            parser=["data", "programAtillO", "flat"],
            creator=self.create_api_typed_item)

        # This one contructs an API url using the metaData['slug'] and then retrieves either the all
        # folders, just the videos in a folder if that is the only folder, or it retrieves the
        # content of a folder with a given metaData['folder_id'].
        self._add_data_parser("#program_item",
                              json=True,
                              name="Data retriever for API folder.",
                              preprocessor=self.fetch_program_api_data)
        self._add_data_parser("#program_item",
                              json=True,
                              name="Folder parser for show listing via API",
                              parser=["folders"],
                              creator=self.create_api_typed_item)

        self._add_data_parser("#program_item",
                              json=True,
                              name="video parser for show listing via API",
                              parser=["videos"],
                              creator=self.create_api_typed_item)

        self._add_data_parser(
            "https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=GridPage",
            name="Default GraphQL GridePage parsers",
            json=True,
            parser=["data", "startForSvtPlay", "selections", 0, "items"],
            creator=self.create_api_typed_item)

        self._add_data_parser(
            "https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=AllGenres",
            json=True,
            name="Genre GraphQL",
            parser=["data", "genresSortedByName", "genres"],
            creator=self.create_api_typed_item)
        self._add_data_parser("#genre_item",
                              json=True,
                              name="Genre data retriever for GraphQL",
                              preprocessor=self.fetch_genre_api_data)
        self._add_data_parser("#genre_item",
                              json=True,
                              name="Genre episode parser for GraphQL",
                              parser=["programs"],
                              creator=self.create_api_typed_item)
        self._add_data_parser("#genre_item",
                              json=True,
                              name="Genre clip parser for GraphQL",
                              parser=["videos"],
                              creator=self.create_api_typed_item)

        # Setup channel listing based on JSON data in the HTML
        self._add_data_parser("https://www.svtplay.se/kanaler",
                              match_type=ParserData.MatchExact,
                              name="Live streams",
                              json=True,
                              preprocessor=self.extract_live_channel_data,
                              parser=[],
                              creator=self.create_channel_item)

        # Searching
        self._add_data_parser(
            "https://api.svt.se/contento/graphql?ua=svtplaywebb-play-render-prod-client&operationName=SearchPage",
            json=True,
            parser=["data", "search"],
            creator=self.create_api_typed_item)

        # Generic updating of videos
        self._add_data_parser("https://api.svt.se/videoplayer-api/video/",
                              updater=self.update_video_api_item)
        # Update via HTML pages
        self._add_data_parser("https://www.svtplay.se/video/",
                              updater=self.update_video_html_item)
        self._add_data_parser("https://www.svtplay.se/klipp/",
                              updater=self.update_video_html_item)

        # Update via the new API urls
        self._add_data_parser("https://www.svt.se/videoplayer-api/",
                              updater=self.update_video_api_item)

        # ===============================================================================================================
        # non standard items
        self.__folder_id = "folder_id"
        self.__genre_id = "genre_id"
        self.__parent_images = "parent_thumb_data"
        self.__apollo_data = None
        self.__expires_text = LanguageHelper.get_localized_string(
            LanguageHelper.ExpiresAt)
        self.__timezone = pytz.timezone("Europe/Stockholm")

        # ===============================================================================================================
        # Test cases:
        #   Affaren Ramel: just 1 folder -> should only list videos

        # ====================================== Actual channel setup STOPS here =======================================
        return
コード例 #25
0
    def __init__(self, addon_name, params, handle=0):
        """ Initialises the plugin with given arguments.

        :param str addon_name:      The add-on name.
        :param str params:          The input parameters from the query string.
        :param int|str handle:      The Kodi directory handle.

        """

        Logger.info("******** Starting %s add-on version %s/repo *********",
                    Config.appName, Config.version)
        # noinspection PyTypeChecker

        super(Plugin, self).__init__(addon_name, handle, params)
        Logger.debug(self)

        # are we in session?
        session_active = SessionHelper.is_session_active(Logger.instance())

        # fetch some environment settings
        env_ctrl = envcontroller.EnvController(Logger.instance())

        if not session_active:
            # do add-on start stuff
            Logger.info("Add-On start detected. Performing startup actions.")

            # print the folder structure
            env_ctrl.print_retrospect_settings_and_folders(
                Config, AddonSettings)

            # show notification
            XbmcWrapper.show_notification(None,
                                          LanguageHelper.get_localized_string(
                                              LanguageHelper.StartingAddonId) %
                                          (Config.appName, ),
                                          fallback=False,
                                          logger=Logger)

            # check for updates. Using local import for performance
            from resources.lib.updater import Updater
            up = Updater(Config.updateUrl, Config.version,
                         UriHandler.instance(), Logger.instance(),
                         AddonSettings.get_release_track())

            if up.is_new_version_available():
                Logger.info("Found new version online: %s vs %s",
                            up.currentVersion, up.onlineVersion)
                notification = LanguageHelper.get_localized_string(
                    LanguageHelper.NewVersion2Id)
                notification = notification % (Config.appName,
                                               up.onlineVersion)
                XbmcWrapper.show_notification(None,
                                              lines=notification,
                                              display_time=20000)

            # check for cache folder
            env_ctrl.cache_check()

            # do some cache cleanup
            env_ctrl.cache_clean_up(Config.cacheDir, Config.cacheValidTime)

            # empty picklestore
            self.pickler.purge_store(Config.addonId)

        # create a session
        SessionHelper.create_session(Logger.instance())
コード例 #26
0
    def __init__(self, channel_info):
        """ Initialisation of the class.

        All class variables should be instantiated here and this method should not
        be overridden by any derived classes.

        :param ChannelInfo channel_info: The channel info object to base this channel on.

        """

        chn_class.Channel.__init__(self, channel_info)

        # ==== Actual channel setup STARTS here and should be overwritten from derived classes ====
        self.noImage = "urplayimage.png"

        # setup the urls
        self.mainListUri = "#mainlist_merge"
        self.baseUrl = "https://urplay.se"
        self.swfUrl = "https://urplay.se/assets/jwplayer-6.12-17973009ab259c1dea1258b04bde6e53.swf"

        # Match the "series" API -> shows TV Shows
        self._add_data_parser(
            self.mainListUri,
            json=True,
            name="Show parser with categories",
            match_type=ParserData.MatchExact,
            preprocessor=self.merge_add_categories_and_search,
            parser=["results"],
            creator=self.create_episode_json_item)

        # Match Videos (programs)
        self._add_data_parser(
            "https://urplay.se/api/bff/v1/search?product_type=program",
            name="Most viewed",
            json=True,
            parser=["results"],
            creator=self.create_video_item_json_with_show_title)

        self._add_data_parser("*",
                              json=True,
                              name="Json based video parser",
                              parser=["accessibleEpisodes"],
                              creator=self.create_video_item_json)

        self._add_data_parser("*", updater=self.update_video_item)

        # Categories
        cat_reg = r'<a[^>]+href="(?<url>/blad[^"]+/(?<slug>[^"]+))"[^>]*>' \
                  r'(?:<svg[\w\W]{0,2000}?</svg>)?(?<title>[^<]+)<'
        cat_reg = Regexer.from_expresso(cat_reg)
        self._add_data_parser("https://urplay.se/",
                              name="Category parser",
                              match_type=ParserData.MatchExact,
                              parser=cat_reg,
                              creator=self.create_category_item)

        self._add_data_parsers([
            "https://urplay.se/api/bff/v1/search?play_category",
            "https://urplay.se/api/bff/v1/search?main_genre",
            "https://urplay.se/api/bff/v1/search?response_type=category",
            "https://urplay.se/api/bff/v1/search?type=programradio",
            "https://urplay.se/api/bff/v1/search?age=",
            "https://urplay.se/api/bff/v1/search?response_type=limited"
        ],
                               name="Category content",
                               json=True,
                               preprocessor=self.merge_category_items,
                               parser=["results"],
                               creator=self.create_json_item)

        # Searching
        self._add_data_parser("https://urplay.se/search/json",
                              json=True,
                              parser=["programs"],
                              creator=self.create_search_result_program)
        self._add_data_parser("https://urplay.se/search/json",
                              json=True,
                              parser=["series"],
                              creator=self.create_search_result_serie)

        self.mediaUrlRegex = r"urPlayer.init\(([^<]+)\);"

        #===========================================================================================
        # non standard items
        self.__videoItemFound = False

        # There is either a slug lookup or an url lookup
        self.__cateogory_slugs = {}

        self.__cateogory_urls = {
            "alla-program":
            "https://urplay.se/api/bff/v1/search?"
            "response_type=limited&"
            "product_type=series&"
            "rows={}&start={}&view=title",
            "barn":
            "https://urplay.se/api/bff/v1/search?"
            "age=children&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}"
            "&view=title",
            "dokumentarfilmer":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre[]=dokument%C3%A4rfilm&main_genre[]=dokument%C3%A4rserie&"
            # "platform=urplay&"
            "singles_and_series=true&view=title&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}"
            "&view=title",
            "drama":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre[]=drama&main_genre[]=kortfilm&main_genre[]=fiktiva%20ber%C3%A4ttelser&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "forelasningar":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre[]=f%C3%B6rel%C3%A4sning&main_genre[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "halsa-och-relationer":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&"
            "main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=kropp%20%26%20sinne&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "kultur-och-historia":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=kultur%20%26%20historia&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "natur-och-resor":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=natur%20%26%20resor&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "radio":
            "https://urplay.se/api/bff/v1/search?"
            "type=programradio&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "samhalle":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=samh%C3%A4lle&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "sprak":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=spr%C3%A5k&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "syntolkat":
            "https://urplay.se/api/bff/v1/search?"
            "response_type=category&"
            "is_audio_described=true&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "teckensprak":
            "https://urplay.se/api/bff/v1/search?"
            "response_type=category&"
            "language=sgn-SWE&"
            "platform=urplay&"
            "rows={}&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "utbildning-och-media":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&"
            "main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=utbildning%20%26%20media&"
            "singles_and_series=true&"
            "start={}&"
            "view=title",
            "vetenskap":
            "https://urplay.se/api/bff/v1/search?"
            "main_genre_must_not[]=forelasning&main_genre_must_not[]=panelsamtal&"
            "platform=urplay&"
            "rows={}&"
            "sab_category=vetenskap%20%26%20teknik&"
            "singles_and_series=true&"
            "start={}&"
            "view=title"
        }

        self.__timezone = pytz.timezone("Europe/Amsterdam")
        self.__episode_text = LanguageHelper.get_localized_string(
            LanguageHelper.EpisodeId)
        #===========================================================================================
        # Test cases:
        #   Anaconda Auf Deutch : RTMP, Subtitles

        # ====================================== Actual channel setup STOPS here ===================
        return
コード例 #27
0
    def process_folder_list(self, item=None):  # NOSONAR
        """ Process the selected item and get's it's child items using the available dataparsers.

        Accepts an <item> and returns a list of MediaListems with at least name & url
        set. The following actions are done:

        * determining the correct parsers to use
        * call a pre-processor
        * parsing the data with the parsers
        * calling the creators for item creations

        if the item is None, we assume that we are dealing with the first call for this channel and the mainlist uri
        is used.

        :param MediaItem|None item: The parent item.

        :return: A list of MediaItems that form the childeren of the <item>.
        :rtype: list[MediaItem]

        """

        items = []
        self.parentItem = item

        if item is None:
            Logger.info("process_folder_list :: No item was specified. Assuming it was the main channel list")
            url = self.mainListUri
        elif len(item.items) > 0:
            return item.items
        else:
            url = item.url

        # Determine the handlers and process
        data_parsers = self.__get_data_parsers(url)
        # Exclude the updaters only
        data_parsers = [p for p in data_parsers if not p.is_video_updater_only()]

        if [p for p in data_parsers if p.LogOnRequired]:
            Logger.info("One or more dataparsers require logging in.")
            self.loggedOn = self.log_on()

        # now set the headers here and not earlier in case they might have been update by the logon
        if item is not None and item.HttpHeaders:
            headers = item.HttpHeaders
        else:
            headers = self.httpHeaders

        # Let's retrieve the required data. Main url's
        if url.startswith("http:") or url.startswith("https:") or url.startswith("file:"):
            # Disable cache on live folders
            no_cache = item is not None and not item.is_playable() and item.isLive
            if no_cache:
                Logger.debug("Disabling cache for '%s'", item)
            data = UriHandler.open(url, proxy=self.proxy, additional_headers=headers, no_cache=no_cache)
        # Searching a site using search_site()
        elif url == "searchSite" or url == "#searchSite":
            Logger.debug("Starting to search")
            return self.search_site()
        # Labels instead of url's
        elif url.startswith("#"):
            data = ""
        # Others
        else:
            Logger.debug("Unknown URL format. Setting data to ''")
            data = ""

        # first check if there is a generic pre-processor
        pre_procs = [p for p in data_parsers if p.is_generic_pre_processor()]
        num_pre_procs = len(pre_procs)
        Logger.trace("Processing %s Generic Pre-Processors DataParsers", num_pre_procs)
        if num_pre_procs > 1:
            # warn for strange results if more than 1 generic pre-processor is present.
            Logger.warning("More than one Generic Pre-Processor is found (%s). They are being processed in the "
                           "order that Python likes which might result in unexpected result.", num_pre_procs)

        for data_parser in pre_procs:
            # remove it from the list
            data_parsers.remove(data_parser)

            # and process it
            Logger.debug("Processing %s", data_parser)
            (data, pre_items) = data_parser.PreProcessor(data)
            items += pre_items

            if isinstance(data, JsonHelper):
                Logger.debug("Generic preprocessor resulted in JsonHelper data")

        # The the other handlers
        Logger.trace("Processing %s Normal DataParsers", len(data_parsers))
        handler_json = None
        for data_parser in data_parsers:
            Logger.debug("Processing %s", data_parser)

            # Check for preprocessors
            if data_parser.PreProcessor:
                Logger.debug("Processing DataParser.PreProcessor")
                (handler_data, pre_items) = data_parser.PreProcessor(data)
                items += pre_items
            else:
                handler_data = data

            Logger.debug("Processing DataParser.Parser")
            if data_parser.Parser is None or (data_parser.Parser == "" and not data_parser.IsJson):
                if data_parser.Creator:
                    Logger.warning("No <parser> found for %s. Skipping.", data_parser.Creator)
                continue

            if data_parser.IsJson:
                if handler_json is None:
                    # Cache the json requests to improve performance
                    Logger.trace("Caching JSON results for Dataparsing")
                    if isinstance(handler_data, JsonHelper):
                        handler_json = handler_data
                    else:
                        handler_json = JsonHelper(handler_data, Logger.instance())

                Logger.trace(data_parser.Parser)
                parser_results = handler_json.get_value(fallback=[], *data_parser.Parser)

                if not isinstance(parser_results, (tuple, list)):
                    # if there is just one match, return that as a list
                    parser_results = [parser_results]
            else:
                if isinstance(handler_data, JsonHelper):
                    raise ValueError("Cannot perform Regex Parser on JsonHelper.")
                else:
                    parser_results = Regexer.do_regex(data_parser.Parser, handler_data)

            Logger.debug("Processing DataParser.Creator for %s items", len(parser_results))
            for parser_result in parser_results:
                handler_result = data_parser.Creator(parser_result)
                if handler_result is not None:
                    if isinstance(handler_result, list):
                        items += handler_result
                    else:
                        items.append(handler_result)

        # should we exclude DRM/GEO?
        hide_geo_locked = AddonSettings.hide_geo_locked_items_for_location(self.language)
        hide_drm_protected = AddonSettings.hide_drm_items()
        hide_premium = AddonSettings.hide_premium_items()
        hide_folders = AddonSettings.hide_restricted_folders()
        type_to_exclude = None
        if not hide_folders:
            type_to_exclude = "folder"

        old_count = len(items)
        if hide_drm_protected:
            Logger.debug("Hiding DRM items")
            items = [i for i in items if not i.isDrmProtected or i.type == type_to_exclude]
        if hide_geo_locked:
            Logger.debug("Hiding GEO Locked items due to GEO region: %s", self.language)
            items = [i for i in items if not i.isGeoLocked or i.type == type_to_exclude]
        if hide_premium:
            Logger.debug("Hiding Premium items")
            items = [i for i in items if not i.isPaid or i.type == type_to_exclude]

        # Local import for performance
        from resources.lib.cloaker import Cloaker
        cloaker = Cloaker(self, AddonSettings.store(LOCAL), logger=Logger.instance())
        if not AddonSettings.show_cloaked_items():
            Logger.debug("Hiding Cloaked items")
            items = [i for i in items if not cloaker.is_cloaked(i.url)]
        else:
            cloaked_items = [i for i in items if cloaker.is_cloaked(i.url)]
            for c in cloaked_items:
                c.isCloaked = True

        if len(items) != old_count:
            Logger.info("Hidden %s items due to DRM/GEO/Premium/cloak filter (Hide Folders=%s)",
                        old_count - len(items), hide_folders)

        # Check for grouping or not
        limit = AddonSettings.get_list_limit()
        folder_items = [i for i in items if i.type.lower() == "folder"]

        # we should also de-duplicate before calculating
        folder_items = list(set(folder_items))
        folders = len(folder_items)

        if 0 < limit < folders:
            # let's filter them by alphabet if the number is exceeded
            Logger.debug("Creating Groups for list exceeding '%s' folder items. Total folders found '%s'.",
                         limit, folders)
            other = "\a{}".format(LanguageHelper.get_localized_string(LanguageHelper.OtherChars))
            title_format = "\a{}".format(LanguageHelper.get_localized_string(LanguageHelper.StartWith))
            result = dict()
            non_grouped = []
            # Should we remove prefixes just as Kodi does?
            # prefixes = ("de", "het", "the", "een", "a", "an")

            for sub_item in items:
                if sub_item.dontGroup or sub_item.type != "folder":
                    non_grouped.append(sub_item)
                    continue

                char = sub_item.name[0].upper()
                # Should we de-prefix?
                # for p in prefixes:
                #     if sub_item.name.lower().startswith(p + " "):
                #         char = sub_item.name[len(p) + 1][0].upper()

                if char.isdigit():
                    char = "0-9"
                elif not char.isalpha():
                    char = other

                if char not in result:
                    Logger.trace("Creating Grouped item from: %s", sub_item)
                    if char == other:
                        item = MediaItem(title_format.replace("'", "") % (char,), "")
                    else:
                        item = MediaItem(title_format % (char.upper(),), "")
                    item.complete = True
                    # item.set_date(2100 + ord(char[0]), 1, 1, text='')
                    result[char] = item
                else:
                    item = result[char]
                item.items.append(sub_item)

            items = non_grouped + list(result.values())

        # In order to get a better performance in de-duplicating and keeping the sort order
        # we first need to store the order in a lookup table. Then we use sorted(set()) and
        # use that lookup table for sorting. Using sorted(set(), items.index) this will be
        # an O(n) (for the index()) times O(n*log(n)) (for the sorted) = O(n^2*log(n)!.
        # The dictionary lookup (O(1)) saves us an O(n).
        # See https://wiki.python.org/moin/TimeComplexity
        sorted_order = {}
        for i in range(0, len(items)):
            sorted_order[items[i]] = i
        unique_results = sorted(set(items), key=sorted_order.get)

        Logger.trace("Found '%d' items of which '%d' are unique.", len(items), len(unique_results))
        return unique_results
コード例 #28
0
    def __update_title_and_description_with_limitations(self):
        """ Updates the title/name and description with the symbols for DRM, GEO and Paid.

        :return:            (tuple) name postfix, description postfix
        :rtype: tuple[str,str]

        """

        geo_lock = "&ordm;"  # º
        drm_lock = "^"  # ^
        paid = "&ordf;"  # ª
        cloaked = "&uml;"  # ¨
        description_prefix = []
        title_postfix = []

        description = ""
        title = ""

        if self.__expires_datetime is not None:
            expires = "{}: {}".format(
                MediaItem.ExpiresAt,
                self.__expires_datetime.strftime("%Y-%m-%d %H:%M"))
            description_prefix.append(expires)

        if self.isDrmProtected:
            title_postfix.append(drm_lock)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.DrmProtected))

        if self.isGeoLocked:
            title_postfix.append(geo_lock)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.GeoLockedId))

        if self.isPaid:
            title_postfix.append(paid)
            description_prefix.append(
                LanguageHelper.get_localized_string(
                    LanguageHelper.PremiumPaid))

        if self.isCloaked:
            title_postfix.append(cloaked)
            description_prefix.append(
                LanguageHelper.get_localized_string(LanguageHelper.HiddenItem))

        if self.uses_external_addon:
            from resources.lib.xbmcwrapper import XbmcWrapper
            external = XbmcWrapper.get_external_add_on_label(self.url)
            title_postfix.append(external)

        # actually update it
        if description_prefix:
            description_prefix = "\n".join(description_prefix)
            description = "[COLOR gold][I]%s[/I][/COLOR]" % (
                description_prefix.rstrip(), )

        if title_postfix:
            title = "".join(title_postfix)
            title = "[COLOR gold]%s[/COLOR]" % (title.lstrip(), )

        return title, description
コード例 #29
0
    def add_categories_and_specials(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 = []

        extras = {
            LanguageHelper.get_localized_string(LanguageHelper.Search): ("searchSite", None, False),
            LanguageHelper.get_localized_string(LanguageHelper.TvShows): (
                "https://www.tv4play.se/alla-program",
                None, False
            ),
            LanguageHelper.get_localized_string(LanguageHelper.Categories): (
                "https://graphql.tv4play.se/graphql?query=query%7Btags%7D", None, False
            ),
            LanguageHelper.get_localized_string(LanguageHelper.CurrentlyPlayingEpisodes): (
                self.__get_api_url("LiveVideos", "9b3d0d2f039089311cde2989760744844f7c4bb5033b0ce5643676ee60cb0901"),
                None, False
            )
        }

        # No more extras
        # today = datetime.datetime.now()
        # days = [LanguageHelper.get_localized_string(LanguageHelper.Monday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Tuesday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Wednesday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Thursday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Friday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Saturday),
        #         LanguageHelper.get_localized_string(LanguageHelper.Sunday)]
        # for i in range(0, 7, 1):
        #     start_date = today - datetime.timedelta(i)
        #     end_date = start_date + datetime.timedelta(1)
        #
        #     day = days[start_date.weekday()]
        #     if i == 0:
        #         day = LanguageHelper.get_localized_string(LanguageHelper.Today)
        #     elif i == 1:
        #         day = LanguageHelper.get_localized_string(LanguageHelper.Yesterday)
        #
        #     Logger.trace("Adding item for: %s - %s", start_date, end_date)
        #     url = "https://api.tv4play.se/play/video_assets?exclude_node_nids=" \
        #           "&platform=tablet&is_live=false&product_groups=2&type=episode&per_page=100"
        #     url = "%s&broadcast_from=%s&broadcast_to=%s&" % (url, start_date.strftime("%Y%m%d"), end_date.strftime("%Y%m%d"))
        #     extras[day] = (url, start_date, False)
        #
        # extras[LanguageHelper.get_localized_string(LanguageHelper.CurrentlyPlayingEpisodes)] = (
        #     "https://api.tv4play.se/play/video_assets?exclude_node_nids=&platform=tablet&"
        #     "is_live=true&product_groups=2&type=episode&per_page=100", None, False)

        # Actually add the extra items
        for name in extras:
            title = name
            url, date, is_live = extras[name]   # type: str, datetime.datetime, bool
            item = MediaItem(title, url)
            item.dontGroup = True
            item.complete = True
            item.HttpHeaders = self.httpHeaders
            item.isLive = is_live

            if date is not None:
                item.set_date(date.year, date.month, date.day, 0, 0, 0, text=date.strftime("%Y-%m-%d"))

            items.append(item)

        Logger.debug("Pre-Processing finished")
        return data, items