{"record":{"id":"014b411260f00b70","repo":"yt-dlp/yt-dlp","slug":"traverse-obj-smil-f-ns-ref-abstract-any","errorCode":null,"errorMessage":"traverse_obj(smil, (f'{ns}ref/@abstract', ..., any))","messagePattern":"traverse_obj\\(smil, \\(f'(.+?)ref/@abstract', \\.\\.\\., any\\)\\)","errorType":"exception","errorClass":"ExtractorError","httpStatus":null,"severity":"error","filePath":"yt_dlp/extractor/nbc.py","lineNumber":60,"sourceCode":"\n    def _download_nbcu_smil_and_extract_m3u8_url(self, tp_path, video_id, query):\n        smil = self._download_xml(\n            f'https://link.theplatform.com/s/{tp_path}', video_id,\n            'Downloading SMIL manifest', 'Failed to download SMIL manifest', query={\n                **query,\n                'format': 'SMIL',  # XXX: Do not confuse \"format\" with \"formats\"\n                'manifest': 'm3u',\n                'switch': 'HLSServiceSecure',  # Or else we get broken mp4 http URLs instead of HLS\n            }, headers=self.geo_verification_headers())\n\n        ns = f'//{{{default_ns}}}'\n        if url := traverse_obj(smil, (f'{ns}video/@src', lambda _, v: determine_ext(v) == 'm3u8', any)):\n            return url\n\n        exc = traverse_obj(smil, (f'{ns}param', lambda _, v: v.get('name') == 'exception', '@value', any))\n        if exc == 'GeoLocationBlocked':\n            self.raise_geo_restricted(countries=self._GEO_COUNTRIES)\n        raise ExtractorError(traverse_obj(smil, (f'{ns}ref/@abstract', ..., any)), expected=exc == 'Expired')\n\n    def _extract_nbcu_formats_and_subtitles(self, tp_path, video_id, query):\n        # formats='mpeg4' will return either a working m3u8 URL or an m3u8 template for non-DRM HLS\n        # formats='m3u+none,mpeg4' may return DRM HLS but w/the \"folders\" needed for non-DRM template\n        query['formats'] = 'm3u+none,mpeg4'\n        orig_m3u8_url = m3u8_url = self._download_nbcu_smil_and_extract_m3u8_url(tp_path, video_id, query)\n\n        if mobj := re.fullmatch(self._M3U8_RE, m3u8_url):\n            query['formats'] = 'mpeg4'\n            m3u8_tmpl = self._download_nbcu_smil_and_extract_m3u8_url(tp_path, video_id, query)\n            # Example: https://vod-lf-oneapp-prd.akamaized.net/prod/video/{folders}master_hls.m3u8\n            if '{folders}' in m3u8_tmpl:\n                self.write_debug('Found m3u8 URL template, formatting URL path')\n            m3u8_url = m3u8_tmpl.format(folders=mobj.group('folders'))\n\n        if '/mpeg_cenc' in m3u8_url or '/mpeg_cbcs' in m3u8_url:\n            self.report_drm(video_id)\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/yt-dlp/yt-dlp/blob/81ecd58b1394793e6da9998cc19fdb45657f1685/yt_dlp/extractor/nbc.py#L42-L78","documentation":"Thrown by NBCUniversalBaseIE._download_nbcu_smil_and_extract_m3u8_url (yt_dlp/extractor/nbc.py:60) when thePlatform SMIL manifest (link.theplatform.com/s/<tp_path>?...&format=SMIL&manifest=m3u&switch=HLSServiceSecure) contains no m3u8 video/@src. The exception MESSAGE is read out of the SMIL document itself - the ref element's @abstract attribute - so it carries thePlatform's own fault text (expiry notice, rights error, etc.). It is flagged expected only when the SMIL's exception param equals 'Expired'.","triggerScenarios":"The SMIL GET returns a fault document instead of a playable manifest: expired theplatform auth token (exception 'Expired'), deleted/expired media, DRM-only asset with no HLS rendition, or a geo fault (GeoLocationBlocked is converted to raise_geo_restricted just above this line).","commonSituations":"Reusing stale mxplayer/theplatform tokenized URLs copied from old pages or caches; NBC/NBC Sports content removed after the broadcast window; non-US IPs receiving geo-fault SMILs; premium DRM streams.","solutions":["Read the text in the error message - it is the provider's own reason (expired, rights removed, geo)","Update yt-dlp nightly; theplatform token handling is patched frequently","If it is geo-related, retry from a US IP: --proxy (and see --geo-verification-proxy)","Fetch the video page fresh so yt-dlp generates current theplatform tokens instead of reusing an old link","If the asset is DRM-only there is nothing yt-dlp can download"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"from yt_dlp.utils import ExtractorError, GeoRestrictedError\ntry:\n    info = ydl.extract_info(url, download=False)\nexcept GeoRestrictedError:\n    log.info('geo-blocked; retry via US proxy')\nexcept ExtractorError as e:\n    if e.expected and 'expired' in str(e).lower():\n        refresh_page_tokens(url)  # re-fetch the NBC page for new theplatform tokens\n    else:\n        raise  # message text comes from the SMIL @abstract: read it","preventionTips":["Always fetch the video page fresh so theplatform tokens are current; never reuse tokenized URLs from old logs","Catch GeoRestrictedError separately from ExtractorError - nbc.py converts GeoLocationBlocked SMILs into it","Treat expected=True 'Expired' errors as permanent for that URL, not retryable"],"tags":["yt-dlp","extractor","nbc","smil","theplatform","expired-token"],"backgroundTag":"expired-media-token","analyzedSha":"81ecd58b1394793e6da9998cc19fdb45657f1685","analyzedAt":"2026-08-22T12:21:25.439Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}