예제 #1
0
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()
예제 #2
0
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'))
예제 #3
0
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)