def update(self, program_name, title, program_id, is_favorite=True):
        """Set a program as favorite, and update local copy"""

        # Survive any recent updates
        self.refresh(ttl=5)

        if is_favorite is self.is_favorite(program_name):
            # Already followed/unfollowed, nothing to do
            return True

        # Lookup program_id
        if program_id == 'None' or program_id is None:
            program_id = self.get_program_id_graphql(program_name)

        # Update local favorites cache
        if is_favorite is True:
            self._favorites[program_name] = dict(program_id=program_id,
                                                 title=title)
        else:
            del self._favorites[program_name]

        # Update cache dict
        from json import dumps
        update_cache(self.FAVORITES_CACHE_FILE, dumps(self._favorites))
        invalidate_caches('my-offline-*.json', 'my-recent-*.json')

        # Update online
        self.set_favorite_graphql(program_id, title, is_favorite)
        return True
示例#2
0
 def update_local(self, asset_id, resumepoint_json, menu_caches=None):
     """Update resumepoint locally and update cache"""
     self._data.update({asset_id: resumepoint_json})
     from json import dumps
     update_cache('resume_points.json', dumps(self._data))
     if menu_caches:
         invalidate_caches(*menu_caches)
示例#3
0
    def update_watchlater(self, episode_id, title, watch_later=None):
        """Set program watchLater status and update local copy"""

        self.refresh_watchlater(ttl=5)

        # Update
        log(3,
            "[watchLater] Update {episode_id} watchLater status",
            episode_id=episode_id)

        # watchLater status is not changed, nothing to do
        if watch_later is not None and watch_later is self.is_watchlater(
                episode_id):
            return True

        # Update local watch_later cache
        if watch_later is True:
            self._watchlater[episode_id] = dict(title=title)
        else:
            del self._watchlater[episode_id]

        # Update cache
        from json import dumps
        update_cache(self.WATCHLATER_CACHE_FILE, dumps(self._watchlater))
        invalidate_caches('watchlater-*.json')

        # Update online
        self.set_watchlater_graphql(episode_id, title, watch_later)

        return True
示例#4
0
 def delete_local(self, asset_id, menu_caches=None):
     """Delete resumepoint locally and update cache"""
     if asset_id in self._data:
         del self._data[asset_id]
         from json import dumps
         update_cache('resume_points.json', dumps(self._data))
         if menu_caches:
             invalidate_caches(*menu_caches)
示例#5
0
    def cleanup_userdata(self):
        """Cleanup userdata"""

        # Delete token cache
        self.delete_tokens()

        # Delete user-related caches
        invalidate_caches('continue-*.json', 'favorites.json', 'my-offline-*.json', 'my-recent-*.json', 'resume_points.json', 'watchlater-*.json')
示例#6
0
    def onSettingsChanged(self):  # pylint: disable=invalid-name
        """Handler for changes to settings"""

        log(1, 'Settings changed')
        TokenResolver().refresh_login()

        invalidate_caches('continue-*.json', 'favorites.json',
                          'my-offline-*.json', 'my-recent-*.json',
                          'resume_points.json', 'watchlater-*.json')

        # Init watching activity again when settings change
        self.init_watching_activity()

        # Refresh container when settings change
        container_refresh()
示例#7
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
示例#8
0
    def update(self, program, title, value=True):
        ''' Set a program as favorite, and update local copy '''

        self.refresh(ttl=60)
        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 statichelper import program_to_url
        payload = dict(isFavorite=value, programUrl=program_to_url(program, 'short'), title=title)
        from json import dumps
        data = dumps(payload).encode('utf-8')
        program_uuid = self.program_to_uuid(program)
        log(2, 'URL post: https://video-user-data.vrt.be/favorites/{uuid}', uuid=program_uuid)
        req = Request('https://video-user-data.vrt.be/favorites/%s' % program_uuid, data=data, headers=headers)
        result = urlopen(req)
        if result.getcode() != 200:
            log_error("Failed to (un)follow program '{program}' at VRT NU", program=program)
            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._favorites[program_uuid] = dict(value=payload)
        update_cache('favorites.json', self._favorites)
        invalidate_caches('my-offline-*.json', 'my-recent-*.json')
        return True
示例#9
0
    def update(self,
               uuid,
               title,
               url,
               watch_later=None,
               position=None,
               total=None):
        ''' Set program resumepoint or watchLater status and update local copy '''

        # The video has no assetPath, so we cannot update resumepoints
        if uuid is None:
            return True

        if position is not None and position >= total - 30:
            watch_later = False

        self.refresh(ttl=0)
        if watch_later is not None and position is None and total is None and watch_later is self.is_watchlater(
                uuid):
            # watchLater status is not changed, nothing to do
            return True

        if watch_later is None and position == self.get_position(
                uuid) and total == self.get_total(uuid):
            # resumepoint is not changed, nothing to do
            return True

        # Collect header info for POST Request
        from tokenresolver import TokenResolver
        xvrttoken = TokenResolver().get_xvrttoken(token_variant='user')
        if xvrttoken is None:
            log_error('Failed to get usertoken from VRT NU')
            notification(message=localize(30975) + title)
            return False

        headers = {
            'authorization': 'Bearer ' + xvrttoken,
            'content-type': 'application/json',
            'Referer': 'https://www.vrt.be' + url,
        }

        if uuid in self._resumepoints:
            # Update existing resumepoint values
            payload = self._resumepoints[uuid]['value']
            payload['url'] = url
        else:
            # Create new resumepoint values
            payload = dict(position=0, total=100, url=url, watchLater=False)

        if position is not None:
            payload['position'] = position

        if total is not None:
            payload['total'] = total

        removes = []
        if position is not None or total is not None:
            removes.append('continue-*.json')

        if watch_later is not None:
            # Add watchLater status to payload
            payload['watchLater'] = watch_later
            removes.append('watchlater-*.json')

        from json import dumps
        data = dumps(payload).encode()
        log(2,
            'URL post: https://video-user-data.vrt.be/resume_points/{uuid}',
            uuid=uuid)
        log(2, 'URL post data:: {data}', data=data)
        try:
            req = Request('https://video-user-data.vrt.be/resume_points/%s' %
                          uuid,
                          data=data,
                          headers=headers)
            urlopen(req)
        except HTTPError as exc:
            log_error('Failed to (un)watch episode at VRT NU ({error})',
                      error=exc)
            notification(message=localize(30977))
            return False

        # NOTE: Updates to resumepoints take a longer time to take effect, so we keep our own cache and use it
        self._resumepoints[uuid] = dict(value=payload)
        update_cache('resume_points.json', self._resumepoints)
        invalidate_caches(*removes)
        return True
示例#10
0
    def get_stream(self, video, roaming=False, api_data=None):
        """Main streamservice function"""
        if not api_data:
            api_data = self._get_api_data(video)

        stream_json = self._get_stream_json(api_data, roaming)

        if not stream_json:

            # Roaming token failed
            if roaming:
                message = localize(
                    30964
                )  # Geoblock error: Cannot be played, need Belgian phone number validation
                return self._handle_stream_api_error(message)
            # X-VRT-Token failed
            message = localize(
                30963)  # You need a VRT NU account to play this stream.
            return self._handle_stream_api_error(message)

        if 'targetUrls' in stream_json:

            # DRM support for ketnet junior/uplynk streaming service
            uplynk = 'uplynk.com' in stream_json.get('targetUrls')[0].get(
                'url')

            vudrm_token = stream_json.get('drm')
            vualto_license_url = stream_json.get(
                'vualto_license_url') or self._get_vualto_license_url()
            drm_stream = (vudrm_token or uplynk)

            # Select streaming protocol
            if not drm_stream and has_inputstream_adaptive():
                protocol = 'mpeg_dash'
            elif drm_stream and self._can_play_drm:
                protocol = 'mpeg_dash'
            elif vudrm_token:
                protocol = 'hls_aes'
            else:
                protocol = 'hls'

            # Get stream manifest url
            manifest_url = next((stream.get('url')
                                 for stream in stream_json.get('targetUrls')
                                 if stream.get('type') == protocol), None)

            # Failed to get compatible manifest url, ask user to toggle "Use Widevine DRM"
            if manifest_url is None:
                available_protocols = [
                    stream.get('type')
                    for stream in stream_json.get('targetUrls')
                ]
                if protocol not in available_protocols:
                    error_json = {
                        'message':
                        '%s is not available for this stream, please try toggling the "Use Widevine DRM" setting'
                        % protocol
                    }
                    message = localize(
                        30989)  # Failed to load a compatible stream
                    return self._handle_stream_api_error(message, error_json)
            else:

                # External virtual subclip, live-to-VOD from past 24 hours archived livestream (airdate feature)
                if video.get('start_date') and video.get('end_date'):
                    manifest_url += '?t=' + video.get(
                        'start_date') + '-' + video.get('end_date')

                # Fix virtual subclip
                from datetime import timedelta
                duration = timedelta(
                    milliseconds=stream_json.get('duration', 0))
                manifest_url = self._fix_virtualsubclip(manifest_url, duration)

                # Prepare stream for Kodi player
                if protocol == 'mpeg_dash' and drm_stream:
                    log(2, 'Protocol: mpeg_dash drm')
                    if vudrm_token:
                        encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'.format(
                            vudrm_token)
                        license_key = self._get_license_key(
                            key_url=vualto_license_url,
                            key_type='D',
                            key_value=encryption_json,
                            key_headers={
                                'Content-Type': 'text/plain;charset=UTF-8'
                            })
                    else:
                        license_key = self._get_license_key(
                            key_url=self._UPLYNK_LICENSE_URL, key_type='R')

                    stream = StreamURLS(manifest_url,
                                        license_key=license_key,
                                        use_inputstream_adaptive=True)
                elif protocol == 'mpeg_dash':
                    log(2, 'Protocol: mpeg_dash')
                    stream = StreamURLS(manifest_url,
                                        use_inputstream_adaptive=True)
                else:
                    log(2, 'Protocol: {protocol}', protocol=protocol)
                    # Fix 720p quality for HLS livestreams
                    manifest_url = manifest_url.replace(
                        '.m3u8?', '.m3u8?hd&'
                    ) if '.m3u8?' in manifest_url else manifest_url + '?hd'
                    # Play HLS directly in Kodi Player on Kodi 17
                    if kodi_version_major(
                    ) < 18 or not has_inputstream_adaptive():
                        stream = self._select_hls_substreams(
                            manifest_url, protocol)
                    else:
                        stream = StreamURLS(manifest_url,
                                            use_inputstream_adaptive=True)
                return stream

        # VRT Geoblock: failed to get stream, now try again with roaming enabled
        if stream_json.get('code') in self._GEOBLOCK_ERROR_CODES:
            log_error('VRT Geoblock: {msg}', msg=stream_json.get('message'))
            if not roaming:
                return self.get_stream(video, roaming=True, api_data=api_data)

            if stream_json.get('code') == self._INVALID_LOCATION:
                message = localize(
                    30965
                )  # Geoblock error: Blocked on your geographical location based on your IP address
                return self._handle_stream_api_error(message, stream_json)

            if stream_json.get('code') == self._BELGIUM_ONLY:
                message = localize(
                    30973
                )  # Geoblock error: This program can only be played from EU
                return self._handle_stream_api_error(message, stream_json)

            message = localize(
                30964
            )  # Geoblock error: Cannot be played, need Belgian phone number validation
            return self._handle_stream_api_error(message, stream_json)
        if stream_json.get('code') == 'VIDEO_NOT_FOUND':
            # Refresh listing
            invalidate_caches('*.json')
            container_reload()
            message = localize(30987)  # No stream found
            return self._handle_stream_api_error(message, stream_json)
        if stream_json.get('code') == 'ERROR_AGE_RESTRICTED':
            message = localize(
                30990
            )  # Cannot be played, VRT NU account not allowed to access 12+ content
            return self._handle_stream_api_error(message, stream_json)

        # Failed to get stream, handle error
        message = localize(30954)  # Whoops something went wrong
        return self._handle_stream_api_error(message, stream_json)
示例#11
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