Skip to content

yt-dlp

Downloads media from a thousand-plus sites, with fine control over formats and metadata.

Homebrew formula

What it does

yt-dlp is the maintained successor to youtube-dl. It resolves a page to its media streams, picks or merges formats, and writes the result with metadata, thumbnails, subtitles and chapters attached.

# best video+audio up to 1080p, merged to mp4
yt-dlp -f "bv*[height<=1080]+ba/b" --merge-output-format mp4 URL

# audio only
yt-dlp -x --audio-format m4a URL

# a playlist into a tidy tree
yt-dlp -o "%(playlist)s/%(playlist_index)02d - %(title)s.%(ext)s" URL

# subtitles, no video
yt-dlp --skip-download --write-auto-subs --sub-langs en URL

Aliases

The flags above are long and never change, which makes yt-dlp a good candidate for a block of shell aliases. Drop this in ~/.zshrc — or in a file it sources — and the common cases become one word.

# Defined only when yt-dlp is on PATH, so shells without it start clean.
if (( $+commands[yt-dlp] )); then
  # Flags shared by every download alias below. Referenced at call time, so
  # editing this array changes all of them at once.
  _yt_common=(
    --embed-metadata
    --embed-chapters
    --embed-thumbnail
    --sponsorblock-mark all
    --concurrent-fragments 4
    -o '%(title)s [%(id)s].%(ext)s'
  )

  # Cookie source for private, age-gated or members-only videos. Empty by
  # default: an empty zsh array expands to zero words, so the aliases below
  # run cookie-free until you opt in. Set it for the session with
  # `ytcookies brave`, or persist it by exporting YTDL_COOKIES (a browser
  # name, "browser:Profile Name", or a cookies.txt path).
  _yt_cookies=()
  if [[ -n $YTDL_COOKIES ]]; then
    if [[ -f $YTDL_COOKIES ]]; then
      _yt_cookies=(--cookies "$YTDL_COOKIES")
    else
      _yt_cookies=(--cookies-from-browser "$YTDL_COOKIES")
    fi
  fi

  ytcookies() {
    case "$1" in
      '')
        if (( $#_yt_cookies )); then
          print "yt cookies: ${_yt_cookies[2]} (${_yt_cookies[1]})"
        else
          print 'yt cookies: off'
        fi
        ;;
      off|none|clear)
        _yt_cookies=()
        print 'yt cookies: off'
        ;;
      *)
        if [[ -f $1 ]]; then
          _yt_cookies=(--cookies "${1:A}")
        else
          _yt_cookies=(--cookies-from-browser "$1")
        fi
        print "yt cookies: ${_yt_cookies[2]} (${_yt_cookies[1]})"
        ;;
    esac
  }

  # Video. --no-playlist so a playlist URL grabs only the video you pasted.
  alias ytv='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist --embed-subs --sub-langs "en.*" -f "bv*[height<=1080][vcodec^=avc1]+ba[acodec^=mp4a]/b[height<=1080]" --merge-output-format mp4'
  alias ytmax='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -f "bv*+ba/b" -S "res,fps,hdr:12,vcodec:av01,br" --merge-output-format mkv'
  alias yt4k='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -f "bv*+ba/b" -S "res:2160,fps,hdr:12,br" --merge-output-format mkv'
  alias yt1080='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -f "bv*+ba/b" -S "res:1080,fps,br" --merge-output-format mkv'

  # Audio. ytm4a/ytopus keep the source stream (no lossy re-encode); ytmp3
  # re-encodes, for players that only speak mp3.
  alias ytmp3='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -x --audio-format mp3 --audio-quality 0'
  alias ytm4a='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -f "ba[ext=m4a]/ba" -x --audio-format m4a --audio-quality 0'
  alias ytopus='yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist -f "ba[ext=webm]/ba" -x --audio-format opus --audio-quality 0'

  # Whole playlist into its own folder, zero-padded track numbers.
  alias ytlist='yt-dlp "${_yt_cookies[@]}" --embed-metadata --embed-chapters --embed-thumbnail --sponsorblock-mark all --concurrent-fragments 4 --yes-playlist -f "bv*+ba/b" -S "res:1080,fps,br" --merge-output-format mkv -o "%(playlist)s/%(playlist_index)03d - %(title)s.%(ext)s"'

  # Sidecars, no media.
  alias ytsub='yt-dlp "${_yt_cookies[@]}" --skip-download --write-subs --write-auto-subs --sub-langs "en.*" --convert-subs srt -o "%(title)s [%(id)s].%(ext)s"'
  alias ytthumb='yt-dlp "${_yt_cookies[@]}" --skip-download --write-thumbnail --convert-thumbnails jpg -o "%(title)s [%(id)s].%(ext)s"'
  alias ytfmt='yt-dlp "${_yt_cookies[@]}" -F'

  alias ytup='brew upgrade yt-dlp'

  # ytclip URL START END   e.g. ytclip https://youtu.be/xyz 1:20 2:45
  ytclip() {
    if (( $# < 3 )); then
      print -u2 'usage: ytclip URL START END   (e.g. ytclip URL 1:20 2:45)'
      return 2
    fi
    yt-dlp "${_yt_common[@]}" "${_yt_cookies[@]}" --no-playlist \
      --download-sections "*$2-$3" --force-keyframes-at-cuts \
      -f "bv*+ba/b" -S "res,fps,br" --merge-output-format mkv \
      -o '%(title)s [%(id)s] '"$2-$3"'.%(ext)s' \
      "$1"
  }
fi

What each one is for:

AliasDoes
ytv URL1080p H.264 + AAC in an mp4 — the format everything plays
ytmax URLBest available, AV1 and HDR preferred, in mkv
yt4k URL / yt1080 URLCapped at that resolution rather than best-effort
ytmp3 / ytm4a / ytopusAudio only; m4a and opus keep the source stream, mp3 re-encodes
ytlist URLThe whole playlist into its own folder, tracks numbered 001 -
ytsub / ytthumbSubtitles or thumbnail, no media
ytfmt URLList the formats the site offers, so -f can be chosen by hand
ytclip URL 1:20 2:45Only that section, cut at keyframes
ytcookies braveRead cookies live from a browser for the rest of the session
ytupUpdate yt-dlp, which fixes most extractor failures

The syntax is zsh. In bash, replace (( $+commands[yt-dlp] )) with command -v yt-dlp >/dev/null and ${1:A} with $(cd "$(dirname "$1")" && printf '%s/%s' "$PWD" "$(basename "$1")").

--cookies-from-browser hands yt-dlp your live session cookies for that site. It is the only way to reach anything behind a login, and it is also a credential — do not pair it with a URL you do not trust, and prefer an exported cookies.txt scoped to one site when you can.

Notes

  • Needs ffmpeg for merging separate video and audio streams and for any post-processing.
  • Site extractors break when sites change; updating (brew upgrade yt-dlp) is the fix for most failures.
  • Downloading is only lawful for content you have the right to download. Respect terms of service and copyright — this is a tool for archiving your own material, Creative Commons content and things you are licensed to keep.

Alternative to

AlternativeTypeTrade-off
youtube-dlOpen sourceThe original, effectively unmaintained; yt-dlp is the fork that kept going
DowniePaidMac-native, a drag-and-drop GUI over the same job
JDownloaderOpen sourceHeavier, Java-based, strong at multi-file hosts
gallery-dlOpen sourceThe equivalent for images and image boards

Install

brew install yt-dlp

Links

This post is licensed under CC BY 4.0 by the author.
Last updated on