Install
openclaw skills install @terryso/youtube-subtitle-burner下载 YouTube 视频到本地,提取 word-level 时间轴,翻译为中文,用 PIL 逐帧烧录字幕到视频画面。支持 2x 超采样抗锯齿消除文字锯齿。适用于横屏和竖屏视频。最终输出带中文字幕的 H.264 MP4 文件,可直接上传到国内平台。
openclaw skills install @terryso/youtube-subtitle-burner下载 YouTube 视频 → 提取字幕时间轴 → 翻译为中文 → 烧录到视频画面(含抗锯齿)。
# macOS
brew install ffmpeg yt-dlp
# uv(Python 环境管理,自动处理依赖)
curl -LsSf https://astral.sh/uv/install.sh | sh
Python 脚本使用 PEP 723 内联依赖声明(# /// script),uv run 会自动创建虚拟环境并安装依赖(如 Pillow),用户无需手动管理 Python 环境。
Clash Verge fake-ip 模式下 yt-dlp 必须显式设置代理:
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
代理不通时用 Clash unix socket 切换节点:
curl -s --unix-socket /tmp/verge/verge-mihomo.sock \
-X PUT 'http://localhost/proxies/%F0%9F%93%B9%20YouTube' \
-H "Content-Type: application/json" \
-d '{"name":"⚡ 最快线路"}'
# 设代理(中国大陆必需)
export http_proxy=http://127.0.0.1:7890 https_proxy=http://127.0.0.1:7890
# 下载最高画质(mp4 容器通常已是 H.264)
yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio/best" \
--merge-output-format mp4 \
-o "%(id)s_h264.mp4" \
"YOUTUBE_URL"
⚠️ 不要用 -f best,会拿到 360p。必须用 bestvideo+bestaudio 分流下载。
⚠️ 不要用 --recode-video h264,新版 yt-dlp 报 invalid recode video format "h264"。下载后用 ffprobe 检查编码,如非 H.264 再单独 ffmpeg 转码:
codec=$(ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 VIDEO.mp4)
if [ "$codec" != "h264" ]; then
ffmpeg -i VIDEO.mp4 -c:v libx264 -crf 18 -preset medium -pix_fmt yuv420p \
-c:a aac -b:a 128k -movflags +faststart -y VIDEO_h264.mp4
fi
⚠️ 必须用 json3 格式,不能用 VTT。VTT 格式有 rolling cues(滚动字幕重复),直接解析会丢失开头字幕段,导致字幕延迟 2 秒以上。
# 下载 json3 格式自动字幕(word-level 精确时间轴)
yt-dlp --skip-download \
--write-auto-subs \
--sub-langs en \
--sub-format json3 \
-o "VIDEO_ID_json3.%(ext)s" \
"YOUTUBE_URL"
然后运行解析脚本,将 json3 的 word-level 时间轴提取为句子段落:
uv run SKILL_DIR/scripts/parse_json3_subs.py \
INPUT.json3 \
OUTPUT_segments.json
输出格式:[{"start": 0.00, "end": 3.26, "text_zh_src": "English sentence"}, ...]
参数说明:
使用 LLM 将英文翻译为中文。批量处理(每批 20 条),规则:
[music] 等标签text_zh 字段uv run SKILL_DIR/scripts/translate_subs.py \
INPUT_segments.json \
OUTPUT_translated.json
注意:
translate_subs.py是批量调用 LLM 的辅助脚本。也可以直接用 agent/LLM 内联翻译,将text_zh_src翻译为text_zh。
这是核心步骤。ffmpeg 8.x 的 subtitles/ass filter 在某些平台不渲染,改用 Python PIL 逐帧绘制。
字幕参数(已验证):
| 参数 | 横屏 (1920x1080) | 竖屏 (1080x1920) |
|---|---|---|
| 字体 | STHeiti Medium | STHeiti Medium |
| 字号 | 56px | 58px |
| 文字颜色 | 黑色 | 黑色 |
| 背景色 | 黄色半透明 (255,220,0,180) | 同左 |
| 每行最大字数 | 15 | 15 |
| 底部边距 | 60px | 200px |
| 圆角半径 | 8px | 8px |
| 超采样倍数 | 2x | 2x |
字体路径: /System/Library/Fonts/STHeiti Medium.ttc
(PingFang.ttc 在 PIL 中不可用,用 STHeiti Medium 替代)
抗锯齿原理(2x 超采样):
# 烧录字幕(含音频,流式管道不写临时文件)
uv run SKILL_DIR/scripts/burn_subtitles.py \
--video INPUT_h264.mp4 \
--subs INPUT_translated.json \
--output OUTPUT_burned.mp4 \
--font-size 56 \
--supersample 2
处理流程(流式管道):
ffmpeg 解码 → Python PIL 逐帧渲染字幕 → ffmpeg 编码
(rawvideo pipe,不写临时文件)
⚠️ 不要写临时 raw 文件,1080p × 30fps × 7分钟 ≈ 85GB,磁盘会满。
烧录完整视频前,先做 10-15 秒预览让用户确认字幕效果:
uv run SKILL_DIR/scripts/burn_subtitles.py \
--video INPUT_h264.mp4 \
--subs INPUT_translated.json \
--output /tmp/preview.mp4 \
--font-size 56 \
--supersample 2 \
--preview 15
# 1. 下载视频
export http_proxy=http://127.0.0.1:7890 https_proxy=http://127.0.0.1:7890
yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best" \
--merge-output-format mp4 -o "VIDEO_h264.mp4" "URL"
# ⚠️ 不要用 --recode-video h264(新版 yt-dlp 报错),下载后用 ffprobe 检查编码
# 2. 下载 json3 字幕
yt-dlp --skip-download --write-auto-subs --sub-langs en \
--sub-format json3 -o "VIDEO_json3.%(ext)s" "URL"
# 3. 解析时间轴
uv run SKILL_DIR/scripts/parse_json3_subs.py VIDEO_json3.en.json3 VIDEO_segments.json
# 4. 翻译(用 LLM 翻译 text_zh_src → text_zh)
# ... agent 内联翻译或用 translate_subs.py ...
# 5. 预览(15秒)
uv run SKILL_DIR/scripts/burn_subtitles.py \
--video VIDEO_h264.mp4 --subs VIDEO_translated.json \
--output /tmp/preview.mp4 --font-size 56 --supersample 2 --preview 15
# 6. 烧录完整视频
uv run SKILL_DIR/scripts/burn_subtitles.py \
--video VIDEO_h264.mp4 --subs VIDEO_translated.json \
--output VIDEO_burned.mp4 --font-size 56 --supersample 2
原因:使用了 VTT 格式字幕。YouTube VTT 有 rolling cues(每段重复前一段内容),解析时会丢失开头段落。
解决:必须用 json3 格式(--sub-format json3),它包含 word-level 的 tOffsetMs 时间戳。
原因:PIL 直接在原分辨率画布渲染文字,边缘有像素锯齿。
解决:启用 --supersample 2,在 2x 分辨率渲染后用 LANCZOS 缩回。
横屏 1080p 用 56px(之前 22px 太小已废弃)。竖屏用 58px。
ffmpeg 8.x 的 subtitles/ass filter 在某些平台(macOS)不渲染。 解决:用 PIL 逐帧绘制(本方案已内置)。
1080p 视频逐帧写 raw 临时文件约 85GB。 解决:burn_subtitles.py 使用流式管道(decode pipe → PIL → encode pipe),不写临时文件。
2x 超采样在 4K 视频(2160x3840)上会放大到 4320x7680 处理,每帧约 100MB 内存,57 秒视频约需 10 分钟。 解决:如不需要 4K 输出,先降分辨率到 1080p 再烧录(快 10 倍以上):
ffmpeg -i INPUT.mp4 -vf scale=1080:1920 -c:v libx264 -crf 18 -preset medium \
-c:a aac -b:a 128k -y INPUT_1080p.mp4
然后再对 1080p 版本烧录字幕。
--recode-video h264 报错新版 yt-dlp 报 invalid recode video format "h264"。不要用 --recode-video,下载后用 ffprobe 检查编码,非 H.264 再单独 ffmpeg 转码。