yt-dlp(1)

💖 yt-dlp — the manual, but make it make sense 💖

ok so. bestie. gather round. this is yt-dlp, from the yt-dlp(1) manual, and it's literally the most feature-rich command-line audio/video downloader in existence. we're talking support for thousands of sites. thousands. not a typo.

quick lore drop for context: the project is a fork of youtube-dl (https://github.com/ytdl-org/youtube-dl) and it's based on youtube-dlc (https://github.com/blackjack4494/yt-dlc), which is now inactive. so yeah, it has ancestors. iconic lineage tbh. 👑


✨ SYNOPSIS ✨

yt-dlp [OPTIONS] URL [URL...]

that's it. that's the whole vibe. options, then one or more URLs. slay.


🌸 DESCRIPTION 🌸

yt-dlp is a feature-rich command-line audio/video downloader with support for thousands of sites. it's a fork of youtube-dl (https://github.com/ytdl-org/youtube-dl), built on top of the now-inactive youtube-dlc (https://github.com/blackjack4494/yt-dlc). basically it inherited the good genes and went to the gym. 🫶


🎀 OPTIONS 🎀

General Options 💅

  • -h, --help — prints this help text and then dips. exit, stage left. bye.
  • --version — prints the program version, then exits. cute and quick.
  • -U, --update — updates the program to the latest version. glow-up time ✨
  • --no-update — does not check for updates. this is the default, bestie. no surprises here.
  • --update-to [CHANNEL]@[TAG] — upgrades or downgrades you to a specific version. plot twist: CHANNEL can also be a repository. if you omit them, CHANNEL and TAG default to "stable" and "latest" respectively. see "UPDATE" for the deets. supported channels: stable, nightly, master.
  • -i, --ignore-errors — ignores download and postprocessing errors. the download still counts as successful even if postprocessing face-plants. generous queen behavior.
  • --no-abort-on-error — keeps going with the next video when download errors happen. perfect for skipping unavailable videos in a playlist. this is the default.
  • --abort-on-error — nope, we stop. aborts downloading further videos if an error occurs. (Alias: --no-ignore-errors)
  • --list-extractors — lists every supported extractor and exits. it's giving "roll call".
  • --extractor-descriptions — outputs descriptions of all supported extractors and exits.
  • --use-extractors NAMES — the extractor names you wanna use, separated by commas. you can also use regexes, "all", "default" and "end" (end URL matching), e.g. --ies "holodex.*,end,youtube". prefix a name with a - to exclude it, e.g. --ies default,-generic. run --list-extractors for the full list of extractor names. (Alias: --ies)
  • --default-search PREFIX — uses this prefix for unqualified URLs. e.g. "gvsearch2:python" downloads two videos from google videos for the search term "python". use "auto" to let yt-dlp guess ("auto_warning" to emit a warning when it's guessing). "error" just throws an error. the default value "fixup_error" repairs broken URLs, but emits an error instead of searching if it can't. 😭
  • --ignore-config — don't load any more configuration files except the ones handed to --config-locations. for backward compatibility, if this option is found inside the system configuration file, the user configuration is not loaded. (Alias: --no-config)
  • --no-config-locations — don't load any custom configuration files. this is the default. when given inside a configuration file, it ignores all previous --config-locations defined in the current file.
  • --config-locations PATH — location of the main configuration file; either the path to the config or its containing directory (- means stdin). can be used multiple times, and even inside other configuration files. nesting era.
  • --plugin-dirs DIR — path to an additional directory to search for plugins. use it multiple times to add multiple directories. use "default" to search the default plugin directories (that's the default).
  • --no-plugin-dirs — clears the plugin directories to search, including the defaults and any added by previous --plugin-dirs. total reset. 💀
  • --js-runtimes RUNTIME[:PATH] — an additional JavaScript runtime to enable, with an optional location for the runtime (either the path to the binary or its containing directory). can be used multiple times to enable multiple runtimes. supported runtimes, in order of priority from highest to lowest: deno, node, quickjs, bun. only deno is enabled by default. the highest priority runtime that is both enabled and available gets used. heads up: to use a lower priority runtime while deno is available, you gotta pass --no-js-runtimes before enabling the other runtimes.
  • --no-js-runtimes — clears the JavaScript runtimes to enable, including defaults and anything from previous --js-runtimes.
  • --remote-components COMPONENT — remote components you allow yt-dlp to fetch when it needs them. currently you don't need this if you're using an official executable or you have the requisite version of the yt-dlp-ejs package installed. can be used multiple times to allow multiple components. supported values: ejs:npm (external JavaScript components from npm), ejs:github (external JavaScript components from yt-dlp-ejs GitHub). by default, no remote components are allowed.
  • --no-remote-components — disallows fetching of all remote components, including any previously allowed by --remote-components or by defaults.
  • --flat-playlist — doesn't extract a playlist's URL result entries. some entry metadata may be missing, and downloading may be bypassed.
  • --no-flat-playlist — fully extracts the videos of a playlist. default behavior, no crumbs left behind. ✨
  • --live-from-start — downloads livestreams from the start. currently experimental, and only supported for YouTube, Twitch, TVer, and mellow-fan. 🧪
  • --no-live-from-start — downloads livestreams from the current time. default.
  • --wait-for-video MIN[-MAX] — waits for scheduled streams to become available. pass the minimum number of seconds (or a range) to wait between retries. patience is a virtue fr.
  • --no-wait-for-video — doesn't wait for scheduled streams. default.
  • --mark-watched — marks videos watched (even with --simulate). bestie said "I was here". 👀
  • --no-mark-watched — doesn't mark videos watched. default.
  • --color [STREAM:]POLICY — whether to emit color codes in the output, optionally prefixed by the STREAM (stdout or stderr) to apply the setting to. can be one of "always", "auto" (default), "never", or "no_color" (use non color terminal sequences). use "auto-tty" or "no_color-tty" to decide based on terminal support only. can be used multiple times. 🌈
  • --compat-options OPTS — options that help keep compatibility with youtube-dl or youtube-dlc configurations by reverting some of the changes made in yt-dlp. see "Differences in default behavior" for the details. nostalgia mode.
  • --alias ALIASES OPTIONS — creates aliases for an option string. unless an alias starts with a dash -, it gets prefixed with --. arguments are parsed according to the Python string formatting mini-language. e.g. --alias get-audio,-X "-S aext:{0},abr -x --audio-format {0}" creates the options --get-audio and -X that take an argument (ARG0) and expand to -S aext:ARG0,abr -x --audio-format ARG0. all defined aliases get listed in the --help output. alias options can trigger more aliases, so be careful not to define recursive options — as a safety measure, each alias may be triggered a maximum of 100 times. can be used multiple times.
  • -t, --preset-alias PRESET — applies a predefined set of options, e.g. --preset-alias mp3. the presets available are: mp3, aac, mp4, mkv, sleep. see the "Preset Aliases" section at the end for more info. can be used multiple times. one-tap slay. 🔥

Network Options 🌐

  • --proxy URL — uses the specified HTTP/HTTPS/SOCKS proxy. to enable a SOCKS proxy, specify a proper scheme, e.g. socks5://user:pass@127.0.0.1:1080/. pass in an empty string (--proxy "") for a direct connection.
  • --socket-timeout SECONDS — how long to wait before giving up, in seconds. no more waiting on read receipts.
  • --source-address IP — the client-side IP address to bind to.
  • --impersonate CLIENT[:OS] — the client to impersonate for requests. e.g. chrome, chrome-110, chrome:windows-10. pass --impersonate="" to impersonate any client. heads up though: forcing impersonation for all requests may have a detrimental impact on download speed and stability. ⚠️
  • --list-impersonate-targets — lists the available clients to impersonate. window shopping.
  • -4, --force-ipv4 — makes all connections via IPv4.
  • -6, --force-ipv6 — makes all connections via IPv6.
  • --enable-file-urls — enables file:// URLs. this is disabled by default for security reasons, so like, know what you're doing. 🔒

Geo-restriction 🗺️

  • --geo-verification-proxy URL — uses this proxy to verify the IP address for some geo-restricted sites. the default proxy specified by --proxy (or none, if that option isn't present) is used for the actual downloading.
  • --xff VALUE — how to fake the X-Forwarded-For HTTP header to try bypassing geographic restriction. one of "default" (only when known to be useful), "never", an IP block in CIDR notation, or a two-letter ISO 3166-2 country code. sneaky but make it legal-adjacent.

Video Selection 👑

  • -I, --playlist-items ITEM_SPEC — comma-separated playlist_index of the items to download. you can specify a range using "[START]:[STOP][:STEP]". for backward compatibility, START-STOP is also supported. use negative indices to count from the right, and negative STEP to download in reverse order. e.g. -I 1:3,7,-5::2 used on a playlist of size 15 will download the items at index 1,2,3,7,11,13,15. chef's kiss selection. 💅
  • --min-filesize SIZE — aborts the download if the filesize is smaller than SIZE, e.g. 50k or 44.6M.
  • --max-filesize SIZE — aborts the download if the filesize is larger than SIZE, e.g. 50k or 44.6M.
  • --date DATE — downloads only videos uploaded on this date. the date can be "YYYYMMDD" or in the format [now|today|yesterday][-N[day|week|month|year]]. e.g. --date today-2weeks downloads only videos uploaded on the same day two weeks ago. time traveler energy. 🦋
  • --datebefore DATE — downloads only videos uploaded on or before this date. accepted date formats are the same as --date.
  • --dateafter DATE — downloads only videos uploaded on or after this date. accepted date formats are the same as --date.

ok we're not done, there's more manual to go — stay tuned bestie 🫶

  • --match-filters FILTER — okay so this is the generic video filter, bestie. You can compare any OUTPUT TEMPLATE field with a number or a string using the operators defined in Filtering Formats. You can also just name a field to match if it’s present, use !field to check if it’s not present, and & to check multiple conditions. If you need to escape & or quotes, use "". If you use this option multiple times, the filter matches if at least one of the conditions is met. Example: --match-filters !is_live --match-filters "like_count>?100 & description~='(?i)& dogs" matches only videos that are not live OR those that have a like count more than 100 (or the like field is not available) and also have a description containing the phrase "cats & dogs" (caseless). And yes, --match-filters - will interactively ask whether to download each video. 💅
  • --no-match-filters — do not use any --match-filters (default).
  • --break-match-filters FILTER — same as --match-filters, but it stops the download process when a video is rejected. no cap.
  • --no-break-match-filters — do not use any --break-match-filters (default).
  • --no-playlist — download only the video, if the URL refers to a video and a playlist.
  • --yes-playlist — download the playlist, if the URL refers to a video and a playlist.
  • --age-limit YEARS — download only videos suitable for the given age.
  • --download-archive FILE — download only videos not listed in the archive file. Record the IDs of all downloaded videos in it.
  • --no-download-archive — do not use an archive file (default).
  • --max-downloads NUMBER — abort after downloading NUMBER files.
  • --break-on-existing — stop the download process when encountering a file that is in the archive supplied with the --download-archive option.
  • --no-break-on-existing — do not stop the download process when encountering a file that is in the archive (default).
  • --break-per-input — alters --max-downloads, --break-on-existing, --break-match-filters, and autonumber to reset per input URL.
  • --no-break-per-input — --break-on-existing and similar options terminate the entire download queue.
  • --skip-playlist-after-errors N — number of allowed failures until the rest of the playlist is skipped.

💾 Download Options

  • -N, --concurrent-fragments N — number of fragments of a dash/hlsnative video that should be downloaded concurrently (default is 1). slay those fragments in parallel.
  • -r, --limit-rate RATE — maximum download rate in bytes per second, e.g. 50K or 4.2M.
  • --throttled-rate RATE — minimum download rate in bytes per second below which throttling is assumed and the video data is re-extracted, e.g. 100K.
  • -R, --retries RETRIES — number of retries (default is 10), or "infinite".
  • --file-access-retries RETRIES — number of times to retry on file access error (default is 3), or "infinite".
  • --fragment-retries RETRIES — number of retries for a fragment (default is 10), or "infinite" (DASH, hlsnative and ISM).
  • --retry-sleep [TYPE:]EXPR — time to sleep between retries in seconds, optionally prefixed by the type of retry (http (default), fragment, file_access, extractor) to apply the sleep to. EXPR can be a number, linear=START[:END[:STEP=1]] or exp=START[:END[:BASE=2]]. This option can be used multiple times to set the sleep for the different retry types, e.g. --retry-sleep linear=1::2 --retry-sleep fragment:exp=1:20.
  • --skip-unavailable-fragments — skip unavailable fragments for DASH, hlsnative and ISM downloads (default) (Alias: --no-abort-on-unavailable-fragments).
  • --abort-on-unavailable-fragments — abort download if a fragment is unavailable (Alias: --no-skip-unavailable-fragments).
  • --keep-fragments — keep downloaded fragments on disk after downloading is finished.
  • --no-keep-fragments — delete downloaded fragments after downloading is finished (default).
  • --buffer-size SIZE — size of download buffer, e.g. 1024 or 16K (default is 1024).
  • --resize-buffer — the buffer size is automatically resized from an initial value of --buffer-size (default).
  • --no-resize-buffer — do not automatically adjust the buffer size.
  • --http-chunk-size SIZE — size of a chunk for chunk-based HTTP downloading, e.g. 10485760 or 10M (default is disabled). May be useful for bypassing bandwidth throttling imposed by a webserver (experimental).
  • --playlist-random — download playlist videos in random order.
  • --lazy-playlist — process entries in the playlist as they are received. This disables n_entries, --playlist-random and --playlist-reverse.
  • --no-lazy-playlist — process videos in the playlist only after the entire playlist is parsed (default).
  • --hls-use-mpegts — use the mpegts container for HLS videos; allowing some players to play the video while downloading, and reducing the chance of file corruption if download is interrupted. This is enabled by default for live streams.
  • --no-hls-use-mpegts — do not use the mpegts container for HLS videos. This is default when not downloading live streams.
  • --download-sections REGEX — download only chapters that match the regular expression. A "*" prefix denotes time-range instead of chapter. Negative timestamps are calculated from the end. "*from-url" can be used to download between the "start_time" and "end_time" extracted from the URL. Needs ffmpeg. This option can be used multiple times to download multiple sections, e.g. --download-sections "*10:15-inf" --download-sections "intro".
  • --downloader [PROTO:]NAME — name or path of the external downloader to use, optionally prefixed by the protocols (http, ftp, m3u8, dash, rtmp) to use it for. Currently supports native, aria2c, axel, curl, ffmpeg, httpie, wget. You can use this option multiple times to set different downloaders for different protocols. E.g. --downloader aria2c --downloader "dash,m3u8:native" will use aria2c for http/ftp downloads, and the native downloader for dash/m3u8 downloads (Alias: --external-downloader).
  • --downloader-args NAME:ARGS — give these arguments to the external downloader. Specify the downloader name and the arguments separated by a colon ":". For ffmpeg, arguments can be passed to different positions using the same syntax as --postprocessor-args. You can use this option multiple times to give different arguments to different downloaders (Alias: --external-downloader-args).

📁 Filesystem Options

  • -a, --batch-file FILE — file containing URLs to download ("-" for stdin), one URL per line. Lines starting with "#", ";" or "]" are considered as comments and ignored.
  • --no-batch-file — do not read URLs from batch file (default).
  • -P, --paths [TYPES:]PATH — the paths where the files should be downloaded. Specify the type of file and the path separated by a colon ":". All the same TYPES as --output are supported. Additionally, you can also provide "home" (default) and "temp" paths. All intermediary files are first downloaded to the temp path and then the final files are moved over to the home path after download is finished. This option is ignored if --output is an absolute path.
  • -o, --output [TYPES:]TEMPLATE — output filename template; see "OUTPUT TEMPLATE" for details.
  • --output-na-placeholder TEXT — placeholder for unavailable fields in --output (default: "NA").
  • --restrict-filenames — restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames.
  • --no-restrict-filenames — allow Unicode characters, "&" and spaces in filenames (default).
  • --windows-filenames — force filenames to be Windows-compatible.
  • --no-windows-filenames — sanitize filenames only minimally.
  • --trim-filenames LENGTH — limit the filename length (excluding extension) to the specified number of characters.
  • -w, --no-overwrites — do not overwrite any files.
  • --force-overwrites — overwrite all video and metadata files. This option includes --no-continue.
  • --no-force-overwrites — do not overwrite the video, but overwrite related files (default).
  • -c, --continue — resume partially downloaded files/fragments (default).
  • --no-continue — do not resume partially downloaded fragments. If the file is not fragmented, restart download of the entire file.
  • --part — use .part files instead of writing directly into output file (default).
  • --no-part — do not use .part files - write directly into output file.
  • --mtime — use the Last-modified header to set the file modification time.
  • --no-mtime — do not use the Last-modified header to set the file modification time (default).
  • --write-description — write video description to a .description file.
  • --no-write-description — do not write video description (default).
  • --write-info-json — write video metadata to a .info.json file (this may contain personal information).
⚠️ heads up bestie: that info JSON can contain personal information, so be mindful before sharing it. 🙈
  • --no-write-info-json — do not write video metadata (default).
  • --write-playlist-metafiles — write playlist metadata in addition to the video metadata when using --write-info-json, --write-description etc. (default).
  • --no-write-playlist-metafiles — do not write playlist metadata when using --write-info-json, --write-description etc.
  • --clean-info-json — remove some internal metadata such as filenames from the infojson (default).
  • --no-clean-info-json — write all fields to the infojson.
  • --write-comments — retrieve video comments to be placed in the infojson. The comments are fetched even without this option if the extraction is known to be quick (Alias: --get-comments).
  • --no-write-comments — do not retrieve video comments unless the extraction is known to be quick (Alias: --no-get-comments).
  • --load-info-json FILE — JSON file containing the video information (created with the "--write-info-json" option).
  • --cookies FILE 🍪 — points to a Netscape-formatted file that yt-dlp both reads cookies from and dumps the cookie jar into. It's the little cookie diary, bestie.
  • --no-cookies — nope, no reading and no dumping cookies from/to a file. This is the default, so if you never touch this, this is what you're already doing.
  • --cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER] 🦋 — grabs cookies straight out of a browser, iconic. The browsers supported rn are: brave, chrome, chromium, edge, firefox, opera, safari, vivaldi, whale. You can optionally pass the KEYRING used for decrypting Chromium cookies on Linux, the name/path of the PROFILE to load cookies from, and the CONTAINER name (only if Firefox) — "none" means no container — each one separated by its own special separator. By default, all containers of the most recently accessed profile get used. Keyrings supported rn: basictext, gnomekeyring, kwallet, kwallet5, kwallet6.
  • --no-cookies-from-browser — don't load cookies from the browser. Also the default, no cap.
  • --cache-dir DIR ✨ — where in the filesystem yt-dlp is allowed to permanently store little bits of downloaded info (client ids, signatures, that kind of thing). The default is ${XDG_CACHE_HOME}/yt-dlp, slay.
  • --no-cache-dir — turns off filesystem caching entirely.
  • --rm-cache-dir 💅 — deletes all the filesystem cache files. Full clean-out, very main character.

🖼️ Thumbnail Options 🖼️

  • --write-thumbnail — writes the thumbnail image to disk. Little picture moment 💖.
  • --no-write-thumbnail — doesn't write the thumbnail image to disk. This is the default.
  • --write-all-thumbnails — writes all the thumbnail image formats to disk, not just one. Greedy in a cute way.
  • --list-thumbnails 🎀 — lists the available thumbnails for each video. It simulates unless --no-simulate is used.

🔗 Internet Shortcut Options 🔗

  • --write-link — writes an internet shortcut file, and which kind depends on your current platform (.url, .webloc or .desktop). Heads up: the URL may be cached by the OS, so it's giving "stale link" potential 🙈.
  • --write-url-link — writes a .url Windows internet shortcut. The OS caches the URL based on the file path, iykyk.
  • --write-webloc-link — writes a .webloc macOS internet shortcut.
  • --write-desktop-link — writes a .desktop Linux internet shortcut.

🤫 Verbosity and Simulation Options 🤫

  • -q, --quiet — activates quiet mode. If you use it together with --verbose, the log gets printed to stderr instead.
  • --no-quiet — deactivates quiet mode. (Default)
  • --no-warnings — ignores warnings. Not me ignoring the red flags 💀.
  • -s, --simulate — does not download the video and writes nothing to disk. Pure vibes only, zero commitment.
  • --no-simulate — actually downloads the video even when printing/listing options are being used.
  • --ignore-no-formats-error — ignores the "No video formats" error. Super useful for extracting metadata even when the videos aren't actually available for download. It's experimental, so handle with care ✨.
  • --no-ignore-no-formats-error — throws the error when no downloadable video formats are found. (Default)
  • --skip-download — doesn't download the video but does write all related files. (Alias: --no-download)
  • -O, --print [WHEN:]TEMPLATE 🫶 — a field name or output template to print to the screen, optionally prefixed with when to print it, separated by a ":". The supported values of "WHEN" are the exact same as those of --use-postprocessor (default: video). This implies --quiet. It also implies --simulate unless --no-simulate is used or later stages of WHEN are involved. You can use this option multiple times, go off.
  • --print-to-file [WHEN:]TEMPLATE FILE — appends the given template to the file. The values of WHEN and TEMPLATE are the same as those of --print. FILE uses the same syntax as the output template. Also usable multiple times 💖.
  • -j, --dump-json — quiet, but prints JSON information for each video. It simulates unless --no-simulate is used. Check "OUTPUT TEMPLATE" for a description of the available keys.
  • -J, --dump-single-json — quiet, but prints JSON information for each URL or infojson passed in. It simulates unless --no-simulate is used. If the URL refers to a playlist, the whole playlist information gets dumped in a single line. One line, so compact, so efficient 💅.
  • --force-write-archive — forces download archive entries to be written as far as no errors occur, even if -s or another simulation option is being used. (Alias: --force-download-archive)
  • --newline — outputs the progress bar as new lines.
  • --no-progress — doesn't print the progress bar.
  • --progress — shows the progress bar even if you're in quiet mode. Sneaky little override 👑
  • --console-title — displays progress in the console titlebar.
  • --progress-template [TYPES:]TEMPLATE ✨ — a template for progress outputs, optionally prefixed with one of "download:" (default), "download-title:" (the console title), "postprocess:", or "postprocess-title:". The video's fields are accessible under the "info" key and the progress attributes are accessible under the "progress" key. For example: --console-title --progress-template "download-title:%(info.id)s-%(progress.eta)s".
  • --progress-delta SECONDS — the time between progress output (default: 0).
  • -v, --verbose — prints various debugging information. Detective mode 🔍.
  • --dump-pages — prints the downloaded pages encoded using base64 so you can debug problems. Very verbose, like very 🙈.
  • --write-pages — writes the downloaded intermediary pages to files in the current directory to debug problems.
  • --print-traffic — displays the sent and read HTTP traffic.

🛠️ Workarounds 🛠️

  • --encoding ENCODING — forces the specified encoding. Experimental, so it's giving "we're testing this in the group chat first" 🧚.
  • --legacy-server-connect — explicitly allows an HTTPS connection to servers that don't support RFC 5746 secure renegotiation. Old-school server, we make it work 💪.
  • --no-check-certificates — suppresses HTTPS certificate validation. Do it at your own risk, bestie.
  • --prefer-insecure — uses an unencrypted connection to retrieve information about the video. Only the info, not the download, okay?
  • --add-headers FIELD:VALUE — specifies a custom HTTP header and its value, separated by a colon ":". You can use this option multiple times 🌸.
  • --bidi-workaround — works around terminals that lack bidirectional text support. Needs the bidiv or fribidi executable in PATH.
  • --sleep-requests SECONDS — the number of seconds to sleep between requests during data extraction. Be polite, take a nap 😴.
  • --sleep-interval SECONDS — the number of seconds to sleep before each download. This is the minimum time to sleep when used along with --max-sleep-interval. (Alias: --min-sleep-interval)
  • --max-sleep-interval SECONDS — the maximum number of seconds to sleep. Can only be used along with --min-sleep-interval.
  • --sleep-subtitles SECONDS — the number of seconds to sleep before each subtitle download.

🎬 Video Format Options 🎬

  • -f, --format FORMAT — the video format code. See "FORMAT SELECTION" for more details, it's a whole journey.
  • -S, --format-sort SORTORDER — sorts the formats by the fields given. See "Sorting Formats" for more details.
  • --format-sort-reset — disregards any previous user-specified sort order and resets to the default. Fresh start energy ✨.
  • --format-sort-force — forces the user-specified sort order to have precedence over all fields, see "Sorting Formats" for more details. (Alias: --S-force)
  • --no-format-sort-force — some fields have precedence over the user-specified sort order. (Default)
  • --video-multistreams — allows multiple video streams to be merged into a single file.
  • --no-video-multistreams — only one video stream is downloaded for each output file. (Default)
  • --audio-multistreams — allows multiple audio streams to be merged into a single file.
  • --no-audio-multistreams — only one audio stream is downloaded for each output file. (Default)
  • --prefer-free-formats — prefers video formats with free containers over non-free ones of the same quality. Use it with -S ext to strictly prefer free containers irrespective of quality 🌈.
  • --no-prefer-free-formats — doesn't give any special preference to free containers. (Default)
  • --check-formats — makes sure formats are selected only from the ones that are actually downloadable.
  • --check-all-formats — checks all formats for whether they're actually downloadable.
  • --no-check-formats — doesn't check that the formats are actually downloadable.
  • -F, --list-formats — lists the available formats of each video. It simulates unless --no-simulate is used.
  • --merge-output-format FORMAT — the containers that may be used when merging formats, separated by "/", e.g. "mp4/mkv". Ignored if no merge is required. Currently supported: avi, flv, mkv, mov, mp4, webm.

💬 Subtitle Options 💬

  • --write-subs — writes the subtitle file.
  • --no-write-subs — doesn't write the subtitle file. (Default)
  • --write-auto-subs — writes the automatically generated subtitle file. (Alias: --write-automatic-subs)
  • --no-write-auto-subs — doesn't write auto-generated subtitles. (Default) (Alias: --no-write-automatic-subs)
  • --list-subs — lists the available subtitles of each video. It simulates unless --no-simulate is used.
  • --sub-format FORMAT — the subtitle format; accepts a format preference separated by "/", e.g. "srt" or "ass/srt/best".
  • --sub-langs LANGS 🦋 — the languages of the subtitles to download (can be regex) or "all", separated by commas, e.g. --sub-langs "en.*,ja" (where "en.*" is a regex pattern that matches "en" followed by 0 or more of any character). You can prefix the language code with a "-" to exclude it from the requested languages, e.g. --sub-langs all,-live_chat. Use --list-subs for a list of available language tags, bestie.

🔐 Authentication Options 🔐

  • -u, --username USERNAME — logs in with this account ID.
  • -p, --password PASSWORD — the account password. If this option is left out, yt-dlp will ask you interactively 🙈.
  • -2, --twofactor TWOFACTOR — the two-factor authentication code.
  • -n, --netrc — uses .netrc authentication data.
  • --netrc-location PATH — location of the .netrc authentication data; either the path itself or its containing directory. Defaults to ~/.netrc.
  • --netrc-cmd NETRC_CMD — the command to execute to get the credentials for an extractor.
  • --video-password PASSWORD — the video-specific password.
  • --ap-mso MSO — the Adobe Pass multiple-system operator (TV provider) identifier. Use --ap-list-mso for a list of available MSOs.
  • --ap-username USERNAME — the multiple-system operator account login.
  • --ap-password PASSWORD — the password for your multiple-system operator account, bestie 💅. Skip it and yt-dlp will hit you with an interactive prompt instead, no cap.
  • --ap-list-mso — lists out every single multiple-system operator we support. That's it. That's the whole job ✨
  • --client-certificate CERTFILE — path to your client certificate file, in PEM format. It may also include the private key, icon behaviour 🎀
  • --client-certificate-key KEYFILE — path to the private key file for that client certificate.
  • --client-certificate-password PASSWORD — the password for the client certificate private key, like, only if it's encrypted. If you don't hand it over and the key is encrypted, yt-dlp will ask you interactively 🙈

🎀 Post-Processing Options 🎀

This is where the real main-character glow-up happens. Get your files cute before they leave the house 💖

  • -x, --extract-audio — converts video files into audio-only files. Heads up: it requires ffmpeg and ffprobe, so make sure they're installed or this is gonna be a whole thing 😭
  • --audio-format FORMAT — the format your audio gets converted to when -x is in play. Currently supported: best (default), aac, alac, flac, m4a, mp3, opus, vorbis, wav. You can specify multiple rules using similar syntax as --remux-video ✨
  • --audio-quality QUALITY — sets the ffmpeg audio quality when converting with -x. Drop in a value between 0 (best) and 10 (worst) for VBR, or a specific bitrate like 128K (default 5). Lower number = slay, higher number = 💀
  • --remux-video FORMAT — remuxes the video into another container if necessary. Currently supported: avi, flv, gif, mkv, mov, mp4, webm, aac, aiff, alac, flac, m4a, mka, mp3, ogg, opus, vorbis, wav. If the target container doesn't support the video/audio codec, remuxing straight up fails, so vibe check your codecs first. You can specify multiple rules, like:
"aac>m4a/mov>mp4/mkv"

…which remuxes aac to m4a, mov to mp4, and anything else to mkv 🔥

  • --recode-video FORMAT — re-encodes the video into another format if necessary. Same syntax and same supported formats as --remux-video, it just does the full re-encode instead of the lil container swap.
  • --postprocessor-args NAME:ARGS — gives these arguments to the postprocessors 🫶. You specify the postprocessor/executable name and the arguments, separated by a colon ":", to give the argument to that specified postprocessor/executable. Supported PPs are: Merger, ModifyChapters, SplitChapters, ExtractAudio, VideoRemuxer, VideoConvertor, Metadata, EmbedSubtitle, EmbedThumbnail, SubtitlesConvertor, ThumbnailsConvertor, FixupStretched, FixupM4a, FixupM3u8, FixupTimestamp and FixupDuration. The supported executables are: AtomicParsley, FFmpeg and FFprobe. You can also specify "PP+EXE:ARGS" to give the arguments to the specified executable only when it's being used by the specified postprocessor. Also, for ffmpeg/ffprobe, "_i"/"_o" can be appended to the prefix, optionally followed by a number, to pass the argument before the specified input/output file, e.g.
--ppa "Merger+ffmpeg_i1:-v quiet"

You can use this option multiple times to give different arguments to different postprocessors. (Alias: --ppa) 💅

  • -k, --keep-video — keeps the intermediate video file on disk after post-processing. Holds onto it like a sentimental screenshot 🥺
  • --no-keep-video — deletes the intermediate video file after post-processing (default). Clean girl aesthetic ✨
  • --post-overwrites — overwrites post-processed files (default).
  • --no-post-overwrites — does not overwrite post-processed files.
  • --embed-subs — embeds subtitles directly in the video (only for mp4, webm and mkv videos, so know your container).
  • --no-embed-subs — doesn't embed subtitles (default).
  • --embed-thumbnail — embeds the thumbnail in the video as cover art 🎨 icon behaviour honestly.
  • --no-embed-thumbnail — doesn't embed the thumbnail (default).
  • --embed-metadata — embeds metadata into the video file. It also embeds chapters/infojson if they're present, unless --no-embed-chapters/--no-embed-info-json are used. (Alias: --add-metadata)
  • --no-embed-metadata — doesn't add metadata to the file (default). (Alias: --no-add-metadata)
  • --embed-chapters — adds chapter markers to the video file. (Alias: --add-chapters)
  • --no-embed-chapters — doesn't add chapter markers (default). (Alias: --no-add-chapters)
  • --embed-info-json — embeds the infojson as an attachment to mkv/mka video files.
  • --no-embed-info-json — doesn't embed the infojson as an attachment to the video file.
  • --parse-metadata [WHEN:]FROM:TO — parses additional metadata like title/artist from other fields; see "MODIFYING METADATA" for the deets 👀. Supported values of "WHEN" are the same as that of --use-postprocessor (default: pre_process).
  • --replace-in-metadata [WHEN:]FIELDS REGEX REPLACE — replaces text in a metadata field using the given regex. This option can be used multiple times. Supported values of "WHEN" are the same as that of --use-postprocessor (default: pre_process) 🧚
  • --xattrs — writes metadata to the video file's xattrs (using Dublin Core and XDG standards). Fancy and standards-compliant, we love her 💜
  • --concat-playlist POLICY — concatenates videos in a playlist. One of "never", "always", or "multi_video" (default; only when the videos form a single show). All the video files must have the same codecs and number of streams to be concatenable — no mixing, bestie. The "pl_video:" prefix can be used with --paths and --output to set the output filename for the concatenated files. See "OUTPUT TEMPLATE" for details 🦋
  • --fixup POLICY — automatically corrects known faults of the file. One of never (do nothing), warn (only emit a warning), detect_or_warn (the default; fix the file if we can, warn otherwise), force (try fixing even if the file already exists) 🩹
  • --ffmpeg-location PATH — location of the ffmpeg binary; either the path to the binary itself or its containing directory.
  • --exec [WHEN:]CMD — executes a command, optionally prefixed with when to execute it, separated by a ":". Supported values of "WHEN" are the same as that of --use-postprocessor (default: after_move). The same syntax as the output template can be used to pass any field as arguments to the command; however, for security reasons the only allowed conversions are: "i"/"d" (signed integer decimal), "f" (floating-point decimal) and "q" (shell-quoted). If no fields are passed, %(filepath,_filename|)q is appended to the end of the command. This option can be used multiple times ⚡
ngl that security note is not a suggestion, it's a boundary. Respect the allowed conversions 😤
  • --no-exec — removes any previously defined --exec.
  • --convert-subs FORMAT — converts the subtitles to another format (currently supported: ass, lrc, srt, vtt). Use "--convert-subs none" to disable conversion (default). (Alias: --convert-subtitles)
  • --convert-thumbnails FORMAT — converts the thumbnails to another format (currently supported: jpg, png, webp). You can specify multiple rules using similar syntax as "--remux-video". Use "--convert-thumbnails none" to disable conversion (default) 🖼️
  • --split-chapters — splits the video into multiple files based on internal chapters. The "chapter:" prefix can be used with --paths and --output to set the output filename for the split files. See "OUTPUT TEMPLATE" for details. Chapter-by-chapter? That's a series, not a video 📖
  • --no-split-chapters — doesn't split the video based on chapters (default).
  • --remove-chapters REGEX — removes chapters whose title matches the given regular expression. The syntax is the same as --download-sections. This option can be used multiple times ✂️
  • --no-remove-chapters — doesn't remove any chapters from the file (default).
  • --force-keyframes-at-cuts — forces keyframes at cuts when downloading/splitting/removing sections. This is slow because it needs a re-encode 😭, but the resulting video may have fewer artifacts around the cuts. Quality over speed, always 💅
  • --no-force-keyframes-at-cuts — doesn't force keyframes around the chapters when cutting/splitting (default).
  • --use-postprocessor NAME[:ARGS] — the (case-sensitive!) name of plugin postprocessors to be enabled, plus (optionally) arguments to pass to it, separated by a colon ":". ARGS are a semicolon ";" delimited list of NAME=VALUE. The "when" argument determines when the postprocessor is invoked. It can be one of:
    • "pre_process" (after video extraction)
    • "after_filter" (after video passes filter)
    • "video" (after --format; before --print/--output)
    • "before_dl" (before each video download)
    • "post_process" (after each video download; default)
    • "after_move" (after moving the video file to its final location)
    • "after_video" (after downloading and processing all formats of a video)
    • "playlist" (at end of playlist)

This option can be used multiple times to add different postprocessors 🌈


👑 SponsorBlock Options 👑

Makes chapter entries for, or removes, various segments (sponsor, introductions, etc.) from downloaded YouTube videos using the SponsorBlock API (https://sponsor.ajay.app). We're literally editing out the ads, main character energy fr fr 💅

  • --sponsorblock-mark CATS — SponsorBlock categories to create chapters for, separated by commas. Available categories are sponsor, intro, outro, selfpromo, preview, filler, interaction, music_offtopic, hook, poi_highlight, chapter, all and default (=all). You can prefix the category with a "-" to exclude it. See [1] for descriptions of the categories. E.g.
--sponsorblock-mark all,-preview

[1] https://wiki.sponsor.ajay.app/w/Segment_Categories 🎀

  • --sponsorblock-remove CATS — SponsorBlock categories to be removed from the video file, separated by commas. If a category is present in both mark and remove, remove takes precedence 👀. The syntax and available categories are the same as for --sponsorblock-mark, except that "default" refers to "all,-filler" and poi_highlight, chapter are not available 🚫
  • --sponsorblock-chapter-title TEMPLATE — an output template for the title of the SponsorBlock chapters created by --sponsorblock-mark. The only available fields are start_time, end_time, category, categories, name, category_names. Defaults to:
"[SponsorBlock]: %(category_names)l"
  • --no-sponsorblock — disables both --sponsorblock-mark and --sponsorblock-remove. All the way off, bestie 💀
  • --sponsorblock-api URL — SponsorBlock API location, defaults to https://sponsor.ajay.app

🦋 Extractor Options 🦋

  • --extractor-retries RETRIES — number of retries for known extractor errors (default is 3), or "infinite" if you're feeling unhinged ✨
  • --allow-dynamic-mpd — processes dynamic DASH manifests (default). (Alias: --no-ignore-dynamic-mpd)
  • --ignore-dynamic-mpd — bestie, do not process dynamic DASH manifests, like, at all (Alias: --no-allow-dynamic-mpd) 💅
  • --hls-split-discontinuity — split HLS playlists into different formats whenever there's a discontinuity (think ad breaks) 🎬
  • --no-hls-split-discontinuity — don't split HLS playlists into different formats at discontinuities such as ad breaks — this is the default behavior, no cap ✨
  • --extractor-args IE_KEY:ARGS — pass ARGS arguments straight to the IE_KEY extractor. Peep "EXTRACTOR ARGUMENTS" for the deets. You can use this option multiple times to give arguments for different extractors 🫶

🎀 Preset Aliases 🎀

Predefined aliases, for convenience and ease of use. Quick note tho: future versions of yt-dlp may add or adjust presets, but the existing preset names will not be changed or removed — icon behavior, fr fr 👑

  • -t mp3 — expands to -f 'ba[acodec^=mp3]/ba/b' -x --audio-format mp3
  • -t aac — expands to -f 'ba[acodec^=aac]/ba[acodec^=mp4a.40.]/ba/b' -x --audio-format aac
  • -t mp4 — expands to --merge-output-format mp4 --remux-video mp4 -S vcodec:h264,lang,quality,res,fps,hdr:12,a codec:aac
  • -t mkv — expands to --merge-output-format mkv --remux-video mkv
  • -t sleep — expands to --sleep-subtitles 5 --sleep-requests 0.75 --sleep-interval 10 --max-sleep-interval 20

⚙️ CONFIGURATION ⚙️

Okay so this is the glow-up part: you can configure yt-dlp by dropping any supported command line option into a configuration file. The config gets loaded from these locations, in this order:

  1. Main Configuration:
    • The file given to --config-locations
  2. Portable Configuration: (recommended for portable installations 🧳)
    • If using a binary, yt-dlp.conf in the same directory as the binary
    • If running from source-code, yt-dlp.conf in the parent directory of yt_dlp
  3. Home Configuration:
    • yt-dlp.conf in the home path given to -P
    • If -P is not given, the current directory is searched
  4. User Configuration:
    • ${XDG_CONFIG_HOME}/yt-dlp.conf
    • ${XDG_CONFIG_HOME}/yt-dlp/config (recommended on Linux/macOS 🐧🍎)
    • ${XDG_CONFIG_HOME}/yt-dlp/config.txt
    • ${APPDATA}/yt-dlp.conf
    • ${APPDATA}/yt-dlp/config (recommended on Windows 🪟)
    • ${APPDATA}/yt-dlp/config.txt
    • ~/yt-dlp.conf
    • ~/yt-dlp.conf.txt
    • ~/.yt-dlp/config
    • ~/.yt-dlp/config.txt See also: Notes about environment variables
  5. System Configuration:
    • /etc/yt-dlp.conf
    • /etc/yt-dlp/config
    • /etc/yt-dlp/config.txt

E.g. with the following configuration file, yt-dlp will always extract the audio, copy the mtime, use a proxy and save all videos under YouTube directory in your home directory — literally so handy:

# Lines starting with # are comments

# Always extract audio
-x

# Copy the mtime
--mtime

# Use this proxy
--proxy 127.0.0.1:3128

# Save all videos under YouTube directory in your home directory
-o ~/YouTube/%(title)s.%(ext)s
Note: options in a configuration file are just the same options aka switches used in regular command line calls; thus there must be no whitespace after - or --, e.g. -o or --proxy but not - o or -- proxy. They must also be quoted when necessary, as if it were a UNIX shell. 🙈

You can use --ignore-config if you want to disable all configuration files for a particular yt-dlp run. If --ignore-config is found inside any configuration file, no further configuration will be loaded. For example, having the option in the portable configuration file prevents loading of home, user, and system configurations. Additionally, (for backward compatibility) if --ignore-config is found inside the system configuration file, the user configuration is not loaded. 🫡

🔤 Configuration file encoding 🔤

The configuration files are decoded according to the UTF BOM if present, and in the encoding from system locale otherwise.

If you want your file to be decoded differently, add # coding: ENCODING to the beginning of the file (e.g. # coding: shift-jis). There must be no characters before that, even spaces or BOM. Not even one, bestie 😭

🔐 Authentication with netrc 🔐

You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with --username and --password) in order not to pass credentials as command line arguments on every yt-dlp execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a .netrc file (https://stackoverflow.com/tags/.netrc/info) on a per-extractor basis. For that, you will need to create a .netrc file in --netrc-location and restrict permissions to read/write by only you — lock it down, queen 🔒:

touch ${HOME}/.netrc
chmod a-rwx,u+rw ${HOME}/.netrc

After that, you can add credentials for an extractor in the following format, where extractor is the name of the extractor in lowercase:

machine <extractor> login <username> password <password>

E.g.

machine youtube login myaccount@gmail.com password my_youtube_password
machine twitch login my_twitch_account_name password my_twitch_password

To activate authentication with the .netrc file you should pass --netrc to yt-dlp or place it in the configuration file.

The default location of the .netrc file is ~ (see below).

As an alternative to using the .netrc file, which has the disadvantage of keeping your passwords in a plain text file, you can configure a custom shell command to provide the credentials for an extractor. This is done by providing the --netrc-cmd parameter, it shall output the credentials in the netrc format and return 0 on success, other values will be treated as an error. {} in the command will be replaced by the name of the extractor to make it possible to select the credentials for the right extractor. Chef's kiss, honestly 🤌

E.g. To use an encrypted .netrc file stored as .authinfo.gpg:

yt-dlp --netrc-cmd 'gpg --decrypt ~/.authinfo.gpg' 'https://www.youtube.com/watch?v=YE7VzlLtp-4'

🌱 Notes about environment variables 🌱

  • Environment variables are normally specified as ${VARIABLE}/$VARIABLE on UNIX and %VARIABLE% on Windows; but is always shown as ${VARIABLE} in this documentation
  • yt-dlp also allows using UNIX-style variables on Windows for path-like options; e.g. --output, --config-locations
  • If unset, ${XDG_CONFIG_HOME} defaults to ~/.config and ${XDG_CACHE_HOME} to ~/.cache
  • On Windows, ~ points to ${HOME} if present; or, ${USERPROFILE} or ${HOMEDRIVE}${HOMEPATH} otherwise
  • On Windows, ${USERPROFILE} generally points to C:\Users\<user name> and ${APPDATA} to ${USERPROFILE}\AppData\Roaming

📁 OUTPUT TEMPLATE 📁

The -o option is used to indicate a template for the output file names, while the -P option is used to specify the path each type of file should be saved to. Two icons, two jobs 💅

The simplest usage of -o is not to set any template arguments when downloading a single file, like in yt-dlp -o funny_video.flv "https://some/video" (hard-coding file extension like this is not recommended and could break some post-processing — so maybe don't, bestie 🙈).

It may however also contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to Python string formatting operations (https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting), e.g. %(NAME)s or %(NAME)05d. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. It's giving template language, and honestly? Slay ✨

The field names themselves (the part inside the parenthesis) can also have some special formatting:

  1. Object traversal: The dictionaries and lists available in metadata can be traversed by using a dot . separator; e.g. %(tags.0)s, %(subtitles.en.-1.ext)s. You can do Python slicing with colon :; E.g. %(id.3:7)s, %(id.6:2:-1)s, %(formats.:.format_id)s. Curly braces {} can be used to build dictionaries with only specific keys; e.g. %(formats.:.{format_id,height})#j. An empty field name %()s refers to the entire infodict; e.g. %(.{id,title})s. Note that all the fields that become available using this method are not listed below. Use -j to see such fields — go look, it's kind of iconic 🔍
  2. Arithmetic: Simple arithmetic can be done on numeric fields using +, - and *. E.g. %(playlist_index+10)03d, %(n_entries+1-playlist_index)d — math class but make it useful 🧮
  3. Date/time Formatting: Date/time fields can be formatted according to strftime formatting (https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a >. E.g. %(duration>%H-%M-%S)s, %(upload_date>%Y-%m-%d)s, %(epoch-3600>%H-%M-%S)s
  4. Alternatives: Alternate fields can be specified separated with a ,. E.g. %(release_date>%Y,upload_date>%Y|Unknown)s
  5. Replacement: A replacement value can be specified using a & separator according to the str.format mini-language (https://docs.python.org/3/library/string.html#format-specification-mini-language). If the field is not empty, this replacement value will be used instead of the actual field content. This is done after alternate fields are considered; thus the replacement is used if any of the alternative fields is not empty. E.g. %(chapters&has chapters|no chapters)s, %(title&TITLE={:>20}|NO TITLE)s
  6. Default: A literal default value can be specified for when the field is empty using a | separator. This overrides --output-na-placeholder. E.g. %(uploader|Unknown)s
  7. More Conversions: In addition to the normal format types diouxXeEfFgGcrs, yt-dlp additionally supports converting to B = Bytes, j = json (flag # for pretty-printing, + for Unicode), h = HTML escaping, l = a comma-separated list (flag # for \n newline-separated), q = a string quoted for the terminal (flag # to split a list into different arguments), D = add Decimal suffixes (e.g. 10M) (flag # to use 1024 as factor), and S = Sanitize as filename (flag # for restricted). So many little guys, obsessed 🥹
  8. Unicode normalization: The format type U can be used for NFC Unicode normalization (https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize). The alternate form flag (#) changes the normalization to NFD and the conversion flag + can be used for NFKC/NFKD compatibility equivalence normalization. E.g. %(title)+.100U is NFKC

To summarize, the general syntax for a field is:

%(name[.keys][addition][>strf][,alternate][&replacement][|default])[flags][width][.precision][length]type

okay so ALSO — yes, there’s an extra layer — you can set different output templates for the various metadata files separately from the general output template. How? By specifying the type of file, then a colon :, then the template. It’s giving modular 💅.

The different file types supported are subtitle, thumbnail, description, annotation (deprecated), infojson, link, pl_thumbnail, pl_description, pl_infojson, chapter, pl_video. E.g. -o "%(title)s.%(ext)s" -o "thumbnail:%(title)s/%(title)s.%(ext)s" will put the thumbnails in a folder with the same name as the video. If any of the templates is empty, that type of file will not be written. E.g. --write-thumbnail -o "thumbnail:" will write thumbnails only for playlists and not for video.

Note: Due to post-processing (i.e. merging etc.), the actual output filename might differ. Use --print after_move:filepath to get the name after all post-processing is complete.

The available fields are: 👇

  • id (string) — video identifier, its lil name tag 💖
  • title (string) — video title
  • fulltitle (string) — video title, but ignoring live timestamp and generic title ✨
  • ext (string) — video filename extension
  • alt_title (string) — a secondary title of the video
  • description (string) — the description of the video
  • display_id (string) — an alternative identifier for the video
  • uploader (string) — full name of the video uploader
  • uploader_id (string) — nickname or id of the video uploader
  • uploader_url (string) — URL to the video uploader’s profile
  • license (string) — license name the video is licensed under
  • creators (list) — the creators of the video
  • creator (string) — the creators of the video; comma-separated
  • timestamp (numeric) — UNIX timestamp of the moment the video became available
  • upload_date (string) — video upload date in UTC (YYYYMMDD)
  • release_timestamp (numeric) — UNIX timestamp of the moment the video was released
  • release_date (string) — the date (YYYYMMDD) when the video was released in UTC
  • release_year (numeric) — year (YYYY) when the video or album was released
  • modified_timestamp (numeric) — UNIX timestamp of the moment the video was last modified
  • modified_date (string) — the date (YYYYMMDD) when the video was last modified in UTC
  • channel (string) — full name of the channel the video is uploaded on
  • channel_id (string) — id of the channel
  • channel_url (string) — URL of the channel
  • channel_follower_count (numeric) — number of followers of the channel
  • channel_is_verified (boolean) — whether the channel is verified on the platform
  • location (string) — physical location where the video was filmed
  • duration (numeric) — length of the video in seconds
  • duration_string (string) — length of the video (HH:mm:ss)
  • view_count (numeric) — how many users have watched the video on the platform
  • concurrent_view_count (numeric) — how many users are currently watching the video on the platform.
  • like_count (numeric) — number of positive ratings of the video
  • dislike_count (numeric) — number of negative ratings of the video
  • repost_count (numeric) — number of reposts of the video
  • average_rating (numeric) — average rating given by users, the scale used depends on the webpage
  • comment_count (numeric) — number of comments on the video (For some extractors, comments are only downloaded at the end, and so this field cannot be used)
  • save_count (numeric) — number of times the video has been saved or bookmarked
  • age_limit (numeric) — age restriction for the video (years)
  • live_status (string) — one of "not_live", "is_live", "is_upcoming", "was_live", "post_live" (was live, but VOD is not yet processed)
  • is_live (boolean) — whether this video is a live stream or a fixed-length video
  • was_live (boolean) — whether this video was originally a live stream
  • playable_in_embed (string) — whether this video is allowed to play in embedded players on other sites
  • availability (string) — whether the video is "private", "premium_only", "subscriber_only", "needs_auth", "unlisted" or "public"
  • media_type (string) — the type of media as classified by the site, e.g. "episode", "clip", "trailer"
  • start_time (numeric) — time in seconds where the reproduction should start, as specified in the URL
  • end_time (numeric) — time in seconds where the reproduction should end, as specified in the URL
  • extractor (string) — name of the extractor
  • extractor_key (string) — key name of the extractor
  • epoch (numeric) — Unix epoch of when the information extraction was completed
  • autonumber (numeric) — number that will be increased with each download, starting at --autonumber-start, padded with leading zeros to 5 digits
  • video_autonumber (numeric) — number that will be increased with each video
  • n_entries (numeric) — total number of extracted items in the playlist
  • playlist_id (string) — identifier of the playlist that contains the video
  • playlist_title (string) — name of the playlist that contains the video
  • playlist (string) — playlist_title if available or else playlist_id
  • playlist_count (numeric) — total number of items in the playlist. May not be known if entire playlist is not extracted
  • playlist_index (numeric) — index of the video in the playlist padded with leading zeros according to the final index
  • playlist_autonumber (numeric) — position of the video in the playlist download queue padded with leading zeros according to the total length of the playlist
  • playlist_uploader (string) — full name of the playlist uploader
  • playlist_uploader_id (string) — nickname or id of the playlist uploader
  • playlist_channel (string) — display name of the channel that uploaded the playlist
  • playlist_channel_id (string) — identifier of the channel that uploaded the playlist
  • playlist_webpage_url (string) — URL of the playlist webpage
  • webpage_url (string) — a URL to the video webpage which, if given to yt-dlp, should yield the same result again
  • webpage_url_basename (string) — the basename of the webpage URL
  • webpage_url_domain (string) — the domain of the webpage URL
  • original_url (string) — the URL given by the user (or the same as webpage_url for playlist entries)
  • categories (list) — list of categories the video belongs to
  • tags (list) — list of tags assigned to the video
  • cast (list) — list of cast members

All the fields in Filtering Formats can also be used — yes, all of them, no cap 🔥

Available for the video that belongs to some logical chapter or section: 🌸

  • chapter (string) — name or title of the chapter the video belongs to
  • chapter_number (numeric) — number of the chapter the video belongs to
  • chapter_id (string) — id of the chapter the video belongs to

Available for the video that is an episode of some series or program: 🎀

  • series (string) — title of the series or program the video episode belongs to
  • series_id (string) — id of the series or program the video episode belongs to
  • season (string) — title of the season the video episode belongs to
  • season_number (numeric) — number of the season the video episode belongs to
  • season_id (string) — id of the season the video episode belongs to
  • episode (string) — title of the video episode
  • episode_number (numeric) — number of the video episode within a season
  • episode_id (string) — id of the video episode

Available for the media that is a track or a part of a music album: 🎧

  • track (string) — title of the track
  • track_number (numeric) — number of the track within an album or a disc
  • track_id (string) — id of the track
  • artists (list) — artist(s) of the track
  • artist (string) — artist(s) of the track; comma-separated
  • genres (list) — genre(s) of the track
  • genre (string) — genre(s) of the track; comma-separated
  • composers (list) — composer(s) of the piece
  • composer (string) — composer(s) of the piece; comma-separated
  • album (string) — title of the album the track belongs to
  • album_type (string) — type of the album
  • album_artists (list) — all artists appeared on the album
  • album_artist (string) — all artists appeared on the album; comma-separated
  • disc_number (numeric) — number of the disc or other physical medium the track belongs to

Available only when using --download-sections and for chapter: prefix when using --split-chapters for videos with internal chapters: ✂️

  • section_title (string) — title of the chapter
  • section_number (numeric) — number of the chapter within the file
  • section_start (numeric) — start time of the chapter in seconds
  • section_end (numeric) — end time of the chapter in seconds

Available only when used in --print: 🖨️

  • urls (string) — the URLs of all requested formats, one in each line
  • filename (string) — name of the video file. Note that the actual filename may differ
  • formats_table (table) — the video format table as printed by --list-formats
  • thumbnails_table (table) — the thumbnail format table as printed by --list-thumbnails
  • subtitles_table (table) — the subtitle format table as printed by --list-subs
  • automatic_captions_table (table) — the automatic subtitle format table as printed by --list-subs

Available only after the video is downloaded (post_process/after_move): 🫶

  • filepath — actual path of downloaded video file

Available only in --sponsorblock-chapter-title: 👑

  • start_time (numeric) — start time of the chapter in seconds
  • end_time (numeric) — end time of the chapter in seconds
  • categories (list) — the SponsorBlock categories (https://wiki.sponsor.ajay.app/w/Types#Category) the chapter belongs to
  • category (string) — the smallest SponsorBlock category the chapter belongs to
  • category_names (list) — friendly names of the categories
  • name (string) — friendly name of the smallest category
  • type (string) — the SponsorBlock action type (https://wiki.sponsor.ajay.app/w/Types#Action_Type) of the chapter

Each aforementioned sequence when referenced in an output template will be replaced by the actual value corresponding to the sequence name. E.g. for -o %(title)s-%(id)s.%(ext)s and an mp4 video with title yt-dlp test video and id YE7VzlLtp-4, this will result in a yt-dlp test video-YE7VzlLtp-4.mp4 file created in the current directory. Literally iconic behavior ✨

Note: Some of the sequences are not guaranteed to be present, since they depend on the metadata obtained by a particular extractor. Such sequences will be replaced with placeholder value provided with --output-na-placeholder (NA by default).
👑 bestie tip: peep the -j output to clock which fields are even available for the URL you're messing with. iykyk.

For numeric sequences you can flex numeric formatting (https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) — like, %(view_count)05d gives you a string with the view count padded with zeros up to 5 characters, so 00042 vibes. ✨

Output templates can also hold arbitrary hierarchical paths, fr — like -o "%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s", which literally downloads each video into a directory matching that path template. And any missing directory? Auto-created for you, no cap. 🫶

Wanna use percent literals in an output template? Use %%. Wanna output to stdout? That's -o -.

The current default template is %(title)s [%(id)s].%(ext)s.

Sometimes you do not want special characters like 中, spaces, or & — like when you're yeeting the downloaded filename onto a Windows system, or pushing the filename through an 8bit-unsafe channel. In those cases just add the --restrict-filenames flag and get a shorter title. 💅

Output template examples

$ yt-dlp --print filename -o "test video.%(ext)s" ptd1NN40vMw
test video.webm    # Literal name with correct extension

$ yt-dlp --print filename -o "%(title)s.%(ext)s" ptd1NN40vMw
To'y!🤯😂🤦🏻‍♂.webm    # All kinds of weird characters

$ yt-dlp --print filename -o "%(title)s.%(ext)s" ptd1NN40vMw --restrict-filenames
To_y.webm    # Restricted file name

# Download YouTube playlist videos in separate directory indexed by video order in a playlist
$ yt-dlp -o "%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" "https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re"

# Download YouTube playlist videos in separate directories according to their uploaded year
$ yt-dlp -o "%(upload_date>%Y)s/%(title)s.%(ext)s" "https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re"

# Prefix playlist index with " - " separator, but only if it is available
$ yt-dlp -o "%(playlist_index&{} - |)s%(title)s.%(ext)s" YE7VzlLtp-4 "https://www.youtube.com/user/TheLinuxFoundation/playlists"

# Download all playlists of YouTube channel/user keeping each playlist in separate directory:
$ yt-dlp -o "%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s" "https://www.youtube.com/user/TheLinuxFoundation/playlists"

# Download Udemy course keeping each chapter in separate directory under MyVideos directory in your home
$ yt-dlp -u user -p password -P "~/MyVideos" -o "%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s" "https://www.udemy.com/java-tutorial"

# Download entire series season keeping each series and each season in separate directory under C:/MyVideos
$ yt-dlp -P "C:/MyVideos" -o "%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" "https://videomore.ru/kino_v_detalayah/5_sezon/367617"

# Download video as "C:\MyVideos\uploader\title.ext", subtitles as "C:\MyVideos\subs\uploader\title.ext"
# and put all temporary files in "C:\MyVideos\tmp"
$ yt-dlp -P "C:/MyVideos" -P "temp:tmp" -P "subtitle:subs" -o "%(uploader)s/%(title)s.%(ext)s" YE7VzlLtp-4 --write-subs

# Download video as "C:\MyVideos\uploader\title.ext" and subtitles as "C:\MyVideos\uploader\subs\title.ext"
$ yt-dlp -P "C:/MyVideos" -o "%(uploader)s/%(title)s.%(ext)s" -o "subtitle:%(uploader)s/subs/%(title)s.%(ext)s" YE7VzlLtp-4 --write-subs

# Stream the video being downloaded to stdout
$ yt-dlp -o - YE7VzlLtp-4

🔥 FORMAT SELECTION 🔥

k so by default, if you don't pass any options, yt-dlp tries to download the best available quality. That's generally equivalent to using -f bestvideo*+bestaudio/best. BUT — plot twist — if multiple audiostreams is enabled (--audio-multistreams), the default format changes to -f bestvideo+bestaudio/best. Similarly, if ffmpeg isn't available, or if you're using yt-dlp to stream to stdout (-o -), the default becomes -f best/bestvideo+bestaudio.

Deprecation warning: latest versions of yt-dlp can stream multiple formats to the stdout simultaneously using ffmpeg. So in future versions, the default for this will be set to -f bv*+ba/b similar to normal downloads. If you want to preserve the -f b/bv+ba setting, it is recommended to explicitly specify it in the configuration options. 😭 (don't say we didn't warn you)

The general syntax for format selection is -f FORMAT (or --format FORMAT) where FORMAT is a selector expression, i.e. an expression that describes format or formats you would like to download.

The simplest case is requesting a specific format — e.g. with -f 22 you can download the format with format code equal to 22. You can get the list of available format codes for a particular video using --list-formats or -F. Note that these format codes are extractor specific (key detail, bestie 🎀).

You can also use a file extension (currently 3gp, aac, flv, m4a, mp3, mp4, ogg, wav, webm are supported) to download the best quality format of a particular file extension served as a single file — e.g. -f webm will download the best quality format with the webm extension served as a single file. slay.

You can use -f - to interactively provide the format selector for each video 👑

You can also use special names to select particular edge case formats:

  • all — select all formats separately
  • mergeall — select and merge all formats (must be used with --audio-multistreams, --video-multistreams or both)
  • b*, best* — select the best quality format that contains either a video or an audio or both (i.e.; vcodec!=none or acodec!=none)
  • b, best — select the best quality format that contains both video and audio. Equivalent to best*[vcodec!=none][acodec!=none]
  • bv, bestvideo — select the best quality video-only format. Equivalent to best*[acodec=none]
  • bv*, bestvideo* — select the best quality format that contains video. It may also contain audio. Equivalent to best*[vcodec!=none]
  • ba, bestaudio — select the best quality audio-only format. Equivalent to best*[vcodec=none]
  • ba*, bestaudio* — select the best quality format that contains audio. It may also contain video. Equivalent to best*[acodec!=none] (Do not use! (https://github.com/yt-dlp/yt-dlp/issues/979#issuecomment-919629354)) 💀
  • w*, worst* — select the worst quality format that contains either a video or an audio
  • w, worst — select the worst quality format that contains both video and audio. Equivalent to worst*[vcodec!=none][acodec!=none]
  • wv, worstvideo — select the worst quality video-only format. Equivalent to worst*[acodec=none]
  • wv*, worstvideo* — select the worst quality format that contains video. It may also contain audio. Equivalent to worst*[vcodec!=none]
  • wa, worstaudio — select the worst quality audio-only format. Equivalent to worst*[vcodec=none]
  • wa*, worstaudio* — select the worst quality format that contains audio. It may also contain video. Equivalent to worst*[acodec!=none]

So like, for example, to download the worst quality video-only format you can use -f worstvideo. It is, however, recommended not to use worst and related options 🙈. When your format selector is worst, the format which is worst in all respects is selected. Most of the time, what you actually want is the video with the smallest filesize instead. So it's generally better to use -S +size or, more rigorously, -S +size,+br,+res,+fps instead of -f worst. See Sorting Formats for more details.

You can select the n'th best format of a type by using best<type>.<n>. For example, best.2 will select the 2nd best combined format. Similarly, bv*.3 will select the 3rd best format that contains a video stream. cute.

If you want to download multiple videos and they don't have the same formats available, you can specify the order of preference using slashes. Note that formats on the left hand side are preferred — e.g. -f 22/17/18 will download format 22 if it's available, otherwise it will download format 17 if it's available, otherwise it will download format 18 if it's available, otherwise it will complain that no suitable formats are available for download. (the audacity 😭)

If you want to download several formats of the same video, use a comma as a separator — e.g. -f 22,17,18 will download all these three formats, of course if they are available. Or a more sophisticated example combined with the precedence feature: -f 136/137/mp4/bestvideo,140/m4a/bestaudio. 💖

You can merge the video and audio of multiple formats into a single file using -f <format1>+<format2>+... (requires ffmpeg installed) — e.g. -f bestvideo+bestaudio will download the best video-only format, the best audio-only format and mux them together with ffmpeg. iconic.

Deprecation warning: since the below described behavior is complex and counter-intuitive, this will be removed and multistreams will be enabled by default in the future. A new operator will be instead added to limit formats to single audio/video. ⚠️

Unless --video-multistreams is used, all formats with a video stream except the first one are ignored. Similarly, unless --audio-multistreams is used, all formats with an audio stream except the first one are ignored. E.g. -f bestvideo+best+bestaudio --video-multistreams --audio-multistreams will download and merge all 3 given formats. The resulting file will have 2 video streams and 2 audio streams. But -f bestvideo+best+bestaudio --no-video-multistreams will download and merge only bestvideo and bestaudio. best is ignored since another format containing a video stream (bestvideo) has already been selected. The order of the formats is therefore important. -f best+bestaudio --no-audio-multistreams will download only best while -f bestaudio+best --no-audio-multistreams will ignore best and download only bestaudio. 🧚 order matters, bestie!!

Filtering Formats

You can also filter the video formats by putting a condition in brackets, as in -f "best[height=720]" (or -f "[filesize>10M]" since filters without a selector are interpreted as best).

The following numeric meta fields can be used with comparisons <, <=, >, >=, = (equals), != (not equals):

  • filesize — the number of bytes, if known in advance
  • filesize_approx — an estimate for the number of bytes
  • width — width of the video, if known
  • height — height of the video, if known
  • aspect_ratio — aspect ratio of the video, if known
  • tbr — average bitrate of audio and video in kbps
  • abr — average audio bitrate in kbps
  • vbr — average video bitrate in kbps
  • asr — audio sampling rate in Hertz
  • fps — frame rate
  • audio_channels — the number of audio channels
  • stretched_ratio — width:height of the video's pixels, if not square

Okay so filtering also works for these comparison operators, bestie: = (equals), ^= (starts with), $= (ends with), *= (contains), ~= (matches regex) — and lowkey they work on all these string meta fields too:

  • url — the video URL
  • ext — file extension
  • acodec — name of the audio codec in use
  • vcodec — name of the video codec in use
  • container — name of the container format
  • protocol — the protocol that'll be used for the actual download, lower-case (http, https, rtmp, rtmpe, f4m, ism, http_dash_segments, m3u8, or m3u8_native)
  • language — language code
  • dynamic_range — the dynamic range of the video
  • format_id — a short description of the format
  • format — a human-readable description of the format
  • format_note — extra info about the format
  • resolution — textual description of width and height

Any string comparison can be prefixed with negation ! to flip it, bestie — e.g. !*= means does not contain. And the comparand of a string comparison needs to be quoted with either double or single quotes if it contains spaces or special characters other than ._-. Periodt. 🎀

Note: literally none of the meta fields above are guaranteed to be present 😭 since this solely depends on the metadata obtained by the particular extractor — i.e. the metadata the website actually offers. Any other field made available by the extractor can also be used for filtering, iykyk.

Formats where the value is not known? Excluded, bestie — unless you put a question mark (?) after the operator. And you can combine format filters! So -f "bv[height<=?720][tbr>500]" selects up to 720p videos (or videos where the height is not known, no cap) with a bitrate greater than 500 kbps. Slay. ✨ You can also use the filters with all to download all formats that satisfy the filter — e.g. -f "all[vcodec=none]" selects all audio-only formats. Obsessed.

Format selectors can also be grouped using parentheses, so cute — e.g. -f "(mp4,webm)[height<480]" will download the best pre-merged mp4 and webm formats with a height lower than 480. 💅


🌈 Sorting Formats 🌈

You can change the criteria for what counts as "the best" by using -S (--format-sort). The general format for this is --format-sort field1,field2....

The available fields are:

  • hasvid — gives priority to formats that have a video stream
  • hasaud — gives priority to formats that have an audio stream
  • ie_pref — the format preference
  • lang — the language preference as determined by the extractor (e.g. original language preferred over audio description)
  • quality — the quality of the format
  • source — the preference of the source
  • proto — protocol used for download (https/ftps > http/ftp > m3u8_native/m3u8 > http_dash_segments > websocket_frag > f4f/f4m)
  • vcodec — video codec (av01 > vp9.2 > vp9 > h265 > h264 > vp8 > h263 > theora > other)
  • acodec — audio codec (flac/alac > wav/aiff > opus > vorbis > aac > mp4a > mp3 > ac4 > eac3 > ac3 > dts > other)
  • codec — equivalent to vcodec,acodec
  • vext — video extension (mp4 > mov > webm > flv > other). If --prefer-free-formats is used, webm is preferred.
  • aext — audio extension (m4a > aac > mp3 > ogg > opus > webm > other). If --prefer-free-formats is used, the order changes to ogg > opus > webm > mp3 > m4a > aac
  • ext — equivalent to vext,aext
  • filesize — exact filesize, if known in advance
  • fs_approx — approximate filesize
  • size — exact filesize if available, otherwise approximate filesize
  • height — height of video
  • width — width of video
  • res — video resolution, calculated as the smallest dimension
  • fps — framerate of video
  • hdr — the dynamic range of the video (DV > HDR12 > HDR10+ > HDR10 > HLG > SDR)
  • channels — the number of audio channels
  • tbr — total average bitrate in kbps
  • vbr — average video bitrate in kbps
  • abr — average audio bitrate in kbps
  • br — average bitrate in kbps, tbr/vbr/abr
  • asr — audio sample rate in Hz
Deprecation warning bestie 🚨: many of these fields have (currently undocumented) aliases that may be removed in a future version. It is recommended to use only the documented field names. Don't get attached to the secret ones, okay?

All fields, unless specified otherwise, are sorted in descending order. To reverse this, prefix the field with a +. E.g. +res prefers formats with the smallest resolution. Additionally, you can suffix a preferred value for the fields, separated by a :. E.g. res:720 prefers larger videos, but no larger than 720p — and the smallest video if there are no videos less than 720p. For codec and ext, you can provide two preferred values, the first for video and the second for audio. E.g. +codec:avc:m4a (equivalent to +vcodec:avc,+acodec:m4a) sets the video codec preference to h264 > h265 > vp9 > vp9.2 > av01 > vp8 > h263 > theora and audio codec preference to mp4a > aac > vorbis > opus > mp3 > ac3 > dts. You can also make the sorting prefer the nearest values to the provided ones by using ~ as the delimiter — e.g. filesize~1G prefers the format with filesize closest to 1 GiB. so smart 💖

The fields hasvid and ie_pref are always given highest priority in sorting, irrespective of the user-defined order. This behavior can be changed by using --format-sort-force. Apart from these, the default order used is: lang,quality,res,fps,hdr:12,vcodec,channels,acodec,size,br,asr,proto,ext,hasaud,source,id. The extractors may override this default order, but they cannot override the user-provided order. Facts. 👑

Note that the default for hdr is hdr:12 — i.e. Dolby Vision is not preferred. This choice was made since DV formats are not yet fully compatible with most devices. This may be changed in the future. Ngl kind of a vibe check on DV, but we move. 🦋

If your format selector is worst, the last item is selected after sorting. This means it will select the format that is worst in all respects. Most of the time, what you actually want is the video with the smallest filesize instead. So it is generally better to use -f best -S +size,+br,+res,+fps. trust me on this one 🙏

If you use the -S/--format-sort option multiple times, each subsequent sorting argument will be prepended to the previous one, and only the highest priority entry of any duplicated field will be preserved. E.g. -S proto -S res is equivalent to -S res,proto, and -S res:720,fps -S vcodec,res:1080 is equivalent to -S vcodec,res:1080,fps. You can use --format-sort-reset to disregard any previously passed -S/--format-sort arguments and reset to the default order. iconic behaviour honestly ✨

Tip bestie: you can use the -v -F to see how the formats have been sorted (worst to best). chef's kiss. 🔥


💅 Format Selection examples 💅

# Download and merge the best video-only format and the best audio-only format,
# or download the best combined format if video-only format is not available
$ yt-dlp -f "bv+ba/b"

# Download best format that contains video,
# and if it doesn't already have an audio stream, merge it with best audio-only format
$ yt-dlp -f "bv*+ba/b"

# Same as above
$ yt-dlp

# Download the best video-only format and the best audio-only format without merging them
# For this case, an output template should be used since
# by default, bestvideo and bestaudio will have the same file name.
$ yt-dlp -f "bv,ba" -o "%(title)s.f%(format_id)s.%(ext)s"

# Download and merge the best format that has a video stream,
# and all audio-only formats into one file
$ yt-dlp -f "bv*+mergeall[vcodec=none]" --audio-multistreams

# Download and merge the best format that has a video stream,
# and the best 2 audio-only formats into one file
$ yt-dlp -f "bv*+ba+ba.2" --audio-multistreams

# The following examples show the old method (without -S) of format selection
# and how to use -S to achieve a similar but (generally) better result

# Download the worst video available (old method)
$ yt-dlp -f "wv*+wa/w"

# Download the best video available but with the smallest resolution
$ yt-dlp -S "+res"

# Download the smallest video available
$ yt-dlp -S "+size,+br"

# Download the best mp4 video available, or the best video if no mp4 available
$ yt-dlp -f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4] / bv*+ba/b"

# Download the best video with the best extension
# (For video, mp4 > mov > webm > flv. For audio, m4a > aac > mp3 ...)
$ yt-dlp -S "ext"

# Download the best video available but no better than 480p,
# or the worst video if there is no video under 480p
$ yt-dlp -f "bv*[height<=480]+ba/b[height<=480] / wv*+ba/w"

# Download the best video available with the largest height but no better than 480p,
# or the best video with the smallest resolution if there is no video under 480p
$ yt-dlp -S "height:480"

# Download the best video available with the largest resolution but no better than 480p,
# or the best video with the smallest resolution if there is no video under 480p
# Resolution is determined by using the smallest dimension.
# So this works correctly for vertical videos as well
$ yt-dlp -S "res:480"

# Download the best video (that also has audio) but no bigger than 50 MB,
# or the worst video (that also has audio) if there is no video under 50 MB
$ yt-dlp -f "b[filesize<50M] / w"

# Download the largest video (that also has audio) but no bigger than 50 MB,
# or the smallest video (that also has audio) if there is no video under 50 MB
$ yt-dlp -f "b" -S "filesize:50M"

# Download the best video (that also has audio) that is closest in size to 50 MB
$ yt-dlp -f "b" -S "filesize~50M"

# Download best video available via direct link over HTTP/HTTPS protocol,
# or the best video available via any protocol if there is no such video
$ yt-dlp -f "(bv*+ba/b)[protocol^=http][protocol!*=dash] / (bv*+ba/b)"

# Download best video available via the best protocol
# (https/ftps > http/ftp > m3u8_native > m3u8 > http_dash_segments ...)
$ yt-dlp -S "proto"

# Download the best video with either h264 or h265 codec,
# or the best video if there is no such video
$ yt-dlp -f "(bv*[vcodec~='^((he|a)vc|h26[45])']+ba) / (bv*+ba/b)"

# Download the best video with best codec no better than h264,
# or the best video with worst codec if there is no such video
$ yt-dlp -S "codec:h264"

# Download the best video with worst codec no worse than h264,
# or the best video with best codec if there is no such video
$ yt-dlp -S "+codec:h264"

# More complex examples
# Download the best video no better than 720p preferring framerate greater than 30,
# or the worst video (still preferring framerate greater than 30) if there is no such video
$ yt-dlp -f "((bv*[fps>30]/bv*)[height<=720]/(wv*[fps>30]/wv*)) + ba / (b[fps>30]/b)[height<=720]/(w[fps>30]/w)"

# Download the video with the largest resolution no better than 720p,
# or the video with the smallest resolution available if there is no such video,
# preferring larger framerate for formats with the same resolution
$ yt-dlp -S "res:720,fps"

# Download the video with smallest resolution no worse than 480p,
# or the video with the largest resolution available if there is no such video,
# preferring better codec and then larger total bitrate for the same resolution
$ yt-dlp -S "+res:480,codec,br"

✨ MODIFYING METADATA ✨

Okay so here's the tea, bestie: all that metadata your extractor pulls? You can literally edit it. The two icons that do it are --parse-metadata and --replace-in-metadata. 💅

--replace-in-metadata FIELDS REGEX REPLACE does exactly what it says on the tin — it swaps out text in any metadata field, using a Python regular expression (https://docs.python.org/3/library/re.html#regular-expression-syntax). And the replace string can use backreferences (https://docs.python.org/3/library/re.html?highlight=backreferences#re.sub) for the advanced girlies out there. Obsessed.

The general syntax of --parse-metadata FROM:TO is: you give it the name of a field (or an output template) to extract data from, then the format to interpret it as, separated by a colon :. For TO you can use any of these three:

And yes, you can stack this option multiple times to parse and modify a whole bunch of fields. Go off. 🎀

Note that these options preserve their relative order — which means replacements can be made in parsed fields and vice versa. Order matters, iykyk. Also, any field you create this way can be used in the output template, and it'll also affect the media file's metadata added when you use --embed-metadata. Main character behavior honestly.

This option also has a few special uses, so listen up:

  • You can download an additional URL based on the metadata of the currently downloaded video. To do this, set the field additional_urls to the URL that you want to download. E.g. --parse-metadata "description:(?P<additional_urls>https?://www\.vimeo\.com/\d+)" will download the first vimeo video found in the description. Iconic.
  • You can use this to change the metadata that is embedded in the media file. To do that, set the value of the corresponding field with a meta_ prefix. For example, any value you set to the meta_description field will be added to the description field in the file — you can use this to set a different "description" and "synopsis". To modify the metadata of individual streams, use the meta<n>_ prefix (e.g. meta1_language). Any value set to the meta_ field will overwrite all default values. Big power move. 💖
Note: Metadata modification happens before format selection, post-extraction and other post-processing operations. Some fields may be added or changed during these steps, overriding your changes. So like, don't come for yt-dlp when your edit gets overwritten, mmkay? 😭

For reference, these are the fields yt-dlp adds by default to the file metadata:

  • title — from track or title
  • date — from upload_date
  • description, synopsis — from description
  • purl, comment — from webpage_url
  • track — from track_number
  • artist — from artist, artists, creator, creators, uploader or uploader_id
  • composer — from composer or composers
  • genre — from genre, genres, categories or tags
  • album — from album or series
  • album_artist — from album_artist or album_artists
  • disc — from disc_number
  • show — from series
  • season_number — from season_number
  • episode_id — from episode or episode_id
  • episode_sort — from episode_number
  • language of each stream — from the format's language
Note: The file format may not support some of these fields. Rude of the file format, but okay. 💀

🌟 Modifying metadata examples 🌟

Real examples time, because vibes only get you so far:

# Interpret the title as "Artist - Title"
$ yt-dlp --parse-metadata "title:%(artist)s - %(title)s"

# Regex example
$ yt-dlp --parse-metadata "description:Artist - (?P<artist>.+)"

# Copy the episode field to the title field (with FROM and TO as single fields)
$ yt-dlp --parse-metadata "episode:title"

# Set title as "Series name S01E05"
$ yt-dlp --parse-metadata "%(series)s S%(season_number)02dE%(episode_number)02d:%(title)s"

# Prioritize uploader as the "artist" field in video metadata
$ yt-dlp --parse-metadata "%(uploader|)s:%(meta_artist)s" --embed-metadata

# Set "comment" field in video metadata using description instead of webpage_url,
# handling multiple lines correctly
$ yt-dlp --parse-metadata "description:(?s)(?P<meta_comment>.+)" --embed-metadata

# Do not set any "synopsis" in the video metadata
$ yt-dlp --parse-metadata ":(?P<meta_synopsis>)"

# Remove "formats" field from the infojson by setting it to an empty string
$ yt-dlp --parse-metadata "video::(?P<formats>)" --write-info-json

# Replace all spaces and "_" in title and uploader with a `-`
$ yt-dlp --replace-in-metadata "title,uploader" "[ _]" "-"

🔍 EXTRACTOR ARGUMENTS 🔍

Some extractors accept extra arguments, and you pass them in with --extractor-args KEY:ARGS. Here's the deal: ARGS is a ; (semicolon) separated string of ARG=VAL1,VAL2. E.g. --extractor-args "youtube:player-client=tv,mweb;formats=incomplete" --extractor-args "twitter:api=syndication". Slay.

Note: In CLI, ARG can use - instead of _; e.g. youtube:player-client" becomes youtube:player_client"

The following extractors use this feature:

📺 youtube 📺

  • lang — Prefer translated metadata (title, description etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to en translated. See youtube/_base.py (https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes.
  • skip — One or more of hls, dash or translated_subs to skip extraction of the m3u8 manifests, dash manifests and auto-translated subtitles (https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively.
  • player_client — Clients to extract video data from. The currently available clients are web, web_safari, web_embedded, web_music, web_creator, mweb, ios, visionos, android, android_vr, tv, tv_downgraded, tv_samsung, and tv_simply. By default, tv_samsung,visionos,web is used. If no JavaScript runtime/engine is available, then web and tv_samsung are omitted. If logged-in cookies are passed to yt-dlp, then web_embedded,tv_samsung,web is used for free accounts and web_creator,tv_samsung,web is used for premium accounts. The web_music client is added for music.youtube.com URLs when logged-in cookies are used. The web_embedded client is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable). The web_embedded client may be added as a fallback if android_vr or visionos is unable to access a video. The web_creator client is added for age-restricted videos if account age-verification is required. Some clients, such as web_creator and web_music, require a po_token for their formats to be downloadable. Some clients, such as web_creator, will only work with authentication. Not all clients support authentication via cookies. You can use default for the default clients, or you can use all for all clients (not recommended). You can prefix a client with - to exclude it, e.g. youtube:player_client=default,-web. Serving face, but read the fine print bestie 💅
  • player_skip — Skip some network requests that are generally needed for robust extraction. One or more of configs (skip client configs), webpage (skip initial webpage), js (skip js player), initial_data (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See #860 (https://github.com/yt-dlp/yt-dlp/pull/860) and #12826 (https://github.com/yt-dlp/yt-dlp/issues/12826) for more details. The way that these are risky… 😬
  • webpage_skip — Skip extraction of embedded webpage data. One or both of player_response, initial_data. These options are for testing purposes and don't skip any network requests. Neither is skipped by default; however, if a player_js_version value other than actual is used, then webpage_skip=player_response is implied.
  • webpage_client — Client to use for the video webpage request. One of web (default) or web_safari.
  • player_params — YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
  • player_js_variant — The player javascript variant to use for n/sig deciphering. The known variants are: main, tcc, tce, es5, es6, es6_tcc, es6_tce, tv, tv_es6, phone, house. The default is main, and the others are for debugging purposes. You can use actual to go with what is prescribed by the site.
  • player_js_version — The player javascript version to use for n/sig deciphering, in the format of signature_timestamp@hash (e.g. 20348@0004de42). The default is to use what is prescribed by the site, and can be selected with actual. Using any other value will imply webpage_skip=player_response.
  • comment_sort — top or new (default) — choose comment sorting mode (on YouTube's side).
  • max_comments — Limit the amount of comments to gather. Comma-separated list of integers representing max-comments,max-parents,max-replies,max-replies-per-thread,max-depth. Default is all,all,all,all,all. 🗣️
    • A max-depth value of 1 will discard all replies, regardless of the max-replies or max-replies-per-thread values given.
    • E.g. all,all,1000,10,2 will get a maximum of 1000 replies total, with up to 10 replies per thread, and only 2 levels of depth (i.e. top-level comments plus their immediate replies). 1000,all,100 will get a maximum of 1000 comments, with a maximum of 100 replies total.
  • formats — okay so this changes the types of formats to return. You can pass dashy (convert HTTP to DASH), duplicate (identical content but different URLs or protocol; includes dashy), incomplete (cannot be downloaded completely — live and post-live dash, post-live m3u8, and live adaptive https without --live-from-start), or missing_pot (include formats that require a PO Token but are missing one) 💅
  • innertube_host — Innertube API host to use for all API requests; e.g. studio.youtube.com, youtubei.googleapis.com. Note that cookies exported from one subdomain will not work on others 🍓
  • innertube_key — Innertube API key to use for all API requests. By default, no API key is used ✨
  • raise_incomplete_data — Incomplete Data Received raises an error instead of reporting a warning 😭
  • data_sync_id — Overrides the account Data Sync ID used in Innertube API requests. This may be needed if you are using an account with youtube:player_skip=webpage,configs or youtubetab:skip=webpage 👑
  • visitor_data — Overrides the Visitor Data used in Innertube API requests. This should be used with player_skip=webpage,configs and without cookies. Note: this may have adverse effects if used improperly. If a session from a browser is wanted, you should pass cookies instead (which contain the Visitor ID) 🫶
  • po_token — Proof of Origin (PO) Token(s) to use. Comma-separated list of PO Tokens in the format CLIENT.CONTEXT+PO_TOKEN, e.g. youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY. Context can be any of gvs (Google Video Server URLs), player (Innertube player request) or subs (Subtitles) 🔥
  • pot_trace — Enable debug logging for PO Token fetching. Either true or false (default) 🌸
  • fetch_pot — Policy to use for fetching a PO Token from providers. One of always (always try fetch a PO Token regardless if the client requires one for the given context), never (never fetch a PO Token), or auto (default; only fetch a PO Token if the client requires one for the given context) 🦋
  • jsc_trace — Enable debug logging for JS Challenge fetching. Either true or false (default) ⭐
  • use_ad_playback_context — Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the mweb and web_music player clients. Either true or false (default) 💀

youtube-ejs ✨

  • jitless — Run supported Javascript engines in JIT-less mode. Supported runtimes are deno, node and bun. Provides better security at the cost of performance/speed. Do note that node and bun are still considered insecure. Either true or false (default) 🎀

youtubepot-webpo 🎀

  • bind_to_visitor_id — Whether to use the Visitor ID instead of Visitor Data for caching WebPO tokens. Either true (default) or false 💖

youtubetab (YouTube playlists, channels, feeds, etc.) 🌈

  • skip — One or more of webpage (skip initial webpage download), authcheck (allow the download of playlists requiring authentication when no initial webpage is downloaded. This may cause unwanted behavior, see #1122 — https://github.com/yt-dlp/yt-dlp/pull/1122 — for more details) 🙈
  • approximate_date — Extract approximate upload_date and timestamp in flat-playlist. This may cause date-based filters to be slightly off 🦋

generic 🧚

  • fragment_query — Passthrough any query in mpd/m3u8 manifest URLs to their fragments if no value is provided, or else apply the query string given as fragment_query=VALUE. Note that if the stream has an HLS AES-128 key, then the query parameters will be passed to the key URI as well, unless the key_query extractor-arg is passed, or unless an external key URI is provided via the hls_key extractor-arg. Does not apply to ffmpeg 💅
  • variant_query — Passthrough the master m3u8 URL query to its variant playlist URLs if no value is provided, or else apply the query string given as variant_query=VALUE 🌸
  • key_query — Passthrough the master m3u8 URL query to its HLS AES-128 decryption key URI if no value is provided, or else apply the query string given as key_query=VALUE. Note that this will have no effect if the key URI is provided via the hls_key extractor-arg. Does not apply to ffmpeg 💜
  • hls_key — An HLS AES-128 key URI or key (as hex), and optionally the IV (as hex), in the form of (URI|KEY)[,IV]; e.g. generic:hls_key=ABCDEF1234567980,0xFEDCBA0987654321. Passing any of these values will force usage of the native HLS downloader and override the corresponding values found in the m3u8 playlist 🎀
  • is_live — Bypass live HLS detection and manually set live_status — a value of false will set not_live, any other value (or no value) will set is_live 🔥
  • impersonate — Target(s) to try and impersonate with the initial webpage request; e.g. generic:impersonate=safari,chrome-110. Use generic:impersonate to impersonate any available target, and use generic:impersonate=false to disable impersonation (default) 🦋

vikichannel 🎀

  • video_types — Types of videos to download - one or more of episodes, movies, clips, trailers ⭐

youtubewebarchive 🌈

  • check_all — Try to check more at the cost of more requests. One or more of thumbnails, captures ✨

gamejolt 🍓

  • comment_sort — hot (default), you (cookies needed), top, new — choose comment sorting mode (on GameJolt's side) 🍓

hotstar 👑

  • res — resolution to ignore - one or more of sd, hd, fhd 💅
  • vcodec — vcodec to ignore - one or more of h264, h265, dvh265 💅
  • dr — dynamic range to ignore - one or more of sdr, hdr10, dv 💅

instagram 🌸

  • app_id — The value of the X-IG-App-ID header used for API requests. Can be the actual ID number, ios, or web (default) 🦋

niconicochannelplus 🎀

  • max_comments — Maximum number of comments to extract - default is 120 😭

tiktok 🔥

  • api_hostname — Hostname to use for mobile API calls, e.g. api22-normal-c-alisg.tiktokv.com ✨
  • app_name — Default app name to use with mobile API calls, e.g. trill ✨
  • app_version — Default app version to use with mobile API calls - should be set along with manifest_app_version, e.g. 34.1.2 ✨
  • manifest_app_version — Default numeric app version to use with mobile API calls, e.g. 2023401020 ✨
  • aid — Default app ID to use with mobile API calls, e.g. 1180 ✨
  • app_info — Enable mobile API extraction with one or more app info strings in the format of <iid>/[app_name]/[app_version]/[manifest_app_version]/[aid], where iid is the unique app install ID. iid is the only required value; all other values and their / separators can be omitted, e.g. tiktok:app_info=1234567890123456789 or tiktok:app_info=123,456/trill///1180,789//34.0.1/340001 🫶
  • device_id — Enable mobile API extraction with a genuine device ID to be used with mobile API calls. Default is a random 19-digit string 👑

rokfinchannel ⭐

  • tab — Which tab to download - one of new, top, videos, podcasts, streams, stacks ⭐

twitter 🦋

  • api — Select one of graphql (default), legacy or syndication as the API for tweet extraction. Has no effect if logged in 💅

stacommu, wrestleuniverse 💜

  • device_id — UUID value assigned by the website and used to enforce device limits for paid livestream content. Can be found in browser local storage 👑

twitch 💜

  • client_id — Client ID value to be sent with GraphQL requests, e.g. twitch:client_id=kimne78kx3ncx6brgo4mv6wki5h1ko 🔥

nhkradirulive (NHK らじる★らじる LIVE) 🌸

  • area — Which regional variation to extract. Valid areas are: sapporo, sendai, tokyo, nagoya, osaka, hiroshima, matsuyama, fukuoka. Defaults to tokyo 🎀

nflplusreplay ⭐

  • type — Type(s) of game replays to extract. Valid types are: full_game, full_game_spanish, condensed_game and all_22. You can use all to extract all available replay types, which is the default ⭐

jiocinema 🎀

  • refresh_token — The refreshToken UUID from browser local storage can be passed to extend the life of your login session when logging in with token as username and the accessToken from browser local storage as password 💖

jiosaavn 🌸

  • bitrate — Audio bitrates to request. One or more of 16, 32, 64, 128, 320. Default is 128,320 🌸

afreecatvlive 🦋

  • cdn — One or more CDN IDs to use with the API call for stream URLs, e.g. gcp_cdn, gs_cdn_pc_app, gs_cdn_mobile_web, gs_cdn_pc_web ✨

soundcloud 🎀

  • formats — Formats to request from the API. Requested values should be in the format of {protocol}_{codec}, e.g. hls_opus,http_aac. The * character functions as a wildcard, e.g. *_mp3, and can be passed by itself to request all formats. Known protocols include http, hls and hls-aes; known codecs include aac, opus and mp3. Original download formats are always extracted. Default is http_aac,hls_aac,http_opus,hls_opus,http_mp3,hls_mp3 💅

orfon (orf:on) 🎀

  • prefer_segments_playlist — Prefer a playlist of program segments instead of a single complete video when available. If individual segments are desired, use --concat-playlist never --extractor-args "orfon:prefer_segments_playlist" 🫶

bilibili 🦋

  • prefer_multi_flv — Prefer extracting flv formats over mp4 for older videos that still provide legacy formats ⭐

sonylivseries 💜

  • sort_order — Episode sort order for series extraction - one of asc (ascending, oldest first) or desc (descending, newest first). Default is asc 🎀

streaks 🔥

  • api_key — API key for the X-Streaks-Api-Key header 👑

tver 🌸

  • backend — Backend API to use for extraction - one of streaks (default) or brightcove (deprecated) 💀

vimeo 🦋

  • client — Client to extract video data from. The currently available clients are android and web. Only one client can be used. The web client is used by default, and it only works with account cookies or login credentials. The android client only works with previously cached OAuth tokens 💅
  • original_format_policy — Policy for when to try extracting original formats. One of always, never, or auto. The default auto policy tries to avoid exceeding the web client's API rate-limit by only making an extra request when Vimeo publicizes the video's downloadability ⭐

adn 🎀

  • profile_id — The numeric ID of the premium account profile that will be used to download videos (default: 1) 💖

zan ✨

  • split_angles — Split multi-angle streams into separate angle formats. Forces re-encoding of the video stream during download, and requires ffmpeg. Either true or false (default) 🧚
Note: These options may be changed/removed in the future without concern for backward compatibility — periodt 💀

✨ INSTALLATION ✨

okay bestie, so like… you literally have three ways to get yt-dlp on your machine: use the binaries, use pip (<https://pypi.org/project/yt-dlp>), or use one of the third-party package managers. That's it, that's the vibe. Detailed instructions live in the wiki over at <https://github.com/yt-dlp/yt-dlp/wiki/Installation> if you want the full step-by-step 🫶

💡 Note: the manpages, shell completion (autocomplete) files etc. are all available inside the source tarball — <https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.tar.gz>

🌙 UPDATE

if you're on the release binaries, just hit yt-dlp -U and you're literally updated. slay.

if you installed with pip (<https://github.com/yt-dlp/yt-dlp/wiki/Installation#with-pip>), simply re-run the same command that was used to install the program. no cap, that's the whole trick.

for other third-party package managers, check the wiki (<https://github.com/yt-dlp/yt-dlp/wiki/Installation#third-party-package-managers>) or refer to their documentation. iykyk.

there are currently three release channels for binaries: stable, nightly and master. let me break it down 🦋

  • stable is the default channel, which offers releases published on a (mostly) monthly schedule. While it is named stable due to many of its changes having been tested by users of the nightly or master release channels, the latest stable release is often "stale" and prone to external breakage (i.e. sites changing things on their end and breaking yt-dlp). yeah… it's giving "stable in name only" bestie 💀
  • the nightly channel offers releases that publish shortly before midnight UTC on any day that sees changes to the codebase. This channel serves as a snapshot of the project's development, and it is the recommended channel for regular users of yt-dlp. The nightly releases are available from yt-dlp/yt-dlp-nightly-builds (<https://github.com/yt-dlp/yt-dlp-nightly-builds/releases>) or as development releases of the yt-dlp PyPI package (which can be installed with pip's --pre flag). periodt. 👑
  • the master channel offers "canary" releases that publish after each push to the master branch. This channel will always provide the very latest fixes and features, but may be prone to bugs or regressions. The master releases are available from yt-dlp/yt-dlp-master-builds (<https://github.com/yt-dlp/yt-dlp-master-builds/releases>). main character energy but at your own risk 🙈

when using --update/-U, a release binary will only update to its current channel. --update-to CHANNEL can be used to switch to a different channel when a newer version is available. --update-to [CHANNEL@]TAG can also be used to upgrade or downgrade to specific tags from a channel.

you may also use --update-to <repository> (<owner>/<repository>) to update to a channel on a completely different repository. Be careful with what repository you are updating to though, there is no verification done for binaries from different repositories. like, fr fr, be careful 😭

example usage ✨:

  • yt-dlp --update-to master — switch to the master channel and update to its latest release
  • yt-dlp --update-to stable@2023.07.06 — upgrade/downgrade to release to stable channel tag 2023.07.06
  • yt-dlp --update-to 2023.10.07 — upgrade/downgrade to tag 2023.10.07 if it exists on the current channel
  • yt-dlp --update-to example/yt-dlp@2023.09.24 — upgrade/downgrade to the release from the example/yt-dlp repository, tag 2023.09.24
⚠️ Important: Any user experiencing an issue with the stable release should install or update to the nightly release before submitting a bug report — like, they will literally ask you to do this first, bestie.
# To update to nightly from stable executable/binary:
yt-dlp --update-to nightly

# To install nightly with pip:
python -m pip install -U --pre "yt-dlp[default]"

when running a yt-dlp version that is older than 90 days, you will see a warning message suggesting to update to the latest version. You can suppress this warning by adding --no-update to your command or configuration file. (that naggy little reminder? yeah, you can mute it 🔇)


🧚 DEPENDENCIES

Python versions 3.11+ (CPython and PyPy) are supported. Other versions and implementations may or may not work correctly. no promises, okay?

While all the other dependencies are optional, ffmpeg, ffprobe, yt-dlp-ejs and a supported JavaScript runtime/engine are highly recommended. like… please. for me. 🥺

Since ffmpeg is such an important dependency, we provide our own builds at yt-dlp/FFmpeg-Builds (<https://github.com/yt-dlp/FFmpeg-Builds>). In the past, patches were applied to these builds in order to fix common issues for yt-dlp users, but currently our builds are equivalent to upstream ffmpeg. See the readme (<https://github.com/yt-dlp/FFmpeg-Builds#patches-applied>) for details

💀 Important: What you need is the ffmpeg binary, NOT the Python package of the same name (<https://pypi.org/project/ffmpeg>). do not do this to yourself bestie.

A JavaScript runtime/engine like deno (<https://deno.land>) (recommended), node.js (<https://nodejs.org>), bun (<https://bun.sh>), or QuickJS (<https://bellard.org/quickjs/>) is also required to run yt-dlp-ejs. See the wiki (<https://github.com/yt-dlp/yt-dlp/wiki/EJS>). it's giving "you need a whole JS engine for this" but that's just how it is 🌈

🌐 Networking

🎭 Impersonation

The following provide support for impersonating browser requests. This may be required for some sites that employ TLS fingerprinting. (sneaky sites being sneaky, we love to see it 🕵️‍♀️)

🏷️ Metadata

🍓 Misc

💀 Deprecated


To use or redistribute the dependencies, you must agree to their respective licensing terms. (yes, actually read them, bestie — they're legally binding, i don't make the rules 💅)

The standalone release binaries are built with the Python interpreter and the packages marked with * included.

If you do not have the necessary dependencies for a task you are attempting, yt-dlp will warn you. All the currently available dependencies are visible at the top of the --verbose output. very handy for the "wait, what do i actually have installed" moment 👀


🔨 COMPILE

📦 Standalone PyInstaller Builds

To build the standalone executable, you must have Python and pyinstaller (plus any of yt-dlp's optional dependencies if needed). The executable will be built for the same CPU architecture as the Python used.

You can run the following commands:

python devscripts/install_deps.py --include-group pyinstaller
python devscripts/make_lazy_extractors.py
python -m bundle.pyinstaller

On some systems, you may need to use py or python3 instead of python. classic, honestly 🙄

python -m bundle.pyinstaller accepts any arguments that can be passed to pyinstaller, such as --onefile/-F or --onedir/-D, which is further documented here (<https://pyinstaller.org/en/stable/usage.html#what-to-generate>).

💡 Note: Pyinstaller versions below 4.4 do not support (<https://github.com/pyinstaller/pyinstaller#requirements-and-tested-platforms>) Python installed from the Windows store without using a virtual environment.
⚠️ Important: Running pyinstaller directly instead of using python -m bundle.pyinstaller is not officially supported. This may or may not work correctly. like… it might just work? but don't come crying to us 😭

🐧 Platform-independent Binary (UNIX)

You will need the build tools python (3.11+), zip, make (GNU), pandoc* and pytest*.

After installing these, simply run make. that's it. iconic. ✨

okay so literally you can also just run make yt-dlp instead if all you want is the binary and you're not trying to touch any of the extra files 💅 (and the build tools marked with * are not needed for this, so don't stress about those bestie)

little helper scripts that live in devscripts/, one for each vibe:

  • devscripts/install_deps.py — installs the dependencies yt-dlp needs. that's the whole job 🫶
  • devscripts/update-version.py — bumps the version number based on the current date ✨
  • devscripts/set-variant.py — sets the build variant of the executable 🦋
  • devscripts/make_changelog.py — makes a markdown changelog out of short commit messages and updates the CONTRIBUTORS file, multitasking icon 💅
  • devscripts/make_lazy_extractors.py — creates lazy extractors. run this before building the binaries (any variant) and their startup performance gets better, no cap 🔥. and if you want to forcefully disable lazy extractor loading, just set the environment variable YTDLP_NO_LAZY_EXTRACTORS to something nonempty, periodt.
Note: see their --help for more info 👑

🌈 Forking the Project 🌈

if you fork the project on GitHub, you can run your fork's build workflow and it'll automatically build the selected version(s) as artifacts, main character energy fr fr. alternatively you can run the release workflow, or enable the nightly workflow to create full (pre-)releases. pick your fighter 💖


🦋 PLUGINS 🦋

okay this part is important so like, listen: all plugins are imported even if not invoked, and there are no checks performed on plugin code. use plugins at your own risk and only if you trust the code, bestie 😭 this is not the moment to be reckless.

Plugins can be of <type>s extractor or postprocessor, and here's the breakdown:

  • Extractor plugins do not need to be enabled from the CLI — they're automatically invoked when the input URL is suitable for it ✨
  • Extractor plugins take priority over built-in extractors 👑
  • Postprocessor plugins can be invoked using --use-postprocessor NAME

Plugins are loaded from the namespace packages yt_dlp_plugins.extractor and yt_dlp_plugins.postprocessor.

so in other words, the file structure on your disk looks something like this:

yt_dlp_plugins/
    extractor/
        myplugin.py
    postprocessor/
        myplugin.py

yt-dlp looks for these yt_dlp_plugins namespace folders in many locations (see below, i gotchu) and loads in plugins from all of them. and if you set the environment variable YTDLP_NO_PLUGINS to something nonempty, plugin loading is disabled entirely. poof, gone 🙈

see the wiki for some known plugins (https://github.com/yt-dlp/yt-dlp/wiki/Plugins)

🌸 Installing Plugins 🌸

plugins can be installed using various methods and locations, so pick whichever one fits your life:

  1. Configuration directories: plugin packages (containing a yt_dlp_plugins namespace folder) can be dropped into the following standard configuration locations:
    • User Plugins
      • ${XDG_CONFIG_HOME}/yt-dlp/plugins/<package name>/yt_dlp_plugins/ (recommended on Linux/macOS)
      • ${XDG_CONFIG_HOME}/yt-dlp-plugins/<package name>/yt_dlp_plugins/
      • ${APPDATA}/yt-dlp/plugins/<package name>/yt_dlp_plugins/ (recommended on Windows)
      • ${APPDATA}/yt-dlp-plugins/<package name>/yt_dlp_plugins/
      • ~/.yt-dlp/plugins/<package name>/yt_dlp_plugins/
      • ~/yt-dlp-plugins/<package name>/yt_dlp_plugins/
    • System Plugins
      • /etc/yt-dlp/plugins/<package name>/yt_dlp_plugins/
      • /etc/yt-dlp-plugins/<package name>/yt_dlp_plugins/
  2. Executable location: plugin packages can similarly be installed in a yt-dlp-plugins directory under the executable location (this one's recommended for portable installations 🧚):
    • Binary: where <root-dir>/yt-dlp.exe, <root-dir>/yt-dlp-plugins/<package name>/yt_dlp_plugins/
    • Source: where <root-dir>/yt_dlp/__main__.py, <root-dir>/yt-dlp-plugins/<package name>/yt_dlp_plugins/
  3. pip and other locations in PYTHONPATH
    • plugin packages can be installed and managed using pip. see yt-dlp-sample-plugins (https://github.com/yt-dlp/yt-dlp-sample-plugins) for an example.
      • > Note: plugin files between plugin packages installed with pip must have unique filenames 👀
    • any path in PYTHONPATH is searched in for the yt_dlp_plugins namespace folder.
      • > Note: this does not apply for Pyinstaller builds.

.zip, .egg and .whl archives containing a yt_dlp_plugins namespace folder in their root are also supported as plugin packages. yes, even zipped, we love convenience 💖

  • e.g. ${XDG_CONFIG_HOME}/yt-dlp/plugins/mypluginpkg.zip where mypluginpkg.zip contains yt_dlp_plugins/<type>/myplugin.py

run yt-dlp with --verbose to check if the plugin has actually been loaded — vibe check your plugin, basically 🔍

⭐ Developing Plugins ⭐

see the yt-dlp-sample-plugins (https://github.com/yt-dlp/yt-dlp-sample-plugins) repo for a template plugin package, and the Plugin Development (https://github.com/yt-dlp/yt-dlp/wiki/Plugin-Development) section of the wiki for a plugin development guide. everything you need, iconically documented ✨

all public classes with a name ending in IE/PP are imported from each file for extractors and postprocessors respectively. this respects underscore prefix (e.g. _MyBasePluginIE is private) and __all__. modules can similarly be excluded by prefixing the module name with an underscore (e.g. _myplugin.py). sneaky little underscore = do not perceive me 🙈

and if you want to replace an existing extractor with a subclass of one, set the plugin_name class keyword argument (e.g. class MyPluginIE(ABuiltInIE, plugin_name='myplugin') will replace ABuiltInIE with MyPluginIE). since the extractor replaces the parent, you should exclude the subclass extractor from being imported separately by making it private using one of the methods described above — don't let it get imported twice bestie, that's chaos 💀

if you're a plugin author, add yt-dlp-plugins (https://github.com/topics/yt-dlp-plugins) as a topic to your repository for discoverability 🏷️

see the Developer Instructions (https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) on how to write and test an extractor 🫶

🫶 EMBEDDING YT-DLP 🫶

okay bestie, here's the tea: yt-dlp tries its absolute best to be a good little command-line citizen, so it should be callable from any programming language. 💅

Your program should avoid parsing the normal stdout, because that output may change in future versions. Instead, use options like -J, --print, --progress-template, --exec etc to create console output you can reliably reproduce and parse. no cap. ✨

From a Python program, you can embed yt-dlp in a much more powerful way, like this:

from yt_dlp import YoutubeDL

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']
with YoutubeDL() as ydl:
    ydl.download(URLS)

Most likely, you'll want to use various options. For a list of options available, have a look at yt_dlp/YoutubeDL.py or help(yt_dlp.YoutubeDL) in a Python shell. If you are already familiar with the CLI, you can use devscripts/cli_to_api.py (https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py) to translate any CLI switches to YoutubeDL params. slay. 🎀

Tip: If you are porting your code from youtube-dl to yt-dlp, one important point to look out for is that we do not guarantee the return value of YoutubeDL.extract_info to be json serializable, or even be a dictionary. It will be dictionary-like, but if you want to ensure it is a serializable dictionary, pass it through YoutubeDL.sanitize_info as shown in the example below. 👑

💖 Embedding examples 💖

🌸 Extracting information 🌸

First up: pulling info without downloading. Very demure, very mindful.

import json
import yt_dlp

URL = 'https://www.youtube.com/watch?v=YE7VzlLtp-4'

# ℹ️ See help(yt_dlp.YoutubeDL) for a list of available options and public functions
ydl_opts = {}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    info = ydl.extract_info(URL, download=False)

    # ℹ️ ydl.sanitize_info makes the info json-serializable
    print(json.dumps(ydl.sanitize_info(info)))

🦋 Download using an info-json 🦋

When you already have the info-json and just need the download part. Iconic.

import yt_dlp

INFO_FILE = 'path/to/video.info.json'

with yt_dlp.YoutubeDL() as ydl:
    error_code = ydl.download_with_info_file(INFO_FILE)

print('Some videos failed to download' if error_code
      else 'All videos successfully downloaded')

🎀 Extract audio 🎀

Just the audio, bestie. ffmpeg is doing the heavy lifting.

import yt_dlp

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']

ydl_opts = {
    'format': 'm4a/bestaudio/best',
    # ℹ️ See help(yt_dlp.postprocessor) for a list of available Postprocessors and their arguments
    'postprocessors': [{  # Extract audio using ffmpeg
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'm4a',
    }]
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    error_code = ydl.download(URLS)

💜 Filter videos 💜

You can filter what actually gets downloaded. This example only grabs videos longer than a minute, or videos with unknown duration. 🫶

import yt_dlp

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']

def longer_than_a_minute(info, *, incomplete):
    """Download only videos longer than a minute (or with unknown duration)"""
    duration = info.get('duration')
    if duration and duration < 60:
        return 'The video is too short'

ydl_opts = {
    'match_filter': longer_than_a_minute,
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    error_code = ydl.download(URLS)

🌈 Adding logger and progress hook 🌈

Custom logger + progress hook, because sometimes you want the receipts in your own format.

import yt_dlp

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']

class MyLogger:
    def debug(self, msg):
        # For compatibility with youtube-dl, both debug and info are passed into debug
        # You can distinguish them by the prefix '[debug] '
        if msg.startswith('[debug] '):
            pass
        else:
            self.info(msg)

    def info(self, msg):
        pass

    def warning(self, msg):
        pass

    def error(self, msg):
        print(msg)

# ℹ️ See "progress_hooks" in help(yt_dlp.YoutubeDL)
def my_hook(d):
    if d['status'] == 'finished':
        print('Done downloading, now post-processing ...')

ydl_opts = {
    'logger': MyLogger(),
    'progress_hooks': [my_hook],
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    ydl.download(URLS)

🔥 Add a custom PostProcessor 🔥

Custom PostProcessor? Obsessed. You get to do your own little thing after extraction.

import yt_dlp

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']

# ℹ️ See help(yt_dlp.postprocessor.PostProcessor)
class MyCustomPP(yt_dlp.postprocessor.PostProcessor):
    def run(self, info):
        self.to_screen('Doing stuff')
        return [], info

with yt_dlp.YoutubeDL() as ydl:
    # ℹ️ "when" can take any value in yt_dlp.utils.POSTPROCESS_WHEN
    ydl.add_post_processor(MyCustomPP(), when='pre_process')
    ydl.download(URLS)

👑 Use a custom format selector 👑

This is the main-character energy example: pick the best video + the best audio that won't result in an mkv. The code itself says it's just an example and does not handle all cases, so don't come for it. 💀

import yt_dlp

URLS = ['https://www.youtube.com/watch?v=YE7VzlLtp-4']

def format_selector(ctx):
    """ Select the best video and the best audio that won't result in an mkv.
    NOTE: This is just an example and does not handle all cases """

    # formats are already sorted worst to best
    formats = ctx.get('formats')[::-1]

    # acodec='none' means there is no audio
    best_video = next(f for f in formats
                      if f['vcodec'] != 'none' and f['acodec'] == 'none')

    # find compatible audio extension
    audio_ext = {'mp4': 'm4a', 'webm': 'webm'}[best_video['ext']]
    # vcodec='none' means there is no video
    best_audio = next(f for f in formats if (
        f['acodec'] != 'none' and f['vcodec'] == 'none' and f['ext'] == audio_ext))

    # These are the minimum required fields for a merged format
    yield {
        'format_id': f'{best_video["format_id"]}+{best_audio["format_id"]}',
        'ext': best_video['ext'],
        'requested_formats': [best_video, best_audio],
        # Must be + separated list of protocols
        'protocol': f'{best_video["protocol"]}+{best_audio["protocol"]}'
    }

ydl_opts = {
    'format': format_selector,
}

with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    ydl.download(URLS)

🦋 CHANGES FROM YOUTUBE-DL 🦋

okay so this is the glow-up list — what's new/different compared to the original youtube-dl era. periodt. 💅

✨ New features ✨

  • Forked from yt-dlc@f9401f2 (https://github.com/blackjack4494/yt-dlc/commit/f9401f2a91987068139c5f757b12fc711d4c0cee) and merged with youtube-dl@a08f2b7 (https://github.com/ytdl-org/youtube-dl/commit/a08f2b7e4567cdc50c0614ee0a4ffdff49b8b6e6) — exceptions (https://github.com/yt-dlp/yt-dlp/issues/21)
  • SponsorBlock Integration — you can mark/remove sponsor sections in YouTube videos by utilizing the SponsorBlock API (https://sponsor.ajay.app). Iconic.
  • Format Sorting — the default format sorting options have been changed so that higher resolution and better codecs will be now preferred instead of simply using larger bitrate. Furthermore, you can now specify the sort order using -S. This allows for much easier format selection than what is possible by simply using --format (examples). 🎀
  • Merged with animelover1984/youtube-dl — you get most of the features and improvements from animelover1984/youtube-dl (https://github.com/animelover1984/youtube-dl) including --write-comments, BiliBiliSearch, BilibiliChannel, Embedding thumbnail in mp4/ogg/opus, playlist infojson etc. See #31 (https://github.com/yt-dlp/yt-dlp/pull/31) for details. 💖
  • YouTube improvements:
    • Supports Clips, Stories (ytstories:<channel UCID>), Search (including filters)*, YouTube Music Search, Channel-specific search, Search prefix (ytsearch:)*, Mixes, and Feeds (:ytfav, :ytwatchlater, :ytsubs, :ythistory, :ytrec, :ytnotif)
    • Fix for n-sig based throttling (https://github.com/ytdl-org/youtube-dl/issues/29326) *
    • Download livestreams from the start using --live-from-start (experimental)
    • Channel URLs download all uploads of the channel, including shorts and live
  • Cookies from browser — cookies can be automatically extracted from all major web browsers using --cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER]. 🍓
  • Download time range — videos can be downloaded partially based on either timestamps or chapters using --download-sections.
  • Split video by chapters — videos can be split into multiple files based on chapters using --split-chapters.
  • Multi-threaded fragment downloads — download multiple fragments of m3u8/mpd videos in parallel. Use --concurrent-fragments (-N) option to set the number of threads used.
  • New and fixed extractors — many new extractors have been added and a lot of existing ones have been fixed. See the changelog or the list of supported sites.
  • New MSOs — Philo, Spectrum, SlingTV, Cablevision, RCN etc.
  • Subtitle extraction from manifests — subtitles can be extracted from streaming media manifests. See commit/be6202f (https://github.com/yt-dlp/yt-dlp/commit/be6202f12b97858b9d716e608394b51065d0419f) for details.
  • Multiple paths and output templates — you can give different output templates and download paths for different types of files. You can also set a temporary path where intermediary files are downloaded to using --paths (-P).
  • Portable Configuration — configuration files are automatically loaded from the home and root directories. See CONFIGURATION for details.
  • Output template improvements — output templates can now have date-time formatting, numeric offsets, object traversal etc. See output template for details. Even more advanced operations can also be done with the help of --parse-metadata and --replace-in-metadata.
  • Other new options — many new options have been added such as --alias, --print, --concat-playlist, --wait-for-video, --retry-sleep, --sleep-requests, --convert-thumbnails, --force-download-archive, --force-overwrites, --break-match-filters etc.
  • Improvements — regex and other operators in --format/--match-filters, multiple --postprocessor-args and --downloader-args, faster archive checking, more format selection options, merge multi-video/audio, multiple --config-locations, --exec at different stages, etc.
  • Plugins — extractors and PostProcessors can be loaded from an external file. See plugins for details.
  • Self updater — the releases can be updated using yt-dlp -U, and downgraded using --update-to if required. 👑
  • Automated builds — the nightly/master builds are out here living their best life, and you can hop on them with --update-to nightly and --update-to master 🚀

Go peek at the changelog or the commits (https://github.com/yt-dlp/yt-dlp/commits) for the whole, entire, exhaustive list of changes — yes, all of them 💖

And heads up: any feature with a little * next to it has been back-ported to youtube-dl. Cute, right?


✨ Differences in default behavior ✨

Okay bestie, this is the part where I hold your hand and tell you that some of yt-dlp's defaults are just straight-up different from youtube-dl's and youtube-dlc's. Not better or worse, just different. Let's go 💅

  • yt-dlp supports only Python 3.11+, and will keep dropping support for more versions as they hit EOL (https://devguide.python.org/versions/#python-release-cycle). Meanwhile youtube-dl is still out here supporting Python 2.6+ and 3.2+ (https://github.com/ytdl-org/youtube-dl/issues/30568#issue-1118238743). It's giving ancient-history energy ngl 😭
  • Some options just no longer work. Peep the removed options for the tea.
  • avconv is not supported as an alternative to ffmpeg. Sorry to the three avconv stans out there 🫶
  • yt-dlp stashes its config files in slightly different locations than youtube-dl. Scroll to CONFIGURATION for the correct locations, no guessing ✨
  • The default output template is %(title)s [%(id)s].%(ext)s. And like… there is genuinely no real reason for this change? It got changed before yt-dlp was ever even public and now there are no plans to change it back to %(title)s-%(id)s.%(ext)s. If you're attached to the old one, use --compat-options filename 🎀
  • The default format sorting is different too — instead of chasing higher bitrates it prefers higher resolution and better codecs, which honestly? Slay. You can change it to literally any order you want with --format-sort, or use --compat-options format-sort to get youtube-dl's sorting order back. Older versions of yt-dlp preferred VP9 because of its broader compatibility, so if you're feeling nostalgic you can use --compat-options prefer-vp9-sort to revert to that preference. These two compat options cannot be used together — pick a lane bestie 💀
  • The default format selector is bv*+ba/b. Translation: if a combined video + audio format that's better than the best video-only format shows up, the combined one wins. Use -f bv+ba/b or --compat-options format-spec to revert that.
  • Unlike youtube-dlc, yt-dlp does not allow merging multiple audio/video streams into one file by default (because that fully conflicts with using -f bv*+ba, bestie). If you need it, you have to opt in with --audio-multistreams and --video-multistreams. Or just use --compat-options multistreams to flip both on at once 🦋
  • --no-abort-on-error is enabled by default. Use --abort-on-error or --compat-options abort-on-error if you'd rather abort on errors instead.
  • When writing metadata files like thumbnails, description or infojson, that same info (if it's available) also gets written for playlists. Use --no-write-playlist-metafiles or --compat-options no-playlist-metafiles to not write those files.
  • --add-metadata attaches the infojson to mkv files in addition to writing the metadata when it's used with --write-info-json. Use --no-embed-info-json or --compat-options no-attach-info-json to revert this.
  • Some metadata gets embedded into different fields when you use --add-metadata compared to youtube-dl. Most notably: the comment field contains the webpage_url and synopsis contains the description. Use --parse-metadata to tweak it to your liking, or --compat-options embed-metadata to revert 🧚
  • playlist_index behaves differently when you use it with options like --playlist-reverse and --playlist-items. See #302 (https://github.com/yt-dlp/yt-dlp/issues/302) for the full details. Use --compat-options playlist-index if you wanna keep the earlier behavior.
  • The output of -F is listed in a shiny new format. Use --compat-options list-formats to revert 💅
  • Live chats (if available) are considered as subtitles. Use --sub-langs all,-live_chat to download all subtitles except live chat. You can also use --compat-options no-live-chat to prevent any live chat/danmaku from downloading at all.
  • YouTube channel URLs download all uploads of the channel. If you only want the videos in a specific tab, pass that tab's URL. If the channel doesn't show the requested tab, an error gets raised. Also, /live URLs raise an error if there are no live videos instead of silently downloading the entire channel — which honestly, iconic behavior. Use --compat-options no-youtube-channel-redirect to revert all these redirections.
  • Unavailable videos are also listed for YouTube playlists. Use --compat-options no-youtube-unavailable-videos to remove them.
  • The upload dates extracted from YouTube are in UTC 🕐
  • If ffmpeg is used as the downloader, downloading and merging of formats happen in a single step when possible. Use --compat-options no-direct-merge to revert this.
  • Thumbnail embedding in mp4 is done with mutagen if possible. Use --compat-options embed-thumbnail-atomicparsley to force the use of AtomicParsley instead.
  • Some internal metadata such as filenames gets removed by default from the infojson. Use --no-clean-infojson or --compat-options no-clean-infojson to revert this.
  • When --embed-subs and --write-subs are used together, the subtitles get written to disk and embedded in the media file. You can use just --embed-subs to embed the subs and automatically delete the separate file — see #630 (comment) (https://github.com/yt-dlp/yt-dlp/issues/630#issuecomment-893659460) for more info. --compat-options no-keep-subs can be used to revert this 🌸
  • certifi will be used for SSL root certificates, if it's installed. If you wanna use system certificates (e.g. self-signed), use --compat-options no-certifi.
  • yt-dlp's sanitization of invalid characters in filenames is different/smarter than in youtube-dl. Use --compat-options filename-sanitization to revert to youtube-dl's behavior ✨
  • (Not currently implemented) [STRIKEOUT:yt-dlp tries to parse the external downloader outputs into the standard progress output if possible. You can use --compat-options no-external-downloader-progress to get the downloader output as-is]
  • yt-dlp versions from 2021.09.01 to 2022.11.11 (inclusive) applied --match-filters to nested playlists. That was a total unintentional side-effect of 8f18ac (https://github.com/yt-dlp/yt-dlp/commit/8f18aca8717bb0dd49054555af8d386e5eda3a88) and it's fixed in d7b460 (https://github.com/yt-dlp/yt-dlp/commit/d7b460d0e5fc710950582baed2e3fc616ed98a80). Use --compat-options playlist-match-filter to revert this.
  • yt-dlp versions from 2021.11.10 to 2023.06.21 (inclusive) estimated filesize_approx values for fragmented/manifest formats. It was added for convenience in f2fe69 (https://github.com/yt-dlp/yt-dlp/commit/f2fe69c7b0d208bdb1f6292b4ae92bc1e1a7444a), but was reverted in 0dff8e (https://github.com/yt-dlp/yt-dlp/commit/0dff8e4d1e6e9fb938f4256ea9af7d81f42fd54f) because those estimated values could be potentially extremely inaccurate 😭 Use --compat-options manifest-filesize-approx to keep extracting the estimated values.
  • yt-dlp uses modern http client backends such as requests. Use --compat-options prefer-legacy-http-handler to prefer the legacy http handler (urllib) for standard http requests.
  • The sub-modules swfinterp and casefold are removed 💀
  • Passing --simulate (or calling extract_info with download=False) no longer alters the default format selection. See #9843 (https://github.com/yt-dlp/yt-dlp/issues/9843) for details.
  • yt-dlp no longer applies the server modified time to downloaded files by default. Use --mtime or --compat-options mtime-by-default to revert this.

For convenience, there are some compat option aliases ready for you to use, bestie 🎀

  • --compat-options all — Use all compat options. (Do NOT use this!) 🙈
  • --compat-options youtube-dl — Same as --compat-options all,-multistreams,-playlist-match-filter,-manifest-filesize-approx,-allow-unsafe-ext,-prefer-vp9-sort,-allow-unsafe-exec-expansion
  • --compat-options youtube-dlc — Same as --compat-options all,-no-live-chat,-no-youtube-channel-redirect,-playlist-match-filter,-manifest-filesize-approx,-allow-unsafe-ext,-prefer-vp9-sort,-allow-unsafe-exec-expansion
  • --compat-options 2021 — Same as --compat-options 2022,no-certifi,filename-sanitization
  • --compat-options 2022 — Same as --compat-options 2023,playlist-match-filter,no-external-downloader-progress,prefer-legacy-http-handler,manifest-filesize-approx
  • --compat-options 2023 — Same as --compat-options 2024,prefer-vp9-sort
  • --compat-options 2024 — Same as --compat-options 2025,mtime-by-default
  • --compat-options 2025 — Currently does nothing. Use this to enable all future compat options 👑

Using one of the yearly compat option aliases will pin yt-dlp's default behavior to whatever it was at the end of that calendar year. Time capsule vibes fr fr 🕰️


The following compat options restore vulnerable behavior from before security patches — so like, read the warnings, okay? 💅

:warning: Only use if a valid file download is rejected because its extension is detected as uncommon. This option can enable remote code execution! Consider opening an issue (https://github.com/yt-dlp/yt-dlp/issues/new/choose) instead!
  • --compat-options allow-unsafe-exec-expansion — The --exec option allows output template syntax to be used in its commands; however, for security reasons the conversions that can be used are restricted to i/d (signed integer decimal), f (floating-point decimal) and q (shell-quoted). yt-dlp versions from 2021.04.11 to 2026.03.17 (inclusive) did not apply this restriction. This option reverts this restriction.
:warning: This option can enable remote code execution! Consider using %()q conversions in your exec command templates for any string values.

💀 Deprecated options 💀

These are, like, literally all the deprecated options, plus the current alternative to achieve the same effect. No cap. 🎀

Almost redundant options

So these are almost the same as their new counterparts, but there are some differences that stop them from being fully redundant — so don't just swap them out mid-vibe, ok? 🦋

  • -j, --dump-json — same energy as --print "%()j"
  • -F, --list-formats — same energy as --print formats_table
  • --list-thumbnails — same energy as --print thumbnails_table --print playlist:thumbnails_table
  • --list-subs — same energy as --print automatic_captions_table --print subtitles_table

Redundant options

Okay so these are genuinely redundant, but honestly? They're still expected to be used because they're just that easy to use — like, the convenience is the whole point, bestie 💖

okay so these are the legacy glow-up receipts, bestie — left side is the old thing, right side is the new thing that does the same job. Keep every spelling exact, because scripts are dramatic and unforgiving 💅

  • --get-description → --print description
  • --get-duration → --print duration_string
  • --get-filename → --print filename
  • --get-format → --print format
  • --get-id → --print id
  • --get-thumbnail → --print thumbnail
  • -e, --get-title → --print title
  • -g, --get-url → --print urls
  • --match-title REGEX → --match-filters "title ~= (?i)REGEX"
  • --reject-title REGEX → --match-filters "title !~= (?i)REGEX"
  • --min-views COUNT → --match-filters "view_count >=? COUNT"
  • --max-views COUNT → --match-filters "view_count <=? COUNT"
  • --break-on-reject → use --break-match-filters
  • --user-agent UA → --add-headers "User-Agent:UA"
  • --referer URL → --add-headers "Referer:URL"
  • --playlist-start NUMBER → -I NUMBER:
  • --playlist-end NUMBER → -I :NUMBER
  • --playlist-reverse → -I ::-1
  • --no-playlist-reverse → Default
  • --no-colors → --color no_color

These options still work, but the manual is lowkey side-eyeing them since there are other alternatives to achieve the same thing. No cap:

  • --force-generic-extractor → --ies generic,default
  • --exec-before-download CMD → --exec "before_dl:CMD"
  • --no-exec-before-download → --no-exec
  • --all-formats → -f all
  • --all-subs → --sub-langs all --write-subs
  • --print-json → -j --no-simulate
  • --autonumber-size NUMBER → use string formatting, e.g. %(autonumber)03d
  • --autonumber-start NUMBER → use internal field formatting like %(autonumber+NUMBER)s
  • --id → -o "%(id)s.%(ext)s"
  • --metadata-from-title FORMAT → --parse-metadata "%(title)s:FORMAT"
  • --hls-prefer-native → --downloader "m3u8:native"
  • --hls-prefer-ffmpeg → --downloader "m3u8:ffmpeg"
  • --list-formats-old → --compat-options list-formats (Alias: --no-list-formats-as-table)
  • --list-formats-as-table → --compat-options -list-formats [Default]
  • --geo-bypass → --xff "default"
  • --no-geo-bypass → --xff "never"
  • --geo-bypass-country CODE → --xff CODE
  • --geo-bypass-ip-block IP_BLOCK → --xff IP_BLOCK

🧚 Developer options 🧚

Real talk: these are not intended to be used by the end-user. The manual said what it said:

  • --test — download only part of video for testing extractors
  • --load-pages — load pages dumped by --write-pages
  • --allow-unplayable-formats — list unplayable formats also
  • --no-allow-unplayable-formats — Default

🎀 Old aliases 🎀

These are aliases that are no longer documented for various reasons. They still get their little moment here, exact spellings and all:

  • --clean-infojson → --clean-info-json
  • --force-write-download-archive → --force-write-archive
  • --no-clean-infojson → --no-clean-info-json
  • --no-split-tracks → --no-split-chapters
  • --no-write-srt → --no-write-subs
  • --prefer-unsecure → --prefer-insecure
  • --rate-limit RATE → --limit-rate RATE
  • --split-tracks → --split-chapters
  • --srt-lang LANGS → --sub-langs LANGS
  • --trim-file-names LENGTH → --trim-filenames LENGTH
  • --write-srt → --write-subs
  • --yes-overwrites → --force-overwrites

💀 Sponskrub Options 💀

Support for SponSkrub (https://github.com/faissaloo/SponSkrub) has been removed in favor of the --sponsorblock options. So this is the memorial wall:

  • --sponskrub → --sponsorblock-mark all
  • --no-sponskrub → --no-sponsorblock
  • --sponskrub-cut → --sponsorblock-remove all
  • --no-sponskrub-cut → --sponsorblock-remove -all
  • --sponskrub-force → Not applicable
  • --no-sponskrub-force → Not applicable
  • --sponskrub-location → Not applicable
  • --sponskrub-args → Not applicable

😭 No longer supported 😭

These options may no longer work as intended. Read the receipts:

  • --prefer-avconv — avconv is not officially supported by yt-dlp (Alias: --no-prefer-ffmpeg)
  • --prefer-ffmpeg — Default (Alias: --no-prefer-avconv)
  • -C, --call-home — Not implemented
  • --no-call-home — Default
  • --include-ads — No longer supported
  • --no-include-ads — Default
  • --write-annotations — No supported site has annotations now
  • --no-write-annotations — Default
  • --cn-verification-proxy URL — Removed alias for --geo-verification-proxy URL
  • --youtube-skip-dash-manifest — Removed alias for --extractor-args "youtube:skip=dash" (Alias: --no-youtube-include-dash-manifest)
  • --youtube-skip-hls-manifest — Removed alias for --extractor-args "youtube:skip=hls" (Alias: --no-youtube-include-hls-manifest)
  • --youtube-include-dash-manifest — Default (Alias: --no-youtube-skip-dash-manifest)
  • --youtube-include-hls-manifest — Default (Alias: --no-youtube-skip-hls-manifest)
  • --youtube-print-sig-code — Removed testing functionality
  • --dump-user-agent — No longer supported
  • --xattr-set-filesize — No longer supported
  • --compat-options seperate-video-versions — No longer needed
  • --compat-options no-youtube-prefer-utc-upload-date — No longer supported

🌈 Removed 🌈

These aliases have been removed and may be reused. Please update your scripts and configuration files accordingly — fr fr, go check them:

  • -A, --auto-number → -o "%(autonumber)s-%(id)s.%(ext)s"
  • -t, -l, --title, --literal → -o "%(title)s-%(id)s.%(ext)s"
  • --avconv-location → --ffmpeg-location
  • --dump-headers → --print-traffic
  • --dump-intermediate-pages → --dump-pages

💖 CONTRIBUTING 💖

See CONTRIBUTING.md for instructions on Opening an Issue and Contributing code to the project.


🦋 WIKI 🦋

See the Wiki (https://github.com/yt-dlp/yt-dlp/wiki) for more information.


and that’s the whole yt-dlp(1) page, bestie — the footer’s source/version and date are doing their little iconic thing down there, and this queen is logging off 💖✨🎀

📦 where this page comes from