yt-dlp/yt-dlp · error · ExtractorError

Unable to download JS dependency (crypto-js/md5)

Error message

Unable to download JS dependency (crypto-js/md5)

What it means

Douyu signing dependency failure. Douyu stream URLs require an MD5-based signature computed by executing crypto-js 3.1.2 inside PhantomJS; DouyuBaseIE first downloads the md5 rollup from cdnjs.cloudflare.com or unpkg.com (both fatal=False) and caches it. If both downloads fail and no cache entry with min_ver 2024.07.04 exists, this unexpected error is raised. The bootcdn mirror was deliberately removed after the polyfill supply-chain attack.

Source

Thrown at yt_dlp/extractor/douyutv.py:36

    url_or_none,
    urlencode_postdata,
    urljoin,
)


class DouyuBaseIE(InfoExtractor):
    def _download_cryptojs_md5(self, video_id):
        for url in [
            # XXX: Do NOT use cdn.bootcdn.net; ref: https://sansec.io/research/polyfill-supply-chain-attack
            'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js',
            'https://unpkg.com/cryptojslib@3.1.2/rollups/md5.js',
        ]:
            js_code = self._download_webpage(
                url, video_id, note='Downloading signing dependency', fatal=False)
            if js_code:
                self.cache.store('douyu', 'crypto-js-md5', js_code)
                return js_code
        raise ExtractorError('Unable to download JS dependency (crypto-js/md5)')

    def _get_cryptojs_md5(self, video_id):
        return self.cache.load(
            'douyu', 'crypto-js-md5', min_ver='2024.07.04') or self._download_cryptojs_md5(video_id)

    def _calc_sign(self, sign_func, video_id, a):
        b = uuid.uuid4().hex
        c = round(time.time())
        js_script = f'{self._get_cryptojs_md5(video_id)};{sign_func};console.log(ub98484234("{a}","{b}","{c}"))'
        phantom = PhantomJSwrapper(self)
        result = phantom.execute(js_script, video_id,
                                 note='Executing JS signing script').strip()
        return {i: v[0] for i, v in urllib.parse.parse_qs(result).items()}

    def _search_js_sign_func(self, webpage, fatal=True):
        # The greedy look-behind ensures last possible script tag is matched
        return self._search_regex(
            r'(?:<script.*)?<script[^>]*>(.*?ub98484234.*?)</script>', webpage, 'JS sign func', fatal=fatal)

View on GitHub (pinned to 81ecd58b13)

Solutions

  1. Check reachability of cdnjs.cloudflare.com and unpkg.com (curl) and fix proxy/DNS; pass --proxy if yt-dlp must route through one.
  2. Update yt-dlp - the CDN list has changed before and may change again.
  3. Run once from an unrestricted network to warm the 'douyu/crypto-js-md5' cache; later runs reuse it.
  4. Note PhantomJS is required for the next step anyway - run yt-dlp --update-to nightly / check requirements so JS execution works.
Defensive patterns

Strategy: retry

Validate before calling

import urllib.request


def cdn_reachable(url: str = 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js') -> bool:
    try:
        urllib.request.urlopen(url, timeout=10)
        return True
    except OSError:
        return False

Try / catch

from yt_dlp.utils import ExtractorError

for attempt in range(3):
    try:
        info = ydl.extract_info(url, download=False)
        break
    except ExtractorError as e:
        if 'JS dependency' not in str(e) or attempt == 2:
            raise
        time.sleep(2 ** attempt)

Prevention

When it happens

Trigger: _download_webpage of both CDN URLs returns empty: cdnjs.cloudflare.com/unpkg.com unreachable (GFW, corporate proxy, DNS filtering), offline machine, or TLS interception; first run has no cached copy to fall back on.

Common situations: Running yt-dlp in mainland China where those CDNs are blocked; locked-down CI containers with no internet beyond douyu.com; air-gapped machines with a pre-min_ver cache.

Related errors


AI-assisted analysis of yt-dlp/yt-dlp@81ecd58b13 (2026-08-22). Data as JSON: /api/errors/ca588e7b6c97c6ac. Report an issue: GitHub.