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)
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)
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()
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)
def is_activated(): """Is resumepoints activated in the menu and do we have credentials ?""" return get_setting_bool('useresumepoints', default=True) and has_credentials()
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'
def show_favorites_menu(self): """The VRT NU addon 'My programs' menu""" self._favorites.refresh(ttl=ttl('indirect')) favorites_items = [ TitleItem(label=localize(30040), # My programs path=url_for('favorites_programs'), art_dict=dict(thumb='DefaultMovieTitle.png'), info_dict=dict(plot=localize(30041))), TitleItem(label=localize(30048), # My recent items path=url_for('favorites_recent'), art_dict=dict(thumb='DefaultRecentlyAddedEpisodes.png'), info_dict=dict(plot=localize(30049))), TitleItem(label=localize(30050), # My soon offline path=url_for('favorites_offline'), art_dict=dict(thumb='DefaultYear.png'), info_dict=dict(plot=localize(30051))), ] # Only add 'My watch later' and 'Continue watching' when it has been activated if self._resumepoints.is_activated(): favorites_items.append(TitleItem( label=localize(30052), # My watch later path=url_for('resumepoints_watchlater'), art_dict=dict(thumb='DefaultVideoPlaylists.png'), info_dict=dict(plot=localize(30053)), )) favorites_items.append(TitleItem( label=localize(30054), # Continue Watching path=url_for('resumepoints_continue'), art_dict=dict(thumb='DefaultInProgressShows.png'), info_dict=dict(plot=localize(30055)), )) if get_setting_bool('addmymovies', default=True): favorites_items.append( TitleItem(label=localize(30042), # My movies path=url_for('categories', category='films'), art_dict=dict(thumb='DefaultAddonVideo.png'), info_dict=dict(plot=localize(30043))), ) if get_setting_bool('addmydocu', default=True): favorites_items.append( TitleItem(label=localize(30044), # My documentaries path=url_for('favorites_docu'), art_dict=dict(thumb='DefaultMovies.png'), info_dict=dict(plot=localize(30045))), ) if get_setting_bool('addmymusic', default=True): favorites_items.append( TitleItem(label=localize(30046), # My music path=url_for('favorites_music'), art_dict=dict(thumb='DefaultAddonMusic.png'), info_dict=dict(plot=localize(30047))), ) show_listing(favorites_items, category=30010, cache=False) # My favorites # Show dialog when no favorites were found if not self._favorites.titles(): ok_dialog(heading=localize(30415), message=localize(30416))
def is_activated(): """Is favorites activated in the menu and do we have credentials ?""" return get_setting_bool('usefavorites', default=True) and has_credentials()
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
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
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
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 ''