def __init__(self): """ Initialise object """ self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(self._auth, cache_path=kodiutils.get_cache_path())
def __init__(self): """ Initialise object """ auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(auth, cache_path=kodiutils.get_cache_path()) # Workaround for Raspberry Pi 3 and older kodiutils.set_global_setting('videoplayer.useomxplayer', True)
def test_get_stream(self): api = ContentApi(self._auth.get_token()) program = api.get_program('vier', 'auwch') episode = program.episodes[0] video = api.get_stream(episode.channel, episode.uuid) self.assertTrue(video) _LOGGER.info('Got video URL: %s', video)
def test_episodes(self): api = ContentApi(self._auth.get_token()) for channel, program in [('vier', 'auwch'), ('vijf', 'zo-man-zo-vrouw')]: program = api.get_program(channel, program) self.assertIsInstance(program, Program) self.assertIsInstance(program.seasons, dict) # self.assertIsInstance(program.seasons[0], Season) self.assertIsInstance(program.episodes, list) self.assertIsInstance(program.episodes[0], Episode) _LOGGER.info('Got program: %s', program)
def test_play_video_from_epg(self): epg = EpgApi() epg_programs = epg.get_epg('vier', date.today().strftime('%Y-%m-%d')) epg_program = [program for program in epg_programs if program.video_url][0] # Lookup the Episode data since we don't have an UUID api = ContentApi(self._auth.get_token()) episode = api.get_episode(epg_program.channel, epg_program.video_url) self.assertIsInstance(episode, Episode) # Get stream based on the Episode's UUID video = api.get_stream(episode.channel, episode.uuid) self.assertTrue(video)
class Metadata: """ Code responsible for the management of the local cached metadata """ def __init__(self): """ Initialise object """ self._api = ContentApi(cache_path=kodiutils.get_cache_path()) 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, refresh=True) # Close progress indicator progress.close() def fetch_metadata(self, callback=None, refresh=False): """ Fetch the metadata for all the items in the catalog :type callback: callable :type refresh: bool """ # Fetch all items from the catalog items = [] for channel in list(CHANNELS): items.extend(self._api.get_programs(channel, CACHE_PREVENT)) count = len(items) # Loop over all of them and download the metadata for index, item in enumerate(items): if isinstance(item, Program): self._api.get_program(item.channel, item.path, CACHE_PREVENT if refresh else CACHE_AUTO) # Run callback after every item if callback and callback(index, count): # Stop when callback returns False return False return True
def _resolve_stream(uuid): """ Resolve the stream for the requested item :type uuid: string """ try: # Check if we have credentials if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'): confirm = kodiutils.yesno_dialog( message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now? if confirm: kodiutils.open_settings() kodiutils.end_of_directory() return None # Fetch an auth token now try: auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) # Get stream information resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid) return resolved_stream except (InvalidLoginException, AuthenticationException) as ex: _LOGGER.exception(ex) kodiutils.ok_dialog(message=kodiutils.localize(30702, error=str(ex))) kodiutils.end_of_directory() return None except GeoblockedException: kodiutils.ok_dialog(message=kodiutils.localize(30710)) # This video is geo-blocked... return None except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return None
def play_from_page(self, channel, path): """ Play the requested item. :type channel: string :type path: string """ # Get episode information episode = ContentApi().get_episode(channel, path) # Play this now we have the uuid self.play(episode.uuid)
def play(item): """ Play the requested item. :type item: string """ # Workaround for Raspberry Pi 3 and older omxplayer = kodiutils.get_global_setting('videoplayer.useomxplayer') if omxplayer is False: kodiutils.set_global_setting('videoplayer.useomxplayer', True) try: # Check if we have credentials if not kodiutils.get_setting( 'username') or not kodiutils.get_setting('password'): confirm = kodiutils.yesno_dialog( message=kodiutils.localize(30701) ) # To watch a video, you need to enter your credentials. Do you want to enter them now? if confirm: kodiutils.open_settings() kodiutils.end_of_directory() return # Fetch an auth token now try: auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) # Get stream information resolved_stream = ContentApi(auth).get_stream_by_uuid(item) except (InvalidLoginException, AuthenticationException) as ex: _LOGGER.error(ex) kodiutils.ok_dialog( message=kodiutils.localize(30702, error=str(ex))) kodiutils.end_of_directory() return except GeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize( 30710)) # This video is geo-blocked... return except UnavailableException: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize( 30712)) # The video is unavailable... return # Play this item kodiutils.play(resolved_stream)
class Player: """ Code responsible for playing media """ def __init__(self): """ Initialise object """ self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(self._auth.get_token()) def play_from_page(self, channel, path): """ Play the requested item. :type channel: string :type path: string """ # Get episode information episode = self._api.get_episode(channel, path) # Play this now we have the uuid self.play(channel, episode.uuid) def play(self, channel, item): """ Play the requested item. :type channel: string :type item: string """ try: # Get stream information resolved_stream = self._api.get_stream(channel, item) except GeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked... return except UnavailableException: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30712)) # The video is unavailable... return # Play this item kodiutils.play(resolved_stream)
class SearchApi: """ GoPlay Search API """ API_ENDPOINT = 'https://api.goplay.be/search' def __init__(self): """ Initialise object """ self._api = ContentApi(None, cache_path=kodiutils.get_cache_path()) self._session = requests.session() def search(self, query): """ Get the stream URL to use for this video. :type query: str :rtype list[Program] """ if not query: return [] response = self._session.post(self.API_ENDPOINT, json={ "query": query, "page": 0, "mode": "programs" }) _LOGGER.debug(response.content) response.raise_for_status() data = json.loads(response.text) results = [] for hit in data['hits']['hits']: if hit['_source']['bundle'] == 'program': path = hit['_source']['url'].split('/')[-1] program = self._api.get_program(path, cache=CACHE_ONLY) if program: results.append(program) else: results.append( Program( path=path, title=hit['_source']['title'], description=hit['_source']['intro'], poster=hit['_source']['img'], )) return results
class Catalog: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(self._auth.get_token()) self._menu = Menu() def show_catalog(self): """ Show all the programs of all channels """ try: items = [] for channel in list(CHANNELS): items.extend(self._api.get_programs(channel)) except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [self._menu.generate_titleitem(item) for item in items] # 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='tvshows', sort='label') def show_catalog_channel(self, channel): """ Show the programs of a specific channel :type channel: str """ try: items = self._api.get_programs(channel) except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [] for item in items: listing.append(self._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='tvshows', sort='label') def show_program(self, channel, program_id): """ Show a program from the catalog :type channel: str :type program_id: str """ try: program = self._api.get_program(channel, program_id) except UnavailableException: kodiutils.ok_dialog( message=kodiutils.localize(30717) ) # This program is not available in the Vier/Vijf/Zes catalogue. kodiutils.end_of_directory() return if not program.episodes: kodiutils.ok_dialog( message=kodiutils.localize(30717) ) # This program is not available in the Vier/Vijf/Zes catalogue. kodiutils.end_of_directory() return # Go directly to the season when we have only one season if len(program.seasons) == 1: self.show_program_season(channel, program_id, program.seasons.values()[0].number) return studio = CHANNELS.get(program.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', channel=channel, program=program_id, season=-1), art_dict={ 'thumb': program.cover, 'fanart': program.background, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30204), # All seasons 'plot': program.description, 'set': program.title, 'studio': studio, })) # Add the seasons for s in list(program.seasons.values()): listing.append( TitleItem( title=s. title, # kodiutils.localize(30205, season=s.number), # Season {season} path=kodiutils.url_for('show_catalog_program_season', channel=channel, program=program_id, season=s.number), art_dict={ 'thumb': s.cover, 'fanart': program.background, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30205, season=s.number), # Season {season} 'plot': s.description, 'set': program.title, 'studio': studio, })) # Sort by label. Some programs return seasons unordered. kodiutils.show_listing(listing, 30003, content='tvshows', sort=['label']) def show_program_season(self, channel, program_id, season): """ Show the episodes of a program from the catalog :type channel: str :type program_id: str :type season: int """ try: program = self._api.get_program(channel, program_id) except UnavailableException: kodiutils.ok_dialog( message=kodiutils.localize(30717) ) # This program is not available in the Vier/Vijf/Zes catalogue. kodiutils.end_of_directory() return if season == -1: # Show all episodes episodes = program.episodes else: # Show the episodes of the season that was selected episodes = [e for e in program.episodes if e.season == season] listing = [ self._menu.generate_titleitem(episode) for episode in episodes ] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration'])
class Player: """ Code responsible for playing media """ def __init__(self): """ Initialise object """ auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(auth, cache_path=kodiutils.get_cache_path()) # Workaround for Raspberry Pi 3 and older kodiutils.set_global_setting('videoplayer.useomxplayer', True) @staticmethod def live(channel): """ Play the live channel. :type channel: string """ # TODO: this doesn't work correctly, playing a live program from the PVR won't play something from the beginning # Lookup current program # broadcast = self._epg.get_broadcast(channel, datetime.datetime.now().isoformat()) # if broadcast and broadcast.video_url: # self.play_from_page(broadcast.video_url) # return channel_name = CHANNELS.get(channel, dict(name=channel)) kodiutils.ok_dialog(message=kodiutils.localize(30718, channel=channel_name.get('name'))) # There is no live stream available for {channel}. kodiutils.end_of_directory() def play_from_page(self, path): """ Play the requested item. :type path: string """ if not path: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return # Get episode information episode = self._api.get_episode(path, cache=CACHE_PREVENT) resolved_stream = None if episode is None: kodiutils.ok_dialog(message=kodiutils.localize(30712)) return if episode.stream: # We already have a resolved stream. Nice! # We don't need credentials for these streams. resolved_stream = ResolvedStream( uuid=episode.uuid, url=episode.stream, ) _LOGGER.debug('Already got a resolved stream: %s', resolved_stream) if episode.uuid: # Lookup the stream resolved_stream = self._resolve_stream(episode.uuid) _LOGGER.debug('Resolved stream: %s', resolved_stream) if resolved_stream: titleitem = Menu.generate_titleitem(episode) if resolved_stream.license_url: # Generate license key license_key = self.create_license_key(resolved_stream.license_url, key_headers=dict( customdata=resolved_stream.auth, )) else: license_key = None kodiutils.play(resolved_stream.url, resolved_stream.stream_type, license_key, info_dict=titleitem.info_dict, art_dict=titleitem.art_dict, prop_dict=titleitem.prop_dict) def play(self, uuid): """ Play the requested item. :type uuid: string """ if not uuid: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return # Lookup the stream resolved_stream = self._resolve_stream(uuid) if resolved_stream.license_url: # Generate license key license_key = self.create_license_key(resolved_stream.license_url, key_headers=dict( customdata=resolved_stream.auth, )) else: license_key = None kodiutils.play(resolved_stream.url, resolved_stream.stream_type, license_key) @staticmethod def _resolve_stream(uuid): """ Resolve the stream for the requested item :type uuid: string """ try: # Check if we have credentials if not kodiutils.get_setting('username') or not kodiutils.get_setting('password'): confirm = kodiutils.yesno_dialog( message=kodiutils.localize(30701)) # To watch a video, you need to enter your credentials. Do you want to enter them now? if confirm: kodiutils.open_settings() kodiutils.end_of_directory() return None # Fetch an auth token now try: auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) # Get stream information resolved_stream = ContentApi(auth).get_stream_by_uuid(uuid) return resolved_stream except (InvalidLoginException, AuthenticationException) as ex: _LOGGER.exception(ex) kodiutils.ok_dialog(message=kodiutils.localize(30702, error=str(ex))) kodiutils.end_of_directory() return None except GeoblockedException: kodiutils.ok_dialog(message=kodiutils.localize(30710)) # This video is geo-blocked... return None except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30712)) # The video is unavailable... return None @staticmethod def create_license_key(key_url, key_type='R', key_headers=None, key_value=None): """ Create a license key string that we need for inputstream.adaptive. :param str key_url: :param str key_type: :param dict[str, str] key_headers: :param str key_value: :rtype: str """ header = '' if key_headers: header = urlencode(key_headers) if key_type in ('A', 'R', 'B'): key_value = key_type + '{SSM}' elif key_type == 'D': if 'D{SSM}' not in key_value: raise ValueError('Missing D{SSM} placeholder') key_value = quote(key_value) return '%s|%s|%s|' % (key_url, header, key_value)
def test_programs(self): api = ContentApi(self._auth.get_token()) for channel in ['vier', 'vijf', 'zes']: channels = api.get_programs(channel) self.assertIsInstance(channels, list)
def __init__(self): """ Initialise object """ self._api = ContentApi(cache_path=kodiutils.get_cache_path())
class Metadata: """ Code responsible for the management of the local cached metadata """ def __init__(self): """ Initialise object """ self._api = ContentApi(cache_path=kodiutils.get_cache_path()) 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, refresh=True) # Close progress indicator progress.close() def fetch_metadata(self, callback=None, refresh=False): """ Fetch the metadata for all the items in the catalog :type callback: callable :type refresh: bool """ # Fetch all items from the catalog items = [] for channel in list(CHANNELS): items.extend(self._api.get_programs(channel, CACHE_PREVENT)) count = len(items) # Loop over all of them and download the metadata for index, item in enumerate(items): if isinstance(item, Program): self._api.get_program(item.channel, item.path, CACHE_PREVENT if refresh else CACHE_AUTO) # Run callback after every item if callback and callback(index, count): # Stop when callback returns True return False return True @staticmethod def clean(): """ Clear metadata (called from settings) """ cache_path = kodiutils.get_cache_path() _, files = kodiutils.listdir(cache_path) for filename in files: if not kodiutils.delete(os.path.join(cache_path, filename)): return kodiutils.ok_dialog(message=kodiutils.localize( 30721)) # Clearing local metadata failed kodiutils.set_setting('metadata_last_updated', '0') return kodiutils.ok_dialog( message=kodiutils.localize(30714)) # Local metadata is cleared
def __init__(self): """ Initialise object """ self._api = ContentApi(None, cache_path=kodiutils.get_cache_path()) self._session = requests.session()
class Catalog: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(self._auth, cache_path=kodiutils.get_cache_path()) def show_catalog(self): """ Show all the programs of all channels """ try: items = self._api.get_programs() except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [Menu.generate_titleitem(item) for item in items] # Sort items by title # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='tvshows', sort='title') def show_catalog_channel(self, channel): """ Show the programs of a specific channel :type channel: str """ try: items = self._api.get_programs(channel) except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [] for item in items: listing.append(Menu.generate_titleitem(item)) # Sort items by title # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='tvshows', sort='title') def show_program(self, program_id): """ Show a program from the catalog :type program_id: str """ try: program = self._api.get_program( program_id, extract_clips=True, cache=CACHE_PREVENT ) # Use CACHE_PREVENT since we want fresh data except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize( 30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return if not program.episodes and not program.clips: kodiutils.ok_dialog(message=kodiutils.localize( 30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return # Go directly to the season when we have only one season and no clips if not program.clips and len(program.seasons) == 1: self.show_program_season(program_id, list(program.seasons.values())[0].uuid) return listing = [] # Add an '* All seasons' entry when configured in Kodi if program.seasons and 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_id, season='-1'), art_dict={ 'fanart': program.fanart, 'poster': program.poster, 'landscape': program.thumb, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30204), # All seasons 'plot': program.description, 'set': program.title, })) # Add the seasons for season in list(program.seasons.values()): listing.append( TitleItem( title=season. title, # kodiutils.localize(30205, season=season.number), # Season {season} path=kodiutils.url_for('show_catalog_program_season', program=program_id, season=season.uuid), art_dict={ 'fanart': program.fanart, 'poster': program.poster, 'landscape': program.thumb, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize( 30205, season=season.number), # Season {season} 'plot': season.description or program.description, 'set': program.title, })) # Add Clips if program.clips: listing.append( TitleItem( title=kodiutils.localize( 30059, program=program.title), # Clips for {program} path=kodiutils.url_for('show_catalog_program_clips', program=program_id), art_dict={ 'fanart': program.fanart, 'poster': program.poster, 'landscape': program.thumb, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize( 30059, program=program.title), # Clips for {program} 'plot': kodiutils.localize(30060, program=program.title ), # Watch short clips of {program} 'set': program.title, })) # Sort by label. Some programs return seasons unordered. kodiutils.show_listing(listing, 30003, content='tvshows') def show_program_season(self, program_id, season_uuid): """ Show the episodes of a program from the catalog :type program_id: str :type season_uuid: str """ try: program = self._api.get_program(program_id) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize( 30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return if season_uuid == "-1": # Show all episodes episodes = program.episodes else: # Show the episodes of the season that was selected episodes = [ e for e in program.episodes if e.season_uuid == season_uuid ] listing = [Menu.generate_titleitem(episode) for episode in episodes] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration']) def show_program_clips(self, program_id): """ Show the clips of a program from the catalog :type program_id: str """ try: # We need to query the backend, since we don't cache clips. program = self._api.get_program(program_id, extract_clips=True, cache=CACHE_PREVENT) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize( 30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return listing = [ Menu.generate_titleitem(episode) for episode in program.clips ] # Sort like we get our results back. kodiutils.show_listing(listing, 30003, content='episodes') def show_categories(self): """ Shows the categories """ categories = self._api.get_categories() listing = [] for category in categories: listing.append( TitleItem(title=category.title, path=kodiutils.url_for('show_category', category=category.uuid), info_dict={ 'title': category.title, })) kodiutils.show_listing(listing, 30003, sort=['title']) def show_category(self, uuid): """ Shows a category """ programs = self._api.get_category_content(int(uuid)) listing = [Menu.generate_titleitem(program) for program in programs] kodiutils.show_listing(listing, 30003, content='tvshows') def show_recommendations(self): """ Shows the recommendations """ # "Meest bekeken" has a specific API endpoint, the other categories are scraped from the website. listing = [ TitleItem(title='Meest bekeken', path=kodiutils.url_for('show_recommendations_category', category='meest-bekeken'), info_dict={ 'title': 'Meest bekeken', }) ] recommendations = self._api.get_recommendation_categories() for category in recommendations: listing.append( TitleItem(title=category.title, path=kodiutils.url_for( 'show_recommendations_category', category=category.uuid), info_dict={ 'title': category.title, })) kodiutils.show_listing(listing, 30005, content='tvshows') def show_recommendations_category(self, uuid): """ Shows the a category of the recommendations """ if uuid == 'meest-bekeken': programs = self._api.get_popular_programs() episodes = [] else: recommendations = self._api.get_recommendation_categories() category = next(category for category in recommendations if category.uuid == uuid) programs = category.programs episodes = category.episodes listing = [] for episode in episodes: title_item = Menu.generate_titleitem(episode) title_item.info_dict[ 'title'] = episode.program_title + ' - ' + title_item.title listing.append(title_item) for program in programs: listing.append(Menu.generate_titleitem(program)) kodiutils.show_listing(listing, 30005, content='tvshows') def show_mylist(self): """ Show all the programs of all channels """ try: mylist, _ = self._auth.get_dataset('myList', 'myList') except Exception as ex: kodiutils.notification(message=str(ex)) raise items = [] if mylist: for item in mylist: program = self._api.get_program_by_uuid(item.get('id')) if program: program.my_list = True items.append(program) listing = [Menu.generate_titleitem(item) for item in items] # Sort items by title # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30011, content='tvshows', sort='title') def mylist_add(self, uuid): """ Add a program to My List """ if not uuid: kodiutils.end_of_directory() return mylist, sync_info = self._auth.get_dataset('myList', 'myList') if not mylist: mylist = [] if uuid not in [item.get('id') for item in mylist]: # Python 2.7 doesn't support .timestamp(), and windows doesn't do '%s', so we need to calculate it ourself epoch = datetime(1970, 1, 1, tzinfo=dateutil.tz.gettz('UTC')) now = datetime.now(tz=dateutil.tz.gettz('UTC')) timestamp = int((now - epoch).total_seconds()) * 1000 mylist.append({ 'id': uuid, 'timestamp': timestamp, }) self._auth.put_dataset('myList', 'myList', mylist, sync_info) kodiutils.end_of_directory() def mylist_del(self, uuid): """ Remove a program from My List """ if not uuid: kodiutils.end_of_directory() return mylist, sync_info = self._auth.get_dataset('myList', 'myList') if not mylist: mylist = [] new_mylist = [item for item in mylist if item.get('id') != uuid] self._auth.put_dataset('myList', 'myList', new_mylist, sync_info) kodiutils.end_of_directory()
class Catalog: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ auth = AuthApi(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_tokens_path()) self._api = ContentApi(auth, cache_path=kodiutils.get_cache_path()) def show_catalog(self): """ Show all the programs of all channels """ try: items = [] for channel in list(CHANNELS): items.extend(self._api.get_programs(channel)) except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [Menu.generate_titleitem(item) for item in items] # Sort items by title # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='tvshows', sort='title') def show_catalog_channel(self, channel): """ Show the programs of a specific channel :type channel: str """ try: items = self._api.get_programs(channel) except Exception as ex: kodiutils.notification(message=str(ex)) raise listing = [] for item in items: listing.append(Menu.generate_titleitem(item)) # Sort items by title # Used for A-Z listing or when movies and episodes are mixed. kodiutils.show_listing(listing, 30003, content='tvshows', sort='title') def show_program(self, channel, program_id): """ Show a program from the catalog :type channel: str :type program_id: str """ try: program = self._api.get_program(channel, program_id, extract_clips=True, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return if not program.episodes and not program.clips: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return # Go directly to the season when we have only one season and no clips if not program.clips and len(program.seasons) == 1: self.show_program_season(channel, program_id, list(program.seasons.values())[0].uuid) return studio = CHANNELS.get(program.channel, {}).get('studio_icon') listing = [] # Add an '* All seasons' entry when configured in Kodi if program.seasons and 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', channel=channel, program=program_id, season='-1'), art_dict={ 'fanart': program.background, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30204), # All seasons 'plot': program.description, 'set': program.title, 'studio': studio, } ) ) # Add the seasons for season in list(program.seasons.values()): listing.append( TitleItem( title=season.title, # kodiutils.localize(30205, season=season.number), # Season {season} path=kodiutils.url_for('show_catalog_program_season', channel=channel, program=program_id, season=season.uuid), art_dict={ 'fanart': program.background, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30205, season=season.number), # Season {season} 'plot': season.description, 'set': program.title, 'studio': studio, } ) ) # Add Clips if program.clips: listing.append( TitleItem( title=kodiutils.localize(30059, program=program.title), # Clips for {program} path=kodiutils.url_for('show_catalog_program_clips', channel=channel, program=program_id), art_dict={ 'fanart': program.background, }, info_dict={ 'tvshowtitle': program.title, 'title': kodiutils.localize(30059, program=program.title), # Clips for {program} 'plot': kodiutils.localize(30060, program=program.title), # Watch short clips of {program} 'set': program.title, 'studio': studio, } ) ) # Sort by label. Some programs return seasons unordered. kodiutils.show_listing(listing, 30003, content='tvshows') def show_program_season(self, channel, program_id, season_uuid): """ Show the episodes of a program from the catalog :type channel: str :type program_id: str :type season_uuid: str """ try: program = self._api.get_program(channel, program_id) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return if season_uuid == "-1": # Show all episodes episodes = program.episodes else: # Show the episodes of the season that was selected episodes = [e for e in program.episodes if e.season_uuid == season_uuid] listing = [Menu.generate_titleitem(episode) for episode in episodes] # Sort by episode number by default. Takes seasons into account. kodiutils.show_listing(listing, 30003, content='episodes', sort=['episode', 'duration']) def show_program_clips(self, channel, program_id): """ Show the clips of a program from the catalog :type channel: str :type program_id: str """ try: # We need to query the backend, since we don't cache clips. program = self._api.get_program(channel, program_id, extract_clips=True, cache=CACHE_PREVENT) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the catalogue. kodiutils.end_of_directory() return listing = [Menu.generate_titleitem(episode) for episode in program.clips] # Sort like we get our results back. kodiutils.show_listing(listing, 30003, content='episodes')
def test_notifications(self): api = ContentApi(self._auth.get_token()) notifications = api.get_notifications() self.assertIsInstance(notifications, list)