# How the Download Module Uses yt-dlp to Fetch Captions and Extract Audio/Video in claude-video

> Learn how the claude-video download module leverages yt-dlp for efficient caption fetching and audio/video extraction. Discover techniques like skip download and N 8 parallelism.

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

---

**The `download` module in `claude-video` wraps yt-dlp in Python subprocess calls, using `--skip-download` for caption-only retrieval and format selectors with `-N 8` parallelism for audio/video extraction.**

The `download` module ([`skills/watch/scripts/download.py`](https://github.com/bradautomates/claude-video/blob/main/skills/watch/scripts/download.py)) is the core media handler in the `bradautomates/claude-video` repository. It provides a clean Python interface to yt-dlp for downloading YouTube and other platform videos, extracting English captions in VTT format, and resolving local file paths. This article breaks down exactly how the module orchestrates yt-dlp subprocess calls, constructs format selectors, and processes the resulting metadata.

## Core Functions and Their yt-dlp Invocation Patterns

The module exposes three primary entry points, two of which invoke yt-dlp directly.

### `fetch_captions()` — Caption Retrieval Without Video Download

When you only need subtitles and metadata, **`fetch_captions(url, out_dir)`** (lines 67-95) invokes yt-dlp with `--skip-download` to avoid transferring video bytes.

The command array built at lines 73-86 includes these critical options:

- `--skip-download` — Prohibit video download entirely
- `--write-info-json` — Save metadata to [`video.info.json`](https://github.com/bradautomates/claude-video/blob/main/video.info.json)
- `--write-subs` and `--write-auto-subs` — Request both manual and auto-generated subtitles
- `--sub-langs en.*` — Filter for English language tracks only
- `--sub-format vtt` and `--convert-subs vtt` — Force VTT output for downstream parsing
- `--no-playlist` and `--ignore-errors` — Single video focus with fault tolerance
- `-o video.%(ext)s` — Predictable output naming

The function returns a dict with `video_path: None` and `downloaded: False` (lines 91-95), signaling that no media file was produced.

### `download_url()` — Full Video or Audio-Only Download

For actual media retrieval, **`download_url(url, out_dir, audio_only=False)`** (lines 26-62) constructs a format-aware yt-dlp command.

**Format selector logic** (line 26):

```python
fmt = "bestaudio/best" if audio_only else "best[height<=720]/best[height<=720]"

```

This caps video resolution at 720p—sufficient for frame extraction while minimizing bandwidth and storage.

The full command assembled at lines 28-43 adds:

- `-N 8` — Download 8 fragments in parallel for speed
- `-f <fmt>` — Inject the format selector above
- `--merge-output-format mp4` — Uniform container format
- All caption options from `fetch_captions()` for simultaneous subtitle retrieval

Post-execution, `_pick_video()` (lines 55-62) locates the downloaded file, `_pick_subtitle()` (lines 44-52) selects the best VTT, and `_read_info()` (lines 98-112) parses the JSON metadata.

### `resolve_local()` — Local File Path Handling

For non-URL inputs, **`resolve_local(path)`** validates the file exists and returns a corresponding dict without any yt-dlp invocation. This ensures the `download()` dispatcher (lines 20-25) handles both remote and local sources uniformly.

## URL Detection and Dispatch Flow

The module's entry point **`download()`** follows this decision tree:

1. **`is_url()`** (lines 20-25) tests if the source string matches `^https?://`
2. If URL: forward to `download_url()` or `fetch_captions()` depending on caller intent
3. If local path: delegate to `resolve_local()`

```python
from pathlib import Path
from skills.watch.scripts.download import download, fetch_captions

# Download video + captions

result = download(
    "https://www.youtube.com/watch?v=abc123XYZ",
    Path("/tmp/watch-output"),
    audio_only=False  # Set True for audio-only extraction

)

# Or fetch captions without downloading video

captions_only = fetch_captions(
    "https://vimeo.com/987654321",
    Path("/tmp/captions")
)

```

## Post-Processing: File Selection and Metadata Extraction

After yt-dlp completes, three helper functions standardize the output:

| Function | Purpose | Lines |
|----------|---------|-------|
| `_pick_video()` | Scan output directory for video file (prefers `.mp4`, falls back to any video extension) | 55-62 |
| `_pick_subtitle()` | Select best VTT subtitle, preferring `en` over `en-*` variants | 44-52 |
| `_read_info()` | Parse [`video.info.json`](https://github.com/bradautomates/claude-video/blob/main/video.info.json) for title, uploader, duration, and canonical URL | 98-112 |

The final returned dict contains:

```python
{
    "video_path": "/tmp/watch-output/video.mp4",   # or None if --skip-download

    "subtitle_path": "/tmp/watch-output/video.en.vtt",
    "info": {
        "title": "Video Title",
        "uploader": "Channel Name",
        "duration": 123,
        "url": "https://www.youtube.com/watch?v=abc123XYZ"
    },
    "downloaded": True  # or False for caption-only mode

}

```

## Why yt-dlp Over Other Tools

The `claude-video` project selected yt-dlp based on source code implementation priorities evident in [`download.py`](https://github.com/bradautomates/claude-video/blob/main/download.py):

**Reliability across platforms** — yt-dlp handles YouTube, Vimeo, and hundreds of other extractors without API keys or rate-limit complications.

**Native subtitle pipeline** — The `--sub-langs en.*` → `--convert-subs vtt` chain produces immediately usable captions for [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py), bypassing Whisper when manual subtitles exist.

**Performance tuning** — The `-N 8` parallelism and 720p format ceiling balance speed against downstream processing needs (frame extraction at 720p yields sufficient detail for most computer vision tasks).

**Predictable output structure** — The `-o video.%(ext)s` template ensures [`download.py`](https://github.com/bradautomates/claude-video/blob/main/download.py) can locate files without parsing yt-dlp's verbose stdout.

## Integration with the Watch Pipeline

The `download` module serves as the first stage in the `/watch` slash-command pipeline:

1. [`watch.py`](https://github.com/bradautomates/claude-video/blob/main/watch.py) receives user URL or path input
2. [`download.py`](https://github.com/bradautomates/claude-video/blob/main/download.py) produces video file, VTT captions, and metadata dict
3. [`frames.py`](https://github.com/bradautomates/claude-video/blob/main/frames.py) extracts representative frames from the video
4. [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py) consumes VTT captions directly, or falls back to Whisper audio transcription

This architecture makes yt-dlp's output contracts (VTT format, JSON info schema) critical integration points across the codebase.

## Summary

- **`fetch_captions()`** uses `--skip-download` for metadata + subtitle retrieval without video transfer
- **`download_url()`** builds format selectors (`best[height<=720]` or `bestaudio`) with `-N 8` parallelism
- **Both functions** enforce VTT subtitle output via `--convert-subs vtt` for downstream compatibility
- **Post-processing helpers** (`_pick_video`, `_pick_subtitle`, `_read_info`) standardize yt-dlp's variable output into predictable Python dicts
- **The 720p ceiling** optimizes the storage/quality trade-off for frame-extraction workflows

## Frequently Asked Questions

### What video quality does claude-video download by default?

The `download_url()` function caps video at 720p using the format selector `best[height<=720]/best[height<=720]`. This resolution provides adequate detail for frame extraction while minimizing download time and storage. For audio-only extraction, set `audio_only=True` to select `bestaudio/best` instead.

### Can I use the download module for platforms other than YouTube?

Yes. The module invokes yt-dlp generically without platform-specific logic, so any site supported by yt-dlp (Vimeo, Twitter, Reddit, etc.) works automatically. The `--no-playlist` flag ensures single-video handling regardless of source.

### How does the module handle videos without English captions?

If yt-dlp finds no English subtitles matching `--sub-langs en.*`, the `_pick_subtitle()` helper returns `None` in the result dict. Downstream [`transcribe.py`](https://github.com/bradautomates/claude-video/blob/main/transcribe.py) detects this absence and falls back to Whisper audio transcription. The video still downloads successfully.

### Is parallel downloading configurable?

The `-N 8` fragment parallelism is hardcoded in `download_url()` at lines 28-43. Modifying this requires editing the source. The value balances speed against connection overhead for typical residential and cloud bandwidth profiles.