How the Claude-Video Download Module Uses yt-dlp for Fetching Videos and Captions

The claude-video download module orchestrates yt-dlp through two dedicated Python functions: fetch_captions() for subtitle-only retrieval and download_url() for full video downloads, both located in skills/watch/scripts/download.py.

The claude-video repository by bradautomates provides a reusable skill for processing video content with Claude. At its core lies a download module that abstracts yt-dlp into clean Python interfaces, handling everything from 720p video extraction to WebVTT subtitle generation. This article breaks down exactly how the module leverages yt-dlp's CLI capabilities through structured subprocess calls.


Overview of the Download Architecture

The download module is designed around a dispatcher pattern. The main entry point, download(), routes requests to specialized handlers based on input type:

  • Remote URLsdownload_url() or fetch_captions()
  • Local file pathsresolve_local()

This abstraction ensures downstream scripts like watch.py and transcribe.py receive consistent dictionary structures regardless of media source.

Key files in the download pipeline:

File Purpose
skills/watch/scripts/download.py Core yt-dlp orchestration
skills/watch/scripts/watch.py Entry point calling download()
skills/watch/scripts/transcribe.py Consumes VTT output from downloads
skills/watch/scripts/config.py Shared configuration defaults

Fetching Captions Without Video Download

The fetch_captions() function provides lightweight subtitle retrieval when video content already exists or isn't needed. Located at skills/watch/scripts/download.py#L65-L95, this function demonstrates precise yt-dlp flag selection.

Implementation Details

def fetch_captions(url: str, out_dir: Path) -> dict:
    """Download only subtitles for a given URL."""
    if not shutil.which("yt-dlp"):
        raise RuntimeError("yt-dlp not found")
    
    out_dir.mkdir(parents=True, exist_ok=True)
    template = str(out_dir / "video.%(ext)s")
    
    cmd = [
        "yt-dlp",
        "--skip-download",           # Skip video entirely

        "--write-subs",              # Request manual subtitles

        "--write-auto-subs",         # Request auto-generated subtitles

        "--sub-langs", "en.*",       # English language tracks only

        "--sub-format", "vtt",       # Preferred format

        "--convert-subs", "vtt",     # Force WebVTT output

        "--output", template,
        url
    ]
    
    subprocess.run(cmd, check=True)
    
    return {
        "video_path": None,
        "subtitle_path": _pick_subtitle(out_dir),
        "info": _read_info(out_dir),
        "downloaded": False
    }

The --skip-download flag is critical here—it prevents any video bytes from transferring while still querying YouTube's subtitle endpoints. The module enforces WebVTT format through dual flags: --sub-format vtt requests VTT from the source, and --convert-subs vtt guarantees conversion if source subtitles exist in SRT or other formats.

The function returns a standardized dictionary with downloaded: False explicitly signaling that no video file was retrieved.


Downloading Video with Optional Captions

For full media retrieval, download_url() at skills/watch/scripts/download.py#L15-L63 constructs a more complex yt-dlp invocation with format selection, parallel downloading, and integrated subtitle handling.

Video Format Selection Strategy

def download_url(url: str, out_dir: Path, audio_only: bool = False) -> dict:
    """Download video/audio with embedded subtitle retrieval."""
    if not shutil.which("yt-dlp"):
        raise RuntimeError("yt-dlp not found")
    
    out_dir.mkdir(parents=True, exist_ok=True)
    template = str(out_dir / "video.%(ext)s")
    
    # Format selector: best 720p video + audio, or audio-only

    fmt = "bestaudio/best" if audio_only else "bestvideo[height<=720]+bestaudio/best"
    
    cmd = [
        "yt-dlp",
        "-N", "8",                           # 8 parallel fragment downloads

        "-f", fmt,                           # Stream selection

        "--merge-output-format", "mp4",      # Container normalization

        "--write-subs",
        "--write-auto-subs",
        "--sub-langs", "en.*",
        "--sub-format", "vtt",
        "--convert-subs", "vtt",
        "--no-playlist",                     # Single video, no playlist expansion

        "--ignore-errors",                   # Continue on non-critical failures

        "--output", template,
        url
    ]
    
    subprocess.run(cmd, check=True)
    
    return {
        "video_path": _pick_video(out_dir),
        "subtitle_path": _pick_subtitle(out_dir),
        "info": _read_info(out_dir),
        "downloaded": True
    }

Key yt-dlp Optimizations

  • -N 8: Enables multithreaded fragment downloading, significantly improving throughput on high-latency connections.
  • bestvideo[height<=720]+bestaudio: Constrains vertical resolution to 720p, balancing quality against storage and processing overhead for Claude's vision capabilities.
  • --merge-output-format mp4: Normalizes outputs to MP4 container regardless of source codec, ensuring predictable downstream handling.

The audio_only parameter switches the format selector to bestaudio/best, producing M4A or similar audio containers without video streams.


Supporting Utilities and Return Uniformity

Both download functions rely on shared helpers that enforce consistent behavior:

URL Detection and Local File Handling

def is_url(source: str) -> bool:
    """Detect whether input is a remote URL."""
    parsed = urllib.parse.urlparse(source)
    return bool(parsed.scheme and parsed.netloc)

def resolve_local(path: str) -> dict:
    """Return compatible structure for already-local files."""
    path_obj = Path(path).resolve()
    return {
        "video_path": str(path_obj),
        "subtitle_path": None,
        "info": {"title": path_obj.stem},
        "downloaded": False
    }

Post-Processing Helpers

  • _pick_video(out_dir): Locates the downloaded video file using glob patterns against expected extensions.
  • _pick_subtitle(out_dir): Selects the best available VTT file, preferring manual subtitles over auto-generated when multiple English tracks exist.
  • _read_info(out_dir): Parses video.info.json produced by yt-dlp's default metadata extraction, surfacing title, duration, uploader, and other metadata.

Unified Dispatch Function

def download(source: str, out_dir: Path, audio_only: bool = False) -> dict:
    """Route to appropriate handler based on source type."""
    if is_url(source):
        return download_url(source, out_dir, audio_only)
    return resolve_local(source)

Practical Usage Examples

Subtitle-Only Retrieval

from pathlib import Path
from skills.watch.scripts.download import fetch_captions

url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
out_dir = Path("/tmp/lecture_captions")

result = fetch_captions(url, out_dir)
print(result["subtitle_path"])   # → /tmp/lecture_captions/video.en.vtt

print(result["info"]["title"])   # → metadata from info.json

Standard Video Download (720p with Captions)

from pathlib import Path
from skills.watch.scripts.download import download

url = "https://www.youtube.com/watch?v=exampleID"
out_dir = Path("/tmp/processed_video")

dl = download(url, out_dir)
print(dl["video_path"])      # → /tmp/processed_video/video.mp4

print(dl["subtitle_path"])   # → /tmp/processed_video/video.en.vtt

print(dl["downloaded"])      # → True

Audio-Only Extraction

from pathlib import Path
from skills.watch.scripts.download import download

url = "https://www.youtube.com/watch?v=podcastID"
out_dir = Path("/tmp/podcast_audio")

dl = download(url, out_dir, audio_only=True)
print(dl["video_path"])   # → /tmp/podcast_audio/video.m4a

Summary

  • fetch_captions() in skills/watch/scripts/download.py retrieves WebVTT subtitles without downloading video bytes, using --skip-download with targeted subtitle flags.
  • download_url() handles full media retrieval with 720p format selection, parallel downloads (-N 8), MP4 container normalization, and integrated subtitle fetching.
  • Return dictionaries maintain consistent structure across all paths, containing video_path, subtitle_path, info metadata, and boolean downloaded status.
  • Utility functions (is_url, resolve_local, _pick_video, _pick_subtitle, _read_info) provide input flexibility and post-processing reliability.
  • The download() dispatcher enables transparent handling of both remote URLs and local file paths for downstream watch.py and transcribe.py operations.

Frequently Asked Questions

What yt-dlp version requirements exist for claude-video?

The module requires any yt-dlp version supporting --convert-subs and parallel download flags (-N). These have been stable since 2022 releases. The code verifies installation via shutil.which("yt-dlp") but does not enforce specific version checks.

Can the download module fetch non-English subtitles?

As implemented in bradautomates/claude-video, the subtitle language is hardcoded to en.* in both fetch_captions() and download_url(). Modifying --sub-langs to other language codes (e.g., es.*, ja.*) would require editing skills/watch/scripts/download.py directly.

What happens if a video has no available subtitles?

The yt-dlp process completes successfully but produces no VTT files. The _pick_subtitle() helper returns None, which downstream scripts like transcribe.py handle by falling back to Whisper transcription on the audio track.

Why is video resolution capped at 720p?

The format selector bestvideo[height<=720]+bestaudio intentionally limits vertical resolution. This 720p ceiling reduces download time, storage requirements, and processing load for Claude's vision model while maintaining sufficient quality for content comprehension.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →