Example #1
0
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):
                if not self._vtm_go.get_movie(item.movie_id, cache=True):
                    self._vtm_go.get_movie(item.movie_id)
            elif isinstance(item, Program):
                if not self._vtm_go.get_program(item.program_id, cache=True):
                    self._vtm_go.get_program(item.program_id)

            # Update the progress indicator
            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
Example #2
0
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
Example #3
0
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')
Example #4
0
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)
Example #5
0
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')
Example #6
0
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')
Example #7
0
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')