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:
                    continue

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Read the embedded feed['error'] text — it is the provider's authoritative reason (expired, geo-blocked, not found)
  2. Regenerate the embed/feed URL from the current page, since feed URLs often embed short-lived tokens
  3. 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

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


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