ytdl-org/youtube-dl · error · ExtractorError
%s said: %s
Error message
%s said: %s
What it means
Raised by the Akamai Adaptive Media Player feed parser when the downloaded JSON has no channel.item element. It then formats '%s said: %s' with feed['error'], surfacing the API's own error message. Note the double failure mode: if the feed lacks both 'item' and 'error', this line itself raises KeyError instead.
Source
Thrown at youtube_dl/extractor/amp.py:24
determine_ext,
ExtractorError,
int_or_none,
mimetype2ext,
parse_iso8601,
unified_timestamp,
url_or_none,
)
class AMPIE(InfoExtractor):
# parse Akamai Adaptive Media Player feed
def _extract_feed_info(self, url):
feed = self._download_json(
url, None, 'Downloading Akamai AMP feed',
'Unable to download Akamai AMP feed')
item = feed.get('channel', {}).get('item')
if not item:
raise ExtractorError('%s said: %s' % (self.IE_NAME, feed['error']))
video_id = item['guid']
def get_media_node(name, default=None):
media_name = 'media-%s' % name
media_group = item.get('media-group') or item
return media_group.get(media_name) or item.get(media_name) or item.get(name, default)
thumbnails = []
media_thumbnail = get_media_node('thumbnail')
if media_thumbnail:
if isinstance(media_thumbnail, dict):
media_thumbnail = [media_thumbnail]
for thumbnail_data in media_thumbnail:
thumbnail = thumbnail_data.get('@attributes', {})
thumbnail_url = url_or_none(thumbnail.get('url'))
if not thumbnail_url:
continueView on GitHub (pinned to 956b8c5855)
Solutions
- Read the embedded feed['error'] text — it is the provider's authoritative reason (expired, geo-blocked, not found)
- Regenerate the embed/feed URL from the current page, since feed URLs often embed short-lived tokens
- If you hit a KeyError on 'error', the feed returned an error shape the extractor does not model — report it upstream
Defensive patterns
Strategy: validation
Validate before calling
# Pre-fetch the AMP feed and check for item/error keys explicitly
import requests
feed = requests.get(feed_url).json()
if not feed.get('channel', {}).get('item'):
err = feed.get('error', '<feed returned no item and no error key>')
handle_amp_error(feed_url, err) # avoids both ExtractorError and the KeyError path Type guard
def is_amp_feed_playable(feed):
"""AMP feed is playable only if channel.item exists."""
return isinstance(feed, dict) and isinstance(feed.get('channel'), dict) and bool(feed['channel'].get('item')) Try / catch
try:
ydl.extract_info(url)
except (ExtractorError, KeyError) as e:
# KeyError('error') means the feed had neither item nor error — same root cause
log_amp_feed_rejected(url, str(e)) Prevention
- Inspect the raw feed JSON yourself before extraction to read the provider's error text
- Re-derive feed/embed URLs from the current page — they often embed expiring tokens
- Handle KeyError alongside ExtractorError: feeds without 'error' crash the error branch itself
When it happens
Trigger: GET of the AMP feed URL returns a JSON error object (no channel.item, but an 'error' key); expired or revoked media IDs; feed endpoints that return {'error': ...} for geo- or auth-blocked content.
Common situations: Embed URLs for removed videos; expired token in the feed URL; API base returning errors for unsupported regions; feed with neither item nor error triggering KeyError 'error'.
Related errors
- %s said: %s
- Show not found in A&E feed (too new?)
- details (dynamic from authorize XML response, xml_text(autho
- Video %s has been deleted
- Video %s does not exist
AI-assisted analysis of ytdl-org/youtube-dl@956b8c5855 (2026-08-14).
Data as JSON: /api/errors/636edf85d0038385.
Report an issue: GitHub.