ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

Raised by the TFO extractor when the TFO API responds with success == 0 and the failure is not the known geo-block code. The message embeds the extractor name and the API's own HTML-cleaned 'msg' field, so the actual reason comes from the server (e.g. expired video, removed product id). It is marked expected=True, meaning youtube-dl treats it as a content-availability error rather than an extractor bug.

Source

Thrown at youtube_dl/extractor/tfo.py:41

            'ext': 'mp4',
            'title': 'Video Game Hackathon',
            'description': 'md5:558afeba217c6c8d96c60e5421795c07',
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        self._request_webpage(HEADRequest('http://www.tfo.org/'), video_id)
        infos = self._download_json(
            'http://www.tfo.org/api/web/video/get_infos', video_id, data=json.dumps({
                'product_id': video_id,
            }).encode(), headers={
                'X-tfo-session': self._get_cookies('http://www.tfo.org/')['tfo-session'].value,
            })
        if infos.get('success') == 0:
            if infos.get('code') == 'ErrGeoBlocked':
                self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
            raise ExtractorError('%s said: %s' % (self.IE_NAME, clean_html(infos['msg'])), expected=True)
        video_data = infos['data']

        return {
            '_type': 'url_transparent',
            'id': video_id,
            'url': 'limelight:media:' + video_data['llid'],
            'title': video_data['title'],
            'description': video_data.get('description'),
            'series': video_data.get('collection'),
            'season_number': int_or_none(video_data.get('season')),
            'episode_number': int_or_none(video_data.get('episode')),
            'duration': int_or_none(video_data.get('duration')),
            'ie_key': 'LimelightMedia',
        }

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Open the same URL in a browser and confirm the video still plays on tfo.org; if it 404s, the video is gone and no tool-side fix exists
  2. Update to the latest youtube-dl / yt-dlp, where TFO extractor changes are tracked
  3. If the API returns ErrGeoBlocked but you still see this error, check that the initial HEADRequest to www.tfo.org succeeded and set the tfo-session cookie
  4. Use a Canadian exit IP (VPN) since TFO is geo-restricted to _GEO_COUNTRIES
Defensive patterns

Strategy: try-catch

Validate before calling

import urllib.request, json
req = urllib.request.Request('http://www.tfo.org/api/web/video/get_infos', data=json.dumps({'product_id': vid}).encode())
# a HEAD to www.tfo.org first is needed for the session cookie; if the API returns success:0 the extractor will fail

Try / catch

from youtube_dl.utils import ExtractorError
try:
    ydl.extract_info(url)
except ExtractorError as e:
    if e.expected and 'TFO said' in str(e):
        mark_unavailable(url, str(e))  # server-side refusal; do not retry
    else:
        raise

Prevention

When it happens

Trigger: Calling the extractor on a tfo.org video whose /api/web/video/get_infos POST returns success:0 with a code other than 'ErrGeoBlocked'; e.g. a product_id that was retired or a video whose Limelight media (llid) was deleted.

Common situations: User passes an old TFO URL found in a playlist or forum post; TFO changes its API response shape; the 'tfo-session' cookie is missing so the API rejects the request; the video is region-locked under a code not equal to 'ErrGeoBlocked'.

Related errors


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