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 ContentTooShortErrorView on GitHub (pinned to 956b8c5855)
Solutions
- Open the ustream.tv/recorded/<id> URL in a browser to confirm the recording still exists.
- 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).
- For channel URLs, verify you are hitting the playlist path (offAirContentVideoIds) rather than the single-video API branch.
- 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
- Query the videos/<id>.json API first to check for an error field.
- Expect Ustream content to disappear after the IBM shutdown; prune dead links from archives.
- Do not retry expected API rejections.
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
- %s returned error: %s
- Video %s is no longer available
- Vidme said: Sorry, this video has been deleted.
- % said: %s
- %s said: %s
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/525863528793c51f.
Report an issue: GitHub.