def __init__(self, kodi): """ Initialise object :type kodi: KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._menu = Menu(self._kodi)
def __init__(self, kodi): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._menu = Menu(self._kodi)
def __init__(self, port): """ Initialise object :type port: int """ self._vtm_go = VtmGo() self._vtm_go_epg = VtmGoEpg() self.port = port
def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = VtmGo(self._auth)
def __init__(self, kodi, port): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._vtm_go_epg = VtmGoEpg(self._kodi) self.port = port
def __init__(self): """ Initialise object """ try: self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) except NoLoginException: self._auth = None self._vtm_go = VtmGo(self._auth)
class Metadata: """ Code responsible for the refreshing of the metadata """ def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._vtm_go = VtmGo(self._auth) def update(self): """ Update the metadata with a foreground progress indicator """ # Create progress indicator progress_dialog = kodiutils.progress(message=kodiutils.localize(30715)) # Updating metadata def update_status(i, total): """ Update the progress indicator """ progress_dialog.update(int(((i + 1) / total) * 100), kodiutils.localize(30716, index=i + 1, total=total)) # Updating metadata ({index}/{total}) return progress_dialog.iscanceled() self.fetch_metadata(callback=update_status) # Close progress indicator progress_dialog.close() def fetch_metadata(self, callback=None): """ Fetch the metadata for all the items in the catalog :type callback: callable """ # Fetch a list of all items from the catalog items = self._vtm_go.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._vtm_go.get_movie(item.movie_id) elif isinstance(item, Program): self._vtm_go.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
def __init__(self, port): """ Initialise object :type port: int """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._vtm_go = VtmGo(self._auth) self._vtm_go_epg = VtmGoEpg() self.port = port
class Metadata: """ Code responsible for the refreshing of the metadata """ def __init__(self, kodi): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) def update(self): """ Update the metadata with a foreground progress indicator """ # Create progress indicator progress = self._kodi.show_progress(message=self._kodi.localize(30715)) # Updating metadata def update_status(i, total): """ Update the progress indicator """ progress.update(int(((i + 1) / total) * 100), self._kodi.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._vtm_go.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._vtm_go.get_movie(item.movie_id) elif isinstance(item, Program): self._vtm_go.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 def clean(self): """ Clear metadata (called from settings) """ self._kodi.invalidate_cache() self._kodi.set_setting('metadata_last_updated', '0') self._kodi.show_ok_dialog(message=self._kodi.localize(30714)) # Local metadata is cleared
def __init__(self, kodi): """ Initialise object :type kodi: KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._vtm_go_stream = VtmGoStream(self._kodi)
def __init__(self): Monitor.__init__(self) self.kodi = KodiWrapper() self.vtm_go = VtmGo(self.kodi) self.vtm_go_auth = VtmGoAuth(self.kodi) self.update_interval = 24 * 3600 # Every 24 hours self.cache_expiry = 30 * 24 * 3600 # One month
def onSettingsChanged(self): # pylint: disable=invalid-name """ Callback when a setting has changed """ # Refresh our VtmGo instance self.vtm_go = VtmGo(self._kodi) if self.vtm_go_auth.has_credentials_changed(): _LOGGER.debug('Clearing auth tokens due to changed credentials') self.vtm_go_auth.clear_token() # Refresh container self._kodi.container_refresh()
def onSettingsChanged(self): """ Callback when a setting has changed """ # Refresh our VtmGo instance self.vtm_go = VtmGo(self.kodi) if self.vtm_go_auth.has_credentials_changed(): self.kodi.log('Clearing auth tokens due to changed credentials', LOG_INFO) self.vtm_go_auth.clear_token() # Refresh container self.kodi.container_refresh()
class Search: """ Menu code related to search """ def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._vtm_go = VtmGo(self._auth) self._menu = Menu() def show_search(self, query=None): """ Shows the search dialog :type query: str """ if not query: # Ask for query query = kodiutils.get_search_string( heading=kodiutils.localize(30009)) # Search VTM GO if not query: kodiutils.end_of_directory() return # Do search try: items = self._vtm_go.do_search(query) except Exception as ex: # pylint: disable=broad-except kodiutils.notification(message=str(ex)) kodiutils.end_of_directory() return # Display results listing = [] for item in items: listing.append(self._menu.generate_titleitem(item)) # Sort like we get our results back. kodiutils.show_listing(listing, 30009, content='tvshows')
class Search: """ Menu code related to search """ def __init__(self, kodi): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._menu = Menu(self._kodi) def show_search(self, query=None): """ Shows the search dialog :type query: str """ if not query: # Ask for query query = self._kodi.get_search_string(heading=self._kodi.localize(30009)) # Search VTM GO if not query: self._kodi.end_of_directory() return # Do search try: items = self._vtm_go.do_search(query) except Exception as ex: # pylint: disable=broad-except self._kodi.show_notification(message=str(ex)) self._kodi.end_of_directory() return # Display results listing = [] for item in items: listing.append(self._menu.generate_titleitem(item)) # Sort like we get our results back. self._kodi.show_listing(listing, 30009, content='tvshows')
class Player: """ Code responsible for playing media """ def __init__(self): """ Initialise object """ try: self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) except NoLoginException: self._auth = None self._vtm_go = VtmGo(self._auth) self._vtm_go_stream = VtmGoStream(self._auth) def play_or_live(self, category, item, channel): """ Ask to play the requested item or switch to the live channel :type category: str :type item: str :type channel: str """ res = kodiutils.show_context_menu([kodiutils.localize(30103), kodiutils.localize(30105)]) # Watch Live | Play from Catalog if res == -1: # user has cancelled return if res == 0: # user selected "Watch Live" # Play live self.play('channels', channel) return # Play this program self.play(category, item) def play(self, category, item): """ Play the requested item. :type category: string :type item: string """ if not self._check_credentials(): kodiutils.end_of_directory() return # Check if inputstreamhelper is correctly installed if not self._check_inputstream(): kodiutils.end_of_directory() return try: # Get stream information resolved_stream = self._vtm_go_stream.get_stream(category, item) except StreamGeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked... kodiutils.end_of_directory() return except StreamUnavailableException: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize(30712)) # The video is unavailable... kodiutils.end_of_directory() return info_dict = { 'tvshowtitle': resolved_stream.program, 'title': resolved_stream.title, 'duration': resolved_stream.duration, } prop_dict = {} stream_dict = { 'duration': resolved_stream.duration, } upnext_data = None # Lookup metadata try: if category in ['movies', 'oneoffs']: info_dict.update({'mediatype': 'movie'}) # Get details movie_details = self._vtm_go.get_movie(item) if movie_details: info_dict.update({ 'plot': movie_details.description, 'year': movie_details.year, 'aired': movie_details.aired, }) elif category == 'episodes': info_dict.update({'mediatype': 'episode'}) # There is no direct API to get episode details, so we go trough the cached program details program = self._vtm_go.get_program(resolved_stream.program_id) if program: episode_details = self._vtm_go.get_episode_from_program(program, item) if episode_details: info_dict.update({ 'plot': episode_details.description, 'season': episode_details.season, 'episode': episode_details.number, }) # Lookup the next episode next_episode_details = self._vtm_go.get_next_episode_from_program(program, episode_details.season, episode_details.number) # Create the data for Up Next if next_episode_details: upnext_data = self.generate_upnext(episode_details, next_episode_details) elif category == 'channels': info_dict.update({'mediatype': 'episode'}) # For live channels, we need to keep on updating the manifest # This might not be needed, and could be done with the Location-tag updates if inputstream.adaptive supports it # See https://github.com/peak3d/inputstream.adaptive/pull/298#issuecomment-524206935 prop_dict.update({ 'inputstream.adaptive.manifest_update_parameter': 'full', }) else: _LOGGER.warning('Unknown category %s', category) except UnavailableException: # We continue without details. # This allows to play some programs that don't have metadata (yet). pass # If we have enabled the Manifest proxy, route the call trough that. if category in ['movies', 'oneoffs', 'episodes'] and kodiutils.get_setting_bool('manifest_proxy'): try: # Python 3 from urllib.parse import urlencode except ImportError: # Python 2 from urllib import urlencode port = kodiutils.get_setting_int('manifest_proxy_port') if not port: kodiutils.notification(message=kodiutils.localize(30718), icon='error') kodiutils.end_of_directory() return url = 'http://127.0.0.1:{port}/manifest?{path}'.format(port=port, path=urlencode({'path': resolved_stream.url})) else: url = resolved_stream.url license_key = self._vtm_go_stream.create_license_key(resolved_stream.license_url) # Play this item kodiutils.play(url, license_key, resolved_stream.title, {}, info_dict, prop_dict, stream_dict) # Wait for playback to start kodi_player = KodiPlayer() if not kodi_player.waitForPlayBack(url=url): # Playback didn't start kodiutils.end_of_directory() return # Send Up Next data if upnext_data and kodiutils.get_setting_bool('useupnext'): _LOGGER.debug("Sending Up Next data: %s", upnext_data) self.send_upnext(upnext_data) @staticmethod def _check_credentials(): """ Check if the user has credentials """ if kodiutils.has_credentials(): return True # You need to configure your credentials before you can access the content of VTM GO. confirm = kodiutils.yesno_dialog(message=kodiutils.localize(30701)) if confirm: kodiutils.open_settings() if kodiutils.has_credentials(): return True return False @staticmethod def _check_inputstream(): """ Check if inputstreamhelper and inputstream.adaptive are fine. :rtype boolean """ try: from inputstreamhelper import Helper is_helper = Helper('mpd', drm='com.widevine.alpha') if not is_helper.check_inputstream(): # inputstreamhelper has already shown an error return False except ImportError: kodiutils.ok_dialog(message=kodiutils.localize(30708)) # Please reboot Kodi return False return True @staticmethod def generate_upnext(current_episode, next_episode): """ Construct the data for Up Next. :type current_episode: resources.lib.vtmgo.vtmgo.Episode :type next_episode: resources.lib.vtmgo.vtmgo.Episode """ upnext_info = dict( current_episode=dict( episodeid=current_episode.episode_id, tvshowid=current_episode.program_id, title=current_episode.name, art={ 'poster': current_episode.poster, 'landscape': current_episode.thumb, 'fanart': current_episode.fanart, }, season=current_episode.season, episode=current_episode.number, showtitle=current_episode.program_name, plot=current_episode.description, playcount=None, rating=None, firstaired=current_episode.aired[:10] if current_episode.aired else '', runtime=current_episode.duration, ), next_episode=dict( episodeid=next_episode.episode_id, tvshowid=next_episode.program_id, title=next_episode.name, art={ 'poster': next_episode.poster, 'landscape': next_episode.thumb, 'fanart': next_episode.fanart, }, season=next_episode.season, episode=next_episode.number, showtitle=next_episode.program_name, plot=next_episode.description, playcount=None, rating=None, firstaired=next_episode.aired[:10] if next_episode.aired else '', runtime=next_episode.duration, ), play_url='plugin://plugin.video.vtm.go/play/catalog/episodes/%s' % next_episode.episode_id, ) return upnext_info @staticmethod def send_upnext(upnext_info): """ Send a message to Up Next with information about the next Episode. :type upnext_info: object """ from base64 import b64encode from json import dumps data = [kodiutils.to_unicode(b64encode(dumps(upnext_info).encode()))] sender = '{addon_id}.SIGNAL'.format(addon_id='plugin.video.vtm.go') kodiutils.notify(sender=sender, message='upnext_data', data=data)
class Channels: """ Menu code related to channels """ def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._vtm_go = VtmGo(self._auth) def show_channels(self): """ Shows TV channels """ # Fetch EPG from API channels = self._vtm_go.get_live_channels() listing = [] for channel in channels: channel_data = CHANNELS.get(channel.key) icon = channel.logo fanart = channel.background title = channel.name if channel_data: icon = '{path}/resources/logos/{logo}-white.png'.format(path=kodiutils.addon_path(), logo=channel.key) title = channel_data.get('label') context_menu = [( kodiutils.localize(30052, channel=title), # Watch live {channel} 'PlayMedia(%s)' % kodiutils.url_for('play', category='channels', item=channel.channel_id), )] if channel_data and channel_data.get('epg'): context_menu.append(( kodiutils.localize(30053, channel=title), # TV Guide for {channel} 'Container.Update(%s)' % kodiutils.url_for('show_tvguide_channel', channel=channel_data.get('epg')) )) context_menu.append(( kodiutils.localize(30055, channel=title), # Catalog for {channel} 'Container.Update(%s)' % kodiutils.url_for('show_catalog_channel', channel=channel.key) )) if channel.epg: label = title + '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format( title=channel.epg[0].title, start=channel.epg[0].start.strftime('%H:%M'), end=channel.epg[0].end.strftime('%H:%M')) else: label = title listing.append(kodiutils.TitleItem( title=label, path=kodiutils.url_for('show_channel_menu', channel=channel.key), art_dict=dict( icon=icon, thumb=icon, fanart=fanart, ), info_dict=dict( plot=Menu.format_plot(channel), playcount=0, mediatype='video', studio=channel_data.get('studio_icon') if channel_data else None, ), stream_dict=dict( codec='h264', height=1080, width=1920, ), context_menu=context_menu, )) kodiutils.show_listing(listing, 30007) def show_channel_menu(self, key): """ Shows a TV channel :type key: str """ # Fetch EPG from API channel = self._vtm_go.get_live_channel(key) channel_data = CHANNELS.get(channel.key) icon = channel.logo fanart = channel.background title = channel.name if channel_data: icon = '{path}/resources/logos/{logo}-white.png'.format(path=kodiutils.addon_path(), logo=channel.key) title = channel_data.get('label') title = kodiutils.localize(30052, channel=title) # Watch live {channel} if channel.epg: label = title + '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format( title=channel.epg[0].title, start=channel.epg[0].start.strftime('%H:%M'), end=channel.epg[0].end.strftime('%H:%M')) else: label = title # The .pvr suffix triggers some code paths in Kodi to mark this as a live channel listing = [kodiutils.TitleItem( title=label, path=kodiutils.url_for('play', category='channels', item=channel.channel_id) + '?.pvr', art_dict=dict( icon=icon, thumb=icon, fanart=fanart, ), info_dict=dict( plot=Menu.format_plot(channel), playcount=0, mediatype='video', ), stream_dict=dict( codec='h264', height=1080, width=1920, ), is_playable=True, )] if channel_data and channel_data.get('epg'): listing.append( kodiutils.TitleItem( title=kodiutils.localize(30053, channel=channel_data.get('label')), # TV Guide for {channel} path=kodiutils.url_for('show_tvguide_channel', channel=channel_data.get('epg')), art_dict=dict( icon='DefaultAddonTvInfo.png', ), info_dict=dict( plot=kodiutils.localize(30054, channel=channel_data.get('label')), # Browse the TV Guide for {channel} ), ) ) listing.append(kodiutils.TitleItem( title=kodiutils.localize(30055, channel=channel_data.get('label')), # Catalog for {channel} path=kodiutils.url_for('show_catalog_channel', channel=key), art_dict=dict( icon='DefaultMovieTitle.png' ), info_dict=dict( plot=kodiutils.localize(30056, channel=channel_data.get('label')), # Browse the Catalog for {channel} ), )) # Add YouTube channels if channel_data and kodiutils.get_cond_visibility('System.HasAddon(plugin.video.youtube)') != 0: for youtube in channel_data.get('youtube', []): listing.append(kodiutils.TitleItem( title=kodiutils.localize(30206, label=youtube.get('label')), # Watch {label} on YouTube path=youtube.get('path'), info_dict=dict( plot=kodiutils.localize(30206, label=youtube.get('label')), # Watch {label} on YouTube ) )) kodiutils.show_listing(listing, 30007, sort=['unsorted'])
class Catalog: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._vtm_go = VtmGo(self._auth) self._menu = Menu() def show_catalog(self): """ Show the catalog """ try: categories = self._vtm_go.get_categories() except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for cat in categories: listing.append( kodiutils.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 VTM GO. kodiutils.show_listing(listing, 30003, content='files') def show_catalog_category(self, category=None): """ Show a category in the catalog :type category: str """ try: items = self._vtm_go.get_items(category) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return 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='movies' if category == 'films' else 'tvshows', sort=['label', 'year', 'duration']) def show_catalog_channel(self, channel): """ Show a category in the catalog :type channel: str """ try: items = self._vtm_go.get_items() except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in items: if item.channel == channel: 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, program): """ Show a program from the catalog :type program: str """ try: program_obj = self._vtm_go.get_program( program, 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 VTM GO catalogue. 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( kodiutils.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( kodiutils.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._vtm_go.get_program( program ) # Use CACHE_AUTO since the data is just refreshed in show_program except UnavailableException: kodiutils.ok_dialog( message=kodiutils.localize(30717) ) # This program is not available in the VTM GO catalogue. 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 = [ self._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 """ try: recommendations = self._vtm_go.get_recommendations(storefront) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for cat in recommendations: listing.append( kodiutils.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 VTM GO. 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 """ try: recommendations = self._vtm_go.get_recommendations(storefront) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for cat in recommendations: # Only show the requested category if cat.category_id != category: continue for item in cat.content: listing.append(self._menu.generate_titleitem(item)) # Sort categories by default like in VTM GO. kodiutils.show_listing(listing, 30015, content='tvshows') def show_mylist(self): """ Show the items in "My List" """ try: mylist = self._vtm_go.get_swimlane('my-list') except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in mylist: item.my_list = True listing.append(self._menu.generate_titleitem(item)) # Sort categories by default like in VTM GO. kodiutils.show_listing(listing, 30017, content='tvshows') def mylist_add(self, video_type, content_id): """ Add an item to "My List" :type video_type: str :type content_id: str """ self._vtm_go.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._vtm_go.del_mylist(video_type, content_id) kodiutils.end_of_directory() kodiutils.container_refresh() def show_continuewatching(self): """ Show the items in "Continue Watching" """ try: mylist = self._vtm_go.get_swimlane('continue-watching') except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize( 30705)) # The VTM GO Service has been updated... return except Exception as ex: # pylint: disable=broad-except _LOGGER.error("%s", ex) kodiutils.ok_dialog(message="%s" % ex) return listing = [] for item in mylist: titleitem = self._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 VTM GO. kodiutils.show_listing(listing, 30019, content='episodes', sort='label')
""" Addon code """ from __future__ import absolute_import, division, unicode_literals import routing from resources.lib import GeoblockedException, UnavailableException from resources.lib.kodiwrapper import KodiWrapper, TitleItem from resources.lib.vtmgo.vtmgo import Content, VtmGo from resources.lib.vtmgo.vtmgoauth import VtmGoAuth, InvalidLoginException from resources.lib.vtmgo.vtmgoepg import VtmGoEpg from resources.lib.vtmgo.vtmgostream import VtmGoStream routing = routing.Plugin() kodi = KodiWrapper(routing=routing) vtm_go = VtmGo(kodi) @routing.route('/kids') def show_kids_index(): """ Show the main menu (kids) """ show_index() @routing.route('/') def show_index(): """ Show the main menu """ kids = kodi.kids_mode() listing = [] listing.extend([
class Authentication: """ Code responsible for the Authentication """ def __init__(self, kodi): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) def select_profile(self, key=None): """ Show your profiles :type key: str """ try: profiles = self._vtm_go.get_profiles() except InvalidLoginException: self._kodi.show_ok_dialog(message=self._kodi.localize( 30203)) # Your credentials are not valid! self._kodi.open_settings() return except LoginErrorException as exc: self._kodi.show_ok_dialog(message=self._kodi.localize( 30702, code=exc.code)) # Unknown error while logging in: {code} self._kodi.open_settings() return except ApiUpdateRequired: self._kodi.show_ok_dialog(message=self._kodi.localize( 30705)) # The VTM GO Service has been updated... return except Exception as exc: # pylint: disable=broad-except self._kodi.show_ok_dialog(message="%s" % exc) return # Show warning when you have no profiles if not profiles: # Your account has no profiles defined. Please login on vtm.be/vtmgo and create a Profile. self._kodi.show_ok_dialog(message=self._kodi.localize(30703)) self._kodi.end_of_directory() return # Select the first profile when you only have one if len(profiles) == 1: key = profiles[0].key # Save the selected profile if key: profile = [x for x in profiles if x.key == key][0] _LOGGER.debug('Setting profile to %s', profile) self._kodi.set_setting('profile', '%s:%s' % (profile.key, profile.product)) self._kodi.set_setting('profile_name', profile.name) self._kodi.redirect(self._kodi.url_for('show_main_menu')) return # Show profile selection when you have multiple profiles listing = [ TitleItem( title=self._get_profile_name(p), path=self._kodi.url_for('select_profile', key=p.key), art_dict=dict(icon='DefaultUser.png'), info_dict=dict(plot=p.name, ), ) for p in profiles ] self._kodi.show_listing(listing, sort=['unsorted'], category=30057) # Select Profile @staticmethod def _get_profile_name(profile): """ Get a descriptive string of the profile :type profile: resources.lib.vtmgo.vtmgo.Profile """ title = profile.name # Convert the VTM GO Profile color to a matching Kodi color color_map = { '#64D8E3': 'skyblue', '#4DFF76': 'mediumspringgreen', '#0243FF': 'blue', '#831CFA': 'blueviolet', '#FFB24D': 'khaki', '#FF4DD5': 'violet', '#FFB002': 'gold', '#FF0257': 'crimson', } if color_map.get(profile.color.upper()): title = '[COLOR %s]%s[/COLOR]' % (color_map.get( profile.color), to_unicode(title)) # Append (Kids) if profile.product == 'VTM_GO_KIDS': title = "%s (Kids)" % title return title
class Channels: """ Menu code related to channels """ def __init__(self, kodi): """ Initialise object :type kodi: resources.lib.kodiwrapper.KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._menu = Menu(self._kodi) def show_channels(self): """ Shows TV channels """ product = self._vtm_go.get_product() kids = (product == 'VTM_GO_KIDS') # Fetch EPG from API channel_infos = self._vtm_go.get_live_channels() listing = [] for i, key in enumerate(CHANNELS): # pylint: disable=unused-variable channel = CHANNELS[key] if kids and channel.get('kids') is False: continue # Find this channel in the list channel_info = next(c for c in channel_infos if c.key == key) # Lookup the high resolution logo based on the channel name icon = '{path}/resources/logos/{logo}-white.png'.format( path=self._kodi.get_addon_path(), logo=channel.get('logo')) fanart = '{path}/resources/logos/{logo}.png'.format( path=self._kodi.get_addon_path(), logo=channel.get('logo')) context_menu = [ ( self._kodi.localize( 30052, channel=channel.get('label')), # Watch live {channel} 'XBMC.PlayMedia(%s)' % self._kodi.url_for('play', category='channels', item=channel_info.channel_id)), ( self._kodi.localize(30053, channel=channel.get( 'label')), # TV Guide for {channel} 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'show_tvguide_channel', channel=channel.get('epg'))) ] if self._kodi.get_setting_as_bool('metadata_update'): context_menu.append(( self._kodi.localize( 30055, channel=channel.get('label')), # Catalog for {channel} 'XBMC.Container.Update(%s)' % self._kodi.url_for('show_catalog_channel', channel=key))) title = channel.get('label') if channel_info and channel_info.epg: title += '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format( title=channel_info.epg[0].title, start=channel_info.epg[0].start.strftime('%H:%M'), end=channel_info.epg[0].end.strftime('%H:%M')) listing.append( TitleItem(title=title, path=self._kodi.url_for('show_channel_menu', channel=key), art_dict={ 'icon': icon, 'thumb': icon, 'fanart': fanart, }, info_dict={ 'plot': self._menu.format_plot(channel), 'playcount': 0, 'mediatype': 'video', 'studio': channel.get('studio_icon'), }, stream_dict={ 'codec': 'h264', 'height': 1080, 'width': 1920, }, context_menu=context_menu), ) self._kodi.show_listing(listing, 30007) def show_channel_menu(self, key): """ Shows a TV channel :type key: str """ channel = CHANNELS[key] # Fetch EPG from API channel_info = self._vtm_go.get_live_channel(key) title = self._kodi.localize( 30052, channel=channel.get('label')) # Watch live {channel} if channel_info.epg: title += '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format( title=channel_info.epg[0].title, start=channel_info.epg[0].start.strftime('%H:%M'), end=channel_info.epg[0].end.strftime('%H:%M')) # Lookup the high resolution logo based on the channel name icon = '{path}/resources/logos/{logo}-white.png'.format( path=self._kodi.get_addon_path(), logo=channel.get('logo')) fanart = '{path}/resources/logos/{logo}.png'.format( path=self._kodi.get_addon_path(), logo=channel.get('logo')) listing = [ TitleItem(title=title, path=self._kodi.url_for('play', category='channels', item=channel_info.channel_id) + '?.pvr', art_dict={ 'icon': icon, 'thumb': icon, 'fanart': fanart, }, info_dict={ 'plot': self._menu.format_plot(channel_info), 'playcount': 0, 'mediatype': 'video', }, stream_dict={ 'codec': 'h264', 'height': 1080, 'width': 1920, }, is_playable=True), TitleItem( title=self._kodi.localize( 30053, channel=channel.get('label')), # TV Guide for {channel} path=self._kodi.url_for('show_tvguide_channel', channel=channel.get('epg')), art_dict={'icon': 'DefaultAddonTvInfo.png'}, info_dict={ 'plot': self._kodi.localize(30054, channel=channel.get( 'label')), # Browse the TV Guide for {channel} }), ] if self._kodi.get_setting_as_bool('metadata_update'): listing.append( TitleItem( title=self._kodi.localize( 30055, channel=channel.get('label')), # Catalog for {channel} path=self._kodi.url_for('show_catalog_channel', channel=key), art_dict={'icon': 'DefaultMovieTitle.png'}, info_dict={ 'plot': self._kodi.localize(30056, channel=channel.get('label')), })) # Add YouTube channels if self._kodi.get_cond_visibility( 'System.HasAddon(plugin.video.youtube)') != 0: for youtube in channel.get('youtube', []): listing.append( TitleItem( title=self._kodi.localize( 30206, label=youtube.get( 'label')), # Watch {label} on YouTube path=youtube.get('path'), info_dict={ 'plot': self._kodi.localize( 30206, label=youtube.get( 'label')), # Watch {label} on YouTube })) self._kodi.show_listing(listing, 30007, sort=['unsorted'])
class Menu: """ Menu code """ def __init__(self, kodi): """ Initialise object :type kodi: KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) def show_mainmenu(self): """ Show the main menu """ kids = self._kodi.kids_mode() listing = [] listing.append( TitleItem( title=self._kodi.localize(30001), # A-Z path=self._kodi.url_for('show_catalog_all', kids=kids), art_dict=dict(icon='DefaultMovieTitle.png'), info_dict=dict(plot=self._kodi.localize(30002), ))) listing.append( TitleItem( title=self._kodi.localize(30003), # Catalogue path=self._kodi.url_for('show_catalog', kids=kids), art_dict=dict(icon='DefaultGenre.png'), info_dict=dict(plot=self._kodi.localize(30004), ))) listing.append( TitleItem( title=self._kodi.localize(30007), # TV Channels path=self._kodi.url_for('show_channels', kids=kids), art_dict=dict(icon='DefaultAddonPVRClient.png'), info_dict=dict(plot=self._kodi.localize(30008), ))) if self._kodi.get_setting_as_bool('interface_show_recommendations'): listing.append( TitleItem( title=self._kodi.localize(30015), # Recommendations path=self._kodi.url_for('show_recommendations', kids=kids), art_dict={'icon': 'DefaultFavourites.png'}, info_dict={ 'plot': self._kodi.localize(30016), })) if self._kodi.get_setting_as_bool('interface_show_mylist'): listing.append( TitleItem( title=self._kodi.localize(30017), # My List path=self._kodi.url_for('show_mylist', kids=kids), art_dict={'icon': 'DefaultPlaylist.png'}, info_dict={ 'plot': self._kodi.localize(30018), })) if self._kodi.get_setting_as_bool('interface_show_continuewatching'): listing.append( TitleItem( title=self._kodi.localize(30019), # Continue watching path=self._kodi.url_for('show_continuewatching', kids=kids), art_dict={'icon': 'DefaultInProgressShows.png'}, info_dict={ 'plot': self._kodi.localize(30020), })) listing.append( TitleItem( title=self._kodi.localize(30009), # Search path=self._kodi.url_for('show_search', kids=kids), art_dict=dict(icon='DefaultAddonsSearch.png'), info_dict=dict(plot=self._kodi.localize(30010), ))) if self._kodi.get_setting_as_bool( 'interface_show_kids_zone') and not kids: listing.append( TitleItem( title=self._kodi.localize(30011), # Kids Zone path=self._kodi.url_for('show_main_menu', kids=True), art_dict=dict(icon='DefaultUser.png'), info_dict=dict(plot=self._kodi.localize(30012), ))) self._kodi.show_listing(listing) def check_credentials(self): """ Check credentials (called from settings) """ try: from resources.lib.vtmgo.vtmgoauth import VtmGoAuth auth = VtmGoAuth(self._kodi) auth.clear_token() auth.get_token() self._kodi.show_ok_dialog( message=self._kodi.localize(30202)) # Credentials are correct! except InvalidLoginException: self._kodi.show_ok_dialog(message=self._kodi.localize( 30203)) # Your credentials are not valid! except LoginErrorException as e: self._kodi.show_ok_dialog(message=self._kodi.localize( 30702, code=e.code)) # Unknown error while logging in: {code} self._kodi.open_settings() def format_plot(self, obj): """ Format the plot for a item :type obj: object :rtype str """ plot = '' if hasattr(obj, 'description'): plot += obj.description plot += '\n\n' if hasattr(obj, 'epg'): if obj.epg: plot += self._kodi.localize( 30213, # Now start=obj.epg[0].start.strftime('%H:%M'), end=obj.epg[0].end.strftime('%H:%M'), title=obj.epg[0].title) + "\n" if len(obj.epg) > 1: plot += self._kodi.localize( 30214, # Next start=obj.epg[1].start.strftime('%H:%M'), end=obj.epg[1].end.strftime('%H:%M'), title=obj.epg[1].title) + "\n" # Add remaining if hasattr(obj, 'remaining') and obj.remaining is not None: if obj.remaining == 0: plot += '» ' + self._kodi.localize( 30208) + "\n" # Available until midnight elif obj.remaining == 1: plot += '» ' + self._kodi.localize( 30209) + "\n" # One more day remaining elif obj.remaining / 365 > 5: pass # If it is available for more than 5 years, do not show elif obj.remaining / 365 > 2: plot += '» ' + self._kodi.localize( 30210, years=int( obj.remaining / 365)) + "\n" # X years remaining elif obj.remaining / 30.5 > 3: plot += '» ' + self._kodi.localize( 30211, months=int( obj.remaining / 30.5)) + "\n" # X months remaining else: plot += '» ' + self._kodi.localize( 30212, days=obj.remaining) + "\n" # X days remaining # Add geo-blocked message if hasattr(obj, 'geoblocked') and obj.geoblocked: plot += self._kodi.localize(30207) # Geo-blocked plot += '\n' return plot.rstrip() def generate_titleitem(self, item, progress=False): """ Generate a TitleItem based on a Movie, Program or Episode. :type item: Union[Movie, Program, Episode] :type progress: bool :rtype TitleItem """ art_dict = { 'thumb': item.cover, } info_dict = { 'title': item.name, 'plot': item.description, } prop_dict = {} # # Movie # if isinstance(item, Movie): if item.my_list: context_menu = [( self._kodi.localize(30101), # Remove from My List 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'mylist_del', kids=self._kodi.kids_mode(), video_type=self._vtm_go.CONTENT_TYPE_MOVIE, content_id=item.movie_id))] else: context_menu = [( self._kodi.localize(30100), # Add to My List 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'mylist_add', kids=self._kodi.kids_mode(), video_type=self._vtm_go.CONTENT_TYPE_MOVIE, content_id=item.movie_id))] art_dict.update({ 'fanart': item.cover, }) info_dict.update({ 'mediatype': 'movie', 'plot': self.format_plot(item), 'duration': item.duration, 'year': item.year, 'aired': item.aired, 'studio': CHANNELS.get(item.channel, {}).get('studio_icon'), 'mpaa': ', '.join(item.legal) if hasattr(item, 'legal') and item.legal else self._kodi.localize(30216), }) return TitleItem(title=item.name, path=self._kodi.url_for('play', category='movies', item=item.movie_id), art_dict=art_dict, info_dict=info_dict, stream_dict={ 'codec': 'h264', 'height': 1080, 'width': 1920, }, context_menu=context_menu, is_playable=True) # # Program # if isinstance(item, Program): if item.my_list: context_menu = [( self._kodi.localize(30101), # Remove from My List 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'mylist_del', kids=self._kodi.kids_mode(), video_type=self._vtm_go.CONTENT_TYPE_PROGRAM, content_id=item.program_id))] else: context_menu = [( self._kodi.localize(30100), # Add to My List 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'mylist_add', kids=self._kodi.kids_mode(), video_type=self._vtm_go.CONTENT_TYPE_PROGRAM, content_id=item.program_id))] art_dict.update({ 'fanart': item.cover, 'banner': item.cover, }) info_dict.update({ 'mediatype': None, 'title': item.name, 'plot': self.format_plot(item), 'studio': CHANNELS.get(item.channel, {}).get('studio_icon'), 'mpaa': ', '.join(item.legal) if hasattr(item, 'legal') and item.legal else self._kodi.localize(30216), 'season': len(item.seasons), }) return TitleItem(title=item.name, path=self._kodi.url_for('show_catalog_program', program=item.program_id), art_dict=art_dict, info_dict=info_dict, context_menu=context_menu) # # Episode # if isinstance(item, Episode): context_menu = [( self._kodi.localize(30102), # Go to Program 'XBMC.Container.Update(%s)' % self._kodi.url_for( 'show_catalog_program', program=item.program_id))] info_dict.update({ 'tvshowtitle': item.program_name, 'title': item.name, 'plot': self.format_plot(item), 'duration': item.duration, 'season': item.season, 'episode': item.number, 'mediatype': 'episode', 'set': item.program_name, 'studio': item.channel, 'aired': item.aired, 'mpaa': ', '.join(item.legal) if hasattr(item, 'legal') and item.legal else self._kodi.localize(30216), }) if progress and item.watched: info_dict.update({ 'playcount': 1, }) stream_dict = { 'codec': 'h264', 'duration': item.duration, 'height': 1080, 'width': 1920, } # Get program and episode details from cache program = self._vtm_go.get_program(item.program_id, cache=True) if program: episode = self._vtm_go.get_episode_from_program( program, item.episode_id) if episode: art_dict.update({ 'fanart': episode.cover, 'banner': episode.cover, }) info_dict.update({ 'tvshowtitle': program.name, 'title': episode.name, 'plot': self.format_plot(episode), 'duration': episode.duration, 'season': episode.season, 'episode': episode.number, 'set': program.name, 'studio': episode.channel, 'aired': episode.aired, 'mpaa': ', '.join(episode.legal) if hasattr(episode, 'legal') and episode.legal else self._kodi.localize(30216), }) if progress and item.watched: info_dict.update({ 'playcount': 1, }) stream_dict.update({ 'duration': episode.duration, }) # Add progress info if progress and not item.watched and item.progress: prop_dict.update({ 'ResumeTime': item.progress, 'TotalTime': item.progress + 1, }) return TitleItem(title=info_dict['title'], path=self._kodi.url_for('play', category='episodes', item=item.episode_id), art_dict=art_dict, info_dict=info_dict, stream_dict=stream_dict, prop_dict=prop_dict, context_menu=context_menu, is_playable=True) raise Exception('Unknown video_type')
class Library: """ Menu code related to the catalog """ def __init__(self): """ Initialise object """ self._auth = VtmGoAuth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), 'VTM', kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = VtmGo(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_mylist(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.vtm.go/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_mylist(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.vtm.go/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.vtm.go/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.vtm.go/library/movies/?movie=%s&kodi_action=refresh_info' % content_id) Library().update('plugin://plugin.video.vtm.go/library/movies/') elif video_type == CONTENT_TYPE_PROGRAM: if kodiutils.get_setting_int( 'library_tvshows') != LIBRARY_ONLY_MYLIST: return Library().update( 'plugin://plugin.video.vtm.go/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.vtm.go/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.vtm.go/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')
def __init__(self): Monitor.__init__(self) self.kodi = KodiWrapper() self.vtm_go = VtmGo(self.kodi) self.update_interval = 24 * 3600 # Every 24 hours
def onSettingsChanged(self): """ Callback when a setting has changed """ # Refresh our VtmGo instance self.vtm_go = VtmGo(self.kodi)
def __init__(self, kodi): """ Initialise object """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi)
def play(self, category, item): """ Play the requested item. :type category: string :type item: string """ # Check if inputstreamhelper is correctly installed try: from inputstreamhelper import Helper is_helper = Helper('mpd', drm='com.widevine.alpha') if not is_helper.check_inputstream(): # inputstreamhelper has already shown an error return except ImportError: self._kodi.show_ok_dialog(message=self._kodi.localize(30708)) # Please reboot Kodi return try: # Get stream information resolved_stream = self._vtm_go_stream.get_stream(category, item) except StreamGeoblockedException: self._kodi.show_ok_dialog(heading=self._kodi.localize(30709), message=self._kodi.localize(30710)) # This video is geo-blocked... self._kodi.end_of_directory() return except StreamUnavailableException: self._kodi.show_ok_dialog(heading=self._kodi.localize(30711), message=self._kodi.localize(30712)) # The video is unavailable... self._kodi.end_of_directory() return info_dict = { 'tvshowtitle': resolved_stream.program, 'title': resolved_stream.title, 'duration': resolved_stream.duration, } prop_dict = {} stream_dict = { 'duration': resolved_stream.duration, } # Lookup metadata try: if category in ['movies', 'oneoffs']: info_dict.update({'mediatype': 'movie'}) # Get details details = VtmGo(self._kodi).get_movie(item) info_dict.update({ 'plot': details.description, 'year': details.year, 'aired': details.aired, }) elif category == 'episodes': info_dict.update({'mediatype': 'episode'}) # Get details details = VtmGo(self._kodi).get_episode(item) info_dict.update({ 'plot': details.description, 'season': details.season, 'episode': details.number, }) elif category == 'channels': info_dict.update({'mediatype': 'episode'}) # For live channels, we need to keep on updating the manifest # This might not be needed, and could be done with the Location-tag updates if inputstream.adaptive supports it # See https://github.com/peak3d/inputstream.adaptive/pull/298#issuecomment-524206935 prop_dict.update({ 'inputstream.adaptive.manifest_update_parameter': 'full', }) else: raise Exception('Unknown category %s' % category) except UnavailableException: # We continue without details. # This seems to make it possible to play some programs what don't have metadata. pass # Play this item self._kodi.play( TitleItem( title=resolved_stream.title, path=resolved_stream.url, subtitles_path=resolved_stream.subtitles, art_dict={}, info_dict=info_dict, prop_dict=prop_dict, stream_dict=stream_dict, is_playable=True, ), license_key=self._vtm_go_stream.create_license_key(resolved_stream.license_url))
class Catalog: """ Menu code related to the catalog """ def __init__(self, kodi): """ Initialise object :type kodi: KodiWrapper """ self._kodi = kodi self._vtm_go = VtmGo(self._kodi) self._menu = Menu(self._kodi) def show_catalog(self): """ Show the catalog """ try: categories = self._vtm_go.get_categories() except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for cat in categories: listing.append( TitleItem(title=cat.title, path=self._kodi.url_for('show_catalog_category', kids=self._kodi.kids_mode(), category=cat.category_id), info_dict={ 'plot': '[B]{category}[/B]'.format(category=cat.title), })) # Sort categories by default like in VTM GO. self._kodi.show_listing(listing, 30003, content='files') def show_catalog_category(self, category=None): """ Show a category in the catalog :type category: str """ try: items = self._vtm_go.get_items(category) except Exception as ex: self._kodi.show_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. self._kodi.show_listing( listing, 30003, content='movies' if category == 'films' else 'tvshows', sort='label') def show_catalog_channel(self, channel): """ Show a category in the catalog :type channel: str """ try: items = self._vtm_go.get_items() except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for item in items: if item.channel == channel: 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. self._kodi.show_listing(listing, 30003, content='tvshows', sort='label') def show_program(self, program): """ Show a program from the catalog :type program: str """ try: program_obj = self._vtm_go.get_program(program) except UnavailableException: self._kodi.show_ok_dialog( message=self._kodi.localize(30717) ) # This program is not available in the VTM GO catalogue. self._kodi.end_of_directory() return studio = CHANNELS.get(program_obj.channel, {}).get('studio_icon') listing = [] # Add an '* All seasons' entry when configured in Kodi if self._kodi.get_global_setting('videolibrary.showallitems') is True: listing.append( TitleItem( title='* %s' % self._kodi.localize(30204), # * All seasons path=self._kodi.url_for('show_catalog_program_season', program=program, season=-1), art_dict={ 'thumb': program_obj.cover, 'fanart': program_obj.cover, }, info_dict={ 'tvshowtitle': program_obj.name, 'title': self._kodi.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 self._kodi.localize(30216), })) # Add the seasons for s in program_obj.seasons.values(): listing.append( TitleItem( title=self._kodi.localize(30205, season=s.number), # Season X path=self._kodi.url_for('show_catalog_program_season', program=program, season=s.number), art_dict={ 'thumb': s.cover, 'fanart': program_obj.cover, }, info_dict={ 'tvshowtitle': program_obj.name, 'title': self._kodi.localize(30205, season=s.number), '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 self._kodi.localize(30216), })) # Sort by label. Some programs return seasons unordered. self._kodi.show_listing(listing, 30003, content='tvshows', sort='label') def show_program_season(self, program, season): """ Show a program from the catalog :type program: str :type season: int """ try: program_obj = self._vtm_go.get_program(program) except UnavailableException: self._kodi.show_ok_dialog( message=self._kodi.localize(30717) ) # This program is not available in the VTM GO catalogue. self._kodi.end_of_directory() return if season == -1: # Show all seasons seasons = program_obj.seasons.values() else: # Show the season that was selected seasons = [program_obj.seasons[season]] listing = [] for s in seasons: for episode in s.episodes.values(): listing.append(self._menu.generate_titleitem(episode)) # Sort by episode number by default. Takes seasons into account. self._kodi.show_listing(listing, 30003, content='episodes', sort='episode') def show_recommendations(self): """ Show the recommendations """ try: recommendations = self._vtm_go.get_recommendations() except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for cat in recommendations: listing.append( TitleItem(title=cat.title, path=self._kodi.url_for( 'show_recommendations_category', kids=self._kodi.kids_mode(), category=cat.category_id), info_dict={ 'plot': '[B]{category}[/B]'.format(category=cat.title), })) # Sort categories by default like in VTM GO. self._kodi.show_listing(listing, 30015, content='files') def show_recommendations_category(self, category): """ Show the items in a recommendations category :type category: str """ try: recommendations = self._vtm_go.get_recommendations() except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for cat in recommendations: # Only show the requested category if cat.category_id != category: continue for item in cat.content: listing.append(self._menu.generate_titleitem(item)) # Sort categories by default like in VTM GO. self._kodi.show_listing(listing, 30015, content='tvshows') def show_mylist(self): """ Show the items in "My List" """ try: mylist = self._vtm_go.get_swimlane('my-list') except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for item in mylist: item.my_list = True listing.append(self._menu.generate_titleitem(item)) # Sort categories by default like in VTM GO. self._kodi.show_listing(listing, 30017, content='tvshows') def mylist_add(self, video_type, content_id): """ Add an item to "My List" :type video_type: str :type content_id: str """ self._vtm_go.add_mylist(video_type, content_id) self._kodi.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._vtm_go.del_mylist(video_type, content_id) self._kodi.end_of_directory() self._kodi.container_refresh() def show_continuewatching(self): """ Show the items in "Continue Watching" """ try: mylist = self._vtm_go.get_swimlane('continue-watching') except Exception as ex: self._kodi.show_notification(message=str(ex)) raise listing = [] for item in mylist: titleitem = self._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.title = title titleitem.info_dict['title'] = title listing.append(titleitem) # Sort categories by default like in VTM GO. self._kodi.show_listing(listing, 30019, content='episodes', sort='label')
def onSettingsChanged(self): """ Callback when a setting has changed """ self.kodi.log('IN VTM GO: Settings changed') # Refresh our VtmGo instance self.vtm_go = VtmGo(self.kodi)
class IPTVManager: """ Code related to the Kodi PVR integration """ def __init__(self, port): """ Initialise object :type port: int """ self._vtm_go = VtmGo() self._vtm_go_epg = VtmGoEpg() self.port = port def via_socket(func): # pylint: disable=no-self-argument """Send the output of the wrapped function to socket""" def send(self): """Decorator to send over a socket""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', self.port)) try: sock.sendall(json.dumps(func(self)).encode()) # pylint: disable=not-callable finally: sock.close() return send @via_socket def send_channels(self): """ Report channel data """ # Fetch EPG from API channels = self._vtm_go.get_live_channels() results = [] for channel in channels: channel_data = CHANNELS.get(channel.key) if not channel_data: _LOGGER.warning( 'Skipping %s since we don\'t know this channel', channel.key) continue results.append( dict( name=channel_data.get('label') if channel_data else channel.name, id=channel_data.get('iptv_id'), preset=channel_data.get('iptv_preset'), logo= 'special://home/addons/{addon}/resources/logos/{logo}.png'. format(addon=kodiutils.addon_id(), logo=channel.key) if channel_data else channel.logo, stream=kodiutils.url_for('play', category='channels', item=channel.channel_id), vod=kodiutils.url_for('play_epg_datetime', channel=channel.key, timestamp='{date}'), )) return dict(version=1, streams=results) @via_socket def send_epg(self): """ Report EPG data """ results = dict() # Fetch EPG data for date in ['yesterday', 'today', 'tomorrow']: channels = self._vtm_go_epg.get_epgs(date) for channel in channels: # Lookup channel data in our own CHANNELS dict channel_data = next((c for c in CHANNELS.values() if c.get('epg') == channel.key), None) if not channel_data: _LOGGER.warning( 'Skipping EPG for %s since we don\'t know this channel', channel.key) continue key = channel_data.get('iptv_id') # Create channel in dict if it doesn't exists if key not in results.keys(): results[key] = [] results[key].extend([ dict( start=broadcast.time.isoformat(), stop=( broadcast.time + timedelta(seconds=broadcast.duration)).isoformat(), title=broadcast.title, description=broadcast.description, # subtitle=None, # Not available in the API # season=None, # Not available in the API # epsiode=None, # Not available in the API genre=broadcast.genre, image=broadcast.thumb, stream=kodiutils.url_for( 'play', category=broadcast.playable_type, item=broadcast.playable_uuid) if broadcast.playable_uuid else None) for broadcast in channel.broadcasts ]) return dict(version=1, epg=results)