Install
openclaw skills install wjs-uploading-videoUpload 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).
openclaw skills install wjs-uploading-videoPush 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).
.mp4 files and wants them on YouTubefinal/ directory with multiple segments and an UPLOAD_META.mdDon't use for:
wjs-reframing-video first to produce the 9:16 cut, then upload that via this skill)~/.config/youtube/credentials.json must exist. See references/credentials-setup.md for the 5-minute setup if missing.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).~/.config/youtube/token.json. Subsequent runs reuse it silently.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.
final/ directorypython3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py \
--dir "/path/to/final" \
--meta "/path/to/final/UPLOAD_META.md"
The script:
UPLOAD_META.md and pairs each ## NN · filename.mp4 block to a video file in --dir--results-file (default <dir>/.youtube_upload_results.json) — safe to re-run after failures--results-filepython3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py \
--video /path/to/clip.mp4 \
--title "My Title" \
--description "Body text" \
--tags "tag1,tag2,tag3"
| Flag | Default | Notes |
|---|---|---|
--privacy | unlisted | 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 |
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#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.
--privacy public)--category per uploadIf 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.
| 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 lines | The proxy is silently buffering. Lower --chunk-mb 4 or restart the proxy daemon |
quotaExceeded | YouTube Data API default quota is 10,000 units/day, each upload is 1,600 units — so ~6 uploads/day. Request a quota bump in Google Cloud Console, or split uploads across days |
| Token refresh fails | Delete ~/.config/youtube/token.json and re-run; OAuth browser flow restarts |
--results-file (JSON: file, title, video id, URL)unlisted by default and remind them to flip to public in YouTube Studio when ready