ytdl-org/youtube-dl · error · ExtractorError

%s returned error: %s

Error message

%s returned error: %s

What it means

Raised by the Ustream extractor when the api.ustream.tv/videos/<id>.json response contains a non-empty top-level 'error' key; the API's own message is passed through. It signals the API rejected the request (video deleted, private, not found) after the HTTP layer succeeded. expected=True means the site itself reported the failure.

Source

Thrown at youtube_dl/extractor/ustream.py:191

            video_id = m.group('id')
            desktop_url = 'http://www.ustream.tv/recorded/' + video_id
            return self.url_result(desktop_url, 'Ustream')
        if m.group('type') == 'embed':
            video_id = m.group('id')
            webpage = self._download_webpage(url, video_id)
            content_video_ids = self._parse_json(self._search_regex(
                r'ustream\.vars\.offAirContentVideoIds=([^;]+);', webpage,
                'content video IDs'), video_id)
            return self.playlist_result(
                map(lambda u: self.url_result('http://www.ustream.tv/recorded/' + u, 'Ustream'), content_video_ids),
                video_id)

        params = self._download_json(
            'https://api.ustream.tv/videos/%s.json' % video_id, video_id)

        error = params.get('error')
        if error:
            raise ExtractorError(
                '%s returned error: %s' % (self.IE_NAME, error), expected=True)

        video = params['video']

        title = video['title']
        filesize = float_or_none(video.get('file_size'))

        formats = [{
            'id': video_id,
            'url': video_url,
            'ext': format_id,
            'filesize': filesize,
        } for format_id, video_url in video['media_urls'].items() if video_url]

        if not formats:
            hls_streams = self._get_streams(url, video_id, app_id_ver=(11, 2))
            if hls_streams:
                # m3u8_native leads to intermittent ContentTooShortError

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Open the ustream.tv/recorded/<id> URL in a browser to confirm the recording still exists.
  2. If the page works but the API fails, the API contract changed — check the response body of api.ustream.tv/videos/<id>.json and update the extractor (endpoint, auth, or error shape).
  3. For channel URLs, verify you are hitting the playlist path (offAirContentVideoIds) rather than the single-video API branch.
  4. Catch and report the API's error text to the user; retrying will not help.
Defensive patterns

Strategy: try-catch

Validate before calling

import json, urllib.request
resp = json.load(urllib.request.urlopen('https://api.ustream.tv/videos/%s.json' % video_id))
if resp.get('error'):
    skip(video_id, 'ustream api: %s' % resp['error'])

Try / catch

try:
    ydl.extract_info(url)
except ExtractorError as e:
    if e.expected and 'returned error:' in str(e):
        log_skip(url, str(e))  # API's own rejection — permanent
    else:
        raise

Prevention

When it happens

Trigger: Extracting a recorded Ustream URL whose id the API answers with {"error": "..."} — deleted recordings, private channels, or ids that only exist as channel pages handled earlier by the offAirContentVideoIds branch.

Common situations: IBM closed/migrated Ustream content after the IBM Cloud Video acquisition; old links in playlists now resolve to removed recordings; geo- or rights-restricted streams.

Related errors


AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14). Data as JSON: /api/errors/525863528793c51f. Report an issue: GitHub.