DEV Community

pickuma
pickuma

Posted on • Originally published at pickuma.com

yt-dlp: The CLI Video Downloader Developers Actually Use in 2026

yt-dlp has become the default tool when you need to programmatically pull video or audio from a URL. It started as a fork of youtube-dl in late 2020, picking up active maintenance after the original project's release cadence slowed. The GitHub repository has crossed 100,000 stars, the extractor list covers well over a thousand sites, and the project ships builds on a regular schedule. We spent a week using it across three workflows — bulk podcast archiving, transcript collection for a speech model, and a small CI job that mirrors a lecture series — and this is what stuck.

Why yt-dlp Replaced youtube-dl

youtube-dl's update cadence slowed in 2020, and YouTube's player kept changing in ways that broke extraction. yt-dlp emerged as a community fork that merged outstanding patches faster, added extractors aggressively, and accepted features the upstream project had declined to ship. The features that matter most for developer workflows:

  • SponsorBlock integration via --sponsorblock-mark and --sponsorblock-remove
  • Native chapter splitting with --split-chapters
  • Concurrent fragment downloads via --concurrent-fragments N
  • A more flexible output template system using Python format-string syntax
  • Plugin architecture for custom extractors and post-processors
  • Live HLS/DASH stream recording with --live-from-start

Most CLI flags from youtube-dl still work, which means existing scripts port over by changing the install command and nothing else. If you have a 2019-era cron job still pointing at youtube-dl, you can usually swap the binary name and keep moving.

Installation and First Run

You have four practical install paths:

# pipx (recommended — isolated environment)
pipx install yt-dlp

# Homebrew on macOS
brew install yt-dlp

# Standalone binary (no Python required on host)
curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o yt-dlp
chmod +x yt-dlp

# pip
pip install -U yt-dlp
Enter fullscreen mode Exit fullscreen mode

The standalone binary embeds Python via PyInstaller, which is the right choice for Docker images where you don't want to maintain a Python toolchain just for downloads. For a one-shot test:

yt-dlp -f "bestvideo[height<=1080]+bestaudio/best" \
       --merge-output-format mp4 \
       'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
Enter fullscreen mode Exit fullscreen mode

That format expression is the bread and butter of yt-dlp. The + joins separate video and audio streams, and --merge-output-format mp4 runs the ffmpeg merge automatically — provided ffmpeg is on your PATH.

yt-dlp does not bundle ffmpeg. Without ffmpeg installed and on PATH, you cannot merge separate video and audio streams, extract audio to mp3, embed thumbnails, or run most post-processors. Install it first: brew install ffmpeg, apt install ffmpeg, or grab a static build for your platform.

Building Pipelines: The Python API

For automation, the CLI is only half the story. yt-dlp is also a Python library, and importing it gives you direct access to the same options without shelling out:

import yt_dlp

opts = {
    'format': 'bestaudio/best',
    'outtmpl': 'downloads/%(channel)s/%(upload_date)s_%(id)s.%(ext)s',
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
    'download_archive': 'archive.txt',
    'ignoreerrors': True,
}

with yt_dlp.YoutubeDL(opts) as ydl:
    ydl.download(['https://www.youtube.com/@somechannel'])
Enter fullscreen mode Exit fullscreen mode

Three flags do the heavy lifting in production pipelines:

--download-archive archive.txt appends each successfully downloaded video ID to a file. On the next run, anything already in the archive is skipped. This is the single most useful flag for cron-driven mirroring of channels or playlists.

-o output template uses Python format-string syntax with metadata fields. %(channel)s, %(upload_date)s, %(id)s, %(title)s, %(ext)s cover most needs. Always include %(id)s somewhere in the path — titles can collide and IDs cannot.

--cookies-from-browser firefox (also accepts chrome, edge, brave, safari, vivaldi) pulls auth cookies from a local browser profile so age-gated, region-gated, or members-only content works. For headless servers, export cookies once with the browser extension of your choice and pass --cookies cookies.txt.

For dataset collection workflows where you only need metadata and captions, combine --write-info-json --write-subs --sub-langs en --skip-download. We used this pattern to build a transcript corpus from a 600-video channel in about 40 minutes — most of the time was waiting on YouTube's subtitle endpoints, not yt-dlp itself.

The --extractor-args flag is the escape hatch when YouTube ships a player change. Something like --extractor-args "youtube:player_client=web,web_safari" forces specific clients when the default starts returning empty format lists. The yt-dlp issue tracker is the canonical place to find the current incantation when extraction suddenly breaks.

Edge Cases and Legal Considerations

Three things bite people in production:

Rate limiting. Hitting YouTube with --concurrent-fragments 16 from a single IP will get you throttled or temporarily blocked. For unattended jobs, throttle yourself: --limit-rate 5M --sleep-interval 5 --max-sleep-interval 15. Slower than you'd like, but it survives the night without a 429 storm.

Site terms of service. yt-dlp can technically download from YouTube, Vimeo, Twitch, SoundCloud, and many other platforms, but most of those services prohibit downloading in their terms. The defensible cases are personal archives of your own uploads, Creative Commons content, content explicitly licensed for redistribution, or material you have written permission to mirror. Building a commercial product on top of scraped video invites takedowns and, in some jurisdictions, civil liability. Talk to a lawyer before you ship a training-data pipeline that ingests anyone else's video.

Format availability changes. Numeric format codes (137, 248, 251, etc.) that worked last quarter may not exist next month — YouTube reshuffles the list when it adds or deprecates encodings. Always use expressions like bestvideo[height<=1080]+bestaudio rather than hard-coding numeric codes. The selector resolves against whatever the extractor returns at runtime.

For long-running pipelines, pin the yt-dlp version. The nightly channel is useful when you need a fresh extractor patch immediately, but breaks reproducibility. Lock to a stable release in production and rebuild the image weekly against the newest stable.

Top comments (0)