Example #1
0
 def __init__(self, kodi):
     """ Initialise object
     :type kodi: KodiWrapper
     """
     self._kodi = kodi
     self._vtm_go = VtmGo(self._kodi)
     self._menu = Menu(self._kodi)
Example #2
0
 def __init__(self, kodi):
     """ Initialise object
     :type kodi: resources.lib.kodiwrapper.KodiWrapper
     """
     self._kodi = kodi
     self._vtm_go = VtmGo(self._kodi)
     self._menu = Menu(self._kodi)
Example #3
0
 def __init__(self, port):
     """ Initialise object
     :type port: int
     """
     self._vtm_go = VtmGo()
     self._vtm_go_epg = VtmGoEpg()
     self.port = port
Example #4
0
 def __init__(self):
     """ Initialise object """
     self._auth = VtmGoAuth(kodiutils.get_setting('username'),
                            kodiutils.get_setting('password'), 'VTM',
                            kodiutils.get_setting('profile'),
                            kodiutils.get_tokens_path())
     self._api = VtmGo(self._auth)
 def __init__(self, kodi, port):
     """ Initialise object
     :type kodi: resources.lib.kodiwrapper.KodiWrapper
     """
     self._kodi = kodi
     self._vtm_go = VtmGo(self._kodi)
     self._vtm_go_epg = VtmGoEpg(self._kodi)
     self.port = port
Example #6
0
 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)
Example #7
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 #8
0
 def __init__(self, port):
     """ Initialise object
     :type port: int
     """
     self._auth = VtmGoAuth(kodiutils.get_setting('username'),
                            kodiutils.get_setting('password'),
                            'VTM',
                            kodiutils.get_setting('profile'),
                            kodiutils.get_tokens_path())
     self._vtm_go = VtmGo(self._auth)
     self._vtm_go_epg = VtmGoEpg()
     self.port = port
Example #9
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):
                self._vtm_go.get_movie(item.movie_id)
            elif isinstance(item, Program):
                self._vtm_go.get_program(item.program_id)

            # Run callback after every item
            if callback and callback(index, count):
                # Stop when callback returns False
                return False

        return True

    def clean(self):
        """ Clear metadata (called from settings) """
        self._kodi.invalidate_cache()
        self._kodi.set_setting('metadata_last_updated', '0')
        self._kodi.show_ok_dialog(message=self._kodi.localize(30714))  # Local metadata is cleared
Example #10
0
 def __init__(self, kodi):
     """ Initialise object
     :type kodi: KodiWrapper
     """
     self._kodi = kodi
     self._vtm_go = VtmGo(self._kodi)
     self._vtm_go_stream = VtmGoStream(self._kodi)
Example #11
0
 def __init__(self):
     Monitor.__init__(self)
     self.kodi = KodiWrapper()
     self.vtm_go = VtmGo(self.kodi)
     self.vtm_go_auth = VtmGoAuth(self.kodi)
     self.update_interval = 24 * 3600  # Every 24 hours
     self.cache_expiry = 30 * 24 * 3600  # One month
Example #12
0
    def onSettingsChanged(self):  # pylint: disable=invalid-name
        """ Callback when a setting has changed """
        # Refresh our VtmGo instance
        self.vtm_go = VtmGo(self._kodi)

        if self.vtm_go_auth.has_credentials_changed():
            _LOGGER.debug('Clearing auth tokens due to changed credentials')
            self.vtm_go_auth.clear_token()

            # Refresh container
            self._kodi.container_refresh()
Example #13
0
    def onSettingsChanged(self):
        """ Callback when a setting has changed """
        # Refresh our VtmGo instance
        self.vtm_go = VtmGo(self.kodi)

        if self.vtm_go_auth.has_credentials_changed():
            self.kodi.log('Clearing auth tokens due to changed credentials',
                          LOG_INFO)
            self.vtm_go_auth.clear_token()

            # Refresh container
            self.kodi.container_refresh()
Example #14
0
class Search:
    """ Menu code related to search """
    def __init__(self):
        """ Initialise object """
        self._auth = VtmGoAuth(kodiutils.get_setting('username'),
                               kodiutils.get_setting('password'), 'VTM',
                               kodiutils.get_setting('profile'),
                               kodiutils.get_tokens_path())
        self._vtm_go = VtmGo(self._auth)
        self._menu = Menu()

    def show_search(self, query=None):
        """ Shows the search dialog
        :type query: str
        """
        if not query:
            # Ask for query
            query = kodiutils.get_search_string(
                heading=kodiutils.localize(30009))  # Search VTM GO
            if not query:
                kodiutils.end_of_directory()
                return

        # Do search
        try:
            items = self._vtm_go.do_search(query)
        except Exception as ex:  # pylint: disable=broad-except
            kodiutils.notification(message=str(ex))
            kodiutils.end_of_directory()
            return

        # Display results
        listing = []
        for item in items:
            listing.append(self._menu.generate_titleitem(item))

        # Sort like we get our results back.
        kodiutils.show_listing(listing, 30009, content='tvshows')
Example #15
0
class Search:
    """ Menu code related to search """

    def __init__(self, kodi):
        """ Initialise object
        :type kodi: resources.lib.kodiwrapper.KodiWrapper
        """
        self._kodi = kodi
        self._vtm_go = VtmGo(self._kodi)
        self._menu = Menu(self._kodi)

    def show_search(self, query=None):
        """ Shows the search dialog
        :type query: str
        """
        if not query:
            # Ask for query
            query = self._kodi.get_search_string(heading=self._kodi.localize(30009))  # Search VTM GO
            if not query:
                self._kodi.end_of_directory()
                return

        # Do search
        try:
            items = self._vtm_go.do_search(query)
        except Exception as ex:  # pylint: disable=broad-except
            self._kodi.show_notification(message=str(ex))
            self._kodi.end_of_directory()
            return

        # Display results
        listing = []
        for item in items:
            listing.append(self._menu.generate_titleitem(item))

        # Sort like we get our results back.
        self._kodi.show_listing(listing, 30009, content='tvshows')
Example #16
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 #17
0
class Channels:
    """ Menu code related to channels """

    def __init__(self):
        """ Initialise object """
        self._auth = VtmGoAuth(kodiutils.get_setting('username'),
                               kodiutils.get_setting('password'),
                               'VTM',
                               kodiutils.get_setting('profile'),
                               kodiutils.get_tokens_path())
        self._vtm_go = VtmGo(self._auth)

    def show_channels(self):
        """ Shows TV channels """
        # Fetch EPG from API
        channels = self._vtm_go.get_live_channels()

        listing = []
        for channel in channels:
            channel_data = CHANNELS.get(channel.key)

            icon = channel.logo
            fanart = channel.background
            title = channel.name
            if channel_data:
                icon = '{path}/resources/logos/{logo}-white.png'.format(path=kodiutils.addon_path(), logo=channel.key)
                title = channel_data.get('label')

            context_menu = [(
                kodiutils.localize(30052, channel=title),  # Watch live {channel}
                'PlayMedia(%s)' %
                kodiutils.url_for('play', category='channels', item=channel.channel_id),
            )]

            if channel_data and channel_data.get('epg'):
                context_menu.append((
                    kodiutils.localize(30053, channel=title),  # TV Guide for {channel}
                    'Container.Update(%s)' %
                    kodiutils.url_for('show_tvguide_channel', channel=channel_data.get('epg'))
                ))

            context_menu.append((
                kodiutils.localize(30055, channel=title),  # Catalog for {channel}
                'Container.Update(%s)' %
                kodiutils.url_for('show_catalog_channel', channel=channel.key)
            ))

            if channel.epg:
                label = title + '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format(
                    title=channel.epg[0].title,
                    start=channel.epg[0].start.strftime('%H:%M'),
                    end=channel.epg[0].end.strftime('%H:%M'))
            else:
                label = title

            listing.append(kodiutils.TitleItem(
                title=label,
                path=kodiutils.url_for('show_channel_menu', channel=channel.key),
                art_dict=dict(
                    icon=icon,
                    thumb=icon,
                    fanart=fanart,
                ),
                info_dict=dict(
                    plot=Menu.format_plot(channel),
                    playcount=0,
                    mediatype='video',
                    studio=channel_data.get('studio_icon') if channel_data else None,
                ),
                stream_dict=dict(
                    codec='h264',
                    height=1080,
                    width=1920,
                ),
                context_menu=context_menu,
            ))

        kodiutils.show_listing(listing, 30007)

    def show_channel_menu(self, key):
        """ Shows a TV channel
        :type key: str
        """
        # Fetch EPG from API
        channel = self._vtm_go.get_live_channel(key)
        channel_data = CHANNELS.get(channel.key)

        icon = channel.logo
        fanart = channel.background
        title = channel.name
        if channel_data:
            icon = '{path}/resources/logos/{logo}-white.png'.format(path=kodiutils.addon_path(), logo=channel.key)
            title = channel_data.get('label')

        title = kodiutils.localize(30052, channel=title)  # Watch live {channel}
        if channel.epg:
            label = title + '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format(
                title=channel.epg[0].title,
                start=channel.epg[0].start.strftime('%H:%M'),
                end=channel.epg[0].end.strftime('%H:%M'))
        else:
            label = title

        # The .pvr suffix triggers some code paths in Kodi to mark this as a live channel
        listing = [kodiutils.TitleItem(
            title=label,
            path=kodiutils.url_for('play', category='channels', item=channel.channel_id) + '?.pvr',
            art_dict=dict(
                icon=icon,
                thumb=icon,
                fanart=fanart,
            ),
            info_dict=dict(
                plot=Menu.format_plot(channel),
                playcount=0,
                mediatype='video',
            ),
            stream_dict=dict(
                codec='h264',
                height=1080,
                width=1920,
            ),
            is_playable=True,
        )]

        if channel_data and channel_data.get('epg'):
            listing.append(
                kodiutils.TitleItem(
                    title=kodiutils.localize(30053, channel=channel_data.get('label')),  # TV Guide for {channel}
                    path=kodiutils.url_for('show_tvguide_channel', channel=channel_data.get('epg')),
                    art_dict=dict(
                        icon='DefaultAddonTvInfo.png',
                    ),
                    info_dict=dict(
                        plot=kodiutils.localize(30054, channel=channel_data.get('label')),  # Browse the TV Guide for {channel}
                    ),
                )
            )

        listing.append(kodiutils.TitleItem(
            title=kodiutils.localize(30055, channel=channel_data.get('label')),  # Catalog for {channel}
            path=kodiutils.url_for('show_catalog_channel', channel=key),
            art_dict=dict(
                icon='DefaultMovieTitle.png'
            ),
            info_dict=dict(
                plot=kodiutils.localize(30056, channel=channel_data.get('label')),  # Browse the Catalog for {channel}
            ),
        ))

        # Add YouTube channels
        if channel_data and kodiutils.get_cond_visibility('System.HasAddon(plugin.video.youtube)') != 0:
            for youtube in channel_data.get('youtube', []):
                listing.append(kodiutils.TitleItem(
                    title=kodiutils.localize(30206, label=youtube.get('label')),  # Watch {label} on YouTube
                    path=youtube.get('path'),
                    info_dict=dict(
                        plot=kodiutils.localize(30206, label=youtube.get('label')),  # Watch {label} on YouTube
                    )
                ))

        kodiutils.show_listing(listing, 30007, sort=['unsorted'])
Example #18
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 #19
0
""" Addon code """

from __future__ import absolute_import, division, unicode_literals

import routing

from resources.lib import GeoblockedException, UnavailableException
from resources.lib.kodiwrapper import KodiWrapper, TitleItem
from resources.lib.vtmgo.vtmgo import Content, VtmGo
from resources.lib.vtmgo.vtmgoauth import VtmGoAuth, InvalidLoginException
from resources.lib.vtmgo.vtmgoepg import VtmGoEpg
from resources.lib.vtmgo.vtmgostream import VtmGoStream

routing = routing.Plugin()
kodi = KodiWrapper(routing=routing)
vtm_go = VtmGo(kodi)


@routing.route('/kids')
def show_kids_index():
    """ Show the main menu (kids) """
    show_index()


@routing.route('/')
def show_index():
    """ Show the main menu """
    kids = kodi.kids_mode()

    listing = []
    listing.extend([
class Authentication:
    """ Code responsible for the Authentication """
    def __init__(self, kodi):
        """ Initialise object
        :type kodi: resources.lib.kodiwrapper.KodiWrapper
        """
        self._kodi = kodi
        self._vtm_go = VtmGo(self._kodi)

    def select_profile(self, key=None):
        """ Show your profiles
        :type key: str
        """
        try:
            profiles = self._vtm_go.get_profiles()
        except InvalidLoginException:
            self._kodi.show_ok_dialog(message=self._kodi.localize(
                30203))  # Your credentials are not valid!
            self._kodi.open_settings()
            return

        except LoginErrorException as exc:
            self._kodi.show_ok_dialog(message=self._kodi.localize(
                30702,
                code=exc.code))  # Unknown error while logging in: {code}
            self._kodi.open_settings()
            return

        except ApiUpdateRequired:
            self._kodi.show_ok_dialog(message=self._kodi.localize(
                30705))  # The VTM GO Service has been updated...
            return

        except Exception as exc:  # pylint: disable=broad-except
            self._kodi.show_ok_dialog(message="%s" % exc)
            return

        # Show warning when you have no profiles
        if not profiles:
            # Your account has no profiles defined. Please login on vtm.be/vtmgo and create a Profile.
            self._kodi.show_ok_dialog(message=self._kodi.localize(30703))
            self._kodi.end_of_directory()
            return

        # Select the first profile when you only have one
        if len(profiles) == 1:
            key = profiles[0].key

        # Save the selected profile
        if key:
            profile = [x for x in profiles if x.key == key][0]
            _LOGGER.debug('Setting profile to %s', profile)
            self._kodi.set_setting('profile',
                                   '%s:%s' % (profile.key, profile.product))
            self._kodi.set_setting('profile_name', profile.name)

            self._kodi.redirect(self._kodi.url_for('show_main_menu'))
            return

        # Show profile selection when you have multiple profiles
        listing = [
            TitleItem(
                title=self._get_profile_name(p),
                path=self._kodi.url_for('select_profile', key=p.key),
                art_dict=dict(icon='DefaultUser.png'),
                info_dict=dict(plot=p.name, ),
            ) for p in profiles
        ]

        self._kodi.show_listing(listing, sort=['unsorted'],
                                category=30057)  # Select Profile

    @staticmethod
    def _get_profile_name(profile):
        """ Get a descriptive string of the profile
        :type profile: resources.lib.vtmgo.vtmgo.Profile
        """
        title = profile.name

        # Convert the VTM GO Profile color to a matching Kodi color
        color_map = {
            '#64D8E3': 'skyblue',
            '#4DFF76': 'mediumspringgreen',
            '#0243FF': 'blue',
            '#831CFA': 'blueviolet',
            '#FFB24D': 'khaki',
            '#FF4DD5': 'violet',
            '#FFB002': 'gold',
            '#FF0257': 'crimson',
        }
        if color_map.get(profile.color.upper()):
            title = '[COLOR %s]%s[/COLOR]' % (color_map.get(
                profile.color), to_unicode(title))

        # Append (Kids)
        if profile.product == 'VTM_GO_KIDS':
            title = "%s (Kids)" % title

        return title
Example #21
0
class Channels:
    """ Menu code related to channels """
    def __init__(self, kodi):
        """ Initialise object
        :type kodi: resources.lib.kodiwrapper.KodiWrapper
        """
        self._kodi = kodi
        self._vtm_go = VtmGo(self._kodi)
        self._menu = Menu(self._kodi)

    def show_channels(self):
        """ Shows TV channels """
        product = self._vtm_go.get_product()
        kids = (product == 'VTM_GO_KIDS')

        # Fetch EPG from API
        channel_infos = self._vtm_go.get_live_channels()

        listing = []
        for i, key in enumerate(CHANNELS):  # pylint: disable=unused-variable
            channel = CHANNELS[key]

            if kids and channel.get('kids') is False:
                continue

            # Find this channel in the list
            channel_info = next(c for c in channel_infos if c.key == key)

            # Lookup the high resolution logo based on the channel name
            icon = '{path}/resources/logos/{logo}-white.png'.format(
                path=self._kodi.get_addon_path(), logo=channel.get('logo'))
            fanart = '{path}/resources/logos/{logo}.png'.format(
                path=self._kodi.get_addon_path(), logo=channel.get('logo'))

            context_menu = [
                (
                    self._kodi.localize(
                        30052,
                        channel=channel.get('label')),  # Watch live {channel}
                    'XBMC.PlayMedia(%s)' %
                    self._kodi.url_for('play',
                                       category='channels',
                                       item=channel_info.channel_id)),
                (
                    self._kodi.localize(30053, channel=channel.get(
                        'label')),  # TV Guide for {channel}
                    'XBMC.Container.Update(%s)' % self._kodi.url_for(
                        'show_tvguide_channel', channel=channel.get('epg')))
            ]
            if self._kodi.get_setting_as_bool('metadata_update'):
                context_menu.append((
                    self._kodi.localize(
                        30055,
                        channel=channel.get('label')),  # Catalog for {channel}
                    'XBMC.Container.Update(%s)' %
                    self._kodi.url_for('show_catalog_channel', channel=key)))

            title = channel.get('label')
            if channel_info and channel_info.epg:
                title += '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format(
                    title=channel_info.epg[0].title,
                    start=channel_info.epg[0].start.strftime('%H:%M'),
                    end=channel_info.epg[0].end.strftime('%H:%M'))

            listing.append(
                TitleItem(title=title,
                          path=self._kodi.url_for('show_channel_menu',
                                                  channel=key),
                          art_dict={
                              'icon': icon,
                              'thumb': icon,
                              'fanart': fanart,
                          },
                          info_dict={
                              'plot': self._menu.format_plot(channel),
                              'playcount': 0,
                              'mediatype': 'video',
                              'studio': channel.get('studio_icon'),
                          },
                          stream_dict={
                              'codec': 'h264',
                              'height': 1080,
                              'width': 1920,
                          },
                          context_menu=context_menu), )

        self._kodi.show_listing(listing, 30007)

    def show_channel_menu(self, key):
        """ Shows a TV channel
        :type key: str
        """
        channel = CHANNELS[key]

        # Fetch EPG from API
        channel_info = self._vtm_go.get_live_channel(key)

        title = self._kodi.localize(
            30052, channel=channel.get('label'))  # Watch live {channel}
        if channel_info.epg:
            title += '[COLOR gray] | {title} ({start} - {end})[/COLOR]'.format(
                title=channel_info.epg[0].title,
                start=channel_info.epg[0].start.strftime('%H:%M'),
                end=channel_info.epg[0].end.strftime('%H:%M'))

        # Lookup the high resolution logo based on the channel name
        icon = '{path}/resources/logos/{logo}-white.png'.format(
            path=self._kodi.get_addon_path(), logo=channel.get('logo'))
        fanart = '{path}/resources/logos/{logo}.png'.format(
            path=self._kodi.get_addon_path(), logo=channel.get('logo'))

        listing = [
            TitleItem(title=title,
                      path=self._kodi.url_for('play',
                                              category='channels',
                                              item=channel_info.channel_id) +
                      '?.pvr',
                      art_dict={
                          'icon': icon,
                          'thumb': icon,
                          'fanart': fanart,
                      },
                      info_dict={
                          'plot': self._menu.format_plot(channel_info),
                          'playcount': 0,
                          'mediatype': 'video',
                      },
                      stream_dict={
                          'codec': 'h264',
                          'height': 1080,
                          'width': 1920,
                      },
                      is_playable=True),
            TitleItem(
                title=self._kodi.localize(
                    30053,
                    channel=channel.get('label')),  # TV Guide for {channel}
                path=self._kodi.url_for('show_tvguide_channel',
                                        channel=channel.get('epg')),
                art_dict={'icon': 'DefaultAddonTvInfo.png'},
                info_dict={
                    'plot':
                    self._kodi.localize(30054, channel=channel.get(
                        'label')),  # Browse the TV Guide for {channel}
                }),
        ]

        if self._kodi.get_setting_as_bool('metadata_update'):
            listing.append(
                TitleItem(
                    title=self._kodi.localize(
                        30055,
                        channel=channel.get('label')),  # Catalog for {channel}
                    path=self._kodi.url_for('show_catalog_channel',
                                            channel=key),
                    art_dict={'icon': 'DefaultMovieTitle.png'},
                    info_dict={
                        'plot':
                        self._kodi.localize(30056,
                                            channel=channel.get('label')),
                    }))

            # Add YouTube channels
        if self._kodi.get_cond_visibility(
                'System.HasAddon(plugin.video.youtube)') != 0:
            for youtube in channel.get('youtube', []):
                listing.append(
                    TitleItem(
                        title=self._kodi.localize(
                            30206, label=youtube.get(
                                'label')),  # Watch {label} on YouTube
                        path=youtube.get('path'),
                        info_dict={
                            'plot':
                            self._kodi.localize(
                                30206, label=youtube.get(
                                    'label')),  # Watch {label} on YouTube
                        }))

        self._kodi.show_listing(listing, 30007, sort=['unsorted'])
Example #22
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')
Example #23
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 #24
0
 def __init__(self):
     Monitor.__init__(self)
     self.kodi = KodiWrapper()
     self.vtm_go = VtmGo(self.kodi)
     self.update_interval = 24 * 3600  # Every 24 hours
 def onSettingsChanged(self):
     """ Callback when a setting has changed """
     # Refresh our VtmGo instance
     self.vtm_go = VtmGo(self.kodi)
Example #26
0
 def __init__(self, kodi):
     """ Initialise object """
     self._kodi = kodi
     self._vtm_go = VtmGo(self._kodi)
Example #27
0
    def play(self, category, item):
        """ Play the requested item.
        :type category: string
        :type item: string
        """
        # Check if inputstreamhelper is correctly installed
        try:
            from inputstreamhelper import Helper
            is_helper = Helper('mpd', drm='com.widevine.alpha')
            if not is_helper.check_inputstream():
                # inputstreamhelper has already shown an error
                return

        except ImportError:
            self._kodi.show_ok_dialog(message=self._kodi.localize(30708))  # Please reboot Kodi
            return

        try:
            # Get stream information
            resolved_stream = self._vtm_go_stream.get_stream(category, item)

        except StreamGeoblockedException:
            self._kodi.show_ok_dialog(heading=self._kodi.localize(30709), message=self._kodi.localize(30710))  # This video is geo-blocked...
            self._kodi.end_of_directory()
            return

        except StreamUnavailableException:
            self._kodi.show_ok_dialog(heading=self._kodi.localize(30711), message=self._kodi.localize(30712))  # The video is unavailable...
            self._kodi.end_of_directory()
            return

        info_dict = {
            'tvshowtitle': resolved_stream.program,
            'title': resolved_stream.title,
            'duration': resolved_stream.duration,
        }

        prop_dict = {}

        stream_dict = {
            'duration': resolved_stream.duration,
        }

        # Lookup metadata
        try:
            if category in ['movies', 'oneoffs']:
                info_dict.update({'mediatype': 'movie'})

                # Get details
                details = VtmGo(self._kodi).get_movie(item)
                info_dict.update({
                    'plot': details.description,
                    'year': details.year,
                    'aired': details.aired,
                })

            elif category == 'episodes':
                info_dict.update({'mediatype': 'episode'})

                # Get details
                details = VtmGo(self._kodi).get_episode(item)
                info_dict.update({
                    'plot': details.description,
                    'season': details.season,
                    'episode': details.number,
                })

            elif category == 'channels':
                info_dict.update({'mediatype': 'episode'})

                # For live channels, we need to keep on updating the manifest
                # This might not be needed, and could be done with the Location-tag updates if inputstream.adaptive supports it
                # See https://github.com/peak3d/inputstream.adaptive/pull/298#issuecomment-524206935
                prop_dict.update({
                    'inputstream.adaptive.manifest_update_parameter': 'full',
                })

            else:
                raise Exception('Unknown category %s' % category)

        except UnavailableException:
            # We continue without details.
            # This seems to make it possible to play some programs what don't have metadata.
            pass

        # Play this item
        self._kodi.play(
            TitleItem(
                title=resolved_stream.title,
                path=resolved_stream.url,
                subtitles_path=resolved_stream.subtitles,
                art_dict={},
                info_dict=info_dict,
                prop_dict=prop_dict,
                stream_dict=stream_dict,
                is_playable=True,
            ),
            license_key=self._vtm_go_stream.create_license_key(resolved_stream.license_url))
Example #28
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 #29
0
    def onSettingsChanged(self):
        """ Callback when a setting has changed """
        self.kodi.log('IN VTM GO: Settings changed')

        # Refresh our VtmGo instance
        self.vtm_go = VtmGo(self.kodi)
Example #30
0
class IPTVManager:
    """ Code related to the Kodi PVR integration """
    def __init__(self, port):
        """ Initialise object
        :type port: int
        """
        self._vtm_go = VtmGo()
        self._vtm_go_epg = VtmGoEpg()
        self.port = port

    def via_socket(func):  # pylint: disable=no-self-argument
        """Send the output of the wrapped function to socket"""
        def send(self):
            """Decorator to send over a socket"""
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect(('127.0.0.1', self.port))
            try:
                sock.sendall(json.dumps(func(self)).encode())  # pylint: disable=not-callable
            finally:
                sock.close()

        return send

    @via_socket
    def send_channels(self):
        """ Report channel data """
        # Fetch EPG from API
        channels = self._vtm_go.get_live_channels()

        results = []
        for channel in channels:
            channel_data = CHANNELS.get(channel.key)

            if not channel_data:
                _LOGGER.warning(
                    'Skipping %s since we don\'t know this channel',
                    channel.key)
                continue

            results.append(
                dict(
                    name=channel_data.get('label')
                    if channel_data else channel.name,
                    id=channel_data.get('iptv_id'),
                    preset=channel_data.get('iptv_preset'),
                    logo=
                    'special://home/addons/{addon}/resources/logos/{logo}.png'.
                    format(addon=kodiutils.addon_id(), logo=channel.key)
                    if channel_data else channel.logo,
                    stream=kodiutils.url_for('play',
                                             category='channels',
                                             item=channel.channel_id),
                    vod=kodiutils.url_for('play_epg_datetime',
                                          channel=channel.key,
                                          timestamp='{date}'),
                ))

        return dict(version=1, streams=results)

    @via_socket
    def send_epg(self):
        """ Report EPG data """
        results = dict()

        # Fetch EPG data
        for date in ['yesterday', 'today', 'tomorrow']:

            channels = self._vtm_go_epg.get_epgs(date)
            for channel in channels:
                # Lookup channel data in our own CHANNELS dict
                channel_data = next((c for c in CHANNELS.values()
                                     if c.get('epg') == channel.key), None)
                if not channel_data:
                    _LOGGER.warning(
                        'Skipping EPG for %s since we don\'t know this channel',
                        channel.key)
                    continue

                key = channel_data.get('iptv_id')

                # Create channel in dict if it doesn't exists
                if key not in results.keys():
                    results[key] = []

                results[key].extend([
                    dict(
                        start=broadcast.time.isoformat(),
                        stop=(
                            broadcast.time +
                            timedelta(seconds=broadcast.duration)).isoformat(),
                        title=broadcast.title,
                        description=broadcast.description,
                        # subtitle=None,  # Not available in the API
                        # season=None,  # Not available in the API
                        # epsiode=None,  # Not available in the API
                        genre=broadcast.genre,
                        image=broadcast.thumb,
                        stream=kodiutils.url_for(
                            'play',
                            category=broadcast.playable_type,
                            item=broadcast.playable_uuid)
                        if broadcast.playable_uuid else None)
                    for broadcast in channel.broadcasts
                ])

        return dict(version=1, epg=results)