def show_tvguide(self, date=None, channel=None): """Offer a menu depending on the information provided""" if not date and not channel: date_items = self.get_date_items() show_listing(date_items, category=30026, content='files') # TV guide elif not channel: channel_items = self.get_channel_items(date=date) entry = find_entry(RELATIVE_DATES, 'id', date) date_name = localize(entry.get('msgctxt')) if entry else date show_listing(channel_items, category=date_name) elif not date: date_items = self.get_date_items(channel=channel) channel_name = find_entry(CHANNELS, 'name', channel).get('label') show_listing(date_items, category=channel_name, content='files', selected=7) else: episode_items = self.get_episode_items(date, channel) channel_name = find_entry(CHANNELS, 'name', channel).get('label') entry = find_entry(RELATIVE_DATES, 'id', date) date_name = localize(entry.get('msgctxt')) if entry else date show_listing(episode_items, category='%s / %s' % (channel_name, date_name), content='episodes', cache=False)
def get_epg_data(self): """Return EPG data""" now = datetime.now(dateutil.tz.tzlocal()) epg_data = {} for date in ['yesterday', 'today', 'tomorrow']: epg = self.parse(date, now) epg_url = epg.strftime(self.VRT_TVGUIDE) schedule = get_url_json(url=epg_url, fail={}) for channel_id, episodes in list(schedule.items()): channel = find_entry(CHANNELS, 'id', channel_id) epg_id = channel.get('epg_id') if epg_id not in epg_data: epg_data[epg_id] = [] for episode in episodes: if episode.get('url') and episode.get('vrt.whatson-id'): path = url_for( 'play_whatson_id', whatson_id=episode.get('vrt.whatson-id')) else: path = None epg_data[epg_id].append( dict( start=episode.get('startTime'), stop=episode.get('endTime'), image=add_https_proto(episode.get('image', '')), title=episode.get('title'), subtitle=html_to_kodi(episode.get('subtitle', '')), description=html_to_kodi( episode.get('description', '')), stream=path, )) return epg_data
def playing_now(self, channel): """Return the EPG information for what is playing now""" now = datetime.now(dateutil.tz.tzlocal()) epg = now # Daily EPG information shows information from 6AM until 6AM if epg.hour < 6: epg += timedelta(days=-1) entry = find_entry(CHANNELS, 'name', channel) if not entry: return '' epg_url = epg.strftime(self.VRT_TVGUIDE) schedule = get_cached_url_json(url=epg_url, cache='schedule.today.json', ttl=ttl('indirect'), fail={}) episodes = iter(schedule.get(entry.get('id'), [])) while True: try: episode = next(episodes) except StopIteration: break start_date = dateutil.parser.parse(episode.get('startTime')) end_date = dateutil.parser.parse(episode.get('endTime')) if start_date <= now <= end_date: # Now playing return episode.get('title') return ''
def get_episode_items(self, date, channel): """Show episodes for a given date and channel""" now = datetime.now(dateutil.tz.tzlocal()) epg = self.parse(date, now) epg_url = epg.strftime(self.VRT_TVGUIDE) self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) cache_file = 'schedule.{date}.json'.format(date=date) if date in ('today', 'yesterday', 'tomorrow'): schedule = get_cached_url_json(url=epg_url, cache=cache_file, ttl=ttl('indirect'), fail={}) else: schedule = get_url_json(url=epg_url, fail={}) entry = find_entry(CHANNELS, 'name', channel) if entry: episodes = schedule.get(entry.get('id'), []) else: episodes = [] episode_items = [] for episode in episodes: program = url_to_program(episode.get('url', '')) if episode.get('url'): video_url = add_https_proto(episode.get('url')) path = url_for('play_url', video_url=video_url) context_menu, favorite_marker, watchlater_marker = self._metadata.get_context_menu( episode, program, cache_file) label = self._metadata.get_label( episode) + favorite_marker + watchlater_marker is_playable = True else: label = '[COLOR={greyedout}]%s[/COLOR]' % self._metadata.get_label( episode) path = url_for('noop') context_menu, _, _ = self._metadata.get_context_menu( episode, program, cache_file) is_playable = False info_labels = self._metadata.get_info_labels(episode, date=date, channel=entry) # FIXME: Due to a bug in Kodi, ListItem.Title is used when Sort methods are used, not ListItem.Label info_labels['title'] = colour(label) episode_items.append( TitleItem( label=colour(label), path=path, art_dict=self._metadata.get_art(episode), info_dict=info_labels, context_menu=context_menu, is_playable=is_playable, )) return episode_items
def show_featured_menu(self, feature=None): """The VRT NU add-on 'Featured content' listing menu""" if feature: self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) tvshow_items = self._apihelper.list_tvshows(feature=feature) from data import FEATURED feature_msgctxt = find_entry(FEATURED, 'id', feature).get('msgctxt') show_listing(tvshow_items, category=feature_msgctxt, sort='label', content='tvshows', cache=False) else: featured_items = self._apihelper.list_featured() show_listing(featured_items, category=30024, sort='label', content='files')
def show_category_menu(self, category=None): """The VRT NU add-on 'Categories' listing menu""" if category: self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) tvshow_items = self._apihelper.list_tvshows(category=category) from data import CATEGORIES category_msgctxt = find_entry(CATEGORIES, 'id', category).get('msgctxt') show_listing(tvshow_items, category=category_msgctxt, sort='label', content='tvshows') else: category_items = self._apihelper.list_categories() show_listing(category_items, category=30014, sort='unsorted', content='files') # Categories
def live_description(self, channel): """Return the EPG information for current and next live program""" now = datetime.now(dateutil.tz.tzlocal()) epg = now # Daily EPG information shows information from 6AM until 6AM if epg.hour < 6: epg += timedelta(days=-1) entry = find_entry(CHANNELS, 'name', channel) if not entry: return '' epg_url = epg.strftime(self.VRT_TVGUIDE) schedule = get_cached_url_json(url=epg_url, cache='schedule.today.json', ttl=ttl('indirect'), fail={}) episodes = iter(schedule.get(entry.get('id'), [])) description = '' episode = None while True: try: episode = next(episodes) except StopIteration: break start_date = dateutil.parser.parse(episode.get('startTime')) end_date = dateutil.parser.parse(episode.get('endTime')) if start_date <= now <= end_date: # Now playing description = '[COLOR={highlighted}][B]%s[/B] %s[/COLOR]\n' % ( localize(30421), self.episode_description(episode)) try: description += '[B]%s[/B] %s' % (localize(30422), self.episode_description( next(episodes))) except StopIteration: break break if now < start_date: # Nothing playing now, but this may be next description = '[B]%s[/B] %s\n' % ( localize(30422), self.episode_description(episode)) try: description += '[B]%s[/B] %s' % (localize(30422), self.episode_description( next(episodes))) except StopIteration: break break if episode and not description: # Add a final 'No transmission' program description = '[COLOR={highlighted}][B]%s[/B] %s - 06:00\n» %s[/COLOR]' % ( localize(30421), episode.get('end'), localize(30423)) return colour(description)
def parse(date, now): """Parse a given string and return a datetime object This supports 'today', 'yesterday' and 'tomorrow' It also compensates for TV-guides covering from 6AM to 6AM """ entry = find_entry(RELATIVE_DATES, 'id', date) if not entry: return dateutil.parser.parse(date) offset = entry.get('offset') if now.hour < 6: return now + timedelta(days=offset - 1) return now + timedelta(days=offset)
def get_date_items(channel=None): """Offer a menu to select the TV-guide date""" epg = datetime.now(dateutil.tz.tzlocal()) # Daily EPG information shows information from 6AM until 6AM if epg.hour < 6: epg += timedelta(days=-1) date_items = [] for offset in range(14, -19, -1): day = epg + timedelta(days=offset) label = localize_datelong(day) date = day.strftime('%Y-%m-%d') # Highlight today with context of 2 days entry = find_entry(RELATIVE_DATES, 'offset', offset) if entry: date_name = localize(entry.get('msgctxt')) if entry.get('permalink'): date = entry.get('id') if offset == 0: label = '[COLOR={highlighted}][B]{name}[/B], {date}[/COLOR]'.format( highlighted=themecolour('highlighted'), name=date_name, date=label) else: label = '[B]{name}[/B], {date}'.format(name=date_name, date=label) plot = '[B]{datelong}[/B]'.format(datelong=localize_datelong(day)) # Show channel list or channel episodes if channel: path = url_for('tvguide', date=date, channel=channel) else: path = url_for('tvguide', date=date) cache_file = 'schedule.{date}.json'.format(date=date) date_items.append( TitleItem( label=label, path=path, art_dict=dict(thumb='DefaultYear.png'), info_dict=dict(plot=plot), context_menu=[( localize(30413), # Refresh menu 'RunPlugin(%s)' % url_for('delete_cache', cache_file=cache_file))], )) return date_items
def show_channels_menu(self, channel=None): """The VRT NU add-on 'Channels' listing menu""" if channel: from tvguide import TVGuide self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) channel_items = self._apihelper.list_channels(channels=[channel]) # Live TV channel_items.extend(TVGuide().get_channel_items(channel=channel)) # TV guide channel_items.extend(self._apihelper.list_youtube(channels=[channel])) # YouTube channel_items.extend(self._apihelper.list_tvshows(channel=channel)) # TV shows from data import CHANNELS channel_name = find_entry(CHANNELS, 'name', channel).get('label') show_listing(channel_items, category=channel_name, sort='unsorted', content='tvshows', cache=False) # Channel else: channel_items = self._apihelper.list_channels(live=False) show_listing(channel_items, category=30016, cache=False)
def get_tag(api_data): """Return categories for a given episode""" # VRT NU Search API if api_data.get('type') == 'episode': from data import CATEGORIES return sorted([ localize( find_entry(CATEGORIES, 'id', category, {}).get('msgctxt', category)) for category in api_data.get('categories', []) ]) # VRT NU Suggest API if api_data.get('type') == 'program': return ['Series'] return []
def show_featured_menu(self, feature=None): """The VRT NU add-on 'Featured content' listing menu""" if feature: self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) programs = None sort = 'label' content = 'tvshows' ascending = True if feature.startswith('jcr_'): media = self._apihelper.get_featured_media_from_web( feature.split('jcr_')[1]) if media.get('mediatype') == 'episodes': variety = 'featured.{name}'.format(name=media.get( 'name').strip().lower().replace(' ', '_')) media_items, sort, ascending, content = self._apihelper.list_episodes( whatson_id=media.get('medialist'), variety=variety) elif media.get('mediatype') == 'tvshows': feature = None media_items = self._apihelper.list_tvshows( feature=feature, programs=media.get('medialist')) else: media_items = self._apihelper.list_tvshows(feature=feature, programs=programs) from data import FEATURED feature_msgctxt = None feature = find_entry(FEATURED, 'id', feature) if feature: feature_msgctxt = feature.get('msgctxt') show_listing(media_items, category=feature_msgctxt, sort=sort, ascending=ascending, content=content, cache=False) else: featured_items = self._apihelper.list_featured() show_listing(featured_items, category=30024, sort='label', content='files')
def get_epg_data(self): """Return EPG data""" epg_data = dict() epg_url = self.WEEK_SCHEDULE schedule = get_url_json( url=epg_url, headers=dict( accept='application/vnd.epg.vrt.be.schedule_3.1+json'), fail={}) for event in schedule.get('events', []): channel_id = event.get('channel', dict(code=None)).get('code') if channel_id is None: log(2, 'No channel code found in EPG event: {event}', event=event) continue channel = find_entry(CHANNELS, 'id', channel_id) if channel is None: log(2, 'No channel found using code: {code}', code=channel_id) continue epg_id = channel.get('epg_id') if epg_id not in epg_data: epg_data[epg_id] = [] if event.get('images'): image = event.get('images')[0].get('url') else: image = None epg_data[epg_id].append( dict( start=event.get('startTime'), stop=event.get('endTime'), image=image, title=event.get('title'), description=html_to_kodi(event.get('description', '')), )) return epg_data
def get_episode_items(self, date, channel): """Show episodes for a given date and channel""" now = datetime.now(dateutil.tz.tzlocal()) epg = self.parse(date, now) epg_url = epg.strftime(self.VRT_TVGUIDE) self._favorites.refresh(ttl=ttl('indirect')) self._resumepoints.refresh(ttl=ttl('indirect')) cache_file = 'schedule.{date}.json'.format(date=date) if date in ('today', 'yesterday', 'tomorrow'): schedule = get_cached_url_json(url=epg_url, cache=cache_file, ttl=ttl('indirect'), fail={}) else: schedule = get_url_json(url=epg_url, fail={}) entry = find_entry(CHANNELS, 'name', channel) if entry: episodes = schedule.get(entry.get('id'), []) else: episodes = [] episode_items = [] for episode in episodes: program = url_to_program(episode.get('url', '')) context_menu, favorite_marker, watchlater_marker = self._metadata.get_context_menu( episode, program, cache_file) label = self._metadata.get_label(episode) path = self.get_episode_path(episode, channel) # Playable item if '/play/' in path: is_playable = True label += favorite_marker + watchlater_marker # Non-actionable item else: is_playable = False label = '[COLOR={greyedout}]%s[/COLOR]' % label # Now playing start_date = dateutil.parser.parse(episode.get('startTime')) end_date = dateutil.parser.parse(episode.get('endTime')) if start_date <= now <= end_date: if is_playable: label = '[COLOR={highlighted}]%s[/COLOR] %s' % ( label, localize(30301)) else: label += localize(30301) info_labels = self._metadata.get_info_labels(episode, date=date, channel=entry) # FIXME: Due to a bug in Kodi, ListItem.Title is used when Sort methods are used, not ListItem.Label info_labels['title'] = colour(label) episode_items.append( TitleItem( label=colour(label), path=path, art_dict=self._metadata.get_art(episode), info_dict=info_labels, context_menu=context_menu, is_playable=is_playable, )) return episode_items
def get_episode_by_air_date(self, channel_name, start_date, end_date=None): """Get an episode of a program given the channel and the air date in iso format (2019-07-06T19:35:00)""" channel = find_entry(CHANNELS, 'name', channel_name) if not channel: return None from datetime import datetime, timedelta import dateutil.parser import dateutil.tz offairdate = None try: onairdate = dateutil.parser.parse( start_date, default=datetime.now(dateutil.tz.gettz('Europe/Brussels'))) except ValueError: return None if end_date: try: offairdate = dateutil.parser.parse( end_date, default=datetime.now(dateutil.tz.gettz('Europe/Brussels'))) except ValueError: return None video = None now = datetime.now(dateutil.tz.gettz('Europe/Brussels')) if onairdate.hour < 6: schedule_date = onairdate - timedelta(days=1) else: schedule_date = onairdate schedule_datestr = schedule_date.isoformat().split('T')[0] url = 'https://www.vrt.be/bin/epg/schedule.{date}.json'.format( date=schedule_datestr) schedule_json = get_url_json(url, fail={}) episodes = schedule_json.get(channel.get('id'), []) if not episodes: return None # Guess the episode episode_guess = None if not offairdate: mindate = min( abs(onairdate - dateutil.parser.parse(episode.get('startTime'))) for episode in episodes) episode_guess = next((episode for episode in episodes if abs( onairdate - dateutil.parser.parse(episode.get('startTime'))) == mindate), None) else: duration = offairdate - onairdate midairdate = onairdate + timedelta( seconds=duration.total_seconds() / 2) mindate = min( abs(midairdate - (dateutil.parser.parse(episode.get('startTime')) + timedelta(seconds=(dateutil.parser.parse( episode.get('endTime')) - dateutil.parser.parse( episode.get('startTime'))).total_seconds() / 2))) for episode in episodes) episode_guess = next((episode for episode in episodes if abs( midairdate - (dateutil.parser.parse(episode.get('startTime')) + timedelta( seconds=(dateutil.parser.parse(episode.get('endTime')) - dateutil.parser.parse(episode.get('startTime')) ).total_seconds() / 2))) == mindate), None) if episode_guess and episode_guess.get('vrt.whatson-id', None): offairdate_guess = dateutil.parser.parse( episode_guess.get('endTime')) video = self.get_single_episode( whatson_id=episode_guess.get('vrt.whatson-id')) if video: return video # Airdate live2vod feature: use livestream cache of last 24 hours if no video was found if now - timedelta(hours=24) <= dateutil.parser.parse( episode_guess.get('endTime')) <= now: start_date = onairdate.astimezone( dateutil.tz.UTC).isoformat()[0:19] end_date = offairdate_guess.astimezone( dateutil.tz.UTC).isoformat()[0:19] # Offairdate defined if offairdate and now - timedelta(hours=24) <= offairdate <= now: start_date = onairdate.astimezone( dateutil.tz.UTC).isoformat()[:19] end_date = offairdate.astimezone( dateutil.tz.UTC).isoformat()[:19] if start_date and end_date: info = self._metadata.get_info_labels(episode_guess, channel=channel, date=start_date) live2vod_title = '{} ({})'.format( info.get('tvshowtitle'), localize(30454)) # from livestream cache info.update(tvshowtitle=live2vod_title) video_item = TitleItem( label=self._metadata.get_label(episode_guess), art_dict=self._metadata.get_art(episode_guess), info_dict=info, prop_dict=self._metadata.get_properties(episode_guess), ) video = dict( listitem=video_item, video_id=channel.get('live_stream_id'), start_date=start_date, end_date=end_date, ) return video video = dict(errorlabel=episode_guess.get('title')) return video