How to Download

๐Ÿ“ฅ How to Download YouTube Videos for Free with yt-dlp

Devin
5 min read

๐Ÿ‘‰ Get the Youbtube Video Downloader


1. The Basic Command

For most public YouTube videos, this works fine:

yt-dlp -f "bv*+ba/best" \
  --merge-output-format mp4 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
  • -f "bv*+ba/best" โ†’ pick best video + best audio.
  • --merge-output-format mp4 โ†’ final file is MP4 even if YouTube offers WebM.

2. Why Downloads Sometimes Fail (403 Errors)

YouTube serves every video in multiple delivery protocols:

  • DASH (default) โ†’ uses videoplayback?...range=... URLs.

    • Pros: more quality choices.
    • Cons: sometimes 403s if a range expires.
  • HLS (m3u8 playlists) โ†’ chunked .ts/.mp4 streams.

    • Pros: more resilient, rarely 403s.
    • Cons: fewer quality tiers in some cases.

๐Ÿ‘‰ Thatโ€™s why you sometimes see:

ERROR: unable to download video data: HTTP Error 403: Forbidden

The fix is to have fallback commands to switch protocols and adjust retry behavior.

3. Command Sequence You Should Try

๐Ÿ”น Step 1: Normal DASH Download (default)

yt-dlp -f "bv*+ba/best" -N 16 \
  --merge-output-format mp4 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
  • -N 16 โ†’ download 16 fragments at once (faster).
  • โœ… Try this first โ€” usually works.

๐Ÿ”น Step 2: Force HLS if DASH 403s

yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
  --hls-prefer-native -N 16 \
  --merge-output-format mp4 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
  • Switches to .m3u8 streams.
  • โœ… Best fallback when DASH fails.

๐Ÿ”น Step 3: Add Stronger Retry Logic

yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
  --hls-prefer-native -N 16 \
  --retries 20 --fragment-retries 100 \
  --merge-output-format mp4 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
  • Retries aggressively if any chunk fails.
  • โœ… Good for long/high-bitrate videos.

๐Ÿ”น Step 4: Use aria2c for Maximum Resilience

First install aria2:

brew install aria2

Then run:

yt-dlp -f "bv*+ba/best" \
  --downloader aria2c \
  --downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" \
  --merge-output-format mp4 \
  "https://www.youtube.com/watch?v=VIDEO_ID"
  • -x 16 -s 16 โ†’ 16 parallel connections.
  • Retries indefinitely until the file completes.
  • โœ… Almost bulletproof, even on unstable networks.

4. Extra Tips

  • Clear cache if signatures go stale:

    yt-dlp --rm-cache-dir
    
  • Age-restricted or region-locked videos: Use cookies from your browser:

    yt-dlp --cookies-from-browser "chrome:Default" "URL"
    
  • Specific resolution:

    yt-dlp -f "bv*[height=1080]+ba/best" "URL"
    

โœ… Summary

  • yt-dlp is the tool to use for YouTube โ€” there isnโ€™t a better maintained alternative.

  • Sometimes downloads 403 mid-way because YouTubeโ€™s CDN rejects DASH requests.

  • Solution: keep a 4-step playbook:

    1. Normal DASH with concurrency.
    2. HLS fallback.
    3. HLS + retries.
    4. aria2c external downloader.

With this sequence, youโ€™ll succeed in almost every case.

Great question ๐Ÿ‘

Unfortunately, yt-dlp itself doesnโ€™t have a built-in โ€œtry this, and if it fails, try thatโ€ fallback sequence. It will either succeed or exit with an error. But you can string your four approaches together in one shell command using || (OR chaining).

That way, if the first command fails, the shell runs the next one automatically.


One-liner โ€œall in oneโ€ fallback command

yt-dlp -f "bv*+ba/best" -N 16 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" --hls-prefer-native -N 16 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" --hls-prefer-native -N 16 --retries 20 --fragment-retries 100 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*+ba/best" --downloader aria2c --downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID"

๐Ÿ” How it works

  • cmd1 || cmd2 || cmd3 ... Shell runs cmd1.

    • If it succeeds โ†’ stop.
    • If it fails (non-zero exit code) โ†’ run the next one.

So this tries:

  1. Normal DASH with concurrency.
  2. HLS fallback.
  3. HLS with retries.
  4. aria2c external downloader.

Whichever works first will complete the download.

Yep โ€” you can chain them, but itโ€™s cleaner to try max-quality (MKV, AV1/VP9 OK) first, then fall back to MP4-only variants, with HLS + retries, and finally aria2c. Hereโ€™s a single paste-and-go you can reuse.

One-liner (Zsh/Bash)

VIDEO_URL="https://www.youtube.com/watch?v=S-mow-gu2XA"; \
yt-dlp -N 16 -f "bestvideo*+bestaudio/best" \
  --remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*+ba/best" \
  --http-chunk-size 10M --retries 20 --fragment-retries 100 --force-ipv4 \
  --remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
  --hls-prefer-native --retries 20 --fragment-retries 100 \
  --remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*[ext=mp4][vcodec*=avc1]+ba[ext=m4a]/best[ext=mp4]" \
  --merge-output-format mp4 "$VIDEO_URL" \
|| yt-dlp -f "bv*+ba/best" \
  --downloader aria2c \
  --downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" \
  --merge-output-format mp4 "$VIDEO_URL"

What this does (in order)

  1. Max quality (DASH) โ†’ grabs AV1/VP9 + best audio, remux to MKV (keeps 4K/HDR).
  2. DASH w/ tougher HTTP โ†’ smaller chunks, retries, IPv4; still MKV so high tiers are allowed.
  3. Force HLS โ†’ more resilient when DASH 403s; remux MKV (usually โ‰ค1080p).
  4. MP4-only โ†’ AVC + M4A for widest compatibility (often โ‰ค1080p).
  5. Aria2c fallback โ†’ segmented downloader with infinite retries; outputs MP4.

Tips

  • Change VIDEO_URL once; the chain reuses it.
  • Want a specific res for MKV path? e.g., 2160p: -f "bestvideo*[height=2160]+bestaudio/best".
  • Add an output template if you want a specific filename/path: -o "%(title)s.%(ext)s".
  • If a password/exclamation ever appears in args, quote it with single quotes in zsh.

Related

Related Posts