class Metadata: """ Code responsible for the refreshing of the metadata """ def __init__(self): """ Initialise object """ self._auth = Auth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_setting('loginprovider'), kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = Api(self._auth) def update(self): """ Update the metadata with a foreground progress indicator """ # Create progress indicator progress = kodiutils.progress( message=kodiutils.localize(30715)) # Updating metadata def update_status(i, total): """ Update the progress indicator """ progress.update( int(((i + 1) / total) * 100), kodiutils.localize( 30716, index=i + 1, total=total)) # Updating metadata ({index}/{total}) return progress.iscanceled() self.fetch_metadata(callback=update_status) # Close progress indicator progress.close() def fetch_metadata(self, callback=None): """ Fetch the metadata for all the items in the catalog :type callback: callable """ # Fetch all items from the catalog items = self._api.get_items() count = len(items) # Loop over all of them and download the metadata for index, item in enumerate(items): if isinstance(item, Movie): self._api.get_movie(item.movie_id) elif isinstance(item, Program): self._api.get_program(item.program_id) # Run callback after every item if callback and callback(index, count): # Stop when callback returns False return False return True @staticmethod def clean(): """ Clear metadata (called from settings) """ kodiutils.invalidate_cache() kodiutils.set_setting('metadata_last_updated', '0') kodiutils.ok_dialog( message=kodiutils.localize(30714)) # Local metadata is cleared
class Catalog: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = Auth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_setting('loginprovider'), kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = Api(self._auth) def show_catalog(self): """ Show the catalog. """ categories = self._api.get_categories() listing = [] for cat in categories: listing.append(TitleItem( title=cat.title, path=kodiutils.url_for('show_catalog_category', category=cat.category_id), info_dict=dict( plot='[B]{category}[/B]'.format(category=cat.title), ), )) # Sort categories by default like in Streamz. kodiutils.show_listing(listing, 30003, content='files') def show_catalog_category(self, category=None): """ Show a category in the catalog. :type category: str """ items = self._api.get_items(category) listing = [] for item in items: listing.append(Menu.generate_titleitem(item)) # Sort items by label, but don't put folders at the top. # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='movies' if category == 'films' else 'tvshows', sort=['label', 'year', 'duration']) def show_program(self, program): """ Show a program from the catalog. :type program: str """ try: program_obj = self._api.get_program(program, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable and can't be played right now. kodiutils.end_of_directory() return # Go directly to the season when we have only one season if len(program_obj.seasons) == 1: self.show_program_season(program, list(program_obj.seasons.values())[0].number) return # studio = CHANNELS.get(program_obj.channel, {}).get('studio_icon') listing = [] # Add an '* All seasons' entry when configured in Kodi if kodiutils.get_global_setting('videolibrary.showallitems') is True: listing.append(TitleItem( title='* %s' % kodiutils.localize(30204), # * All seasons path=kodiutils.url_for('show_catalog_program_season', program=program, season=-1), art_dict=dict( thumb=program_obj.cover, fanart=program_obj.cover, ), info_dict=dict( tvshowtitle=program_obj.name, title=kodiutils.localize(30204), # All seasons tagline=program_obj.description, set=program_obj.name, # studio=studio, mpaa=', '.join(program_obj.legal) if hasattr(program_obj, 'legal') and program_obj.legal else kodiutils.localize(30216), # All ages ), )) # Add the seasons for season in list(program_obj.seasons.values()): listing.append(TitleItem( title=kodiutils.localize(30205, season=season.number), # Season {season} path=kodiutils.url_for('show_catalog_program_season', program=program, season=season.number), art_dict=dict( thumb=season.cover, fanart=program_obj.cover, ), info_dict=dict( tvshowtitle=program_obj.name, title=kodiutils.localize(30205, season=season.number), # Season {season} tagline=program_obj.description, set=program_obj.name, # studio=studio, mpaa=', '.join(program_obj.legal) if hasattr(program_obj, 'legal') and program_obj.legal else kodiutils.localize(30216), # All ages ), )) # Sort by label. Some programs return seasons unordered. kodiutils.show_listing(listing, 30003, content='tvshows', sort=['label']) def show_program_season(self, program, season): """ Show the episodes of a program from the catalog. :type program: str :type season: int """ try: program_obj = self._api.get_program(program) # Use CACHE_AUTO since the data is just refreshed in show_program except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable and can't be played right now. kodiutils.end_of_directory() return if season == -1: # Show all seasons seasons = list(program_obj.seasons.values()) else: # Show the season that was selected seasons = [program_obj.seasons[season]] listing = [Menu.generate_titleitem(e) for s in seasons for e in list(s.episodes.values())] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration']) def show_recommendations(self, storefront): """ Show the recommendations. :type storefront: str """ recommendations = self._api.get_recommendations(storefront) listing = [] for cat in recommendations: listing.append(TitleItem( title=cat.title, path=kodiutils.url_for('show_recommendations_category', storefront=storefront, category=cat.category_id), info_dict=dict( plot='[B]{category}[/B]'.format(category=cat.title), ), )) # Sort categories by default like in Streamz. kodiutils.show_listing(listing, 30015, content='files') def show_recommendations_category(self, storefront, category): """ Show the items in a recommendations category. :type storefront: str :type category: str """ recommendations = self._api.get_recommendations(storefront) listing = [] for cat in recommendations: # Only show the requested category if cat.category_id != category: continue for item in cat.content: listing.append(Menu.generate_titleitem(item)) # Sort categories by default like in Streamz. kodiutils.show_listing(listing, 30015, content='tvshows', sort=['unsorted', 'label', 'year', 'duration']) def show_mylist(self): """ Show the items in "My List". """ mylist = self._api.get_swimlane('my-list') listing = [] for item in mylist: item.my_list = True listing.append(Menu.generate_titleitem(item)) # Sort categories by default like in Streamz. kodiutils.show_listing(listing, 30017, content='tvshows', sort=['unsorted', 'label', 'year', 'duration']) def mylist_add(self, video_type, content_id): """ Add an item to "My List". :type video_type: str :type content_id: str """ self._api.add_mylist(video_type, content_id) kodiutils.end_of_directory() def mylist_del(self, video_type, content_id): """ Remove an item from "My List". :type video_type: str :type content_id: str """ self._api.del_mylist(video_type, content_id) kodiutils.end_of_directory() def show_continuewatching(self): """ Show the items in "Continue Watching". """ mylist = self._api.get_swimlane('continue-watching') listing = [] for item in mylist: titleitem = Menu.generate_titleitem(item, progress=True) # Add Program Name to title since this list contains episodes from multiple programs title = '%s - %s' % ( titleitem.info_dict.get('tvshowtitle'), titleitem.info_dict.get('title')) titleitem.info_dict['title'] = title listing.append(titleitem) # Sort categories by default like in Streamz. kodiutils.show_listing(listing, 30019, content='episodes', sort='label')
class Library: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = Auth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_setting('loginprovider'), kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = Api(self._auth) def show_library_movies(self, movie=None): """ Return a list of the movies that should be exported. """ if movie is None: if kodiutils.get_setting_int('library_movies') == LIBRARY_FULL_CATALOG: # Full catalog # Use cache if available, fetch from api otherwise so we get rich metadata for new content items = self._api.get_items(content_filter=Movie, cache=CACHE_AUTO) else: # Only favourites, use cache if available, fetch from api otherwise items = self._api.get_swimlane('my-list', content_filter=Movie) else: items = [self._api.get_movie(movie)] listing = [] for item in items: title_item = Menu.generate_titleitem(item) # title_item.path = kodiutils.url_for('library_movies', movie=item.movie_id) # We need a trailing / title_item.path = 'plugin://plugin.video.streamz/library/movies/?movie=%s' % item.movie_id listing.append(title_item) kodiutils.show_listing(listing, 30003, content='movies', sort=['label', 'year', 'duration']) def show_library_tvshows(self, program=None): """ Return a list of the series that should be exported. """ if program is None: if kodiutils.get_setting_int('library_tvshows') == LIBRARY_FULL_CATALOG: # Full catalog # Use cache if available, fetch from api otherwise so we get rich metadata for new content # NOTE: We should probably use CACHE_PREVENT here, so we can pick up new episodes, but we can't since that would # require a massive amount of API calls for each update. We do this only for programs in 'My list'. items = self._api.get_items(content_filter=Program, cache=CACHE_AUTO) else: # Only favourites, don't use cache, fetch from api # If we use CACHE_AUTO, we will miss updates until the user manually opens the program in the Add-on items = self._api.get_swimlane('my-list', content_filter=Program, cache=CACHE_PREVENT) else: # Fetch only a single program items = [self._api.get_program(program, cache=CACHE_PREVENT)] listing = [] for item in items: title_item = Menu.generate_titleitem(item) # title_item.path = kodiutils.url_for('library_tvshows', program=item.program_id) # We need a trailing / title_item.path = 'plugin://plugin.video.streamz/library/tvshows/?program={program_id}'.format(program_id=item.program_id) listing.append(title_item) kodiutils.show_listing(listing, 30003, content='tvshows', sort=['label', 'year', 'duration']) def show_library_tvshows_program(self, program): """ Return a list of the episodes that should be exported. """ program_obj = self._api.get_program(program) listing = [] for season in list(program_obj.seasons.values()): for item in list(season.episodes.values()): title_item = Menu.generate_titleitem(item) # title_item.path = kodiutils.url_for('library_tvshows', program=item.program_id, episode=item.episode_id) title_item.path = 'plugin://plugin.video.streamz/library/tvshows/?program={program_id}&episode={episode_id}'.format(program_id=item.program_id, episode_id=item.episode_id) listing.append(title_item) # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration']) def check_library_movie(self, movie): """ Check if the given movie is still available. """ _LOGGER.debug('Checking if movie %s is still available', movie) # Our parent path always exists if movie is None: kodiutils.library_return_status(True) return if kodiutils.get_setting_int('library_movies') == LIBRARY_FULL_CATALOG: id_list = self._api.get_catalog_ids() else: id_list = self._api.get_mylist_ids() kodiutils.library_return_status(movie in id_list) def check_library_tvshow(self, program): """ Check if the given program is still available. """ _LOGGER.debug('Checking if program %s is still available', program) # Our parent path always exists if program is None: kodiutils.library_return_status(True) return if kodiutils.get_setting_int('library_tvshows') == LIBRARY_FULL_CATALOG: id_list = self._api.get_catalog_ids() else: id_list = self._api.get_mylist_ids() kodiutils.library_return_status(program in id_list) @staticmethod def mylist_added(video_type, content_id): """ Something has been added to My List. We want to index this. """ if video_type == CONTENT_TYPE_MOVIE: if kodiutils.get_setting_int('library_movies') != LIBRARY_ONLY_MYLIST: return # This unfortunately adds the movie to the database with the wrong parent path: # Library().update('plugin://plugin.video.streamz/library/movies/?movie=%s&kodi_action=refresh_info' % content_id) Library().update('plugin://plugin.video.streamz/library/movies/') elif video_type == CONTENT_TYPE_PROGRAM: if kodiutils.get_setting_int('library_tvshows') != LIBRARY_ONLY_MYLIST: return Library().update('plugin://plugin.video.streamz/library/tvshows/?program=%s&kodi_action=refresh_info' % content_id) @staticmethod def mylist_removed(video_type, content_id): """ Something has been removed from My List. We want to de-index this. """ if video_type == CONTENT_TYPE_MOVIE: if kodiutils.get_setting_int('library_movies') != LIBRARY_ONLY_MYLIST: return Library().clean('plugin://plugin.video.streamz/library/movies/?movie=%s' % content_id) elif video_type == CONTENT_TYPE_PROGRAM: if kodiutils.get_setting_int('library_tvshows') != LIBRARY_ONLY_MYLIST: return Library().clean('plugin://plugin.video.streamz/library/tvshows/?program=%s' % content_id) @staticmethod def configure(): """ Configure the library integration. """ # There seems to be no way to add sources automatically. # * https://forum.kodi.tv/showthread.php?tid=228840 # Open the sources view kodiutils.execute_builtin('ActivateWindow(Videos,sources://video/)') @staticmethod def update(path=None): """ Update the library integration. """ _LOGGER.debug('Scanning %s', path) if path: # We can use this to instantly add something to the library when we've added it to 'My List'. kodiutils.jsonrpc(method='VideoLibrary.Scan', params=dict( directory=path, showdialogs=False, )) else: kodiutils.jsonrpc(method='VideoLibrary.Scan') @staticmethod def clean(path=None): """ Cleanup the library integration. """ _LOGGER.debug('Cleaning %s', path) if path: # We can use this to instantly remove something from the library when we've removed it from 'My List'. # This only works from Kodi 19 however. See https://github.com/xbmc/xbmc/pull/18562 if kodiutils.kodi_version_major() > 18: kodiutils.jsonrpc(method='VideoLibrary.Clean', params=dict( directory=path, showdialogs=False, )) else: kodiutils.jsonrpc(method='VideoLibrary.Clean', params=dict( showdialogs=False, )) else: kodiutils.jsonrpc(method='VideoLibrary.Clean')