class VrtMonitor(Monitor, object): # pylint: disable=useless-object-inheritance """This is the class that monitors Kodi for the VRT NU video plugin""" def __init__(self): """VRT Monitor initialisation""" self._resumepoints = ResumePoints() self._playerinfo = None self._favorites = None self._apihelper = None self.init_watching_activity() super(VrtMonitor, self).__init__() def run(self): """Main loop""" while not self.abortRequested(): if self.waitForAbort(10): break def init_watching_activity(self): """Only load components for watching activity when needed""" if self._resumepoints.is_activated(): if not self._playerinfo: self._playerinfo = PlayerInfo() if not self._favorites: self._favorites = Favorites() if not self._apihelper: self._apihelper = ApiHelper(self._favorites, self._resumepoints) else: self._playerinfo = None def onNotification(self, sender, method, data): # pylint: disable=invalid-name """Handler for notifications""" # log(2, '[Notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) # Handle play_action events from upnextprovider if sender.startswith('upnextprovider') and method.endswith( 'plugin.video.vrt.nu_play_action'): from json import loads hexdata = loads(data) if not hexdata: return # NOTE: With Python 3.5 and older json.loads() does not support bytes or bytearray, so we convert to unicode from base64 import b64decode data = loads(to_unicode(b64decode(hexdata[0]))) log(2, '[Up Next notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) self._playerinfo.add_upnext(data.get('video_id')) def onSettingsChanged(self): # pylint: disable=invalid-name """Handler for changes to settings""" log(1, 'Settings changed') TokenResolver().refresh_login() # Init watching activity again when settings change self.init_watching_activity() # Refresh container when settings change container_refresh()
class VRTPlayer: """An object providing all methods for Kodi menu generation""" def __init__(self): """Initialise object""" self._favorites = Favorites() self._resumepoints = ResumePoints() self._apihelper = ApiHelper(self._favorites, self._resumepoints) wait_for_resumepoints() def show_main_menu(self): """The VRT NU add-on main menu""" # self._favorites.refresh(ttl=ttl('indirect')) main_items = [] # Only add 'My favorites' when it has been activated if self._favorites.is_activated(): main_items.append(TitleItem( label=localize(30010), # My favorites path=url_for('favorites_menu'), art_dict=dict(thumb='DefaultFavourites.png'), info_dict=dict(plot=localize(30011)), )) main_items.extend([ TitleItem(label=localize(30012), # All programs path=url_for('programs'), art_dict=dict(thumb='DefaultMovieTitle.png'), info_dict=dict(plot=localize(30013))), TitleItem(label=localize(30014), # Categories path=url_for('categories'), art_dict=dict(thumb='DefaultGenre.png'), info_dict=dict(plot=localize(30015))), TitleItem(label=localize(30016), # Channels path=url_for('channels'), art_dict=dict(thumb='DefaultTags.png'), info_dict=dict(plot=localize(30017))), TitleItem(label=localize(30018), # Live TV path=url_for('livetv'), art_dict=dict(thumb='DefaultTVShows.png'), info_dict=dict(plot=localize(30019))), TitleItem(label=localize(30020), # Recent items path=url_for('recent'), art_dict=dict(thumb='DefaultRecentlyAddedEpisodes.png'), info_dict=dict(plot=localize(30021))), TitleItem(label=localize(30022), # Soon offline path=url_for('offline'), art_dict=dict(thumb='DefaultYear.png'), info_dict=dict(plot=localize(30023))), TitleItem(label=localize(30024), # Featured content path=url_for('featured'), art_dict=dict(thumb='DefaultCountry.png'), info_dict=dict(plot=localize(30025))), TitleItem(label=localize(30026), # TV guide path=url_for('tvguide'), art_dict=dict(thumb='DefaultAddonTvInfo.png'), info_dict=dict(plot=localize(30027))), TitleItem(label=localize(30028), # Search path=url_for('search'), art_dict=dict(thumb='DefaultAddonsSearch.png'), info_dict=dict(plot=localize(30029))), ]) show_listing(main_items, cache=False) # No category self._version_check() def _version_check(self): first_run, settings_version, addon_version = self._first_run() if first_run: # 2.0.0 version: changed plugin:// url interface: show warning that Kodi favourites and what-was-watched will break if settings_version == '' and has_credentials(): ok_dialog(localize(30978), localize(30979)) if addon_version == '2.2.1': # 2.2.1 version: changed artwork: delete old cached artwork delete_cached_thumbnail(get_addon_info('fanart').replace('.png', '.jpg')) delete_cached_thumbnail(get_addon_info('icon')) # 2.2.1 version: moved tokens: delete old tokens from tokenresolver import TokenResolver TokenResolver().delete_tokens() # Make user aware that timeshift functionality will not work without ISA when user starts up the first time if settings_version == '' and kodi_version_major() > 17 and not has_inputstream_adaptive(): ok_dialog(message=localize(30988)) @staticmethod def _first_run(): '''Check if this add-on version is run for the first time''' # Get version from settings.xml settings_version = get_setting('version', default='') # Get version from addon.xml addon_version = get_addon_info('version') # Compare versions (settings_version was not present in version 1.10.0 and older) settings_comp = tuple(map(int, settings_version.split('+')[0].split('.'))) if settings_version != '' else (1, 10, 0) addon_comp = tuple(map(int, addon_version.split('+')[0].split('.'))) if addon_comp > settings_comp: # New version found, save addon version to settings set_setting('version', addon_version) return True, settings_version, addon_version return False, settings_version, addon_version def show_favorites_menu(self): """The VRT NU addon 'My programs' menu""" self._favorites.refresh(ttl=ttl('indirect')) favorites_items = [ TitleItem(label=localize(30040), # My programs path=url_for('favorites_programs'), art_dict=dict(thumb='DefaultMovieTitle.png'), info_dict=dict(plot=localize(30041))), TitleItem(label=localize(30048), # My recent items path=url_for('favorites_recent'), art_dict=dict(thumb='DefaultRecentlyAddedEpisodes.png'), info_dict=dict(plot=localize(30049))), TitleItem(label=localize(30050), # My soon offline path=url_for('favorites_offline'), art_dict=dict(thumb='DefaultYear.png'), info_dict=dict(plot=localize(30051))), ] # Only add 'My watch later' and 'Continue watching' when it has been activated if self._resumepoints.is_activated(): favorites_items.append(TitleItem( label=localize(30052), # My watch later path=url_for('resumepoints_watchlater'), art_dict=dict(thumb='DefaultVideoPlaylists.png'), info_dict=dict(plot=localize(30053)), )) favorites_items.append(TitleItem( label=localize(30054), # Continue Watching path=url_for('resumepoints_continue'), art_dict=dict(thumb='DefaultInProgressShows.png'), info_dict=dict(plot=localize(30055)), )) if get_setting_bool('addmymovies', default=True): favorites_items.append( TitleItem(label=localize(30042), # My movies path=url_for('categories', category='films'), art_dict=dict(thumb='DefaultAddonVideo.png'), info_dict=dict(plot=localize(30043))), ) if get_setting_bool('addmydocu', default=True): favorites_items.append( TitleItem(label=localize(30044), # My documentaries path=url_for('favorites_docu'), art_dict=dict(thumb='DefaultMovies.png'), info_dict=dict(plot=localize(30045))), ) if get_setting_bool('addmymusic', default=True): favorites_items.append( TitleItem(label=localize(30046), # My music path=url_for('favorites_music'), art_dict=dict(thumb='DefaultAddonMusic.png'), info_dict=dict(plot=localize(30047))), ) show_listing(favorites_items, category=30010, cache=False) # My favorites # Show dialog when no favorites were found if not self._favorites.titles(): ok_dialog(heading=localize(30415), message=localize(30416)) def show_favorites_docu_menu(self): """The VRT NU add-on 'My documentaries' listing menu""" self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) episode_items, sort, ascending, content = self._apihelper.list_episodes(category='docu', season='allseasons', programtype='oneoff') show_listing(episode_items, category=30044, sort=sort, ascending=ascending, content=content, cache=False) def show_favorites_music_menu(self): """The VRT NU add-on 'My music' listing menu""" self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) episode_items, sort, ascending, content = self._apihelper.list_episodes(category='muziek', season='allseasons', programtype='oneoff') show_listing(episode_items, category=30046, sort=sort, ascending=ascending, content=content, cache=False) def show_tvshow_menu(self, use_favorites=False): """The VRT NU add-on 'All programs' listing menu""" # My favorites menus may need more up-to-date favorites self._favorites.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) self._resumepoints.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) tvshow_items = self._apihelper.list_tvshows(use_favorites=use_favorites) show_listing(tvshow_items, category=30440, sort='label', content='tvshows') # A-Z def show_category_menu(self, category=None): """The VRT NU add-on 'Categories' listing menu""" if category: self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) tvshow_items = self._apihelper.list_tvshows(category=category) from data import CATEGORIES category_msgctxt = find_entry(CATEGORIES, 'id', category).get('msgctxt') show_listing(tvshow_items, category=category_msgctxt, sort='label', content='tvshows') else: category_items = self._apihelper.list_categories() show_listing(category_items, category=30014, sort='unsorted', content='files') # Categories def show_channels_menu(self, channel=None): """The VRT NU add-on 'Channels' listing menu""" if channel: from tvguide import TVGuide self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) channel_items = self._apihelper.list_channels(channels=[channel]) # Live TV channel_items.extend(TVGuide().get_channel_items(channel=channel)) # TV guide channel_items.extend(self._apihelper.list_youtube(channels=[channel])) # YouTube channel_items.extend(self._apihelper.list_tvshows(channel=channel)) # TV shows from data import CHANNELS channel_name = find_entry(CHANNELS, 'name', channel).get('label') show_listing(channel_items, category=channel_name, sort='unsorted', content='tvshows', cache=False) # Channel else: channel_items = self._apihelper.list_channels(live=False) show_listing(channel_items, category=30016, cache=False) def show_featured_menu(self, feature=None): """The VRT NU add-on 'Featured content' listing menu""" if feature: self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) tvshow_items = self._apihelper.list_tvshows(feature=feature) from data import FEATURED feature_msgctxt = find_entry(FEATURED, 'id', feature).get('msgctxt') show_listing(tvshow_items, category=feature_msgctxt, sort='label', content='tvshows', cache=False) else: featured_items = self._apihelper.list_featured() show_listing(featured_items, category=30024, sort='label', content='files') def show_livetv_menu(self): """The VRT NU add-on 'Live TV' listing menu""" channel_items = self._apihelper.list_channels() show_listing(channel_items, category=30018, cache=False) def show_episodes_menu(self, program, season=None): """The VRT NU add-on episodes listing menu""" self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) episode_items, sort, ascending, content = self._apihelper.list_episodes(program=program, season=season) # FIXME: Translate program in Program Title show_listing(episode_items, category=program.title(), sort=sort, ascending=ascending, content=content, cache=False) def show_recent_menu(self, page=0, use_favorites=False): """The VRT NU add-on 'Most recent' and 'My most recent' listing menu""" # My favorites menus may need more up-to-date favorites self._favorites.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) self._resumepoints.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) page = realpage(page) episode_items, sort, ascending, content = self._apihelper.list_episodes(page=page, use_favorites=use_favorites, variety='recent') # Add 'More...' entry at the end if len(episode_items) == get_setting_int('itemsperpage', default=50): recent = 'favorites_recent' if use_favorites else 'recent' episode_items.append(TitleItem( label=colour(localize(30300)), path=url_for(recent, page=page + 1), art_dict=dict(thumb='DefaultRecentlyAddedEpisodes.png'), info_dict=dict(), )) show_listing(episode_items, category=30020, sort=sort, ascending=ascending, content=content, cache=False) def show_offline_menu(self, page=0, use_favorites=False): """The VRT NU add-on 'Soon offline' and 'My soon offline' listing menu""" # My favorites menus may need more up-to-date favorites self._favorites.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) self._resumepoints.refresh(ttl=ttl('direct' if use_favorites else 'indirect')) page = realpage(page) items_per_page = get_setting_int('itemsperpage', default=50) sort_key = 'assetOffTime' episode_items, sort, ascending, content = self._apihelper.list_episodes(page=page, items_per_page=items_per_page, use_favorites=use_favorites, variety='offline', sort_key=sort_key) # Add 'More...' entry at the end if len(episode_items) == items_per_page: offline = 'favorites_offline' if use_favorites else 'offline' episode_items.append(TitleItem( label=localize(30300), path=url_for(offline, page=page + 1), art_dict=dict(thumb='DefaultYear.png'), info_dict=dict(), )) show_listing(episode_items, category=30022, sort=sort, ascending=ascending, content=content, cache=False) def show_watchlater_menu(self, page=0): """The VRT NU add-on 'My watch later' listing menu""" # My watch later menu may need more up-to-date favorites self._favorites.refresh(ttl=ttl('direct')) self._resumepoints.refresh(ttl=ttl('direct')) page = realpage(page) episode_items, sort, ascending, content = self._apihelper.list_episodes(page=page, variety='watchlater') show_listing(episode_items, category=30052, sort=sort, ascending=ascending, content=content, cache=False) def show_continue_menu(self, page=0): """The VRT NU add-on 'Continue waching' listing menu""" # Continue watching menu may need more up-to-date favorites self._favorites.refresh(ttl=ttl('direct')) self._resumepoints.refresh(ttl=ttl('direct')) page = realpage(page) episode_items, sort, ascending, content = self._apihelper.list_episodes(page=page, variety='continue') show_listing(episode_items, category=30054, sort=sort, ascending=ascending, content=content, cache=False) def play_latest_episode(self, program): """A hidden feature in the VRT NU add-on to play the latest episode of a program""" video = self._apihelper.get_latest_episode(program) if not video: log_error('Play latest episode failed, program {program}', program=program) ok_dialog(message=localize(30954)) end_of_directory() return self.play(video) def play_episode_by_air_date(self, channel, start_date, end_date): """Play an episode of a program given the channel and the air date in iso format (2019-07-06T19:35:00)""" video = self._apihelper.get_episode_by_air_date(channel, start_date, end_date) if video and video.get('errorlabel'): ok_dialog(message=localize(30986, title=video.get('errorlabel'))) end_of_directory() return if not video: log_error('Play episode by air date failed, channel {channel}, start_date {start}', channel=channel, start=start_date) ok_dialog(message=localize(30954)) end_of_directory() return self.play(video) def play_episode_by_whatson_id(self, whatson_id): """Play an episode of a program given the whatson_id""" video = self._apihelper.get_single_episode(whatson_id=whatson_id) if not video: log_error('Play episode by whatson_id failed, whatson_id {whatson_id}', whatson_id=whatson_id) ok_dialog(message=localize(30954)) end_of_directory() return self.play(video) def play_upnext(self, video_id): """Play the next episode of a program by video_id""" video = self._apihelper.get_single_episode(video_id=video_id) if not video: log_error('Play Up Next with video_id {video_id} failed', video_id=video_id) ok_dialog(message=localize(30954)) end_of_directory() return self.play(video) @staticmethod def play(video): """A wrapper for playing video items""" from tokenresolver import TokenResolver from streamservice import StreamService _tokenresolver = TokenResolver() _streamservice = StreamService(_tokenresolver) stream = _streamservice.get_stream(video) if stream is None: end_of_directory() return play(stream, video.get('listitem'))
class VrtMonitor(Monitor): ''' This is the class that monitors Kodi for the VRT NU video plugin ''' def __init__(self): ''' VRT Monitor initialisiation ''' self._resumepoints = ResumePoints() self._container = None self._playerinfo = None self._favorites = None self._apihelper = None self.init_watching_activity() Monitor.__init__(self) def run(self): ''' Main loop ''' while not self.abortRequested(): if self.waitForAbort(10): break def init_watching_activity(self): ''' Only load components for watching activity when needed ''' if self._resumepoints.is_activated(): if not self._playerinfo: self._playerinfo = PlayerInfo(info=self.handle_info) if not self._favorites: self._favorites = Favorites() if not self._apihelper: self._apihelper = ApiHelper(self._favorites, self._resumepoints) def onNotification(self, sender, method, data): # pylint: disable=invalid-name ''' Handler for notifications ''' log(2, '[Notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) if method.endswith('source_container'): from json import loads self._container = loads(data).get('container') return if not sender.startswith('upnextprovider'): return if not method.endswith('plugin.video.vrt.nu_play_action'): return from json import loads hexdata = loads(data) if not hexdata: return from binascii import unhexlify data = loads(unhexlify(hexdata[0])) log(2, '[Up Next notification] sender={sender}, method={method}, data={data}', sender=sender, method=method, data=to_unicode(data)) jsonrpc(method='Player.Open', params=dict(item=dict( file='plugin://plugin.video.vrt.nu/play/whatson/%s' % data.get('whatson_id')))) def onSettingsChanged(self): # pylint: disable=invalid-name ''' Handler for changes to settings ''' log(1, 'Settings changed') TokenResolver().refresh_login() invalidate_caches('continue-*.json', 'favorites.json', 'my-offline-*.json', 'my-recent-*.json', 'resume_points.json', 'watchlater-*.json') # Init watching activity again when settings change self.init_watching_activity() # Refresh container when settings change container_refresh() def handle_info(self, info): ''' Handle information from PlayerInfo class ''' log(2, 'Got VRT NU Player info: {info}', info=str(info)) # Push resume position if info.get('position'): self.push_position(info) # Push up next episode info if info.get('program'): self.push_upnext(info) def push_position(self, info): ''' Push player position to VRT NU resumepoints API ''' # Get uuid, title and url from api based on video.get('publication_id') or video.get('video_url') ep_id = play_url_to_id(info.get('path')) if ep_id.get('video_id'): episode = self._apihelper.get_episodes( video_id=ep_id.get('video_id'), variety='single')[0] elif ep_id.get('whatson_id'): episode = self._apihelper.get_episodes( whatson_id=ep_id.get('whatson_id'), variety='single')[0] elif ep_id.get('video_url'): episode = self._apihelper.get_episodes( video_url=ep_id.get('video_url'), variety='single')[0] uuid = self._resumepoints.assetpath_to_uuid(episode.get('assetPath')) title = episode.get('program') url = url_to_episode(episode.get('url', '')) # Push resumepoint to VRT NU self._resumepoints.update(uuid=uuid, title=title, url=url, watch_later=None, position=info.get('position'), total=info.get('total')) # Only update container if the play action was initiated from it current_container = current_container_url() log(2, '[PlayerPosition] resumepoint update {info} {container}', info=episode.get('title'), container=current_container) if current_container is None or self._container == current_container: log(2, '[PlayerPosition] update container {info}', info=self._container) container_update(self._container) def push_upnext(self, info): ''' Push episode info to Up Next service add-on''' if has_addon('service.upnext') and get_setting('useupnext', 'true') == 'true': next_info = self._apihelper.get_upnext(info) if next_info: from binascii import hexlify from json import dumps data = [to_unicode(hexlify(dumps(next_info).encode()))] sender = '%s.SIGNAL' % addon_id() notify(sender=sender, message='upnext_data', data=data)