ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

SRGSSRIE._get_media_data reads the resolved chapter's 'blockReason' and maps it through the extractor's _ERRORS table. GEOBLOCK routes to raise_geo_restricted with the allowed countries; any other known reason is raised as '<SRF/RTS/...> said: <mapped message>' with expected=True. It is the platform's explicit content-blocking signal (end of availability, age rating, legal restrictions).

Source

Thrown at youtube_dl/extractor/srgssr.py:79

    def _get_media_data(self, bu, media_type, media_id):
        query = {'onlyChapters': True} if media_type == 'video' else {}
        full_media_data = self._download_json(
            'https://il.srgssr.ch/integrationlayer/2.0/%s/mediaComposition/%s/%s.json'
            % (bu, media_type, media_id),
            media_id, query=query)['chapterList']
        try:
            media_data = next(
                x for x in full_media_data if x.get('id') == media_id)
        except StopIteration:
            raise ExtractorError('No media information found')

        block_reason = media_data.get('blockReason')
        if block_reason and block_reason in self._ERRORS:
            message = self._ERRORS[block_reason]
            if block_reason == 'GEOBLOCK':
                self.raise_geo_restricted(
                    msg=message, countries=self._GEO_COUNTRIES)
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, message), expected=True)

        return media_data

    def _real_extract(self, url):
        bu, media_type, media_id = re.match(self._VALID_URL, url).groups()
        media_data = self._get_media_data(bu, media_type, media_id)
        title = media_data['title']

        formats = []
        q = qualities(['SD', 'HD'])
        for source in (media_data.get('resourceList') or []):
            format_url = source.get('url')
            if not format_url:
                continue
            protocol = source.get('protocol')
            quality = source.get('quality')
            format_id = []

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Read the mapped message - 'end of availability' means the content expired permanently and cannot be fetched.
  2. If GEOBLOCK, use a CH/LI VPN or proxy; the allowed countries list is embedded in the extractor.
  3. Look for the same program on the broadcaster's official YouTube channel after the window.
  4. Update yt-dlp if blockReason keys were added/renamed upstream.
Defensive patterns

Strategy: try-catch

Validate before calling

chapter = get_media_data(bu, media_type, media_id)
if chapter.get('blockReason'):
    reason = chapter['blockReason']
    if reason == 'GEOBLOCK':
        use_ch_proxy()
    else:
        skip(media_id, reason='blocked: ' + reason)

Type guard

def chapter_is_blocked(chapter: dict) -> bool:
    return bool(chapter.get('blockReason'))

Try / catch

try:
    extract(url)
except GeoRestrictedError:
    retry_via_ch_proxy()
except ExtractorError as e:
    if e.expected and 'said:' in str(e):
        drop_expired(media_id, str(e))
    else:
        raise

Prevention

When it happens

Trigger: A mediaComposition chapter carrying blockReason equal to one of the extractor's known keys (e.g. 'ENDEAVOUR_END_AVAILABILITY' style 'end of availability' codes, geoblock, legal block). The chapter exists (so the StopIteration path passed) but the platform refuses playback.

Common situations: Swiss content expiring after its broadcast window (SRF/RTS take downs after a fixed availability period); viewers outside Switzerland/Liechtenstein hitting GEOBLOCK; sports content with shorter licenses than regular shows.

Related errors


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