def download_subtitle(url,
                          file_name="",
                          format='sami',
                          proxy=None,
                          replace=None):
        """Downloads a SAMI and stores the SRT in the cache folder

        Arguments:
        @param url:         string - URL location of the SAMI file
        @param file_name:    string - [opt] Filename to use to store the subtitle in SRT format.
                                     if not specified, an MD5 hash of the URL with .xml
                                     extension will be used
        @param format:      string - Defines the source format. Defaults to Sami.
        @param proxy:       Proxy  - If specified, a proxy will be used
        @param replace:     dict   - Dictionary with key to will be replaced with their values

        @return: The full patch of the cached SRT file.


        """

        if file_name == "":
            Logger.debug(
                "No filename present, generating filename using MD5 hash of url."
            )
            file_name = "%s.srt" % (EncodingHelper.encode_md5(url), )
        elif not file_name.endswith(".srt"):
            Logger.debug("No SRT extension present, appending it.")
            file_name = "%s.srt" % (file_name, )

        srt = ""
        try:
            local_complete_path = os.path.join(Config.cacheDir, file_name)

            # no need to download it again!
            if os.path.exists(local_complete_path):
                return local_complete_path

            Logger.trace("Opening Subtitle URL")
            raw = UriHandler.open(url, proxy=proxy)
            if UriHandler.instance().status.error:
                Logger.warning("Could not retrieve subtitle from %s", url)
                return ""

            if raw == "":
                Logger.warning(
                    "Empty Subtitle path found. Not setting subtitles.")
                return ""

            # try to decode it as `raw` should be a string.
            if isinstance(raw, bytes):
                try:
                    raw = raw.decode()
                except:
                    # fix some weird chars
                    try:
                        raw = raw.replace("\x96", "-")
                    except:
                        Logger.error("Error replacing some weird chars.")
                    Logger.warning(
                        "Converting input to UTF-8 using 'unicode_escape'")
                    raw = raw.decode('unicode_escape')

            # do some auto detection
            if raw.startswith("WEBVTT") and format != "webvtt":
                Logger.info(
                    "Discovered subtitle format 'webvtt' instead of '%s'",
                    format)
                format = "webvtt"

            if format.lower() == 'sami':
                srt = SubtitleHelper.__convert_sami_to_srt(raw)
            elif format.lower() == 'srt':
                srt = raw
            elif format.lower() == 'webvtt':
                srt = SubtitleHelper.__convert_web_vtt_to_srt(
                    raw)  # With Krypton and Leia VTT is supported natively
            elif format.lower() == 'ttml':
                srt = SubtitleHelper.__convert_ttml_to_srt(raw)
            elif format.lower() == 'dcsubtitle':
                srt = SubtitleHelper.__convert_dc_subtitle_to_srt(raw)
            elif format.lower() == 'json':
                srt = SubtitleHelper.__convert_json_subtitle_to_srt(raw)
            elif format.lower() == 'm3u8srt':
                srt = SubtitleHelper.__convert_m3u8_srt_to_subtitle_to_srt(
                    raw, url, proxy)
            else:
                error = "Uknown subtitle format: %s" % (format, )
                raise NotImplementedError(error)

            if replace:
                Logger.debug("Replacing SRT data: %s", replace)
                for needle in replace:
                    srt = srt.replace(needle, replace[needle])

            with io.open(local_complete_path, 'w', encoding="utf-8") as f:
                f.write(srt)

            Logger.info("Saved SRT as %s", local_complete_path)
            return local_complete_path
        except:
            Logger.error("Error handling Subtitle file: [%s]",
                         srt,
                         exc_info=True)
            return ""
Пример #2
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.contentType = "episodes"
        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 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 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
Пример #3
0
def run_plugin():
    """ Runs Retrospect as a Video Add-On """

    log_file = None

    try:
        from retroconfig import Config
        from helpers.sessionhelper import SessionHelper

        # get a logger up and running
        from logger import Logger

        # only append if there are no active sessions
        if not SessionHelper.is_session_active():
            # first call in the session, so do not append the log
            append_log_file = False
        else:
            append_log_file = True

        log_file = Logger.create_logger(
            os.path.join(Config.profileDir, Config.logFileNameAddon),
            Config.appName,
            append=append_log_file,
            dual_logger=lambda x, y=4: xbmc.log(x, y))

        from urihandler import UriHandler

        from addonsettings import AddonSettings
        AddonSettings.set_language()

        from textures import TextureHandler

        # update the loglevel
        Logger.instance().minLogLevel = AddonSettings.get_log_level()

        use_caching = AddonSettings.cache_http_responses()
        cache_dir = None
        if use_caching:
            cache_dir = Config.cacheDir

        ignore_ssl_errors = AddonSettings.ignore_ssl_errors()
        UriHandler.create_uri_handler(cache_dir=cache_dir,
                                      cookie_jar=os.path.join(
                                          Config.profileDir, "cookiejar.dat"),
                                      ignore_ssl_errors=ignore_ssl_errors)

        # start texture handler
        TextureHandler.set_texture_handler(Config, Logger.instance(),
                                           UriHandler.instance())

        # run the plugin
        import plugin
        plugin.Plugin(sys.argv[0], sys.argv[2], sys.argv[1])

        # make sure we leave no references behind
        AddonSettings.clear_cached_addon_settings_object()
        # close the log to prevent locking on next call
        Logger.instance().close_log()
        log_file = None

    except:
        if log_file:
            log_file.critical("Error running plugin", exc_info=True)
            log_file.close_log()
        raise
Пример #4
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

        """

        headers = {"accept": "application/vnd.sbs.ovp+json; version=2.0"}
        data = UriHandler.open(item.url,
                               proxy=self.proxy,
                               additional_headers=headers)

        if UriHandler.instance().status.code == 404:
            Logger.warning("No normal stream found. Trying newer method")
            new_url = item.url.replace("https://embed.kijk.nl/api/",
                                       "https://embed.kijk.nl/")
            item.url = new_url[:new_url.index("?")]
            return self.__update_embedded_video(item)

        json = JsonHelper(data)
        embed_url = json.get_value("metadata", "embedURL")
        if embed_url:
            Logger.warning(
                "Embed URL found. Using that to determine the streams.")
            item.url = embed_url
            try:
                return self.__update_embedded_video(item)
            except:
                Logger.warning("Failed to update embedded item:",
                               exc_info=True)

        use_adaptive_with_encryption = AddonSettings.use_adaptive_stream_add_on(
            with_encryption=True, channel=self)
        mpd_info = json.get_value("entitlements", "play")

        # is there MPD information in the API response?
        if mpd_info is not None:
            return self.__update_video_from_mpd(item, mpd_info,
                                                use_adaptive_with_encryption)

        # Try the plain M3u8 streams
        part = item.create_new_empty_media_part()
        m3u8_url = json.get_value("playlist")
        use_adaptive = AddonSettings.use_adaptive_stream_add_on(channel=self)

        # with the Accept: application/vnd.sbs.ovp+json; version=2.0 header, the m3u8 streams that
        # are brightcove based have an url paramter instead of an empty m3u8 file
        Logger.debug("Trying standard M3u8 streams.")
        if m3u8_url != "https://embed.kijk.nl/api/playlist/.m3u8" \
                and "hostingervice=brightcove" not in m3u8_url:
            for s, b in M3u8.get_streams_from_m3u8(m3u8_url,
                                                   self.proxy,
                                                   append_query_string=True):
                if "_enc_" in s:
                    continue

                if use_adaptive:
                    # we have at least 1 none encrypted streams
                    Logger.info("Using HLS InputStreamAddon")
                    strm = part.append_media_stream(m3u8_url, 0)
                    M3u8.set_input_stream_addon_input(strm, proxy=self.proxy)
                    item.complete = True
                    return item

                part.append_media_stream(s, b)
                item.complete = True
            return item

        Logger.warning("No M3u8 data found. Falling back to BrightCove")
        video_id = json.get_value("vpakey")
        # videoId = json.get_value("videoId") -> Not all items have a videoId
        mpd_manifest_url = "https://embed.kijk.nl/video/%s?width=868&height=491" % (
            video_id, )
        referer = "https://embed.kijk.nl/video/%s" % (video_id, )

        data = UriHandler.open(mpd_manifest_url,
                               proxy=self.proxy,
                               referer=referer)
        # First try to find an M3u8
        m3u8_urls = Regexer.do_regex('https:[^"]+.m3u8', data)
        for m3u8_url in m3u8_urls:
            m3u8_url = m3u8_url.replace("\\", "")

            # We need the actual URI to make this work, so fetch it.
            m3u8_url = UriHandler.header(m3u8_url, proxy=self.proxy)[-1]
            Logger.debug("Found direct M3u8 in brightcove data.")
            if use_adaptive:
                # we have at least 1 none encrypted streams
                Logger.info("Using HLS InputStreamAddon")
                strm = part.append_media_stream(m3u8_url, 0)
                M3u8.set_input_stream_addon_input(strm, proxy=self.proxy)
                item.complete = True
                return item

            for s, b in M3u8.get_streams_from_m3u8(m3u8_url,
                                                   self.proxy,
                                                   append_query_string=True):
                item.complete = True
                part.append_media_stream(s, b)

            return item

        return self.__update_video_from_brightcove(
            item, data, use_adaptive_with_encryption)