Skip to content

YouTube β€” Programmatic Download & Automation

Last reviewed: 2026-09-17

Purpose: Reference for downloading and post-processing YouTube media from the command line and from Python β€” yt-dlp usage, FFmpeg post-processing, subtitle and metadata extraction, and configuration patterns.


Overview

YouTube exposes no official download API, so the practical tooling is a small, stable set of open-source components:

Tool Role
yt-dlp Actively maintained fork of youtube-dl β€” downloads video, audio, subtitles, playlists and metadata
pytube Older pure-Python client; simpler API, but breaks whenever YouTube changes internals
FFmpeg Merges separate video/audio streams, transcodes, extracts audio and embeds metadata
python-dotenv Loads URLs and flags from the environment instead of hardcoding them

Respect copyright and each site's terms of service: download your own content, public-domain or explicitly licensed material, and anything you have permission to archive.


Topics Covered

  • Choosing video vs audio-only output
  • Format selection and stream merging with FFmpeg
  • Subtitles and metadata without the media
  • Playlist, channel and time-range selection
  • Configuration through environment variables
  • Driving downloads from Python with the yt-dlp API
  • Comparison of yt-dlp and pytube

Installing the Tools

# yt-dlp (recommended)
python3 -m pip install -U yt-dlp     # or: pipx install yt-dlp

# FFmpeg β€” required for merging streams and audio extraction
sudo apt install -y ffmpeg           # Debian/Ubuntu
brew install ffmpeg                  # macOS

# Verify
yt-dlp --version
ffmpeg -version

Common yt-dlp Commands

# Best video+audio, merged by FFmpeg
yt-dlp -f "bv*+ba/b" -o "%(title)s.%(ext)s" "https://www.youtube.com/watch?v=VIDEO_ID"

# Audio only -> MP3 at 192 kbps
yt-dlp -x --audio-format mp3 --audio-quality 192K "https://www.youtube.com/watch?v=VIDEO_ID"

# List available formats without downloading
yt-dlp -F "https://www.youtube.com/watch?v=VIDEO_ID"

# Subtitles only, no media
yt-dlp --write-auto-subs --skip-download --sub-langs "en.*" "URL"

# Metadata as JSON (title, duration, chapters, uploader, formats)
yt-dlp --dump-json "URL"

# A single item from a playlist (indexes are 1-based)
yt-dlp --playlist-items 3 "https://www.youtube.com/playlist?list=PLAYLIST_ID"

# Embed thumbnail and metadata into the output file
yt-dlp --embed-thumbnail --embed-metadata "URL"
Flag Effect
-f Format selector (bv*+ba/b = best video + best audio, fallback to combined)
-x / --extract-audio Audio-only output
--audio-format Target codec (mp3, m4a, opus, wav)
--audio-quality Bitrate, e.g. 192K
-o Output filename template
--playlist-items Subset of a playlist
--download-archive Skip items already downloaded
--cookies-from-browser Use an authenticated browser session for restricted media
-N Number of concurrent fragment downloads

Downloading from Python

from yt_dlp import YoutubeDL

opts = {
    "format": "bv*+ba/b",
    "outtmpl": "%(title)s.%(ext)s",
    "postprocessors": [
        {
            "key": "FFmpegExtractAudio",
            "preferredcodec": "mp3",
            "preferredquality": "192",
        }
    ],
}

with YoutubeDL(opts) as ydl:
    info = ydl.extract_info("https://www.youtube.com/watch?v=VIDEO_ID", download=True)
    print(info["title"], info["duration"])

A small reusable runner with error handling:

import os
from dotenv import load_dotenv
from yt_dlp import YoutubeDL
from yt_dlp.utils import DownloadError


def download(url: str, audio_only: bool = False) -> None:
    """Download one URL as video, or as MP3 when audio_only is set."""
    opts = {
        "outtmpl": "%(title)s.%(ext)s",
        "format": "bestaudio/best" if audio_only else "bv*+ba/b",
        "quiet": False,
    }
    if audio_only:
        opts["postprocessors"] = [
            {
                "key": "FFmpegExtractAudio",
                "preferredcodec": "mp3",
                "preferredquality": "192",
            }
        ]
    try:
        with YoutubeDL(opts) as ydl:
            ydl.download([url])
    except DownloadError as exc:      # network, geo-blocking, removed video, ...
        print(f"download failed: {exc}")


if __name__ == "__main__":
    load_dotenv()
    download(
        os.environ["VIDEO_URL"],
        audio_only=os.environ.get("ONLY_AUDIO", "FALSE").upper() == "TRUE",
    )

The postprocessors list is what replaces manual FFmpeg calls β€” FFmpegExtractAudio, FFmpegMetadata, EmbedThumbnail cover the common cases.


Library Comparison

Feature pytube yt-dlp
Maintenance Archived/limited Active development
Format support Basic Advanced (HLS, DASH, subtitles, chapters)
Post-processing None FFmpeg integration (merge, convert, thumbnail)
Reliability Moderate (breaks with YouTube changes) High (frequently updated)
Audio extraction Manual rename hack Native FFmpeg integration

Recommendation: use yt-dlp for new projects. It is actively maintained, more reliable, and handles more edge cases.


Audio-Only Workflow

YouTube URL
    ↓
yt-dlp downloads best audio stream
    ↓
FFmpeg extracts audio
    ↓
Converts to MP3 (192 kbps)
    ↓
Saves as "title.mp3"

Configuration via Environment Variables

VIDEO_URL=https://www.youtube.com/watch?v=VIDEO_ID
ONLY_AUDIO=TRUE
import os
from dotenv import load_dotenv

load_dotenv()
url = os.environ["VIDEO_URL"]
audio_only = os.environ.get("ONLY_AUDIO", "FALSE").upper() == "TRUE"

Keeping URLs and flags in the environment (or a .env file excluded from version control) means the same script runs unchanged in a container, a cron job or a notebook.


Automating Downloads

# Append one line per successful download, then skip those on the next run
yt-dlp --download-archive archive.txt -f "bv*+ba/b" "https://www.youtube.com/playlist?list=PLAYLIST_ID"

# Archive new uploads from a channel, keeping filenames stable
yt-dlp --download-archive archive.txt -o "%(upload_date)s - %(title)s.%(ext)s" \
  "https://www.youtube.com/@CHANNEL_HANDLE"

For scheduled jobs, run the command from cron (or a systemd timer) with an absolute output directory and --no-progress, and treat a non-zero exit status as a failure to alert on.


Resources