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

> Discover how the claude-video module leverages yt-dlp to download videos and captions. Learn about the fetch_captions and download_url functions for efficient content retrieval.

- Repository: [bradautomates/claude-video](https://github.com/bradautomates/claude-video)
- Tags: how-to-guide
- Published: 2026-08-04

---

**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`](https://github.com/bradautomates/claude-video/blob/main/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 URLs** → `download_url()` or `fetch_captions()`
- **Local file paths** → `resolve_local()`

This abstraction ensures downstream scripts like [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) and [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py) receive consistent dictionary structures regardless of media source.

Key files in the download pipeline:

| File | Purpose |
|------|---------|
| [`skills/watch/scripts/download.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/download.py) | Core yt-dlp orchestration |
| [`skills/watch/scripts/watch.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/watch.py) | Entry point calling `download()` |
| [`skills/watch/scripts/transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/transcribe.py) | Consumes VTT output from downloads |
| [`skills/watch/scripts/config.py`](https://github.com/bradautomates/claude-video/blob/main/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

```python
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

```python
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

```python
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`](https://github.com/bradautomates/claude-video/blob/main/video.info.json) produced by yt-dlp's default metadata extraction, surfacing title, duration, uploader, and other metadata.

### Unified Dispatch Function

```python
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

```python
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)

```python
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

```python
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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/watch.py) and [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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`](https://github.com/bradautomates/claude-video/blob/main/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.