yt-dlp/yt-dlp
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()`.
- `_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`.
- `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.
- 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.
- 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).
- 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.
- 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`.
- 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`.
- 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.
- 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`.
- 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()`.
- Python >=3.10 is enforced twice: `requires-python` in pyproject and an `ImportError` raised at import time in `yt_dlp/__init__.py`.
- 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`.