ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

Thrown by the LiTV extractor when the vod/getMainUrl API call succeeds HTTP-wise but the returned JSON lacks a 'fullpath' key and instead carries an 'errorMessage'. It is marked expected=True, meaning it represents a normal, user-facing service refusal (e.g. expired asset, member-only content) rather than a bug in youtube-dl. The '%s said: %s' pattern prefixes the extractor name (LiTV) with the raw service message so the user sees exactly what the provider replied.

Source

Thrown at youtube_dl/extractor/litv.py:124

            r'uiHlsUrl\s*=\s*testBackendData\(([^;]+)\);',
            webpage, 'video data', default='{}'), video_id)
        if not video_data:
            payload = {
                'assetId': program_info['assetId'],
                'watchDevices': program_info['watchDevices'],
                'contentType': program_info['contentType'],
            }
            video_data = self._download_json(
                'https://www.litv.tv/vod/getMainUrl', video_id,
                data=json.dumps(payload).encode('utf-8'),
                headers={'Content-Type': 'application/json'})

        if not video_data.get('fullpath'):
            error_msg = video_data.get('errorMessage')
            if error_msg == 'vod.error.outsideregionerror':
                self.raise_geo_restricted('This video is available in Taiwan only')
            if error_msg:
                raise ExtractorError('%s said: %s' % (self.IE_NAME, error_msg), expected=True)
            raise ExtractorError('Unexpected result from %s' % self.IE_NAME)

        formats = self._extract_m3u8_formats(
            video_data['fullpath'], video_id, ext='mp4',
            entry_protocol='m3u8_native', m3u8_id='hls')
        for a_format in formats:
            # LiTV HLS segments doesn't like compressions
            a_format.setdefault('http_headers', {})['Youtubedl-no-compression'] = True

        title = program_info['title'] + program_info.get('secondaryMark', '')
        description = program_info.get('description')
        thumbnail = program_info.get('imageFile')
        categories = [item['name'] for item in program_info.get('category', [])]
        episode = int_or_none(program_info.get('episode'))

        return {
            'id': video_id,
            'formats': formats,

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Read the embedded service message; it is LiTV's own text and names the exact refusal reason (expired, member-only, removed).
  2. If the message indicates region blocking, supply a Taiwan IP/proxy via --proxy or --geo-verification-proxy instead of retrying directly.
  3. If content is member-only, log in with the appropriate account cookies (--add-header 'Cookie: ...').
  4. If the message looks like a schema change (unrecognized text), report the video URL to the youtube-dl issue tracker so the LiTV extractor can be updated.
Defensive patterns

Strategy: try-catch

Type guard

def is_litv_error_payload(video_data):
    return isinstance(video_data, dict) and not video_data.get('fullpath') and bool(video_data.get('errorMessage'))

Try / catch

try:
    info = ydl.extract_info(url, download=False)
except ExtractorError as e:
    if 'LiTV said:' in str(e):
        reason = str(e).split('said:', 1)[1].strip()  # service's own message; act on it (geo, membership, expiry)
    else:
        raise

Prevention

When it happens

Trigger: POST to https://www.litv.tv/vod/getMainUrl with the contentType payload returns JSON without 'fullpath' but with a non-empty 'errorMessage'. Any errorMessage other than 'vod.error.outsideregionerror' (which is converted to a geo-restriction error just above) reaches this raise.

Common situations: Requesting a drama/episode that is no longer licensed, member-only content without a subscription cookie, or a video_id that LiTV no longer serves. Also hit when LiTV changes its API response shape so 'fullpath' is renamed, leaving only an error field.

Related errors


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