def get_repo_resourceaddons(filterstr=""):
    '''helper to retrieve all available resource addons on the kodi repo'''
    result = []
    simplecache = SimpleCache()
    for item in xbmcvfs.listdir("addons://all/kodi.resource.images/")[1]:
        if not filterstr or item.lower().startswith(filterstr.lower()):
            addoninfo = get_repo_addoninfo(item, simplecache)
            if not addoninfo.get("name"):
                addoninfo = {"addonid": item, "name": item, "author": ""}
                addoninfo["thumbnail"] = "http://mirrors.kodi.tv/addons/krypton/%s/icon.png" % item
            addoninfo["path"] = "resource://%s/" % item
            result.append(addoninfo)
    simplecache.close()
    return result
Exemplo n.º 2
0
def get_repo_resourceaddons(filterstr=""):
    '''helper to retrieve all available resource addons on the kodi repo'''
    result = []
    simplecache = SimpleCache()
    for item in xbmcvfs.listdir("addons://all/kodi.resource.images/")[1]:
        if not filterstr or item.lower().startswith(filterstr.lower()):
            addoninfo = get_repo_addoninfo(item, simplecache)
            if not addoninfo.get("name"):
                addoninfo = {"addonid": item, "name": item, "author": ""}
                addoninfo["thumbnail"] = "http://mirrors.kodi.tv/addons/krypton/%s/icon.png" % item
            addoninfo["path"] = "resource://%s/" % item
            result.append(addoninfo)
    simplecache.close()
    return result
class PluginContent:
    """Hidden plugin entry point providing some helper features"""

    params = {}
    win = None

    def __init__(self):
        self.cache = SimpleCache()
        self.kodi_db = KodiDb()
        self.win = xbmcgui.Window(10000)
        try:
            self.params = dict(urlparse.parse_qsl(sys.argv[2].replace("?", "").lower().decode("utf-8")))
            log_msg("plugin called with parameters: %s" % self.params)
            self.main()
        except Exception as exc:
            log_exception(__name__, exc)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

        # cleanup when done processing
        self.close()

    def close(self):
        """Cleanup Kodi Cpython instances"""
        self.cache.close()
        del self.win

    def main(self):
        """main action, load correct function"""
        action = self.params.get("action", "")
        if self.win.getProperty("SkinHelperShutdownRequested"):
            # do not proceed if kodi wants to exit
            log_msg("%s --> Not forfilling request: Kodi is exiting" % __name__, xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
        else:
            try:
                if hasattr(self.__class__, action):
                    # launch module for action provided by this plugin
                    getattr(self, action)()
                else:
                    # legacy (widget) path called !!!
                    self.load_widget()
            except Exception as exc:
                log_exception(__name__, exc)

    def load_widget(self):
        """legacy entrypoint called (widgets are moved to seperate addon), start redirect..."""
        action = self.params.get("action", "")
        newaddon = "script.skin.helper.widgets"
        log_msg(
            "Deprecated method: %s. Please reassign your widgets to get rid of this message. -"
            "This automatic redirect will be removed in the future" % (action),
            xbmc.LOGWARNING,
        )
        paramstring = ""
        for key, value in self.params.iteritems():
            paramstring += ",%s=%s" % (key, value)
        if xbmc.getCondVisibility("System.HasAddon(%s)" % newaddon):
            # TEMP !!! for backwards compatability reasons only - to be removed in the near future!!
            import imp

            addon = xbmcaddon.Addon(newaddon)
            addon_path = addon.getAddonInfo("path").decode("utf-8")
            imp.load_source("plugin", os.path.join(addon_path, "plugin.py"))
            from plugin import main

            main.Main()
            del addon
        else:
            # trigger install of the addon
            if KODI_VERSION >= 17:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    def playchannel(self):
        """play channel from widget helper"""
        params = {"item": {"channelid": int(self.params["channelid"])}}
        self.kodi_db.set_json("Player.Open", params)

    def playrecording(self):
        """retrieve the recording and play to get resume working"""
        recording = self.kodi_db.recording(self.params["recordingid"])
        params = {"item": {"recordingid": recording["recordingid"]}}
        self.kodi_db.set_json("Player.Open", params)
        # manually seek because passing resume to the player json cmd doesn't seem to work
        if recording["resume"].get("position"):
            for i in range(50):
                if xbmc.getCondVisibility("Player.HasVideo"):
                    break
                xbmc.sleep(50)
            xbmc.Player().seekTime(recording["resume"].get("position"))

    def launch(self):
        """launch any builtin action using a plugin listitem"""
        if "runscript" in self.params["path"]:
            self.params["path"] = self.params["path"].replace("?", ",")
        xbmc.executebuiltin(self.params["path"])

    def playalbum(self):
        """helper to play an entire album"""
        xbmc.executeJSONRPC(
            '{ "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "albumid": %d } }, "id": 1 }'
            % int(self.params["albumid"])
        )

    def smartshortcuts(self):
        """called from skinshortcuts to retrieve listing of all smart shortcuts"""
        import skinshortcuts

        skinshortcuts.get_smartshortcuts(self.params.get("path", ""))

    @staticmethod
    def backgrounds():
        """called from skinshortcuts to retrieve listing of all backgrounds"""
        import skinshortcuts

        skinshortcuts.get_backgrounds()

    def widgets(self):
        """called from skinshortcuts to retrieve listing of all widgetss"""
        import skinshortcuts

        skinshortcuts.get_widgets(self.params.get("path", ""), self.params.get("sublevel", ""))

    def resourceimages(self):
        """retrieve listing of specific resource addon images"""
        from resourceaddons import get_resourceimages

        addontype = self.params.get("addontype", "")
        for item in get_resourceimages(addontype, True):
            listitem = xbmcgui.ListItem(item[0], label2=item[2], path=item[1], iconImage=item[3])
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item[1], listitem=listitem, isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def extrafanart(self):
        """helper to display extrafanart in multiimage control in the skin"""
        fanarts = eval(self.params["fanarts"])
        # process extrafanarts
        for item in fanarts:
            listitem = xbmcgui.ListItem(item, path=item)
            listitem.setProperty("mimetype", "image/jpeg")
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=item, listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def genrebackground(self):
        """helper to display images for a specific genre in multiimage control in the skin"""
        genre = self.params.get("genre").split(".")[0]
        arttype = self.params.get("arttype", "fanart")
        randomize = self.params.get("random", "false") == "true"
        mediatype = self.params.get("mediatype", "movies")
        if genre and genre != "..":
            filters = [{"operator": "is", "field": "genre", "value": genre}]
            if randomize:
                sort = {"method": "random", "order": "descending"}
            else:
                sort = None
            items = getattr(self.kodi_db, mediatype)(sort=sort, filters=filters, limits=(0, 50))
            for item in items:
                image = get_clean_image(item["art"].get(arttype, ""))
                if image:
                    image = get_clean_image(item["art"][arttype])
                    listitem = xbmcgui.ListItem(image, path=image)
                    listitem.setProperty("mimetype", "image/jpeg")
                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=image, listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcastmedia(self):
        """helper to display get all media for a specific actor"""
        name = self.params.get("name")
        if name:
            all_items = self.kodi_db.castmedia(name)
            all_items = process_method_on_list(self.kodi_db.prepare_listitem, all_items)
            all_items = process_method_on_list(self.kodi_db.create_listitem, all_items)
            xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items, len(all_items))
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcast(self):
        """helper to get all cast for a given media item"""
        db_id = None
        all_cast = []
        all_cast_names = list()
        cache_str = ""
        download_thumbs = self.params.get("downloadthumbs", "") == "true"
        extended_cast_action = self.params.get("castaction", "") == "extendedinfo"
        tmdb = Tmdb()
        movie = self.params.get("movie")
        tvshow = self.params.get("tvshow")
        episode = self.params.get("episode")
        movieset = self.params.get("movieset")

        try:  # try to parse db_id
            if movieset:
                cache_str = "movieset.castcache-%s-%s" % (self.params["movieset"], download_thumbs)
                db_id = int(movieset)
            elif tvshow:
                cache_str = "tvshow.castcache-%s-%s" % (self.params["tvshow"], download_thumbs)
                db_id = int(tvshow)
            elif movie:
                cache_str = "movie.castcache-%s-%s" % (self.params["movie"], download_thumbs)
                db_id = int(movie)
            elif episode:
                cache_str = "episode.castcache-%s-%s" % (self.params["episode"], download_thumbs)
                db_id = int(episode)
        except Exception:
            pass

        cachedata = self.cache.get(cache_str)
        if cachedata:
            # get data from cache
            all_cast = cachedata
        else:
            # retrieve data from json api...
            if movie and db_id:
                all_cast = self.kodi_db.movie(db_id)["cast"]
            elif movie and not db_id:
                filters = [{"operator": "is", "field": "title", "value": movie}]
                result = self.kodi_db.movies(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif tvshow and db_id:
                all_cast = self.kodi_db.tvshow(db_id)["cast"]
            elif tvshow and not db_id:
                filters = [{"operator": "is", "field": "title", "value": tvshow}]
                result = self.kodi_db.tvshows(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif episode and db_id:
                all_cast = self.kodi_db.episode(db_id)["cast"]
            elif episode and not db_id:
                filters = [{"operator": "is", "field": "title", "value": episode}]
                result = self.kodi_db.episodes(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif movieset:
                if not db_id:
                    for item in self.kodi_db.moviesets():
                        if item["title"].lower() == movieset.lower():
                            db_id = item["setid"]
                if db_id:
                    json_result = self.kodi_db.movieset(db_id, include_set_movies_fields=["cast"])
                    if "movies" in json_result:
                        for movie in json_result["movies"]:
                            all_cast += movie["cast"]

            # optional: download missing actor thumbs
            if all_cast and download_thumbs:
                for cast in all_cast:
                    if cast.get("thumbnail"):
                        cast["thumbnail"] = get_clean_image(cast.get("thumbnail"))
                    if not cast.get("thumbnail"):
                        artwork = tmdb.get_actor(cast["name"])
                        cast["thumbnail"] = artwork.get("thumb", "")
            # lookup tmdb if item is requested that is not in local db
            if not all_cast:
                tmdbdetails = {}
                if movie and not db_id:
                    tmdbdetails = tmdb.search_movie(movie)
                elif tvshow and not db_id:
                    tmdbdetails = tmdb.search_tvshow(tvshow)
                if tmdbdetails.get("cast"):
                    all_cast = tmdbdetails["cast"]
            # save to cache
            self.cache.set(cache_str, all_cast)

        # process listing with the results...
        for cast in all_cast:
            if cast.get("name") not in all_cast_names:
                liz = xbmcgui.ListItem(label=cast.get("name"), label2=cast.get("role"), iconImage=cast.get("thumbnail"))
                if extended_cast_action:
                    url = "RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % cast.get("name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % url
                    is_folder = False
                else:
                    url = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % cast.get("name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % urlencode(url)
                    is_folder = False
                all_cast_names.append(cast.get("name"))
                liz.setThumbnailImage(cast.get("thumbnail"))
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=liz, isFolder=is_folder)
        xbmcplugin.endOfDirectory(int(sys.argv[1]))

    @staticmethod
    def alphabet():
        """display an alphabet scrollbar in listings"""
        all_letters = []
        if xbmc.getInfoLabel("Container.NumItems"):
            for i in range(int(xbmc.getInfoLabel("Container.NumItems"))):
                all_letters.append(xbmc.getInfoLabel("Listitem(%s).SortLetter" % i).upper())
            start_number = ""
            for number in ["2", "3", "4", "5", "6", "7", "8", "9"]:
                if number in all_letters:
                    start_number = number
                    break
            for letter in [
                start_number,
                "A",
                "B",
                "C",
                "D",
                "E",
                "F",
                "G",
                "H",
                "I",
                "J",
                "K",
                "L",
                "M",
                "N",
                "O",
                "P",
                "Q",
                "R",
                "S",
                "T",
                "U",
                "V",
                "W",
                "X",
                "Y",
                "Z",
            ]:
                if letter == start_number:
                    label = "#"
                else:
                    label = letter
                listitem = xbmcgui.ListItem(label=label)
                if letter not in all_letters:
                    lipath = "noop"
                    listitem.setProperty("NotAvailable", "true")
                else:
                    lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter
                xbmcplugin.addDirectoryItem(int(sys.argv[1]), lipath, listitem, isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def alphabetletter(self):
        """used with the alphabet scrollbar to jump to a letter"""
        if KODI_VERSION > 16:
            xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=False, listitem=xbmcgui.ListItem())
        letter = self.params.get("letter", "").upper()
        jumpcmd = ""
        if letter in ["A", "B", "C", "2"]:
            jumpcmd = "2"
        elif letter in ["D", "E", "F", "3"]:
            jumpcmd = "3"
        elif letter in ["G", "H", "I", "4"]:
            jumpcmd = "4"
        elif letter in ["J", "K", "L", "5"]:
            jumpcmd = "5"
        elif letter in ["M", "N", "O", "6"]:
            jumpcmd = "6"
        elif letter in ["P", "Q", "R", "S", "7"]:
            jumpcmd = "7"
        elif letter in ["T", "U", "V", "8"]:
            jumpcmd = "8"
        elif letter in ["W", "X", "Y", "Z", "9"]:
            jumpcmd = "9"
        if jumpcmd:
            xbmc.executebuiltin("SetFocus(50)")
            for i in range(40):
                xbmc.executeJSONRPC(
                    '{ "jsonrpc": "2.0", "method": "Input.ExecuteAction",\
                    "params": { "action": "jumpsms%s" }, "id": 1 }'
                    % (jumpcmd)
                )
                xbmc.sleep(50)
                if xbmc.getInfoLabel("ListItem.Sortletter").upper() == letter:
                    break
Exemplo n.º 4
0
class MetadataUtils(object):
    '''
        Provides all kind of mediainfo for kodi media, returned as dict with details
    '''
    _audiodb, _addon, _close_called, _omdb, _kodidb, _tmdb, _fanarttv, _channellogos = [
        None
    ] * 8
    _imdb, _google, _studiologos, _animatedart, _thetvdb, _musicart, _pvrart, _lastfm = [
        None
    ] * 8
    _studiologos_path, _process_method_on_list, _detect_plugin_content, _get_streamdetails = [
        None
    ] * 4
    _extend_dict, _get_clean_image, _get_duration, _get_extrafanart, _get_extraposter, _get_moviesetdetails = [
        None
    ] * 6
    cache = None

    def __init__(self):
        '''Initialize and load all our helpers'''
        self.cache = SimpleCache()
        log_msg("Initialized")

    @use_cache(14)
    def get_extrafanart(self, file_path):
        '''helper to retrieve the extrafanart path for a kodi media item'''
        log_msg("metadatautils get_extrafanart called for %s" % file_path)
        if not self._get_extrafanart:
            from helpers.extrafanart import get_extrafanart
            self._get_extrafanart = get_extrafanart
        return self._get_extrafanart(file_path)

    @use_cache(14)
    def get_extraposter(self, file_path):
        '''helper to retrieve the extraposter path for a kodi media item'''
        if not self._get_extraposter:
            from helpers.extraposter import get_extraposter
            self._get_extraposter = get_extraposter
        return self._get_extraposter(file_path)

    def get_music_artwork(self,
                          artist,
                          album="",
                          track="",
                          disc="",
                          ignore_cache=False,
                          flush_cache=False):
        '''method to get music artwork for the goven artist/album/song'''
        return self.musicart.get_music_artwork(artist,
                                               album,
                                               track,
                                               disc,
                                               ignore_cache=ignore_cache,
                                               flush_cache=flush_cache)

    def music_artwork_options(self, artist, album="", track="", disc=""):
        '''options for music metadata for specific item'''
        return self.musicart.music_artwork_options(artist, album, track, disc)

    @use_cache(7)
    def get_extended_artwork(self,
                             imdb_id="",
                             tvdb_id="",
                             tmdb_id="",
                             media_type=""):
        '''get extended artwork for the given imdbid or tvdbid'''
        result = None
        if "movie" in media_type and tmdb_id:
            result = self.fanarttv.movie(tmdb_id)
        elif "movie" in media_type and imdb_id:
            # prefer local artwork
            local_details = self.kodidb.movie_by_imdbid(imdb_id)
            if local_details:
                result = local_details["art"]
            result = self.extend_dict(result, self.fanarttv.movie(imdb_id))
        elif media_type in ["tvshow", "tvshows", "seasons", "episodes"]:
            if not tvdb_id:
                if imdb_id and not imdb_id.startswith("tt"):
                    tvdb_id = imdb_id
                elif imdb_id:
                    tvdb_id = self.thetvdb.get_series_by_imdb_id(imdb_id).get(
                        "tvdb_id")
            if tvdb_id:
                # prefer local artwork
                local_details = self.kodidb.tvshow_by_imdbid(tvdb_id)
                if local_details:
                    result = local_details["art"]
                elif imdb_id and imdb_id != tvdb_id:
                    local_details = self.kodidb.tvshow_by_imdbid(imdb_id)
                    if local_details:
                        result = local_details["art"]
                result = self.extend_dict(result,
                                          self.fanarttv.tvshow(tvdb_id))
        # add additional art with special path
        if result:
            result = {"art": result}
            for arttype in [
                    "fanarts", "posters", "clearlogos", "banners", "discarts",
                    "cleararts", "characterarts"
            ]:
                if result["art"].get(arttype):
                    result["art"][arttype] = "plugin://script.skin.helper.service/"\
                        "?action=extrafanart&fanarts=%s" % quote_plus(repr(result["art"][arttype]))
        return result

    @use_cache(90)
    def get_tmdb_details(self,
                         imdb_id="",
                         tvdb_id="",
                         title="",
                         year="",
                         media_type="",
                         preftype="",
                         manual_select=False,
                         ignore_cache=False):
        '''returns details from tmdb'''
        result = {}
        if imdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                imdb_id, "imdb_id")
        elif tvdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                tvdb_id, "tvdb_id")
        elif title and media_type in ["movies", "setmovies", "movie"]:
            result = self.tmdb.search_movie(title,
                                            year,
                                            manual_select=manual_select,
                                            ignore_cache=ignore_cache)
        elif title and media_type in ["tvshows", "tvshow"]:
            result = self.tmdb.search_tvshow(title,
                                             year,
                                             manual_select=manual_select,
                                             ignore_cache=ignore_cache)
        elif title:
            result = self.tmdb.search_video(title,
                                            year,
                                            preftype=preftype,
                                            manual_select=manual_select,
                                            ignore_cache=ignore_cache)
        if result and result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result and result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    @use_cache(90)
    def get_moviesetdetails(self, title, set_id):
        '''get a nicely formatted dict of the movieset details which we can for example set as window props'''
        # get details from tmdb
        if not self._get_moviesetdetails:
            from helpers.moviesetdetails import get_moviesetdetails
            self._get_moviesetdetails = get_moviesetdetails
        return self._get_moviesetdetails(self, title, set_id)

    @use_cache(14)
    def get_streamdetails(self, db_id, media_type, ignore_cache=False):
        '''get a nicely formatted dict of the streamdetails'''
        if not self._get_streamdetails:
            from helpers.streamdetails import get_streamdetails
            self._get_streamdetails = get_streamdetails
        return self._get_streamdetails(self.kodidb, db_id, media_type)

    def get_pvr_artwork(self,
                        title,
                        channel="",
                        genre="",
                        manual_select=False,
                        ignore_cache=False):
        '''get artwork and mediadetails for PVR entries'''
        return self.pvrart.get_pvr_artwork(title,
                                           channel,
                                           genre,
                                           manual_select=manual_select,
                                           ignore_cache=ignore_cache)

    def pvr_artwork_options(self, title, channel="", genre=""):
        '''options for pvr metadata for specific item'''
        return self.pvrart.pvr_artwork_options(title, channel, genre)

    def get_channellogo(self, channelname):
        '''get channellogo for the given channel name'''
        return self.channellogos.get_channellogo(channelname)

    def get_studio_logo(self, studio):
        '''get studio logo for the given studio'''
        # dont use cache at this level because of changing logospath
        return self.studiologos.get_studio_logo(studio, self.studiologos_path)

    @property
    def studiologos_path(self):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        return self._studiologos_path

    @studiologos_path.setter
    def studiologos_path(self, value):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        self._studiologos_path = value

    def get_animated_artwork(self,
                             imdb_id,
                             manual_select=False,
                             ignore_cache=False):
        '''get animated artwork, perform extra check if local version still exists'''
        artwork = self.animatedart.get_animated_artwork(
            imdb_id, manual_select=manual_select, ignore_cache=ignore_cache)
        if not (manual_select or ignore_cache):
            refresh_needed = False
            if artwork.get("animatedposter") and not xbmcvfs.exists(
                    artwork["animatedposter"]):
                refresh_needed = True
            if artwork.get("animatedfanart") and not xbmcvfs.exists(
                    artwork["animatedfanart"]):
                refresh_needed = True

        return {"art": artwork}

    @use_cache(90)
    def get_omdb_info(self, imdb_id="", title="", year="", content_type=""):
        '''Get (kodi compatible formatted) metadata from OMDB, including Rotten tomatoes details'''
        title = title.split(" (")[0]  # strip year appended to title
        result = {}
        if imdb_id:
            result = self.omdb.get_details_by_imdbid(imdb_id)
        elif title and content_type in [
                "seasons", "season", "episodes", "episode", "tvshows", "tvshow"
        ]:
            result = self.omdb.get_details_by_title(title, "", "tvshows")
        elif title and year:
            result = self.omdb.get_details_by_title(title, year, content_type)
        if result and result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result and result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    def get_top250_rating(self, imdb_id):
        '''get the position in the IMDB top250 for the given IMDB ID'''
        return self.imdb.get_top250_rating(imdb_id)

    @use_cache(14)
    def get_duration(self, duration):
        '''helper to get a formatted duration'''
        if not self._get_duration:
            from helpers.utils import get_duration
            self._get_duration = get_duration
        if sys.version_info.major == 3:
            if isinstance(duration, str) and ":" in duration:
                dur_lst = duration.split(":")
                return {
                    "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]),
                    "Duration.Hours": dur_lst[0],
                    "Duration.Minutes": dur_lst[1],
                    "Runtime": int(dur_lst[0]) * 60 + int(dur_lst[1]),
                }
            else:
                return self._get_duration(duration)
        else:
            if isinstance(duration, (str)) and ":" in duration:
                dur_lst = duration.split(":")
                return {
                    "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]),
                    "Duration.Hours": dur_lst[0],
                    "Duration.Minutes": dur_lst[1],
                    "Runtime": str((int(dur_lst[0]) * 60) + int(dur_lst[1])),
                }
            else:
                return self._get_duration(duration)

    @use_cache(2)
    def get_tvdb_details(self, imdbid="", tvdbid=""):
        '''get metadata from tvdb by providing a tvdbid or tmdbid'''
        result = {}
        self.thetvdb.days_ahead = 365
        if not tvdbid and imdbid and not imdbid.startswith("tt"):
            # assume imdbid is actually a tvdbid...
            tvdbid = imdbid
        if tvdbid:
            result = self.thetvdb.get_series(tvdbid)
        elif imdbid:
            result = self.thetvdb.get_series_by_imdb_id(imdbid)
        if result:
            if result["status"] == "Continuing":
                # include next episode info
                result["nextepisode"] = self.thetvdb.get_nextaired_episode(
                    result["tvdb_id"])
            # include last episode info
            result["lastepisode"] = self.thetvdb.get_last_episode_for_series(
                result["tvdb_id"])
            result["status"] = self.translate_string(result["status"])
            if result.get("runtime"):
                result["runtime"] = result["runtime"] / 60
                result.update(self.get_duration(result["runtime"]))
        return result

    @use_cache(90)
    def get_imdbtvdb_id(self,
                        title,
                        content_type,
                        year="",
                        imdbid="",
                        tvshowtitle=""):
        '''try to figure out the imdbnumber and/or tvdbid'''
        tvdbid = ""
        if content_type in ["seasons", "episodes"] or tvshowtitle:
            title = tvshowtitle
            content_type = "tvshows"
        if imdbid and not imdbid.startswith("tt"):
            if content_type in ["tvshows", "seasons", "episodes"]:
                tvdbid = imdbid
                imdbid = ""
        if not imdbid and year:
            omdb_info = self.get_omdb_info("", title, year, content_type)
            if omdb_info:
                imdbid = omdb_info.get("imdbnumber", "")
        if not imdbid:
            # repeat without year
            omdb_info = self.get_omdb_info("", title, "", content_type)
            if omdb_info:
                imdbid = omdb_info.get("imdbnumber", "")
        # return results
        return (imdbid, tvdbid)

    def translate_string(self, _str):
        '''translate the received english string from the various sources like tvdb, tmbd etc'''
        translation = _str
        _str = _str.lower()
        if "continuing" in _str:
            translation = self.addon.getLocalizedString(32037)
        elif "ended" in _str:
            translation = self.addon.getLocalizedString(32038)
        elif "released" in _str:
            translation = self.addon.getLocalizedString(32040)
        return translation

    def process_method_on_list(self, *args, **kwargs):
        '''expose our process_method_on_list method to public'''
        if not self._process_method_on_list:
            from helpers.utils import process_method_on_list
            self._process_method_on_list = process_method_on_list
        return self._process_method_on_list(*args, **kwargs)

    def detect_plugin_content(self, *args, **kwargs):
        '''expose our detect_plugin_content method to public'''
        if not self._detect_plugin_content:
            from helpers.utils import detect_plugin_content
            self._detect_plugin_content = detect_plugin_content
        return self._detect_plugin_content(*args, **kwargs)

    def extend_dict(self, *args, **kwargs):
        '''expose our extend_dict method to public'''
        if not self._extend_dict:
            from helpers.utils import extend_dict
            self._extend_dict = extend_dict
        return self._extend_dict(*args, **kwargs)

    def get_clean_image(self, *args, **kwargs):
        '''expose our get_clean_image method to public'''
        if not self._get_clean_image:
            from helpers.utils import get_clean_image
            self._get_clean_image = get_clean_image
        return self._get_clean_image(*args, **kwargs)

    @property
    def omdb(self):
        '''public omdb object - for lazy loading'''
        if not self._omdb:
            from helpers.omdb import Omdb
            self._omdb = Omdb(self.cache)
        return self._omdb

    @property
    def kodidb(self):
        '''public kodidb object - for lazy loading'''
        if not self._kodidb:
            from helpers.kodidb import KodiDb
            self._kodidb = KodiDb(self.cache)
        return self._kodidb

    @property
    def tmdb(self):
        '''public Tmdb object - for lazy loading'''
        if not self._tmdb:
            from helpers.tmdb import Tmdb
            self._tmdb = Tmdb(self.cache)
        return self._tmdb

    @property
    def fanarttv(self):
        '''public FanartTv object - for lazy loading'''
        if not self._fanarttv:
            from helpers.fanarttv import FanartTv
            self._fanarttv = FanartTv(self.cache)
        return self._fanarttv

    @property
    def channellogos(self):
        '''public ChannelLogos object - for lazy loading'''
        if not self._channellogos:
            from helpers.channellogos import ChannelLogos
            self._channellogos = ChannelLogos(self.kodidb)
        return self._channellogos

    @property
    def imdb(self):
        '''public Imdb object - for lazy loading'''
        if not self._imdb:
            from helpers.imdb import Imdb
            self._imdb = Imdb(self.cache)
        return self._imdb

    @property
    def google(self):
        '''public GoogleImages object - for lazy loading'''
        if not self._google:
            from helpers.google import GoogleImages
            self._google = GoogleImages(self.cache)
        return self._google

    @property
    def studiologos(self):
        '''public StudioLogos object - for lazy loading'''
        if not self._studiologos:
            from helpers.studiologos import StudioLogos
            self._studiologos = StudioLogos(self.cache)
        return self._studiologos

    @property
    def animatedart(self):
        '''public AnimatedArt object - for lazy loading'''
        if not self._animatedart:
            from helpers.animatedart import AnimatedArt
            self._animatedart = AnimatedArt(self.cache, self.kodidb)
        return self._animatedart

    @property
    def thetvdb(self):
        '''public TheTvDb object - for lazy loading'''
        if not self._thetvdb:
            from thetvdb import TheTvDb
            self._thetvdb = TheTvDb()
        return self._thetvdb

    @property
    def musicart(self):
        '''public MusicArtwork object - for lazy loading'''
        if not self._musicart:
            from helpers.musicartwork import MusicArtwork
            self._musicart = MusicArtwork(self)
        return self._musicart

    @property
    def pvrart(self):
        '''public PvrArtwork object - for lazy loading'''
        if not self._pvrart:
            from helpers.pvrartwork import PvrArtwork
            self._pvrart = PvrArtwork(self)
        return self._pvrart

    @property
    def addon(self):
        '''public Addon object - for lazy loading'''
        if not self._addon:
            import xbmcaddon
            self._addon = xbmcaddon.Addon(ADDON_ID)
        return self._addon

    @property
    def lastfm(self):
        '''public LastFM object - for lazy loading'''
        if not self._lastfm:
            from helpers.lastfm import LastFM
            self._lastfm = LastFM()
        return self._lastfm

    @property
    def audiodb(self):
        '''public TheAudioDb object - for lazy loading'''
        if not self._audiodb:
            from helpers.theaudiodb import TheAudioDb
            self._audiodb = TheAudioDb()
        return self._audiodb

    def close(self):
        '''Cleanup instances'''
        self._close_called = True
        if self.cache:
            self.cache.close()
            del self.cache
        if self._addon:
            del self._addon
        if self._thetvdb:
            del self._thetvdb
        log_msg("Exited")

    def __del__(self):
        '''make sure close is called'''
        if not self._close_called:
            self.close()
Exemplo n.º 5
0
class ListItemMonitor(threading.Thread):
    '''Our main class monitoring the kodi listitems and providing additional information'''
    event = None
    exit = False
    delayed_task_interval = 1795
    listitem_details = {}
    all_window_props = {}
    cur_listitem = ""
    last_folder = ""
    last_listitem = ""
    foldercontent = {}
    screensaver_setting = None
    screensaver_disabled = False
    lookup_busy = {}
    enable_extendedart = False
    enable_musicart = False
    enable_animatedart = False
    enable_extrafanart = False
    enable_extraposter = False
    enable_pvrart = False
    enable_forcedviews = False

    def __init__(self, *args, **kwargs):
        self.cache = SimpleCache()
        self.metadatautils = kwargs.get("metadatautils")
        self.win = kwargs.get("win")
        self.kodimonitor = kwargs.get("monitor")
        self.event = threading.Event()
        threading.Thread.__init__(self, *args)

    def stop(self):
        '''called when the thread has to stop working'''
        log_msg("ListItemMonitor - stop called")
        self.exit = True
        self.cache.close()
        self.event.set()
        self.event.clear()
        self.join(1)

    def run(self):
        '''our main loop monitoring the listitem and folderpath changes'''
        log_msg("ListItemMonitor - started")
        self.get_settings()

        while not self.exit:

            # check screensaver and OSD
            self.check_screensaver()
            self.check_osd()

            # do some background stuff every 30 minutes
            if (self.delayed_task_interval >= 1800) and not self.exit:
                thread.start_new_thread(self.do_background_work, ())
                self.delayed_task_interval = 0

            # skip if any of the artwork context menus is opened
            if self.win.getProperty("SkinHelper.Artwork.ManualLookup"):
                self.reset_win_props()
                self.last_listitem = ""
                self.listitem_details = {}
                self.kodimonitor.waitForAbort(3)
                self.delayed_task_interval += 3

            # skip when modal dialogs are opened (e.g. textviewer in musicinfo dialog)
            elif getCondVisibility(
                    "Window.IsActive(DialogSelect.xml) | Window.IsActive(progressdialog) | "
                    "Window.IsActive(contextmenu) | Window.IsActive(busydialog)"
            ):
                self.kodimonitor.waitForAbort(2)
                self.delayed_task_interval += 2
                self.last_listitem = ""

            # skip when container scrolling
            elif getCondVisibility(
                    "Container.OnScrollNext | Container.OnScrollPrevious | Container.Scrolling"
            ):
                self.kodimonitor.waitForAbort(1)
                self.delayed_task_interval += 1
                self.last_listitem = ""

            # media window is opened or widgetcontainer set - start listitem monitoring!
            elif getCondVisibility(
                    "Window.IsMedia | "
                    "!IsEmpty(Window(Home).Property(SkinHelper.WidgetContainer))"
            ):
                self.monitor_listitem()
                self.kodimonitor.waitForAbort(0.15)
                self.delayed_task_interval += 0.15

            # flush any remaining window properties
            elif self.all_window_props:
                self.reset_win_props()
                self.win.clearProperty("SkinHelper.ContentHeader")
                self.win.clearProperty("contenttype")
                self.win.clearProperty("curlistitem")
                self.last_listitem = ""

            # other window active - do nothing
            else:
                self.kodimonitor.waitForAbort(1)
                self.delayed_task_interval += 1

    def get_settings(self):
        '''collect our skin settings that control the monitoring'''
        self.enable_extendedart = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnableExtendedArt)") == 1
        self.enable_musicart = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnableMusicArt)") == 1
        self.enable_animatedart = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnableAnimatedPosters)") == 1
        self.enable_extrafanart = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnableExtraFanart)") == 1
        self.enable_extraposter = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnableExtraPoster)") == 1
        self.enable_pvrart = getCondVisibility(
            "Skin.HasSetting(SkinHelper.EnablePVRThumbs) + PVR.HasTVChannels"
        ) == 1
        self.enable_forcedviews = getCondVisibility(
            "Skin.HasSetting(SkinHelper.ForcedViews.Enabled)") == 1
        studiologos_path = xbmc.getInfoLabel(
            "Skin.String(SkinHelper.StudioLogos.Path)").decode("utf-8")
        if studiologos_path != self.metadatautils.studiologos_path:
            self.listitem_details = {}
            self.metadatautils.studiologos_path = studiologos_path
        # set additional window props to control contextmenus as using the skinsetting gives unreliable results
        for skinsetting in [
                "EnableAnimatedPosters", "EnableMusicArt", "EnablePVRThumbs"
        ]:
            if getCondVisibility("Skin.HasSetting(SkinHelper.%s)" %
                                 skinsetting):
                self.win.setProperty("SkinHelper.%s" % skinsetting, "enabled")
            else:
                self.win.clearProperty("SkinHelper.%s" % skinsetting)

    def monitor_listitem(self):
        '''Monitor listitem details'''

        cur_folder, cont_prefix = self.get_folderandprefix()
        # identify current listitem - prefer parent folder (tvshows, music)
        cur_listitem = xbmc.getInfoLabel(
            "$INFO[%sListItem.TvshowTitle]$INFO[%sListItem.Artist]$INFO[%sListItem.Album]"
            % (cont_prefix, cont_prefix, cont_prefix)).decode('utf-8')
        if not cur_listitem:
            # fallback to generic approach
            cur_listitem = xbmc.getInfoLabel(
                "$INFO[%sListItem.Label]$INFO[%sListItem.DBID]$INFO[%sListItem.Title]"
                % (cont_prefix, cont_prefix, cont_prefix)).decode('utf-8')

        if self.exit:
            return

        # perform actions if the container path has changed
        if cur_folder != self.last_folder and not (
                cur_listitem and cur_listitem == self.last_listitem):
            self.reset_win_props()
            self.get_settings()
            self.last_folder = cur_folder
            self.last_listitem = None
            content_type = self.get_content_type(cur_folder, cur_listitem,
                                                 cont_prefix)
            # additional actions to perform when we have a valid contenttype and no widget container
            if not cont_prefix and content_type:
                self.set_forcedview(content_type)
                self.set_content_header(content_type)
        else:
            content_type = self.get_content_type(cur_folder, cur_listitem,
                                                 cont_prefix)

        if self.exit:
            return

        # only perform actions when the listitem has actually changed
        if cur_listitem != self.last_listitem:
            self.last_listitem = cur_listitem
            self.win.setProperty("curlistitem", cur_listitem)
            if cur_listitem and cur_listitem != "..":
                # set listitem details in background thread
                thread.start_new_thread(
                    self.set_listitem_details,
                    (cur_listitem, content_type, cont_prefix))

    def get_folderandprefix(self):
        '''get the current folder and prefix'''
        cur_folder = ""
        cont_prefix = ""
        try:
            widget_container = self.win.getProperty(
                "SkinHelper.WidgetContainer").decode('utf-8')
            if getCondVisibility("Window.IsActive(movieinformation)"):
                cont_prefix = ""
                cur_folder = xbmc.getInfoLabel(
                    "$INFO[Window.Property(xmlfile)]$INFO[Container.FolderPath]"
                    "$INFO[Container.NumItems]$INFO[Container.Content]"
                ).decode('utf-8')
            elif widget_container:
                cont_prefix = "Container(%s)." % widget_container
                cur_folder = xbmc.getInfoLabel(
                    "widget-%s-$INFO[Container(%s).NumItems]-$INFO[Container(%s).ListItemAbsolute(1).Label]"
                    % (widget_container, widget_container,
                       widget_container)).decode('utf-8')
            else:
                cont_prefix = ""
                cur_folder = xbmc.getInfoLabel(
                    "$INFO[Window.Property(xmlfile)]$INFO[Container.FolderPath]$INFO[Container.NumItems]$INFO[Container.Content]"
                ).decode('utf-8')
        except Exception as exc:
            log_exception(__name__, exc)
            cur_folder = ""
            cont_prefix = ""
        return (cur_folder, cont_prefix)

    def get_content_type(self, cur_folder, cur_listitem, cont_prefix):
        '''get contenttype for current folder'''
        content_type = ""
        if cur_folder in self.foldercontent:
            content_type = self.foldercontent[cur_folder]
        elif cur_folder and cur_listitem:
            # always wait for the content_type because some listings can be slow
            for i in range(20):
                content_type = get_current_content_type(cont_prefix)
                if self.exit:
                    return ""
                if content_type:
                    break
                else:
                    xbmc.sleep(250)
            self.foldercontent[cur_folder] = content_type
        self.win.setProperty("contenttype", content_type)
        return content_type

    def check_screensaver(self):
        '''Allow user to disable screensaver on fullscreen music playback'''
        if getCondVisibility(
                "Window.IsActive(visualisation) + Skin.HasSetting(SkinHelper.DisableScreenSaverOnFullScreenMusic)"
        ):
            if not self.screensaver_disabled:
                # disable screensaver when fullscreen music active
                self.screensaver_disabled = True
                screensaver_setting = kodi_json(
                    'Settings.GetSettingValue',
                    '{"setting":"screensaver.mode"}')
                if screensaver_setting:
                    self.screensaver_setting = screensaver_setting
                    kodi_json('Settings.SetSettingValue', {
                        "setting": "screensaver.mode",
                        "value": None
                    })
                    log_msg(
                        "Disabled screensaver while fullscreen music playback - previous setting: %s"
                        % self.screensaver_setting, xbmc.LOGNOTICE)
        elif self.screensaver_disabled and self.screensaver_setting:
            # enable screensaver again after fullscreen music playback was ended
            kodi_json('Settings.SetSettingValue', {
                "setting": "screensaver.mode",
                "value": self.screensaver_setting
            })
            self.screensaver_disabled = False
            self.screensaver_setting = None
            log_msg(
                "fullscreen music playback ended - restoring screensaver: %s" %
                self.screensaver_setting, xbmc.LOGNOTICE)

    @staticmethod
    def check_osd():
        '''Allow user to set a default close timeout for the OSD panels'''
        if getCondVisibility(
                "[Window.IsActive(videoosd) + Skin.String(SkinHelper.AutoCloseVideoOSD)] | "
                "[Window.IsActive(musicosd) + Skin.String(SkinHelper.AutoCloseMusicOSD)]"
        ):
            if getCondVisibility("Window.IsActive(videoosd)"):
                seconds = xbmc.getInfoLabel(
                    "Skin.String(SkinHelper.AutoCloseVideoOSD)")
                window = "videoosd"
            elif getCondVisibility("Window.IsActive(musicosd)"):
                seconds = xbmc.getInfoLabel(
                    "Skin.String(SkinHelper.AutoCloseMusicOSD)")
                window = "musicosd"
            else:
                seconds = ""
            if seconds and seconds != "0":
                while getCondVisibility("Window.IsActive(%s)" % window):
                    if getCondVisibility("System.IdleTime(%s)" % seconds):
                        if getCondVisibility("Window.IsActive(%s)" % window):
                            xbmc.executebuiltin("Dialog.Close(%s)" % window)
                    else:
                        xbmc.sleep(500)

    def set_listitem_details(self, cur_listitem, content_type, prefix):
        '''set the window properties based on the current listitem'''
        try:
            if cur_listitem in self.listitem_details:
                # data already in memory
                all_props = self.listitem_details[cur_listitem]
            else:
                # skip if another lookup for the same listitem is already in progress...
                if self.lookup_busy.get(cur_listitem) or self.exit:
                    return
                self.lookup_busy[cur_listitem] = True

                # clear all window props, do this delayed to prevent flickering of the screen
                thread.start_new_thread(self.delayed_flush, (cur_listitem, ))

                # wait if we already have more than 5 items in the queue
                while len(self.lookup_busy) > 5:
                    xbmc.sleep(100)
                    if self.exit or cur_listitem != self.last_listitem:
                        self.lookup_busy.pop(cur_listitem, None)
                        return

                # prefer listitem's contenttype over container's contenttype
                dbtype = xbmc.getInfoLabel("%sListItem.DBTYPE" % prefix)
                if not dbtype:
                    dbtype = xbmc.getInfoLabel("%sListItem.Property(DBTYPE)" %
                                               prefix)
                if dbtype:
                    content_type = dbtype + "s"

                # collect details from listitem
                details = self.get_listitem_details(content_type, prefix)

                if self.exit:
                    return

                # music content
                if content_type in ["albums", "artists", "songs"
                                    ] and self.enable_musicart:
                    details = self.metadatautils.extend_dict(
                        details,
                        self.metadatautils.get_music_artwork(
                            details["artist"], details["album"],
                            details["title"], details["discnumber"]))
                # moviesets
                elif details["path"].startswith(
                        "videodb://movies/sets/") and details["dbid"]:
                    details = self.metadatautils.extend_dict(
                        details,
                        self.metadatautils.get_moviesetdetails(
                            details["title"], details["dbid"]), ["year"])
                    content_type = "sets"
                # video content
                elif content_type in [
                        "movies", "setmovies", "tvshows", "seasons",
                        "episodes", "musicvideos"
                ]:

                    # get imdb and tvdbid
                    details[
                        "imdbnumber"], tvdbid = self.metadatautils.get_imdbtvdb_id(
                            details["title"], content_type, details["year"],
                            details["imdbnumber"], details["tvshowtitle"])

                    if self.exit:
                        return

                    # generic video properties (studio, streamdetails, omdb, top250)
                    details = merge_dict(
                        details,
                        self.get_directors_writers(details["director"],
                                                   details["writer"]))
                    if self.enable_extrafanart:
                        if not details["filenameandpath"]:
                            details["filenameandpath"] = details["path"]
                        if "videodb://" not in details["filenameandpath"]:
                            efa = self.metadatautils.get_extrafanart(
                                details["filenameandpath"])
                            if efa:
                                details["art"] = merge_dict(
                                    details["art"], efa["art"])
                    if self.enable_extraposter:
                        if not details["filenameandpath"]:
                            details["filenameandpath"] = details["path"]
                        if "videodb://" not in details["filenameandpath"]:
                            efa = self.metadatautils.get_extraposter(
                                details["filenameandpath"])
                            if efa:
                                details["art"] = merge_dict(
                                    details["art"], efa["art"])
                    if self.exit:
                        return

                    details = merge_dict(
                        details,
                        self.metadatautils.get_duration(details["duration"]))
                    details = merge_dict(details,
                                         self.get_genres(details["genre"]))
                    details = merge_dict(
                        details,
                        self.metadatautils.get_studio_logo(details["studio"]))
                    details = merge_dict(
                        details,
                        self.metadatautils.get_omdb_info(
                            details["imdbnumber"]))
                    details = merge_dict(
                        details,
                        self.get_streamdetails(details["dbid"],
                                               details["path"], content_type))
                    details = merge_dict(
                        details,
                        self.metadatautils.get_top250_rating(
                            details["imdbnumber"]))

                    if self.exit:
                        return

                    # tvshows-only properties (tvdb)
                    if content_type in ["tvshows", "seasons", "episodes"]:
                        details = merge_dict(
                            details,
                            self.metadatautils.get_tvdb_details(
                                details["imdbnumber"], tvdbid))

                    # movies-only properties (tmdb, animated art)
                    if content_type in ["movies", "setmovies"]:
                        details = merge_dict(
                            details,
                            self.metadatautils.get_tmdb_details(
                                details["imdbnumber"]))
                        if details["imdbnumber"] and self.enable_animatedart:
                            details = self.metadatautils.extend_dict(
                                details,
                                self.metadatautils.get_animated_artwork(
                                    details["imdbnumber"]))

                    if self.exit:
                        return

                    # extended art
                    if self.enable_extendedart:
                        tmdbid = details.get("tmdb_id", "")
                        details = self.metadatautils.extend_dict(
                            details,
                            self.metadatautils.get_extended_artwork(
                                details["imdbnumber"], tvdbid, tmdbid,
                                content_type), [
                                    "posters", "clearlogos", "banners",
                                    "discarts", "cleararts", "characterarts"
                                ])
                # monitor listitem props when PVR is active
                elif content_type in [
                        "tvchannels", "tvrecordings", "channels", "recordings",
                        "timers", "tvtimers"
                ]:
                    details = self.get_pvr_artwork(details, prefix)

                # process all properties
                all_props = prepare_win_props(details)
                if "sets" not in content_type:
                    self.listitem_details[cur_listitem] = all_props

                self.lookup_busy.pop(cur_listitem, None)

            if cur_listitem == self.last_listitem:
                self.set_win_props(all_props)
        except Exception as exc:
            log_exception(__name__, exc)
            self.lookup_busy.pop(cur_listitem, None)

    def delayed_flush(self, cur_listitem):
        '''flushes existing properties when it takes too long to grab the new ones'''
        xbmc.sleep(500)
        if cur_listitem == self.last_listitem and cur_listitem in self.lookup_busy:
            self.reset_win_props()

    def do_background_work(self):
        '''stuff that's processed in the background'''
        try:
            if self.exit:
                return
            log_msg("Started Background worker...")
            self.set_generic_props()
            self.listitem_details = {}
            if self.exit:
                return
            self.cache.check_cleanup()
            log_msg("Ended Background worker...")
        except Exception as exc:
            log_exception(__name__, exc)

    def set_generic_props(self):
        '''set some generic window props with item counts'''
        # GET TOTAL ADDONS COUNT
        addons_count = len(kodi_json('Addons.GetAddons'))
        self.win.setProperty("SkinHelper.TotalAddons", "%s" % addons_count)

        addontypes = []
        addontypes.append(("executable", "SkinHelper.TotalProgramAddons"))
        addontypes.append(("video", "SkinHelper.TotalVideoAddons"))
        addontypes.append(("audio", "SkinHelper.TotalAudioAddons"))
        addontypes.append(("image", "SkinHelper.TotalPicturesAddons"))
        for addontype in addontypes:
            media_array = kodi_json('Addons.GetAddons',
                                    {"content": addontype[0]})
            self.win.setProperty(addontype[1], str(len(media_array)))

        # GET FAVOURITES COUNT
        favs = kodi_json('Favourites.GetFavourites')
        if favs:
            self.win.setProperty("SkinHelper.TotalFavourites",
                                 "%s" % len(favs))

        if self.exit:
            return

        # GET TV CHANNELS COUNT
        if getCondVisibility("Pvr.HasTVChannels"):
            tv_channels = kodi_json('PVR.GetChannels',
                                    {"channelgroupid": "alltv"})
            self.win.setProperty("SkinHelper.TotalTVChannels",
                                 "%s" % len(tv_channels))

        if self.exit:
            return

        # GET MOVIE SETS COUNT
        movieset_movies_count = 0
        moviesets = kodi_json('VideoLibrary.GetMovieSets')
        for item in moviesets:
            for item in kodi_json('VideoLibrary.GetMovieSetDetails',
                                  {"setid": item["setid"]}):
                movieset_movies_count += 1
        self.win.setProperty("SkinHelper.TotalMovieSets",
                             "%s" % len(moviesets))
        self.win.setProperty("SkinHelper.TotalMoviesInSets",
                             "%s" % movieset_movies_count)

        if self.exit:
            return

        # GET RADIO CHANNELS COUNT
        if getCondVisibility("Pvr.HasRadioChannels"):
            radio_channels = kodi_json('PVR.GetChannels',
                                       {"channelgroupid": "allradio"})
            self.win.setProperty("SkinHelper.TotalRadioChannels",
                                 "%s" % len(radio_channels))

    def reset_win_props(self):
        '''reset all window props set by the script...'''
        self.metadatautils.process_method_on_list(
            self.win.clearProperty, self.all_window_props.iterkeys())
        self.all_window_props = {}

    def set_win_prop(self, prop_tuple):
        '''sets a window property based on the given key-value'''
        key = prop_tuple[0]
        value = prop_tuple[1]
        if (key not in self.all_window_props) or (
                key in self.all_window_props
                and self.all_window_props[key] != value):
            self.all_window_props[key] = value
            self.win.setProperty(key, value)

    def set_win_props(self, prop_tuples):
        '''set multiple window properties from list of tuples'''
        self.metadatautils.process_method_on_list(self.set_win_prop,
                                                  prop_tuples)
        # cleanup remaining properties
        new_keys = [item[0] for item in prop_tuples]
        for key, value in self.all_window_props.iteritems():
            if value and key not in new_keys:
                self.all_window_props[key] = ""
                self.win.clearProperty(key)

    def set_content_header(self, content_type):
        '''sets a window propery which can be used as headertitle'''
        self.win.clearProperty("SkinHelper.ContentHeader")
        itemscount = xbmc.getInfoLabel("Container.NumItems")
        if itemscount:
            if xbmc.getInfoLabel(
                    "Container.ListItemNoWrap(0).Label"
            ).startswith("*") or xbmc.getInfoLabel(
                    "Container.ListItemNoWrap(1).Label").startswith("*"):
                itemscount = int(itemscount) - 1
            headerprefix = ""
            if content_type == "movies":
                headerprefix = xbmc.getLocalizedString(36901)
            elif content_type == "tvshows":
                headerprefix = xbmc.getLocalizedString(36903)
            elif content_type == "seasons":
                headerprefix = xbmc.getLocalizedString(36905)
            elif content_type == "episodes":
                headerprefix = xbmc.getLocalizedString(36907)
            elif content_type == "sets":
                headerprefix = xbmc.getLocalizedString(36911)
            elif content_type == "albums":
                headerprefix = xbmc.getLocalizedString(36919)
            elif content_type == "songs":
                headerprefix = xbmc.getLocalizedString(36921)
            elif content_type == "artists":
                headerprefix = xbmc.getLocalizedString(36917)
            if headerprefix:
                self.win.setProperty("SkinHelper.ContentHeader",
                                     "%s %s" % (itemscount, headerprefix))

    @staticmethod
    def get_genres(genres):
        '''get formatted genre string from actual genre'''
        details = {}
        if not isinstance(genres, list):
            genres = genres.split(" / ")
        details['genres'] = "[CR]".join(genres)
        for count, genre in enumerate(genres):
            details["genre.%s" % count] = genre
        return details

    @staticmethod
    def get_directors_writers(director, writer):
        '''get a formatted string with directors/writers from the actual string'''
        directors = director.split(" / ")
        writers = writer.split(" / ")
        return {
            'Directors': "[CR]".join(directors),
            'Writers': "[CR]".join(writers)
        }

    def get_listitem_details(self, content_type, prefix):
        '''collect all listitem properties/values we need'''
        listitem_details = {"art": {}}

        # basic properties
        for prop in ["dbtype", "dbid", "imdbnumber"]:
            propvalue = xbmc.getInfoLabel('$INFO[%sListItem.%s]' %
                                          (prefix, prop)).decode('utf-8')
            if not propvalue or propvalue == "-1":
                propvalue = xbmc.getInfoLabel(
                    '$INFO[%sListItem.Property(%s)]' %
                    (prefix, prop)).decode('utf-8')
            listitem_details[prop] = propvalue

        # generic properties
        props = [
            "label", "title", "filenameandpath", "year", "genre", "path",
            "folderpath", "duration", "plot", "plotoutline", "label2", "icon",
            "thumb"
        ]
        # properties for media items
        if content_type in [
                "movies", "tvshows", "seasons", "episodes", "musicvideos",
                "setmovies"
        ]:
            props += [
                "studio", "tvshowtitle", "premiered", "director", "writer",
                "firstaired", "tagline", "rating", "season", "episode"
            ]
        # properties for music items
        elif content_type in ["musicvideos", "artists", "albums", "songs"]:
            props += ["artist", "album", "rating", "albumartist", "discnumber"]
        # properties for pvr items
        elif content_type in [
                "tvchannels", "tvrecordings", "channels", "recordings",
                "timers", "tvtimers"
        ]:
            props += ["channel", "channelname"]

        for prop in props:
            if self.exit:
                break
            propvalue = xbmc.getInfoLabel('$INFO[%sListItem.%s]' %
                                          (prefix, prop)).decode('utf-8')
            listitem_details[prop] = propvalue

        # artwork properties
        artprops = [
            "fanart", "poster", "clearlogo", "clearart", "landscape", "thumb",
            "banner", "discart", "characterart"
        ]
        for prop in artprops:
            if self.exit:
                break
            propvalue = xbmc.getInfoLabel('$INFO[%sListItem.Art(%s)]' %
                                          (prefix, prop)).decode('utf-8')
            if not propvalue:
                propvalue = xbmc.getInfoLabel(
                    '$INFO[%sListItem.Art(tvshow.%s)]' %
                    (prefix, prop)).decode('utf-8')
            if propvalue:
                listitem_details["art"][prop] = propvalue

        # fix for folderpath
        if not listitem_details.get(
                "path") and "folderpath" in listitem_details:
            listitem_details["path"] = listitem_details["folderpath"]
        # fix for thumb
        if "thumb" not in listitem_details[
                "art"] and "thumb" in listitem_details:
            listitem_details["art"]["thumb"] = listitem_details["thumb"]
        if "fanart" not in listitem_details[
                "art"] and "fanart" in listitem_details:
            listitem_details["art"]["fanart"] = listitem_details["fanart"]
        return listitem_details

    def get_streamdetails(self, li_dbid, li_path, content_type):
        '''get the streamdetails for the current video'''
        details = {}
        if li_dbid and content_type in [
                "movies", "episodes", "musicvideos"
        ] and not li_path.startswith("videodb://movies/sets/"):
            details = self.metadatautils.get_streamdetails(
                li_dbid, content_type)
        return details

    def set_forcedview(self, content_type):
        '''helper to force the view in certain conditions'''
        if self.enable_forcedviews:
            cur_forced_view = xbmc.getInfoLabel(
                "Skin.String(SkinHelper.ForcedViews.%s)" % content_type)
            if getCondVisibility(
                    "Control.IsVisible(%s) | IsEmpty(Container.Viewmode) | System.HasModalDialog | System.HasVisibleModalDialog"
                    % cur_forced_view):
                # skip if the view is already visible or if we're not in an actual media window
                return
            if (content_type and cur_forced_view and cur_forced_view != "None"
                    and
                    not getCondVisibility("Window.IsActive(MyPvrGuide.xml)")):
                self.win.setProperty("SkinHelper.ForcedView", cur_forced_view)
                count = 0
                while not getCondVisibility(
                        "Control.HasFocus(%s)" % cur_forced_view):
                    xbmc.sleep(100)
                    xbmc.executebuiltin("Container.SetViewMode(%s)" %
                                        cur_forced_view)
                    xbmc.executebuiltin("SetFocus(%s)" % cur_forced_view)
                    count += 1
                    if count == 50 or self.exit:
                        break
            else:
                self.win.clearProperty("SkinHelper.ForcedView")
        else:
            self.win.clearProperty("SkinHelper.ForcedView")

    def get_pvr_artwork(self, listitem, prefix):
        '''get pvr artwork from artwork module'''
        if self.enable_pvrart:
            if getCondVisibility(
                    "%sListItem.IsFolder" % prefix
            ) and not listitem["channelname"] and not listitem["title"]:
                listitem["title"] = listitem["label"]
            listitem = self.metadatautils.extend_dict(
                listitem,
                self.metadatautils.get_pvr_artwork(listitem["title"],
                                                   listitem["channelname"],
                                                   listitem["genre"]),
                ["title", "genre", "genres", "thumb"])
        # pvr channellogo
        if listitem["channelname"]:
            listitem["art"][
                "ChannelLogo"] = self.metadatautils.get_channellogo(
                    listitem["channelname"])
        elif listitem.get("pvrchannel"):
            listitem["art"][
                "ChannelLogo"] = self.metadatautils.get_channellogo(
                    listitem["pvrchannel"])
        return listitem
Exemplo n.º 6
0
class MainModule:
    '''mainmodule provides the script methods for the skinhelper addon'''
    def __init__(self):
        '''Initialization and main code run'''
        self.win = xbmcgui.Window(10000)
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.kodidb = KodiDb()
        self.cache = SimpleCache()

        self.params = self.get_params()
        log_msg("MainModule called with parameters: %s" % self.params)
        action = self.params.get("action", "")
        # launch module for action provided by this script
        try:
            getattr(self, action)()
        except AttributeError:
            log_exception(__name__, "No such action: %s" % action)
        except Exception as exc:
            log_exception(__name__, exc)
        finally:
            xbmc.executebuiltin("dialog.Close(busydialog)")

        # do cleanup
        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances on exit'''
        self.cache.close()
        del self.win
        del self.addon
        del self.kodidb
        log_msg("MainModule exited")

    @classmethod
    def get_params(self):
        '''extract the params from the called script path'''
        params = {}
        for arg in sys.argv[1:]:
            paramname = arg.split('=')[0]
            paramvalue = arg.replace(paramname + "=", "")
            paramname = paramname.lower()
            if paramname == "action":
                paramvalue = paramvalue.lower()
            params[paramname] = paramvalue
        return params

    def deprecated_method(self, newaddon):
        '''
            used when one of the deprecated methods is called
            print warning in log and call the external script with the same parameters
        '''
        action = self.params.get("action")
        log_msg(
            "Deprecated method: %s. Please call %s directly" %
            (action, newaddon), xbmc.LOGWARNING)
        paramstring = ""
        for key, value in self.params.iteritems():
            paramstring += ",%s=%s" % (key, value)
        if xbmc.getCondVisibility("System.HasAddon(%s)" % newaddon):
            xbmc.executebuiltin("RunAddon(%s%s)" % (newaddon, paramstring))
        else:
            # trigger install of the addon
            if KODI_VERSION > 16:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    @staticmethod
    def musicsearch():
        '''helper to go directly to music search dialog'''
        xbmc.executebuiltin("ActivateWindow(Music)")
        xbmc.executebuiltin("SendClick(8)")

    def setview(self):
        '''sets the selected viewmode for the container'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        content_type = get_current_content_type()
        if not content_type:
            content_type = "files"
        current_view = xbmc.getInfoLabel("Container.Viewmode").decode("utf-8")
        view_id, view_label = self.selectview(content_type, current_view)
        current_forced_view = xbmc.getInfoLabel(
            "Skin.String(SkinHelper.ForcedViews.%s)" % content_type)

        if view_id is not None:
            # also store forced view
            if (content_type and current_forced_view
                    and current_forced_view != "None"
                    and xbmc.getCondVisibility(
                        "Skin.HasSetting(SkinHelper.ForcedViews.Enabled)")):
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s,%s)" %
                    (content_type, view_id))
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" %
                    (content_type, view_label))
                self.win.setProperty("SkinHelper.ForcedView", view_id)
                if not xbmc.getCondVisibility(
                        "Control.HasFocus(%s)" % current_forced_view):
                    xbmc.sleep(100)
                    xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)
                    xbmc.executebuiltin("SetFocus(%s)" % view_id)
            else:
                self.win.clearProperty("SkinHelper.ForcedView")
            # set view
            xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)

    def selectview(self,
                   content_type="other",
                   current_view=None,
                   display_none=False):
        '''reads skinfile with all views to present a dialog to choose from'''
        cur_view_select_id = None
        label = ""
        all_views = []
        if display_none:
            listitem = xbmcgui.ListItem(label="None")
            listitem.setProperty("id", "None")
            all_views.append(listitem)
        # read the special skin views file
        views_file = xbmc.translatePath(
            'special://skin/extras/views.xml').decode("utf-8")
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            itemcount = 0
            for view in listing:
                label = xbmc.getLocalizedString(
                    int(view.attributes['languageid'].nodeValue))
                viewid = view.attributes['value'].nodeValue
                mediatypes = view.attributes['type'].nodeValue.lower().split(
                    ",")
                if label.lower() == current_view.lower(
                ) or viewid == current_view:
                    cur_view_select_id = itemcount
                    if display_none:
                        cur_view_select_id += 1
                if (("all" in mediatypes or content_type.lower() in mediatypes)
                        and (not "!" + content_type.lower() in mediatypes)
                        and not xbmc.getCondVisibility(
                            "Skin.HasSetting(SkinHelper.view.Disabled.%s)" %
                            viewid)):
                    image = "special://skin/extras/viewthumbs/%s.jpg" % viewid
                    listitem = xbmcgui.ListItem(label=label, iconImage=image)
                    listitem.setProperty("viewid", viewid)
                    listitem.setProperty("icon", image)
                    all_views.append(listitem)
                    itemcount += 1
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=all_views,
                              windowtitle=self.addon.getLocalizedString(32012),
                              richlayout=True)
        dialog.autofocus_id = cur_view_select_id
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            viewid = result.getProperty("viewid")
            label = result.getLabel().decode("utf-8")
            return (viewid, label)
        else:
            return (None, None)

    # pylint: disable-msg=too-many-local-variables
    def enableviews(self):
        '''show select dialog to enable/disable views'''
        all_views = []
        views_file = xbmc.translatePath(
            'special://skin/extras/views.xml').decode("utf-8")
        richlayout = self.params.get("richlayout", "") == "true"
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            for view in listing:
                view_id = view.attributes['value'].nodeValue
                label = xbmc.getLocalizedString(
                    int(view.attributes['languageid'].nodeValue))
                desc = label + " (" + str(view_id) + ")"
                image = "special://skin/extras/viewthumbs/%s.jpg" % view_id
                listitem = xbmcgui.ListItem(label=label,
                                            label2=desc,
                                            iconImage=image)
                listitem.setProperty("viewid", view_id)
                if not xbmc.getCondVisibility(
                        "Skin.HasSetting(SkinHelper.view.Disabled.%s)" %
                        view_id):
                    listitem.select(selected=True)
                excludefromdisable = False
                try:
                    excludefromdisable = view.attributes[
                        'excludefromdisable'].nodeValue == "true"
                except Exception:
                    pass
                if not excludefromdisable:
                    all_views.append(listitem)

        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=all_views,
                              windowtitle=self.addon.getLocalizedString(32013),
                              multiselect=True,
                              richlayout=richlayout)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            for item in result:
                view_id = item.getProperty("viewid")
                if item.isSelected():
                    # view is enabled
                    xbmc.executebuiltin(
                        "Skin.Reset(SkinHelper.view.Disabled.%s)" % view_id)
                else:
                    # view is disabled
                    xbmc.executebuiltin(
                        "Skin.SetBool(SkinHelper.view.Disabled.%s)" % view_id)

    # pylint: enable-msg=too-many-local-variables

    def setforcedview(self):
        '''helper that sets a forced view for a specific content type'''
        content_type = self.params.get("contenttype")
        if content_type:
            current_view = xbmc.getInfoLabel(
                "Skin.String(SkinHelper.ForcedViews.%s)" % content_type)
            if not current_view:
                current_view = "0"
            view_id, view_label = self.selectview(content_type, current_view,
                                                  True)
            if view_id or view_label:
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s,%s)" %
                    (content_type, view_id))
                xbmc.executebuiltin(
                    "Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" %
                    (content_type, view_label))

    @staticmethod
    def get_youtube_listing(searchquery):
        '''get items from youtube plugin by query'''
        lib_path = u"plugin://plugin.video.youtube/kodion/search/query/?q=%s" % searchquery
        return KodiDb().files(lib_path)

    def searchyoutube(self):
        '''helper to search youtube for the given title'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        title = self.params.get("title", "")
        window_header = self.params.get("header", "")
        results = []
        for media in self.get_youtube_listing(title):
            if not media["filetype"] == "directory":
                label = media["label"]
                label2 = media["plot"]
                image = ""
                if media.get('art'):
                    if media['art'].get('thumb'):
                        image = (media['art']['thumb'])
                listitem = xbmcgui.ListItem(label=label,
                                            label2=label2,
                                            iconImage=image)
                listitem.setProperty("path", media["file"])
                results.append(listitem)

        # finished lookup - display listing with results
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=results,
                              windowtitle=window_header,
                              multiselect=False,
                              richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            if xbmc.getCondVisibility(
                    "Window.IsActive(script-skin_helper_service-CustomInfo.xml) | "
                    "Window.IsActive(movieinformation)"):
                xbmc.executebuiltin("Dialog.Close(movieinformation)")
                xbmc.executebuiltin(
                    "Dialog.Close(script-skin_helper_service-CustomInfo.xml)")
                xbmc.sleep(1000)
            xbmc.executebuiltin('PlayMedia("%s")' % result.getProperty("path"))
            del result

    def getcastmedia(self):
        '''helper to show a dialog with all media for a specific actor'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        name = self.params.get("name", "")
        window_header = self.params.get("name", "")
        results = []
        items = self.kodidb.castmedia(name)
        items = process_method_on_list(self.kodidb.prepare_listitem, items)
        for item in items:
            if item["file"].startswith("videodb://"):
                item[
                    "file"] = "ActivateWindow(Videos,%s,return)" % item["file"]
            else:
                item["file"] = 'PlayMedia("%s")' % item["file"]
            results.append(self.kodidb.create_listitem(item, False))
        # finished lookup - display listing with results
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml",
                              "",
                              listing=results,
                              windowtitle=window_header,
                              richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            while xbmc.getCondVisibility("System.HasModalDialog"):
                xbmc.executebuiltin("Action(Back)")
                xbmc.sleep(300)
            xbmc.executebuiltin(result.getfilename())
            del result

    def setfocus(self):
        '''helper to set focus on a list or control'''
        control = self.params.get("control")
        fallback = self.params.get("fallback")
        position = self.params.get("position", "0")
        relativeposition = self.params.get("relativeposition")
        if relativeposition:
            position = int(relativeposition) - 1
        count = 0
        if control:
            while not xbmc.getCondVisibility("Control.HasFocus(%s)" % control):
                if xbmc.getCondVisibility("Window.IsActive(busydialog)"):
                    xbmc.sleep(150)
                    continue
                elif count == 20 or (xbmc.getCondVisibility(
                        "!Control.IsVisible(%s) | "
                        "!IntegerGreaterThan(Container(%s).NumItems,0)" %
                    (control, control))):
                    if fallback:
                        xbmc.executebuiltin("Control.SetFocus(%s)" % fallback)
                    break
                else:
                    xbmc.executebuiltin("Control.SetFocus(%s,%s)" %
                                        (control, position))
                    xbmc.sleep(50)
                    count += 1

    def setwidgetcontainer(self):
        '''helper that reports the current selected widget container/control'''
        controls = self.params.get("controls", "").split("-")
        if controls:
            xbmc.sleep(50)
            for i in range(10):
                for control in controls:
                    if xbmc.getCondVisibility(
                            "Control.IsVisible(%s) + IntegerGreaterThan(Container(%s).NumItems,0)"
                            % (control, control)):
                        self.win.setProperty("SkinHelper.WidgetContainer",
                                             control)
                        return
                xbmc.sleep(50)
        self.win.clearProperty("SkinHelper.WidgetContainer")

    def saveskinimage(self):
        '''let the user select an image and save it to addon_data for easy backup'''
        skinstring = self.params.get("skinstring", "")
        allow_multi = self.params.get("multi", "") == "true"
        header = self.params.get("header", "")
        value = SkinSettings().save_skin_image(skinstring, allow_multi, header)
        if value:
            xbmc.executebuiltin(
                "Skin.SetString(%s,%s)" %
                (skinstring.encode("utf-8"), value.encode("utf-8")))

    @staticmethod
    def checkskinsettings():
        '''performs check of all default skin settings and labels'''
        SkinSettings().correct_skin_settings()

    def setskinsetting(self):
        '''allows the user to set a skin setting with a select dialog'''
        setting = self.params.get("setting", "")
        org_id = self.params.get("id", "")
        if "$" in org_id:
            org_id = xbmc.getInfoLabel(org_id).decode("utf-8")
        header = self.params.get("header", "")
        SkinSettings().set_skin_setting(setting=setting,
                                        window_header=header,
                                        original_id=org_id)

    def setskinconstant(self):
        '''allows the user to set a skin constant with a select dialog'''
        setting = self.params.get("setting", "")
        value = self.params.get("value", "")
        header = self.params.get("header", "")
        SkinSettings().set_skin_constant(setting, header, value)

    def setskinconstants(self):
        '''allows the skinner to set multiple skin constants'''
        settings = self.params.get("settings", "").split("|")
        values = self.params.get("values", "").split("|")
        SkinSettings().set_skin_constants(settings, values)

    def setskinshortcutsproperty(self):
        '''allows the user to make a setting for skinshortcuts using the special skinsettings dialogs'''
        setting = self.params.get("setting", "")
        prop = self.params.get("property", "")
        header = self.params.get("header", "")
        SkinSettings().set_skinshortcuts_property(setting, header, prop)

    def togglekodisetting(self):
        '''toggle kodi setting'''
        settingname = self.params.get("setting", "")
        cur_value = xbmc.getCondVisibility("system.getbool(%s)" % settingname)
        if cur_value:
            new_value = "false"
        else:
            new_value = "true"
        xbmc.executeJSONRPC(
            '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"%s","value":%s}}'
            % (settingname, new_value))

    def setkodisetting(self):
        '''set kodi setting'''
        settingname = self.params.get("setting", "")
        value = self.params.get("value", "")
        is_int = False
        try:
            valueint = int(value)
            is_int = True
            del valueint
        except Exception:
            pass
        if value.lower() == "true":
            value = 'true'
        elif value.lower() == "false":
            value = 'false'
        elif is_int:
            value = '"%s"' % value
        xbmc.executeJSONRPC(
            '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue",\
            "params":{"setting":"%s","value":%s}}' % (settingname, value))

    def playtrailer(self):
        '''auto play windowed trailer inside video listing'''
        if not xbmc.getCondVisibility(
                "Player.HasMedia | Container.Scrolling | Container.OnNext | "
                "Container.OnPrevious | !IsEmpty(Window(Home).Property(traileractionbusy))"
        ):
            self.win.setProperty("traileractionbusy", "traileractionbusy")
            widget_container = self.params.get("widgetcontainer", "")
            trailer_mode = self.params.get("mode", "").replace("auto_", "")
            allow_youtube = self.params.get("youtube", "") == "true"
            if not trailer_mode:
                trailer_mode = "windowed"
            if widget_container:
                widget_container_prefix = "Container(%s)." % widget_container
            else:
                widget_container_prefix = ""

            li_title = xbmc.getInfoLabel(
                "%sListItem.Title" % widget_container_prefix).decode('utf-8')
            li_trailer = xbmc.getInfoLabel(
                "%sListItem.Trailer" % widget_container_prefix).decode('utf-8')
            if not li_trailer and allow_youtube:
                youtube_result = self.get_youtube_listing("%s Trailer" %
                                                          li_title)
                if youtube_result:
                    li_trailer = youtube_result[0].get("file")
            # always wait a bit to prevent trailer start playing when we're scrolling the list
            xbmc.Monitor().waitForAbort(3)
            if li_trailer and (li_title == xbmc.getInfoLabel(
                    "%sListItem.Title" %
                    widget_container_prefix).decode('utf-8')):
                if trailer_mode == "fullscreen" and li_trailer:
                    xbmc.executebuiltin('PlayMedia("%s")' % li_trailer)
                else:
                    xbmc.executebuiltin('PlayMedia("%s",1)' % li_trailer)
                self.win.setProperty("TrailerPlaying", trailer_mode)
            self.win.clearProperty("traileractionbusy")

    def colorpicker(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.colorpicker")

    def backup(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restore(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def reset(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def colorthemes(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def createcolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restorecolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def conditionalbackgrounds(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.backgrounds")

    def splashscreen(self):
        '''helper to show a user defined splashscreen in the skin'''
        import time
        splashfile = self.params.get("file", "")
        duration = int(self.params.get("duration", 5))
        if (splashfile.lower().endswith("jpg")
                or splashfile.lower().endswith("gif")
                or splashfile.lower().endswith("png")
                or splashfile.lower().endswith("tiff")):
            # this is an image file
            self.win.setProperty("SkinHelper.SplashScreen", splashfile)
            # for images we just wait for X seconds to close the splash again
            start_time = time.time()
            while (time.time() - start_time) <= duration:
                xbmc.sleep(500)
        else:
            # for video or audio we have to wait for the player to finish...
            xbmc.Player().play(splashfile, windowed=True)
            xbmc.sleep(500)
            while xbmc.getCondVisibility("Player.HasMedia"):
                xbmc.sleep(150)
        # replace startup window with home
        startupwindow = xbmc.getInfoLabel("System.StartupWindow")
        xbmc.executebuiltin("ReplaceWindow(%s)" % startupwindow)
        autostart_playlist = xbmc.getInfoLabel(
            "$ESCINFO[Skin.String(autostart_playlist)]")
        if autostart_playlist:
            xbmc.executebuiltin("PlayMedia(%s)" % autostart_playlist)

    def videosearch(self):
        '''show the special search dialog'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        from resources.lib.searchdialog import SearchDialog
        search_dialog = SearchDialog(
            "script-skin_helper_service-CustomSearch.xml",
            self.addon.getAddonInfo('path').decode("utf-8"), "Default",
            "1080i")
        search_dialog.doModal()
        del search_dialog

    def showinfo(self):
        '''shows our special videoinfo dialog'''
        dbid = self.params.get("dbid", "")
        dbtype = self.params.get("dbtype", "")
        from infodialog import show_infodialog
        show_infodialog(dbid, dbtype)

    def deletedir(self):
        '''helper to delete a directory, input can be normal filesystem path or vfs'''
        del_path = self.params.get("path")
        if del_path:
            ret = xbmcgui.Dialog().yesno(
                heading=xbmc.getLocalizedString(122),
                line1=u"%s[CR]%s" % (xbmc.getLocalizedString(125), del_path))
            if ret:
                success = recursive_delete_dir(del_path)
                if success:
                    xbmcgui.Dialog().ok(
                        heading=xbmc.getLocalizedString(19179),
                        line1=self.addon.getLocalizedString(32014))
                else:
                    xbmcgui.Dialog().ok(heading=xbmc.getLocalizedString(16205),
                                        line1=xbmc.getLocalizedString(32015))

    def overlaytexture(self):
        '''legacy: helper to let the user choose a background overlay from a skin defined folder'''
        skinstring = self.params.get("skinstring", "BackgroundOverlayTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.backgroundoverlays"
        self.params["customfolder"] = "special://skin/extras/bgoverlays/"
        self.params["allowmulti"] = "false"
        self.params["header"] = self.addon.getLocalizedString(32002)
        self.selectimage()

    def busytexture(self):
        '''legacy: helper which lets the user select a busy spinner from predefined spinners in the skin'''
        skinstring = self.params.get("skinstring", "SkinHelper.SpinnerTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.busyspinners"
        self.params["customfolder"] = "special://skin/extras/busy_spinners/"
        self.params["allowmulti"] = "true"
        self.params["header"] = self.addon.getLocalizedString(32006)
        self.selectimage()

    def selectimage(self):
        '''helper which lets the user select an image or imagepath from resourceaddons or custom path'''
        skinsettings = SkinSettings()
        skinstring = self.params.get("skinstring", "")
        skinshortcutsprop = self.params.get("skinshortcutsproperty", "")
        current_value = self.params.get("currentvalue", "")
        resource_addon = self.params.get("resourceaddon", "")
        allow_multi = self.params.get("allowmulti", "false") == "true"
        windowheader = self.params.get("header", "")
        skinhelper_backgrounds = self.params.get("skinhelperbackgrounds",
                                                 "false") == "true"
        label, value = skinsettings.select_image(
            skinstring,
            allow_multi=allow_multi,
            windowheader=windowheader,
            resource_addon=resource_addon,
            skinhelper_backgrounds=skinhelper_backgrounds,
            current_value=current_value)
        if label:
            if skinshortcutsprop:
                # write value to skinshortcuts prop
                from skinshortcuts import set_skinshortcuts_property
                set_skinshortcuts_property(skinshortcutsprop, value, label)
            else:
                # write the values to skin strings
                if value.startswith("$INFO"):
                    # we got an dynamic image from window property
                    skinsettings.set_skin_variable(skinstring, value)
                    value = "$VAR[%s]" % skinstring
                skinstring = skinstring.encode("utf-8")
                label = label.encode("utf-8")
                xbmc.executebuiltin("Skin.SetString(%s.label,%s)" %
                                    (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s.name,%s)" %
                                    (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s,%s)" %
                                    (skinstring, value))
                xbmc.executebuiltin("Skin.SetString(%s.path,%s)" %
                                    (skinstring, value))
        del skinsettings

    def dialogok(self):
        '''helper to show an OK dialog with a message'''
        headertxt = self.params.get("header")
        bodytxt = self.params.get("message")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        dialog = xbmcgui.Dialog()
        dialog.ok(heading=headertxt, line1=bodytxt)
        del dialog

    def dialogyesno(self):
        '''helper to show a YES/NO dialog with a message'''
        headertxt = self.params.get("header")
        bodytxt = self.params.get("message")
        yesactions = self.params.get("yesaction", "").split("|")
        noactions = self.params.get("noaction", "").split("|")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        if xbmcgui.Dialog().yesno(heading=headertxt, line1=bodytxt):
            for action in yesactions:
                xbmc.executebuiltin(action.encode("utf-8"))
        else:
            for action in noactions:
                xbmc.executebuiltin(action.encode("utf-8"))

    def textviewer(self):
        '''helper to show a textviewer dialog with a message'''
        headertxt = self.params.get("header", "")
        bodytxt = self.params.get("message", "")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        xbmcgui.Dialog().textviewer(headertxt, bodytxt)

    def fileexists(self):
        '''helper to let the skinner check if a file exists
        and write the outcome to a window prop or skinstring'''
        filename = self.params.get("file")
        skinstring = self.params.get("skinstring")
        windowprop = self.params.get("winprop")
        if xbmcvfs.exists(filename):
            if windowprop:
                self.win.setProperty(windowprop, "exists")
            if skinstring:
                xbmc.executebuiltin("Skin.SetString(%s,exists)" % skinstring)
        else:
            if windowprop:
                self.win.clearProperty(windowprop)
            if skinstring:
                xbmc.executebuiltin("Skin.Reset(%s)" % skinstring)

    def stripstring(self):
        '''helper to allow the skinner to strip a string and write results to a skin string'''
        splitchar = self.params.get("splitchar")
        if splitchar.upper() == "[SPACE]":
            splitchar = " "
        skinstring = self.params.get("string")
        if not skinstring:
            skinstring = self.params.get("skinstring")
        output = self.params.get("output")
        index = self.params.get("index", 0)
        skinstring = skinstring.split(splitchar)[int(index)]
        self.win.setProperty(output, skinstring)

    def getfilename(self, filename=""):
        '''helper to display a sanitized filename in the vidoeinfo dialog'''
        output = self.params.get("output")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileName")
        if "filename=" in filename:
            url_params = dict(urlparse.parse_qsl(filename))
            filename = url_params.get("filename")
        self.win.setProperty(output, filename)

    def getplayerfilename(self):
        '''helper to parse the filename from a plugin (e.g. emby) filename'''
        filename = xbmc.getInfoLabel("Player.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("Player.FileName")
        self.getfilename(filename)

    def getpercentage(self):
        '''helper to calculate the percentage of 2 numbers and write results to a skinstring'''
        total = int(params.get("total"))
        count = int(params.get("count"))
        roundsteps = self.params.get("roundsteps")
        skinstring = self.params.get("skinstring")
        percentage = int(round((1.0 * count / total) * 100))
        if roundsteps:
            roundsteps = int(roundsteps)
            percentage = percentage + (roundsteps - percentage) % roundsteps
        xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, percentage))

    def setresourceaddon(self):
        '''helper to let the user choose a resource addon and set that as skin string'''
        from resourceaddons import setresourceaddon
        addontype = self.params.get("addontype", "")
        skinstring = self.params.get("skinstring", "")
        setresourceaddon(addontype, skinstring)

    def checkresourceaddons(self):
        '''allow the skinner to perform a basic check if some required resource addons are available'''
        from resourceaddons import checkresourceaddons
        addonslist = self.params.get("addonslist", [])
        if addonslist:
            addonslist = addonslist.split("|")
        checkresourceaddons(addonslist)
Exemplo n.º 7
0
class MetadataUtils(object):
    '''
        Provides all kind of mediainfo for kodi media, returned as dict with details
    '''
    close_called = False

    def __init__(self):
        '''Initialize and load all our helpers'''
        self._studiologos_path = ""
        self.cache = SimpleCache()
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.kodidb = KodiDb()
        self.omdb = Omdb(self.cache)
        self.tmdb = Tmdb(self.cache)
        self.channellogos = ChannelLogos(self.kodidb)
        self.fanarttv = FanartTv(self.cache)
        self.imdb = Imdb(self.cache)
        self.google = GoogleImages(self.cache)
        self.studiologos = StudioLogos(self.cache)
        self.animatedart = AnimatedArt(self.cache, self.kodidb)
        self.thetvdb = TheTvDb()
        self.musicart = MusicArtwork(self)
        self.pvrart = PvrArtwork(self)
        log_msg("Initialized")

    def close(self):
        '''Cleanup instances'''
        self.close_called = True
        self.cache.close()
        self.addon = None
        del self.addon
        del self.kodidb
        del self.omdb
        del self.tmdb
        del self.channellogos
        del self.fanarttv
        del self.imdb
        del self.google
        del self.studiologos
        del self.animatedart
        del self.thetvdb
        del self.musicart
        del self.pvrart
        log_msg("Exited")

    def __del__(self):
        '''make sure close is called'''
        if not self.close_called:
            self.close()

    @use_cache(14)
    def get_extrafanart(self, file_path):
        '''helper to retrieve the extrafanart path for a kodi media item'''
        from helpers.extrafanart import get_extrafanart
        return get_extrafanart(file_path)

    def get_music_artwork(self,
                          artist,
                          album="",
                          track="",
                          disc="",
                          ignore_cache=False,
                          flush_cache=False):
        '''method to get music artwork for the goven artist/album/song'''
        return self.musicart.get_music_artwork(artist,
                                               album,
                                               track,
                                               disc,
                                               ignore_cache=ignore_cache,
                                               flush_cache=flush_cache)

    def music_artwork_options(self, artist, album="", track="", disc=""):
        '''options for music metadata for specific item'''
        return self.musicart.music_artwork_options(artist, album, track, disc)

    @use_cache(14)
    def get_extended_artwork(self,
                             imdb_id="",
                             tvdb_id="",
                             tmdb_id="",
                             media_type=""):
        '''get extended artwork for the given imdbid or tvdbid'''
        from urllib import quote_plus
        result = {"art": {}}
        if "movie" in media_type and tmdb_id:
            result["art"] = self.fanarttv.movie(tmdb_id)
        elif "movie" in media_type and imdb_id:
            result["art"] = self.fanarttv.movie(imdb_id)
        elif media_type in ["tvshow", "tvshows", "seasons", "episodes"]:
            if not tvdb_id:
                if imdb_id and not imdb_id.startswith("tt"):
                    tvdb_id = imdb_id
                elif imdb_id:
                    tvdb_id = self.thetvdb.get_series_by_imdb_id(imdb_id).get(
                        "tvdb_id")
            if tvdb_id:
                result["art"] = self.fanarttv.tvshow(tvdb_id)
        # add additional art with special path
        for arttype in ["fanarts", "posters", "clearlogos", "banners"]:
            if result["art"].get(arttype):
                result["art"][arttype] = "plugin://script.skin.helper.service/"\
                    "?action=extrafanart&fanarts=%s" % quote_plus(repr(result["art"][arttype]))
        return result

    @use_cache(14)
    def get_tmdb_details(self,
                         imdb_id="",
                         tvdb_id="",
                         title="",
                         year="",
                         media_type="",
                         preftype="",
                         manual_select=False,
                         ignore_cache=False):
        '''returns details from tmdb'''
        result = {}
        if imdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                imdb_id, "imdb_id")
        elif tvdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                tvdb_id, "tvdb_id")
        elif title and media_type in ["movies", "setmovies", "movie"]:
            result = self.tmdb.search_movie(title,
                                            year,
                                            manual_select=manual_select)
        elif title and media_type in ["tvshows", "tvshow"]:
            result = self.tmdb.search_tvshow(title,
                                             year,
                                             manual_select=manual_select)
        elif title:
            result = self.tmdb.search_video(title,
                                            year,
                                            preftype=preftype,
                                            manual_select=manual_select)
        if result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    def get_moviesetdetails(self, title, set_id):
        '''get a nicely formatted dict of the movieset details which we can for example set as window props'''
        # get details from tmdb
        from helpers.moviesetdetails import get_moviesetdetails
        return get_moviesetdetails(self, title, set_id)

    @use_cache(14)
    def get_streamdetails(self, db_id, media_type, ignore_cache=False):
        '''get a nicely formatted dict of the streamdetails '''
        from helpers.streamdetails import get_streamdetails
        return get_streamdetails(self.kodidb, db_id, media_type)

    def get_pvr_artwork(self,
                        title,
                        channel="",
                        genre="",
                        manual_select=False,
                        ignore_cache=False):
        '''get artwork and mediadetails for PVR entries'''
        import requests, urllib, random
        print title.encode('utf8')
        r = requests.get(
            'http://192.168.0.2:8081/api/teleguide/get_arts?title=' +
            urllib.quote_plus(title.encode('utf8')))
        j = r.json()
        if j['arts']:
            data = {
                'art': {
                    'fanarts': j['arts'],
                    'fanart': random.choice(j['arts']),
                    'thumb': random.choice(j['arts'])
                },
                'cachestr':
                "pvr_artwork.%s.%s" % (title.lower(), channel.lower()),
                'pvrtitle': title,
                'pvrchannel': channel,
                'pvrgenre': genre,
                'genre': [genre],
                'thumbnail': random.choice(j['arts'])
            }
        else:
            data = self.pvrart.get_pvr_artwork(title,
                                               channel,
                                               genre,
                                               manual_select=manual_select,
                                               ignore_cache=ignore_cache)
        log_msg(data)
        return data

    def pvr_artwork_options(self, title, channel="", genre=""):
        '''options for pvr metadata for specific item'''
        return self.pvrart.pvr_artwork_options(title, channel, genre)

    @use_cache(14)
    def get_channellogo(self, channelname):
        '''get channellogo for the given channel name'''
        return self.channellogos.get_channellogo(channelname)

    def get_studio_logo(self, studio):
        '''get studio logo for the given studio'''
        # dont use cache at this level because of changing logospath
        return self.studiologos.get_studio_logo(studio, self.studiologos_path)

    @property
    def studiologos_path(self):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        return self._studiologos_path

    @studiologos_path.setter
    def studiologos_path(self, value):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        self._studiologos_path = value

    def get_animated_artwork(self,
                             imdb_id,
                             manual_select=False,
                             ignore_cache=False):
        '''get animated artwork, perform extra check if local version still exists'''
        artwork = self.animatedart.get_animated_artwork(
            imdb_id, manual_select, ignore_cache=ignore_cache)
        if not (manual_select or ignore_cache):
            refresh_needed = False
            if artwork.get("animatedposter") and not xbmcvfs.exists(
                    artwork["animatedposter"]):
                refresh_needed = True
            if artwork.get("animatedfanart") and not xbmcvfs.exists(
                    artwork["animatedfanart"]):
                refresh_needed = True
            if refresh_needed:
                artwork = self.animatedart.get_animated_artwork(
                    imdb_id, manual_select, ignore_cache=True)
        return {"art": artwork}

    @use_cache(14)
    def get_omdb_info(self, imdb_id="", title="", year="", content_type=""):
        '''Get (kodi compatible formatted) metadata from OMDB, including Rotten tomatoes details'''
        title = title.split(" (")[0]  # strip year appended to title
        result = {}
        if imdb_id:
            result = self.omdb.get_details_by_imdbid(imdb_id)
        elif title and content_type in [
                "seasons", "season", "episodes", "episode", "tvshows", "tvshow"
        ]:
            result = self.omdb.get_details_by_title(title, "", "tvshows")
        elif title and year:
            result = self.omdb.get_details_by_title(title, year, content_type)
        if result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    @use_cache(7)
    def get_top250_rating(self, imdb_id):
        '''get the position in the IMDB top250 for the given IMDB ID'''
        return self.imdb.get_top250_rating(imdb_id)

    @use_cache(14)
    def get_duration(self, duration):
        '''helper to get a formatted duration'''
        if isinstance(duration, (str, unicode)) and ":" in duration:
            dur_lst = duration.split(":")
            return {
                "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]),
                "Duration.Hours": dur_lst[0],
                "Duration.Minutes": dur_lst[1],
                "Runtime": str((int(dur_lst[0]) * 60) + int(dur_lst[1])),
            }
        else:
            return _get_duration(duration)

    @use_cache(2)
    def get_tvdb_details(self, imdbid="", tvdbid=""):
        '''get metadata from tvdb by providing a tvdbid or tmdbid'''
        result = {}
        self.thetvdb.days_ahead = 365
        if not tvdbid and imdbid and not imdbid.startswith("tt"):
            # assume imdbid is actually a tvdbid...
            tvdbid = imdbid
        if tvdbid:
            result = self.thetvdb.get_series(tvdbid)
        elif imdbid:
            result = self.thetvdb.get_series_by_imdb_id(imdbid)
        if result:
            if result["status"] == "Continuing":
                # include next episode info
                result["nextepisode"] = self.thetvdb.get_nextaired_episode(
                    result["tvdb_id"])
            # include last episode info
            result["lastepisode"] = self.thetvdb.get_last_episode_for_series(
                result["tvdb_id"])
            result["status"] = self.translate_string(result["status"])
            if result.get("runtime"):
                result["runtime"] = result["runtime"] / 60
                result.update(_get_duration(result["runtime"]))
        return result

    @use_cache(14)
    def get_imdbtvdb_id(self,
                        title,
                        content_type,
                        year="",
                        imdbid="",
                        tvshowtitle=""):
        '''try to figure out the imdbnumber and/or tvdbid'''
        tvdbid = ""
        if content_type in ["seasons", "episodes"] or tvshowtitle:
            title = tvshowtitle
            content_type = "tvshows"
        if imdbid and not imdbid.startswith("tt"):
            if content_type in ["tvshows", "seasons", "episodes"]:
                tvdbid = imdbid
                imdbid = ""
        if not imdbid and year:
            imdbid = self.get_omdb_info("", title, year,
                                        content_type).get("imdbnumber", "")
        if not imdbid:
            # repeat without year
            imdbid = self.get_omdb_info("", title, "",
                                        content_type).get("imdbnumber", "")
        # return results
        return (imdbid, tvdbid)

    def translate_string(self, _str):
        '''translate the received english string from the various sources like tvdb, tmbd etc'''
        translation = _str
        _str = _str.lower()
        if "continuing" in _str:
            translation = self.addon.getLocalizedString(32037)
        elif "ended" in _str:
            translation = self.addon.getLocalizedString(32038)
        elif "released" in _str:
            translation = self.addon.getLocalizedString(32040)
        return translation
Exemplo n.º 8
0
class MainModule:
    '''mainmodule provides the script methods for the skinhelper addon'''

    def __init__(self):
        '''Initialization and main code run'''
        self.win = xbmcgui.Window(10000)
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.kodidb = KodiDb()
        self.cache = SimpleCache()

        self.params = self.get_params()
        log_msg("MainModule called with parameters: %s" % self.params)
        action = self.params.get("action", "")
        # launch module for action provided by this script
        try:
            getattr(self, action)()
        except AttributeError:
            log_exception(__name__, "No such action: %s" % action)
        except Exception as exc:
            log_exception(__name__, exc)
        finally:
            xbmc.executebuiltin("dialog.Close(busydialog)")

        # do cleanup
        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances on exit'''
        self.cache.close()
        del self.win
        del self.addon
        del self.kodidb
        log_msg("MainModule exited")

    @classmethod
    def get_params(self):
        '''extract the params from the called script path'''
        params = {}
        for arg in sys.argv[1:]:
            paramname = arg.split('=')[0]
            paramvalue = arg.replace(paramname + "=", "")
            paramname = paramname.lower()
            if paramname == "action":
                paramvalue = paramvalue.lower()
            params[paramname] = paramvalue
        return params

    def deprecated_method(self, newaddon):
        '''
            used when one of the deprecated methods is called
            print warning in log and call the external script with the same parameters
        '''
        action = self.params.get("action")
        log_msg("Deprecated method: %s. Please call %s directly" % (action, newaddon), xbmc.LOGWARNING)
        paramstring = ""
        for key, value in self.params.iteritems():
            paramstring += ",%s=%s" % (key, value)
        if xbmc.getCondVisibility("System.HasAddon(%s)" % newaddon):
            xbmc.executebuiltin("RunAddon(%s%s)" % (newaddon, paramstring))
        else:
            # trigger install of the addon
            if KODI_VERSION > 16:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    @staticmethod
    def musicsearch():
        '''helper to go directly to music search dialog'''
        xbmc.executebuiltin("ActivateWindow(Music)")
        xbmc.executebuiltin("SendClick(8)")

    def setview(self):
        '''sets the selected viewmode for the container'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        content_type = get_current_content_type()
        if not content_type:
            content_type = "files"
        current_view = xbmc.getInfoLabel("Container.Viewmode").decode("utf-8")
        view_id, view_label = self.selectview(content_type, current_view)
        current_forced_view = xbmc.getInfoLabel("Skin.String(SkinHelper.ForcedViews.%s)" % content_type)

        if view_id is not None:
            # also store forced view
            if (content_type and current_forced_view and current_forced_view != "None" and
                    xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.ForcedViews.Enabled)")):
                xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s,%s)" % (content_type, view_id))
                xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" % (content_type, view_label))
                self.win.setProperty("SkinHelper.ForcedView", view_id)
                if not xbmc.getCondVisibility("Control.HasFocus(%s)" % current_forced_view):
                    xbmc.sleep(100)
                    xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)
                    xbmc.executebuiltin("SetFocus(%s)" % view_id)
            else:
                self.win.clearProperty("SkinHelper.ForcedView")
            # set view
            xbmc.executebuiltin("Container.SetViewMode(%s)" % view_id)

    def selectview(self, content_type="other", current_view=None, display_none=False):
        '''reads skinfile with all views to present a dialog to choose from'''
        cur_view_select_id = None
        label = ""
        all_views = []
        if display_none:
            listitem = xbmcgui.ListItem(label="None")
            listitem.setProperty("id", "None")
            all_views.append(listitem)
        # read the special skin views file
        views_file = xbmc.translatePath('special://skin/extras/views.xml').decode("utf-8")
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            itemcount = 0
            for view in listing:
                label = xbmc.getLocalizedString(int(view.attributes['languageid'].nodeValue))
                viewid = view.attributes['value'].nodeValue
                mediatypes = view.attributes['type'].nodeValue.lower().split(",")
                if label.lower() == current_view.lower() or viewid == current_view:
                    cur_view_select_id = itemcount
                    if display_none:
                        cur_view_select_id += 1
                if (("all" in mediatypes or content_type.lower() in mediatypes) and
                    (not "!" + content_type.lower() in mediatypes) and not
                        xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.view.Disabled.%s)" % viewid)):
                    image = "special://skin/extras/viewthumbs/%s.jpg" % viewid
                    listitem = xbmcgui.ListItem(label=label, iconImage=image)
                    listitem.setProperty("viewid", viewid)
                    listitem.setProperty("icon", image)
                    all_views.append(listitem)
                    itemcount += 1
        dialog = DialogSelect("DialogSelect.xml", "", listing=all_views,
                              windowtitle=self.addon.getLocalizedString(32012), richlayout=True)
        dialog.autofocus_id = cur_view_select_id
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            viewid = result.getProperty("viewid")
            label = result.getLabel().decode("utf-8")
            return (viewid, label)
        else:
            return (None, None)

    # pylint: disable-msg=too-many-local-variables
    def enableviews(self):
        '''show select dialog to enable/disable views'''
        all_views = []
        views_file = xbmc.translatePath('special://skin/extras/views.xml').decode("utf-8")
        richlayout = self.params.get("richlayout", "") == "true"
        if xbmcvfs.exists(views_file):
            doc = parse(views_file)
            listing = doc.documentElement.getElementsByTagName('view')
            for view in listing:
                view_id = view.attributes['value'].nodeValue
                label = xbmc.getLocalizedString(int(view.attributes['languageid'].nodeValue))
                desc = label + " (" + str(view_id) + ")"
                image = "special://skin/extras/viewthumbs/%s.jpg" % view_id
                listitem = xbmcgui.ListItem(label=label, label2=desc, iconImage=image)
                listitem.setProperty("viewid", view_id)
                if not xbmc.getCondVisibility("Skin.HasSetting(SkinHelper.view.Disabled.%s)" % view_id):
                    listitem.select(selected=True)
                excludefromdisable = False
                try:
                    excludefromdisable = view.attributes['excludefromdisable'].nodeValue == "true"
                except Exception:
                    pass
                if not excludefromdisable:
                    all_views.append(listitem)

        dialog = DialogSelect(
            "DialogSelect.xml",
            "",
            listing=all_views,
            windowtitle=self.addon.getLocalizedString(32013),
            multiselect=True, richlayout=richlayout)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            for item in result:
                view_id = item.getProperty("viewid")
                if item.isSelected():
                    # view is enabled
                    xbmc.executebuiltin("Skin.Reset(SkinHelper.view.Disabled.%s)" % view_id)
                else:
                    # view is disabled
                    xbmc.executebuiltin("Skin.SetBool(SkinHelper.view.Disabled.%s)" % view_id)
    # pylint: enable-msg=too-many-local-variables

    def setforcedview(self):
        '''helper that sets a forced view for a specific content type'''
        content_type = self.params.get("contenttype")
        if content_type:
            current_view = xbmc.getInfoLabel("Skin.String(SkinHelper.ForcedViews.%s)" % content_type)
            if not current_view:
                current_view = "0"
            view_id, view_label = self.selectview(content_type, current_view, True)
            if view_id or view_label:
                xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s,%s)" % (content_type, view_id))
                xbmc.executebuiltin("Skin.SetString(SkinHelper.ForcedViews.%s.label,%s)" % (content_type, view_label))

    @staticmethod
    def get_youtube_listing(searchquery):
        '''get items from youtube plugin by query'''
        lib_path = u"plugin://plugin.video.youtube/kodion/search/query/?q=%s" % searchquery
        return KodiDb().files(lib_path)

    def searchyoutube(self):
        '''helper to search youtube for the given title'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        title = self.params.get("title", "")
        window_header = self.params.get("header", "")
        results = []
        for media in self.get_youtube_listing(title):
            if not media["filetype"] == "directory":
                label = media["label"]
                label2 = media["plot"]
                image = ""
                if media.get('art'):
                    if media['art'].get('thumb'):
                        image = (media['art']['thumb'])
                listitem = xbmcgui.ListItem(label=label, label2=label2, iconImage=image)
                listitem.setProperty("path", media["file"])
                results.append(listitem)

        # finished lookup - display listing with results
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml", "", listing=results, windowtitle=window_header,
                              multiselect=False, richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            if xbmc.getCondVisibility(
                    "Window.IsActive(script-skin_helper_service-CustomInfo.xml) | "
                    "Window.IsActive(movieinformation)"):
                xbmc.executebuiltin("Dialog.Close(movieinformation)")
                xbmc.executebuiltin("Dialog.Close(script-skin_helper_service-CustomInfo.xml)")
                xbmc.sleep(1000)
            xbmc.executebuiltin('PlayMedia("%s")' % result.getProperty("path"))
            del result

    def getcastmedia(self):
        '''helper to show a dialog with all media for a specific actor'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        name = self.params.get("name", "")
        window_header = self.params.get("name", "")
        results = []
        items = self.kodidb.castmedia(name)
        items = process_method_on_list(self.kodidb.prepare_listitem, items)
        for item in items:
            if item["file"].startswith("videodb://"):
                item["file"] = "ActivateWindow(Videos,%s,return)" % item["file"]
            else:
                item["file"] = 'PlayMedia("%s")' % item["file"]
            results.append(self.kodidb.create_listitem(item, False))
        # finished lookup - display listing with results
        xbmc.executebuiltin("dialog.Close(busydialog)")
        dialog = DialogSelect("DialogSelect.xml", "", listing=results, windowtitle=window_header, richlayout=True)
        dialog.doModal()
        result = dialog.result
        del dialog
        if result:
            while xbmc.getCondVisibility("System.HasModalDialog"):
                xbmc.executebuiltin("Action(Back)")
                xbmc.sleep(300)
            xbmc.executebuiltin(result.getfilename())
            del result

    def setfocus(self):
        '''helper to set focus on a list or control'''
        control = self.params.get("control")
        fallback = self.params.get("fallback")
        position = self.params.get("position", "0")
        relativeposition = self.params.get("relativeposition")
        if relativeposition:
            position = int(relativeposition) - 1
        count = 0
        if control:
            while not xbmc.getCondVisibility("Control.HasFocus(%s)" % control):
                if xbmc.getCondVisibility("Window.IsActive(busydialog)"):
                    xbmc.sleep(150)
                    continue
                elif count == 20 or (xbmc.getCondVisibility(
                        "!Control.IsVisible(%s) | "
                        "!IntegerGreaterThan(Container(%s).NumItems,0)" % (control, control))):
                    if fallback:
                        xbmc.executebuiltin("Control.SetFocus(%s)" % fallback)
                    break
                else:
                    xbmc.executebuiltin("Control.SetFocus(%s,%s)" % (control, position))
                    xbmc.sleep(50)
                    count += 1

    def setwidgetcontainer(self):
        '''helper that reports the current selected widget container/control'''
        controls = self.params.get("controls", "").split("-")
        if controls:
            xbmc.sleep(50)
            for i in range(10):
                for control in controls:
                    if xbmc.getCondVisibility("Control.IsVisible(%s) + IntegerGreaterThan(Container(%s).NumItems,0)"
                                              % (control, control)):
                        self.win.setProperty("SkinHelper.WidgetContainer", control)
                        return
                xbmc.sleep(50)
        self.win.clearProperty("SkinHelper.WidgetContainer")

    def saveskinimage(self):
        '''let the user select an image and save it to addon_data for easy backup'''
        skinstring = self.params.get("skinstring", "")
        allow_multi = self.params.get("multi", "") == "true"
        header = self.params.get("header", "")
        value = SkinSettings().save_skin_image(skinstring, allow_multi, header)
        if value:
            xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring.encode("utf-8"), value.encode("utf-8")))

    @staticmethod
    def checkskinsettings():
        '''performs check of all default skin settings and labels'''
        SkinSettings().correct_skin_settings()

    def setskinsetting(self):
        '''allows the user to set a skin setting with a select dialog'''
        setting = self.params.get("setting", "")
        org_id = self.params.get("id", "")
        if "$" in org_id:
            org_id = xbmc.getInfoLabel(org_id).decode("utf-8")
        header = self.params.get("header", "")
        SkinSettings().set_skin_setting(setting=setting, window_header=header, original_id=org_id)

    def setskinconstant(self):
        '''allows the user to set a skin constant with a select dialog'''
        setting = self.params.get("setting", "")
        value = self.params.get("value", "")
        header = self.params.get("header", "")
        SkinSettings().set_skin_constant(setting, header, value)

    def setskinconstants(self):
        '''allows the skinner to set multiple skin constants'''
        settings = self.params.get("settings", "").split("|")
        values = self.params.get("values", "").split("|")
        SkinSettings().set_skin_constants(settings, values)

    def setskinshortcutsproperty(self):
        '''allows the user to make a setting for skinshortcuts using the special skinsettings dialogs'''
        setting = self.params.get("setting", "")
        prop = self.params.get("property", "")
        header = self.params.get("header", "")
        SkinSettings().set_skinshortcuts_property(setting, header, prop)

    def togglekodisetting(self):
        '''toggle kodi setting'''
        settingname = self.params.get("setting", "")
        cur_value = xbmc.getCondVisibility("system.getbool(%s)" % settingname)
        if cur_value:
            new_value = "false"
        else:
            new_value = "true"
        xbmc.executeJSONRPC(
            '{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"%s","value":%s}}' %
            (settingname, new_value))

    def setkodisetting(self):
        '''set kodi setting'''
        settingname = self.params.get("setting", "")
        value = self.params.get("value", "")
        is_int = False
        try:
            valueint = int(value)
            is_int = True
            del valueint
        except Exception:
            pass
        if value.lower() == "true":
            value = 'true'
        elif value.lower() == "false":
            value = 'false'
        elif is_int:
            value = '"%s"' % value
        xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue",\
            "params":{"setting":"%s","value":%s}}' % (settingname, value))

    def playtrailer(self):
        '''auto play windowed trailer inside video listing'''
        if not xbmc.getCondVisibility("Player.HasMedia | Container.Scrolling | Container.OnNext | "
                                      "Container.OnPrevious | !IsEmpty(Window(Home).Property(traileractionbusy))"):
            self.win.setProperty("traileractionbusy", "traileractionbusy")
            widget_container = self.params.get("widgetcontainer", "")
            trailer_mode = self.params.get("mode", "").replace("auto_", "")
            allow_youtube = self.params.get("youtube", "") == "true"
            if not trailer_mode:
                trailer_mode = "windowed"
            if widget_container:
                widget_container_prefix = "Container(%s)." % widget_container
            else:
                widget_container_prefix = ""

            li_title = xbmc.getInfoLabel("%sListItem.Title" % widget_container_prefix).decode('utf-8')
            li_trailer = xbmc.getInfoLabel("%sListItem.Trailer" % widget_container_prefix).decode('utf-8')
            if not li_trailer and allow_youtube:
                youtube_result = self.get_youtube_listing("%s Trailer" % li_title)
                if youtube_result:
                    li_trailer = youtube_result[0].get("file")
            # always wait a bit to prevent trailer start playing when we're scrolling the list
            xbmc.Monitor().waitForAbort(3)
            if li_trailer and (li_title == xbmc.getInfoLabel("%sListItem.Title"
                                                             % widget_container_prefix).decode('utf-8')):
                if trailer_mode == "fullscreen" and li_trailer:
                    xbmc.executebuiltin('PlayMedia("%s")' % li_trailer)
                else:
                    xbmc.executebuiltin('PlayMedia("%s",1)' % li_trailer)
                self.win.setProperty("TrailerPlaying", trailer_mode)
            self.win.clearProperty("traileractionbusy")

    def colorpicker(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.colorpicker")

    def backup(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restore(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def reset(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def colorthemes(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def createcolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def restorecolortheme(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.skinbackup")

    def conditionalbackgrounds(self):
        '''legacy'''
        self.deprecated_method("script.skin.helper.backgrounds")

    def splashscreen(self):
        '''helper to show a user defined splashscreen in the skin'''
        import time
        splashfile = self.params.get("file", "")
        duration = int(self.params.get("duration", 5))
        if (splashfile.lower().endswith("jpg") or splashfile.lower().endswith("gif") or
                splashfile.lower().endswith("png") or splashfile.lower().endswith("tiff")):
            # this is an image file
            self.win.setProperty("SkinHelper.SplashScreen", splashfile)
            # for images we just wait for X seconds to close the splash again
            start_time = time.time()
            while (time.time() - start_time) <= duration:
                xbmc.sleep(500)
        else:
            # for video or audio we have to wait for the player to finish...
            xbmc.Player().play(splashfile, windowed=True)
            xbmc.sleep(500)
            while xbmc.getCondVisibility("Player.HasMedia"):
                xbmc.sleep(150)
        # replace startup window with home
        startupwindow = xbmc.getInfoLabel("System.StartupWindow")
        xbmc.executebuiltin("ReplaceWindow(%s)" % startupwindow)
        autostart_playlist = xbmc.getInfoLabel("$ESCINFO[Skin.String(autostart_playlist)]")
        if autostart_playlist:
            xbmc.executebuiltin("PlayMedia(%s)" % autostart_playlist)

    def videosearch(self):
        '''show the special search dialog'''
        xbmc.executebuiltin("ActivateWindow(busydialog)")
        from resources.lib.searchdialog import SearchDialog
        search_dialog = SearchDialog("script-skin_helper_service-CustomSearch.xml",
                                     self.addon.getAddonInfo('path').decode("utf-8"), "Default", "1080i")
        search_dialog.doModal()
        del search_dialog

    def showinfo(self):
        '''shows our special videoinfo dialog'''
        dbid = self.params.get("dbid", "")
        dbtype = self.params.get("dbtype", "")
        from infodialog import show_infodialog
        show_infodialog(dbid, dbtype)

    def deletedir(self):
        '''helper to delete a directory, input can be normal filesystem path or vfs'''
        del_path = self.params.get("path")
        if del_path:
            ret = xbmcgui.Dialog().yesno(heading=xbmc.getLocalizedString(122),
                                         line1=u"%s[CR]%s" % (xbmc.getLocalizedString(125), del_path))
            if ret:
                success = recursive_delete_dir(del_path)
                if success:
                    xbmcgui.Dialog().ok(heading=xbmc.getLocalizedString(19179),
                                        line1=self.addon.getLocalizedString(32014))
                else:
                    xbmcgui.Dialog().ok(heading=xbmc.getLocalizedString(16205),
                                        line1=xbmc.getLocalizedString(32015))

    def overlaytexture(self):
        '''legacy: helper to let the user choose a background overlay from a skin defined folder'''
        skinstring = self.params.get("skinstring", "BackgroundOverlayTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.backgroundoverlays"
        self.params["customfolder"] = "special://skin/extras/bgoverlays/"
        self.params["allowmulti"] = "false"
        self.params["header"] = self.addon.getLocalizedString(32002)
        self.selectimage()

    def busytexture(self):
        '''legacy: helper which lets the user select a busy spinner from predefined spinners in the skin'''
        skinstring = self.params.get("skinstring", "SkinHelper.SpinnerTexture")
        self.params["skinstring"] = skinstring
        self.params["resourceaddon"] = "resource.images.busyspinners"
        self.params["customfolder"] = "special://skin/extras/busy_spinners/"
        self.params["allowmulti"] = "true"
        self.params["header"] = self.addon.getLocalizedString(32006)
        self.selectimage()

    def selectimage(self):
        '''helper which lets the user select an image or imagepath from resourceaddons or custom path'''
        skinsettings = SkinSettings()
        skinstring = self.params.get("skinstring", "")
        skinshortcutsprop = self.params.get("skinshortcutsproperty", "")
        current_value = self.params.get("currentvalue", "")
        resource_addon = self.params.get("resourceaddon", "")
        allow_multi = self.params.get("allowmulti", "false") == "true"
        windowheader = self.params.get("header", "")
        skinhelper_backgrounds = self.params.get("skinhelperbackgrounds", "false") == "true"
        label, value = skinsettings.select_image(
            skinstring, allow_multi=allow_multi, windowheader=windowheader, resource_addon=resource_addon,
            skinhelper_backgrounds=skinhelper_backgrounds, current_value=current_value)
        if label:
            if skinshortcutsprop:
                # write value to skinshortcuts prop
                from skinshortcuts import set_skinshortcuts_property
                set_skinshortcuts_property(skinshortcutsprop, value, label)
            else:
                # write the values to skin strings
                if value.startswith("$INFO"):
                    # we got an dynamic image from window property
                    skinsettings.set_skin_variable(skinstring, value)
                    value = "$VAR[%s]" % skinstring
                skinstring = skinstring.encode("utf-8")
                label = label.encode("utf-8")
                xbmc.executebuiltin("Skin.SetString(%s.label,%s)" % (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s.name,%s)" % (skinstring, label))
                xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, value))
                xbmc.executebuiltin("Skin.SetString(%s.path,%s)" % (skinstring, value))
        del skinsettings

    def dialogok(self):
        '''helper to show an OK dialog with a message'''
        headertxt = self.params.get("header")
        bodytxt = self.params.get("message")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        dialog = xbmcgui.Dialog()
        dialog.ok(heading=headertxt, line1=bodytxt)
        del dialog

    def dialogyesno(self):
        '''helper to show a YES/NO dialog with a message'''
        headertxt = self.params.get("header")
        bodytxt = self.params.get("message")
        yesactions = self.params.get("yesaction", "").split("|")
        noactions = self.params.get("noaction", "").split("|")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        if xbmcgui.Dialog().yesno(heading=headertxt, line1=bodytxt):
            for action in yesactions:
                xbmc.executebuiltin(action.encode("utf-8"))
        else:
            for action in noactions:
                xbmc.executebuiltin(action.encode("utf-8"))

    def textviewer(self):
        '''helper to show a textviewer dialog with a message'''
        headertxt = self.params.get("header", "")
        bodytxt = self.params.get("message", "")
        if bodytxt.startswith(" "):
            bodytxt = bodytxt[1:]
        if headertxt.startswith(" "):
            headertxt = headertxt[1:]
        xbmcgui.Dialog().textviewer(headertxt, bodytxt)

    def fileexists(self):
        '''helper to let the skinner check if a file exists
        and write the outcome to a window prop or skinstring'''
        filename = self.params.get("file")
        skinstring = self.params.get("skinstring")
        windowprop = self.params.get("winprop")
        if xbmcvfs.exists(filename):
            if windowprop:
                self.win.setProperty(windowprop, "exists")
            if skinstring:
                xbmc.executebuiltin("Skin.SetString(%s,exists)" % skinstring)
        else:
            if windowprop:
                self.win.clearProperty(windowprop)
            if skinstring:
                xbmc.executebuiltin("Skin.Reset(%s)" % skinstring)

    def stripstring(self):
        '''helper to allow the skinner to strip a string and write results to a skin string'''
        splitchar = self.params.get("splitchar")
        if splitchar.upper() == "[SPACE]":
            splitchar = " "
        skinstring = self.params.get("string")
        if not skinstring:
            skinstring = self.params.get("skinstring")
        output = self.params.get("output")
        index = self.params.get("index", 0)
        skinstring = skinstring.split(splitchar)[int(index)]
        self.win.setProperty(output, skinstring)

    def getfilename(self, filename=""):
        '''helper to display a sanitized filename in the vidoeinfo dialog'''
        output = self.params.get("output")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("ListItem.FileName")
        if "filename=" in filename:
            url_params = dict(urlparse.parse_qsl(filename))
            filename = url_params.get("filename")
        self.win.setProperty(output, filename)

    def getplayerfilename(self):
        '''helper to parse the filename from a plugin (e.g. emby) filename'''
        filename = xbmc.getInfoLabel("Player.FileNameAndPath")
        if not filename:
            filename = xbmc.getInfoLabel("Player.FileName")
        self.getfilename(filename)

    def getpercentage(self):
        '''helper to calculate the percentage of 2 numbers and write results to a skinstring'''
        total = int(params.get("total"))
        count = int(params.get("count"))
        roundsteps = self.params.get("roundsteps")
        skinstring = self.params.get("skinstring")
        percentage = int(round((1.0 * count / total) * 100))
        if roundsteps:
            roundsteps = int(roundsteps)
            percentage = percentage + (roundsteps - percentage) % roundsteps
        xbmc.executebuiltin("Skin.SetString(%s,%s)" % (skinstring, percentage))

    def setresourceaddon(self):
        '''helper to let the user choose a resource addon and set that as skin string'''
        from resourceaddons import setresourceaddon
        addontype = self.params.get("addontype", "")
        skinstring = self.params.get("skinstring", "")
        setresourceaddon(addontype, skinstring)

    def checkresourceaddons(self):
        '''allow the skinner to perform a basic check if some required resource addons are available'''
        from resourceaddons import checkresourceaddons
        addonslist = self.params.get("addonslist", [])
        if addonslist:
            addonslist = addonslist.split("|")
        checkresourceaddons(addonslist)
class PluginContent:
    '''Hidden plugin entry point providing some helper features'''
    params = {}
    win = None

    def __init__(self):
        self.cache = SimpleCache()
        self.mutils = MetadataUtils()
        self.win = xbmcgui.Window(10000)
        try:
            self.params = dict(
                urlparse.parse_qsl(sys.argv[2].replace(
                    '?', '').lower().decode("utf-8")))
            log_msg("plugin called with parameters: %s" % self.params)
            self.main()
        except Exception as exc:
            log_exception(__name__, exc)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

        # cleanup when done processing
        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances'''
        self.cache.close()
        self.mutils.close()
        del self.mutils
        del self.win

    def main(self):
        '''main action, load correct function'''
        action = self.params.get("action", "")
        if self.win.getProperty("SkinHelperShutdownRequested"):
            # do not proceed if kodi wants to exit
            log_msg(
                "%s --> Not forfilling request: Kodi is exiting" % __name__,
                xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
        else:
            try:
                if hasattr(self.__class__, action):
                    # launch module for action provided by this plugin
                    getattr(self, action)()
                else:
                    # legacy (widget) path called !!!
                    self.load_widget()
            except Exception as exc:
                log_exception(__name__, exc)

    def load_widget(self):
        '''legacy entrypoint called (widgets are moved to seperate addon), start redirect...'''
        action = self.params.get("action", "")
        newaddon = "script.skin.helper.widgets"
        log_msg(
            "Deprecated method: %s. Please reassign your widgets to get rid of this message. -"
            "This automatic redirect will be removed in the future" % (action),
            xbmc.LOGWARNING)
        paramstring = ""
        for key, value in self.params.iteritems():
            paramstring += ",%s=%s" % (key, value)
        if getCondVisibility("System.HasAddon(%s)" % newaddon):
            # TEMP !!! for backwards compatability reasons only - to be removed in the near future!!
            import imp
            addon = xbmcaddon.Addon(newaddon)
            addon_path = addon.getAddonInfo('path').decode("utf-8")
            imp.load_source('plugin', os.path.join(addon_path, "plugin.py"))
            from plugin import main
            main.Main()
            del addon
        else:
            # trigger install of the addon
            if KODI_VERSION > 16:
                xbmc.executebuiltin("InstallAddon(%s)" % newaddon)
            else:
                xbmc.executebuiltin("RunPlugin(plugin://%s)" % newaddon)

    def playchannel(self):
        '''play channel from widget helper'''
        params = {"item": {"channelid": int(self.params["channelid"])}}
        self.mutils.kodidb.set_json("Player.Open", params)

    def playrecording(self):
        '''retrieve the recording and play to get resume working'''
        recording = self.mutils.kodidb.recording(self.params["recordingid"])
        params = {"item": {"recordingid": recording["recordingid"]}}
        self.mutils.kodidb.set_json("Player.Open", params)
        # manually seek because passing resume to the player json cmd doesn't seem to work
        if recording["resume"].get("position"):
            for i in range(50):
                if getCondVisibility("Player.HasVideo"):
                    break
                xbmc.sleep(50)
            xbmc.Player().seekTime(recording["resume"].get("position"))

    def launch(self):
        '''launch any builtin action using a plugin listitem'''
        if "runscript" in self.params["path"]:
            self.params["path"] = self.params["path"].replace("?", ",")
        xbmc.executebuiltin(self.params["path"])

    def playalbum(self):
        '''helper to play an entire album'''
        xbmc.executeJSONRPC(
            '{ "jsonrpc": "2.0", "method": "Player.Open", "params": { "item": { "albumid": %d } }, "id": 1 }'
            % int(self.params["albumid"]))

    def smartshortcuts(self):
        '''called from skinshortcuts to retrieve listing of all smart shortcuts'''
        import skinshortcuts
        skinshortcuts.get_smartshortcuts(self.params.get("path", ""))

    @staticmethod
    def backgrounds():
        '''called from skinshortcuts to retrieve listing of all backgrounds'''
        import skinshortcuts
        skinshortcuts.get_backgrounds()

    def widgets(self):
        '''called from skinshortcuts to retrieve listing of all widgetss'''
        import skinshortcuts
        skinshortcuts.get_widgets(self.params.get("path", ""),
                                  self.params.get("sublevel", ""))

    def resourceimages(self):
        '''retrieve listing of specific resource addon images'''
        from resourceaddons import get_resourceimages
        addontype = self.params.get("addontype", "")
        for item in get_resourceimages(addontype, True):
            listitem = xbmcgui.ListItem(item[0],
                                        label2=item[2],
                                        path=item[1],
                                        iconImage=item[3])
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item[1],
                                        listitem=listitem,
                                        isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def extrafanart(self):
        '''helper to display extrafanart in multiimage control in the skin'''
        fanarts = eval(self.params["fanarts"])
        # process extrafanarts
        for count, item in enumerate(fanarts):
            listitem = xbmcgui.ListItem("fanart%s" % count, path=item)
            listitem.setProperty('mimetype', 'image/jpeg')
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item,
                                        listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def extraposter(self):
        '''helper to display extraposter in multiimage control in the skin'''
        posters = eval(self.params["posters"])
        # process extraposters
        for count, item in enumerate(posters):
            listitem = xbmcgui.ListItem("poster%s" % count, path=item)
            listitem.setProperty('mimetype', 'image/jpeg')
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                        url=item,
                                        listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def genrebackground(self):
        '''helper to display images for a specific genre in multiimage control in the skin'''
        genre = self.params.get("genre").split(".")[0]
        arttype = self.params.get("arttype", "fanart")
        randomize = self.params.get("random", "false") == "true"
        mediatype = self.params.get("mediatype", "movies")
        if genre and genre != "..":
            filters = [{"operator": "is", "field": "genre", "value": genre}]
            if randomize:
                sort = {"method": "random", "order": "descending"}
            else:
                sort = {"method": "sorttitle", "order": "ascending"}
            items = getattr(self.mutils.kodidb, mediatype)(sort=sort,
                                                           filters=filters,
                                                           limits=(0, 50))
            for item in items:
                image = self.mutils.get_clean_image(item["art"].get(
                    arttype, ""))
                if image:
                    image = self.mutils.get_clean_image(item["art"][arttype])
                    listitem = xbmcgui.ListItem(image, path=image)
                    listitem.setProperty('mimetype', 'image/jpeg')
                    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                                url=image,
                                                listitem=listitem)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcastmedia(self):
        '''helper to display get all media for a specific actor'''
        name = self.params.get("name")
        if name:
            all_items = self.mutils.kodidb.castmedia(name)
            all_items = self.mutils.process_method_on_list(
                self.mutils.kodidb.prepare_listitem, all_items)
            all_items = self.mutils.process_method_on_list(
                self.mutils.kodidb.create_listitem, all_items)
            xbmcplugin.addDirectoryItems(int(sys.argv[1]), all_items,
                                         len(all_items))
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def getcast(self):
        '''helper to get all cast for a given media item'''
        db_id = None
        all_cast = []
        all_cast_names = list()
        cache_str = ""
        download_thumbs = self.params.get("downloadthumbs", "") == "true"
        extended_cast_action = self.params.get("castaction",
                                               "") == "extendedinfo"
        movie = self.params.get("movie")
        tvshow = self.params.get("tvshow")
        episode = self.params.get("episode")
        movieset = self.params.get("movieset")

        try:  # try to parse db_id
            if movieset:
                cache_str = "movieset.castcache-%s-%s" % (
                    self.params["movieset"], download_thumbs)
                db_id = int(movieset)
            elif tvshow:
                cache_str = "tvshow.castcache-%s-%s" % (self.params["tvshow"],
                                                        download_thumbs)
                db_id = int(tvshow)
            elif movie:
                cache_str = "movie.castcache-%s-%s" % (self.params["movie"],
                                                       download_thumbs)
                db_id = int(movie)
            elif episode:
                cache_str = "episode.castcache-%s-%s" % (
                    self.params["episode"], download_thumbs)
                db_id = int(episode)
        except Exception:
            pass

        cachedata = self.cache.get(cache_str)
        if cachedata:
            # get data from cache
            all_cast = cachedata
        else:
            # retrieve data from json api...
            if movie and db_id:
                all_cast = self.mutils.kodidb.movie(db_id)["cast"]
            elif movie and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": movie
                }]
                result = self.mutils.kodidb.movies(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif tvshow and db_id:
                all_cast = self.mutils.kodidb.tvshow(db_id)["cast"]
            elif tvshow and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": tvshow
                }]
                result = self.mutils.kodidb.tvshows(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif episode and db_id:
                all_cast = self.mutils.kodidb.episode(db_id)["cast"]
            elif episode and not db_id:
                filters = [{
                    "operator": "is",
                    "field": "title",
                    "value": episode
                }]
                result = self.mutils.kodidb.episodes(filters=filters)
                all_cast = result[0]["cast"] if result else []
            elif movieset:
                if not db_id:
                    for item in self.mutils.kodidb.moviesets():
                        if item["title"].lower() == movieset.lower():
                            db_id = item["setid"]
                if db_id:
                    json_result = self.mutils.kodidb.movieset(
                        db_id, include_set_movies_fields=["cast"])
                    if "movies" in json_result:
                        for movie in json_result['movies']:
                            all_cast += movie['cast']

            # optional: download missing actor thumbs
            if all_cast and download_thumbs:
                for cast in all_cast:
                    if cast.get("thumbnail"):
                        cast["thumbnail"] = self.mutils.get_clean_image(
                            cast.get("thumbnail"))
                    if not cast.get("thumbnail"):
                        artwork = self.mutils.tmdb.get_actor(cast["name"])
                        cast["thumbnail"] = artwork.get("thumb", "")
            # lookup tmdb if item is requested that is not in local db
            if not all_cast:
                tmdbdetails = {}
                if movie and not db_id:
                    tmdbdetails = self.mutils.tmdb.search_movie(movie)
                elif tvshow and not db_id:
                    tmdbdetails = self.mutils.tmdb.search_tvshow(tvshow)
                if tmdbdetails.get("cast"):
                    all_cast = tmdbdetails["cast"]
            # save to cache
            self.cache.set(cache_str, all_cast)

        # process listing with the results...
        for cast in all_cast:
            if cast.get("name") not in all_cast_names:
                liz = xbmcgui.ListItem(label=cast.get("name"),
                                       label2=cast.get("role"),
                                       iconImage=cast.get("thumbnail"))
                if extended_cast_action:
                    url = "RunScript(script.extendedinfo,info=extendedactorinfo,name=%s)" % cast.get(
                        "name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % url
                    is_folder = False
                else:
                    url = "RunScript(script.skin.helper.service,action=getcastmedia,name=%s)" % cast.get(
                        "name")
                    url = "plugin://script.skin.helper.service/?action=launch&path=%s" % urlencode(
                        url)
                    is_folder = False
                all_cast_names.append(cast.get("name"))
                liz.setThumbnailImage(cast.get("thumbnail"))
                xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]),
                                            url=url,
                                            listitem=liz,
                                            isFolder=is_folder)
        xbmcplugin.endOfDirectory(int(sys.argv[1]))

    @staticmethod
    def alphabet():
        '''display an alphabet scrollbar in listings'''
        all_letters = []
        if xbmc.getInfoLabel("Container.NumItems"):
            for i in range(int(xbmc.getInfoLabel("Container.NumItems"))):
                all_letters.append(
                    xbmc.getInfoLabel("Listitem(%s).SortLetter" % i).upper())
            start_number = ""
            for number in ["2", "3", "4", "5", "6", "7", "8", "9"]:
                if number in all_letters:
                    start_number = number
                    break
            for letter in [
                    start_number, "A", "B", "C", "D", "E", "F", "G", "H", "I",
                    "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
                    "V", "W", "X", "Y", "Z"
            ]:
                if letter == start_number:
                    label = "#"
                else:
                    label = letter
                listitem = xbmcgui.ListItem(label=label)
                if letter not in all_letters:
                    lipath = "noop"
                    listitem.setProperty("NotAvailable", "true")
                else:
                    lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter
                xbmcplugin.addDirectoryItem(int(sys.argv[1]),
                                            lipath,
                                            listitem,
                                            isFolder=False)
        xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))

    def alphabetletter(self):
        '''used with the alphabet scrollbar to jump to a letter'''
        if KODI_VERSION > 16:
            xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]),
                                      succeeded=False,
                                      listitem=xbmcgui.ListItem())
        letter = self.params.get("letter", "").upper()
        jumpcmd = ""
        if letter in ["A", "B", "C", "2"]:
            jumpcmd = "2"
        elif letter in ["D", "E", "F", "3"]:
            jumpcmd = "3"
        elif letter in ["G", "H", "I", "4"]:
            jumpcmd = "4"
        elif letter in ["J", "K", "L", "5"]:
            jumpcmd = "5"
        elif letter in ["M", "N", "O", "6"]:
            jumpcmd = "6"
        elif letter in ["P", "Q", "R", "S", "7"]:
            jumpcmd = "7"
        elif letter in ["T", "U", "V", "8"]:
            jumpcmd = "8"
        elif letter in ["W", "X", "Y", "Z", "9"]:
            jumpcmd = "9"
        if jumpcmd:
            xbmc.executebuiltin("SetFocus(50)")
            for i in range(40):
                xbmc.executeJSONRPC(
                    '{ "jsonrpc": "2.0", "method": "Input.ExecuteAction",\
                    "params": { "action": "jumpsms%s" }, "id": 1 }' %
                    (jumpcmd))
                xbmc.sleep(50)
                if xbmc.getInfoLabel("ListItem.Sortletter").upper() == letter:
                    break
Exemplo n.º 10
0
class MetadataUtils(object):
    '''
        Provides all kind of mediainfo for kodi media, returned as dict with details
    '''
    close_called = False

    def __init__(self):
        '''Initialize and load all our helpers'''
        self._studiologos_path = ""
        self.cache = SimpleCache()
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.kodidb = KodiDb()
        self.omdb = Omdb(self.cache)
        self.tmdb = Tmdb(self.cache)
        self.channellogos = ChannelLogos(self.kodidb)
        self.fanarttv = FanartTv(self.cache)
        self.imdb = Imdb(self.cache)
        self.google = GoogleImages(self.cache)
        self.studiologos = StudioLogos(self.cache)
        self.animatedart = AnimatedArt(self.cache, self.kodidb)
        self.thetvdb = TheTvDb()
        self.musicart = MusicArtwork(self)
        self.pvrart = PvrArtwork(self)
        log_msg("Initialized")

    def close(self):
        '''Cleanup instances'''
        self.close_called = True
        self.cache.close()
        self.addon = None
        del self.addon
        del self.kodidb
        del self.omdb
        del self.tmdb
        del self.channellogos
        del self.fanarttv
        del self.imdb
        del self.google
        del self.studiologos
        del self.animatedart
        del self.thetvdb
        del self.musicart
        del self.pvrart
        log_msg("Exited")

    def __del__(self):
        '''make sure close is called'''
        if not self.close_called:
            self.close()

    @use_cache(14)
    def get_extrafanart(self, file_path):
        '''helper to retrieve the extrafanart path for a kodi media item'''
        from helpers.extrafanart import get_extrafanart
        return get_extrafanart(file_path)

    def get_music_artwork(self, artist, album="", track="", disc="", ignore_cache=False, flush_cache=False):
        '''method to get music artwork for the goven artist/album/song'''
        return self.musicart.get_music_artwork(
            artist, album, track, disc, ignore_cache=ignore_cache, flush_cache=flush_cache)

    def music_artwork_options(self, artist, album="", track="", disc=""):
        '''options for music metadata for specific item'''
        return self.musicart.music_artwork_options(artist, album, track, disc)

    @use_cache(7)
    def get_extended_artwork(self, imdb_id="", tvdb_id="", tmdb_id="", media_type=""):
        '''get extended artwork for the given imdbid or tvdbid'''
        result = None
        if "movie" in media_type and tmdb_id:
            result = self.fanarttv.movie(tmdb_id)
        elif "movie" in media_type and imdb_id:
            result = self.fanarttv.movie(imdb_id)
        elif media_type in ["tvshow", "tvshows", "seasons", "episodes"]:
            if not tvdb_id:
                if imdb_id and not imdb_id.startswith("tt"):
                    tvdb_id = imdb_id
                elif imdb_id:
                    tvdb_id = self.thetvdb.get_series_by_imdb_id(imdb_id).get("tvdb_id")
            if tvdb_id:
                result = self.fanarttv.tvshow(tvdb_id)
        # add additional art with special path
        if result:
            result = {"art": result}
            for arttype in ["fanarts", "posters", "clearlogos", "banners"]:
                if result["art"].get(arttype):
                    result["art"][arttype] = "plugin://script.skin.helper.service/"\
                        "?action=extrafanart&fanarts=%s" % quote_plus(repr(result["art"][arttype]))
        return result

    def get_tmdb_details(self, imdb_id="", tvdb_id="", title="", year="", media_type="",
                         preftype="", manual_select=False, ignore_cache=False):
        '''returns details from tmdb'''
        result = {}
        if imdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                imdb_id, "imdb_id")
        elif tvdb_id:
            result = self.tmdb.get_videodetails_by_externalid(
                tvdb_id, "tvdb_id")
        elif title and media_type in ["movies", "setmovies", "movie"]:
            result = self.tmdb.search_movie(
                title, year, manual_select=manual_select)
        elif title and media_type in ["tvshows", "tvshow"]:
            result = self.tmdb.search_tvshow(
                title, year, manual_select=manual_select)
        elif title:
            result = self.tmdb.search_video(
                title, year, preftype=preftype, manual_select=manual_select)
        if result and result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result and result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    def get_moviesetdetails(self, title, set_id):
        '''get a nicely formatted dict of the movieset details which we can for example set as window props'''
        # get details from tmdb
        from helpers.moviesetdetails import get_moviesetdetails
        return get_moviesetdetails(self, title, set_id)

    @use_cache(14)
    def get_streamdetails(self, db_id, media_type, ignore_cache=False):
        '''get a nicely formatted dict of the streamdetails '''
        from helpers.streamdetails import get_streamdetails
        return get_streamdetails(self.kodidb, db_id, media_type)

    def get_pvr_artwork(self, title, channel="", genre="", manual_select=False, ignore_cache=False):
        '''get artwork and mediadetails for PVR entries'''
        return self.pvrart.get_pvr_artwork(
            title, channel, genre, manual_select=manual_select, ignore_cache=ignore_cache)

    def pvr_artwork_options(self, title, channel="", genre=""):
        '''options for pvr metadata for specific item'''
        return self.pvrart.pvr_artwork_options(title, channel, genre)

    def get_channellogo(self, channelname):
        '''get channellogo for the given channel name'''
        return self.channellogos.get_channellogo(channelname)

    def get_studio_logo(self, studio):
        '''get studio logo for the given studio'''
        # dont use cache at this level because of changing logospath
        return self.studiologos.get_studio_logo(studio, self.studiologos_path)

    @property
    def studiologos_path(self):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        return self._studiologos_path

    @studiologos_path.setter
    def studiologos_path(self, value):
        '''path to use to lookup studio logos, must be set by the calling addon'''
        self._studiologos_path = value

    def get_animated_artwork(self, imdb_id, manual_select=False, ignore_cache=False):
        '''get animated artwork, perform extra check if local version still exists'''
        artwork = self.animatedart.get_animated_artwork(
            imdb_id, manual_select=manual_select, ignore_cache=ignore_cache)
        if not (manual_select or ignore_cache):
            refresh_needed = False
            if artwork.get("animatedposter") and not xbmcvfs.exists(
                    artwork["animatedposter"]):
                refresh_needed = True
            if artwork.get("animatedfanart") and not xbmcvfs.exists(
                    artwork["animatedfanart"]):
                refresh_needed = True

        return {"art": artwork}

    def get_omdb_info(self, imdb_id="", title="", year="", content_type=""):
        '''Get (kodi compatible formatted) metadata from OMDB, including Rotten tomatoes details'''
        title = title.split(" (")[0]  # strip year appended to title
        result = {}
        if imdb_id:
            result = self.omdb.get_details_by_imdbid(imdb_id)
        elif title and content_type in ["seasons", "season", "episodes", "episode", "tvshows", "tvshow"]:
            result = self.omdb.get_details_by_title(title, "", "tvshows")
        elif title and year:
            result = self.omdb.get_details_by_title(title, year, content_type)
        if result and result.get("status"):
            result["status"] = self.translate_string(result["status"])
        if result and result.get("runtime"):
            result["runtime"] = result["runtime"] / 60
            result.update(self.get_duration(result["runtime"]))
        return result

    def get_top250_rating(self, imdb_id):
        '''get the position in the IMDB top250 for the given IMDB ID'''
        return self.imdb.get_top250_rating(imdb_id)

    @use_cache(14)
    def get_duration(self, duration):
        '''helper to get a formatted duration'''
        if isinstance(duration, (str, unicode)) and ":" in duration:
            dur_lst = duration.split(":")
            return {
                "Duration": "%s:%s" % (dur_lst[0], dur_lst[1]),
                "Duration.Hours": dur_lst[0],
                "Duration.Minutes": dur_lst[1],
                "Runtime": str((int(dur_lst[0]) * 60) + int(dur_lst[1])),
            }
        else:
            return _get_duration(duration)

    @use_cache(2)
    def get_tvdb_details(self, imdbid="", tvdbid=""):
        '''get metadata from tvdb by providing a tvdbid or tmdbid'''
        result = {}
        self.thetvdb.days_ahead = 365
        if not tvdbid and imdbid and not imdbid.startswith("tt"):
            # assume imdbid is actually a tvdbid...
            tvdbid = imdbid
        if tvdbid:
            result = self.thetvdb.get_series(tvdbid)
        elif imdbid:
            result = self.thetvdb.get_series_by_imdb_id(imdbid)
        if result:
            if result["status"] == "Continuing":
                # include next episode info
                result["nextepisode"] = self.thetvdb.get_nextaired_episode(result["tvdb_id"])
            # include last episode info
            result["lastepisode"] = self.thetvdb.get_last_episode_for_series(result["tvdb_id"])
            result["status"] = self.translate_string(result["status"])
            if result.get("runtime"):
                result["runtime"] = result["runtime"] / 60
                result.update(_get_duration(result["runtime"]))
        return result

    @use_cache(90)
    def get_imdbtvdb_id(self, title, content_type, year="", imdbid="", tvshowtitle=""):
        '''try to figure out the imdbnumber and/or tvdbid'''
        tvdbid = ""
        if content_type in ["seasons", "episodes"] or tvshowtitle:
            title = tvshowtitle
            content_type = "tvshows"
        if imdbid and not imdbid.startswith("tt"):
            if content_type in ["tvshows", "seasons", "episodes"]:
                tvdbid = imdbid
                imdbid = ""
        if not imdbid and year:
            imdbid = self.get_omdb_info(
                "", title, year, content_type).get("imdbnumber", "")
        if not imdbid:
            # repeat without year
            imdbid = self.get_omdb_info("", title, "", content_type).get("imdbnumber", "")
        # return results
        return (imdbid, tvdbid)

    def translate_string(self, _str):
        '''translate the received english string from the various sources like tvdb, tmbd etc'''
        translation = _str
        _str = _str.lower()
        if "continuing" in _str:
            translation = self.addon.getLocalizedString(32037)
        elif "ended" in _str:
            translation = self.addon.getLocalizedString(32038)
        elif "released" in _str:
            translation = self.addon.getLocalizedString(32040)
        return translation
class TheTvDb(object):
    '''Our main class'''
    _win = None
    _addon = None
    _token = None
    cache = None
    api_key = 'A7613F5C1482A540'  # default api key
    days_ahead = 120
    ignore_cache = False
    _close_called = False

    def __init__(self, api_key=None):
        '''Initialize our Module'''
        if api_key:
            self.api_key = api_key
        self.cache = SimpleCache()
        self._win = xbmcgui.Window(10000)
        self._addon = xbmcaddon.Addon(ADDON_ID)
        addonversion = self._addon.getAddonInfo('version').decode("utf-8")
        self.cache.global_checksum = "%s%s" % (addonversion, KODI_LANGUAGE)
        self._log_msg("Initialized")

    def close(self):
        '''Cleanup Kodi cpython classes'''
        self._close_called = True
        self.cache.close()
        del self._win
        del self._addon
        self._log_msg("Exited")

    def __del__(self):
        '''make sure close is called'''
        if not self._close_called:
            self.close()

    def get_data(self, endpoint, prefer_localized=False):
        '''grab the results from the api'''
        data = {}
        url = 'https://api.thetvdb.com/' + endpoint
        headers = {'Content-Type': 'application/json',
                   'Accept': 'application/json',
                   'User-agent': 'Mozilla/5.0', 'Authorization': 'Bearer %s' % self._get_token()}
        if prefer_localized:
            headers["Accept-Language"] = KODI_LANGUAGE
        try:
            response = requests.get(url, headers=headers, timeout=20)
            if response and response.content and response.status_code == 200:
                data = json.loads(response.content.decode('utf-8', 'replace'))
            elif response.status_code == 401:
                # token expired, refresh it and repeat our request
                self._log_msg("Token expired, refreshing...")
                headers['Bearer'] = self._get_token(True)
                response = requests.get(url, headers=headers, timeout=5)
                if response and response.content and response.status_code == 200:
                    data = json.loads(response.content.decode('utf-8', 'replace'))
            if data.get("data"):
                data = data["data"]
        except Exception as exc:
            self._log_msg("Exception in get_data --> %s" % repr(exc), xbmc.LOGERROR)
        return data

    @use_cache(60)
    def get_series_posters(self, seriesid, season=None):
        '''retrieves the URL for the series poster, prefer season poster if season number provided'''
        if season:
            images = self.get_data("series/%s/images/query?keyType=season&subKey=%s" % (seriesid, season))
        else:
            images = self.get_data("series/%s/images/query?keyType=poster" % (seriesid))
        return self.process_images(images)

    @use_cache(60)
    def get_series_fanarts(self, seriesid, landscape=False):
        '''retrieves the URL for the series fanart image'''
        if landscape:
            images = self.get_data("series/%s/images/query?keyType=fanart&subKey=text" % (seriesid))
        else:
            images = self.get_data("series/%s/images/query?keyType=fanart&subKey=graphical" % (seriesid))
        return self.process_images(images)

    @staticmethod
    def process_images(images):
        '''helper to sort and correct the images as the api output is rather messy'''
        result = []
        if images:
            for image in images:
                if image["fileName"] and not image["fileName"].endswith("/"):
                    if image["fileName"].startswith("http://"): 
                        image["fileName"] = image["fileName"].replace("http://", "https://")
                    elif not image["fileName"].startswith("https://"):
                        image["fileName"] = "https://thetvdb.com/banners/" + image["fileName"]
                    image_score = image["ratingsInfo"]["average"] * image["ratingsInfo"]["count"]
                    image["score"] = image_score
                    result.append(image)
        return [item["fileName"] for item in sorted(result, key=itemgetter("score"), reverse=True)]

    @use_cache(7)
    def get_episode(self, episodeid, seriesdetails=None):
        '''
            Returns the full information for a given episode id.
            Usage: specify the episode ID: TheTvDb().get_episode(episodeid)
        '''
        episode = self.get_data("episodes/%s" % episodeid, True)
        # we prefer localized content but if that fails, fallback to default
        if episode and not episode.get("overview"):
            episode = self.get_data("episodes/%s" % episodeid)
        if episode:
            if not seriesdetails and "seriesid" in episode:
                seriesdetails = self.get_series(episode["seriesid"])
            elif not seriesdetails and "seriesId" in episode:
                seriesdetails = self.get_series(episode["seriesId"])
            episode = self._map_episode_data(episode, seriesdetails)
        return episode

    @use_cache(14)
    def get_series(self, seriesid):
        '''
            Returns a series record that contains all information known about a particular series id.
            Usage: specify the serie ID: TheTvDb().get_series(seriesid)
        '''
        seriesinfo = self.get_data("series/%s" % seriesid, True)
        # we prefer localized content but if that fails, fallback to default
        if not seriesinfo.get("overview"):
            seriesinfo = self.get_data("series/%s" % seriesid)
        return self._map_series_data(seriesinfo)

    @use_cache(7)
    def get_series_by_imdb_id(self, imdbid=""):
        '''get full series details by providing an imdbid'''
        items = self.get_data("search/series?imdbId=%s" % imdbid)
        if items:
            return self.get_series(items[0]["id"])
        else:
            return {}

    @use_cache(3)
    def get_continuing_series(self):
        '''
            only gets the continuing series,
            based on which series were recently updated as there is no other api call to get that information
        '''
        recent_series = self.get_recently_updated_series()
        continuing_series = []
        for recent_serie in recent_series:
            seriesinfo = self.get_series(recent_serie["id"])
            if seriesinfo and seriesinfo.get("status", "") == "Continuing":
                continuing_series.append(seriesinfo)
        return continuing_series

    @use_cache(60)
    def get_series_actors(self, seriesid):
        '''
            Returns actors for the given series id.
            Usage: specify the series ID: TheTvDb().get_series_actors(seriesid)
        '''
        return self.get_data("series/%s/actors" % seriesid)

    @use_cache(30)
    def get_series_episodes(self, seriesid):
        '''
            Returns all episodes for a given series.
            Usage: specify the series ID: TheTvDb().get_series_episodes(seriesid)
            Note: output is only summary of episode details (non kodi formatted)
        '''
        all_episodes = []
        page = 1
        while True:
            # get all episodes by iterating over the pages
            data = self.get_data("series/%s/episodes?page=%s" % (seriesid, page))
            if not data:
                break
            else:
                all_episodes += data
                page += 1
        return all_episodes

    @use_cache(14)
    def get_last_season_for_series(self, seriesid):
        '''get the last season for the series'''
        highest_season = 0
        summary = self.get_series_episodes_summary(seriesid)
        if summary:
            for season in summary["airedSeasons"]:
                if int(season) > highest_season:
                    highest_season = int(season)
        return highest_season

    @use_cache(1)
    def get_last_episode_for_series(self, seriesid):
        '''
            Returns the last aired episode for a given series
            Usage: specify the series ID: TheTvDb().get_last_episode_for_series(seriesid)
        '''
        # somehow the absolutenumber is broken in the api so we have to get this info the hard way
        highest_season = self.get_last_season_for_series(seriesid)
        while not highest_season == -1:
            season_episodes = self.get_series_episodes_by_query(seriesid, "airedSeason=%s" % highest_season)
            season_episodes = sorted(season_episodes, key=lambda k: k.get('airedEpisodeNumber', 0), reverse=True)

            highest_eps = (arrow.get("1970-01-01").date(), 0)
            if season_episodes:
                for episode in season_episodes:
                    if episode["firstAired"]:
                        airdate = arrow.get(episode["firstAired"]).date()
                        if (airdate < date.today()) and (airdate > highest_eps[0]):
                            highest_eps = (airdate, episode["id"])
                if highest_eps[1] != 0:
                    return self.get_episode(highest_eps[1])
            # go down one season untill we reach a match (there may be already announced seasons in the seasons list)
            highest_season -= 1
        self._log_msg("No last episodes found for series %s" % seriesid)
        return None

    @use_cache(7)
    def get_series_episodes_by_query(self, seriesid, query=""):
        '''
            This route allows the user to query against episodes for the given series.
            The response is an array of episode records that have been filtered down to basic information.
            Usage: specify the series ID: TheTvDb().get_series_episodes_by_query(seriesid)
            You must specify one or more fields for the query (combine multiple with &):
            absolutenumber=X --> Absolute number of the episode
            airedseason=X --> Aired season number
            airedepisode=X --> Aired episode number
            dvdseason=X --> DVD season number
            dvdepisode=X --> DVD episode number
            imdbid=X --> IMDB id of the series
            Note: output is only summary of episode details (non kodi formatted)
        '''
        all_episodes = []
        page = 1
        while True:
            # get all episodes by iterating over the pages
            data = self.get_data("series/%s/episodes/query?%s&page=%s" % (seriesid, query, page))
            if not data:
                break
            else:
                all_episodes += data
                page += 1
        return all_episodes

    @use_cache(7)
    def get_series_episodes_summary(self, seriesid):
        '''
            Returns a summary of the episodes and seasons available for the series.
            Note: Season 0 is for all episodes that are considered to be specials.

            Usage: specify the series ID: TheTvDb().get_series_episodes_summary(seriesid)
        '''
        return self.get_data("series/%s/episodes/summary" % (seriesid))

    @use_cache(8)
    def search_series(self, query="", prefer_localized=False):
        '''
            Allows the user to search for a series based the name.
            Returns an array of results that match the query.
            Usage: specify the query: TheTvDb().search_series(searchphrase)

            Available parameter:
            prefer_localized --> True if you want to set the current kodi language as preferred in the results
        '''
        return self.get_data("search/series?name=%s" % query, prefer_localized)

    @use_cache(7)
    def get_recently_updated_series(self):
        '''
            Returns all series that have been updated in the last week
        '''
        day = 24 * 60 * 60
        utc_date = date.today() - timedelta(days=7)
        cur_epoch = (utc_date.toordinal() - date(1970, 1, 1).toordinal()) * day
        return self.get_data("updated/query?fromTime=%s" % cur_epoch)

    def get_unaired_episodes(self, seriesid):
        '''
            Returns the unaired episodes for the specified seriesid
            Usage: specify the series ID: TheTvDb().get_unaired_episodes(seriesid)
        '''
        next_episodes = []
        seriesinfo = self.get_series(seriesid)
        if seriesinfo and seriesinfo.get("status", "") == "Continuing":
            highest_season = self.get_last_season_for_series(seriesid)
            episodes = self.get_series_episodes_by_query(seriesid, "airedSeason=%s" % highest_season)
            episodes = sorted(episodes, key=lambda k: k.get('airedEpisodeNumber', 0))
            for episode in episodes:
                if episode["firstAired"] and episode["episodeName"]:
                    airdate = arrow.get(episode["firstAired"]).date()
                    if airdate >= date.today() and (airdate <= (date.today() + timedelta(days=self.days_ahead))):
                        # if airdate is today or (max X days) in the future add to our list
                        episode = self.get_episode(episode["id"], seriesinfo)
                        if episode:  # apparently some episode id's are reported that do not exist
                            next_episodes.append(episode)
        # return our list sorted by episode
        return sorted(next_episodes, key=lambda k: k.get('episode', ""))

    def get_nextaired_episode(self, seriesid):
        '''
            Returns the first next airing episode for the specified seriesid
            Usage: specify the series ID: TheTvDb().get_nextaired_episode(seriesid)
        '''
        next_episodes = self.get_unaired_episodes(seriesid)
        if next_episodes:
            return next_episodes[0]
        else:
            return None

    def get_unaired_episode_list(self, seriesids):
        '''
            Returns the next airing episode for each specified seriesid
            Usage: specify the series ID: TheTvDb().get_unaired_episode_list(list of seriesids)
        '''
        next_episodes = []
        for seriesid in seriesids:
            episodes = self.get_unaired_episodes(seriesid)
            if episodes and episodes[0] is not None:
                next_episodes.append(episodes[0])
        # return our list sorted by date
        return sorted(next_episodes, key=lambda k: k.get('firstAired', ""))

    def get_kodishows(self, continuing_only=False):
        '''
            get all tvshows in the kodi library and make sure we have a valid tvdb id
            returns combined tvshow details
        '''
        kodi_series = self._get_kodi_json('VideoLibrary.GetTvShows', '{"properties": [ %s ] }' % KODI_TV_PROPS)
        all_series = []
        monitor = xbmc.Monitor()
        if kodi_series and kodi_series.get("tvshows"):
            for kodi_serie in kodi_series["tvshows"]:
                if monitor.abortRequested() or self._close_called:
                    break
                tvdb_details = self._parse_kodi_show(kodi_serie)
                if tvdb_details and "tvdb_status" in tvdb_details:
                    if not continuing_only or (continuing_only and tvdb_details["tvdb_status"] == "Continuing"):
                        all_series.append(tvdb_details)
        del monitor
        return all_series

    def get_kodishows_details(self, continuing_only=False):
        '''
            returns full info for each tvshow in the kodi library
            returns both kodi and tvdb info combined, including next-/last episode
        '''
        result = []
        monitor = xbmc.Monitor()
        for series_info in self.get_kodishows(continuing_only=continuing_only):
            if monitor.abortRequested() or self._close_called:
                break
            details = self.get_kodishow_details(series_info["title"], serie_details=series_info)
            if details:
                result.append(series_info)
        del monitor
        return result

    def get_kodishows_airingtoday(self):
        '''
            returns full info for each tvshow in the kodi library that airs today
        '''
        result = []
        monitor = xbmc.Monitor()
        for series_info in self.get_kodishows(continuing_only=True):
            if monitor.abortRequested() or self._close_called:
                break
            details = self.get_kodishow_details(series_info["title"], serie_details=series_info)
            if details and details.get("next_episode"):
                airdate = arrow.get(details["next_episode"]["firstaired"]).date()
                if airdate == date.today():
                    result.append(series_info)
        del monitor
        return sorted(result, key=lambda k: k.get('airtime', ""))

    @use_cache(2)
    def get_kodishow_details(self, title, serie_details=None):
        '''get full details for the kodi serie in library - search by title (as in kodi db)'''
        result = None
        if not serie_details and title:
            # get kodi details by title
            filter_str = '"filter": {"operator": "is", "field": "title", "value": "%s"}' % title
            kodi_series = self._get_kodi_json('VideoLibrary.GetTvShows',
                                              '{"properties": [ %s ], %s }' % (KODI_TV_PROPS, filter_str))
            if kodi_series and kodi_series.get("tvshows"):
                kodi_details = kodi_series["tvshows"][0]
                serie_details = self._parse_kodi_show(kodi_details)
        if serie_details:
            # append next airing episode details
            serie_details["next_episode"] = self.get_nextaired_episode(serie_details["tvdb_id"])
            # append last episode details
            serie_details["last_episode"] = self.get_last_episode_for_series(serie_details["tvdb_id"])
            result = serie_details
        return result

    def get_kodi_unaired_episodes(self, single_episode_per_show=True, include_last_episode=False, tvshows_ids=None):
        '''
            Returns the next unaired episode for all continuing tv shows in the Kodi library
            single_episode_per_show: Only return a single episode (next unaired) for each show, defaults to True.
            include_last_episode: Also include the last aired episode in the listing for each show.
        '''
        kodi_series = self.get_kodishows(True)
        next_episodes = []
        if tvshows_ids:
            kodi_series = [ tvshow for tvshow in kodi_series if tvshow["tvshowid"] in tvshows_ids ]
        for kodi_serie in kodi_series:
            serieid = kodi_serie["tvdb_id"]
            if single_episode_per_show:
                episodes = [self.get_nextaired_episode(serieid)]
            else:
                episodes = self.get_unaired_episodes(serieid)
            if include_last_episode:
                episodes.append(self.get_last_episode_for_series(serieid))
            for next_episode in episodes:
                if next_episode:
                    # make the json output kodi compatible
                    next_episodes.append(self._map_kodi_episode_data(kodi_serie, next_episode))
        # return our list sorted by date
        return sorted(next_episodes, key=lambda k: k.get('firstaired', ""))

    def _map_episode_data(self, episode_details, seriesdetails=None):
        '''maps full episode data from tvdb to kodi compatible format'''
        result = {}
        result["art"] = {}
        if episode_details.get("filename"):
            result["art"]["thumb"] = "https://thetvdb.com/banners/" + episode_details["filename"]
            result["thumbnail"] = result["art"]["thumb"]
        result["title"] = episode_details["episodeName"]
        result["label"] = "%sx%s. %s" % (episode_details["airedSeason"],
                                         episode_details["airedEpisodeNumber"], episode_details["episodeName"])
        result["season"] = episode_details["airedSeason"]
        result["episode"] = episode_details["airedEpisodeNumber"]
        result["firstaired"] = episode_details["firstAired"]
        result["writer"] = episode_details["writers"]
        result["director"] = episode_details["directors"]
        result["gueststars"] = episode_details["guestStars"]
        result["rating"] = episode_details["siteRating"]
        # make sure we have a decimal in the rating
        if len(str(result["rating"])) == 1:
            result["rating"] = "%s.0" % result["rating"]
        result["plot"] = episode_details["overview"]
        result["airdate"] = self._get_local_date(episode_details["firstAired"])
        result["airdate.long"] = self._get_local_date(episode_details["firstAired"], True)
        result["airdate.label"] = "%s (%s)" % (result["label"], result["airdate"])
        result["seriesid"] = episode_details["seriesId"]
        # append seriesinfo to details if provided
        if seriesdetails:
            result["tvshowtitle"] = seriesdetails["title"]
            result["showtitle"] = seriesdetails["title"]
            result["network"] = seriesdetails["network"]
            result["studio"] = seriesdetails["studio"]
            result["genre"] = seriesdetails["genre"]
            result["classification"] = seriesdetails["classification"]
            result["tvshow.firstaired"] = seriesdetails["firstaired"]
            result["tvshow.status"] = seriesdetails["status"]
            result["airtime"] = seriesdetails["airtime"]
            result["airday"] = seriesdetails["airday"]
            result["airday.int"] = seriesdetails["airday.int"]
            result["airdatetime"] = "%s %s" % (result["airdate"], result["airtime"])
            result["airdatetime.label"] = "%s - %s %s" % (result["airdatetime"],
                                                          xbmc.getLocalizedString(145), result["network"])
            result["art"]["tvshow.poster"] = seriesdetails["art"].get("poster", "")
            result["art"]["tvshow.landscape"] = seriesdetails["art"].get("landscape", "")
            result["art"]["tvshow.fanart"] = seriesdetails["art"].get("fanart", "")
            result["art"]["tvshow.banner"] = seriesdetails["art"].get("banner", "")
            try:
                result["runtime"] = int(seriesdetails["runtime"]) * 60
            except Exception:
                pass
            season_posters = self.get_series_posters(episode_details["seriesId"], episode_details["airedSeason"])
            if season_posters:
                result["art"]["season.poster"] = season_posters[0]
            result["library"] = seriesdetails.get("library", "")
            result["file"] = seriesdetails.get("file", "")
            result["year"] = seriesdetails.get("year", "")
        return result

    def _map_kodi_episode_data(self, kodi_tvshow_details, episode_details):
        '''combine kodi tvshow details with tvdb episode details'''
        result = episode_details
        # add images from kodi series details
        for key, value in kodi_tvshow_details["art"].items():
            result["art"]["tvshow.%s" % key] = self._get_clean_image(value)
        result["art"]["season.poster"] = episode_details.get("season.poster", "")
        result["tvshowtitle"] = kodi_tvshow_details["title"]
        result["showtitle"] = kodi_tvshow_details["title"]
        result["studio"] = kodi_tvshow_details["studio"]
        result["genre"] = kodi_tvshow_details["genre"]
        result["cast"] = kodi_tvshow_details["cast"]
        result["kodi_tvshowid"] = kodi_tvshow_details["tvshowid"]
        result["episodeid"] = -1
        result["file"] = "videodb://tvshows/titles/%s/" % kodi_tvshow_details["tvshowid"]
        result["type"] = "episode"
        result["DBTYPE"] = "episode"
        result["isFolder"] = True
        return result

    def _map_series_data(self, showdetails):
        '''maps the tvdb data to more kodi compatible format'''
        result = {}
        if showdetails:
            result["title"] = showdetails["seriesName"]
            result["status"] = showdetails["status"]
            result["tvdb_status"] = showdetails["status"]
            result["tvdb_id"] = showdetails["id"]
            result["network"] = showdetails["network"]
            result["studio"] = [showdetails["network"]]
            local_airday, local_airday_short, airday_int = self._get_local_weekday(showdetails["airsDayOfWeek"])
            result["airday"] = local_airday
            result["airday.short"] = local_airday_short
            result["airday.int"] = airday_int
            result["airtime"] = self._get_local_time(showdetails["airsTime"])
            result["airdaytime"] = "%s %s (%s)" % (result["airday"], result["airtime"], result["network"])
            result["airdaytime.short"] = "%s %s" % (result["airday.short"], result["airtime"])
            result["airdaytime.label"] = "%s %s - %s %s" % (result["airday"],
                                                            result["airtime"],
                                                            xbmc.getLocalizedString(145),
                                                            result["network"])
            result["airdaytime.label.short"] = "%s %s - %s %s" % (
                result["airday.short"],
                result["airtime"],
                xbmc.getLocalizedString(145),
                result["network"])
            result["votes"] = showdetails["siteRatingCount"]
            result["rating.tvdb"] = showdetails["siteRating"]
            # make sure we have a decimal in the rating
            if len(str(result["rating.tvdb"])) == 1:
                result["rating.tvdb"] = "%s.0" % result["rating.tvdb"]
            result["rating"] = result["rating.tvdb"]
            result["votes.tvdb"] = showdetails["siteRatingCount"]
            try:
                result["runtime"] = int(showdetails["runtime"]) * 60
            except Exception:
                pass
            result["plot"] = showdetails["overview"]
            result["genre"] = showdetails["genre"]
            classification = CLASSIFICATION_REGEX.search("/".join(showdetails["genre"])) if isinstance(showdetails["genre"], list) else None
            result["classification"] = classification.group(1) if classification else 'Scripted'
            result["firstaired"] = showdetails["firstAired"]
            result["imdbnumber"] = showdetails["imdbId"]
            # artwork
            result["art"] = {}
            fanarts = self.get_series_fanarts(showdetails["id"])
            if fanarts:
                result["art"]["fanart"] = fanarts[0]
                result["art"]["fanarts"] = fanarts
            landscapes = self.get_series_fanarts(showdetails["id"], True)
            if landscapes:
                result["art"]["landscapes"] = landscapes
                result["art"]["landscape"] = landscapes[0]
            posters = self.get_series_posters(showdetails["id"])
            if posters:
                result["art"]["posters"] = posters
                result["art"]["poster"] = posters[0]
            if showdetails.get("banner"):
                result["art"]["banner"] = "https://thetvdb.com/banners/" + showdetails["banner"]
        return result

    def _parse_kodi_show(self, kodi_details):
        ''' get tvdb series details by providing kodi showdetails'''
        tvdb_details = None
        result = None
        if kodi_details["imdbnumber"] and kodi_details["imdbnumber"].startswith("tt"):
            # lookup serie by imdbid
            tvdb_details = self.get_series_by_imdb_id(kodi_details["imdbnumber"])
        elif kodi_details["imdbnumber"]:
            # assume imdbid in kodidb is already tvdb id
            tvdb_details = self.get_series(kodi_details["imdbnumber"])
        if not tvdb_details and "uniqueid" in kodi_details:
            # kodi 17+ uses the uniqueid field to store the imdb/tvdb number
            for value in kodi_details["uniqueid"]:
                if value.startswith("tt"):
                    tvdb_details = self.get_series_by_imdb_id(value)
                elif value:
                    tvdb_details = self.get_series(value)
                if tvdb_details:
                    break
        if not tvdb_details:
            # lookup series by name as fallback
            tvdb_search = self.search_series(kodi_details["title"])
            if tvdb_search:
                tvdb_details = self.get_series(tvdb_search[0]["id"])
        if tvdb_details:
            # combine kodi tvshow details with tvdb series details
            result = kodi_details
            # append images from tvdb details
            for key, value in tvdb_details["art"].items():
                if value and not result["art"].get(key):
                    result["art"][key] = value
            # combine info from both dicts
            for key, value in tvdb_details.items():
                if value and not result.get(key):
                    result[key] = value
            result["isFolder"] = True
            result["tvdb_status"] = tvdb_details["status"]
            result["library"] = "videodb://tvshows/titles/%s/" % kodi_details["tvshowid"]
        return result

    @staticmethod
    def _log_msg(msg, level=xbmc.LOGDEBUG):
        '''logger to kodi log'''
        if isinstance(msg, unicode):
            msg = msg.encode("utf-8")
        xbmc.log('{0} --> {1}'.format(ADDON_ID, msg), level=level)

    @staticmethod
    def _get_clean_image(image):
        '''helper to strip all kodi tags/formatting of an image path/url'''
        if not image or isinstance(image, list):
            return image
        if image and "image://" in image:
            image = image.replace("image://", "")
            image = urllib.unquote(image.encode("utf-8"))
            if image.endswith("/"):
                image = image[:-1]
        if not isinstance(image, unicode):
            image = image.decode("utf8")
        return image

    def _get_token(self, refresh=False):
        '''get jwt token for api'''
        # get token from memory cache first
        if self._token and not refresh:
            return self._token
        token = self._win.getProperty("script.module.thetvdb.token").decode('utf-8')
        if token and not refresh:
            return token

        # refresh previous token
        prev_token = self._addon.getSetting("token")
        if prev_token:
            url = 'https://api.thetvdb.com/refresh_token'
            headers = {'Content-Type': 'application/json', 'Accept': 'application/json',
                       'User-agent': 'Mozilla/5.0', 'Authorization': 'Bearer %s' % prev_token}
            response = requests.get(url, headers=headers)
            if response and response.content and response.status_code == 200:
                data = json.loads(response.content.decode('utf-8', 'replace'))
                token = data["token"]
            if token:
                self._win.setProperty("script.module.thetvdb.token", token)
                self._token = token
                return token

        # do first login to get initial token
        url = 'https://api.thetvdb.com/login'
        payload = {'apikey': self.api_key}
        headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'User-agent': 'Mozilla/5.0'}
        response = requests.post(url, data=json.dumps(payload), headers=headers)
        if response and response.content and response.status_code == 200:
            data = json.loads(response.content.decode('utf-8', 'replace'))
            token = data["token"]
            self._addon.setSetting("token", token)
            self._win.setProperty("script.module.thetvdb.token", token)
            self._token = token
            return token
        else:
            self._log_msg("Error getting JWT token!", xbmc.LOGWARNING)
            return None

    @staticmethod
    def _get_kodi_json(method, params):
        '''helper to get data from the kodi json database'''
        json_response = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method" : "%s", "params": %s, "id":1 }'
                                            % (method, params.encode("utf-8")))
        jsonobject = json.loads(json_response.decode('utf-8', 'replace'))
        if 'result' in jsonobject:
            jsonobject = jsonobject['result']
        return jsonobject

    def _get_local_time(self, timestr):
        '''returns the correct localized representation of the time provided by the api'''
        result = ""
        try:
            if timestr and ":" in timestr:
                timestr = timestr.replace(".", ":")
                if "H" in xbmc.getRegion('time'):
                    time_format = "HH:mm"
                else:
                    time_format = "h:mm A"
                if " AM" in timestr or " PM" in timestr:
                    result = arrow.get(timestr, 'h:mm A').format(time_format, locale=KODI_LANGUAGE)
                elif " am" in timestr or " pm" in timestr:
                    result = arrow.get(timestr, 'h:mm a').format(time_format, locale=KODI_LANGUAGE)
                elif "AM" in timestr or "PM" in timestr:
                    result = arrow.get(timestr, 'h:mmA').format(time_format, locale=KODI_LANGUAGE)
                elif "am" in timestr or "pm" in timestr:
                    result = arrow.get(timestr, 'h:mma').format(time_format, locale=KODI_LANGUAGE)
                elif len(timestr.split(":")[0]) == 1:
                    result = arrow.get(timestr, 'h:mm').format(time_format, locale=KODI_LANGUAGE)
                else:
                    result = arrow.get(timestr, 'HH:mm').format(time_format, locale=KODI_LANGUAGE)
        except Exception as exc:
            self._log_msg(str(exc), xbmc.LOGWARNING)
            return timestr
        return result

    def _get_local_date(self, datestr, long_date=False):
        '''returns the localized representation of the date provided by the api'''
        result = ""
        try:
            if long_date:
                result = arrow.get(datestr).strftime(xbmc.getRegion('datelong'))
            else:
                result = arrow.get(datestr).strftime(xbmc.getRegion('dateshort'))
        except Exception as exc:
            self._log_msg("Exception in _get_local_date: %s" % exc)
        return result

    def _get_local_weekday(self, weekday):
        '''returns the localized representation of the weekday provided by the api'''
        if not weekday:
            return ("", "", 0)
        day_name = weekday
        day_name_short = day_name[:3]
        day_int = 0
        try:
            locale = arrow.locales.get_locale(KODI_LANGUAGE)
            day_names = {"monday": 1, "tuesday": 2, "wednesday": 3, "thurday": 4,
                         "friday": 5, "saturday": 6, "sunday": 7}
            day_int = day_names.get(weekday.lower(), 0)
            if day_int:
                day_name = locale.day_name(day_int).capitalize()
                day_name_short = locale.day_abbreviation(day_int).capitalize()
        except Exception as exc:
            self._log_msg(str(exc))
        return (day_name, day_name_short, day_int)
Exemplo n.º 12
0
class Main(object):
    '''Main entry path for our widget listing. Process the arguments and load correct class and module'''
    def __init__(self):
        ''' Initialization '''

        self.artutils = ArtUtils()
        self.cache = SimpleCache()
        self.addon = xbmcaddon.Addon(ADDON_ID)
        self.win = xbmcgui.Window(10000)
        self.options = self.get_options()

        # skip if shutdown requested
        if self.win.getProperty("SkinHelperShutdownRequested"):
            log_msg("Not forfilling request: Kodi is exiting!",
                    xbmc.LOGWARNING)
            xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)
            return

        if not "mediatype" in self.options or not "action" in self.options:
            # we need both mediatype and action, so show the main listing
            self.mainlisting()
        else:
            # we have a mediatype and action so display the widget listing
            self.show_widget_listing()

        self.close()

    def close(self):
        '''Cleanup Kodi Cpython instances'''
        self.artutils.close()
        self.cache.close()
        del self.addon
        del self.win
        log_msg("MainModule exited")

    def get_options(self):
        '''get the options provided to the plugin path'''

        options = dict(
            urlparse.parse_qsl(sys.argv[2].replace(
                '?', '').lower().decode("utf-8")))

        if not "mediatype" in options and "action" in options:
            # get the mediatype and action from the path (for backwards compatability with old style paths)
            for item in [("movies", "movies"), ("shows", "tvshows"),
                         ("episode", "episodes"),
                         ("musicvideos", "musicvideos"), ("pvr", "pvr"),
                         ("albums", "albums"), ("songs", "songs"),
                         ("artists", "artists"), ("media", "media"),
                         ("favourites", "favourites")]:
                if item[0] in options["action"]:
                    options["mediatype"] = item[1]
                    options["action"] = options["action"].replace(
                        item[1], "").replace(item[0], "")
                    break

        # prefer reload param for the mediatype
        if "mediatype" in options:
            alt_reload = self.win.getProperty("widgetreload-%s" %
                                              options["mediatype"])
            if options["mediatype"] == "favourites" or "favourite" in options[
                    "action"]:
                options["skipcache"] = "true"
            elif alt_reload:
                options["reload"] = alt_reload
            if not options.get(
                    "action") and options["mediatype"] == "favourites":
                options["action"] = "favourites"
            elif not options.get("action"):
                options["action"] = "listing"
            if "listing" in options["action"]:
                options["skipcache"] = "true"

        # set the widget settings as options
        options["hide_watched"] = self.addon.getSetting(
            "hide_watched") == "true"
        if self.addon.getSetting(
                "hide_watched_recent") == "true" and "recent" in options.get(
                    "action", ""):
            options["hide_watched"] = True
        options["next_inprogress_only"] = self.addon.getSetting(
            "nextup_inprogressonly") == "true"
        options["episodes_enable_specials"] = self.addon.getSetting(
            "episodes_enable_specials") == "true"
        options["group_episodes"] = self.addon.getSetting(
            "episodes_grouping") == "true"
        if "limit" in options:
            options["limit"] = int(options["limit"])
        else:
            options["limit"] = int(self.addon.getSetting("default_limit"))
        return options

    def show_widget_listing(self):
        '''display the listing for the provided action and mediatype'''
        media_type = self.options["mediatype"]
        action = self.options["action"]
        # set widget content type
        xbmcplugin.setContent(ADDON_HANDLE, media_type)

        # try to get from cache first...
        # we use a checksum based on the options to make sure the cache is ignored when needed
        all_items = []
        cache_str = "SkinHelper.Widgets.%s.%s" % (media_type, action)
        if not self.win.getProperty("widgetreload2"):
            # at startup we simply accept whatever is in the cache
            cache_checksum = None
        else:
            cache_checksum = ""
            for key in sorted(self.options):
                cache_checksum += "%s.%s" % (key, self.options[key])
        cache = self.cache.get(cache_str, checksum=cache_checksum)
        if cache and not self.options.get("skipcache") == "true":
            log_msg(
                "MEDIATYPE: %s - ACTION: %s -- got items from cache - CHECKSUM: %s"
                % (media_type, action, cache_checksum))
            all_items = cache

        # Call the correct method to get the content from json when no cache
        if not all_items:
            log_msg(
                "MEDIATYPE: %s - ACTION: %s -- no cache, quering kodi api to get items - CHECKSUM: %s"
                % (media_type, action, cache_checksum))

            # dynamically import and load the correct module, class and function
            try:
                media_module = __import__(media_type)
                media_class = getattr(media_module,
                                      media_type.capitalize())(self.addon,
                                                               self.artutils,
                                                               self.options)
                all_items = getattr(media_class, action)()
                del media_class
            except AttributeError:
                log_exception(__name__,
                              "Incorrect widget action or type called")
            except Exception as exc:
                log_exception(__name__, exc)

            # randomize output if requested by skinner or user
            if self.options.get("randomize", "") == "true":
                all_items = sorted(all_items, key=lambda k: random.random())

            # prepare listitems and store in cache
            all_items = process_method_on_list(
                self.artutils.kodidb.prepare_listitem, all_items)
            self.cache.set(cache_str, all_items, checksum=cache_checksum)

        # fill that listing...
        xbmcplugin.addSortMethod(int(sys.argv[1]),
                                 xbmcplugin.SORT_METHOD_UNSORTED)
        all_items = process_method_on_list(
            self.artutils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))

        # end directory listing
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)

    def mainlisting(self):
        '''main listing'''
        all_items = []

        # movie node
        if xbmc.getCondVisibility("Library.HasContent(movies)"):
            all_items.append((xbmc.getLocalizedString(342), "movieslisting",
                              "DefaultMovies.png"))

        # tvshows and episodes nodes
        if xbmc.getCondVisibility("Library.HasContent(tvshows)"):
            all_items.append((xbmc.getLocalizedString(20343), "tvshowslisting",
                              "DefaultTvShows.png"))
            all_items.append((xbmc.getLocalizedString(20360),
                              "episodeslisting", "DefaultTvShows.png"))

        # pvr node
        if xbmc.getCondVisibility("Pvr.HasTVChannels"):
            all_items.append((self.addon.getLocalizedString(32054),
                              "pvrlisting", "DefaultAddonPVRClient.png"))

        # music nodes
        if xbmc.getCondVisibility("Library.HasContent(music)"):
            all_items.append((xbmc.getLocalizedString(132), "albumslisting",
                              "DefaultAlbumCover.png"))
            all_items.append((xbmc.getLocalizedString(134), "songslisting",
                              "DefaultMusicSongs.png"))
            all_items.append((xbmc.getLocalizedString(133), "artistslisting",
                              "DefaultArtist.png"))

        # musicvideo node
        if xbmc.getCondVisibility("Library.HasContent(musicvideos)"):
            all_items.append(
                (xbmc.getLocalizedString(20389), "musicvideoslisting",
                 "DefaultAddonAlbumInfo.png"))

        # media node
        if xbmc.getCondVisibility(
                "Library.HasContent(movies) | Library.HasContent(tvshows) | Library.HasContent(music)"
        ):
            all_items.append((self.addon.getLocalizedString(32057),
                              "medialisting", "DefaultAddonAlbumInfo.png"))

        # favourites node
        all_items.append((xbmc.getLocalizedString(10134), "favouriteslisting",
                          "DefaultAddonAlbumInfo.png"))

        # process the listitems and display listing
        all_items = process_method_on_list(create_main_entry, all_items)
        all_items = process_method_on_list(
            self.artutils.kodidb.prepare_listitem, all_items)
        all_items = process_method_on_list(
            self.artutils.kodidb.create_listitem, all_items)
        xbmcplugin.addDirectoryItems(ADDON_HANDLE, all_items, len(all_items))
        xbmcplugin.endOfDirectory(handle=ADDON_HANDLE)
    def alphabet():

        if sys.argv[2] != "?action=alphabet&reload=0":

            #log_msg("alphabet function", xbmc.LOGINFO)
            NumItems = xbmc.getInfoLabel('Container.NumItems')
            cache = SimpleCache()
            mycache = cache.get(NumItems)
            if mycache:
                all_letters = mycache
                #log_msg("alphabet function: all_letters CACHE", xbmc.LOGINFO)
            else:
                all_letters = []
                for i in range(int(NumItems)):
                    all_letters.append(
                        xbmc.getInfoLabel('Listitem(%s).SortLetter' %
                                          i).upper())
                cache.set(NumItems, all_letters)
                #log_msg("alphabet function: all_letters", xbmc.LOGINFO)

            if len(all_letters) > 1:
                numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
                alphabet = [
                    '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
                    'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                    'X', 'Y', 'Z'
                ]
                first_number = False

                for item in numbers:
                    if item in all_letters:
                        first_number = item
                        break

                for letter in alphabet:
                    listitem = xbmcgui.ListItem(label=letter)

                    if letter == '#' and first_number:
                        lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % first_number
                        listitem.setProperty('IsNumber', first_number)
                        xbmcplugin.addDirectoryItem(int(sys.argv[1]),
                                                    lipath,
                                                    listitem,
                                                    isFolder=False)

                    elif letter in all_letters:
                        lipath = "plugin://script.skin.helper.service/?action=alphabetletter&letter=%s" % letter
                        listitem.setProperty('IsNumber', letter)
                        xbmcplugin.addDirectoryItem(int(sys.argv[1]),
                                                    lipath,
                                                    listitem,
                                                    isFolder=False)

                    elif letter not in all_letters:
                        lipath = ""
                        listitem.setProperty("NotAvailable", "true")
                        xbmcplugin.addDirectoryItem(int(sys.argv[1]),
                                                    lipath,
                                                    listitem,
                                                    isFolder=False)

                xbmcplugin.endOfDirectory(handle=int(sys.argv[1]))
            cache.close()