Skip to main content
ClaudeWave
Skill82 repo starsupdated 2d ago

wjs-uploading-video

Upload one or many videos to YouTube. Use when the user wants to "上传到 YouTube", "发 YouTube", "批量上传", "upload to YouTube", "post videos to YouTube", or to publish a finished `final/` directory of MP4s. Reads per-video metadata (title / description / tags) from a sibling `UPLOAD_META.md` file when present (the user's standard markdown format), or from command-line flags. Survives behind a SOCKS/HTTP proxy by using `requests` directly for the resumable upload (the stock `google-api-python-client` MediaFileUpload stalls under this user's proxy setup).

Install in Claude Code
Copy
git clone --depth 1 https://github.com/jianshuo/claude-skills /tmp/wjs-uploading-video && cp -r /tmp/wjs-uploading-video/wjs-uploading-video ~/.claude/skills/wjs-uploading-video
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# wjs-uploading-video

Push finished videos to YouTube. Defaults are tuned for this user's workflow (王建硕 channel, China network with local proxy, 1080p horizontal recordings from Riverside / multicam edits).

## When to use

- User has one or more finished `.mp4` files and wants them on YouTube
- User points to a `final/` directory with multiple segments and an `UPLOAD_META.md`
- User wants a specific privacy / playlist / scheduled publish

**Don't use** for:
- 微信视频号 upload (no public API; user uploads manually via web)
- 抖音 / 小红书 / B 站 (different APIs, not yet implemented here)
- YouTube Shorts variants from horizontal source (use `wjs-reframing-video` first to produce the 9:16 cut, then upload that via this skill)

## Prerequisites (one-time per machine)

1. **Google Cloud OAuth client**: `~/.config/youtube/credentials.json` must exist. See `references/credentials-setup.md` for the 5-minute setup if missing.
2. **Python deps**: `pip3 install google-auth-oauthlib google-api-python-client requests` (only `google-auth` + `requests` are strictly needed at upload time, but the OAuth-lib pulls them).
3. **First-ever upload** opens a browser for Google consent and writes `~/.config/youtube/token.json`. Subsequent runs reuse it silently.

## How it works (and why it's not the stock youtube-uploader)

YouTube's resumable upload protocol issues a `Location:` URL after the metadata POST, then accepts the bytes in chunked `PUT` requests. The stock `google-api-python-client` runs this over `httplib2`, which under this user's local SOCKS+HTTP proxy stack throws `[Errno 65] No route to host` or `socket.timeout` on those follow-up PUTs and stalls indefinitely.

This skill bypasses `httplib2`: it does OAuth via `google-auth`, then drives the resumable upload manually with `requests`, passing the proxy explicitly. 8 MB chunks (not the stock 256 KB) — fewer round-trips through the proxy. Exponential-backoff retry on `socket.timeout` / `ConnectionError` / 5xx.

If you're tempted to "just call the YouTube API client directly," don't — it'll fail in this environment.

## Usage

### Batch upload a `final/` directory

```bash
python3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py \
  --dir "/path/to/final" \
  --meta "/path/to/final/UPLOAD_META.md"
```

The script:
1. Reads `UPLOAD_META.md` and pairs each `## NN · filename.mp4` block to a video file in `--dir`
2. Skips videos already in `--results-file` (default `<dir>/.youtube_upload_results.json`) — safe to re-run after failures
3. Uploads sequentially with progress every 8 MB
4. Writes the final URL list to `--results-file`

### Single file

```bash
python3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py \
  --video /path/to/clip.mp4 \
  --title "My Title" \
  --description "Body text" \
  --tags "tag1,tag2,tag3"
```

### Common overrides

| Flag | Default | Notes |
|---|---|---|
| `--privacy` | `public` | `private` / `unlisted` / `public` |
| `--category` | `28` | 28 = Science & Tech. 27 = Education. 24 = Entertainment. |
| `--made-for-kids` | `false` | YouTube requires this declaration |
| `--playlist <ID>` | none | Add each uploaded video to a playlist |
| `--publish-at <ISO8601>` | none | Schedule publish (requires `--privacy private`) |
| `--credentials` | `~/.config/youtube/credentials.json` | OAuth client JSON |
| `--token` | `~/.config/youtube/token.json` | Cached OAuth token |
| `--chunk-mb` | `8` | Smaller chunks if uploads keep failing mid-flight |
| `--dry-run` | off | Parse meta + list what would upload, don't touch network |

## UPLOAD_META.md format

The parser expects the user's standard structure:

```
## 01 · segment_01_no-bugs.mp4

**短标题**
代码没有错误,只有意图不一致

**视频描述**
AI 时代屎山的重新定义...

—— 王建硕 × 任鑫《...》第 1 集

#王建硕 #AI编程 #ClaudeCode

---
```

Mapping:
- `## NN · <filename>` → which video this block describes
- `**短标题**` (or `**Title**`) block → YouTube **title**, verbatim. Short titles work but consider that YouTube allows up to 100 chars — if you want a richer title with series name, write it that way in `**短标题**`
- `**视频描述**` (or `**Description**`) block → YouTube **description**, verbatim, with the `#tag` hashtags retained at the bottom
- All `#word` tokens in the 视频描述 → comma-separated YouTube **tags** (each `#` is stripped; the user's channel name `王建硕` is auto-prepended per global instructions)

Filename in the heading must match an actual file in `--dir`. If a file exists but has no meta block, the script errors loudly — pass `--allow-missing-meta` to upload it with `--title <basename>` and empty description.

## Sensible defaults this skill bakes in

- **Privacy = public**: videos go live immediately and appear in subscriber feeds + search. Override per call with `--privacy unlisted` (link-only) or `--privacy private` (owner-only) when you want to review first
- **Category = 28 (Science & Tech)**: matches 王建硕 channel's main content; override with `--category` per upload
- **selfDeclaredMadeForKids = false**: YouTube requires this; the user's content is adult-targeted
- **Chunk size = 8 MB**: validated working size through this user's local proxy (256 KB stalled)
- **Skip already-uploaded**: results file is the source of truth; deleting it forces re-upload

## Channel-name CTA rule

If you write description footers, signatures, or "subscribe to me" lines into a video's metadata, use **王建硕** (the user's channel name). Don't put a guest's name there — guests like 任鑫 belong inside the description body when they're the conversation partner, never in the channel-CTA slot.

## Troubleshooting

| Symptom | Fix |
|---|---|
| `access_denied 403` on consent screen | Add user's Google account to the OAuth client's Test users list in Google Cloud Console |
| `[Errno 65] No route to host` mid-upload | Almost always a proxy issue — verify `curl --max-time 10 https://upload.googleapis.com/upload/youtube/v3/videos` returns a 4xx (any 4xx = proxy reachable); if `000`, the proxy is down |
| Upload stalls with no progress li
skill-quality-reviewerSubagent

Repo-wide drift detector for the wjs-* Claude Code skills in this marketplace. Sweeps every SKILL.md, scores it against the repo's own conventions (V-ing naming, trigger-phrase density, companion files, description shape), and returns a grouped punch list ordered by severity. Read-only — never edits files. Use before pushing a batch of skill changes, or whenever you wonder "are these skills still internally consistent?

wangjianshuo-perspectiveSkill

|

wjs-auditing-projectSkill

Use when the user asks to audit what's wrong with a project, "make it right", "看看项目出了什么问题", "为什么用户的需求还没上线", "为什么没提交App Store", "为什么没新build", or wants a holistic state-of-the-project check covering unmerged branches, stalled PRs, failed GitHub Actions, stale builds, plan drift (TODOS.md / ROADMAP), unreleased commits, and log errors. Runs read-only investigation, presents a grouped checklist, fixes only after explicit user confirmation. Aware of the Cathier iOS app workflow (Xcode + fastlane + auto-merge @claude PRs from in-app feedback).

wjs-burning-subtitlesSkill

Use when the user has a video + an SRT and wants the subtitles either burned into the pixels (libass, always-visible) or soft-muxed as a togglable track. Also handles the final composite step for the localization pipeline — burn subs, mix a dub track, and keep the original audio as a low-volume bed, all in ONE ffmpeg encode (no cascade). Verifies libass availability and auto-downloads a static evermeet ffmpeg build when Homebrew's stripped binary lacks it. Triggers — "烧字幕", "硬字幕", "burn subtitles", "burn-in subs", "embed subtitle", "soft mux SRT", "把字幕烧进视频", "做最终合成".

wjs-cleaning-spamSkill

Use when the user complains about spam on his X/Twitter posts — 同城面付 / 寻固炮 / 线下上门 / 免费破处 这类引流号在他推文下刷的 emoji 垃圾回复 — and wants them removed. Covers the last 7 days (X recent-search window). Triggers — "把这些spam删掉", "清理X垃圾回复", "推文下面好多引流号", "clean spam replies", "/wjs-cleaning-spam".

wjs-converting-text-to-videoSkill

Use when the user wants a 王建硕-style WeChat article (article.md) turned into a narrated short MP4 video — TTS voiceover via 火山引擎 Volcano TTS, HyperFrames CSS/GSAP animation per scene, subtle SFX, abstract watercolor background, full pipeline rendering to 1080×1920 portrait MP4 (30-90s). Triggers — "把这篇文章做成视频", "做一个解说视频", "讲解视频", "/wjs-converting-text-to-video".

wjs-converting-wp-to-hugoSkill

Use when migrating a WordPress site to a Hugo static site on GitHub Pages from a WXR export (.xml) plus the wp-content/uploads folder — preserving /archives/<id>/ URLs, localizing images, and deploying via GitHub Actions. Triggers — "把 WordPress 迁成 Hugo", "wordpress 转静态站", "migrate WordPress to Hugo", "WXR to Hugo", "publish WordPress to GitHub Pages", "/wjs-converting-wp-to-hugo".

wjs-dubbing-videoSkill

Use when the user has a video + a target-language SRT and wants the video to actually speak that language — generates a time-aligned TTS voice dub. Routes by voice ID — Volcano (豆包) TTS for Chinese, edge-tts neural for any language. Defaults to one voice (single-speaker); opt-in multi-speaker via visual diarization. Outputs `*_<lang>_dub.mp4` with the dub audio in place of the original. Final mixing (audio bed + burn-in) is handed off to `/wjs-burning-subtitles`. Triggers — "配音", "中文配音", "Chinese dub", "voice over this", "dub the video", "TTS this SRT", "different voice for each speaker".