Пример #1
0
    def list_tvshows(self,
                     category=None,
                     channel=None,
                     feature=None,
                     use_favorites=False):
        """List all TV shows for a given category, channel or feature, optionally filtered by favorites"""

        # Get tvshows
        tvshows = self.get_tvshows(category=category,
                                   channel=channel,
                                   feature=feature)

        # Get oneoffs
        if get_setting_bool('showoneoff', default=True):
            cache_file = 'oneoff.json'
            oneoffs = self.get_episodes(variety='oneoff',
                                        cache_file=cache_file)
        else:
            cache_file = None
            # Return empty list
            oneoffs = []

        return self.__map_tvshows(tvshows,
                                  oneoffs,
                                  use_favorites=use_favorites,
                                  cache_file=cache_file)
Пример #2
0
    def list_tvshows(self,
                     category=None,
                     channel=None,
                     feature=None,
                     programs=None,
                     use_favorites=False):
        """List all TV shows for a given category, channel, feature or list of programNames, optionally filtered by favorites"""

        # Get tvshows
        tvshows = self.get_tvshows(category=category,
                                   channel=channel,
                                   feature=feature)

        # Filter tvshows using a list of programNames
        if programs:
            filtered_tvshows = []
            for tvshow in tvshows:
                if tvshow.get('programName') in programs:
                    filtered_tvshows.append(tvshow)
            tvshows = filtered_tvshows

        # Get oneoffs
        if get_setting_bool('showoneoff', default=True):
            cache_file = 'oneoff.json'
            oneoffs = self.get_episodes(variety='oneoff',
                                        cache_file=cache_file)
        else:
            cache_file = None
            # Return empty list
            oneoffs = []

        return self.__map_tvshows(tvshows,
                                  oneoffs,
                                  use_favorites=use_favorites,
                                  cache_file=cache_file)
    def _select_hls_substreams(self, master_hls_url, protocol):
        """Select HLS substreams to speed up Kodi player start, workaround for slower Kodi selection"""
        hls_variant_url = None
        subtitle_url = None
        hls_audio_id = None
        hls_subtitle_id = None
        hls_base_url = master_hls_url.split('.m3u8')[0]
        try:
            response = open_url(master_hls_url, raise_errors=[415])
        except HTTPError as exc:
            self._handle_bad_stream_error(protocol, exc.code, exc.reason)
            return None
        if response is None:
            return None
        hls_playlist = to_unicode(response.read())
        max_bandwidth = get_max_bandwidth()
        stream_bandwidth = None

        # Get hls variant url based on max_bandwidth setting
        import re
        hls_variant_regex = re.compile(r'#EXT-X-STREAM-INF:[\w\-.,=\"]*?BANDWIDTH=(?P<BANDWIDTH>\d+),'
                                       r'[\w\-.,=\"]+\d,(?:AUDIO=\"(?P<AUDIO>[\w\-]+)\",)?(?:SUBTITLES=\"'
                                       r'(?P<SUBTITLES>\w+)\",)?[\w\-.,=\"]+?[\r\n](?P<URI>[\w:\/\-.=?&]+)')
        # reverse sort by bandwidth
        for match in sorted(re.finditer(hls_variant_regex, hls_playlist), key=lambda m: int(m.group('BANDWIDTH')), reverse=True):
            stream_bandwidth = int(match.group('BANDWIDTH')) // 1000
            if max_bandwidth == 0 or stream_bandwidth < max_bandwidth:
                if match.group('URI').startswith('http'):
                    hls_variant_url = match.group('URI')
                else:
                    hls_variant_url = hls_base_url + match.group('URI')
                hls_audio_id = match.group('AUDIO')
                hls_subtitle_id = match.group('SUBTITLES')
                break

        if stream_bandwidth > max_bandwidth and not hls_variant_url:
            message = localize(30057, max=max_bandwidth, min=stream_bandwidth)
            ok_dialog(message=message)
            open_settings()

        # Get audio url
        if hls_audio_id:
            audio_regex = re.compile(r'#EXT-X-MEDIA:TYPE=AUDIO[\w\-=,\.\"\/]+?GROUP-ID=\"' + hls_audio_id + ''
                                     r'\"[\w\-=,\.\"\/]+?URI=\"(?P<AUDIO_URI>[\w\-=]+)\.m3u8\"')
            match_audio = re.search(audio_regex, hls_playlist)
            if match_audio:
                hls_variant_url = hls_base_url + match_audio.group('AUDIO_URI') + '-' + hls_variant_url.split('-')[-1]

        # Get subtitle url, works only for on demand streams
        if get_setting_bool('showsubtitles', default=True) and '/live/' not in master_hls_url and hls_subtitle_id:
            subtitle_regex = re.compile(r'#EXT-X-MEDIA:TYPE=SUBTITLES[\w\-=,\.\"\/]+?GROUP-ID=\"' + hls_subtitle_id + ''
                                        r'\"[\w\-=,\.\"\/]+URI=\"(?P<SUBTITLE_URI>[\w\-=]+)\.m3u8\"')
            match_subtitle = re.search(subtitle_regex, hls_playlist)
            if match_subtitle:
                subtitle_url = hls_base_url + match_subtitle.group('SUBTITLE_URI') + '.webvtt'

        return StreamURLS(hls_variant_url, subtitle_url)
Пример #4
0
    def list_youtube(channels=None):
        """Construct a list of youtube ListItems, either for Live TV or the TV Guide listing"""

        youtube_items = []

        if not has_addon('plugin.video.youtube') or not get_setting_bool(
                'showyoutube', default=True):
            return youtube_items

        for channel in CHANNELS:
            if channels and channel.get('name') not in channels:
                continue

            art_dict = {}

            # Try to use the white icons for thumbnails (used for icons as well)
            if has_addon('resource.images.studios.white'):
                art_dict[
                    'thumb'] = 'resource://resource.images.studios.white/{studio}.png'.format(
                        **channel)
            else:
                art_dict['thumb'] = 'DefaultTags.png'

            for youtube in channel.get('youtube', []):
                path = youtube_to_plugin_url(youtube['url'])
                label = localize(30143, **youtube)  # Channel on YouTube
                # A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
                if channels and len(channels) == 1:
                    label = '[B]%s[/B]' % label
                plot = localize(30144, **youtube)  # Watch on YouTube
                # NOTE: Playcount is required to not have live streams as "Watched"
                info_dict = dict(title=label,
                                 plot=plot,
                                 studio=channel.get('studio'),
                                 mediatype='video',
                                 playcount=0)

                context_menu = [(
                    localize(30413),  # Refresh menu
                    'RunPlugin(%s)' %
                    url_for('delete_cache',
                            cache_file='channel.{channel}.json'.format(
                                channel=channel)),
                )]

                youtube_items.append(
                    TitleItem(
                        label=label,
                        path=path,
                        art_dict=art_dict,
                        info_dict=info_dict,
                        context_menu=context_menu,
                        is_playable=False,
                    ))

        return youtube_items
 def _handle_bad_stream_error(protocol, code=None, reason=None):
     """Show a localized error message in Kodi GUI for a failing VRT NU stream based on protocol: hls, hls_aes, mpeg_dash)
         message: VRT NU stream <stream_type> problem, try again with (InputStream Adaptive) (and) (DRM) enabled/disabled:
             30959=and DRM, 30960=disabled, 30961=enabled
    """
     # HLS AES DRM failed
     if protocol == 'hls_aes' and not supports_drm():
         message = localize(30962, protocol=protocol.upper(), version=kodi_version_major())
     elif protocol == 'hls_aes' and not has_inputstream_adaptive() and not get_setting_bool('usedrm', default=True):
         message = localize(30958, protocol=protocol.upper(), component=localize(30959), state=localize(30961))
     elif protocol == 'hls_aes' and has_inputstream_adaptive():
         message = localize(30958, protocol=protocol.upper(), component='Widevine DRM', state=localize(30961))
     elif protocol == 'hls_aes' and get_setting_bool('usedrm', default=True):
         message = localize(30958, protocol=protocol.upper(), component='InputStream Adaptive', state=localize(30961))
     else:
         message = localize(30958, protocol=protocol.upper(), component='InputStream Adaptive', state=localize(30960))
     heading = 'HTTP Error {code}: {reason}'.format(code=code, reason=reason) if code and reason else None
     log_error('Unable to play stream. {error}', error=heading)
     ok_dialog(heading=heading, message=message)
     end_of_directory()
Пример #6
0
 def list_categories(self):
     """Construct a list of category ListItems"""
     from webscraper import get_categories
     categories = get_categories()
     category_items = []
     from data import CATEGORIES
     for category in self.localize_categories(categories, CATEGORIES):
         if get_setting_bool('showfanart', default=True):
             thumbnail = category.get('thumbnail', 'DefaultGenre.png')
         else:
             thumbnail = 'DefaultGenre.png'
         category_items.append(TitleItem(
             label=category.get('name'),
             path=url_for('categories', category=category.get('id')),
             art_dict=dict(thumb=thumbnail, icon='DefaultGenre.png'),
             info_dict=dict(plot='[B]%s[/B]' % category.get('name'), studio='VRT'),
         ))
     return category_items
 def push_upnext(self):
     """Push episode info to Up Next service add-on"""
     if has_addon('service.upnext') and get_setting_bool(
             'useupnext', default=True) and self.isPlaying():
         info_tag = self.getVideoInfoTag()
         next_info = self.apihelper.get_upnext(
             dict(
                 program=to_unicode(info_tag.getTVShowTitle()),
                 playcount=info_tag.getPlayCount(),
                 rating=info_tag.getRating(),
                 path=self.path,
                 runtime=self.total,
             ))
         if next_info:
             from base64 import b64encode
             from json import dumps
             data = [to_unicode(b64encode(dumps(next_info).encode()))]
             sender = '{addon_id}.SIGNAL'.format(addon_id=addon_id())
             notify(sender=sender, message='upnext_data', data=data)
Пример #8
0
 def is_activated():
     """Is resumepoints activated in the menu and do we have credentials ?"""
     return get_setting_bool('useresumepoints',
                             default=True) and has_credentials()
Пример #9
0
def get_category_thumbnail(element):
    """Return a category thumbnail, if available"""
    if get_setting_bool('showfanart', default=True):
        raw_thumbnail = element.find(class_='media').get('data-responsive-image', 'DefaultGenre.png')
        return add_https_proto(raw_thumbnail)
    return 'DefaultGenre.png'
Пример #10
0
    def show_favorites_menu(self):
        """The VRT NU addon 'My programs' menu"""
        self._favorites.refresh(ttl=ttl('indirect'))
        favorites_items = [
            TitleItem(label=localize(30040),  # My programs
                      path=url_for('favorites_programs'),
                      art_dict=dict(thumb='DefaultMovieTitle.png'),
                      info_dict=dict(plot=localize(30041))),
            TitleItem(label=localize(30048),  # My recent items
                      path=url_for('favorites_recent'),
                      art_dict=dict(thumb='DefaultRecentlyAddedEpisodes.png'),
                      info_dict=dict(plot=localize(30049))),
            TitleItem(label=localize(30050),  # My soon offline
                      path=url_for('favorites_offline'),
                      art_dict=dict(thumb='DefaultYear.png'),
                      info_dict=dict(plot=localize(30051))),
        ]

        # Only add 'My watch later' and 'Continue watching' when it has been activated
        if self._resumepoints.is_activated():
            favorites_items.append(TitleItem(
                label=localize(30052),  # My watch later
                path=url_for('resumepoints_watchlater'),
                art_dict=dict(thumb='DefaultVideoPlaylists.png'),
                info_dict=dict(plot=localize(30053)),
            ))
            favorites_items.append(TitleItem(
                label=localize(30054),  # Continue Watching
                path=url_for('resumepoints_continue'),
                art_dict=dict(thumb='DefaultInProgressShows.png'),
                info_dict=dict(plot=localize(30055)),
            ))

        if get_setting_bool('addmymovies', default=True):
            favorites_items.append(
                TitleItem(label=localize(30042),  # My movies
                          path=url_for('categories', category='films'),
                          art_dict=dict(thumb='DefaultAddonVideo.png'),
                          info_dict=dict(plot=localize(30043))),
            )

        if get_setting_bool('addmydocu', default=True):
            favorites_items.append(
                TitleItem(label=localize(30044),  # My documentaries
                          path=url_for('favorites_docu'),
                          art_dict=dict(thumb='DefaultMovies.png'),
                          info_dict=dict(plot=localize(30045))),
            )

        if get_setting_bool('addmymusic', default=True):
            favorites_items.append(
                TitleItem(label=localize(30046),  # My music
                          path=url_for('favorites_music'),
                          art_dict=dict(thumb='DefaultAddonMusic.png'),
                          info_dict=dict(plot=localize(30047))),
            )

        show_listing(favorites_items, category=30010, cache=False)  # My favorites

        # Show dialog when no favorites were found
        if not self._favorites.titles():
            ok_dialog(heading=localize(30415), message=localize(30416))
Пример #11
0
 def is_activated():
     """Is favorites activated in the menu and do we have credentials ?"""
     return get_setting_bool('usefavorites',
                             default=True) and has_credentials()
Пример #12
0
    def list_channels(self, channels=None, live=True):
        """Construct a list of channel ListItems, either for Live TV or the TV Guide listing"""
        from tvguide import TVGuide
        _tvguide = TVGuide()

        channel_items = []
        for channel in CHANNELS:
            if channels and channel.get('name') not in channels:
                continue

            context_menu = []
            art_dict = {}

            # Try to use the white icons for thumbnails (used for icons as well)
            if has_addon('resource.images.studios.white'):
                art_dict[
                    'thumb'] = 'resource://resource.images.studios.white/{studio}.png'.format(
                        **channel)
            else:
                art_dict['thumb'] = 'DefaultTags.png'

            if not live:
                path = url_for('channels', channel=channel.get('name'))
                label = channel.get('label')
                plot = '[B]%s[/B]' % channel.get('label')
                is_playable = False
                info_dict = dict(title=label,
                                 plot=plot,
                                 studio=channel.get('studio'),
                                 mediatype='video')
                stream_dict = []
                prop_dict = {}
            elif channel.get('live_stream') or channel.get('live_stream_id'):
                if channel.get('live_stream_id'):
                    path = url_for('play_id',
                                   video_id=channel.get('live_stream_id'))
                elif channel.get('live_stream'):
                    path = url_for('play_url',
                                   video_url=channel.get('live_stream'))
                label = localize(30141, **channel)  # Channel live
                playing_now = _tvguide.playing_now(channel.get('name'))
                if playing_now:
                    label += ' [COLOR=yellow]| %s[/COLOR]' % playing_now
                # A single Live channel means it is the entry for channel's TV Show listing, so make it stand out
                if channels and len(channels) == 1:
                    label = '[B]%s[/B]' % label
                is_playable = True
                if channel.get('name') in ['een', 'canvas', 'ketnet']:
                    if get_setting_bool('showfanart', default=True):
                        art_dict['fanart'] = self.get_live_screenshot(
                            channel.get('name', art_dict.get('fanart')))
                    plot = '%s\n\n%s' % (localize(30142, **channel),
                                         _tvguide.live_description(
                                             channel.get('name')))
                else:
                    plot = localize(30142, **channel)  # Watch live
                # NOTE: Playcount and resumetime are required to not have live streams as "Watched" and resumed
                info_dict = dict(title=label,
                                 plot=plot,
                                 studio=channel.get('studio'),
                                 mediatype='video',
                                 playcount=0,
                                 duration=0)
                prop_dict = dict(resumetime=0)
                stream_dict = dict(duration=0)
                context_menu.append((
                    localize(30413),  # Refresh menu
                    'RunPlugin(%s)' %
                    url_for('delete_cache',
                            cache_file='channel.{channel}.json'.format(
                                channel=channel)),
                ))
            else:
                # Not a playable channel
                continue

            channel_items.append(
                TitleItem(
                    label=label,
                    path=path,
                    art_dict=art_dict,
                    info_dict=info_dict,
                    prop_dict=prop_dict,
                    stream_dict=stream_dict,
                    context_menu=context_menu,
                    is_playable=is_playable,
                ))

        return channel_items
Пример #13
0
    def get_episodes(self,
                     program=None,
                     season=None,
                     episodes=None,
                     category=None,
                     feature=None,
                     programtype=None,
                     keywords=None,
                     whatson_id=None,
                     video_id=None,
                     video_url=None,
                     page=None,
                     use_favorites=False,
                     variety=None,
                     cache_file=None):
        """Get episodes or season data from VRT NU Search API"""

        # Contruct params
        if page:
            page = realpage(page)
            all_items = False
            items_per_page = get_setting_int('itemsperpage', default=50)
            params = {
                'from': ((page - 1) * items_per_page) + 1,
                'i': 'video',
                'size': items_per_page,
            }
        elif variety == 'single':
            all_items = False
            params = {
                'i': 'video',
                'size': '1',
            }
        else:
            all_items = True
            params = {
                'i': 'video',
                'size': '300',
            }

        if variety:
            season = 'allseasons'

            if variety == 'offline':
                from datetime import datetime, timedelta
                import dateutil.tz
                now = datetime.now(dateutil.tz.gettz('Europe/Brussels'))
                off_dates = [(now + timedelta(days=day)).strftime('%Y-%m-%d')
                             for day in range(0, 7)]
                params['facets[assetOffTime]'] = '[%s]' % (','.join(off_dates))

            if variety == 'oneoff':
                params[
                    'facets[episodeNumber]'] = '[0,1]'  # This to avoid VRT NU metadata errors (see #670)
                params['facets[programType]'] = 'oneoff'

            if variety == 'watchlater':
                self._resumepoints.refresh(ttl=ttl('direct'))
                episode_urls = self._resumepoints.watchlater_urls()
                params['facets[url]'] = '[%s]' % (','.join(episode_urls))

            if variety == 'continue':
                self._resumepoints.refresh(ttl=ttl('direct'))
                episode_urls = self._resumepoints.resumepoints_urls()
                params['facets[url]'] = '[%s]' % (','.join(episode_urls))

            if use_favorites:
                program_urls = [
                    program_to_url(p, 'medium')
                    for p in self._favorites.programs()
                ]
                params['facets[programUrl]'] = '[%s]' % (
                    ','.join(program_urls))
            elif variety in ('offline', 'recent'):
                channel_filter = []
                for channel in CHANNELS:
                    if channel.get('vod') is True and get_setting_bool(
                            channel.get('name'), default=True):
                        channel_filter.append(channel.get('name'))
                params['facets[programBrands]'] = '[%s]' % (
                    ','.join(channel_filter))

        if program:
            params['facets[programUrl]'] = program_to_url(program, 'medium')

        if season and season != 'allseasons':
            params['facets[seasonTitle]'] = season

        if episodes:
            params['facets[episodeNumber]'] = '[%s]' % (','.join(
                str(episode) for episode in episodes))

        if category:
            params['facets[categories]'] = category

        if feature:
            params['facets[programTags.title]'] = feature

        if programtype:
            params['facets[programType]'] = programtype

        if keywords:
            if not season:
                season = 'allseasons'
            params['q'] = quote_plus(from_unicode(keywords))
            params['highlight'] = 'true'

        if whatson_id:
            params['facets[whatsonId]'] = whatson_id

        if video_id:
            params['facets[videoId]'] = video_id

        if video_url:
            params['facets[url]'] = video_url

        # Construct VRT NU Search API Url and get api data
        querystring = '&'.join('{}={}'.format(key, value)
                               for key, value in list(params.items()))
        search_url = self._VRTNU_SEARCH_URL + '?' + querystring.replace(
            ' ', '%20')  # Only encode spaces to minimize url length
        if cache_file:
            search_json = get_cached_url_json(url=search_url,
                                              cache=cache_file,
                                              ttl=ttl('indirect'),
                                              fail={})
        else:
            search_json = get_url_json(url=search_url, fail={})

        # Check for multiple seasons
        seasons = []
        if 'facets[seasonTitle]' not in unquote(search_url):
            facets = search_json.get('facets', {}).get('facets')
            if facets:
                seasons = next((f.get('buckets', [])
                                for f in facets if f.get('name') == 'seasons'
                                and len(f.get('buckets', [])) > 1), None)
            # Experimental: VRT Search API only returns a maximum of 10 seasons, to get all seasons we need to use the "model.json" API
            if seasons and program and len(seasons) == 10:
                season_json = get_url_json(
                    'https://www.vrt.be/vrtnu/a-z/%s.model.json' % program)
                season_items = None
                try:
                    season_items = season_json.get(':items').get('parsys').get(':items').get('container') \
                                              .get(':items').get('banner').get(':items').get('navigation').get(':items')
                except AttributeError:
                    pass
                if season_items:
                    seasons = []
                    for item in season_items:
                        seasons.append(dict(key=item.lstrip('0')))

        episodes = search_json.get('results', [{}])
        show_seasons = bool(season != 'allseasons')

        # Return seasons
        if show_seasons and seasons:
            return (seasons, episodes)

        api_pages = search_json.get('meta').get('pages').get('total')
        api_page_size = search_json.get('meta').get('pages').get('size')
        total_results = search_json.get('meta').get('total_results')

        if all_items and total_results > api_page_size:
            for api_page in range(1, api_pages):
                api_page_url = search_url + '&from=' + str(api_page *
                                                           api_page_size + 1)
                api_page_json = get_url_json(api_page_url)
                if api_page_json is not None:
                    episodes += api_page_json.get('results', [{}])

        # Return episodes
        return episodes
Пример #14
0
    def get_art(api_data, season=False):
        """Get art dict from single item json api data"""
        art_dict = {}

        # VRT NU Search API
        if api_data.get('type') == 'episode':
            if season is not False:
                if get_setting_bool('showfanart', default=True):
                    art_dict['fanart'] = add_https_proto(
                        api_data.get('programImageUrl', 'DefaultSets.png'))
                    if season != 'allseasons':
                        art_dict['thumb'] = add_https_proto(
                            api_data.get('videoThumbnailUrl',
                                         art_dict.get('fanart')))
                    else:
                        art_dict['thumb'] = art_dict.get('fanart')
                    art_dict['banner'] = art_dict.get('fanart')
                    if api_data.get('programAlternativeImageUrl'):
                        art_dict['cover'] = add_https_proto(
                            api_data.get('programAlternativeImageUrl'))
                        art_dict['poster'] = add_https_proto(
                            api_data.get('programAlternativeImageUrl'))
                else:
                    art_dict['thumb'] = 'DefaultSets.png'
            else:
                if get_setting_bool('showfanart', default=True):
                    art_dict['thumb'] = add_https_proto(
                        api_data.get('videoThumbnailUrl',
                                     'DefaultAddonVideo.png'))
                    art_dict['fanart'] = add_https_proto(
                        api_data.get('programImageUrl', art_dict.get('thumb')))
                    art_dict['banner'] = art_dict.get('fanart')
                    if api_data.get('programAlternativeImageUrl'):
                        art_dict['cover'] = add_https_proto(
                            api_data.get('programAlternativeImageUrl'))
                        art_dict['poster'] = add_https_proto(
                            api_data.get('programAlternativeImageUrl'))
                else:
                    art_dict['thumb'] = 'DefaultAddonVideo.png'

            return art_dict

        # VRT NU Suggest API
        if api_data.get('type') == 'program':
            if get_setting_bool('showfanart', default=True):
                art_dict['thumb'] = add_https_proto(
                    api_data.get('thumbnail', 'DefaultAddonVideo.png'))
                art_dict['fanart'] = art_dict.get('thumb')
                art_dict['banner'] = art_dict.get('fanart')
                if api_data.get('alternativeImage'):
                    art_dict['cover'] = add_https_proto(
                        api_data.get('alternativeImage'))
                    art_dict['poster'] = add_https_proto(
                        api_data.get('alternativeImage'))
            else:
                art_dict['thumb'] = 'DefaultAddonVideo.png'

            return art_dict

        # VRT NU Schedule API (some are missing vrt.whatson-id)
        if api_data.get('vrt.whatson-id') or api_data.get('startTime'):
            if get_setting_bool('showfanart', default=True):
                art_dict['thumb'] = add_https_proto(
                    api_data.get('image', 'DefaultAddonVideo.png'))
                art_dict['fanart'] = art_dict.get('thumb')
                art_dict['banner'] = art_dict.get('fanart')
            else:
                art_dict['thumb'] = 'DefaultAddonVideo.png'

            return art_dict

        # Not Found
        return art_dict
Пример #15
0
    def get_plot(self, api_data, season=False, date=None):
        """Get plot string from single item json api data"""
        from datetime import datetime
        import dateutil.parser
        import dateutil.tz

        # VRT NU Search API
        if api_data.get('type') == 'episode':
            if season is not False:
                plot = html_to_kodi(api_data.get('programDescription', ''))

                # Add additional metadata to plot
                plot_meta = ''
                if api_data.get('allowedRegion') == 'BE':
                    plot_meta += localize(30201) + '\n\n'  # Geo-blocked
                plot = '%s[B]%s[/B]\n%s' % (plot_meta, api_data.get('program'),
                                            plot)
                return colour(plot)

            # Add additional metadata to plot
            plot_meta = ''
            # Only display when a video disappears if it is within the next 3 months
            if api_data.get('assetOffTime'):
                offtime = dateutil.parser.parse(api_data.get('assetOffTime'))

                # Show the remaining days/hours the episode is still available
                if offtime:
                    now = datetime.now(dateutil.tz.tzlocal())
                    remaining = offtime - now
                    if remaining.days / 365 > 5:
                        pass  # If it is available for more than 5 years, do not show
                    elif remaining.days / 365 > 2:
                        plot_meta += localize(
                            30202, years=int(remaining.days /
                                             365))  # X years remaining
                    elif remaining.days / 30.5 > 3:
                        plot_meta += localize(
                            30203, months=int(remaining.days /
                                              30.5))  # X months remaining
                    elif remaining.days > 1:
                        plot_meta += localize(
                            30204, days=remaining.days)  # X days to go
                    elif remaining.days == 1:
                        plot_meta += localize(30205)  # 1 day to go
                    elif remaining.seconds // 3600 > 1:
                        plot_meta += localize(30206,
                                              hours=remaining.seconds //
                                              3600)  # X hours to go
                    elif remaining.seconds // 3600 == 1:
                        plot_meta += localize(30207)  # 1 hour to go
                    else:
                        plot_meta += localize(30208,
                                              minutes=remaining.seconds //
                                              60)  # X minutes to go

            if api_data.get('allowedRegion') == 'BE':
                if plot_meta:
                    plot_meta += '  '
                plot_meta += localize(30201)  # Geo-blocked

            # Add product placement
            if api_data.get('productPlacement') is True:
                if plot_meta:
                    plot_meta += '  '
                plot_meta += '[B]PP[/B]'

            # Add film rating
            rating = self.get_mpaa(api_data)
            if rating:
                if plot_meta:
                    plot_meta += '  '
                plot_meta += '[B]%s[/B]' % rating

            plot = html_to_kodi(api_data.get('description', ''))

            if plot_meta:
                plot = '%s\n\n%s' % (plot_meta, plot)

            permalink = shorten_link(
                api_data.get('permalink')) or api_data.get('externalPermalink')
            if permalink and get_setting_bool('showpermalink', default=False):
                plot = '%s\n\n[COLOR={highlighted}]%s[/COLOR]' % (plot,
                                                                  permalink)
            return colour(plot)

        # VRT NU Suggest API
        if api_data.get('type') == 'program':
            plot = unescape(api_data.get('description', '???'))
            # permalink = shorten_link(api_data.get('programUrl'))
            # if permalink and get_setting_bool('showpermalink', default=False):
            #     plot = '%s\n\n[COLOR={highlighted}]%s[/COLOR]' % (plot, permalink)
            return colour(plot)

        # VRT NU Schedule API (some are missing vrt.whatson-id)
        if api_data.get('vrt.whatson-id') or api_data.get('startTime'):
            now = datetime.now(dateutil.tz.tzlocal())
            epg = self.parse(date, now)
            plot = '[B]{datelong}[/B]\n{start} - {end}\n\n{description}'.format(
                datelong=localize_datelong(epg),
                start=api_data.get('start'),
                end=api_data.get('end'),
                description=html_to_kodi(api_data.get('description', '')),
            )
            return colour(plot)

        # Not Found
        return ''