wjs-uploading-video

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

wjs-uploading-video

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).
将已完成的视频推送至YouTube。默认配置已针对该用户的工作流进行优化(王建硕频道、中国网络环境搭配本地代理、来自Riverside的1080p横向录制视频或多机位剪辑视频)。

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)
  • 用户拥有一个或多个已完成的
    .mp4
    文件,希望上传至YouTube
  • 用户指定包含多段视频和
    UPLOAD_META.md
    文件的
    final/
    目录
  • 用户需要设置特定的隐私权限、播放列表或定时发布
请勿用于:
  • 微信视频号上传(无公开API;用户需通过网页手动上传)
  • 抖音/小红书/B站上传(不同API,暂未在此实现)
  • 从横向视频源生成YouTube Shorts变体(需先使用
    wjs-reframing-video
    生成9:16比例的剪辑版本,再通过本工具上传)

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.
  1. Google Cloud OAuth客户端: 必须存在
    ~/.config/youtube/credentials.json
    文件。若缺失,可查看
    references/credentials-setup.md
    完成5分钟快速配置。
  2. Python依赖: 执行
    pip3 install google-auth-oauthlib google-api-python-client requests
    (上传时仅严格需要
    google-auth
    requests
    ,但OAuth库会自动拉取相关依赖)。
  3. 首次上传: 会打开浏览器进行Google授权,并生成
    ~/.config/youtube/token.json
    文件。后续运行将自动复用该令牌。

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

工作原理(为何不使用原生YouTube上传工具)

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.
YouTube的可恢复上传协议会在元数据POST请求后返回一个
Location:
URL,随后接受分块的
PUT
请求来上传字节数据。原生
google-api-python-client
基于
httplib2
实现该功能,但在该用户的本地SOCKS+HTTP代理环境下,后续的PUT请求会抛出
[Errno 65] No route to host
socket.timeout
错误,并无限卡顿。
本工具绕过了
httplib2
:通过
google-auth
完成OAuth授权,然后使用
requests
手动执行可恢复上传,并显式传递代理配置。采用8MB分块(而非原生的256KB),减少代理往返次数。针对
socket.timeout
/
ConnectionError
/5xx错误采用指数退避重试机制。
如果你想「直接调用YouTube API客户端」,请勿这样做——在该环境下会失败。

Usage

使用方法

Batch upload a
final/
directory

批量上传
final/
目录

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
bash
python3 ~/.claude/skills/wjs-uploading-video/scripts/upload_youtube.py \
  --dir "/path/to/final" \
  --meta "/path/to/final/UPLOAD_META.md"
该脚本会:
  1. 读取
    UPLOAD_META.md
    文件,将每个
    ## NN · filename.mp4
    块与
    --dir
    目录下的对应视频文件配对
  2. 跳过已记录在
    --results-file
    (默认路径为
    <dir>/.youtube_upload_results.json
    )中的视频——上传失败后可安全重跑脚本
  3. 按顺序上传,每上传8MB显示一次进度
  4. 将最终的URL列表写入
    --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"
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

常用参数覆盖

FlagDefaultNotes
--privacy
public
private
/
unlisted
/
public
--category
28
28 = Science & Tech. 27 = Education. 24 = Entertainment.
--made-for-kids
false
YouTube requires this declaration
--playlist <ID>
noneAdd each uploaded video to a playlist
--publish-at <ISO8601>
noneSchedule 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
offParse meta + list what would upload, don't touch network
参数默认值说明
--privacy
public
可选值:
private
/
unlisted
/
public
--category
28
28=科技类,27=教育类,24=娱乐类
--made-for-kids
false
YouTube要求必须声明此项
--playlist <ID>
将每个上传的视频添加至指定播放列表
--publish-at <ISO8601>
定时发布(需配合
--privacy private
使用)
--credentials
~/.config/youtube/credentials.json
OAuth客户端JSON文件路径
--token
~/.config/youtube/token.json
缓存的OAuth令牌文件路径
--chunk-mb
8
若上传中途持续失败,可减小分块大小
--dry-run
关闭仅解析元数据并列出待上传内容,不进行网络操作

UPLOAD_META.md format

UPLOAD_META.md格式

The parser expects the user's standard structure:
undefined
解析器遵循用户的标准结构:
undefined

01 · segment_01_no-bugs.mp4

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.
短标题 代码没有错误,只有意图不一致
视频描述 AI 时代屎山的重新定义...
—— 王建硕 × 任鑫《...》第 1 集
#王建硕 #AI编程 #ClaudeCode


映射关系:
- `## NN · <filename>` → 该块对应的视频文件
- `**短标题**`(或`**Title**`)块 → 直接作为YouTube的**标题**。短标题可用,但YouTube允许最多100字符——若想添加系列名称等更丰富的内容,可直接写在`**短标题**`中
- `**视频描述**`(或`**Description**`)块 → 直接作为YouTube的**描述**,底部的`#tag`标签将保留
- 视频描述中的所有`#word`标记 → 转换为逗号分隔的YouTube**标签**(会去除每个`#`符号;根据全局设置,会自动添加用户频道名`王建硕`作为前缀)

标题中的文件名必须与`--dir`目录下的实际文件匹配。若文件存在但无对应元数据块,脚本会报错——可添加`--allow-missing-meta`参数,以文件名作为标题、空描述上传该视频。

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
  • 隐私权限=公开:视频上传后立即上线,会出现在订阅者动态和搜索结果中。若需先审核,可在调用时通过
    --privacy unlisted
    (仅链接可访问)或
    --privacy private
    (仅所有者可访问)覆盖默认设置
  • 分类=28(科技类):匹配王建硕频道的主要内容;可在每次上传时通过
    --category
    参数覆盖
  • selfDeclaredMadeForKids = false:YouTube要求必须声明此项;用户的内容面向成人
  • 分块大小=8MB:已在用户的本地代理环境下验证可用(256KB分块会卡顿)
  • 跳过已上传视频:结果文件为判断依据;删除该文件将强制重新上传

Channel-name CTA rule

频道名CTA规则

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.
若在视频元数据中添加描述页脚、签名或「订阅我」等内容,请使用王建硕(用户的频道名)。请勿在此处添加嘉宾姓名——嘉宾如任鑫作为对话伙伴时,应出现在描述正文中,而非频道CTA区域。

Troubleshooting

故障排查

SymptomFix
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 linesThe 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 failsDelete
~/.config/youtube/token.json
and re-run; OAuth browser flow restarts
症状解决方法
授权页面显示
access_denied 403
在Google Cloud控制台中,将用户的Google账号添加至OAuth客户端的测试用户列表
上传中途出现
[Errno 65] No route to host
几乎都是代理问题——验证
curl --max-time 10 https://upload.googleapis.com/upload/youtube/v3/videos
是否返回4xx状态码(任何4xx都表示代理可访问);若返回000,说明代理已宕机
上传卡顿,无进度更新代理在静默缓冲。可减小
--chunk-mb
至4,或重启代理进程
出现
quotaExceeded
YouTube Data API默认配额为每天10000单位,每次上传消耗1600单位——即每天约可上传6个视频。可在Google Cloud控制台申请配额提升,或分多天上传
令牌刷新失败删除
~/.config/youtube/token.json
并重新运行脚本;OAuth浏览器授权流程将重启

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)
  • 结果保存至
    --results-file
    (JSON格式:包含文件、标题、视频ID、URL)
  • 以markdown表格形式向用户返回URL列表
  • 视频默认设为
    public
    状态——上传完成后立即上线。若用户需要审核缓冲,可告知其使用
    --privacy unlisted
    重新上传(或在YouTube Studio中切换为未列出状态)
  • 若用户提及合集/系列,可提议创建YouTube播放列表并批量添加新上传的视频(用户需提供播放列表ID或允许创建新列表)