def update_online(self, asset_id, title, url, payload): """Update resumepoint online""" from json import dumps try: get_url_json('https://video-user-data.vrt.be/resume_points/{asset_id}'.format(asset_id=asset_id), headers=self.resumepoint_headers(url), data=dumps(payload).encode()) except HTTPError as exc: log_error('Failed to (un)watch episode {title} at VRT NU ({error})', title=title, error=exc) notification(message=localize(30977, title=title)) return False return True
def update(self, program, title, value=True): """Set a program as favorite, and update local copy""" # Survive any recent updates self.refresh(ttl=5) if value is self.is_favorite(program): # Already followed/unfollowed, nothing to do return True from tokenresolver import TokenResolver xvrttoken = TokenResolver().get_xvrttoken(token_variant='user') if xvrttoken is None: log_error('Failed to get favorites token from VRT NU') notification(message=localize(30975)) return False headers = { 'authorization': 'Bearer ' + xvrttoken, 'content-type': 'application/json', 'Referer': 'https://www.vrt.be/vrtnu', } from json import dumps from utils import program_to_url payload = dict(isFavorite=value, programUrl=program_to_url(program, 'short'), title=title) data = dumps(payload).encode('utf-8') program_id = program_to_id(program) try: get_url_json( 'https://video-user-data.vrt.be/favorites/{program_id}'.format( program_id=program_id), headers=headers, data=data) except HTTPError as exc: log_error( "Failed to (un)follow program '{program}' at VRT NU ({error})", program=program, error=exc) notification(message=localize(30976, program=program)) return False # NOTE: Updates to favorites take a longer time to take effect, so we keep our own cache and use it self._data[program_id] = dict(value=payload) update_cache('favorites.json', dumps(self._data)) invalidate_caches('my-offline-*.json', 'my-recent-*.json') return True
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 set_watchlater_graphql(self, episode_id, title, watch_later=True): """Set watchLater episode using GraphQL API""" from tokenresolver import TokenResolver vrtlogin_at = TokenResolver().get_token('vrtlogin-at') result_json = {} if vrtlogin_at: headers = { 'Authorization': 'Bearer ' + vrtlogin_at, 'Content-Type': 'application/json', } graphql_query = """ mutation setWatchLater($input: WatchLaterActionInput!) { setWatchLater(input: $input) { id watchLater } } """ payload = dict( variables=dict(input=dict( id=episode_id, title=title, watchLater=watch_later, ), ), query=graphql_query, ) from json import dumps data = dumps(payload).encode('utf-8') result_json = get_url_json(url=self.GRAPHQL_URL, cache=None, headers=headers, data=data, raise_errors='all') return result_json
def set_favorite_graphql(self, program_id, title, is_favorite=True): """Set favorite using GraphQL API""" from tokenresolver import TokenResolver vrtlogin_at = TokenResolver().get_token('vrtlogin-at') result_json = {} if vrtlogin_at: headers = { 'Authorization': 'Bearer ' + vrtlogin_at, 'Content-Type': 'application/json', } graphql_query = """ mutation setFavorite($input: FavoriteActionInput!) { setFavorite(input: $input) { id favorite } } """ payload = dict( variables=dict(input=dict( id=program_id, title=title, favorite=is_favorite, ), ), query=graphql_query, ) from json import dumps data = dumps(payload).encode('utf-8') result_json = get_url_json(url=self.GRAPHQL_URL, cache=None, headers=headers, data=data, raise_errors='all') return result_json
def _get_stream_json(self, api_data, roaming=False): """Get JSON with stream details from VRT API""" if not api_data: return None # Try cache for livestreams if api_data.is_live_stream and not roaming: filename = api_data.video_id + '.json' data = get_cache(filename) if data: return data token_url = api_data.media_api_url + '/tokens' if api_data.is_live_stream: playertoken = self._tokenresolver.get_token('vrtPlayerToken', 'live', token_url, roaming=roaming) else: playertoken = self._tokenresolver.get_token('vrtPlayerToken', 'ondemand', token_url, roaming=roaming) # Construct api_url and get video json if not playertoken: return None api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \ api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client stream_json = get_url_json(url=api_url) # Update livestream cache if we have a livestream if stream_json and api_data.is_live_stream: from json import dumps # Warning: Currently, the drmExpired key in the stream_json cannot be used because it provides a wrong 6 hour ttl for the VUDRM tokens. # After investigation these tokens seem to have an expiration time of only two hours, so we set the expirationDate value accordingly. stream_json.update(expirationDate=generate_expiration_date(hours=2), vualto_license_url=self._get_vualto_license_url()) cache_file = api_data.video_id + '.json' update_cache(cache_file, dumps(stream_json)) return stream_json
def get_program_id_graphql(self, program_name): """Get programId from programName using GraphQL API""" from tokenresolver import TokenResolver vrtlogin_at = TokenResolver().get_token('vrtlogin-at') program_id = None if vrtlogin_at: headers = { 'Authorization': 'Bearer ' + vrtlogin_at, 'Content-Type': 'application/json', } graphql = """ query Page($id: ID!) { page(id: $id) { ... on IPage { id } } } """ payload = dict( variables=dict( id='/vrtnu/a-z/{}.model.json'.format(program_name)), query=graphql, ) from json import dumps data = dumps(payload).encode('utf-8') page_json = get_url_json(url=self.GRAPHQL_URL, cache=None, headers=headers, data=data, raise_errors='all') program_id = page_json.get('data').get('page').get('id') return program_id
def _get_new_playertoken(self, token_url, headers, token_variant=None): """Get new playertoken from VRT Token API""" playertoken = get_url_json(url=token_url, headers=headers, data=b'') if playertoken is None: return None self._set_cached_token(playertoken, token_variant) return playertoken.get('vrtPlayerToken')
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 _get_login_json(self): """Get login json""" payload = dict( loginID=from_unicode(get_setting('username')), password=from_unicode(get_setting('password')), sessionExpiration='-2', APIKey=self._API_KEY, targetEnv='jssdk', ) data = urlencode(payload).encode() return get_url_json(self._LOGIN_URL, data=data, fail={})
def refresh(self, ttl=None): """Get a cached copy or a newer resumepoints from VRT, or fall back to a cached file""" if not self.is_activated(): return resumepoints_json = get_cache('resume_points.json', ttl) if not resumepoints_json: resumepoints_url = 'https://video-user-data.vrt.be/resume_points' headers = self.resumepoint_headers() if not headers: return resumepoints_json = get_url_json(url=resumepoints_url, cache='resume_points.json', headers=headers) if resumepoints_json is not None: self._data = resumepoints_json
def _get_vualto_license_url(self): """Get Widevine license URL from Vualto API""" # Try cache data = get_cache('vualto_license_url.json') if data: return data.get('la_url') vualto_license_url = get_url_json(url=self._VUPLAY_API_URL, fail={}).get('drm_providers', {}).get('widevine', {}) if vualto_license_url: from json import dumps vualto_license_url.update(expirationDate=generate_expiration_date(hours=168)) update_cache('vualto_license_url.json', dumps(vualto_license_url)) return vualto_license_url.get('la_url')
def get_featured_from_web(): """Return a list of featured items from VRT NU website using AEM JSON Exporter""" featured = [] data = get_url_json( 'https://www.vrt.be/vrtnu/jcr:content/par.model.json') if data is not None: items = data.get(':items') if items: for item in items: filled = items.get(item).get('items') if filled: featured.append( dict(name=items.get(item).get('title'), id='jcr_%s' % item)) return featured
def _get_stream_json(self, api_data, roaming=False): """Get JSON with stream details from VRT API""" if not api_data: return None token_url = api_data.media_api_url + '/tokens' if api_data.is_live_stream: playertoken = self._tokenresolver.get_playertoken(token_url, token_variant='live', roaming=roaming) else: playertoken = self._tokenresolver.get_playertoken(token_url, token_variant='ondemand', roaming=roaming) # Construct api_url and get video json if not playertoken: return None api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \ api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client return get_url_json(url=api_url)
def refresh_resumepoints(self, ttl=None): """Get a cached copy or a newer resumepoints from VRT, or fall back to a cached file""" if not self.is_activated(): return resumepoints_json = get_cache(self.RESUMEPOINTS_CACHE_FILE, ttl) if not resumepoints_json: resumepoints_url = self.RESUMEPOINTS_URL + '?max=500&sortBy=-updated' headers = self.resumepoints_headers() if not headers: return resumepoints_json = get_url_json( url=resumepoints_url, cache=self.RESUMEPOINTS_CACHE_FILE, headers=headers) if resumepoints_json is not None: self._resumepoints = resumepoints_json
def get_favorites(self): """Get favorites using VRT NU REST API""" from tokenresolver import TokenResolver vrtlogin_at = TokenResolver().get_token('vrtlogin-at') favorites_json = {} if vrtlogin_at: headers = { 'Authorization': 'Bearer ' + vrtlogin_at, 'Accept': 'application/json', } querystring = 'tileType=program-poster&tileContentType=program&tileOrientation=portrait&layout=slider&title=Mijn+favoriete+programma%27s' favorites_json = get_url_json(url='{}?{}'.format( self.FAVORITES_REST_URL, querystring), cache=None, headers=headers, raise_errors='all') return favorites_json
def get_online_categories(): """Return a list of categories from the VRT NU website""" categories = [] categories_json = get_url_json( 'https://www.vrt.be/vrtnu/categorieen/jcr:content/par/categories.model.json' ) if categories_json is not None: categories = [] for category in categories_json.get('items'): categories.append( dict( id=category.get('name'), thumbnail=add_https_proto( category.get('image').get('src')), name=category.get('title'), )) return categories
def refresh(self, ttl=None): """Get a cached copy or a newer favorites from VRT, or fall back to a cached file""" if not self.is_activated(): return favorites_json = get_cache('favorites.json', ttl) if not favorites_json: from tokenresolver import TokenResolver xvrttoken = TokenResolver().get_token('X-VRT-Token', variant='user') if xvrttoken: headers = { 'authorization': 'Bearer ' + xvrttoken, 'content-type': 'application/json', 'Referer': 'https://www.vrt.be/vrtnu', } favorites_url = 'https://video-user-data.vrt.be/favorites' favorites_json = get_url_json(url=favorites_url, cache='favorites.json', headers=headers) if favorites_json is not None: self._data = favorites_json
def get_featured_media_from_web(feature): """Return a list of featured media from VRT NU website using AEM JSON Exporter""" media = [] data = get_url_json( 'https://www.vrt.be/vrtnu/jcr:content/par/%s.model.json' % feature) if data is not None: for item in data.get('items'): mediatype = 'tvshows' for action in item.get('actions'): if 'episode' in action.get('type'): mediatype = 'episodes' if mediatype == 'episodes': media.append(item.get('whatsonId')) else: media.append(item.get('programName')) return dict(name=data.get('title'), mediatype=mediatype, medialist=media)
def get_watchlater(self): """Get watchlater using VRT NU REST API""" from tokenresolver import TokenResolver vrtlogin_at = TokenResolver().get_token('vrtlogin-at') watchlater_json = {} if vrtlogin_at: headers = { 'Authorization': 'Bearer ' + vrtlogin_at, 'Accept': 'application/json', } payload = dict(tileType='mixed-content', tileContentType='episode', tileOrientation='landscape', layout='slider', title='Later kijken') watchlater_json = get_url_json(url='{}?{}'.format( self.WATCHLATER_REST_URL, urlencode(payload)), cache=None, headers=headers, raise_errors='all') return watchlater_json
def _get_stream_json(self, api_data, roaming=False): """Get JSON with stream details from VRT API""" if not api_data: return None # Try cache for livestreams if api_data.is_live_stream and not roaming: filename = api_data.video_id + '.json' data = get_cache(filename) if data: return data token_url = api_data.media_api_url + '/tokens' if api_data.is_live_stream: playertoken = self._tokenresolver.get_token('vrtPlayerToken', 'live', token_url, roaming=roaming) else: playertoken = self._tokenresolver.get_token('vrtPlayerToken', 'ondemand', token_url, roaming=roaming) # Construct api_url and get video json if not playertoken: return None api_url = api_data.media_api_url + '/videos/' + api_data.publication_id + \ api_data.video_id + '?vrtPlayerToken=' + playertoken + '&client=' + api_data.client stream_json = get_url_json(url=api_url) if stream_json and api_data.is_live_stream: from json import dumps exp = stream_json.get('drmExpired') or generate_expiration_date() vualto_license_url = self._get_vualto_license_url().get('la_url') stream_json.update(expirationDate=exp, vualto_license_url=vualto_license_url) cache_file = api_data.video_id + '.json' update_cache(cache_file, dumps(stream_json)) return stream_json
def _get_playertoken(self, variant, url, roaming=False): """Get a vrtPlayerToken""" from json import dumps headers = {'Content-Type': 'application/json'} data = b'' if roaming or variant == 'ondemand': if roaming: # Delete cached vrtPlayerToken cache_file = self._get_token_filename('vrtPlayerToken', variant) delete_cache(cache_file, self._TOKEN_CACHE_DIR) xvrttoken = self.get_token('X-VRT-Token', 'roaming') elif variant == 'ondemand': xvrttoken = self.get_token('X-VRT-Token') if xvrttoken is None: return None payload = dict(identityToken=xvrttoken) data = dumps(payload).encode() playertoken = get_url_json(url=url, headers=headers, data=data) if playertoken: return playertoken return None
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 update_resumepoint(self, video_id, asset_str, title, position=None, total=None, path=None, episode_id=None, episode_title=None): """Set episode resumepoint and update local copy""" if video_id is None: return True menu_caches = [] self.refresh_resumepoints(ttl=5) # Add existing position and total if None if video_id in self._resumepoints and position is None and total is None: position = self.get_position(video_id) total = self.get_total(video_id) # Update if (self.still_watching(position, total) or (path and path.startswith('plugin://plugin.video.vrt.nu/play/upnext'))): # Normally, VRT NU resumepoints are deleted when an episode is (un)watched and Kodi GUI automatically sets # the (un)watched status when Kodi Player exits. This mechanism doesn't work with "Up Next" episodes because # these episodes are not initiated from a ListItem in Kodi GUI. # For "Up Next" episodes, we should never delete the VRT NU resumepoints to make sure the watched status # can be forced in Kodi GUI using the playcount infolabel. log(3, "[Resumepoints] Update resumepoint '{video_id}' {position}/{total}", video_id=video_id, position=position, total=total) if position == self.get_position( video_id) and total == self.get_total(video_id): # Resumepoint is not changed, nothing to do return True menu_caches.append('continue-*.json') # Update online gdpr = '{asset_str} gekeken tot {at} seconden.'.format( asset_str=asset_str, at=position) payload = dict( at=position, total=total, gdpr=gdpr, ) from json import dumps try: resumepoint_json = get_url_json( '{api}/{video_id}'.format(api=self.RESUMEPOINTS_URL, video_id=video_id), headers=self.resumepoints_headers(), data=dumps(payload).encode()) except HTTPError as exc: log_error( 'Failed to update resumepoint of {title} at VRT NU ({error})', title=title, error=exc) notification(message=localize(30977, title=title)) return False # Update local for idx, item in enumerate(self._resumepoints.get('items')): if item.get('mediaId') == video_id: self._resumepoints.get('items')[idx] = resumepoint_json break update_cache(self.RESUMEPOINTS_CACHE_FILE, dumps(self._resumepoints)) if menu_caches: invalidate_caches(*menu_caches) else: # Delete log(3, "[Resumepoints] Delete resumepoint '{asset_str}' {position}/{total}", asset_str=asset_str, position=position, total=total) # Delete watchlater self.update_watchlater(episode_id, episode_title, watch_later=False) # Do nothing if there is no resumepoint for this video_id from json import dumps if video_id not in dumps(self._resumepoints): log(3, "[Resumepoints] '{video_id}' not present, nothing to delete", video_id=video_id) return True # Add menu caches menu_caches.append('continue-*.json') # Delete online try: result = open_url('{api}/{video_id}'.format( api=self.RESUMEPOINTS_URL, video_id=video_id), headers=self.resumepoints_headers(), method='DELETE', raise_errors='all') log(3, "[Resumepoints] '{video_id}' online deleted: {code}", video_id=video_id, code=result.getcode()) except HTTPError as exc: log_error( "Failed to remove resumepoint of '{video_id}': {error}", video_id=video_id, error=exc) return False # Delete local representation and cache for item in self._resumepoints.get('items'): if item.get('mediaId') == video_id: self._resumepoints.get('items').remove(item) break update_cache(self.RESUMEPOINTS_CACHE_FILE, dumps(self._resumepoints)) if menu_caches: invalidate_caches(*menu_caches) return True
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
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_vualto_license_url(self): """Get Widevine license URL from Vualto API""" json_data = get_url_json(url=self._VUPLAY_API_URL, fail={}) self._vualto_license_url = json_data.get('drm_providers', {}).get('widevine', {}).get('la_url')