コード例 #1
0
 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
コード例 #2
0
    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
コード例 #3
0
    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
コード例 #4
0
 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
コード例 #5
0
 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
コード例 #6
0
    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
コード例 #7
0
 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
コード例 #8
0
    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')
コード例 #9
0
    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
コード例 #10
0
 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={})
コード例 #11
0
 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
コード例 #12
0
    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')
コード例 #13
0
 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
コード例 #14
0
    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)
コード例 #15
0
 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
コード例 #16
0
 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
コード例 #17
0
 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
コード例 #18
0
 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
コード例 #19
0
 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)
コード例 #20
0
 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
コード例 #21
0
    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
コード例 #22
0
 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
コード例 #23
0
 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
コード例 #24
0
    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
コード例 #25
0
    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
コード例 #26
0
    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
コード例 #27
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
コード例 #28
0
 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')