ytdl-org/youtube-dl · error · ExtractorError

%s said: %s

Error message

%s said: %s

What it means

PornHdIE raises ExtractorError('%s said: %s' % (IE_NAME, message), expected=True) when the video page yields neither a 'sources' JSON object nor any parseable HTML5 <video> entries, and the page instead contains an element (div or p) with class 'no-video'. The message interpolated into the error is the text scraped from that no-video block, so the actual wording comes from the site (typically 'video has been removed' style notices).

Source

Thrown at youtube_dl/extractor/pornhd.py:74

        title = self._html_search_regex(
            [r'<span[^>]+class=["\']video-name["\'][^>]*>([^<]+)',
             r'<title>(.+?) - .*?[Pp]ornHD.*?</title>'], webpage, 'title')

        sources = self._parse_json(js_to_json(self._search_regex(
            r"(?s)sources'?\s*[:=]\s*(\{.+?\})",
            webpage, 'sources', default='{}')), video_id)

        info = {}
        if not sources:
            entries = self._parse_html5_media_entries(url, webpage, video_id)
            if entries:
                info = entries[0]

        if not sources and not info:
            message = self._html_search_regex(
                r'(?s)<(div|p)[^>]+class="no-video"[^>]*>(?P<value>.+?)</\1',
                webpage, 'error message', group='value')
            raise ExtractorError('%s said: %s' % (self.IE_NAME, message), expected=True)

        formats = []
        for format_id, video_url in sources.items():
            video_url = urljoin(url, video_url)
            if not video_url:
                continue
            height = int_or_none(self._search_regex(
                r'^(\d+)[pP]', format_id, 'height', default=None))
            formats.append({
                'url': video_url,
                'ext': determine_ext(video_url, 'mp4'),
                'format_id': format_id,
                'height': height,
            })
        if formats:
            info['formats'] = formats
        self._sort_formats(info['formats'])

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Open the URL in a browser to read the actual no-video message (removed, private, region-locked) and act accordingly.
  2. If region-blocked, route through an egress point where the video is available.
  3. Remove the dead entry from your queue/playlist and retry the remainder of the batch.
  4. Update youtube-dl — if the site changed its markup, a newer extractor revision may parse sources correctly again.
Defensive patterns

Strategy: try-catch

Try / catch

try:
    info = ydl.extract_info(url)
except ExtractorError as e:
    if 'PornHD said:' in str(e):
        mark_dead(url)  # content removed; drop from queue

Prevention

When it happens

Trigger: Extracting a pornhd.com video URL whose content was taken down or is region-blocked: sources JSON is empty/absent, _parse_html5_media_entries returns nothing, and the regex '(?s)<(div|p)[^>]+class="no-video"...' matches. If the no-video block itself is missing, _html_search_regex raises a different 'unable to extract error message' error instead.

Common situations: Dead/removed video links from old playlists; DMCA takedowns; geo-restrictions rendering the player empty; site template changes that rename the no-video class (then the inner regex fails first).

Related errors


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