class Media(object):
    """all media (mixed) widgets provided by the script"""

    ICON_IMAGE_MOVIES = "DefaultMovies.png"
    ICON_IMAGE_TVSHOWS = "DefaultTvShows.png"
    ICON_IMAGE_TAGS = "DefaultTags.png"
    MOVIES_TAGS_PATH = "videodb://movies/tags"
    TVSHOWS_TAGS_PATH = "videodb://tvshows/tags"
    KODI_USER_PLAYLISTS_PATH = "special://videoplaylists/"
    KODI_SKIN_PLAYLISTS_PATH = "special://skin/playlists/"
    PLAYLIST_SORT_OPTIONS = [
        'Recommended', 'TopPicks', 'Random', 'Recent', 'Year', 'Title'
    ]
    YEAR_TODAY_MINUS_FOUR = str(date.today().year - 4)
    FILTER_LAST_THREE_YEARS = {
        "operator": "greaterthan",
        "field": "year",
        "value": YEAR_TODAY_MINUS_FOUR
    }
    SORT_VOTES = {"method": "votes", "order": "descending"}

    def __init__(self, addon, metadatautils, options):
        """Initializations pass our common classes and the widget options as arguments"""
        self.metadatautils = metadatautils
        self.addon = addon
        self.options = options
        self.movies = Movies(self.addon, self.metadatautils, self.options)
        self.tvshows = Tvshows(self.addon, self.metadatautils, self.options)
        self.songs = Songs(self.addon, self.metadatautils, self.options)
        self.albums = Albums(self.addon, self.metadatautils, self.options)
        self.pvr = Pvr(self.addon, self.metadatautils, self.options)
        self.episodes = Episodes(self.addon, self.metadatautils, self.options)

    def listing(self):
        """main listing with all our media nodes"""
        tag = self.options.get("tag", "")
        exp_setting = self.options["exp_recommended"]
        extended_info_setting = self.options["extended_info"]
        mylist_setting = self.options["mylist"]
        if tag:
            label_prefix = u"%s - " % tag
        else:
            label_prefix = u""

        all_items = [
            (label_prefix + self.addon.getLocalizedString(32011),
             "inprogress&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32070),
             "inprogressshowsandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32080),
             "inprogressepisodesandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32005),
             "recent&mediatype=media&tag=%s" % tag, Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32078),
             "recentshowsandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32059),
             "random&mediatype=media&tag=%s" % tag, Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32079),
             "randomshowsandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32088),
             "unwatchedshowsandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32086),
             "watchagainshowsandmovies&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32087),
             "newrelease&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32058),
             "top250&mediatype=media&tag=%s" % tag, Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32081),
             "randomtop250&mediatype=media&tag=%s" % tag,
             Media.ICON_IMAGE_MOVIES),
            (label_prefix + self.addon.getLocalizedString(32004),
             "toprated&mediatype=media&tag=%s" % tag, Media.ICON_IMAGE_MOVIES)
        ]
        if exp_setting:
            all_items += [
                (label_prefix + self.addon.getLocalizedString(32084),
                 "recommendedmoviesandshows&mediatype=media&tag=%s" % tag,
                 Media.ICON_IMAGE_MOVIES),
                (label_prefix + self.addon.getLocalizedString(32085),
                 "toppicks&mediatype=media&tag=%s" % tag,
                 Media.ICON_IMAGE_MOVIES)
            ]
            if not tag:
                all_items += [
                    (self.addon.getLocalizedString(32022),
                     "similar&mediatype=media", Media.ICON_IMAGE_MOVIES)
                ]
        if mylist_setting:
            all_items += [(self.addon.getLocalizedString(32094),
                           "mylist&mediatype=media", Media.ICON_IMAGE_MOVIES)]
        if not tag:
            all_items += [
                (self.addon.getLocalizedString(32007),
                 "inprogressandrecommended&mediatype=media",
                 Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32060),
                 "inprogressandrandom&mediatype=media",
                 Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32090),
                 "popular&mediatype=media", Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32089),
                 "forgenre&mediatype=media", Media.ICON_IMAGE_MOVIES),
                (xbmc.getLocalizedString(135), "browsegenres&mediatype=media",
                 "DefaultGenres.png"),
                (self.addon.getLocalizedString(32098),
                 "categories&mediatype=media", Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32001),
                 "favourites&mediatype=media", Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32075),
                 "playlistslisting&mediatype=media&movie_label=",
                 Media.ICON_IMAGE_MOVIES),
                (xbmc.getLocalizedString(20459), "tagslisting&mediatype=media",
                 Media.ICON_IMAGE_MOVIES)
            ]
        if extended_info_setting:
            all_items += [
                (self.addon.getLocalizedString(32100) + ' - ' +
                 self.addon.getLocalizedString(32090),
                 "extendedpopulartmdb&mediatype=media",
                 Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32101) + ' - ' +
                 self.addon.getLocalizedString(32090),
                 "extendedpopulartrakt&mediatype=media",
                 Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32101) + ' - ' +
                 self.addon.getLocalizedString(32102),
                 "extendedtrending&mediatype=media", Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32101) + ' - ' +
                 self.addon.getLocalizedString(32105),
                 "extendedmostplayed&mediatype=media",
                 Media.ICON_IMAGE_MOVIES),
                (self.addon.getLocalizedString(32101) + ' - ' +
                 self.addon.getLocalizedString(32108),
                 "extendedmostwatched&mediatype=media",
                 Media.ICON_IMAGE_MOVIES)
            ]
        return self.metadatautils.process_method_on_list(
            create_main_entry, all_items)

    def tagslisting(self):
        """get tag listing that are shared with movies and tv shows"""
        all_items = []
        # fetch tag lists
        movies_taglist = self.metadatautils.kodidb.files(
            Media.MOVIES_TAGS_PATH)
        tvshows_taglist = self.metadatautils.kodidb.files(
            Media.TVSHOWS_TAGS_PATH)
        # find matched tag
        for movie_tag in movies_taglist:
            for tv_tag in tvshows_taglist:
                if movie_tag["label"] == tv_tag["label"]:
                    details = (movie_tag["label"],
                               "listing&mediatype=media&tag=%s" %
                               movie_tag["label"], Media.ICON_IMAGE_TAGS)
                    all_items.append(create_main_entry(details))
                    # no need to iterate rest of tvshow's tags since match is found -> build entry
                    break
        return all_items

    def playlistslisting(self):
        """get playlists listing
        first set movie_label, then tv_label then return sort entries and call sort method"""
        # add read skin playlists
        tv_label = self.options.get("tv_label")
        movie_label = self.options.get("movie_label")
        if movie_label and tv_label:
            # got both playlist -> let's build sort methods entries from const list
            return [
                create_main_entry((
                    sort_method,
                    "playlist&mediatype=media&movie_label=%s&tv_label=%s&sort=%s"
                    % (movie_label, tv_label, sort_method),
                    Media.ICON_IMAGE_MOVIES))
                for sort_method in Media.PLAYLIST_SORT_OPTIONS
            ]
        all_items = []
        all_playlists = self.metadatautils.kodidb.files(Media.KODI_USER_PLAYLISTS_PATH) \
                        + self.metadatautils.kodidb.files(Media.KODI_SKIN_PLAYLISTS_PATH)
        for item in all_playlists:
            # replace '&' with [and] -- will get fixed when processed in playlist action
            label = item["label"].replace('&', '[and]')
            if movie_label:
                # got movie playlist -> build playlist entries for tvshows
                details = (
                    item["label"],
                    "playlistslisting&mediatype=media&movie_label=%s&tv_label=%s&sort="
                    % (movie_label, label), Media.ICON_IMAGE_TVSHOWS)
            else:
                # both labels are empty -> build playlist entries for movies
                details = (item["label"],
                           "playlistslisting&mediatype=media&movie_label=%s" %
                           label, Media.ICON_IMAGE_MOVIES)
            all_items.append(create_main_entry(details))
        return all_items

    def playlist(self):
        """get items in both playlists, sorted by requested sort (defaults to recommended)"""
        # reversing replacing and to ampersand and assign filter dicts
        movie_filter = {
            "operator": "is",
            "field": "playlist",
            "value": self.options.get("movie_label").replace('[and]', '&')
        }
        tvshow_filter = {
            "operator": "is",
            "field": "playlist",
            "value": self.options.get("tv_label").replace('[and]', '&')
        }
        sort = self.options.get("sort").lower()
        all_items = self.metadatautils.kodidb.movies(filters=[movie_filter])
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(filters=[tvshow_filter]))
        # switch case using dict
        playlist_sort_options = {
            'recommended': self.playlist_recommended,
            'toppicks': self.playlist_toppicks,
            'random': self.playlist_random,
            'recent': self.playlist_recent,
            'year': self.playlist_year,
            'title': self.playlist_title
        }
        if sort and sort in playlist_sort_options:
            return playlist_sort_options[sort](all_items)
        # default case
        else:
            return self.playlist_recommended(all_items)

    def mylist(self):
        """ get mylist """
        filters = []
        if self.options["hide_watched"]:
            filters.append(kodi_constants.FILTER_UNWATCHED)
        filters.append({
            "operator": "contains",
            "field": "tag",
            "value": 'mylist'
        })
        all_items = self.metadatautils.kodidb.movies(filters=filters)
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(filters=filters))
        return sorted(all_items, key=itemgetter("dateadded"),
                      reverse=True)[:self.options["limit"]]

    def categories(self):
        """not ready"""
        all_items = []
        all_items += self.browsegenres()
        all_items += [{
            "art": {},
            "label": "movies",
            "title": xbmc.getLocalizedString(342),
            "file": "videodb://movies/titles/",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_MOVIES,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "tvshows",
            "title": xbmc.getLocalizedString(20343),
            "file": "videodb://tvshows/titles/",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_TVSHOWS,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "topratedmovies",
            "title": self.addon.getLocalizedString(32083),
            "file":
            u"plugin://script.skin.helper.widgets/?action=toprated&mediatype=movies&limit=100",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_MOVIES,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "topratedtvshows",
            "title": self.addon.getLocalizedString(32097),
            "file":
            u"plugin://script.skin.helper.widgets/?action=toprated&mediatype=tvshows&limit=100",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_TVSHOWS,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "recentlyadded",
            "title": self.addon.getLocalizedString(32078),
            "file":
            u"plugin://script.skin.helper.widgets/?action=recentshowsandmovies&mediatype=media&limit=100",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_MOVIES,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "newrelease",
            "title": self.addon.getLocalizedString(32087),
            "file":
            u"plugin://script.skin.helper.widgets/?action=newrelease&mediatype=media&limit=100",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_MOVIES,
            "type": "categorie"
        }]
        all_items += [{
            "art": {},
            "label": "popular",
            "title": self.addon.getLocalizedString(32090),
            "file":
            u"plugin://script.skin.helper.widgets/?action=popular&mediatype=media&limit=100",
            "isFolder": True,
            "IsPlayable": "false",
            "thumbnail": Media.ICON_IMAGE_MOVIES,
            "type": "categorie"
        }]
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def favourites(self):
        """get favourite media"""
        from favourites import Favourites
        self.options["mediafilter"] = "media"
        return Favourites(self.addon, self.metadatautils,
                          self.options).favourites()

    def favourite(self):
        """synonym to favourites"""
        return self.favourites()

    def forgenre(self):
        """ get random movies and tv shows for given shared genre"""
        genre = self.options.get("genre", "")
        movie_genres = self.metadatautils.kodidb.genres("movie")
        tvshow_genres = self.metadatautils.kodidb.genres("tvshow")
        if not genre:
            media_genres = []
            for movie_genre in movie_genres:
                for tvshow_genre in tvshow_genres:
                    if movie_genre["label"] == tvshow_genre["label"]:
                        media_genres.append(movie_genre["label"])
                        break
            if media_genres:
                # get random genre from matched genres
                genre = media_genres[randint(0, len(media_genres) - 1)]
        all_items = []
        if genre:
            for item in self.tvshows.get_genre_tvshows(
                    genre, self.options["hide_watched"],
                    self.options["limit"]):
                item["extraproperties"] = {
                    "genretitle": genre,
                    "originalpath": item["file"]
                }
                all_items.append(item)
            # proccess tvshows before adding movies
            all_items = self.metadatautils.process_method_on_list(
                self.tvshows.process_tvshow, all_items)
            for item in self.movies.get_genre_movies(
                    genre, self.options["hide_watched"],
                    self.options["limit"]):
                item["extraproperties"] = {
                    "genretitle": genre,
                    "originalpath": item["file"]
                }
                all_items.append(item)
        # return the list sorted random capped by limit
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def toprated(self):
        """get top rated mixed media"""
        all_items = []
        all_items += self.movies.toprated()
        all_items += self.tvshows.toprated()
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def recommendedmoviesandshows(self):
        """ get recommended movies and tv shows """
        return self.sort_by_recommended(self.get_items_for_recommended())

    def toppicks(self):
        """ get top picks movies and tv shows based on profile reference pooling """
        return self.sort_by_recommended(self.get_items_for_recommended(), True)

    def popular(self):
        """get popular movies and tv shows based on new and most voted, arranged by date added and year of release"""
        filters = []
        if self.options["hide_watched"]:
            filters.append(kodi_constants.FILTER_UNWATCHED)
        all_items = self.metadatautils.kodidb.movies(
            filters=filters + [Media.FILTER_LAST_THREE_YEARS],
            sort=Media.SORT_VOTES,
            limits=(0, self.options["limit"]))
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(
                filters=filters + [Media.FILTER_LAST_THREE_YEARS],
                sort=Media.SORT_VOTES,
                limits=(0, self.options["limit"])))
        # first sort by dateadded to mix tv and movies and maybe better release indication, then verify by year
        return sorted(sorted(all_items,
                             key=itemgetter("dateadded"),
                             reverse=True)[:self.options["limit"]],
                      key=itemgetter("year"),
                      reverse=True)

    def unwatchedshowsandmovies(self):
        """ get random unwatched tvshows and movies"""
        filters = [kodi_constants.FILTER_UNWATCHED]
        if self.options.get("tag"):
            filters.append({
                "operator": "contains",
                "field": "tag",
                "value": self.options["tag"]
            })
        all_items = self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_RANDOM,
            filters=filters,
            limits=(0, self.options["limit"]))
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(sort=kodi_constants.SORT_RANDOM,
                                              filters=filters,
                                              limits=(0,
                                                      self.options["limit"])))
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def newrelease(self):
        """ get newly released movies and tvshows based on added recently and released in last 3 years"""
        filters = [Media.FILTER_LAST_THREE_YEARS]
        if self.options["hide_watched"]:
            filters.append(kodi_constants.FILTER_UNWATCHED)
        if self.options.get("tag"):
            filters.append({
                "operator": "contains",
                "field": "tag",
                "value": self.options["tag"]
            })
        # first we take all recently added and released in last 3 years capped by limit
        all_items = self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_DATEADDED,
            filters=filters,
            limits=(0, self.options["limit"]))
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(
                sort=kodi_constants.SORT_DATEADDED,
                filters=filters,
                limits=(0, self.options["limit"])))
        # randomize to let tvshows mix with movies due to likeliness of being same year, and return sorted by year
        return sorted(sorted(all_items, key=lambda k: random()),
                      key=itemgetter("year"),
                      reverse=True)[:self.options["limit"]]

    def recent(self):
        """ get recently added media """
        all_items = self.movies.recent()
        all_items += self.albums.recent()
        all_items += self.songs.recent()
        all_items += self.episodes.recent()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("dateadded"),
                      reverse=True)[:self.options["limit"]]

    def recentshowsandmovies(self):
        """ get recently added movies and tvshows """
        all_items = self.movies.recent()
        all_items += self.tvshows.recent()
        return sorted(all_items, key=itemgetter("dateadded"),
                      reverse=True)[:self.options["limit"]]

    def random(self):
        """ get random media """
        all_items = self.movies.random()
        all_items += self.tvshows.random()
        all_items += self.albums.random()
        all_items += self.songs.random()
        all_items += self.episodes.random()
        all_items += self.pvr.recordings()
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def randomshowsandmovies(self):
        """ get random tv shows and movies """
        all_items = self.movies.random()
        all_items += self.tvshows.random()
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def inprogress(self):
        """ get in progress media """
        all_items = self.movies.inprogress()
        all_items += self.episodes.inprogress()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("lastplayed"),
                      reverse=True)[:self.options["limit"]]

    def inprogressepisodesandmovies(self):
        """ get in progress episodes and movies """
        all_items = self.movies.inprogress()
        all_items += self.episodes.inprogress()
        return sorted(all_items, key=itemgetter("lastplayed"),
                      reverse=True)[:self.options["limit"]]

    def inprogressshowsandmovies(self):
        """ get in-progress/next episodes AS TV Shows and in-progress movies """
        all_items = self.movies.inprogress()
        all_items += self.tvshows.nextshows()
        return sorted(all_items, key=itemgetter("lastplayed"),
                      reverse=True)[:self.options["limit"]]

    def inprogressandrecommended(self):
        """ get recommended and in progress media """
        all_items = self.inprogress()
        all_titles = [item["title"] for item in all_items]
        for item in self.recommended():
            if item["title"] not in all_titles:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def inprogressandrandom(self):
        """ get in progress AND random movies """
        all_items = self.inprogress()
        all_ids = [item["movieid"] for item in all_items]
        for item in self.random():
            if item["movieid"] not in all_ids:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def watchagainshowsandmovies(self):
        """ get random recently watched movies and tv shows """
        filters = [kodi_constants.FILTER_WATCHED]
        if self.options.get("tag"):
            filters.append({
                "operator": "contains",
                "field": "tag",
                "value": self.options["tag"]
            })
        all_items = self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_LASTPLAYED,
            filters=filters,
            limits=(0, self.options["limit"]))
        all_items += self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(
                sort=kodi_constants.SORT_LASTPLAYED,
                filters=filters + [kodi_constants.FILTER_INPROGRESS],
                filtertype="or",
                limits=(0, self.options["limit"])))
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def top250(self):
        """ get imdb top250 movies and tvshows in library by top250 rand """
        return sorted(self.get_top_250(),
                      key=itemgetter("top250_rank"))[:self.options["limit"]]

    def randomtop250(self):
        """ get random imdb top250 movies and tvshows in library """
        return sorted(self.get_top_250(),
                      key=lambda k: random())[:self.options["limit"]]

    def extendedpopulartmdb(self):
        """gets popular movies and tvshows from tmdb"""
        all_items = []
        all_items += self.movies.extendedpopulartmdb()
        all_items += self.tvshows.extendedpopulartmdb()
        return sorted(all_items,
                      key=itemgetter("extendedindex"))[:self.options["limit"]]

    def extendedpopulartrakt(self):
        """gets popular movies and tvshows from trakt"""
        all_items = []
        all_items += self.movies.extendedpopulartrakt()
        all_items += self.tvshows.extendedpopulartrakt()
        return sorted(all_items,
                      key=itemgetter("extendedindex"))[:self.options["limit"]]

    def extendedtrending(self):
        """gets popular movies and tvshows from trakt"""
        all_items = []
        all_items += self.movies.extendedtrending()
        all_items += self.tvshows.extendedtrending()
        return sorted(all_items,
                      key=itemgetter("extendedindex"))[:self.options["limit"]]

    def extendedmostplayed(self):
        """gets most played movies and tvshows from trakt"""
        all_items = []
        all_items += self.movies.extendedmostplayed()
        all_items += self.tvshows.extendedmostplayed()
        return sorted(all_items,
                      key=itemgetter("extendedindex"))[:self.options["limit"]]

    def extendedmostwatched(self):
        """gets most watched movies and tvshows from trakt"""
        all_items = []
        all_items += self.movies.extendedmostwatched()
        all_items += self.tvshows.extendedmostwatched()
        return sorted(all_items,
                      key=itemgetter("extendedindex"))[:self.options["limit"]]

    def browsegenres(self):
        """special entry which can be used to create custom genre listings
            returns each genre with poster/fanart artwork properties from 5
            random movies/tvshows in the genre."""
        # find matches
        movie_genres = self.metadatautils.kodidb.genres("movie")
        tvshow_genres = self.metadatautils.kodidb.genres("tvshow")
        media_genres = []
        for movie_genre in movie_genres:
            for tvshow_genre in tvshow_genres:
                if movie_genre["label"] == tvshow_genre["label"]:
                    media_genres.append(movie_genre["label"])
                    break
        # build genres
        all_items = []
        for genre in media_genres:
            all_items.append(self.process_genre(genre))
        return all_items

    def process_genre(self, genre):
        """method to create genre listitem from genre's label"""
        genre_json = {
            "art": {},
            "label":
            genre,
            "title":
            genre,
            "file":
            u"plugin://script.skin.helper.widgets/?action=forgenre&mediatype=media&genre=%s"
            % genre,
            "isFolder":
            True,
            "IsPlayable":
            "false",
            "thumbnail":
            "DefaultGenre.png",
            "type":
            "genre"
        }
        # randomly select fanart/poster from tvshows OR movies
        flip_coin = randint(0, 1)
        if flip_coin:
            genre_items = self.movies.get_genre_movies(
                genre, False, 5, kodi_constants.SORT_RANDOM)
        else:
            genre_items = self.tvshows.get_genre_tvshows(
                genre, False, 5, kodi_constants.SORT_RANDOM)
        if genre_items:
            for count, item in enumerate(genre_items):
                genre_json["art"]["poster.%s" % count] = item["art"].get(
                    "poster", "")
                genre_json["art"]["fanart.%s" % count] = item["art"].get(
                    "fanart", "")
                if "fanart" not in genre_json["art"]:
                    # set genre's primary fanart image to first movie fanart
                    genre_json["art"]["fanart"] = item["art"].get("fanart", "")
        return genre_json

    def get_top_250(self):
        """ get all imdb top250 movies and tv shows in library """
        all_items = self.movies.top250()
        all_items += self.tvshows.top250()
        return all_items

    def get_items_for_recommended(self):
        """get all items for recommended and top picks methods"""
        filters = [kodi_constants.FILTER_UNWATCHED]
        # get all unwatched, not in-progess movies & tvshows
        if self.options.get("tag"):
            filters.append({
                "operator": "contains",
                "field": "tag",
                "value": self.options["tag"]
            })
        movies = self.metadatautils.kodidb.movies(filters=filters)
        filters.append({
            "operator": "false",
            "field": "inprogress",
            "value": ""
        })
        tvshows = self.metadatautils.process_method_on_list(
            self.tvshows.process_tvshow,
            self.metadatautils.kodidb.tvshows(filters=filters))
        return movies + tvshows

    def get_recently_watched_item(self):
        """ get a random recently watched movie or tvshow """
        num_recent_similar = (self.options["num_recent_similar"] + 1) / 2
        recent_items = self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_LASTPLAYED,
            filters=[kodi_constants.FILTER_WATCHED],
            limits=(0, num_recent_similar))
        recent_items += self.metadatautils.kodidb.tvshows(
            sort=kodi_constants.SORT_LASTPLAYED,
            filters=[
                kodi_constants.FILTER_WATCHED, kodi_constants.FILTER_INPROGRESS
            ],
            filtertype="or",
            limits=(0, num_recent_similar))
        if recent_items:
            return recent_items[randint(0, len(recent_items) - 1)]

    def similar(self):
        """ get similar movies and similar tvshows for given imdbid"""
        if self.options["exp_recommended"]:
            # get ref item, and check if movie
            ref_item = self.get_recently_watched_item()
            is_ref_movie = False
            if ref_item:
                is_ref_movie = "uniqueid" in ref_item
            # create list of all items
            if self.options["hide_watched_similar"]:
                all_items = self.metadatautils.kodidb.movies(
                    filters=[kodi_constants.FILTER_UNWATCHED])
                all_items += self.metadatautils.process_method_on_list(
                    self.tvshows.process_tvshow,
                    self.metadatautils.kodidb.tvshows(filters=[
                        kodi_constants.FILTER_UNWATCHED, {
                            "operator": "false",
                            "field": "inprogress",
                            "value": ""
                        }
                    ]))
            else:
                all_items = self.metadatautils.kodidb.movies()
                all_items += self.metadatautils.process_method_on_list(
                    self.tvshows.process_tvshow,
                    self.metadatautils.kodidb.tvshows())
            if ref_item:
                if is_ref_movie:
                    # define sets for speed
                    set_genres = set(ref_item["genre"])
                    set_directors = set(ref_item["director"])
                    set_writers = set(ref_item["writer"])
                    set_cast = set([x["name"] for x in ref_item["cast"][:5]])
                    # get similarity score for all items
                    for item in all_items:
                        if "uniqueid" in item:
                            # if item is also movie, check if it's the ref_item
                            if item["title"] == ref_item["title"] and item[
                                    "year"] == ref_item["year"]:
                                # don't rank the reference movie
                                similarscore = 0
                            else:
                                # otherwise, use movie method for score
                                similarscore = self.movies.get_similarity_score(
                                    ref_item,
                                    item,
                                    set_genres=set_genres,
                                    set_directors=set_directors,
                                    set_writers=set_writers,
                                    set_cast=set_cast)
                        else:
                            # if item isn't movie, use mixed method
                            similarscore = self.get_similarity_score(
                                ref_item, item)
                        # set extraproperties
                        item["similarscore"] = similarscore
                        item["extraproperties"] = {
                            "similartitle": ref_item["title"],
                            "originalpath": item["file"]
                        }
                else:
                    # define sets for speed
                    set_genres = set(ref_item["genre"])
                    set_cast = set([x["name"] for x in ref_item["cast"][:10]])
                    # get similarity score for all items
                    for item in all_items:
                        if "uniqueid" not in item:
                            # if item is also tvshow, check if it's the ref_item
                            if item["title"] == ref_item["title"] and item[
                                    "year"] == ref_item["year"]:
                                # don't rank the reference movie
                                similarscore = 0
                            else:
                                # otherwise, use tvshow method for score
                                similarscore = self.tvshows.get_similarity_score(
                                    ref_item,
                                    item,
                                    set_genres=set_genres,
                                    set_cast=set_cast)
                        else:
                            # if item isn't tvshow, use mixed method
                            similarscore = self.get_similarity_score(
                                ref_item, item)
                        # set extraproperties
                        item["similarscore"] = similarscore
                        item["extraproperties"] = {
                            "similartitle": ref_item["title"],
                            "originalpath": item["file"]
                        }
                # return list sorted by score and capped by limit
                return sorted(all_items,
                              key=itemgetter("similarscore"),
                              reverse=True)[:self.options["limit"]]
        else:
            all_items = self.movies.similar()
            all_items += self.tvshows.similar()
            all_items += self.albums.similar()
            all_items += self.songs.similar()
            return sorted(all_items,
                          key=lambda k: random())[:self.options["limit"]]

    def sort_by_recommended(self, all_items, probe=False):
        if probe:
            all_refs = self.get_references_by_profile()
        else:
            all_refs = self.get_references_last_played()
        # average scores together for every item
        if all_refs:
            for item in all_items:
                similarscore = 0
                for ref_item in all_refs:
                    # add all similarscores for item
                    if "uniqueid" in ref_item and "uniqueid" in item:
                        # use movies method if both items are movies
                        similarscore += self.movies.get_similarity_score(
                            ref_item, item)
                    elif "uniqueid" in ref_item or "uniqueid" in item:
                        # use media method if only one item is a movie
                        similarscore += self.get_similarity_score(
                            ref_item, item)
                    else:
                        # use tvshows method if neither items are movies
                        similarscore += self.tvshows.get_similarity_score(
                            ref_item, item)

                item["recommendedscore"] = similarscore / (
                    1 + item["playcount"]) / len(all_refs)
            # return sorted list capped by limit
            return sorted(all_items,
                          key=itemgetter("recommendedscore"),
                          reverse=True)[:self.options["limit"]]

    def get_references_last_played(self):
        """ sort list of mixed movies/tvshows by recommended score"""
        # get recently watched items
        num_recent_similar = self.options["num_recent_similar"]
        all_refs = []
        all_refs += self.metadatautils.kodidb.tvshows(
            sort=kodi_constants.SORT_LASTPLAYED,
            filters=[kodi_constants.FILTER_WATCHED],
            limits=(0, num_recent_similar))
        all_refs += self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_LASTPLAYED,
            filters=[kodi_constants.FILTER_WATCHED],
            limits=(0, num_recent_similar))
        return all_refs

    def get_references_by_profile(self):
        """ sort list of mixed movies/tvshows by recommended score"""
        # get recently watched items
        num_recent_similar = self.options["num_recent_similar"] + 1
        # get random values for pools
        first_pool = randint(1, num_recent_similar - 1)
        second_pool = randint(1, num_recent_similar - first_pool)

        # first pool
        all_refs_movies = self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_RANDOM,
            filters=[{
                "operator": "contains",
                "field": "tag",
                "value": 'mylist'
            }],
            limits=(0, first_pool))
        all_refs_tvshows = self.metadatautils.kodidb.tvshows(
            sort=kodi_constants.SORT_RANDOM,
            filters=[{
                "operator": "contains",
                "field": "tag",
                "value": 'mylist'
            }],
            limits=(0, first_pool))
        # second pool
        all_refs_movies += self.metadatautils.kodidb.movies(
            sort=kodi_constants.SORT_RANDOM,
            filters=[{
                "operator": "greaterthan",
                "field": "userrating",
                "value": '6'
            }, {
                "operator": "doesnotcontain",
                "field": "tag",
                "value": 'mylist'
            }],
            limits=(0, second_pool))
        all_refs_tvshows += self.metadatautils.kodidb.tvshows(
            sort=kodi_constants.SORT_RANDOM,
            filters=[{
                "operator": "greaterthan",
                "field": "userrating",
                "value": '6'
            }, {
                "operator": "doesnotcontain",
                "field": "tag",
                "value": 'mylist'
            }],
            limits=(0, second_pool))
        # fill what's left
        # note: might be overlap but probably better to allow some than add calculation time & space in memory
        third_pool = num_recent_similar * 2 - (len(all_refs_movies) +
                                               len(all_refs_tvshows))
        if third_pool > 0 and (len(all_refs_movies) +
                               len(all_refs_tvshows)) > 0:
            priority_factor = int(
                len(all_refs_movies) /
                (len(all_refs_movies) + len(all_refs_tvshows)))
            third_pool_movies = third_pool * priority_factor
            third_pool_tvshows = third_pool - third_pool_movies

            all_refs_movies += self.metadatautils.kodidb.movies(
                sort=kodi_constants.SORT_LASTPLAYED,
                filters=[kodi_constants.FILTER_WATCHED],
                limits=(0, third_pool_movies))
            all_refs_tvshows += self.metadatautils.kodidb.tvshows(
                sort=kodi_constants.SORT_LASTPLAYED,
                filters=[kodi_constants.FILTER_WATCHED],
                limits=(0, third_pool_tvshows))
        return all_refs_movies + all_refs_tvshows

    def playlist_recommended(self, all_items):
        return self.sort_by_recommended(all_items)

    def playlist_toppicks(self, all_items):
        return self.sort_by_recommended(all_items, True)

    def playlist_random(self, all_items):
        return sorted(all_items,
                      key=lambda k: random())[:self.options["limit"]]

    def playlist_recent(self, all_items):
        return sorted(all_items, key=itemgetter("dateadded"),
                      reverse=True)[:self.options["limit"]]

    def playlist_year(self, all_items):
        return sorted(all_items, key=itemgetter("year"),
                      reverse=True)[:self.options["limit"]]

    def playlist_title(self, all_items):
        return sorted(all_items,
                      key=itemgetter("title"))[:self.options["limit"]]

    @staticmethod
    def get_similarity_score(ref_item, other_item):
        """get a similarity score (0-.75) between movie and tvshow"""
        set_genres = set(ref_item["genre"])
        set_cast = set([x["name"] for x in ref_item["cast"][:5]])
        # calculate individual scores for contributing factors
        # genre_score = (number of matching genres) / (number of unique genres between both)
        genre_score = float(len(set_genres.intersection(other_item["genre"]))) / \
                      len(set_genres.union(other_item["genre"]))
        # cast_score is normalized by fixed amount of 5, and scaled up nonlinearly
        cast_score = (float(
            len(
                set_cast.intersection(
                    [x["name"]
                     for x in other_item["cast"][:5]]))) / 5)**(1. / 2)
        # rating_score is "closeness" in rating, scaled to 1
        if ref_item["rating"] and other_item["rating"] and abs(
                ref_item["rating"] - other_item["rating"]) < 3:
            rating_score = 1 - abs(ref_item["rating"] -
                                   other_item["rating"]) / 3
        else:
            rating_score = 0
        # year_score is "closeness" in release year, scaled to 1 (0 if not from same decade)
        if ref_item["year"] and other_item["year"] and abs(
                ref_item["year"] - other_item["year"]) < 10:
            year_score = 1 - abs(ref_item["year"] - other_item["year"]) / 10
        else:
            year_score = 0
        # calculate overall score using weighted average
        similarscore = .5 * genre_score + .1 * cast_score + .025 * rating_score + .025 * year_score
        return similarscore
class Media(object):
    '''all media (mixed) widgets provided by the script'''
    def __init__(self, addon, artutils, options):
        '''Initializations pass our common classes and the widget options as arguments'''
        self.artutils = artutils
        self.addon = addon
        self.options = options
        self.movies = Movies(self.addon, self.artutils, self.options)
        self.tvshows = Tvshows(self.addon, self.artutils, self.options)
        self.songs = Songs(self.addon, self.artutils, self.options)
        self.albums = Albums(self.addon, self.artutils, self.options)
        self.pvr = Pvr(self.addon, self.artutils, self.options)
        self.episodes = Episodes(self.addon, self.artutils, self.options)

    def listing(self):
        '''main listing with all our movie nodes'''
        all_items = [
            (self.addon.getLocalizedString(32011),
             "inprogress&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32005), "recent&mediatype=media",
             "DefaultMovies.png"),
            (self.addon.getLocalizedString(32004),
             "recommended&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32007),
             "inprogressandrecommended&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32060),
             "inprogressandrandom&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32022), "similar&mediatype=media",
             "DefaultMovies.png"),
            (self.addon.getLocalizedString(32059), "random&mediatype=media",
             "DefaultMovies.png"),
            (self.addon.getLocalizedString(32058), "top250&mediatype=media",
             "DefaultMovies.png"),
            (self.addon.getLocalizedString(32001),
             "favourites&mediatype=media", "DefaultMovies.png")
        ]
        return process_method_on_list(create_main_entry, all_items)

    def recommended(self):
        ''' get recommended media '''
        all_items = self.movies.recommended()
        all_items += self.tvshows.recommended()
        all_items += self.albums.recommended()
        all_items += self.songs.recommended()
        all_items += self.episodes.recommended()
        return sorted(all_items,
                      key=lambda k: random.random())[:self.options["limit"]]

    def recent(self):
        ''' get recently added media '''
        all_items = self.movies.recent()
        all_items += self.albums.recent()
        all_items += self.songs.recent()
        all_items += self.episodes.recent()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("dateadded"),
                      reverse=True)[:self.options["limit"]]

    def random(self):
        ''' get random media '''
        all_items = self.movies.random()
        all_items += self.tvshows.random()
        all_items += self.albums.random()
        all_items += self.songs.random()
        all_items += self.episodes.random()
        all_items += self.pvr.recordings()
        return sorted(all_items,
                      key=lambda k: random.random())[:self.options["limit"]]

    def inprogress(self):
        ''' get in progress media '''
        all_items = self.movies.inprogress()
        all_items += self.episodes.inprogress()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("lastplayed"),
                      reverse=True)[:self.options["limit"]]

    def similar(self):
        ''' get similar movies and similar tvshows for given imdbid'''
        all_items = self.movies.similar()
        all_items += self.tvshows.similar()
        all_items += self.albums.similar()
        all_items += self.songs.similar()
        return sorted(all_items,
                      key=lambda k: random.random())[:self.options["limit"]]

    def inprogressandrecommended(self):
        ''' get recommended AND in progress media '''
        all_items = self.inprogress()
        all_titles = [item["title"] for item in all_items]
        for item in self.recommended():
            if item["title"] not in all_titles:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def inprogressandrandom(self):
        ''' get in progress AND random movies '''
        all_items = self.inprogress()
        all_ids = [item["movieid"] for item in all_items]
        for item in self.random():
            if item["movieid"] not in all_ids:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def top250(self):
        ''' get imdb top250 movies in library '''
        all_items = self.movies.top250()
        all_items += self.tvshows.top250()
        return sorted(all_items,
                      key=itemgetter("top250_rank"))[:self.options["limit"]]

    def favourites(self):
        '''get favourite media'''
        from favourites import Favourites
        self.options["mediafilter"] = "media"
        return Favourites(self.addon, self.artutils, self.options).favourites()

    def favourite(self):
        '''synonym to favourites'''
        return self.favourites()
Exemple #3
0
class Media(object):
    '''all media (mixed) widgets provided by the script'''

    def __init__(self, addon, metadatautils, options):
        '''Initializations pass our common classes and the widget options as arguments'''
        self.metadatautils = metadatautils
        self.addon = addon
        self.options = options
        self.movies = Movies(self.addon, self.metadatautils, self.options)
        self.tvshows = Tvshows(self.addon, self.metadatautils, self.options)
        self.songs = Songs(self.addon, self.metadatautils, self.options)
        self.albums = Albums(self.addon, self.metadatautils, self.options)
        self.pvr = Pvr(self.addon, self.metadatautils, self.options)
        self.episodes = Episodes(self.addon, self.metadatautils, self.options)

    def listing(self):
        '''main listing with all our movie nodes'''
        all_items = [
            (self.addon.getLocalizedString(32011), "inprogress&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32070), "inprogressshowsandmovies&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32005), "recent&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32004), "recommended&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32007), "inprogressandrecommended&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32060), "inprogressandrandom&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32022), "similar&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32059), "random&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32058), "top250&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32001), "favourites&mediatype=media", "DefaultMovies.png"),
            (self.addon.getLocalizedString(32075), "playlistslisting&mediatype=media",
             "DefaultMovies.png"),
            (self.addon.getLocalizedString(32077), "playlistslisting&mediatype=media&tag=ref",
             "DefaultMovies.png")
        ]
        return self.metadatautils.process_method_on_list(create_main_entry, all_items)

    def playlistslisting(self):
        '''get tv playlists listing'''
        #TODO: append (Movie playlist) and (TV Show Playlist)
        #TODO: only show playlists with appropriate type (Movie or TV Show)
        movie_label = self.options.get("movie_label")
        tag_label = self.options.get("tag")
        all_items = []
        for item in self.metadatautils.kodidb.files("special://videoplaylists/"):
            # replace '&' with [and] -- will get fixed when processed in playlist action
            label = item["label"].replace('&', '[and]')
            if tag_label == 'ref':
                if movie_label:
                    details = (item["label"], "refplaylist&mediatype=media&movie_label=%s&tv_label=%s" %
                               (movie_label, label), "DefaultTvShows.png")
                else:
                    details = (item["label"], "playlistslisting&mediatype=media&tag=ref&movie_label=%s" % label,
                               "DefaultMovies.png")
            else:
                if movie_label:
                    details = (item["label"], "playlist&mediatype=media&movie_label=%s&tv_label=%s" %
                               (movie_label, label), "DefaultTvShows.png")
                else:
                    details = (item["label"], "playlistslisting&mediatype=media&movie_label=%s" % label,
                               "DefaultMovies.png")
            all_items.append(create_main_entry(details))
        return all_items

    def playlist(self):
        '''get items in both playlists, sorted by recommended score'''
        movie_label = self.options.get("movie_label").replace('[and]', '&')
        tv_label = self.options.get("tv_label").replace('[and]', '&')
        movies = self.metadatautils.kodidb.movies(
            filters=[{"operator": "is", "field": "playlist", "value": movie_label}])
        tvshows = self.metadatautils.kodidb.tvshows(
            filters=[{"operator": "is", "field": "playlist", "value": tv_label}])
        tvshows = self.metadatautils.process_method_on_list(self.tvshows.process_tvshow, tvshows)
        all_items = self.sort_by_recommended(movies+tvshows)
        return sorted(all_items, key=itemgetter("recommendedscore"), reverse=True)[:self.options["limit"]]

    def refplaylist(self):
        '''get items similar to items in playlists '''
        movie_label = self.options.get("movie_label").replace('[and]', '&')
        tv_label = self.options.get("tv_label").replace('[and]', '&')
        ref_movies = self.metadatautils.kodidb.movies(
            filters=[{"operator": "is", "field": "playlist", "value": movie_label}])
        ref_tvshows = self.metadatautils.kodidb.tvshows(
            filters=[{"operator": "is", "field": "playlist", "value": tv_label}])
        movies = self.metadatautils.kodidb.movies(
            filters=[{"operator": "isnot", "field": "playlist", "value": movie_label}])
        tvshows = self.metadatautils.kodidb.tvshows(
            filters=[{"operator": "isnot", "field": "playlist", "value": tv_label}])
        tvshows = self.metadatautils.process_method_on_list(self.tvshows.process_tvshow, tvshows)
        all_items = self.sort_by_recommended(movies+tvshows, ref_movies+ref_tvshows)
        return sorted(all_items, key=itemgetter("recommendedscore"), reverse=True)[:self.options["limit"]]

    def recommended(self):
        ''' get recommended media '''
        if self.options["exp_recommended"]:
            # get all unwatched, not in-progess movies & tvshows
            movies = self.metadatautils.kodidb.movies(filters=[kodi_constants.FILTER_UNWATCHED])
            tvshows = self.metadatautils.kodidb.tvshows(filters=[kodi_constants.FILTER_UNWATCHED])
            tvshows = self.metadatautils.process_method_on_list(self.tvshows.process_tvshow, tvshows)
            # return list sorted by recommended score, and capped by limit
            return self.sort_by_recommended(movies+tvshows)
        all_items = self.movies.recommended()
        all_items += self.tvshows.recommended()
        all_items += self.albums.recommended()
        all_items += self.songs.recommended()
        all_items += self.episodes.recommended()
        return sorted(all_items, key=lambda k: random.random())[:self.options["limit"]]

    def recent(self):
        ''' get recently added media '''
        all_items = self.movies.recent()
        all_items += self.albums.recent()
        all_items += self.songs.recent()
        all_items += self.episodes.recent()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("dateadded"), reverse=True)[:self.options["limit"]]

    def random(self):
        ''' get random media '''
        all_items = self.movies.random()
        all_items += self.tvshows.random()
        all_items += self.albums.random()
        all_items += self.songs.random()
        all_items += self.episodes.random()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=lambda k: random.random())[:self.options["limit"]]

    def inprogress(self):
        ''' get in progress media '''
        all_items = self.movies.inprogress()
        all_items += self.episodes.inprogress()
        all_items += self.pvr.recordings()
        return sorted(all_items, key=itemgetter("lastplayed"), reverse=True)[:self.options["limit"]]

    def inprogressshowsandmovies(self):
        ''' get in progress media '''
        all_items = self.movies.inprogress()
        all_items += self.episodes.inprogress()
        return sorted(all_items, key=itemgetter("lastplayed"), reverse=True)[:self.options["limit"]]

    def similar(self):
        ''' get similar movies and similar tvshows for given imdbid'''
        if self.options["exp_recommended"]:
            # get ref item, and check if movie
            ref_item = self.get_recently_watched_item()
            is_ref_movie = ref_item.has_key("uniqueid")
            # create list of all items
            if self.options["hide_watched_similar"]:
                all_items = self.metadatautils.kodidb.movies(filters=[kodi_constants.FILTER_UNWATCHED])
                all_items += self.metadatautils.process_method_on_list(
                    self.tvshows.process_tvshow, self.metadatautils.kodidb.tvshows(
                        filters=[kodi_constants.FILTER_UNWATCHED]))
            else:
                all_items = self.metadatautils.kodidb.movies()
                all_items += self.metadatautils.process_method_on_list(
                    self.tvshows.process_tvshow, self.metadatautils.kodidb.tvshows())
            if not ref_item:
                return None
            if is_ref_movie:
                # define sets for speed
                set_genres = set(ref_item["genre"])
                set_directors = set(ref_item["director"])
                set_writers = set(ref_item["writer"])
                set_cast = set([x["name"] for x in ref_item["cast"][:5]])
                sets = (set_genres, set_directors, set_writers, set_cast)
                # get similarity score for all items
                for item in all_items:
                    if item.has_key("uniqueid"):
                        # if item is also movie, check if it's the ref_item
                        if item["title"] == ref_item["title"] and item["year"] == ref_item["year"]:
                            # don't rank the reference movie
                            similarscore = 0
                        else:
                            # otherwise, use movie method for score
                            similarscore = self.movies.get_similarity_score(
                                ref_item, item, sets=sets)
                    else:
                        # if item isn't movie, use mixed method
                        similarscore = self.get_similarity_score(ref_item, item)
                    # set extraproperties
                    item["similarscore"] = similarscore
                    item["extraproperties"] = {"similartitle": ref_item["title"], "originalpath": item["file"]}
            else:
                # define sets for speed
                set_genres = set(ref_item["genre"])
                set_cast = set([x["name"] for x in ref_item["cast"][:10]])
                sets = (set_genres, set_cast)
                # get similarity score for all items
                for item in all_items:
                    if not item.has_key("uniqueid"):
                        # if item is also tvshow, check if it's the ref_item
                        if item["title"] == ref_item["title"] and item["year"] == ref_item["year"]:
                            # don't rank the reference movie
                            similarscore = 0
                        else:
                            # otherwise, use tvshow method for score
                            similarscore = self.tvshows.get_similarity_score(
                                ref_item, item, sets=sets)
                    else:
                        # if item isn't tvshow, use mixed method
                        similarscore = self.get_similarity_score(ref_item, item)
                    # set extraproperties
                    item["similarscore"] = similarscore
                    item["extraproperties"] = {"similartitle": ref_item["title"], "originalpath": item["file"]}
            # return list sorted by score and capped by limit
            return sorted(all_items, key=itemgetter("similarscore"), reverse=True)[:self.options["limit"]]
        all_items = self.movies.similar()
        all_items += self.tvshows.similar()
        all_items += self.albums.similar()
        all_items += self.songs.similar()
        return sorted(all_items, key=lambda k: random.random())[:self.options["limit"]]

    def inprogressandrecommended(self):
        ''' get recommended AND in progress media '''
        all_items = self.inprogress()
        all_titles = [item["title"] for item in all_items]
        for item in self.recommended():
            if item["title"] not in all_titles:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def inprogressandrandom(self):
        ''' get in progress AND random movies '''
        all_items = self.inprogress()
        all_ids = [item["movieid"] for item in all_items]
        for item in self.random():
            if item["movieid"] not in all_ids:
                all_items.append(item)
        return all_items[:self.options["limit"]]

    def top250(self):
        ''' get imdb top250 movies in library '''
        all_items = self.movies.top250()
        all_items += self.tvshows.top250()
        return sorted(all_items, key=itemgetter("top250_rank"))[:self.options["limit"]]

    def favourites(self):
        '''get favourite media'''
        from favourites import Favourites
        self.options["mediafilter"] = "media"
        return Favourites(self.addon, self.metadatautils, self.options).favourites()

    def favourite(self):
        '''synonym to favourites'''
        return self.favourites()

    def get_recently_watched_item(self):
        ''' get a random recently watched movie or tvshow '''
        num_recent_similar = self.options["num_recent_similar"]
        # get recently played movies
        recent_items = self.metadatautils.kodidb.movies(sort=kodi_constants.SORT_LASTPLAYED,
                                                        filters=[kodi_constants.FILTER_WATCHED],
                                                        limits=(0, num_recent_similar))
        # get recently played episodes
        recent_items += self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_LASTPLAYED,
                                                           filters=[kodi_constants.FILTER_WATCHED],
                                                           limits=(0, num_recent_similar))
        # sort all by last played, then randomly pick
        recent_items = sorted(recent_items, key=itemgetter("lastplayed"), reverse=True)[:num_recent_similar]
        item = random.choice(recent_items)
        # if item is an episode, get its tvshow
        if not item.has_key("genre"):
            show_title = item['showtitle']
            title_filter = [{"field": "title", "operator": "is", "value": "%s" % show_title}]
            tvshow = self.metadatautils.kodidb.tvshows(filters=title_filter, limits=(0, 1))
            return tvshow[0]
        return item

    def sort_by_recommended(self, all_items, ref_items=None):
        ''' sort list of mixed movies/tvshows by recommended score'''
        # use recent items if ref_items not given
        if not ref_items:
            num_recent_similar = self.options["num_recent_similar"]
            # get recently watched movies
            movies = self.metadatautils.kodidb.movies(sort=kodi_constants.SORT_LASTPLAYED,
                                                      filters=[kodi_constants.FILTER_WATCHED],
                                                      limits=(0, 2*num_recent_similar))
            # get recently watched episodes
            episodes = self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_LASTPLAYED,
                                                          filters=[kodi_constants.FILTER_WATCHED],
                                                          limits=(0, 2*num_recent_similar))
            # get tvshows from episodes
            tvshows = self.tvshows.get_tvshows_from_episodes(episodes)
            # combine lists and sort by last played recent
            items = sorted(movies + tvshows, key=itemgetter('lastplayed'), reverse=True)
            # find duplicates and set weights
            titles = set()
            ref_items = list()
            weights = dict()
            weight_sum = 0
            for item in items:
                title = item['title']
                if title in titles:
                    weights[title] += 0.5
                    weight_sum += 0.5
                else:
                    ref_items.append(item)
                    titles.add(title)
                    weights[title] = 1
                    weight_sum += 1
                if weight_sum >= num_recent_similar:
                    break
            del titles, items, weight_sum
        else:
            # set equal weights for pre-defined ref_items
            weights = dict()
            for item in ref_items:
                weights[item['title']] = 1
        # average scores together for every item
        for item in all_items:
            similarscore = 0
            for ref_item in ref_items:
                title = ref_item['title']
                # add all similarscores for item
                if ref_item.has_key("uniqueid") and item.has_key("uniqueid"):
                    # use movies method if both items are movies
                    similarscore += weights[title] * self.movies.get_similarity_score(ref_item, item)
                elif ref_item.has_key("uniqueid") or item.has_key("uniqueid"):
                    # use media method if only one item is a movie
                    similarscore += weights[title] * self.get_similarity_score(ref_item, item)
                else:
                    # use tvshows method if neither items are movies
                    similarscore += weights[title] * self.tvshows.get_similarity_score(ref_item, item)
            # average score and scale down based on playcount
            item["recommendedscore"] = similarscore / (1+item["playcount"]) / len(ref_items)
        # return sorted list capped by limit
        return sorted(all_items, key=itemgetter("recommendedscore"), reverse=True)[:self.options["limit"]]

    def get_similarity_score(self, ref_item, other_item):
        '''
            get a similarity score (0-.625) between movie and tvshow
        '''
        # get set of genres
        if ref_item.has_key("uniqueid"):
            set_genres = set(ref_item["genre"])
        else:
            # change genres to movie equivalents if tvshow
            set_genres = self.convert_tvshow_genres(ref_item["genre"])
        set_cast = set([x["name"] for x in ref_item["cast"][:5]])
        # calculate individual scores for contributing factors
        # genre_score = (numer of matching genres) / (number of unique genres between both)
        genre_score = 0 if not set_genres else \
            float(len(set_genres.intersection(other_item["genre"]))) / \
            len(set_genres.union(other_item["genre"]))
        # cast_score is normalized by fixed amount of 5, and scaled up nonlinearly
        cast_score = (float(len(set_cast.intersection([x["name"] for x in other_item["cast"][:5]])))/5)**(1./2)
        # rating_score is "closeness" in rating, scaled to 1
        if ref_item["rating"] and other_item["rating"] and abs(ref_item["rating"]-other_item["rating"]) < 3:
            rating_score = 1 - abs(ref_item["rating"]-other_item["rating"])/3
        else:
            rating_score = 0
        # year_score is "closeness" in release year, scaled to 1 (0 if not from same decade)
        if ref_item["year"] and other_item["year"] and abs(ref_item["year"]-other_item["year"]) < 10:
            year_score = 1 - abs(ref_item["year"]-other_item["year"])/10
        else:
            year_score = 0
        # calculate overall score using weighted average
        similarscore = .5*genre_score + .05*cast_score + .025*rating_score + .05*year_score
        return similarscore

    @staticmethod
    def convert_tvshow_genres(genres):
        ''' converts tvshow genres into movie genre equivalent '''
        mapped_genres = {'TV Documentaries': 'Documentary',
                         'TV Sci-Fi & Fantasy': 'Sci-Fi & Fantasy',
                         'TV Action & Adventure': 'Action & Adventure',
                         'TV Comedies': 'Comedy',
                         'TV Mysteries': 'Mystery',
                         'TV Westerns': 'Westerns',
                         'TV Dramas': 'Drama',
                         'TV Crime Dramas': 'Crime Dramas',
                        }
        for genre in genres:
            if mapped_genres.has_key(genre):
                genre = mapped_genres[genre]
        return set(genres)