ytdl-org/youtube-dl · error · ExtractorError

error

Error message

error

What it means

Raised by the xHamster extractor when the desktop page contains a <div id="videoClosed"> element. xHamster uses this element to state why a video is unavailable (removed, private, banned in region, disabled by uploader), and its text is forwarded as the error message.

Solutions

  1. Read the videoClosed div text forwarded in the error — it names the exact cause.
  2. For region bans, access via an permitted region or accept unavailability.
  3. For private/deleted videos, no download is possible without the uploader's grant.
Defensive patterns

Strategy: try-catch

Try / catch

try:
    info = ydl.extract_info(url)
except ExtractorError as e:
    if 'videoClosed' in str(e) or is_site_availability_error(e):
        classify_and_skip(url, str(e))
    else:
        raise

Prevention

When it happens

Trigger: Downloading an xHamster video whose page (after rewriting mobile URLs to desktop) matches r'<div[^>]+id=["\']videoClosed["\'][^>]*>(.+?)</div>' with non-empty content.

Common situations: Videos set to private or deleted by the uploader; content banned in the requester's country (some regions redirect to a closed notice); uploader accounts terminated.

Related errors


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

Appendix: source

Thrown at youtube_dl/extractor/xhamster.py:143

        'only_matching': True,
    }, {
        'url': 'https://xhvid.com/videos/lk-mm-xhc6wn6',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id') or mobj.group('id_2')
        display_id = mobj.group('display_id') or mobj.group('display_id_2')

        desktop_url = re.sub(r'^(https?://(?:.+?\.)?)m\.', r'\1', url)
        webpage, urlh = self._download_webpage_handle(desktop_url, video_id)

        error = self._html_search_regex(
            r'<div[^>]+id=["\']videoClosed["\'][^>]*>(.+?)</div>',
            webpage, 'error', default=None)
        if error:
            raise ExtractorError(error, expected=True)

        age_limit = self._rta_search(webpage)

        def get_height(s):
            return int_or_none(self._search_regex(
                r'^(\d+)[pP]', s, 'height', default=None))

        initials = self._parse_json(
            self._search_regex(
                (r'window\.initials\s*=\s*({.+?})\s*;\s*</script>',
                 r'window\.initials\s*=\s*({.+?})\s*;'), webpage, 'initials',
                default='{}'),
            video_id, fatal=False)
        if initials:
            video = initials['videoModel']
            title = video['title']
            formats = []
            format_urls = set()

View on GitHub (pinned to 956b8c5855)