{"record":{"id":"636edf85d0038385","repo":"ytdl-org/youtube-dl","slug":"s-said-s-636edf","errorCode":null,"errorMessage":"%s said: %s","messagePattern":"%s said: %s","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"youtube_dl/extractor/amp.py","lineNumber":24,"sourceCode":"    determine_ext,\n    ExtractorError,\n    int_or_none,\n    mimetype2ext,\n    parse_iso8601,\n    unified_timestamp,\n    url_or_none,\n)\n\n\nclass AMPIE(InfoExtractor):\n    # parse Akamai Adaptive Media Player feed\n    def _extract_feed_info(self, url):\n        feed = self._download_json(\n            url, None, 'Downloading Akamai AMP feed',\n            'Unable to download Akamai AMP feed')\n        item = feed.get('channel', {}).get('item')\n        if not item:\n            raise ExtractorError('%s said: %s' % (self.IE_NAME, feed['error']))\n\n        video_id = item['guid']\n\n        def get_media_node(name, default=None):\n            media_name = 'media-%s' % name\n            media_group = item.get('media-group') or item\n            return media_group.get(media_name) or item.get(media_name) or item.get(name, default)\n\n        thumbnails = []\n        media_thumbnail = get_media_node('thumbnail')\n        if media_thumbnail:\n            if isinstance(media_thumbnail, dict):\n                media_thumbnail = [media_thumbnail]\n            for thumbnail_data in media_thumbnail:\n                thumbnail = thumbnail_data.get('@attributes', {})\n                thumbnail_url = url_or_none(thumbnail.get('url'))\n                if not thumbnail_url:\n                    continue","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/ytdl-org/youtube-dl/blob/956b8c585591b401a543e409accb163eeaaa1193/youtube_dl/extractor/amp.py#L6-L42","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Pre-fetch the AMP feed and check for item/error keys explicitly\nimport requests\nfeed = requests.get(feed_url).json()\nif not feed.get('channel', {}).get('item'):\n    err = feed.get('error', '<feed returned no item and no error key>')\n    handle_amp_error(feed_url, err)  # avoids both ExtractorError and the KeyError path","typeGuard":"def is_amp_feed_playable(feed):\n    \"\"\"AMP feed is playable only if channel.item exists.\"\"\"\n    return isinstance(feed, dict) and isinstance(feed.get('channel'), dict) and bool(feed['channel'].get('item'))","tryCatchPattern":"try:\n    ydl.extract_info(url)\nexcept (ExtractorError, KeyError) as e:\n    # KeyError('error') means the feed had neither item nor error — same root cause\n    log_amp_feed_rejected(url, str(e))","preventionTips":["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"],"tags":["akamai-amp","feed-api","server-error","content-missing"],"backgroundTag":null,"analyzedSha":"956b8c585591b401a543e409accb163eeaaa1193","analyzedAt":"2026-08-14T18:59:47.863Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}