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:CHANNELcan also be a repository. if you omit them,CHANNELandTAGdefault 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-extractorsfor 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-locationsdefined 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. onlydenois enabled by default. the highest priority runtime that is both enabled and available gets used. heads up: to use a lower priority runtime whiledenois available, you gotta pass--no-js-runtimesbefore 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 theyt-dlp-ejspackage 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-componentsor 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 theSTREAM(stdoutorstderr) 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-audioand-Xthat take an argument (ARG0) and expand to-S aext:ARG0,abr -x --audio-format ARG0. all defined aliases get listed in the--helpoutput. 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 of100times. 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— enablesfile://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 theX-Forwarded-ForHTTP 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-separatedplaylist_indexof the items to download. you can specify a range using"[START]:[STOP][:STEP]". for backward compatibility,START-STOPis also supported. use negative indices to count from the right, and negativeSTEPto download in reverse order. e.g.-I 1:3,7,-5::2used on a playlist of size15will download the items at index1,2,3,7,11,13,15. chef's kiss selection. 💅--min-filesize SIZE— aborts the download if the filesize is smaller thanSIZE, e.g.50kor44.6M.--max-filesize SIZE— aborts the download if the filesize is larger thanSIZE, e.g.50kor44.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-2weeksdownloads 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 anyOUTPUT TEMPLATEfield with a number or a string using the operators defined inFiltering Formats. You can also just name a field to match if it’s present, use!fieldto 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 downloadingNUMBERfiles.--break-on-existing— stop the download process when encountering a file that is in the archive supplied with the--download-archiveoption.--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-existingand 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 is1). slay those fragments in parallel.-r,--limit-rate RATE— maximum download rate in bytes per second, e.g.50Kor4.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 is10), or"infinite".--file-access-retries RETRIES— number of times to retry on file access error (default is3), or"infinite".--fragment-retries RETRIES— number of retries for a fragment (default is10), 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.EXPRcan be a number,linear=START[:END[:STEP=1]]orexp=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.1024or16K(default is1024).--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.10485760or10M(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 disablesn_entries,--playlist-randomand--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 supportsnative,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 sameTYPESas--outputare 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--outputis 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.partfiles instead of writing directly into output file (default).--no-part— do not use.partfiles - 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.descriptionfile.--no-write-description— do not write video description (default).--write-info-json— write video metadata to a.info.jsonfile (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-descriptionetc. (default).--no-write-playlist-metafiles— do not write playlist metadata when using--write-info-json,--write-descriptionetc.--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).--cookiesFILE 🍪 — 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-browserBROWSER[+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-dirDIR ✨ — 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-simulateis used.
🔗 Internet Shortcut Options 🔗
--write-link— writes an internet shortcut file, and which kind depends on your current platform (.url,.weblocor.desktop). Heads up: the URL may be cached by the OS, so it's giving "stale link" potential 🙈.--write-url-link— writes a.urlWindows internet shortcut. The OS caches the URL based on the file path, iykyk.--write-webloc-link— writes a.weblocmacOS internet shortcut.--write-desktop-link— writes a.desktopLinux 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--simulateunless--no-simulateis 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-simulateis 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-simulateis 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-sor 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-deltaSECONDS — 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 🛠️
--encodingENCODING — 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-headersFIELD: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 thebidivorfribidiexecutable inPATH.--sleep-requestsSECONDS — the number of seconds to sleep between requests during data extraction. Be polite, take a nap 😴.--sleep-intervalSECONDS — 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-intervalSECONDS — the maximum number of seconds to sleep. Can only be used along with--min-sleep-interval.--sleep-subtitlesSECONDS — the number of seconds to sleep before each subtitle download.
🎬 Video Format Options 🎬
-f,--formatFORMAT — the video format code. See"FORMAT SELECTION"for more details, it's a whole journey.-S,--format-sortSORTORDER — 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 extto 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-simulateis used.--merge-output-formatFORMAT — 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-simulateis used.--sub-formatFORMAT — the subtitle format; accepts a format preference separated by"/", e.g."srt"or"ass/srt/best".--sub-langsLANGS 🦋 — 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-subsfor a list of available language tags, bestie.
🔐 Authentication Options 🔐
-u,--usernameUSERNAME — logs in with this account ID.-p,--passwordPASSWORD — the account password. If this option is left out, yt-dlp will ask you interactively 🙈.-2,--twofactorTWOFACTOR — the two-factor authentication code.-n,--netrc— uses.netrcauthentication data.--netrc-locationPATH — location of the.netrcauthentication data; either the path itself or its containing directory. Defaults to~/.netrc.--netrc-cmdNETRC_CMD — the command to execute to get the credentials for an extractor.--video-passwordPASSWORD — the video-specific password.--ap-msoMSO — the Adobe Pass multiple-system operator (TV provider) identifier. Use--ap-list-msofor a list of available MSOs.--ap-usernameUSERNAME — 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 requiresffmpegandffprobe, 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-xis 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 theffmpegaudio quality when converting with-x. Drop in a value between0(best) and10(worst) for VBR, or a specific bitrate like128K(default5). 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,FixupTimestampandFixupDuration. The supported executables are:AtomicParsley,FFmpegandFFprobe. 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 formp4,webmandmkvvideos, 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-jsonare 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 tomkv/mkavideo 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--pathsand--outputto set the output filename for the concatenated files. See "OUTPUT TEMPLATE" for details 🦋--fixup POLICY— automatically corrects known faults of the file. One ofnever(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 theffmpegbinary; 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|)qis 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--pathsand--outputto 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 ofNAME=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 aresponsor,intro,outro,selfpromo,preview,filler,interaction,music_offtopic,hook,poi_highlight,chapter,allanddefault(=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"andpoi_highlight,chapterare not available 🚫--sponsorblock-chapter-title TEMPLATE— an output template for the title of the SponsorBlock chapters created by--sponsorblock-mark. The only available fields arestart_time,end_time,category,categories,name,category_names. Defaults to:
"[SponsorBlock]: %(category_names)l"
--no-sponsorblock— disables both--sponsorblock-markand--sponsorblock-remove. All the way off, bestie 💀--sponsorblock-api URL— SponsorBlock API location, defaults tohttps://sponsor.ajay.app
🦋 Extractor Options 🦋
--extractor-retries RETRIES— number of retries for known extractor errors (default is3), 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— passARGSarguments straight to theIE_KEYextractor. 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:
- Main Configuration:
- The file given to
--config-locations
- The file given to
- Portable Configuration: (recommended for portable installations 🧳)
- If using a binary,
yt-dlp.confin the same directory as the binary - If running from source-code,
yt-dlp.confin the parent directory ofyt_dlp
- If using a binary,
- Home Configuration:
yt-dlp.confin the home path given to-P- If
-Pis not given, the current directory is searched
- 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.txtSee also: Notes about environment variables
- 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.-oor--proxybut not- oor-- 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}/$VARIABLEon 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~/.configand${XDG_CACHE_HOME}to~/.cache - On Windows,
~points to${HOME}if present; or,${USERPROFILE}or${HOMEDRIVE}${HOMEPATH}otherwise - On Windows,
${USERPROFILE}generally points toC:\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:
- 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%()srefers 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-jto see such fields — go look, it's kind of iconic 🔍 - 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 🧮 - 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 - Alternatives: Alternate fields can be specified separated with a
,. E.g.%(release_date>%Y,upload_date>%Y|Unknown)s - 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 - 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 - More Conversions: In addition to the normal format types
diouxXeEfFgGcrs, yt-dlp additionally supports converting toB= Bytes,j= json (flag#for pretty-printing,+for Unicode),h= HTML escaping,l= a comma-separated list (flag#for\nnewline-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), andS= Sanitize as filename (flag#for restricted). So many little guys, obsessed 🥹 - Unicode normalization: The format type
Ucan 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)+.100Uis 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 titlefulltitle(string) — video title, but ignoring live timestamp and generic title ✨ext(string) — video filename extensionalt_title(string) — a secondary title of the videodescription(string) — the description of the videodisplay_id(string) — an alternative identifier for the videouploader(string) — full name of the video uploaderuploader_id(string) — nickname or id of the video uploaderuploader_url(string) — URL to the video uploader’s profilelicense(string) — license name the video is licensed undercreators(list) — the creators of the videocreator(string) — the creators of the video; comma-separatedtimestamp(numeric) — UNIX timestamp of the moment the video became availableupload_date(string) — video upload date in UTC (YYYYMMDD)release_timestamp(numeric) — UNIX timestamp of the moment the video was releasedrelease_date(string) — the date (YYYYMMDD) when the video was released in UTCrelease_year(numeric) — year (YYYY) when the video or album was releasedmodified_timestamp(numeric) — UNIX timestamp of the moment the video was last modifiedmodified_date(string) — the date (YYYYMMDD) when the video was last modified in UTCchannel(string) — full name of the channel the video is uploaded onchannel_id(string) — id of the channelchannel_url(string) — URL of the channelchannel_follower_count(numeric) — number of followers of the channelchannel_is_verified(boolean) — whether the channel is verified on the platformlocation(string) — physical location where the video was filmedduration(numeric) — length of the video in secondsduration_string(string) — length of the video (HH:mm:ss)view_count(numeric) — how many users have watched the video on the platformconcurrent_view_count(numeric) — how many users are currently watching the video on the platform.like_count(numeric) — number of positive ratings of the videodislike_count(numeric) — number of negative ratings of the videorepost_count(numeric) — number of reposts of the videoaverage_rating(numeric) — average rating given by users, the scale used depends on the webpagecomment_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 bookmarkedage_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 videowas_live(boolean) — whether this video was originally a live streamplayable_in_embed(string) — whether this video is allowed to play in embedded players on other sitesavailability(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 URLend_time(numeric) — time in seconds where the reproduction should end, as specified in the URLextractor(string) — name of the extractorextractor_key(string) — key name of the extractorepoch(numeric) — Unix epoch of when the information extraction was completedautonumber(numeric) — number that will be increased with each download, starting at--autonumber-start, padded with leading zeros to 5 digitsvideo_autonumber(numeric) — number that will be increased with each videon_entries(numeric) — total number of extracted items in the playlistplaylist_id(string) — identifier of the playlist that contains the videoplaylist_title(string) — name of the playlist that contains the videoplaylist(string) —playlist_titleif available or elseplaylist_idplaylist_count(numeric) — total number of items in the playlist. May not be known if entire playlist is not extractedplaylist_index(numeric) — index of the video in the playlist padded with leading zeros according to the final indexplaylist_autonumber(numeric) — position of the video in the playlist download queue padded with leading zeros according to the total length of the playlistplaylist_uploader(string) — full name of the playlist uploaderplaylist_uploader_id(string) — nickname or id of the playlist uploaderplaylist_channel(string) — display name of the channel that uploaded the playlistplaylist_channel_id(string) — identifier of the channel that uploaded the playlistplaylist_webpage_url(string) — URL of the playlist webpagewebpage_url(string) — a URL to the video webpage which, if given toyt-dlp, should yield the same result againwebpage_url_basename(string) — the basename of the webpage URLwebpage_url_domain(string) — the domain of the webpage URLoriginal_url(string) — the URL given by the user (or the same aswebpage_urlfor playlist entries)categories(list) — list of categories the video belongs totags(list) — list of tags assigned to the videocast(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 tochapter_number(numeric) — number of the chapter the video belongs tochapter_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 toseries_id(string) — id of the series or program the video episode belongs toseason(string) — title of the season the video episode belongs toseason_number(numeric) — number of the season the video episode belongs toseason_id(string) — id of the season the video episode belongs toepisode(string) — title of the video episodeepisode_number(numeric) — number of the video episode within a seasonepisode_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 tracktrack_number(numeric) — number of the track within an album or a disctrack_id(string) — id of the trackartists(list) — artist(s) of the trackartist(string) — artist(s) of the track; comma-separatedgenres(list) — genre(s) of the trackgenre(string) — genre(s) of the track; comma-separatedcomposers(list) — composer(s) of the piececomposer(string) — composer(s) of the piece; comma-separatedalbum(string) — title of the album the track belongs toalbum_type(string) — type of the albumalbum_artists(list) — all artists appeared on the albumalbum_artist(string) — all artists appeared on the album; comma-separateddisc_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 chaptersection_number(numeric) — number of the chapter within the filesection_start(numeric) — start time of the chapter in secondssection_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 linefilename(string) — name of the video file. Note that the actual filename may differformats_table(table) — the video format table as printed by--list-formatsthumbnails_table(table) — the thumbnail format table as printed by--list-thumbnailssubtitles_table(table) — the subtitle format table as printed by--list-subsautomatic_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 secondsend_time(numeric) — end time of the chapter in secondscategories(list) — the SponsorBlock categories (https://wiki.sponsor.ajay.app/w/Types#Category) the chapter belongs tocategory(string) — the smallest SponsorBlock category the chapter belongs tocategory_names(list) — friendly names of the categoriesname(string) — friendly name of the smallest categorytype(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(NAby 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 usingffmpeg. So in future versions, the default for this will be set to-f bv*+ba/bsimilar to normal downloads. If you want to preserve the-f b/bv+basetting, 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 separatelymergeall— select and merge all formats (must be used with--audio-multistreams,--video-multistreamsor both)b*,best*— select the best quality format that contains either a video or an audio or both (i.e.;vcodec!=noneoracodec!=none)b,best— select the best quality format that contains both video and audio. Equivalent tobest*[vcodec!=none][acodec!=none]bv,bestvideo— select the best quality video-only format. Equivalent tobest*[acodec=none]bv*,bestvideo*— select the best quality format that contains video. It may also contain audio. Equivalent tobest*[vcodec!=none]ba,bestaudio— select the best quality audio-only format. Equivalent tobest*[vcodec=none]ba*,bestaudio*— select the best quality format that contains audio. It may also contain video. Equivalent tobest*[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 audiow,worst— select the worst quality format that contains both video and audio. Equivalent toworst*[vcodec!=none][acodec!=none]wv,worstvideo— select the worst quality video-only format. Equivalent toworst*[acodec=none]wv*,worstvideo*— select the worst quality format that contains video. It may also contain audio. Equivalent toworst*[vcodec!=none]wa,worstaudio— select the worst quality audio-only format. Equivalent toworst*[vcodec=none]wa*,worstaudio*— select the worst quality format that contains audio. It may also contain video. Equivalent toworst*[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 advancefilesize_approx— an estimate for the number of byteswidth— width of the video, if knownheight— height of the video, if knownaspect_ratio— aspect ratio of the video, if knowntbr— average bitrate of audio and video in kbpsabr— average audio bitrate in kbpsvbr— average video bitrate in kbpsasr— audio sampling rate in Hertzfps— frame rateaudio_channels— the number of audio channelsstretched_ratio—width:heightof 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 URLext— file extensionacodec— name of the audio codec in usevcodec— name of the video codec in usecontainer— name of the container formatprotocol— the protocol that'll be used for the actual download, lower-case (http,https,rtmp,rtmpe,f4m,ism,http_dash_segments,m3u8, orm3u8_native)language— language codedynamic_range— the dynamic range of the videoformat_id— a short description of the formatformat— a human-readable description of the formatformat_note— extra info about the formatresolution— 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 streamhasaud— gives priority to formats that have an audio streamie_pref— the format preferencelang— the language preference as determined by the extractor (e.g. original language preferred over audio description)quality— the quality of the formatsource— the preference of the sourceproto— 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 tovcodec,acodecvext— video extension (mp4>mov>webm>flv> other). If--prefer-free-formatsis used,webmis preferred.aext— audio extension (m4a>aac>mp3>ogg>opus>webm> other). If--prefer-free-formatsis used, the order changes toogg>opus>webm>mp3>m4a>aacext— equivalent tovext,aextfilesize— exact filesize, if known in advancefs_approx— approximate filesizesize— exact filesize if available, otherwise approximate filesizeheight— height of videowidth— width of videores— video resolution, calculated as the smallest dimensionfps— framerate of videohdr— the dynamic range of the video (DV>HDR12>HDR10+>HDR10>HLG>SDR)channels— the number of audio channelstbr— total average bitrate in kbpsvbr— average video bitrate in kbpsabr— average audio bitrate in kbpsbr— average bitrate in kbps,tbr/vbr/abrasr— 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 forhdrishdr: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:
- a Python regular expression (https://docs.python.org/3/library/re.html#regular-expression-syntax) with named capture groups
- a single field name
- or a similar syntax to the output template (only
%(field)sformatting is supported)
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_urlsto 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 themeta_descriptionfield will be added to thedescriptionfield in the file — you can use this to set a different "description" and "synopsis". To modify the metadata of individual streams, use themeta<n>_prefix (e.g.meta1_language). Any value set to themeta_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— fromtrackortitledate— fromupload_datedescription,synopsis— fromdescriptionpurl,comment— fromwebpage_urltrack— fromtrack_numberartist— fromartist,artists,creator,creators,uploaderoruploader_idcomposer— fromcomposerorcomposersgenre— fromgenre,genres,categoriesortagsalbum— fromalbumorseriesalbum_artist— fromalbum_artistoralbum_artistsdisc— fromdisc_numbershow— fromseriesseason_number— fromseason_numberepisode_id— fromepisodeorepisode_idepisode_sort— fromepisode_numberlanguage 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,ARGcan use-instead of_; e.g.youtube:player-client"becomesyoutube: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 toentranslated. Seeyoutube/_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 ofhls,dashortranslated_substo 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 areweb,web_safari,web_embedded,web_music,web_creator,mweb,ios,visionos,android,android_vr,tv,tv_downgraded,tv_samsung, andtv_simply. By default,tv_samsung,visionos,webis used. If no JavaScript runtime/engine is available, thenwebandtv_samsungare omitted. If logged-in cookies are passed to yt-dlp, thenweb_embedded,tv_samsung,webis used for free accounts andweb_creator,tv_samsung,webis used for premium accounts. Theweb_musicclient is added formusic.youtube.comURLs when logged-in cookies are used. Theweb_embeddedclient is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable). Theweb_embeddedclient may be added as a fallback ifandroid_vrorvisionosis unable to access a video. Theweb_creatorclient is added for age-restricted videos if account age-verification is required. Some clients, such asweb_creatorandweb_music, require apo_tokenfor their formats to be downloadable. Some clients, such asweb_creator, will only work with authentication. Not all clients support authentication via cookies. You can usedefaultfor the default clients, or you can useallfor 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 ofconfigs(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 ofplayer_response,initial_data. These options are for testing purposes and don't skip any network requests. Neither is skipped by default; however, if aplayer_js_versionvalue other thanactualis used, thenwebpage_skip=player_responseis implied.webpage_client— Client to use for the video webpage request. One ofweb(default) orweb_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 ismain, and the others are for debugging purposes. You can useactualto go with what is prescribed by the site.player_js_version— The player javascript version to use for n/sig deciphering, in the format ofsignature_timestamp@hash(e.g.20348@0004de42). The default is to use what is prescribed by the site, and can be selected withactual. Using any other value will implywebpage_skip=player_response.comment_sort—topornew(default) — choose comment sorting mode (on YouTube's side).max_comments— Limit the amount of comments to gather. Comma-separated list of integers representingmax-comments,max-parents,max-replies,max-replies-per-thread,max-depth. Default isall,all,all,all,all. 🗣️- A
max-depthvalue of1will discard all replies, regardless of themax-repliesormax-replies-per-threadvalues given. - E.g.
all,all,1000,10,2will 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,100will get a maximum of 1000 comments, with a maximum of 100 replies total.
- A
formats— okay so this changes the types of formats to return. You can passdashy(convert HTTP to DASH),duplicate(identical content but different URLs or protocol; includesdashy),incomplete(cannot be downloaded completely — live and post-live dash, post-live m3u8, and live adaptive https without--live-from-start), ormissing_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 Receivedraises 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 withyoutube:player_skip=webpage,configsoryoutubetab:skip=webpage👑visitor_data— Overrides the Visitor Data used in Innertube API requests. This should be used withplayer_skip=webpage,configsand 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 formatCLIENT.CONTEXT+PO_TOKEN, e.g.youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY. Context can be any ofgvs(Google Video Server URLs),player(Innertube player request) orsubs(Subtitles) 🔥pot_trace— Enable debug logging for PO Token fetching. Eithertrueorfalse(default) 🌸fetch_pot— Policy to use for fetching a PO Token from providers. One ofalways(always try fetch a PO Token regardless if the client requires one for the given context),never(never fetch a PO Token), orauto(default; only fetch a PO Token if the client requires one for the given context) 🦋jsc_trace— Enable debug logging for JS Challenge fetching. Eithertrueorfalse(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 themwebandweb_musicplayer clients. Eithertrueorfalse(default) 💀
youtube-ejs ✨
jitless— Run supported Javascript engines in JIT-less mode. Supported runtimes aredeno,nodeandbun. Provides better security at the cost of performance/speed. Do note thatnodeandbunare still considered insecure. Eithertrueorfalse(default) 🎀
youtubepot-webpo 🎀
bind_to_visitor_id— Whether to use the Visitor ID instead of Visitor Data for caching WebPO tokens. Eithertrue(default) orfalse💖
youtubetab (YouTube playlists, channels, feeds, etc.) 🌈
skip— One or more ofwebpage(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 approximateupload_dateandtimestampin 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 asfragment_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 thekey_queryextractor-arg is passed, or unless an external key URI is provided via thehls_keyextractor-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 asvariant_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 askey_query=VALUE. Note that this will have no effect if the key URI is provided via thehls_keyextractor-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 setlive_status— a value offalsewill setnot_live, any other value (or no value) will setis_live🔥impersonate— Target(s) to try and impersonate with the initial webpage request; e.g.generic:impersonate=safari,chrome-110. Usegeneric:impersonateto impersonate any available target, and usegeneric:impersonate=falseto disable impersonation (default) 🦋
vikichannel 🎀
video_types— Types of videos to download - one or more ofepisodes,movies,clips,trailers⭐
youtubewebarchive 🌈
check_all— Try to check more at the cost of more requests. One or more ofthumbnails,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 ofsd,hd,fhd💅vcodec— vcodec to ignore - one or more ofh264,h265,dvh265💅dr— dynamic range to ignore - one or more ofsdr,hdr10,dv💅
instagram 🌸
app_id— The value of theX-IG-App-IDheader used for API requests. Can be the actual ID number,ios, orweb(default) 🦋
niconicochannelplus 🎀
max_comments— Maximum number of comments to extract - default is120😭
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 withmanifest_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], whereiidis the unique app install ID.iidis the only required value; all other values and their/separators can be omitted, e.g.tiktok:app_info=1234567890123456789ortiktok: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 ofnew,top,videos,podcasts,streams,stacks⭐
twitter 🦋
api— Select one ofgraphql(default),legacyorsyndicationas 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 totokyo🎀
nflplusreplay ⭐
type— Type(s) of game replays to extract. Valid types are:full_game,full_game_spanish,condensed_gameandall_22. You can useallto extract all available replay types, which is the default ⭐
jiocinema 🎀
refresh_token— TherefreshTokenUUID from browser local storage can be passed to extend the life of your login session when logging in withtokenas username and theaccessTokenfrom browser local storage as password 💖
jiosaavn 🌸
bitrate— Audio bitrates to request. One or more of16,32,64,128,320. Default is128,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 includehttp,hlsandhls-aes; known codecs includeaac,opusandmp3. Original download formats are always extracted. Default ishttp_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 extractingflvformats overmp4for older videos that still provide legacy formats ⭐
sonylivseries 💜
sort_order— Episode sort order for series extraction - one ofasc(ascending, oldest first) ordesc(descending, newest first). Default isasc🎀
streaks 🔥
api_key— API key for theX-Streaks-Api-Keyheader 👑
tver 🌸
backend— Backend API to use for extraction - one ofstreaks(default) orbrightcove(deprecated) 💀
vimeo 🦋
client— Client to extract video data from. The currently available clients areandroidandweb. Only one client can be used. Thewebclient is used by default, and it only works with account cookies or login credentials. Theandroidclient only works with previously cached OAuth tokens 💅original_format_policy— Policy for when to try extracting original formats. One ofalways,never, orauto. The defaultautopolicy 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. Eithertrueorfalse(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
--preflag). 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 releaseyt-dlp --update-to stable@2023.07.06— upgrade/downgrade to release to stable channel tag2023.07.06yt-dlp --update-to 2023.10.07— upgrade/downgrade to tag2023.10.07if it exists on the current channelyt-dlp --update-to example/yt-dlp@2023.09.24— upgrade/downgrade to the release from theexample/yt-dlprepository, tag2023.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. 🥺
🌸 Strongly recommended
ffmpegandffprobe(<https://www.ffmpeg.org>) — Required for merging separate video and audio files, as well as for various post-processing tasks. License depends on the build (<https://www.ffmpeg.org/legal.html>)
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.
yt-dlp-ejs(<https://github.com/yt-dlp/ejs>) — Required for full YouTube support. Licensed under Unlicense (<https://github.com/yt-dlp/ejs/blob/main/LICENSE>), bundles MIT (<https://github.com/davidbonnet/astring/blob/main/LICENSE>) and ISC (<https://github.com/meriyah/meriyah/blob/main/LICENSE.md>) components.
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
certifi(<https://github.com/certifi/python-certifi>)* — Provides Mozilla's root certificate bundle. Licensed under MPLv2 (<https://github.com/certifi/python-certifi/blob/master/LICENSE>)brotli(<https://github.com/google/brotli>)* orbrotlicffi(<https://github.com/python-hyper/brotlicffi>) — Brotli (<https://en.wikipedia.org/wiki/Brotli>) content encoding support. Both licensed under MIT 1 (<https://github.com/google/brotli/blob/master/LICENSE>) 2 (<https://github.com/python-hyper/brotlicffi/blob/master/LICENSE>)websockets(<https://github.com/aaugustin/websockets>)* — For downloading over websocket. Licensed under BSD-3-Clause (<https://github.com/aaugustin/websockets/blob/main/LICENSE>)requests(<https://github.com/psf/requests>)* — HTTP library. For HTTPS proxy and persistent connections support. Licensed under Apache-2.0 (<https://github.com/psf/requests/blob/main/LICENSE>)
🎭 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 🕵️♀️)
curl_cffi(<https://github.com/lexiforest/curl_cffi>) (recommended) — Python binding forcurl-impersonate(<https://github.com/lexiforest/curl-impersonate>). Provides impersonation targets for Chrome, Edge and Safari. Licensed under MIT (<https://github.com/lexiforest/curl_cffi/blob/main/LICENSE>)- Can be installed with the
curl-cffiextra, e.g.pip install "yt-dlp[default,curl-cffi]" - Currently included in most builds except
yt-dlp(Unix zipimport binary) andyt-dlp_x86(Windows 32-bit)
- Can be installed with the
🏷️ Metadata
mutagen(<https://github.com/quodlibet/mutagen>)* — For--embed-thumbnailin certain formats. Licensed under GPLv2+ (<https://github.com/quodlibet/mutagen/blob/master/COPYING>)AtomicParsley(<https://github.com/wez/atomicparsley>) — For--embed-thumbnailinmp4/m4afiles whenmutagen/ffmpegcannot. Licensed under GPLv2+ (<https://github.com/wez/atomicparsley/blob/master/COPYING>)xattr(<https://github.com/xattr/xattr>),pyxattr(<https://github.com/iustin/pyxattr>) orsetfattr(<http://savannah.nongnu.org/projects/attr>) — For writing xattr metadata (--xattrs) on Mac and BSD. Licensed under MIT (<https://github.com/xattr/xattr/blob/master/LICENSE.txt>), LGPL2.1 (<https://github.com/iustin/pyxattr/blob/master/COPYING>) and GPLv2+ (<http://git.savannah.nongnu.org/cgit/attr.git/tree/doc/COPYING>) respectively
🍓 Misc
pycryptodomex(<https://github.com/Legrandin/pycryptodome>)* — For decrypting AES-128 HLS streams and various other data. Licensed under BSD-2-Clause (<https://github.com/Legrandin/pycryptodome/blob/master/LICENSE.rst>)phantomjs(<https://github.com/ariya/phantomjs>) — Used in some extractors where JavaScript needs to be run. No longer used for YouTube. To be deprecated in the near future. Licensed under BSD-3-Clause (<https://github.com/ariya/phantomjs/blob/master/LICENSE.BSD>)secretstorage(<https://github.com/mitya57/secretstorage>)* — For--cookies-from-browserto access the Gnome keyring while decrypting cookies of Chromium-based browsers on Linux. Licensed under BSD-3-Clause (<https://github.com/mitya57/secretstorage/blob/master/LICENSE>)- Any external downloader that you want to use with
--downloader
💀 Deprecated
rtmpdump(<http://rtmpdump.mplayerhq.hu>) — For downloading rtmp streams.ffmpegcan be used instead with--downloader ffmpeg. Licensed under GPLv2+ (<http://rtmpdump.mplayerhq.hu>)
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: Runningpyinstallerdirectly instead of usingpython -m bundle.pyinstalleris 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)
🎀 Related Scripts 🎀
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 variableYTDLP_NO_LAZY_EXTRACTORSto 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:
- Configuration directories: plugin packages (containing a
yt_dlp_pluginsnamespace 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/
- User Plugins
- Executable location: plugin packages can similarly be installed in a
yt-dlp-pluginsdirectory 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/
- Binary: where
pipand other locations inPYTHONPATH- 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
pipmust have unique filenames 👀
- > Note: plugin files between plugin packages installed with
- any path in
PYTHONPATHis searched in for theyt_dlp_pluginsnamespace folder.- > Note: this does not apply for Pyinstaller builds.
- plugin packages can be installed and managed using
.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.zipwheremypluginpkg.zipcontainsyt_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 fromyoutube-dltoyt-dlp, one important point to look out for is that we do not guarantee the return value ofYoutubeDL.extract_infoto 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 throughYoutubeDL.sanitize_infoas 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 withyoutube-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
- Supports Clips, Stories (
- 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/mpdvideos 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-metadataand--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-filtersetc. - Improvements — regex and other operators in
--format/--match-filters, multiple--postprocessor-argsand--downloader-args, faster archive checking, more format selection options, merge multi-video/audio, multiple--config-locations,--execat 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-toif required. 👑 - Automated builds — the nightly/master builds are out here living their best life, and you can hop on them with
--update-to nightlyand--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.
avconvis not supported as an alternative toffmpeg. 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-sortto 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-sortto 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/bor--compat-options format-specto 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-multistreamsand--video-multistreams. Or just use--compat-options multistreamsto flip both on at once 🦋 --no-abort-on-erroris enabled by default. Use--abort-on-erroror--compat-options abort-on-errorif 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-metafilesor--compat-options no-playlist-metafilesto not write those files. --add-metadataattaches the infojson to mkv files in addition to writing the metadata when it's used with--write-info-json. Use--no-embed-info-jsonor--compat-options no-attach-info-jsonto revert this.- Some metadata gets embedded into different fields when you use
--add-metadatacompared to youtube-dl. Most notably: thecommentfield contains thewebpage_urlandsynopsiscontains thedescription. Use--parse-metadatato tweak it to your liking, or--compat-options embed-metadatato revert 🧚 playlist_indexbehaves differently when you use it with options like--playlist-reverseand--playlist-items. See #302 (https://github.com/yt-dlp/yt-dlp/issues/302) for the full details. Use--compat-options playlist-indexif you wanna keep the earlier behavior.- The output of
-Fis listed in a shiny new format. Use--compat-options list-formatsto revert 💅 - Live chats (if available) are considered as subtitles. Use
--sub-langs all,-live_chatto download all subtitles except live chat. You can also use--compat-options no-live-chatto 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,
/liveURLs 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-redirectto revert all these redirections. - Unavailable videos are also listed for YouTube playlists. Use
--compat-options no-youtube-unavailable-videosto remove them. - The upload dates extracted from YouTube are in UTC 🕐
- If
ffmpegis used as the downloader, downloading and merging of formats happen in a single step when possible. Use--compat-options no-direct-mergeto revert this. - Thumbnail embedding in
mp4is done withmutagenif possible. Use--compat-options embed-thumbnail-atomicparsleyto force the use ofAtomicParsleyinstead. - Some internal metadata such as filenames gets removed by default from the infojson. Use
--no-clean-infojsonor--compat-options no-clean-infojsonto revert this. - When
--embed-subsand--write-subsare used together, the subtitles get written to disk and embedded in the media file. You can use just--embed-substo 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-subscan be used to revert this 🌸 certifiwill 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-sanitizationto 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-progressto get the downloader output as-is] - yt-dlp versions from 2021.09.01 to 2022.11.11 (inclusive) applied
--match-filtersto 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-filterto revert this. - yt-dlp versions from 2021.11.10 to 2023.06.21 (inclusive) estimated
filesize_approxvalues 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-approxto keep extracting the estimated values. - yt-dlp uses modern http client backends such as
requests. Use--compat-options prefer-legacy-http-handlerto prefer the legacy http handler (urllib) for standard http requests. - The sub-modules
swfinterpandcasefoldare removed 💀 - Passing
--simulate(or callingextract_infowithdownload=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
--mtimeor--compat-options mtime-by-defaultto 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? 💅
--compat-options allow-unsafe-ext— Allow files with any extension (including unsafe ones) to be downloaded (GHSA-79w7-vh3h-8g4j (https://github.com/yt-dlp/yt-dlp/security/advisories/GHSA-79w7-vh3h-8g4j))
: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--execoption allows output template syntax to be used in its commands; however, for security reasons the conversions that can be used are restricted toi/d(signed integer decimal),f(floating-point decimal) andq(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
🌸 Not recommended 🌸
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—avconvis not officially supported byyt-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
- arch rolling: yt-dlp 2026.10.26-1 · Unlicense
- 💖 girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms