# Youtube Publisher

> Upload and fully manage YouTube videos and live events: metadata, thumbnails, captions, playlists, scheduled broadcasts, encoder streams, binding, lifecycle transitions, and cleanup.

- Skill: `wlzh/youtube-publisher` (Agent Skill, multi-file: 28 files)
- Install (CLI): `npx skillmds@latest add wlzh/youtube-publisher`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wlzh/youtube-publisher/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: wlzh (https://skillmd.com/u/wlzh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wlzh/youtube-publisher

---


## 🔴 Strict Execution Rule (Highest Priority)

Every instruction, step, and check in this SKILL.md must be **followed exactly as written** — no deviations allowed.

- **Scripts must be actually executed**: The `youtube-upload.ts` script must be run with all required parameters. Do not simulate or manually construct API calls.
- **Steps must not be skipped or merged**: Execute authentication, metadata sanitization, and upload in the defined order. Each step must succeed before the next begins.
- **Checks must not be omitted**: All metadata validation (title ≤100 chars, description ≤5000 chars, tags ≤500 chars) and upload verification must be fully executed.
- **Config must be read from files**: OAuth credentials and default settings must be read from the configured files. Do not hardcode.

---

# YouTube Publisher

> **First time?** If `setup_complete: false` above, run `./SETUP.md` first, then set `setup_complete: true`.

Upload videos to YouTube with full metadata control.

## Live broadcasts and encoder streams

Use `youtube-live.ts` to list, inspect, schedule, update, bind, transition, and delete
YouTube Live broadcasts and encoder streams. Mutating commands support `--dry-run`;
destructive deletion, live/completed transitions, and stream-key disclosure require
explicit `--yes`. Stream keys are redacted from ordinary output.

The safe default is one dedicated named encoder stream per independent show/event.
Intentional reuse across active or upcoming broadcasts requires `--allow-shared-stream`.

Read [references/live-streaming.md](references/live-streaming.md) before operating a live
event. Scheduled timestamps must include an explicit timezone (`Z` or `±HH:MM`).

The skill passes `--inherit-previous` before creation unless the user requests a clean
profile. The raw CLI remains deterministic and offline without that explicit flag.

An interrupted video insert is never retried inside `youtube-upload.ts`. Recovery must
identify exactly one recent same-title video with a verified non-zero duration. Persistent
`P0D` exits `42` with `PERSISTENT_P0D_VIDEO_ID`; unavailable or inconclusive inspection
exits `43` with `AMBIGUOUS_UPLOAD_VIDEO_ID`. Inspect the channel before another insert.

Caption recovery updates one exact language/name track in place or inserts a new track.
It never deletes an existing caption before the replacement upload succeeds.

## Metadata sanitization

- `>` in title/description is automatically rewritten to `》`
- `<` in title/description is automatically rewritten to `《`
- Use this when YouTube rejects metadata because of special characters

## Quick Start

```bash
cd ~/.claude/skills/youtube-publisher/scripts

# First time: authenticate
npx ts-node youtube-upload.ts --auth

# Upload video
npx ts-node youtube-upload.ts \
  --video /path/to/video.mp4 \
  --title "My Awesome Video" \
  --description "Check out this amazing content!" \
  --tags "tech,ai,tutorial" \
  --privacy unlisted

# Upload as YouTube Short (vertical video)
npx ts-node youtube-upload.ts \
  --video /path/to/short.mp4 \
  --title "Quick Tip #Shorts" \
  --description "A quick tip for you!" \
  --privacy public \
  --short
```

## Options

| Option | Short | Description |
|--------|-------|-------------|
| `--video` | `-v` | Video file path (required) |
| `--title` | `-t` | Video title (required) |
| `--description` | `-d` | Video description |
| `--tags` | | Comma-separated tags |
| `--privacy` | `-p` | Privacy: public, unlisted, private (default: unlisted) |
| `--category` | `-c` | Category ID (default: 22 = People & Blogs) |
| `--thumbnail` | | Custom thumbnail image (local path or URL) |
| `--subtitles` | | Subtitle file path (SRT/VTT) |
| `--subtitle-lang` | | Subtitle language code (default: zh) |
| `--subtitle-name` | | Subtitle display name (default: 中文) |
| `--playlist` | | Add to playlist ID |
| `--short` | | Mark as YouTube Short |
| `--auth` | | Run OAuth2 authentication flow |
| `--dry-run` | | Preview without uploading |

## Category IDs

| ID | Category |
|----|----------|
| 1 | Film & Animation |
| 2 | Autos & Vehicles |
| 10 | Music |
| 15 | Pets & Animals |
| 17 | Sports |
| 19 | Travel & Events |
| 20 | Gaming |
| 22 | People & Blogs |
| 23 | Comedy |
| 24 | Entertainment |
| 25 | News & Politics |
| 26 | Howto & Style |
| 27 | Education |
| 28 | Science & Technology |

## Authentication

First-time setup requires OAuth2 authentication:

1. Run `npx ts-node youtube-upload.ts --auth`
2. Browser opens Google login
3. Grant permissions to upload videos
4. Token is saved to `.youtube-token.json`

Token refreshes automatically. Re-run `--auth` if expired.

## Environment

Create `scripts/.env`:

```env
YOUTUBE_CLIENT_ID=your_client_id
YOUTUBE_CLIENT_SECRET=your_client_secret
```

Get credentials from Google Cloud Console:
1. Create project at console.cloud.google.com
2. Enable YouTube Data API v3
3. Create OAuth2 credentials (Desktop app)
4. Download and extract client_id & client_secret

## Examples

### Upload Tutorial Video

```bash
npx ts-node youtube-upload.ts \
  -v tutorial.mp4 \
  -t "How to Use Claude Code - Complete Guide" \
  -d "Learn everything about Claude Code in this comprehensive tutorial.

Timestamps:
00:00 Introduction
02:30 Getting Started
05:00 Advanced Features

#ClaudeCode #AI #Tutorial" \
  --tags "claude code,ai,tutorial,anthropic,coding" \
  --category 28 \
  --privacy public
```

### Upload YouTube Short

```bash
npx ts-node youtube-upload.ts \
  -v short_video.mp4 \
  -t "Mind-blowing AI trick! #Shorts" \
  -d "This will change how you work! #AI #Tech" \
  --privacy public \
  --short
```

### Upload to Playlist

```bash
npx ts-node youtube-upload.ts \
  -v episode5.mp4 \
  -t "Podcast Episode 5" \
  --playlist PLxxxxxxxxxxxxxx \
  --privacy unlisted
```

### Upload with Thumbnail and Subtitles

```bash
npx ts-node youtube-upload.ts \
  -v tutorial.mp4 \
  -t "Tutorial with Subtitles" \
  -d "Learn step by step with subtitles" \
  --thumbnail /path/to/cover.jpg \
  --subtitles /path/to/subtitles.srt \
  --subtitle-lang zh \
  --subtitle-name "中文" \
  --privacy public
```

### Upload with Local Thumbnail

```bash
npx ts-node youtube-upload.ts \
  -v video.mp4 \
  -t "My Video Title" \
  --thumbnail "/Users/m/Downloads/shell/work/cover.jpg" \
  --privacy public
```

### Retry Only a Failed Thumbnail

Use the dedicated recovery command when the video already exists and only its
custom thumbnail needs to be uploaded again. It validates the video ID and
local JPG/PNG file, retries transient failures, verifies that the target video
is readable, and prints structured result markers.

```bash
npx ts-node upload-thumbnail.ts \
  --video-id VIDEO_ID \
  --thumbnail /path/to/cover.jpg \
  --attempts 3
```

This command never deletes or uploads a video. Do not create one-off recovery
scripts with hardcoded video IDs or local paths.

## Output

On success, returns:
- Video ID
- Video URL (https://youtu.be/VIDEO_ID)
- Status

## Limitations

- Max file size: 256GB (YouTube limit)
- Supported formats: MP4, MOV, AVI, WMV, FLV, 3GP, MPEG
- Supported subtitle formats: SRT, VTT
- Daily upload quota: 10,000 units (typically ~6 videos/day)
- Title max: 100 characters
- Description max: 5,000 characters
- Tags max: 500 characters total

## Changelog

### v1.6.0 - Safe Thumbnail-Only Recovery (2026-07-28)

- Added `upload-thumbnail.ts` with parameter validation, bounded retry, target-video verification, and structured output.
- Removed the hardcoded thumbnail repair script that also deleted a fixed video ID.
- Expanded the production TypeScript gate to cover the thumbnail recovery command.

### v1.1 - Resumable Upload + Retry (2026-04-22)

- Fixed EPIPE error on large file uploads by switching to resumable upload with progress tracking
- Added automatic retry (up to 3 attempts) for transient network errors (EPIPE, ETIMEDOUT, ECONNRESET)
- Added upload progress logging (every 10%)
- Added `media.mimeType` for better upload compatibility

### v1.0 - Initial Release

- Basic video upload with metadata
- Thumbnail, subtitle, playlist support
- OAuth2 authentication

