ytdl-org/youtube-dl · error · ExtractorError

This video is DRM protected.

Error message

This video is DRM protected.

What it means

Raised by YoutubeIE._real_extract when the streaming_data contains no usable formats and includes 'licenseInfos', meaning YouTube served DRM-licensed streams only. Expected=True: the video is published with DRM protection that youtube-dl cannot and will not circumvent. It is distinct from report_drm, which fires when formats exist but all carry has_drm.

Source

Thrown at youtube_dl/extractor/youtube.py:2783

                for f in self._extract_mpd_formats(
                        dash_manifest_url, video_id, fatal=False):
                    if process_manifest_format(
                            f, 'dash', None, f['format_id']):
                        f['filesize'] = traverse_obj(f, (
                            ('fragment_base_url', 'url'), T(lambda u: self._search_regex(
                                r'/clen/(\d+)', u, 'file size', default=None)),
                            T(int_or_none)), get_all=False)
                        formats.append(f)

        playable_formats = [f for f in formats if not f.get('has_drm')]
        if formats:
            if not playable_formats:
                # If there are no formats that definitely don't have DRM, all have DRM
                self.report_drm(video_id)
            formats[:] = playable_formats
        else:
            if streaming_data.get('licenseInfos'):
                raise ExtractorError(
                    'This video is DRM protected.', expected=True)
            pemr = try_get(
                playability_status,
                lambda x: x['errorScreen']['playerErrorMessageRenderer'],
                dict) or {}
            reason = get_text(pemr.get('reason')) or playability_status.get('reason') or ''
            subreason = pemr.get('subreason')
            if subreason:
                subreason = clean_html(get_text(subreason))
                if subreason.startswith('The uploader has not made this video available in your country'):
                    countries = microformat.get('availableCountries')
                    if not countries:
                        regions_allowed = search_meta('regionsAllowed')
                        countries = regions_allowed.split(',') if regions_allowed else None
                    self.raise_geo_restricted(
                        subreason, countries)
                reason += '\n' + subreason

View on GitHub (pinned to 956b8c5855)

Solutions

  1. Accept that DRM content is out of scope — no youtube-dl option will decrypt it.
  2. If you believe the video should be DRM-free, verify in a browser and check whether a non-DRM format appears when authenticated; otherwise use another source.
  3. For batch jobs, catch this expected error and skip such entries.
  4. Ensure you are not hitting this due to a partial player response (try with cookies); a normal video with licenseInfos absent would instead surface a playability reason.
Defensive patterns

Strategy: try-catch

Validate before calling

def is_drm_response(player_response: dict) -> bool:
    streaming_data = player_response.get('streamingData') or {}
    return bool(streaming_data.get('licenseInfos'))

Try / catch

try:
    info = ydl.extract_info(url)
except ExtractorError as e:
    if 'DRM protected' in str(e):
        skip_permanently(url)  # not retryable; no workaround exists
    raise

Prevention

When it happens

Trigger: Extracting a video whose player response has empty/no streaming formats but streaming_data['licenseInfos'] is non-empty — e.g. movies/TV purchases, DRM-protected channel content (often also requiring purchase).

Common situations: Paid rentals/purchases on YouTube Movies; labels/publishers delivering DRM-protected streams; users expecting youtube-dl to bypass DRM (it does not).

Related errors


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