拾流 / YouTube 视频批量上传 · 创作技能

SHILIU · 创作资源

YouTube 视频批量上传 · 创作技能

用自带 Python 脚本把单个或 final/ 目录下的多个 MP4 上传到 YouTube,从 UPLOAD_META.md 解析标题、描述与标签,绕过 httplib2 走 requests 分块续传以适配代理环境,并给出配额与限流排查。

拾流编辑部 · 更新于 2026-11-02

这个技能解决什么问题

用自带 Python 脚本把单个或 final/ 目录下的多个 MP4 上传到 YouTube,从 UPLOAD_META.md 解析标题、描述与标签,绕过 httplib2 走 requests 分块续传以适配代理环境,并给出配额与限流排查。

适用任务

把已完成的视频文件按元数据批量或单个上传到 YouTube,并处理代理、配额与每日上传数限制。

需要的输入

待上传的 .mp4 文件或含多个分段的 final/ 目录;可选的 UPLOAD_META.md(## NN · 文件名 块 + **短标题** + **视频描述**);Google Cloud OAuth 客户端 JSON 与首次授权生成的 token;Python 依赖 google-auth、requests(或 uvx 临时带依赖)。

交付内容

每个视频的 YouTube URL 与 video id,写入 --results-file(默认 <dir>/.youtube_upload_results.json);终端进度输出;可选的播放列表归属与定时发布设置。

任务示例

python3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py --dir "/path/to/final" --meta "/path/to/final/UPLOAD_META.md" --privacy unlisted --dry-run

输出示例

示意(非真实结果):dry-run 列出将上传的 3 个文件及解析出的标题/标签;正式运行后 results JSON 含 file、title、video id、URL,并以表格回显给用户。

来源与使用

作者 建硕 · jianshuo;许可 MIT。已核对来源与包结构,未据此宣称完成运行实测。

查看原始来源

机器可读详情 · 读取 SKILL.md · 下载完整技能包

技能说明全文

---
name: wjs-uploading-video
description: 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).
---

# 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.

**`ModuleNotFoundError: No module named 'google.oauth2'`** — Homebrew 升级 Python 大版本后 site-packages 会丢(2026-08 从 3.13→3.14 实际发生过)。别重新 pip install,直接用 uvx 带依赖跑(已验证):

```bash
uvx --with google-auth --with requests python \
  ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py --dir "/path/to/final"
```

## 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 lines | The proxy is silently buffering. Lower `--chunk-mb 4` or restart the proxy daemon |
| `quotaExceeded` (API units) | 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 |
| `429 rateLimitExceeded` · `Video Uploads per day` | **A DIFFERENT, separate limit** from API units — YouTube's per-project *daily video-upload count* (anti-abuse, tied to project verification status; can be very low). When hit, **every** upload in the batch fails `init session failed: 429`, none succeed. It is NOT a proxy/auth problem and can't be worked around. It resets at **midnight US Pacific Time** (not your local midnight). Options: wait for the PT reset and re-run (the results file was cleared so all retry); or **upload via the YouTube Studio web UI**, which does NOT consume this API limit; or get the GCP project verified to raise the cap. Earlier uploads the same PT-day (even manual API ones) eat into this count. |
| Token refresh fails | Delete `~/.config/youtube/token.json` and re-run; OAuth browser flow restarts |

## After uploading

- Results saved to `--results-file` (JSON: file, title, video id, URL)
- Echo the URL list back to the user in a markdown table
- Videos are `public` by default — they're live the moment the upload completes. If the user wanted a review buffer, mention they can re-upload with `--privacy unlisted` (or flip back to unlisted in YouTube Studio)
- If they mentioned a 合集 / series, offer to create a YouTube playlist and batch-add the new uploads (the user must give a playlist ID or let you create one)

技能是任务材料,不会自动授予账户权限、付费授权或其他系统权限。请让 Agent 按说明核对依赖,再在授权范围内使用。

把这个任务交给你的 Agent

数据、技能和创作空间,一起备好。复制接入指令,发给你正在使用的 AI 工具。

阅读 Agent 接入指南
正在复制指令

粘贴发给你的 Agent
让它成为你的自媒体专家

打开你正在使用的 AI 工具,把下面这段话粘贴到对话框并发送。

发给 Agent 的完整指令

发送后,告诉它你想做什么账号、实现什么目标。

这些 Agent 都可以使用24 个工具