Skip to content

Notes

The yt-dlp flags worth knowing on a Mac

Not another install tutorial. The twenty or so flags a real user converges on after a year, what each is actually for, and the config file that means never typing them again.

yt-dlp has several hundred options. You will use about twenty. This is those twenty, grouped by the problem they solve, written to be bookmarked rather than read once.

Everything here was checked against yt-dlp 2026.07.04 on macOS.

Install and keep it current

brew install yt-dlp ffmpeg

FFmpeg is not optional in practice: above 360p, YouTube stores video and audio separately and something has to combine them. Without it you get two files, one silent.

brew upgrade yt-dlp

Do this before debugging anything. A large share of “yt-dlp is broken” is a build from before YouTube’s last change. yt-dlp -U updates a standalone binary but will refuse to touch a Homebrew install.

Choosing a format

The single most useful flag, and the one most people never touch:

yt-dlp -F "URL"

It lists every stream, with codec, resolution and size. Read it before guessing.

-f "bestvideo[vcodec^=avc1]+bestaudio[ext=m4a]"   # H.264 — opens everywhere
-f "bestvideo+bestaudio/best"                     # best available, any codec
-f "best[ext=mp4]"                                # single file, capped at 360p
-f bestaudio[ext=m4a]                             # audio only, no re-encoding
-f "bv[height<=1080]+ba"                          # cap the resolution

bv and ba are shorthand for bestvideo and bestaudio. The / is a fallback: A/B means try A, then B.

The one worth understanding is vcodec^=avc1, which is H.264. YouTube serves nothing above 1080p in H.264 — above that it is VP9 and AV1, which QuickTime does not play. That is an article of its own and the reason this selector exists.

--merge-output-format mkv

When mixing codecs that MP4 will not hold. MKV accepts anything without converting.

Naming files

-o "%(title)s.%(ext)s"                                  # the default, roughly
-o "%(uploader)s - %(title)s.%(ext)s"
-o "%(upload_date)s - %(title)s [%(id)s].%(ext)s"       # sorts by date
-o "%(playlist_index)03d - %(title)s.%(ext)s"           # sorts by position
-o "%(upload_date>%Y)s/%(title)s.%(ext)s"               # into per-year folders

Three details that save trouble later:

  • %(playlist_index)03d — the 03d zero-pads, so 002 sorts before 010. Without it Finder puts 10 before 2.
  • [%(id)s] — keeps the eleven-character video ID in the name. It is how you find the original after a title changes.
  • %(upload_date>%Y)s — anything after > is a strftime format.
--restrict-filenames

ASCII only, no spaces. Worth it if the files are headed for a server, a script, or a drive shared with Windows.

Subtitles

--list-subs                                    # see what exists
--write-subs --sub-langs en --convert-subs srt # creator subtitles
--write-auto-subs --sub-format srv1            # automatic captions, clean
--embed-subs                                   # as a track inside the video
--skip-download                                # subtitles only, no video

--sub-format srv1 is the one to remember. The default download of automatic captions arrives with every line duplicated two or three times; srv1 does not have that problem at all.

Playlists and channels

--yes-playlist / --no-playlist       # resolve the video-or-list ambiguity
--playlist-items 1-25                # a range
--playlist-items 3,7,11-14           # a selection
--flat-playlist --print "%(title)s"  # list without opening each video
--download-archive done.txt          # skip what previous runs completed
--match-filter "duration > 120"      # skip trailers and shorts

--download-archive is the one that matters. It records completed IDs, so a re-run skips them — which is what makes a long job survivable and a scheduled sync cheap. See playlists and channel archives.

Only part of a video

--download-sections "*3:40-7:15"     # a time range
--download-sections "*-30-0"         # the last 30 seconds
--download-sections "Introduction"   # a chapter, by name (no *)
--split-chapters                     # every chapter as its own file
--force-keyframes-at-cuts            # exact cuts, at the cost of re-encoding

--download-sections fetches only the bytes it needs — ten seconds out of a long 4K video came out at 93 KB in about two seconds. The full piece, including why it returns 403 if you pair it with the wrong format.

Not getting blocked

--sleep-requests 1                     # between metadata requests
--min-sleep-interval 5                 # between downloads
--max-sleep-interval 15
--limit-rate 5M                        # cap bandwidth
--retries 5
--fragment-retries 20
--continue                             # resume a partial file

--sleep-requests is the one that prevents HTTP Error 429 on playlists. --fragment-retries matters more than it looks: big downloads arrive in fragments and one failed fragment should not restart the file. Details in 403, 429 and downloads that crawl.

Metadata and tidying up

--embed-thumbnail       # cover art in the file
--embed-metadata        # title, artist, date into the tags
--embed-chapters        # chapter markers players can jump between
--write-info-json       # everything YouTube knows, as JSON alongside
--write-thumbnail       # the thumbnail as a separate image

For music, --embed-thumbnail --embed-metadata is the difference between a usable library and a folder of untitled tracks. For an archive, --write-info-json preserves descriptions and dates that disappear with the channel.

Diagnosing

--simulate              # do everything except download
--print "%(title)s"     # print one field and stop
--print "%(chapters)j"  # a field as JSON
-J                      # the whole metadata object
--verbose               # what to paste into a bug report
--rm-cache-dir          # clear cached signature data
--ignore-config         # run without your config file

--ignore-config is the first thing to try when behaviour makes no sense: it tells you in one command whether the problem is yt-dlp or something you wrote in the config months ago and forgot.

Stop typing all this

Put the settings you always want in ~/.config/yt-dlp/config. One flag per line, no quotes around the whole line:

# Format: H.264 so it opens in QuickTime, capped at 1080p
-f bestvideo[vcodec^=avc1][height<=1080]+bestaudio[ext=m4a]/best

# Where things go
-o ~/Downloads/%(title)s.%(ext)s

# Be polite; 429 costs hours
--sleep-requests 1
--min-sleep-interval 3
--max-sleep-interval 8

# Resilience
--continue
--retries 5
--fragment-retries 20

# Metadata worth keeping
--embed-metadata
--embed-chapters

Override any of it per run — command line beats config. And when something behaves strangely, --ignore-config tells you whether this file is why.

The ones people use and probably should not

--cookies-from-browser chrome — genuinely necessary for private and members-only videos. Everywhere else it attaches your identity to activity that was previously anonymous, which raises what a mistake costs without fixing anything. It is widely recommended as a cure for “Sign in to confirm you’re not a bot”; read that before reaching for it.

--concurrent-fragments 8 — faster on a fast line, and one of the surest ways to get rate limited on a long job. Fine for one video, wrong for nine hundred.

--retries infinite — turns a temporary block into a permanent one by never stopping asking.

And if you would rather not

Everything above is one program with a large surface. Most people want three of these behaviours — H.264 so it plays, audio extraction that does not re-encode, and an engine that stays current — and would rather not maintain a config file to get them.

That is the app I build: the same yt-dlp underneath, with those defaults chosen and kept up to date on its own. It does less than the command line, on purpose. When you need --download-sections or a scheduled channel sync, come back to this page — the commands here are the ones it runs anyway.