# yt-dlp/yt-dlp — codebase unpack

> Canonical page: https://codevetter.com/unpack/yt-dlp

This is a deterministic Repo Unpack scan of yt-dlp/yt-dlp — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: c7fb478d21e9e59524befbe23f7801bb267fb880 (2026-09-16)
- Last upstream commit: [ie/youtube] Use Safari UA for web_embedded client (#17684)
- Generated by codevetter 1.12.0 on 2026-09-22
- 1229 files scanned · 11.4 MB

## Languages

- Python: 1126 files
- YAML: 32 files
- Markdown: 10 files
- JSON: 3 files
- Shell: 3 files
- TOML: 1 files

## Repository structure

- yt_dlp/ — 1045 files
- test/ — 81 files
- .github/ — 31 files
- devscripts/ — 30 files
- bundle/ — 23 files
- .NO_AI/ — 1 files

Probable source entrypoint: `yt_dlp/__init__.py` (inferred from the tree, not upstream docs)

## Source layout — yt_dlp/

- yt_dlp/__pyinstaller/ — 2 files
- yt_dlp/compat/ — 8 files
- yt_dlp/dependencies/ — 2 files
- yt_dlp/downloader/ — 17 files
- yt_dlp/extractor/ — 322 files
- yt_dlp/networking/ — 10 files
- yt_dlp/postprocessor/ — 10 files
- yt_dlp/utils/ — 10 files
- yt_dlp/__init__.py — 1 file
- yt_dlp/aes.py — 1 file
- yt_dlp/cache.py — 1 file
- yt_dlp/cookies.py — 1 file
- yt_dlp/globals.py — 1 file
- yt_dlp/options.py — 1 file

## Package

- pyproject.toml: yt-dlp — 53 declared dependencies

## Structural health signals

Average score 5.9/10 across 96 files; 94 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- c7fb478d21e9 (2026-09-16) [ie/youtube] Use Safari UA for web_embedded client (#17684)
- bbc809a1161d (2026-08-30) [utils] `devalue`: Improve binary type parsing (#16934)
- 9caaf4540149 (2026-08-30) [ie] Do not warn on intentional generic results (#16683)
- 2f3929ba1a79 (2026-08-29) [ie/applepodcasts] Fix token caching (#17567)
- f1df65d5d7a9 (2026-08-30) [utils]`download_range_func`: Update `__eq__` and `__repr__` to include `from_info` (#16393)

## Overview

yt-dlp is a Python 3.10+ command-line audio/video downloader (also usable as the `yt_dlp` library) that routes URLs through a registry of ~1750 site-specific InfoExtractor classes to produce metadata 'info dicts', then dispatches downloads to protocol-specific FileDownloaders and runs ffmpeg-based post-processors. It is a youtube-dl fork distinguished by zero required dependencies, a plugin system, pluggable network request handlers (urllib/requests/curl_cffi/websockets) with browser impersonation, and a built-in self-updater that pulls verified binaries from GitHub releases.

## System map

yt-dlp is a single-process Python CLI/package. Execution enters through `yt_dlp.main()` (console script or `python -m yt_dlp`), parses optparse options plus config files into a `ydl_opts` params dict, then constructs one `YoutubeDL` orchestrator that owns the extractor table, post-processor lists, cookiejar, cache, and the RequestDirector. The codebase is layered: `extractor/` produces info dicts, `networking/` abstracts HTTP/WebSocket transports, `downloader/` writes bytes to disk per protocol, and `postprocessor/` runs ffmpeg and other mutations. Shared mutable state lives in `globals.py` `Indirect` cells, which is also how plugins merge into the core registries. Build artifacts (zipapp, PyInstaller binaries, lazy_extractors.py) are produced by the Makefile and bundle/devscripts.
- CLI entrypoint chain: the `yt-dlp` console script maps to `yt_dlp:main`; `python -m yt_dlp` runs `__main__.py` which calls `yt_dlp.main()`; `main()` sets `IN_CLI` and calls `_real_main()`. — [pyproject.toml#L143-144](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L143-L144) [yt_dlp/__main__.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__main__.py) [yt_dlp/__init__.py#L1077-1080](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1077-L1080)
- `_real_main()` orchestrates the whole run: `setproctitle`, `parse_options`, plugin loading via `plugin_dirs`/`_load_all_plugins`, `Updater` self-update, then `ydl.download(all_urls)` or `download_with_info_file`, returning `_download_retcode`. — [yt_dlp/__init__.py#L964-1074](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L964-L1074)
- `YoutubeDL` (4554-line god object) is the central runtime boundary: its `params` dict is the configuration, and it owns `_ies` extractor instances, `_pps` post-processor lists keyed by POSTPROCESS_WHEN, the cookiejar, `Cache`, and the request director. — [yt_dlp/YoutubeDL.py#L204](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L204) [yt_dlp/YoutubeDL.py#L634-660](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L634-L660) [yt_dlp/utils/_utils.py#L2863](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/utils/_utils.py#L2863)
- The extractor registry is `yt_dlp/extractor/_extractors.py` importing ~1752 IE classes; `extractor/extractors.py` prefers a generated `lazy_extractors.py` (`_CLASS_LOOKUP`) and otherwise eagerly builds the lookup, ordering YouTube extractors first for match performance and `GenericIE` last as the catch-all. — [yt_dlp/extractor/_extractors.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/_extractors.py) [yt_dlp/extractor/extractors.py#L7-31](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L7-L31) [devscripts/make_lazy_extractors.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/make_lazy_extractors.py)
- All HTTP/WebSocket traffic goes through `RequestDirector.send()`, which preference-sorts registered `RequestHandler` subclasses in the module-level `_REQUEST_HANDLERS` dict (Urllib always; Requests, Websockets, CurlCffi imported defensively). — [yt_dlp/networking/common.py#L51-140](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L51-L140) [yt_dlp/networking/__init__.py#L19-38](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/__init__.py#L19-L38)
- Downloads are dispatched by protocol: `get_suitable_downloader`/`_get_suitable_downloader` consult `PROTOCOL_MAP` (HlsFD, DashSegmentsFD, F4mFD, RtmpFD, HttpFD, YoutubeLiveChatFD, etc.) and can delegate to external tools (aria2c, wget, curl, axel, httpie, ffmpeg) via `ExternalFD` subclasses. — [yt_dlp/downloader/__init__.py#L4-58](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/__init__.py#L4-L58) [yt_dlp/downloader/external.py#L37-38](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/external.py#L37-L38)
- Post-processors register into the `postprocessors` Indirect dict (all classes ending in 'PP'); FFmpeg work is executed by `real_run_ffmpeg` building an argv and calling `Popen.run`. — [yt_dlp/postprocessor/__init__.py#L55-67](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/__init__.py#L55-L67) [yt_dlp/postprocessor/ffmpeg.py#L326-364](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/ffmpeg.py#L326-L364)
- Cross-module shared state is held in `Indirect` cells in `globals.py`: `extractors`, `postprocessors`, `plugin_dirs` (default `['default']`), `plugin_ies`, `plugin_pps`, `supported_js_runtimes`, `supported_remote_components`, `IN_CLI`, `LAZY_EXTRACTORS`. — [yt_dlp/globals.py#L10-41](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L10-L41)
- The package has zero hard dependencies (`dependencies = []`); extras `default`, `curl-cffi`, `secretstorage`, `deno` add requests/urllib3/websockets/brotli/mutagen/pycryptodomex/certifi/yt-dlp-ejs, with exact-pinned `pin-*` groups for reproducible builds. — [pyproject.toml#L46-101](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L46-L101)
- On-disk state: `Cache` writes JSON envelopes (`yt-dlp_version` + data) under `cachedir` (default `$XDG_CACHE_HOME` or `~/.cache/yt-dlp`) keyed by section/key; the download archive is a text file of `extractor_key video_id` lines appended under `locked_file`. — [yt_dlp/cache.py#L17-46](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cache.py#L17-L46) [yt_dlp/YoutubeDL.py#L3855-3894](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3855-L3894)
- Self-update subsystem `Updater` targets three GitHub repos (stable/nightly/master), downloads the `_update_spec` and `SHA2-256SUMS` release assets, sha256-verifies the new binary, swaps `.new`/`.old` files, and re-executes via `Popen` in `restart()`. — [yt_dlp/update.py#L38-53](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L38-L53) [yt_dlp/update.py#L368-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L368-L500) [yt_dlp/update.py#L502-610](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L502-L610)
- Python >=3.10 is enforced twice: `requires-python` in pyproject and an `ImportError` raised at import time in `yt_dlp/__init__.py`. — [pyproject.toml#L18](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L18) [yt_dlp/__init__.py#L3-5](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L3-L5)
- Distribution shapes: hatchling wheel/sdist, a zipapp `yt-dlp` (shebang prepended to yt-dlp.zip) built by the Makefile, and PyInstaller bundles wired through the `pyinstaller40` hook-dirs entry point and `bundle/pyinstaller.py`. — [Makefile#L106-130](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L106-L130) [pyproject.toml#L146-147](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L146-L147) [bundle/pyinstaller.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/bundle/pyinstaller.py)

## Feature catalog

The feature surface is one large optparse CLI plus the `YoutubeDL` Python API. Options are organized into ~15 groups (General, Network, Geo, Selection, Download, Filesystem, Thumbnail, Verbosity, Workarounds, Video Format, Subtitles, Authentication, Post-Processing, SponsorBlock, Extractor). Major features: per-site extraction with playlist/search support, format selection DSL, output templates, download archive dedup, browser-cookie and netrc authentication, TLS/browser impersonation, ffmpeg post-processing incl. SponsorBlock, external downloaders, plugins, JS runtimes/remote components for YouTube challenge solving, self-update, and extensive listing/simulation/JSON dump modes.
- The CLI is defined in `create_parser()` using optparse OptionGroups: General, Network, Geo-restriction, Video Selection, Download, Filesystem, Thumbnail, Internet Shortcut, Verbosity/Simulation, Workarounds, Video Format, Subtitle, Authentication, Post-Processing, SponsorBlock, Extractor Options. — [yt_dlp/options.py#L247](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L247) [yt_dlp/options.py#L355-1999](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L355-L1999)
- Config files are auto-loaded: `parseOpts` searches user/system config dirs for `yt-dlp.conf`, `config`, `config.txt` and supports `--ignore-config`; URLs come from argv, `-a/--batch-file` (including '-' for stdin). — [yt_dlp/options.py#L43-80](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L43-L80) [yt_dlp/__init__.py#L83-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L83-L100)
- Format selection uses a DSL parsed by `build_format_selector` (e.g. 'b/bv', the test default) combined with `FormatSorter` sorting in `sort_formats`. — [yt_dlp/YoutubeDL.py#L2341](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2341) [yt_dlp/YoutubeDL.py#L2830-2835](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2830-L2835) [test/parameters.json](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/parameters.json)
- Output naming is a template engine (`outtmpl`) supporting `%(field)s` expansion, defaults per type, sanitization, and literal `%%` — implemented in `parse_outtmpl`/`prepare_outtmpl`/`evaluate_outtmpl`. — [yt_dlp/YoutubeDL.py#L1200-1261](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1200-L1261) [yt_dlp/YoutubeDL.py#L1267-1520](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1267-L1520)
- `--download-archive` deduplicates across runs: `_make_archive_id` builds `extractor_key video_id`, `in_download_archive` checks the in-memory set (plus `_old_archive_ids`), `record_download_archive` appends under `locked_file`. — [yt_dlp/YoutubeDL.py#L3855-3894](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3855-L3894)
- Authentication features: `--cookies-from-browser` supports brave/chrome/chromium/edge/opera/vivaldi/whale/firefox/safari with OS keyring decryption, `--cookies` Netscape files, and per-extractor username/password or netrc via `_NETRC_MACHINE`/`_perform_login`. — [yt_dlp/cookies.py#L49-50](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L49-L50) [yt_dlp/extractor/common.py#L589-667](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L589-L667)
- Browser impersonation (`--impersonate`, `--list-impersonate-targets`) maps targets like chrome/safari/firefox/edge/tor to the curl_cffi request handler, with a rendered availability table in `_real_main`. — [yt_dlp/__init__.py#L997-1032](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L997-L1032) [yt_dlp/networking/impersonate.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/impersonate.py) [yt_dlp/YoutubeDL.py#L4240-4280](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4240-L4280)
- Post-processing features include FFmpeg merge/extract-audio/remux/embed-subs/thumbnail & subtitle conversion/fixups/chapter splitting, SponsorBlock removal, metadata parsing, MoveFilesAfterDownload, and xattr writing. — [yt_dlp/postprocessor/__init__.py#L4-34](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/__init__.py#L4-L34) [yt_dlp/options.py#L1614-1902](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L1614-L1902)
- `--exec [WHEN:]CMD` runs user shell commands after chosen stages via `ExecPP`, which validates template expansion safety unless the `allow-unsafe-exec-expansion` compat opt is set. — [yt_dlp/postprocessor/exec.py#L12-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/exec.py#L12-L40) [yt_dlp/options.py#L1768-1783](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L1768-L1783)
- Self-update `-U/--update` supports channels stable/nightly/master (`UPDATE_SOURCES`) and `--update-to CHANNEL@TAG` for pinning. — [yt_dlp/update.py#L38-44](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L38-L44) [yt_dlp/options.py#L378](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L378)
- Listing/simulation outputs: `--list-extractors`, `--list-extractor-descriptions`, `-F/--list-formats` (render_formats_table), list-thumbnails/subtitles, `--simulate`, `--dump-json`/`--dump-single-json` via `sanitize_info`, and forced `--print`/`--print-to-file` machinery. — [yt_dlp/__init__.py#L103-132](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L103-L132) [yt_dlp/YoutubeDL.py#L3217-3286](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3217-L3286) [yt_dlp/YoutubeDL.py#L3982-4088](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3982-L4088)
- Geo-restriction bypass fakes `X-Forwarded-For` with a random IP from a country or CIDR block (`--geo-bypass-country`, `--geo-bypass-ip-block`), initialized per-extractor and retried once on GeoRestrictedError. — [yt_dlp/extractor/common.py#L673-755](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L673-L755) [yt_dlp/extractor/common.py#L790-803](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L790-L803)
- Plugin loading (`--plugin-dirs`, `--no-plugin-dirs`) imports `yt_dlp_plugins` namespace packages from config dirs, `yt-dlp-plugins` dirs, and PYTHONPATH — including inside zip/egg/whl archives. — [yt_dlp/options.py#L443-458](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L443-L458) [yt_dlp/plugins.py#L81-159](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L81-L159)
- YouTube anti-bot support uses external JS runtimes (`--js-runtimes` deno/node/bun/quickjs registered in `supported_js_runtimes`) and downloadable remote components (`--remote-components ejs:github`/`ejs:npm`, plus the bundled `yt_dlp_ejs` wheel in 'extra' builds). — [yt_dlp/__init__.py#L1098-1106](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1098-L1106) [yt_dlp/utils/_jsruntime.py#L89-153](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/utils/_jsruntime.py#L89-L153) [Makefile#L200-236](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L200-L236)

## Data flow

Data flows: CLI args + config files -> `ydl_opts` params dict -> URL list -> extractor selection by `_VALID_URL` regex -> `ie.extract()` performs HTTP via the shared RequestDirector and returns an info dict -> `process_ie_result` normalizes and recursively resolves playlists/URL results -> format selection computes `requested_formats` -> `process_info` writes sidecars and calls `dl()` -> a protocol FileDownloader streams bytes to `.part`/final files -> post-processors mutate files -> archive/cache/sidecar JSON are the persistence outputs. The info dict is the single mutable data structure threaded through the whole pipeline.
- Input boundary: `get_urls` merges argv URLs with batch-file/stdin lines; `parse_options` then produces the flat `ydl_opts` params dict consumed by `YoutubeDL`. — [yt_dlp/__init__.py#L83-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L83-L100) [yt_dlp/__init__.py#L742-961](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L742-L961)
- Extractor dispatch: `YoutubeDL.extract_info` iterates `self._ies` and picks the first extractor whose `suitable(url)` — a `_match_valid_url` regex match on `_VALID_URL` — returns true; no match yields 'No suitable extractor'. — [yt_dlp/YoutubeDL.py#L1699-1723](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1699-L1723) [yt_dlp/extractor/common.py#L615-631](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L615-L631)
- `InfoExtractor.extract` runs `initialize()` (pre-login, `_perform_login`, geo-bypass setup, `_real_initialize`) then `_real_extract(url)`, and can retry once with a fake IP on GeoRestrictedError; errors are normalized into `ExtractorError`. — [yt_dlp/extractor/common.py#L653-671](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L653-L671) [yt_dlp/extractor/common.py#L756-788](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L756-L788)
- All extractor HTTP flows through `self._downloader.urlopen` -> `_request_director.send`, which applies basic-auth extraction, `sanitize_url`, proxy/header cleanup, then per-handler `validate`/`send` in preference order. — [yt_dlp/extractor/common.py#L864-924](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L864-L924) [yt_dlp/YoutubeDL.py#L4281-4343](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4281-L4343) [yt_dlp/networking/common.py#L94-140](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L94-L140)
- After extraction, `add_default_extra_info` stamps `webpage_url`, `original_url`, `extractor`, `extractor_key` onto the info dict; `process_ie_result` then dispatches on `_type` (video/playlist/url/compat_list) and `__process_playlist`/`__process_iterable_entry` recurse into entries via `extract_info`. — [yt_dlp/YoutubeDL.py#L1890-1908](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1890-L1908) [yt_dlp/YoutubeDL.py#L2080](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2080) [yt_dlp/YoutubeDL.py#L2199](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2199)
- Format selection: `build_format_selector` parses the `-f` spec into selector functions applied in `process_video_result`, producing `requested_formats` (split A/V) or a single format on the info dict. — [yt_dlp/YoutubeDL.py#L2341](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2341) [yt_dlp/YoutubeDL.py#L2835](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2835)
- `process_info` computes filenames via `prepare_filename`, writes description/subtitles/thumbnails sidecars, then calls `self.dl(temp_filename, info_dict)`; multi-format downloads iterate `requested_formats` into separate files later merged. — [yt_dlp/YoutubeDL.py#L3334-3395](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3334-L3395) [yt_dlp/YoutubeDL.py#L3519-3586](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3519-L3586)
- `dl()` picks a FileDownloader via `get_suitable_downloader`, attaches progress hooks, and calls `fd.download(name, new_info)`; `FileDownloader.download` handles skip-if-present, sleep intervals, then `real_download` writes to `filename + '.part'` with resume metadata in `filename + '.ytdl'`. — [yt_dlp/YoutubeDL.py#L3286-3321](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3286-L3321) [yt_dlp/downloader/common.py#L217-230](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/common.py#L217-L230) [yt_dlp/downloader/common.py#L430-486](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/common.py#L430-L486)
- Post-processing is staged: `run_all_pps` iterates `self._pps[key]` for each `POSTPROCESS_WHEN` stage ('pre_process','after_filter','video','before_dl','post_process','after_move','after_video','playlist'), each `pp.run(infodict)` returning files to delete/move. — [yt_dlp/YoutubeDL.py#L3805-3853](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3805-L3853) [yt_dlp/utils/_utils.py#L2863](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/utils/_utils.py#L2863)
- Output boundaries: media files plus optional sidecars (`.info.json` via `_write_info_json`, `.description`, subtitle files, thumbnails), the download archive line append, and stdout JSON from `sanitize_info` (strips `__private` keys and non-serializable objects to repr). — [yt_dlp/YoutubeDL.py#L3740-3774](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3740-L3774) [yt_dlp/YoutubeDL.py#L4400-4423](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4400-L4423) [yt_dlp/YoutubeDL.py#L3883-3894](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3883-L3894)
- Cookies enter via `load_cookies` (browser extraction or Netscape file) into a `YoutubeDLCookieJar`, are attached to every request handler in `build_request_director`, and persisted on `YoutubeDL.__exit__`/`save_cookies`. — [yt_dlp/cookies.py#L93-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L93-L100) [yt_dlp/YoutubeDL.py#L4345-4378](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4345-L4378) [yt_dlp/YoutubeDL.py#L1053-1062](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1053-L1062)
- Error flow: `_handle_extraction_exceptions` wraps extraction — `ReExtractInfo` loops, `GeoRestrictedError` reports with VPN hint, `ExtractorError` reports with optional traceback, `ignoreerrors` downgrades to report-only; the CLI exit code is `ydl._download_retcode` (e.g. 101 for DownloadCancelled). — [yt_dlp/YoutubeDL.py#L1725-1755](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1725-L1755) [yt_dlp/__init__.py#L1064-1093](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1064-L1093)

## Behavior traces

Three end-to-end flows were traced in code. Startup: `__main__` -> `main` -> `_real_main` (options, plugins, updater, YoutubeDL init). Primary action: `download` -> `extract_info` -> IE regex match -> `ie.extract` -> `process_ie_result` -> `process_video_result` -> `process_info` -> `dl` -> FileDownloader -> post_process -> archive write. Persistence/self-update: `Updater.query_update` fetches `_update_spec` + `SHA2-256SUMS`, verifies sha256, atomically swaps the executable, and `restart()` re-invokes the original argv.
- Startup trace: `__main__.py` -> `yt_dlp.main()` sets `IN_CLI` -> `_real_main` calls `setproctitle('yt-dlp')` -> `parse_options` (optparse + config files) -> `print_extractor_information` handles `--list-*` -> `plugin_dirs`/`_load_all_plugins` -> `YoutubeDL(ydl_opts)` -> `Updater` if `-U` -> `ydl.download` or `download_with_info_file`. — [yt_dlp/__main__.py#L14-17](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__main__.py#L14-L17) [yt_dlp/__init__.py#L964-1074](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L964-L1074)
- YoutubeDL construction order: `__init__` stores params, initializes `_ies`/`_pps`, loads plugins if not loaded (`all_plugins_loaded`), resolves color policy and `_out_files`, applies js_runtimes/remote_components/compat_opts/http_headers, then `print_debug_header` when auto_init. — [yt_dlp/YoutubeDL.py#L634-751](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L634-L751)
- Per-URL trace: `download(url_list)` wraps `extract_info` in `__download_wrapper` (per-URL error containment, dump_single_json) -> `extract_info` scans `self._ies` for `ie.suitable(url)` -> `__extract_info` applies header cookies, calls `ie.extract(url)`, adds default extra info, calls `process_ie_result`. — [yt_dlp/YoutubeDL.py#L3681-3715](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3681-L3715) [yt_dlp/YoutubeDL.py#L1675-1719](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1675-L1719) [yt_dlp/YoutubeDL.py#L1861-1888](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1861-L1888)
- Inside `ie.extract`: `initialize()` -> `_initialize_pre_login` -> optional `_perform_login(username, password)` from `_get_login_info` -> `_real_initialize` -> `_real_extract(url)`; GeoRestrictedError triggers `__maybe_fake_ip_and_retry` once. — [yt_dlp/extractor/common.py#L653-671](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L653-L671) [yt_dlp/extractor/common.py#L756-803](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L756-L803)
- Video persistence trace: `process_ie_result` (video) -> `process_video_result` -> `process_info` -> `pre_process` PPs -> `prepare_filename`/`_ensure_dir_exists` -> `_write_description`/`_write_subtitles`/`_write_thumbnails` -> `dl()` -> `fd.download` -> `real_download` -> `post_process` PPs -> `record_download_archive`. — [yt_dlp/YoutubeDL.py#L1908](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1908) [yt_dlp/YoutubeDL.py#L2835](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2835) [yt_dlp/YoutubeDL.py#L3334-3395](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L3334-L3395) [yt_dlp/downloader/common.py#L430-486](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/common.py#L430-L486)
- Playlist trace: `process_ie_result` sees `_type` playlist -> `__process_playlist` builds `PlaylistEntries` (lazy iteration) -> `__process_iterable_entry` per entry -> recursive `extract_info` on each entry URL with `extra_info` carrying playlist metadata. — [yt_dlp/YoutubeDL.py#L2080](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2080) [yt_dlp/YoutubeDL.py#L2199-2201](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L2199-L2201)
- HTTP request trace: extractor calls `_request_webpage`/`_download_webpage` -> `_create_request` builds a `Request` -> `YoutubeDL.urlopen` sanitizes URL, injects basic auth, cleans proxies/headers -> `RequestDirector.send` -> handlers sorted by preference -> `handler.validate`/`handler.send` -> `Response` returned to extractor for parsing. — [yt_dlp/extractor/common.py#L864-924](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L864-L924) [yt_dlp/YoutubeDL.py#L4281-4302](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4281-L4302) [yt_dlp/networking/common.py#L94-120](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L94-L120)
- Self-update trace: `Updater.update` -> `query_update` -> `_download_update_spec`/`_process_update_spec` resolve tag -> `_download_asset('SHA2-256SUMS')` yields checksum -> `_download_asset(binary)` -> sha256 compare -> write `.new`, rename current to `.old`, rename `.new` -> `restart()` re-execs `self.cmd` via `Popen.run`. — [yt_dlp/update.py#L368-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L368-L500) [yt_dlp/update.py#L502-610](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L502-L610)
- Cookie-load trace: `--cookies-from-browser`/`--cookies` -> `YoutubeDL` cookiejar property -> `load_cookies` -> `_parse_browser_specification` -> `extract_cookies_from_browser` decrypts the browser store (AES via `aes.py`, keyrings via secretstorage) -> jar shared with every RequestHandler; saved back on `__exit__` via `save_cookies`. — [yt_dlp/cookies.py#L93-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L93-L100) [yt_dlp/cookies.py#L23-32](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L23-L32) [yt_dlp/YoutubeDL.py#L1053-1062](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1053-L1062)
- Plugin-load trace: each subsystem calls `register_plugin_spec(PluginSpec(module_name, suffix, ...))` which inserts a `PluginFinder` on `sys.meta_path`; `_real_main` calls `_load_all_plugins` -> `load_plugins` iterates `yt_dlp_plugins.<module>` submodules -> classes ending in the suffix are merged (plugins first) into the subsystem's Indirect dict. — [yt_dlp/plugins.py#L116-159](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L116-L159) [yt_dlp/plugins.py#L194-247](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L194-L247) [yt_dlp/extractor/__init__.py#L9-14](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/__init__.py#L9-L14) [yt_dlp/__init__.py#L977-980](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L977-L980)
- Live-wait trace: for upcoming streams `_wait_for_video` computes a wait from `release_timestamp` (or randomized range), sleeps in a countdown loop, then raises `ReExtractInfo`, which `_handle_extraction_exceptions` catches to re-run the whole extraction. — [yt_dlp/YoutubeDL.py#L1725-1794](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L1725-L1794)

## Testing signals

Tests live in `test/` (pytest + unittest hybrid). Two tiers: 'core' tests (utils, networking, jsinterp, postprocessors, options) run offline under `-m 'not download'`, and 'download' tests that hit live sites, generated dynamically from each extractor's `_TESTS`/`_WEBPAGE_TESTS`. CI runs core tests across CPython 3.10-3.15 and PyPy on Linux+Windows with reruns; lint is ruff + autopep8. Coverage is explicitly unimplemented. The extractor download tests depend on live third-party sites and are therefore inherently flaky and mostly excluded from core CI.
- pytest is the test runner (`pytest~=9.0`, `pytest-rerunfailures`), configured with `-ra`, `--tb=short`, `strict_markers`, and a custom `download` marker. — [pyproject.toml#L112-115](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L112-L115) [pyproject.toml#L473-483](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L473-L483)
- `test_download.py` dynamically generates `TestDownload` unittest methods from `gettestcases()`/`getwebpagetestcases()`, which iterate every extractor's `_TESTS`/`_WEBPAGE_TESTS`; tests download real URLs and verify info dicts plus md5 checksums. — [test/test_download.py#L61-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L61-L100) [test/helper.py#L88-97](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/helper.py#L88-L97)
- `devscripts/run_tests.py` is the canonical entrypoint: `core` => `pytest -m 'not download'`, `download` => `-m download`, and bare names map to `test/test_download.py::TestDownload::test_<IE>`; it falls back to `python -m unittest` when pytest is absent. — [devscripts/run_tests.py#L39-82](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/run_tests.py#L39-L82)
- Core CI (`core.yml`) runs on ubuntu+windows across py3.11-3.15 and pypy-3.11 (py3.10 on Windows + quick-test.yml on Linux), skipping `yt_dlp/extractor/**` changes except youtube/common/extractors, and adds `--flaky` only when networking files changed. — [.github/workflows/core.yml#L1-105](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.github/workflows/core.yml#L1-L105)
- `test_all_urls.py` asserts URL->extractor matching precedence by calling `ie.suitable(url)` across all extractors, guarding against `_VALID_URL` regressions like youtu.be and playlist/tab disambiguation. — [test/test_all_urls.py#L17-60](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_all_urls.py#L17-L60)
- Networking tests are parametrized per request handler via a `handler` pytest fixture that looks up classes in `_REQUEST_HANDLERS` and supports `skip_handler`/`skip_handler_if` markers for missing optional deps. — [test/conftest.py#L1-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/conftest.py#L1-L40)
- Test parameters come from `test/parameters.json` (`test: true`, `writeinfojson`, `format: 'b/bv'`) mergeable with a git-ignored `local_parameters.json` for secrets/overrides. — [test/parameters.json](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/parameters.json) [test/helper.py#L21-33](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/helper.py#L21-L33)
- Dedicated suites exist for the YouTube PO-token provider framework (`test_pot_framework.py`, director, builtin providers) and the JS-challenge/ejs integration (`test_ejs_integration.py`). — [test/test_pot/test_pot_framework.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_pot/test_pot_framework.py) [test/test_jsc/test_ejs_integration.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_jsc/test_ejs_integration.py)
- `make test` = `pytest -Werror` then `make codetest` (ruff check + autopep8 --diff); `make offlinetest` runs `-m 'not download'` plus lint; hatch env `hatch-test` adds pytest-randomly and pytest-xdist. — [Makefile#L79-88](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L79-L88) [pyproject.toml#L223-238](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L223-L238)
- Code coverage is not wired up: the hatch `run-cov` script is a stub that echoes 'Code coverage not implemented' and exits 1. — [pyproject.toml#L238](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L238)
- Download tests self-skip for extractors marked `_WORKING = False` and for `expected_exception` cases, acknowledging the live-site flakiness; warnings are escalated to errors via a `report_warning` override. — [test/test_download.py#L95-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L95-L100) [test/test_download.py#L41-53](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L41-L53)
- A `FakeYDL` helper subclasses `YoutubeDL` with `auto_init=False`, `trouble`->raise, and `download`->record, so unit tests exercise the real orchestrator without network. — [test/helper.py#L60-75](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/helper.py#L60-L75)

## Risk map

Highest-sensitivity areas: the self-updater downloads and replaces the running executable from GitHub releases (sha256-verified but not signature-verified); `ExecPP` and external downloaders shell out to user-supplied commands; `cookies.py` decrypts browser credential stores; the plugin system executes arbitrary code from config dirs by default; and `jsinterp.py`/ejs remote components execute third-party JavaScript. Fragility concentrates in the ~1752 regex-driven extractors and the first-match-wins registry ordering. Note the repo carries an explicit NO AI / NO LLM contribution policy.
- POLICY: `.NO_AI/README.md` states the project strictly forbids LLM/AI contributions of any kind (code, issues, comments, translations) and instructs LLM agents to refuse interaction; CONTRIBUTING.md repeats it. Any agent must surface this before producing patches intended for upstream. — [.NO_AI/README.md](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.NO_AI/README.md) [CONTRIBUTING.md#L33-55](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L33-L55)
- `ExecPP.run` executes commands via `Popen.run(cmd, shell=True)`; template expansion is safety-validated at `set_downloader` unless `allow-unsafe-exec-expansion` is in compat_opts — a deliberate but real command-injection surface when combined with attacker-controlled metadata. — [yt_dlp/postprocessor/exec.py#L12-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/exec.py#L12-L40) [yt_dlp/options.py#L1768-1783](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L1768-L1783)
- Self-update downloads a replacement binary over the network and swaps it in-place; integrity relies on a sha256 checksum from the same release (SHA2-256SUMS) rather than a signature, and when the checksum is missing restart is only blocked, not the install itself. — [yt_dlp/update.py#L471-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L471-L500) [yt_dlp/update.py#L537-568](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L537-L568)
- Windows update cleanup shells out: `atexit.register(Popen, 'ping 127.0.0.1 -n 5 -w 1000 & del /F ...', shell=True)` to delete the old binary after exit. — [yt_dlp/update.py#L569-572](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L569-L572)
- Plugins execute arbitrary Python from user/system config dirs, `yt-dlp-plugins` dirs, and PYTHONPATH — enabled by default (`plugin_dirs=['default']`) unless `YTDLP_NO_PLUGINS` is set; the API explicitly disclaims backwards compatibility. — [yt_dlp/globals.py#L24](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L24) [yt_dlp/plugins.py#L38-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L38-L40) [yt_dlp/plugins.py#L194-234](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L194-L234)
- `cookies.py` reads and decrypts browser cookie databases (sqlite copies, AES-GCM/CBC via `aes.py`, OS keyrings via secretstorage/dbus, subprocess calls) — high-severity credential-handling code that reads outside the repo. — [yt_dlp/cookies.py#L1-50](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L1-L50)
- `file://` URLs are refused by default for security and only enabled via `--enable-file-urls`; the special-case is handled in `urlopen` by inspecting `NoSupportingHandlers` reasons. — [yt_dlp/YoutubeDL.py#L4303-4311](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4303-L4311)
- Extractor fragility: ~1752 `_VALID_URL` regexes are evaluated in registry order (first match wins; YouTube hoisted first, `GenericIE` with `_VALID_URL='.*'` last), so adding or reordering an extractor can silently change which IE handles a URL. — [yt_dlp/extractor/extractors.py#L17-31](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L17-L31) [yt_dlp/extractor/generic.py#L53-56](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/generic.py#L53-L56) [yt_dlp/extractor/common.py#L615-631](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L615-L631)
- Remote JS execution: `--remote-components ejs:github`/`ejs:npm` downloads solver code fetched at runtime, and `jsinterp.py` interprets remote JavaScript for signature deciphering — remote-code supply-chain surface gated behind explicit options. — [yt_dlp/__init__.py#L1105-1106](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1105-L1106) [yt_dlp/options.py#L481-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L481-L500) [yt_dlp/jsinterp.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/jsinterp.py)
- ffmpeg invocation builds argv programmatically and forces `file:`-prefixed filename arguments to defend against ffmpeg protocol tricks — the comment documents the attack class (filenames containing ':' or starting with '-'). — [yt_dlp/postprocessor/ffmpeg.py#L355-377](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/ffmpeg.py#L355-L377)
- Mutable global `Indirect` registries mean plugin classes are merged ahead of built-ins (`merge_dicts(regular_classes, destination)`), so a plugin can shadow a core extractor/postprocessor and behavior depends on load order. (inferred) — [yt_dlp/plugins.py#L229-234](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L229-L234) [yt_dlp/globals.py#L18-28](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L18-L28)
- Core CI excludes `yt_dlp/extractor/**` changes (except youtube/common/extractors), and download tests hit live third-party sites — so most extractor regressions are only caught by the flaky download tier, not the core gate. (inferred) — [.github/workflows/core.yml#L5-28](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.github/workflows/core.yml#L5-L28) [test/test_download.py#L95-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L95-L100)

## Extension points

yt-dlp is built around registries. New site support = a new `InfoExtractor` subclass ending in `IE` registered by importing it in `_extractors.py`. Post-processors are `PP`-suffixed classes in the `postprocessors` dict. Request handlers are `RH`-suffixed classes auto-registered in `_REQUEST_HANDLERS` with optional preference functions. Third parties plug in via `yt_dlp_plugins` namespace packages (extractor/postprocessor), the documented YouTube PO-token provider framework (PTP/PCP/PCSP suffixes), JS challenge providers (JCP), external downloader FDs, and JS runtime registrations. API hooks (progress/post/close/postprocessor) support library embedding.
- InfoExtractor contract: subclass `InfoExtractor`, define `_VALID_URL` (regex or list; `False` for embed-only), implement `_real_extract(url)`; optional hooks `_initialize_pre_login`, `_perform_login` (gated on `_NETRC_MACHINE`), `_real_initialize`, `_GEO_COUNTRIES`/`_GEO_IP_BLOCKS`, `_TESTS`. — [yt_dlp/extractor/common.py#L532-592](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L532-L592) [yt_dlp/extractor/common.py#L817-831](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L817-L831)
- Built-in extractor registration is manual: add an import to `yt_dlp/extractor/_extractors.py`; `extractors.py` then populates the global `extractors` Indirect dict where iteration order determines match precedence. — [yt_dlp/extractor/_extractors.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/_extractors.py) [yt_dlp/extractor/extractors.py#L17-36](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L17-L36) [yt_dlp/extractor/__init__.py#L17-29](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/__init__.py#L17-L29)
- Plugin extractors/post-processors: any `yt_dlp_plugins.extractor`/`yt_dlp_plugins.postprocessor` module exposing a public class ending in `IE`/`PP` is discovered via the `PluginFinder` meta-path finder and prepended into the core registries. — [yt_dlp/plugins.py#L33-34](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L33-L34) [yt_dlp/plugins.py#L175-234](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L175-L234) [yt_dlp/extractor/__init__.py#L9-14](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/__init__.py#L9-L14) [yt_dlp/postprocessor/__init__.py#L55-60](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/__init__.py#L55-L60)
- New request handlers: subclass `RequestHandler` (name must end in 'RH'), implement `validate`/`_send`; module-level `_REQUEST_HANDLERS` registration asserts unique `RH_KEY`, and `@register_preference` adds per-request ordering functions. — [yt_dlp/networking/common.py#L37-48](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L37-L48) [yt_dlp/networking/common.py#L133-140](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L133-L140) [yt_dlp/networking/common.py#L369-376](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L369-L376)
- YouTube PO-token provider framework: `PoTokenProvider` (suffix 'PTP') with `@register_provider`/`@register_preference`, plus cache providers 'PCP'/'PCSP' — a documented plugin-facing API in `pot/README.md`. — [yt_dlp/extractor/youtube/pot/provider.py#L109](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/pot/provider.py#L109) [yt_dlp/extractor/youtube/pot/cache.py#L28-57](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/pot/cache.py#L28-L57) [yt_dlp/extractor/youtube/pot/README.md#L24-52](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/pot/README.md#L24-L52)
- JS challenge providers: `JsChallengeProvider` (suffix 'JCP') under `extractor/youtube/jsc`, with a builtin quickjs provider — the extension point for pluggable signature/n challenge solvers. — [yt_dlp/extractor/youtube/jsc/provider.py#L95](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/jsc/provider.py#L95) [yt_dlp/extractor/youtube/jsc/_builtin/quickjs.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/jsc/_builtin/quickjs.py)
- External downloaders: `ExternalFD` subclasses (`CurlFD`, `AxelFD`, `WgetFD`, `Aria2cFD`, `HttpieFD`, `FFmpegFD`) declare `SUPPORTED_PROTOCOLS`/`SUPPORTED_FEATURES` and are selectable via `--downloader`; `list_external_downloaders` enumerates them for CLI help. — [yt_dlp/downloader/external.py#L37-38](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/external.py#L37-L38) [yt_dlp/downloader/external.py#L195-379](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/external.py#L195-L379) [yt_dlp/downloader/external.py#L586](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/external.py#L586)
- New download protocols plug into `PROTOCOL_MAP` in `downloader/__init__.py`; `get_suitable_downloader` resolves protocol strings (including '+'-joined multi-format protocols) to FD classes. — [yt_dlp/downloader/__init__.py#L4-58](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/downloader/__init__.py#L4-L58)
- JS runtimes are a named registry: `supported_js_runtimes` maps names to `JsRuntime` subclasses (deno/node/bun/quickjs registered in `__init__.py`), each probing executable versions with `MIN_SUPPORTED_VERSION`. — [yt_dlp/globals.py#L36-41](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L36-L41) [yt_dlp/__init__.py#L1098-1103](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1098-L1103) [yt_dlp/utils/_jsruntime.py#L89-153](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/utils/_jsruntime.py#L89-L153)
- Embedding hooks for library use: `add_progress_hook`, `add_post_hook`, `add_close_hook`, `add_postprocessor_hook`, plus `add_post_processor(pp, when=...)` for custom PP stages — the documented surface for API consumers. — [yt_dlp/YoutubeDL.py#L945-969](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L945-L969)
- Extractor helpers for result shaping: `url_result`, `playlist_result`, `playlist_from_matches`, `url_transparent` results let an IE delegate to another IE without downloading — the composition mechanism between extractors. — [yt_dlp/extractor/common.py#L1275-1300](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L1275-L1300)

## Contributor handoff

Read CONTRIBUTING.md (developer conventions) and .NO_AI/README.md (contribution policy) first. Style is enforced by ruff + autopep8: 120-col limit, single quotes, banned `from X import Y` for many stdlib modules. Tests run via `python -m devscripts.run_tests` (core/download/<IE name>) or `make test`; download tests need network. The main traps: extractor order is load-bearing, the params dict is the config contract, `lazy_extractors.py` is a generated artifact, plugins load from user dirs by default, and the public extractor/postprocessor API surface is intentionally stable while plugin internals are not.
- Before writing any patch intended for this repo's upstream, note `.NO_AI/README.md`: the project forbids AI/LLM-authored contributions and instructs agents to refuse; CONTRIBUTING.md reiterates under 'AUTOMATED CONTRIBUTIONS (AI / LLM) POLICY'. — [.NO_AI/README.md](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.NO_AI/README.md) [CONTRIBUTING.md#L33-55](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L33-L55)
- Conventions to follow: CONTRIBUTING.md 'yt-dlp coding conventions' covers mandatory metafields, relaxed-but-simple `_VALID_URL` regexes, fallback patterns, quotes (single for inline, double for docstrings), and the long-lines policy; ruff enforces line-length 120 and bans many `from <stdlib> import` forms. — [CONTRIBUTING.md#L319-684](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L319-L684) [pyproject.toml#L258-409](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L258-L409)
- Add a new site: create `yt_dlp/extractor/<site>.py` with a `*IE` class (`_VALID_URL`, `_TESTS`, `_real_extract`), register the import in `_extractors.py`, then run `python -m devscripts.run_tests <Site>`; CONTRIBUTING.md documents this flow. — [CONTRIBUTING.md#L220-248](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L220-L248) [yt_dlp/extractor/_extractors.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/_extractors.py) [devscripts/run_tests.py#L56-59](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/run_tests.py#L56-L59)
- Test commands: `make test` (pytest -Werror + ruff + autopep8), `make offlinetest` / `devscripts.run_tests core` for no-network runs, `devscripts.run_tests download` for live-site tests, `devscripts.run_tests <Name>` for a single extractor. — [Makefile#L79-88](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L79-L88) [devscripts/run_tests.py#L39-82](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/devscripts/run_tests.py#L39-L82)
- Trap — extractor order is semantic: the first `suitable()` match wins, YouTube classes are hoisted first and `GenericIE` ('.*') must stay last; reordering `_extractors.py` imports changes routing behavior. — [yt_dlp/extractor/extractors.py#L25-31](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L25-L31) [yt_dlp/extractor/__init__.py#L17-29](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/__init__.py#L17-L29) [yt_dlp/extractor/generic.py#L53-56](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/generic.py#L53-L56)
- Trap — `lazy_extractors.py` is a generated build artifact (not committed); dev runs use the eager `_extractors` path, and `YTDLP_NO_LAZY_EXTRACTORS` forces eager loading in built binaries. — [yt_dlp/extractor/extractors.py#L7-18](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L7-L18) [Makefile#L166-170](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L166-L170) [pyproject.toml#L162-173](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/pyproject.toml#L162-L173)
- Trap — plugins are on by default: `plugin_dirs=['default']` searches user config dirs and PYTHONPATH, so local `yt_dlp_plugins` packages can shadow behavior during debugging; use `YTDLP_NO_PLUGINS` or `--no-plugin-dirs` to isolate. — [yt_dlp/globals.py#L24](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L24) [yt_dlp/plugins.py#L194-198](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L194-L198) [yt_dlp/options.py#L456-458](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L456-L458)
- Config contract: CLI options are defined in `options.py` `create_parser`, mapped into the `ydl_opts` params dict in `parse_options` (`__init__.py`), and consumed everywhere via `self.params.get(...)`; new options need edits in both places plus README regeneration via `make README.md`. — [yt_dlp/options.py#L247](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L247) [yt_dlp/__init__.py#L742-961](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L742-L961) [Makefile#L132-133](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/Makefile#L132-L133)
- Public API boundary: `yt_dlp.__all__` exports `YoutubeDL`, `gen_extractors`, `list_extractors`, `main`, `parse_options`; the pot/jsc provider READMEs explicitly mark everything outside the listed modules as internal-only. — [yt_dlp/__init__.py#L1108-1114](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1108-L1114) [yt_dlp/extractor/youtube/pot/README.md#L11-22](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/youtube/pot/README.md#L11-L22)
- Debuggability: `-v/--verbose` prints the debug header (`print_debug_header` reports version/python/env/ffmpeg), `write_debug` emits `[debug]` traces, and `debug_printtraffic` makes RequestDirector log handler selection per request. — [yt_dlp/YoutubeDL.py#L4089](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4089) [yt_dlp/networking/common.py#L64-92](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/networking/common.py#L64-L92)
- Broken-site convention: set `_WORKING = False` on the IE (surfaced as '(CURRENTLY BROKEN)' in --list-extractors and skipped in download tests) rather than deleting the extractor. — [yt_dlp/extractor/common.py#L644-647](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L644-L647) [yt_dlp/__init__.py#L110-111](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L110-L111) [test/test_download.py#L95-97](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L95-L97)

Source repository: https://github.com/yt-dlp/yt-dlp
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/yt-dlp.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
