# Code with Claude: Audio Archive

Unofficial fan archive tooling for converting Code with Claude YouTube workshop videos into audio-first podcast episode folders. All original audio content belongs to Anthropic.

## Setup

Required media tools:

```bash
brew install yt-dlp ffmpeg
npm install
```

Optional local transcription fallback for Apple Silicon:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install mlx-whisper
```

`mlx-whisper` needs access to the Mac Metal device. In sandboxed shells, run the pipeline outside the sandbox if local transcription is required.

## Commands

Process the latest videos from the configured Code with Claude channel:

```bash
npm run process:channel -- --limit 25 --skip-ai --local-transcribe
```

This discovers the latest channel videos with `yt-dlp --flat-playlist`, skips episode folders that already have matching `metadata.json`, and runs `process-episode.js` sequentially for the rest. The same YouTube caption mitigation environment variables apply to every video in the batch.

Use Claude API enrichment for the batch:

```bash
ANTHROPIC_API_KEY=... npm run process:channel -- --limit 25 --combined-ai --local-transcribe
```

Reprocess existing episodes:

```bash
npm run process:channel -- --limit 25 --force --skip-ai --local-transcribe
```

Process a curated list instead of channel discovery:

```bash
npm run process:channel -- --file input/video-ids.txt --skip-ai --local-transcribe
```

The file should contain one YouTube video ID or URL per line. Lines beginning with `#` are ignored.

Default spec mode: download audio/captions, clean transcript, then generate notes and intro with separate Claude API calls.

```bash
ANTHROPIC_API_KEY=... node scripts/process-episode.js VIDEO_ID_OR_URL
```

Cheaper Claude API mode: generate show notes and intro in one Haiku call so the transcript input is only paid for once.

```bash
ANTHROPIC_API_KEY=... node scripts/process-episode.js --combined-ai VIDEO_ID_OR_URL
```

No-key local draft mode: download audio, use YouTube captions if available, fall back to local MLX transcription if captions are rate-limited, and write local draft notes/intro without API spend.

```bash
node scripts/process-episode.js --skip-ai --local-transcribe VIDEO_ID_OR_URL
```

The local transcription model defaults to `mlx-community/whisper-small-mlx`. Override it with:

```bash
CWC_WHISPER_MODEL=mlx-community/whisper-base-mlx node scripts/process-episode.js --skip-ai --local-transcribe VIDEO_ID_OR_URL
```

## YouTube Transcript 429 Mitigation

The pipeline now tries transcript sources in this order:

1. `yt-dlp` exact English VTT captions (`--sub-lang en`)
2. Direct English timed-text URL from `yt-dlp --dump-json` metadata
3. Local MLX Whisper fallback when `--local-transcribe` is enabled

If YouTube returns HTTP 429 for timed-text captions, configure one of these before running the pipeline.

Use fresh browser cookies:

```bash
CWC_YTDLP_COOKIES_FROM_BROWSER=chrome node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

For batch processing:

```bash
CWC_YTDLP_COOKIES_FROM_BROWSER=chrome npm run process:channel -- --limit 25 --local-transcribe
```

or:

```bash
CWC_YTDLP_COOKIES_FROM_BROWSER=safari node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

Use an exported cookies file:

```bash
CWC_YTDLP_COOKIES=/absolute/path/to/cookies.txt node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

Use a YouTube subtitle PO token:

```bash
CWC_YTDLP_EXTRACTOR_ARGS='youtube:po_token=web.subs+TOKEN_VALUE' node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

For batch processing:

```bash
CWC_YTDLP_EXTRACTOR_ARGS='youtube:po_token=web.subs+TOKEN_VALUE' npm run process:channel -- --limit 25 --local-transcribe
```

Use a PO-token provider plugin if installed. The plugin is loaded by `yt-dlp`; no code change is needed here. Confirm with:

```bash
yt-dlp -v --skip-download 'https://www.youtube.com/watch?v=8N1-XHNupfg' 2>&1 | grep 'PO Token Providers'
```

Optional request pacing:

```bash
CWC_YTDLP_SLEEP_REQUESTS=2 CWC_YTDLP_SLEEP_SUBTITLES=10 node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

Optional browser impersonation:

```bash
CWC_YTDLP_IMPERSONATE='chrome-136:macos-15' node scripts/process-episode.js --local-transcribe VIDEO_ID_OR_URL
```

Notes:

- Do not use `--all-subs` for this archive; it creates many timed-text requests and makes 429s more likely.
- Prefer exact `en` captions over translated captions.
- If cookies or PO tokens are not configured, `--local-transcribe` is the reliable no-cost fallback.

## Test Episode

The current verified test episode was produced from:

```bash
node scripts/process-episode.js --skip-ai --local-transcribe 8N1-XHNupfg
```

YouTube timed-text captions returned HTTP 429 during verification, so the non-empty transcript was produced by local MLX Whisper from the downloaded audio.

Output:

```text
output/episodes/team-thinking-visualized-by-claude-8N1-XHNupfg/
  audio.mp3
  transcript.txt
  show-notes.md
  intro-script.md
  metadata.json
```

## Tests

```bash
npm test
```
