コード例 #1
0
    def get_setting(self, setting_id):
        """ Retrieves an encrypted setting from the Kodi Add-on Settings.

        :param str setting_id: the ID for the setting to retrieve.

        :return: the decrypted value for the setting.
        :rtype: str
        """

        Logger.info("Decrypting value for setting '%s'", setting_id)
        encrypted_value = AddonSettings.get_setting(setting_id)
        if not encrypted_value:
            Logger.warning("Found empty string as encrypted data")
            return encrypted_value

        try:
            decrypted_value = self.__decrypt(encrypted_value, Vault.__Key)
            if not decrypted_value.startswith(setting_id):
                Logger.error("Invalid decrypted value for setting '%s'",
                             setting_id)
                return None

            decrypted_value = decrypted_value[len(setting_id) + 1:]
            Logger.info("Successfully decrypted value for setting '%s'",
                        setting_id)
        except UnicodeDecodeError:
            Logger.error(
                "Invalid Unicode data returned from decryption. Must be wrong data"
            )
            return None

        return decrypted_value
コード例 #2
0
    def create_category_item(self, result_set):
        """ Creates a MediaItem of type 'folder' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

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

        """

        if 'thumburl' in result_set and not result_set['thumburl'].startswith(
                "http"):
            result_set['thumburl'] = "%s/%s" % (self.baseUrl,
                                                result_set["thumburl"])

        slug = result_set["slug"]
        url = self.__cateogory_urls.get(slug)

        if url is None:
            Logger.warning("Missing category in list: %s", slug)
            return None

        result_set["url"] = url
        return chn_class.Channel.create_folder_item(self, result_set)
コード例 #3
0
    def create_json_item(self, result_set):
        """ Creates a new MediaItem for an folder or video.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

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

        """

        Logger.trace(result_set)

        item_type = result_set["format"]
        if item_type == "video":
            return self.create_video_item_json(result_set)
        elif item_type == "audio":
            # Apparently the audio is always linking to a show folder.
            return self.create_episode_json_item(result_set)
        else:
            Logger.warning("Found unknown type: %s", item_type)
            return None
コード例 #4
0
    def create_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param result_set: The result_set of the self.episodeItemRegex
        :type result_set: list[str]|dict[str,dict[str,dict]]

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        if "fullengthSegment" in result_set and "segment" in result_set[
                "fullengthSegment"]:
            video_id = result_set["fullengthSegment"]["segment"]["id"]
            geo_location = result_set["fullengthSegment"]["segment"][
                "geolocation"]
            geo_block = False
            if "flags" in result_set["fullengthSegment"]["segment"]:
                geo_block = result_set["fullengthSegment"]["segment"][
                    "flags"].get("geoblock", None)
            Logger.trace("Found geoLocation/geoBlock: %s/%s", geo_location,
                         geo_block)
        else:
            Logger.warning("No video information found.")
            return None

        url = "http://www.srf.ch/player/webservice/videodetail/index?id=%s" % (
            video_id, )
        item = MediaItem(result_set["titleFull"], url)
        item.type = "video"

        # noinspection PyTypeChecker
        item.thumb = result_set.get("segmentThumbUrl", None)
        # apparently only the 144 return the correct HEAD info
        # item.thumb = "%s/scale/width/288" % (item.thumb, )
        # the HEAD will not return a size, so Kodi can't handle it
        # item.fanart = resultSet.get("imageUrl", None)
        item.description = result_set.get("description", "")

        date_value = str(result_set["time_published"])
        # 2015-01-20 22:17:59"
        date_time = DateHelper.get_date_from_string(date_value,
                                                    "%Y-%m-%d %H:%M:%S")
        item.set_date(*date_time[0:6])

        item.icon = self.icon
        item.httpHeaders = self.httpHeaders
        item.complete = False
        return item
コード例 #5
0
    def create_video_item_new(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param result_set: The result_set of the self.episodeItemRegex
        :type result_set: list[str]|dict[str,str]

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        videos = self.__get_nested_value(result_set, "Assets", "Video")
        if not videos:
            Logger.warning("No video information found.")
            return None

        video_infos = [vi for vi in videos if vi["fullLength"]]
        if len(video_infos) > 0:
            video_info = video_infos[0]
        else:
            Logger.warning("No full length video found.")
            return None
        # noinspection PyTypeChecker
        video_id = video_info["id"]

        url = "http://il.srgssr.ch/integrationlayer/1.0/ue/srf/video/play/%s.json" % (
            video_id, )
        item = MediaItem(result_set["title"], url)
        item.type = "video"

        item.thumb = self.__get_nested_value(video_info, "Image",
                                             "ImageRepresentations",
                                             "ImageRepresentation", 0, "url")
        item.description = self.__get_nested_value(video_info,
                                                   "AssetMetadatas",
                                                   "AssetMetadata", 0,
                                                   "description")

        date_value = str(result_set["publishedDate"])
        date_value = date_value[0:-6]
        # 2015-01-20T22:17:59"
        date_time = DateHelper.get_date_from_string(date_value,
                                                    "%Y-%m-%dT%H:%M:%S")
        item.set_date(*date_time[0:6])

        item.icon = self.icon
        item.httpHeaders = self.httpHeaders
        item.complete = False
        return item
コード例 #6
0
    def __initialise_channel_set(self, channel_info):
        # type: (ChannelInfo) -> None
        """ Initialises a channelset (.py file)

        WARNING: these actions are done ONCE per python file, not per channel.

        Arguments:
        channelInfo : ChannelInfo - The channelinfo

        Keyword Arguments:
        abortOnNew  : Boolean - If set to true, channel initialisation will not continue if a new channel was found.
                                This will have to be done later.

        Returns True if any operations where executed

        """

        Logger.info("Initialising channel set at: %s.", channel_info.path)

        # now import (required for the PerformFirstTimeActions
        sys.path.append(channel_info.path)

        # make sure a pyo or pyc exists
        # __import__(channelInfo.moduleName)
        # The debugger won't compile if __import__ is used. So let's use this one.
        import py_compile
        py_compile.compile(os.path.join(channel_info.path, "%s.py" % (channel_info.moduleName,)))

        # purge the texture cache.
        if TextureHandler.instance():
            TextureHandler.instance().purge_texture_cache(channel_info)
        else:
            Logger.warning("Could not purge_texture_cache: no TextureHandler available")
        return
コード例 #7
0
    def extract_json(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 = []

        json_data = Regexer.do_regex('type="application/json">([^<]+)<', data)
        if not json_data:
            Logger.warning("No JSON data found.")
            return data, items

        json = JsonHelper(json_data[0])
        result = []
        for key, value in json.json.items():
            result.append(value)
            value["title"] = key

        # set new json and return JsonHelper object
        json.json = result
        return json, items
コード例 #8
0
    def __update_video_from_brightcove(self, item, data,
                                       use_adaptive_with_encryption):
        """ Updates an existing MediaItem with more data based on an MPD stream.

        :param str data:                            Stream info retrieved from BrightCove.
        :param bool use_adaptive_with_encryption:   Do we use the Adaptive InputStream add-on?
        :param MediaItem item:                      The original MediaItem that needs updating.

        :return: The original item with more data added to it's properties.
        :rtype: MediaItem

        """

        part = item.create_new_empty_media_part()
        # Then try the new BrightCove JSON
        bright_cove_regex = '<video[^>]+data-video-id="(?<videoId>[^"]+)[^>]+data-account="(?<videoAccount>[^"]+)'
        bright_cove_data = Regexer.do_regex(
            Regexer.from_expresso(bright_cove_regex), data)
        if not bright_cove_data:
            Logger.warning("Error updating using BrightCove data: %s", item)
            return item

        Logger.info("Found new BrightCove JSON data")
        bright_cove_url = 'https://edge.api.brightcove.com/playback/v1/accounts/' \
                          '%(videoAccount)s/videos/%(videoId)s' % bright_cove_data[0]
        headers = {
            "Accept":
            "application/json;pk=BCpkADawqM3ve1c3k3HcmzaxBvD8lXCl89K7XEHiKutxZArg2c5RhwJHJANOwPwS_4o7UsC4RhIzXG8Y69mrwKCPlRkIxNgPQVY9qG78SJ1TJop4JoDDcgdsNrg"
        }

        bright_cove_data = UriHandler.open(bright_cove_url,
                                           additional_headers=headers)
        bright_cove_json = JsonHelper(bright_cove_data)
        streams = [
            d for d in bright_cove_json.get_value("sources")
            if d["container"] == "M2TS"
        ]
        # Old filter
        # streams = filter(lambda d: d["container"] == "M2TS", bright_cove_json.get_value("sources"))
        if not streams:
            Logger.warning("Error extracting streams from BrightCove data: %s",
                           item)
            return item

        # noinspection PyTypeChecker
        stream_url = streams[0]["src"]

        # these streams work better with the the InputStreamAddon because it removes the
        # "range" http header
        if use_adaptive_with_encryption:
            Logger.info("Using InputStreamAddon for playback of HLS stream")
            strm = part.append_media_stream(stream_url, 0)
            M3u8.set_input_stream_addon_input(strm)
            item.complete = True
            return item

        for s, b in M3u8.get_streams_from_m3u8(stream_url):
            item.complete = True
            part.append_media_stream(s, b)
        return item
コード例 #9
0
    def create_api_typed_item(self, result_set):
        """ Creates a new MediaItem based on the __typename attribute.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        :param list[str]|dict result_set: The result_set of the self.episodeItemRegex

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

        """

        api_type = result_set["__typename"]
        Logger.trace("%s: %s", api_type, result_set)

        if api_type == "Program":
            item = self.create_api_program_type(result_set)
        elif api_type == "ProgramCard":
            item = self.create_api_program_type(result_set.get("program"))
        else:
            Logger.warning("Missing type: %s", api_type)
            return None

        return item
コード例 #10
0
    def __get_proxies(self, proxy, url):
        """

        :param ProxyInfo proxy:
        :param url:

        :return:
        :rtype: dict[str, str]

        """

        if proxy is None:
            return None

        elif not proxy.use_proxy_for_url(url):
            Logger.debug("Not using proxy due to filter mismatch")

        elif proxy.Scheme == "http":
            Logger.debug("Using a http(s) %s", proxy)
            proxy_address = proxy.get_proxy_address()
            return {"http": proxy_address, "https": proxy_address}

        elif proxy.Scheme == "dns":
            Logger.debug("Using a DNS %s", proxy)
            return {"dns": proxy.Proxy}

        Logger.warning("Unsupported Proxy Scheme: %s", proxy.Scheme)
        return None
コード例 #11
0
    def execute(self):
        Logger.debug("Performing Custom Contextmenu command: %s",
                     self.__action)

        item = self.__media_item
        if not item.complete:
            Logger.debug(
                "The contextmenu action requires a completed item. Updating %s",
                item)
            item = self.__channel.process_video_item(item)

            if not item.complete:
                Logger.warning(
                    "update_video_item returned an item that had item.complete = False:\n%s",
                    item)

        # invoke the call
        function_string = "returnItem = channel_object.%s(item)" % (
            self.__action, )
        Logger.debug("Calling '%s'", function_string)
        try:
            # noinspection PyRedundantParentheses
            exec(function_string)  # NOSONAR We just need this here.
        except:
            Logger.error("on_action_from_context_menu :: Cannot execute '%s'.",
                         function_string,
                         exc_info=True)
        return
コード例 #12
0
    def on_action_from_context_menu(self, action):
        """Peforms the action from a custom contextmenu

        Arguments:
        action : String - The name of the method to call

        """
        Logger.debug("Performing Custom Contextmenu command: %s", action)

        item = self.media_item
        if not item.complete:
            Logger.debug(
                "The contextmenu action requires a completed item. Updating %s",
                item)
            item = self.channelObject.process_video_item(item)

            if not item.complete:
                Logger.warning(
                    "update_video_item returned an item that had item.complete = False:\n%s",
                    item)

        # invoke
        function_string = "returnItem = self.channelObject.%s(item)" % (
            action, )
        Logger.debug("Calling '%s'", function_string)
        try:
            # noinspection PyRedundantParentheses
            exec(function_string)  # NOSONAR We just need this here.
        except:
            Logger.error("on_action_from_context_menu :: Cannot execute '%s'.",
                         function_string,
                         exc_info=True)
        return
コード例 #13
0
    def __init__(self):
        """ Creates a new instance of the Vault class """

        self.__newKeyGeneratedInConstructor = False  # : This was the very first time a key was generated

        # ask for PIN of no key is present
        if Vault.__Key is None:
            howto_shown = self.__show_howto()
            key = self.__get_application_key()  # type: bytes

            # was there a key? No, let's initialize it.
            if key is None:
                Logger.warning(
                    "No Application Key present. Initializing a new one.")

                # Show the how to if it was not already shown during this __init__()
                if not howto_shown:
                    self.__show_howto(force=True)

                key = self.__get_new_key()
                if not self.change_pin(key):
                    raise RuntimeError("Error creating Application Key.")
                Logger.info(
                    "Created a new Application Key with MD5: %s (length=%s)",
                    EncodingHelper.encode_md5(key), len(key))
                self.__newKeyGeneratedInConstructor = True

            Vault.__Key = key
            Logger.trace("Using Application Key with MD5: %s (length=%s)",
                         EncodingHelper.encode_md5(key), len(key))
コード例 #14
0
    def execute(self):
        title = LanguageHelper.get_localized_string(
            LanguageHelper.CleanupCache)[:-1]
        clean = \
            XbmcWrapper.show_yes_no(title, LanguageHelper.CleanupConfirmation)
        if not clean:
            Logger.warning("Clean-up cancelled")
            return

        files_to_remove = {
            "channelindex.json": "Cleaning: Channel Index",
            "cookiejar.dat": "Cleaning: Cookies in cookiejar.dat",
            "xot.session.lock": "Cleaning: Session lock"
        }
        for file_name, log_line in files_to_remove.items():
            Logger.info(log_line)
            files_to_remove = os.path.join(Config.profileDir, file_name)
            if os.path.isfile(files_to_remove):
                os.remove(files_to_remove)

        Logger.info("Cleaning: PickeStore objects")
        self.parameter_parser.pickler.purge_store(Config.addonId, age=0)

        Logger.info("Cleaning: Cache objects in cache folder")
        env_ctrl = EnvController(Logger.instance())
        env_ctrl.cache_clean_up(Config.cacheDir, 0)
コード例 #15
0
    def __get_context_menu_items(self, channel, item=None):
        """ Retrieves the custom context menu items to display.

        favouritesList : Boolean   - Indication that the menu is for the favorites

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

        # Genenric, 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, 'XBMC.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

        # 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._create_action_url(channel, action=menu_item.functionName, item=item)
                cmd = "XBMC.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
コード例 #16
0
    def execute(self):
        """ Shows the current channels settings dialog. """

        if not self.__channel_info:
            Logger.warning("Cannot configure channel without channel info")

        Logger.info("Configuring channel: %s", self.__channel_info)
        AddonSettings.show_channel_settings(self.__channel_info)
コード例 #17
0
    def update_video_item(self, item):
        """ Updates an existing MediaItem with more data.

        Used to update none complete MediaItems (self.complete = False). This
        could include opening the item's URL to fetch more data and then process that
        data or retrieve it's real media-URL.

        The method should at least:
        * cache the thumbnail to disk (use self.noImage if no thumb is available).
        * set at least one MediaItemPart with a single MediaStream.
        * set self.complete = True.

        if the returned item does not have a MediaItemPart then the self.complete flag
        will automatically be set back to False.

        :param MediaItem item: the original MediaItem that needs updating.

        :return: The original item with more data added to it's properties.
        :rtype: MediaItem

        """

        Logger.debug('Starting update_video_item for %s (%s)', item.name, self.channelName)

        if not item.url.endswith(".js"):
            data = UriHandler.open(item.url)
            data_id = Regexer.do_regex(r'data-id="(\d+)"[^>]+data-playout', data)
            if data_id is None:
                Logger.warning("Cannot find stream-id for L1 stream.")
                return item

            data_url = "https://limburg.bbvms.com/p/L1_video/c/{}.json".format(data_id[0])
        else:
            data_url = item.url

        data = UriHandler.open(data_url)
        json = JsonHelper(data, logger=Logger.instance())
        Logger.trace(json)

        base_url = json.get_value("publicationData", "defaultMediaAssetPath")
        streams = json.get_value("clipData", "assets")
        item.MediaItemParts = []
        part = item.create_new_empty_media_part()
        for stream in streams:
            url = stream.get("src", None)
            if "://" not in url:
                url = "{}{}".format(base_url, url)
            bitrate = stream.get("bandwidth", None)
            if url:
                part.append_media_stream(url, bitrate)

        if not item.thumb and json.get_value("thumbnails"):
            url = json.get_value("thumbnails")[0].get("src", None)
            if url and "http:/" not in url:
                url = "%s%s" % (self.baseUrl, url)
            item.thumb = url
        item.complete = True
        return item
コード例 #18
0
    def extract_json_video(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 = []

        data = Regexer.do_regex(r'window.__DATA__ = ([\w\W]+?});\s*window.__PUSH_STATE__', data)[0]
        json_data = JsonHelper(data)
        # Get the main content container
        main_container = [m for m in json_data.get_value("children") if m["type"] == "MainContainer"]

        # Extract seasons
        seasons = []
        if not self.parentItem.metaData.get("is_season", False):
            seasons = [
                lst["props"]["items"]
                for lst in main_container[0]["children"]
                if lst["type"] == "SeasonSelector"
            ]
            if seasons:
                seasons = [s for s in seasons[0] if s["url"]]
                # Inject them
                json_data.json["seasons"] = seasons

        # Find the actual
        line_lists = [lst for lst in main_container[0]["children"] if lst["type"] == "LineList"]
        for line_list in line_lists:
            if line_list.get("props", {}).get("type") == "video-guide":
                json_data.json = line_list["props"]

                # Get the actual full episode list
                all_episodes = json_data.get_value("filters", "items", 0, "url")
                url_all_episodes = "{}{}".format(self.baseUrl, all_episodes)
                data = UriHandler.open(url_all_episodes)
                json_data = JsonHelper(data)

                # And append seasons again
                if seasons:
                    json_data.json["seasons"] = seasons
                return json_data, items

        Logger.warning("Cannot extract video items")
        return json_data, items
コード例 #19
0
    def update_user_agent():
        """ Creates a user agent for this instance of XOT

        this is a very slow action on lower end systems (ATV and rPi) so we minimize the number of runs

        :return: Nothing
        :rtype: None

        Actual:
        User-Agent: Kodi/16.1 (Windows NT 10.0; WOW64) App_Bitness/32 Version/16.1-Git:20160424-c327c53
        Retro:
        User-Agent: Kodi/16.1 Git:20160424-c327c53 (Windows 10;AMD64; http://kodi.tv)

        Firefox:
        User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0
        """

        # there are slow imports, so only do them here
        import platform
        from resources.lib.envcontroller import EnvController

        # noinspection PyNoneFunctionAssignment
        version = AddonSettings.get_kodi_version()
        Logger.debug("Found Kodi version: %s", version)
        git = ""
        try:
            # noinspection PyNoneFunctionAssignment
            if "Git:" in version:
                version, git = version.split("Git:", 1)
            version = version.rstrip()

            # The platform.<method> are not working on rPi and IOS
            # kernel = platform.architecture()
            # Logger.Trace(kernel)
            # machine = platform.machine()
            # Logger.Trace(machine)

            uname = platform.uname()
            Logger.trace(uname)
            if git:
                user_agent = "Kodi/%s (%s %s; %s; http://kodi.tv) Version/%s Git:%s" % \
                             (version, uname[0], uname[2], uname[4], version, git)
            else:
                user_agent = "Kodi/%s (%s %s; %s; http://kodi.tv) Version/%s" % \
                             (version, uname[0], uname[2], uname[4], version)
        except:
            Logger.warning("Error setting user agent", exc_info=True)
            current_env = EnvController.get_platform(True)
            # Kodi/14.2 (Windows NT 6.1; WOW64) App_Bitness/32 Version/14.2-Git:20150326-7cc53a9
            user_agent = "Kodi/%s (%s; <unknown>; http://kodi.tv)" % (version, current_env)

        # now we store it
        AddonSettings.store(LOCAL).set_setting(AddonSettings.__USER_AGENT_SETTING, user_agent)
        AddonSettings.__user_agent = user_agent
        Logger.info("User agent set to: %s", user_agent)
        return
コード例 #20
0
    def update_json_video_item(self, item):
        """ Updates an existing MediaItem with more data.

        Used to update none complete MediaItems (self.complete = False). This
        could include opening the item's URL to fetch more data and then process that
        data or retrieve it's real media-URL.

        The method should at least:
        * cache the thumbnail to disk (use self.noImage if no thumb is available).
        * set at least one MediaItemPart with a single MediaStream.
        * set self.complete = True.

        if the returned item does not have a MediaItemPart then the self.complete flag
        will automatically be set back to False.

        :param MediaItem item: the original MediaItem that needs updating.

        :return: The original item with more data added to it's properties.
        :rtype: MediaItem

        """

        data = UriHandler.open(item.url)
        video_data = JsonHelper(data)
        stream_data = video_data.get_value("playable")
        if not stream_data:
            return item

        part = item.create_new_empty_media_part()
        for stream_info in stream_data["assets"]:
            url = stream_info["url"]
            stream_type = stream_info["format"]
            if stream_type == "HLS":
                item.complete = M3u8.update_part_with_m3u8_streams(part, url)
            else:
                Logger.warning("Found unknow stream type: %s", stream_type)

        if "subtitles" not in stream_data or not stream_data["subtitles"]:
            return item

        for sub in stream_data["subtitles"]:
            sub_url = None
            sub_type = sub["type"]
            default_sub = sub["defaultOn"]
            if default_sub:
                sub_url = sub["webVtt"]
                sub_type = "webvtt"  # set Retrospect type

            if sub_url:
                part.Subtitle = SubtitleHelper.download_subtitle(
                    sub_url, format=sub_type)
                break

        return item
コード例 #21
0
    def __configure_channel(self, channel_info):
        """ Shows the current channels settings dialog.

        :param ChannelInfo channel_info:    The channel info for the channel

        """

        if not channel_info:
            Logger.warning("Cannot configure channel without channel info")

        Logger.info("Configuring channel: %s", channel_info)
        AddonSettings.show_channel_settings(channel_info)
コード例 #22
0
    def __update_m3u8(self, url, part, headers, use_kodi_hls):
        """ Update a video that has M3u8 streams.

        :param str url:                 The URL for the stream.
        :param MediaItemPart part:      The new part that needs updating.
        :param dict[str,str] headers:   The URL headers to use.
        :param bool use_kodi_hls:       Should we use the InputStream Adaptive add-on?

        """
        # first see if there are streams in this file, else check the second location.
        for s, b in M3u8.get_streams_from_m3u8(url,
                                               self.proxy,
                                               headers=headers):
            if use_kodi_hls:
                strm = part.append_media_stream(url, 0)
                M3u8.set_input_stream_addon_input(strm, headers=headers)
                # Only the main M3u8 is needed
                break
            else:
                part.append_media_stream(s, b)

        if not part.MediaStreams and "manifest.m3u8" in url:
            Logger.warning(
                "No streams found in %s, trying alternative with 'master.m3u8'",
                url)
            url = url.replace("manifest.m3u8", "master.m3u8")
            for s, b in M3u8.get_streams_from_m3u8(url,
                                                   self.proxy,
                                                   headers=headers):
                if use_kodi_hls:
                    strm = part.append_media_stream(url, 0)
                    M3u8.set_input_stream_addon_input(strm, headers=headers)
                    # Only the main M3u8 is needed
                    break
                else:
                    part.append_media_stream(s, b)

        # check for subs
        # https://mtgxse01-vh.akamaihd.net/i/201703/13/DCjOLN_1489416462884_427ff3d3_,48,260,460,900,1800,2800,.mp4.csmil/master.m3u8?__b__=300&hdnts=st=1489687185~exp=3637170832~acl=/*~hmac=d0e12e62c219d96798e5b5ef31b11fa848724516b255897efe9808c8a499308b&cc1=name=Svenska%20f%C3%B6r%20h%C3%B6rselskadade~default=no~forced=no~lang=sv~uri=https%3A%2F%2Fsubstitch.play.mtgx.tv%2Fsubtitle%2Fconvert%2Fxml%3Fsource%3Dhttps%3A%2F%2Fcdn-subtitles-mtgx-tv.akamaized.net%2Fpitcher%2F20xxxxxx%2F2039xxxx%2F203969xx%2F20396967%2F20396967-swt.xml%26output%3Dm3u8
        # https://cdn-subtitles-mtgx-tv.akamaized.net/pitcher/20xxxxxx/2039xxxx/203969xx/20396967/20396967-swt.xml&output=m3u8
        if "uri=" in url and not part.Subtitle:
            Logger.debug("Extracting subs from M3u8")
            sub_url = url.rsplit("uri=")[-1]
            sub_url = HtmlEntityHelper.url_decode(sub_url)
            sub_data = UriHandler.open(sub_url, proxy=self.proxy)
            subs = [
                line for line in sub_data.split("\n")
                if line.startswith("http")
            ]
            if subs:
                part.Subtitle = SubtitleHelper.download_subtitle(
                    subs[0], format='webvtt', proxy=self.proxy)
        return
コード例 #23
0
    def create_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        url = "%s%s" % (self.baseUrl, result_set["url"])
        if self.parentItem.url not in url:
            return None

        name = result_set["title"]
        desc = result_set.get("description", "")
        thumb = result_set["thumburl"]

        if thumb and not thumb.startswith("http://"):
            thumb = "%s%s" % (self.baseUrl, thumb)

        item = MediaItem(name, url)
        item.thumb = thumb
        item.description = desc
        item.icon = self.icon
        item.type = 'video'
        item.complete = False

        try:
            name_parts = name.rsplit("/", 3)
            if len(name_parts) == 3:
                Logger.debug("Found possible date in name: %s", name_parts)
                year = name_parts[2]
                if len(year) == 2:
                    year = 2000 + int(year)
                month = name_parts[1]
                day = name_parts[0].rsplit(" ", 1)[1]
                Logger.trace("%s - %s - %s", year, month, day)
                item.set_date(year, month, day)
        except:
            Logger.warning("Apparently it was not a date :)")
        return item
コード例 #24
0
    def create_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param list[str]|dict[str,str] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        # Validate the input and raise errors
        if not isinstance(result_set, dict):
            Logger.critical(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )
            raise NotImplementedError(
                "No Dictionary as a result_set. Implement a custom create_video_item"
            )

        elif "title" not in result_set or "url" not in result_set:
            Logger.warning("No ?P<title> or ?P<url> in result_set")
            raise LookupError("No ?P<title> or ?P<url> in result_set")

        # The URL
        url = self._prefix_urls(result_set["url"])

        # The title
        if "subtitle" in result_set and result_set["subtitle"]:
            # noinspection PyStringFormat
            title = "%(title)s - %(subtitle)s" % result_set
        else:
            title = result_set["title"]
        if title.isupper():
            title = title.title()

        item = MediaItem(title, url)
        item.thumb = self._prefix_urls(result_set.get("thumburl", ""))
        item.description = result_set.get("description", "")
        item.type = 'video'
        item.HttpHeaders = self.httpHeaders
        item.complete = False
        return item
コード例 #25
0
    def create_json_video_item(self, result_set, prepend_serie=False):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param list[str]|dict result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        if not result_set.get("available", True):
            Logger.warning("Item not available: %s", result_set)
            return None

        item = self.create_json_episode_item(result_set)
        if item is None:
            return None

        if prepend_serie and 'seriesTitle' in result_set:
            item.name = "{0} - {1}".format(item.name,
                                           result_set['seriesTitle'])
        elif 'seriesTitle' in result_set:
            item.name = result_set['seriesTitle']

        item.type = "video"
        # Older URL
        item.url = "https://embed.kijk.nl/api/video/%(id)s?id=kijkapp&format=DASH&drm=CENC" % result_set
        # New URL
        # item.url = "https://embed.kijk.nl/video/%(id)s" % result_set

        if 'subtitle' in result_set:
            item.name = "{0} - {1}".format(item.name, result_set['subtitle'])

        if "date" in result_set:
            date = result_set["date"].split("+")[0]
            # 2016-12-25T17:58:00+01:00
            time_stamp = DateHelper.get_date_from_string(
                date, "%Y-%m-%dT%H:%M:%S")
            item.set_date(*time_stamp[0:6])

        return item
コード例 #26
0
    def update_video_item(self, item):
        """ Updates an existing MediaItem with more data.

        Used to update none complete MediaItems (self.complete = False). This
        could include opening the item's URL to fetch more data and then process that
        data or retrieve it's real media-URL.

        The method should at least:
        * cache the thumbnail to disk (use self.noImage if no thumb is available).
        * set at least one MediaItemPart with a single MediaStream.
        * set self.complete = True.

        if the returned item does not have a MediaItemPart then the self.complete flag
        will automatically be set back to False.

        :param MediaItem item: the original MediaItem that needs updating.

        :return: The original item with more data added to it's properties.
        :rtype: MediaItem

        """

        Logger.debug('Starting update_video_item for %s (%s)', item.name,
                     self.channelName)

        xml_data = UriHandler.open(item.url, proxy=self.proxy)
        # <ref type='adaptive' device='pc' host='http://manifest.us.rtl.nl' href='/rtlxl/network/pc/adaptive/components/videorecorder/27/278629/278630/d009c025-6e8c-3d11-8aba-dc8579373134.ssm/d009c025-6e8c-3d11-8aba-dc8579373134.m3u8' />
        m3u8_urls = Regexer.do_regex(
            "<ref type='adaptive' device='pc' host='([^']+)' href='/([^']+)' />",
            xml_data)
        if not m3u8_urls:
            Logger.warning("No m3u8 data found for: %s", item)
            return item
        m3u8_url = "%s/%s" % (m3u8_urls[0][0], m3u8_urls[0][1])

        part = item.create_new_empty_media_part()
        # prevent the "418 I'm a teapot" error
        part.HttpHeaders[
            "user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0"

        # Remove the Range header to make all streams start at the beginning.
        # Logger.debug("Setting an empty 'Range' http header to force playback at the start of a stream")
        # part.HttpHeaders["Range"] = ''

        item.complete = M3u8.update_part_with_m3u8_streams(
            part,
            m3u8_url,
            proxy=self.proxy,
            headers=part.HttpHeaders,
            channel=self)
        return item
コード例 #27
0
    def create_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param dict[str,str|dict] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)

        title = result_set["title"]
        if title is None:
            Logger.warning("Found item with all <null> items. Skipping")
            return None

        if "subtitle" in result_set and result_set['subtitle'].lower(
        ) not in title.lower():
            title = "%(title)s - %(subtitle)s" % result_set

        url = "http://m.schooltv.nl/api/v1/afleveringen/%(mid)s.json" % result_set
        item = MediaItem(title, url)
        item.description = result_set.get("description", "")
        age_groups = result_set.get('ageGroups', ['Onbekend'])
        item.description = "%s\n\nLeeftijden: %s" % (item.description,
                                                     ", ".join(age_groups))

        item.thumb = result_set.get("image", "")
        item.icon = self.icon
        item.type = 'video'
        item.fanart = self.fanart
        item.complete = False
        item.set_info_label("duration", result_set['duration'])

        if "publicationDate" in result_set:
            broadcast_date = DateHelper.get_date_from_posix(
                int(result_set['publicationDate']))
            item.set_date(broadcast_date.year, broadcast_date.month,
                          broadcast_date.day, broadcast_date.hour,
                          broadcast_date.minute, broadcast_date.second)
        return item
コード例 #28
0
    def create_json_video_item(self, result_set):
        """ Creates a MediaItem of type 'video' using the result_set from the regex.

        This method creates a new MediaItem from the Regular Expression or Json
        results <result_set>. The method should be implemented by derived classes
        and are specific to the channel.

        If the item is completely processed an no further data needs to be fetched
        the self.complete property should be set to True. If not set to True, the
        self.update_video_item method is called if the item is focussed or selected
        for playback.

        :param dict[str,str|None] result_set: The result_set of the self.episodeItemRegex

        :return: A new MediaItem of type 'video' or 'audio' (despite the method's name).
        :rtype: MediaItem|None

        """

        Logger.trace(result_set)
        url = result_set["url"]
        if not url.startswith("http"):
            url = "{}{}".format(self.baseUrl, url)

        title = result_set["title"]
        item = MediaItem(title, url)
        item.description = result_set.get("synopsis", None)
        item.thumb = result_set.get("photo", self.noImage)
        item.type = "video"

        if "publicationTimeString" in result_set:
            try:
                # publicationTimeString=7 jun 2018 17:20 uur
                date_parts = result_set["publicationTimeString"].split(" ")
                day = int(date_parts[0])
                month = DateHelper.get_month_from_name(date_parts[1],
                                                       language="nl",
                                                       short=True)
                year = int(date_parts[2])
                hours, minutes = date_parts[3].split(":")
                hours = int(hours)
                minutes = int(minutes)
                item.set_date(year, month, day, hours, minutes, 0)
            except:
                Logger.warning("Error parsing date %s",
                               result_set["publicationTimeString"],
                               exc_info=True)

        item.complete = False
        return item
コード例 #29
0
    def get_channel(self, channel_id, channel_code, info_only=False):
        """ Fetches a single channel for a given className and channelCode

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

        :param str|unicode channel_id:      The chn_<name> class name.
        :param str|unicode channel_code:    A possible channel code within the channel set.
        :param bool info_only:              Only return the ChannelInfo.

        :return: a Channel object
        :rtype: Channel

        """

        # determine the channel folder
        channel_path = os.path.join(Config.rootDir, self.__INTERNAL_CHANNEL_PATH)
        channel_pack, channel_set = channel_id.rsplit(".", 1)
        channel_set_info_path = os.path.join(channel_path, channel_pack, channel_set, "chn_{}.json".format(channel_set))

        channel_infos = ChannelInfo.from_json(channel_set_info_path)
        if channel_code is None:
            channel_infos = [ci for ci in channel_infos if ci.channelCode is None]
        else:
            channel_infos = [ci for ci in channel_infos if ci.channelCode == channel_code]

        if len(channel_infos) != 1:
            Logger.error("Found none or more than 1 matches for '%s' and '%s' in the channel index.",
                         channel_id, channel_code or "None")
            return None
        else:
            Logger.debug("Found single channel in the channel index: %s.", channel_infos[0])

        channel_info = channel_infos[0]
        if self.__is_channel_set_updated(channel_info):
            Logger.warning("Found updated channel_set: %s.", channel_set_info_path)

            # new we should init all channels by loading them all, just to be sure that all is ok
            Logger.debug("Going to fetching all channels to init them all.")
            self.get_channels()
            return self.get_channel(channel_id, channel_code)

        if channel_info.ignore:
            Logger.warning("Channel %s is ignored in channel set", channel_info)
            return None

        if info_only:
            return channel_info

        return channel_info.get_channel()
コード例 #30
0
    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