#!/usr/bin/python # -*- coding: utf-8 -*- ''' script.skin.helper.widgets episodes.py all episodes widgets provided by the script ''' from operator import itemgetter import xbmc from metadatautils import kodi_constants from utils import create_main_entry class Episodes(object): '''all episode widgets provided by the script''' options = {} kodidb = None addon = None def __init__(self, addon, metadatautils, options): '''Initialization''' self.addon = addon self.metadatautils = metadatautils 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" self.options = options def listing(self): '''main listing with all our episode nodes''' all_items = [ (self.addon.getLocalizedString(32027), "inprogress&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32002), "next&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32039), "recent&mediatype=episodes", "DefaultRecentlyAddedEpisodes.png"), (self.addon.getLocalizedString(32009), "recommended&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32010), "inprogressandrecommended&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32049), "inprogressandrandom&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32008), "random&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32042), "unaired&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32043), "nextaired&mediatype=episodes", "DefaultTvShows.png"), (self.addon.getLocalizedString(32068), "airingtoday&mediatype=episodes", "DefaultTvShows.png"), (xbmc.getLocalizedString(10134), "favourites&mediatype=episodes", "DefaultMovies.png") ] return self.metadatautils.process_method_on_list(create_main_entry, all_items) def favourites(self): '''get favourites''' from favourites import Favourites self.options["mediafilter"] = "episodes" return Favourites(self.addon, self.metadatautils, self.options).favourites() def favourite(self): '''synonym to favourites''' return self.favourites() def recommended(self): ''' get recommended episodes - library episodes with score higher than 7 ''' filters = [kodi_constants.FILTER_RATING] if self.options["hide_watched"]: filters.append(kodi_constants.FILTER_UNWATCHED) return self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_RATING, filters=filters, limits=(0, self.options["limit"])) def recent(self): ''' get recently added episodes ''' tvshow_episodes = {} total_count = 0 unique_count = 0 filters = [] 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"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) while unique_count < self.options["limit"]: recent_episodes = self.metadatautils.kodidb.episodes( sort=kodi_constants.SORT_DATEADDED, filters=filters, limits=( total_count, self.options["limit"] + total_count)) if not self.options["group_episodes"]: # grouping is not enabled, just return the result return recent_episodes if len(recent_episodes) < self.options["limit"]: # break the loop if there are no more episodes unique_count = self.options["limit"] # if multiple episodes for the same show with same addition date, we combine them into one # to do that we build a dict with recent episodes for all episodes of the same season added on the same date for episode in recent_episodes: total_count += 1 unique_key = "%s-%s-%s" % (episode["tvshowid"], episode["dateadded"].split(" ")[0], episode["season"]) if unique_key not in tvshow_episodes: tvshow_episodes[unique_key] = [] unique_count += 1 tvshow_episodes[unique_key].append(episode) # create our entries and return the result sorted by dateadded all_items = self.metadatautils.process_method_on_list(self.create_grouped_entry, tvshow_episodes.itervalues()) return sorted(all_items, key=itemgetter("dateadded"), reverse=True)[:self.options["limit"]] def random(self): ''' get random episodes ''' filters = [] 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"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) return self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_RANDOM, filters=filters, limits=(0, self.options["limit"])) def inprogress(self): ''' get in progress episodes ''' filters = [kodi_constants.FILTER_INPROGRESS] if self.options.get("tag"): filters.append({"operator": "contains", "field": "tag", "value": self.options["tag"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) return self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_LASTPLAYED, filters=filters, limits=(0, self.options["limit"])) def inprogressandrecommended(self): ''' get recommended AND in progress episodes ''' 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 recommended AND random episodes ''' all_items = self.inprogress() all_ids = [item["episodeid"] for item in all_items] for item in self.random(): if item["episodeid"] not in all_ids: all_items.append(item) return all_items[:self.options["limit"]] def next(self): ''' get next episodes ''' filters = [kodi_constants.FILTER_UNWATCHED] if self.options["next_inprogress_only"]: filters = [kodi_constants.FILTER_INPROGRESS] if self.options.get("tag"): filters.append({"operator": "contains", "field": "tag", "value": self.options["tag"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) # First we get a list of all the inprogress/unwatched TV shows ordered by lastplayed all_shows = self.metadatautils.kodidb.tvshows(sort=kodi_constants.SORT_LASTPLAYED, filters=filters, limits=(0, self.options["limit"])) return self.metadatautils.process_method_on_list(self.get_next_episode_for_show, [d['tvshowid'] for d in all_shows]) def get_next_episode_for_show(self, show_id): ''' get last played watched episode for show, return next unwatched episode after that, unless nothing after that, then return first episode ''' filters = [] fields = ["playcount", "season"] next_episode = None if not self.options["episodes_enable_specials"]: filters.append({"field": "season", "operator": "greaterthan", "value": "0"}) # get the next unwatched episode after the last played episode last_played_episode = self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_LASTPLAYED, filters=filters + [kodi_constants.FILTER_WATCHED], limits=(0, 1), tvshowid=show_id, fields=fields) if last_played_episode: last_played_episode = last_played_episode[0] filter_season = last_played_episode["season"] - 1 filter_season = [{"field": "season", "operator": "greaterthan", "value": "%s" % filter_season}] all_episodes = self.metadatautils.kodidb.episodes(sort=kodi_constants.SORT_EPISODE, filters=filters + filter_season, tvshowid=show_id, fields=fields) # find index of last_played_episode in the list all_episodes try: for index, episode in enumerate(all_episodes): if episode['episodeid'] == last_played_episode['episodeid']: i = 1 while True: if int(all_episodes[index + i]['playcount']) < 1: next_episode = all_episodes[index + i] break i += 1 except IndexError: # no unplayed episodes left next_episode = None # just get the first unwatched episode (e.g. when we simply do not yet have any fully played episodes) if not next_episode: next_episode = self.metadatautils.kodidb.episodes( sort=kodi_constants.SORT_EPISODE, filters=filters + [kodi_constants.FILTER_UNWATCHED], limits=(0, 1), tvshowid=show_id, fields=fields) next_episode = next_episode[0] if next_episode else None # return full details for our episode return self.metadatautils.kodidb.episode(next_episode["episodeid"]) if next_episode else None def unaired(self): ''' get all unaired episodes for shows in the library - provided by tvdb module''' self.metadatautils.thetvdb.days_ahead = 120 filters = [kodi_constants.FILTER_UNWATCHED] if self.options["next_inprogress_only"]: filters = [kodi_constants.FILTER_INPROGRESS] if self.options.get("tag"): filters.append({"operator": "contains", "field": "tag", "value": self.options["tag"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) # First we get a list of all the inprogress/unwatched TV shows ordered by lastplayed all_shows = self.metadatautils.kodidb.tvshows(sort=kodi_constants.SORT_LASTPLAYED, filters=filters, limits=(0, self.options["limit"])) tvshows_ids = [d['tvshowid'] for d in all_shows] episodes = self.metadatautils.thetvdb.get_kodi_unaired_episodes(False, False, tvshows_ids) episodes = episodes[:self.options["limit"]] return [self.map_episode_props(episode) for episode in episodes] def nextaired(self, days_ahead=60): ''' get all next airing episodes for shows in the library - provided by tvdb module''' self.metadatautils.thetvdb.days_ahead = days_ahead filters = [kodi_constants.FILTER_UNWATCHED] if self.options["next_inprogress_only"]: filters = [kodi_constants.FILTER_INPROGRESS] if self.options.get("tag"): filters.append({"operator": "contains", "field": "tag", "value": self.options["tag"]}) if self.options.get("path"): filters.append({"operator": "startswith", "field": "path", "value": self.options["path"]}) # First we get a list of all the inprogress/unwatched TV shows ordered by lastplayed all_shows = self.metadatautils.kodidb.tvshows(sort=kodi_constants.SORT_LASTPLAYED, filters=filters, limits=(0, self.options["limit"])) tvshows_ids = [d['tvshowid'] for d in all_shows] episodes = self.metadatautils.thetvdb.get_kodi_unaired_episodes(True, False, tvshows_ids) return [self.map_episode_props(episode) for episode in episodes] def airingtoday(self): ''' get today airing episodes - provided by tvdb module''' return self.nextaired(0) @staticmethod def create_grouped_entry(tvshow_episodes): '''helper for grouped episodes''' firstepisode = tvshow_episodes[0] if len(tvshow_episodes) > 2: # add as season entry if there were multiple episodes for the same show # use first episode as reference to keep the correct sorting order item = firstepisode item["type"] = "season" item["label"] = "%s %s" % (xbmc.getLocalizedString(20373), firstepisode["season"]) item["plot"] = u"[B]%s[/B] • %s %s[CR]%s: %s"\ % (item["label"], len(tvshow_episodes), xbmc.getLocalizedString(20387), xbmc.getLocalizedString(570), firstepisode["dateadded"].split(" ")[0]) item["extraproperties"] = {"UnWatchedEpisodes": "%s" % len(tvshow_episodes)} return item # just add the single item return firstepisode @staticmethod def map_episode_props(episode_details): ''''adds some of the optional fields as extra properties for the listitem''' extraprops = {} for item in ["network", "airdate", "airdate.label", "airtime", "airdatetime", "airdatetime.label", "airday"]: extraprops[item] = episode_details[item] extraprops["DBTYPE"] = "episode" episode_details["extraproperties"] = extraprops return episode_details