blender-motion-state-inspection
AFaffaan-m/ECC299k近 7 天新增 5,106 个 star
视频转写与翻译工具:抓取字幕/音频转写 → 输出 Markdown 文档;翻译视频 → 转写+翻译+烧录中文字幕。支持精确/快速两种转写模式。当用户说'视频转写''转写视频''翻译视频''视频转文字''配字幕''加字幕''视频转markdown'时必须执行完整翻译管线(提取音频→Whisper转写SRT→调用subtitle-polish翻译→烧录字幕到视频),不能只输出文档。
翻译视频时,除非用户在对话中明确指定了字幕类型(说了"中文""双语""中英"等),否则必须用 AskUserQuestion 弹选项让用户选择。 ARGUMENTS 里的字幕类型不算用户指定。示例:
AskUserQuestion(
question="选择字幕类型",
options=["中文字幕(只显示中文)", "中英双语字幕(中文在上,英文在下)"]
)
双语字幕 SRT 格式(每条两行):
1
00:00:03,660 --> 00:00:06,360
中文翻译在上面
English original below
双语烧录走 ASS,不走 subtitles + force_style(中文大 / 英文小,形成反差):
python3 ~/.claude/skills/xiaohu-subtitle-polish/scripts/bilingual_ass.py \
<双语SRT> --output <双语ASS> [--cn-size N] [--height 视频高度]
ass=<双语ASS> 滤镜烧录(不是 subtitles=)。完整规则见 xiaohu-subtitle-polish SKILL.md「双语字幕模式」段。--cn-size,英文自动按 中文 / 1.7 算;没给则脚本按分辨率选默认。中文︰英文 ≈ 1.7 是实测干净反差。⛔ 为什么不用 subtitles force_style 做双语:force_style 的 FontSize 对整条统一,做不到一条内中文大英文小;SRT 里的 inline {\fsN} 会被 ffmpeg 剥离(2026-05-31 实测三档字号 md5 全同)。只有 ASS 喂 libass 才能一条内分字号。
⛔ ASS FontSize 是相对值不是像素:libass 已按视频分辨率自动缩放,不要按分辨率线性放大(1080p 的 17 乘 2 变 34 会让 4K 字幕占满下半屏)。2026-05-26 Jony Ive 法拉利视频踩坑根治。
config.json,取 output_dir 作为 <输出根>(支持 ~ 展开)。<输出根>/... 在执行前必须替换为真实绝对路径;禁止把 <输出根> 原样带到命令里执行(否则会写到当前目录,表现为“乱保存”)。config.json 缺失、output_dir 为空/相对路径/不可写:立即停止并提示用户在网页面板填写,或用 --outdir <绝对路径> 覆盖。<输出根>/tmp/ 与 <输出根>/data/。建议的“输出根解析”固定前置步骤(避免手打长路径导致误写目录):
# 在本技能目录执行
OUTPUT_ROOT="$(python3 - <<'PY'
import json, os
from pathlib import Path
cfg = json.load(open("config.json", "r", encoding="utf-8"))
out = Path(os.path.expanduser(str(cfg.get("output_dir", ""))).strip())
if not out.is_absolute():
raise SystemExit("config.json: output_dir 必须是绝对路径或以 ~ 开头")
print(str(out))
PY
)"
mkdir -p "$OUTPUT_ROOT/tmp" "$OUTPUT_ROOT/data"
echo "OUTPUT_ROOT=$OUTPUT_ROOT"
后续所有命令都必须使用 $OUTPUT_ROOT/...,不要在命令里手写/粘贴完整长路径。
路径说明:
scripts/config.json 设置 output_dir=<绝对路径>,或调用时用 --outdir <绝对路径>(命令行优先)。必须是绝对路径;output_dir 为空/缺失/相对路径时,脚本会强制提示用户输入绝对路径并写回 config.json,不会默认回落技能目录。根目录下自动创建/使用 tmp/(中间产物)和 data/(最终产物)。转写引擎(默认走脚本,零配置开箱即用):
scripts/transcribe_srt.py)——首次运行自动从 HuggingFace 下载模型,无需手动准备。所有场景一律首选脚本。brew install whisper-cpp 并把 ggml 模型下到 ~/.cache/whisper-cpp/(见 初始化.md):
~/.cache/whisper-cpp/ggml-large-v3-turbo.bin(默认模型)~/.cache/whisper-cpp/ggml-medium.bin(用户没有要求不允许使用此模型)--outdir → config.json),output_dir 缺失/无效时立即提示用户输入绝对路径并写回 config.json,未配置不可继续。<输出根>/data/,不再询问;用户指令/工作流若明确指定路径,优先使用指定路径(必须是绝对路径)。根据音频时长选择方案:
| 音频时长 | 方案 | 原因 |
|---|---|---|
| <2 分钟 | faster-whisper | word-level 精度最高,CPU 几秒完成 |
| ≥2 分钟 | MLX Whisper | word-level + Metal GPU,速度和精度兼得 |
faster-whisper 命令(<2分钟短视频):
python3 scripts/transcribe_srt.py "<输入文件>" --output "<输出文件>.srt"
MLX Whisper 命令(≥2分钟,翻译管线默认)— 一律走脚本,不在对话里现写 Python:
python3 scripts/transcribe_srt.py "<输入文件>" --output "<输出文件>.srt"
# 语种误判时加 --language en
脚本默认按 Whisper segment 边界切(不是按字符 greedy 切),下游字幕不会出现夹生帧。
禁止在烧字幕场景使用 whisper-cli:没有 word-level timestamps,字幕会明显不同步。
默认同样走脚本(自动下模型、零配置),转写后从 SRT 取纯文本(去掉序号和时间戳行)即可:
python3 scripts/transcribe_srt.py "<输入文件>" --output "<输出文件>.srt"
可选备份:若已装 whisper-cpp 且想要最快的纯文本输出,可用 whisper-cli(需自备 ggml 模型,见上方「转写引擎」):
whisper-cli -m ~/.cache/whisper-cpp/ggml-large-v3-turbo.bin -t 8 <输入文件> -otxt # 快速模式(用户说"快速"/"快"):把模型换成 ggml-medium.bin
--download-sections(会先下完整视频再裁剪,极慢),直接下完整视频后用 ffmpeg -ss 裁剪cleaned.txt 或 Whisper 输出 txt),严禁增删改原文,只做标点、合句、分段。<输出根>/data/<视频标题>-中文.md。<输出根>/data/<视频标题>-原文.md(同样不得增删改),再按 非中文翻译规则.md 翻译该原文生成 <输出根>/data/<视频标题>-中文.md。title、source_url。使用场景:只出文档时,视频有字幕则优先抓取字幕(比 Whisper 快)。 ⛔ 烧字幕场景禁止使用本流程:YouTube 自动字幕是滚动叠加格式,无法提取干净的逐句文本+精确时间戳。烧字幕必须走"翻译视频管线"用 Whisper 转写。
处理流程:
先确保输出根有效(步骤0 已完成)。
仅抓字幕(不下载视频):
# 下载 .vtt 字幕(最快)
yt-dlp --skip-download --write-subs --write-auto-subs \
--sub-lang "zh-Hans,zh-CN,en" \
-o "<输出根>/tmp/%(id)s.%(ext)s" \
"<视频URL>"
python3 scripts/youtube_subs_download.py "<视频URL>"
--cookies-from-browser chrome 重试。python3 scripts/youtube_subs_download.py --browser firefox "<视频URL>"--extractor-args "youtube:po_token=..." 或切换代理后重试。# 清洗 vtt/srt → 纯文本(每行一句)
# <id>.zh-Hans.vtt 为 yt-dlp 实际输出的字幕文件名(语言代码可能是 zh-Hans、zh-CN 等)
scripts/vtt_to_text.sh "<输出根>/tmp/<id>.<语言代码>.vtt" > "<输出根>/tmp/cleaned.txt"
<输出根>/tmp/cleaned.txt 全部内容,按“通用 Markdown 生成规则”产出。使用场景:当视频没有字幕时,自动提取音频并用 Whisper 转写。
处理流程:
yt-dlp -x --audio-format mp3 --embed-thumbnail --add-metadata \
--restrict-filenames \
-o "<输出根>/tmp/%(title).40s.%(ext)s" \
"<视频URL>"
python3 scripts/youtube_audio_download.py "<视频URL>"
python3 scripts/transcribe_srt.py "<输出根>/tmp/<audio>.mp3" --output "<输出根>/tmp/<audio>.srt"
可选备份:已装 whisper-cpp 时可用 whisper-cli 直接出 txt(需自备 ggml 模型,见上方「转写引擎」):
whisper-cli -m ~/.cache/whisper-cpp/ggml-large-v3-turbo.bin -t 8 "<输出根>/tmp/<audio>.mp3" -otxt
<输出根>/tmp/<audio>.srt(默认脚本路线)或 <audio>.mp3.txt(whisper-cli 路线),去掉序号和时间戳行得到纯文本,按“通用 Markdown 生成规则”产出(<audio> 即视频ID)。重要原则:
config.json 设置 output_dir(绝对路径),或调用时使用 --outdir <绝对路径>(命令行优先)。缺失/无效时脚本会提示用户输入绝对路径并写回 config.json。根目录下使用 tmp/、data/。https://www.douyin.com/video/<ID>。链接格式转换(脚本自动处理):短链需先跳转取完整 URL;modal_id 取 ID 后拼 /video/ID;标准 /video/<ID> 可直接使用。
处理流程:
python3 scripts/douyin_download.py <抖音视频URL> --audio
输出:<输出根>/tmp/<视频标题>-音频.m4a
ffmpeg -i "<输出根>/tmp/<视频标题>-音频.m4a" -acodec libmp3lame -q:a 2 "<输出根>/tmp/<视频标题>-音频.mp3"
python3 scripts/transcribe_srt.py "<输出根>/tmp/<视频标题>-音频.mp3" --output "<输出根>/tmp/<视频标题>-音频.srt" --language zh
(其他语言可改 --language en/ja/...;可选备份引擎 whisper-cli 见上方「转写引擎」)
<输出根>/tmp/<视频标题>-音频.srt,去掉序号和时间戳行得到纯文本,按“通用 Markdown 生成规则”产出(默认中文)。错误处理:
ModuleNotFoundError: No module named 'patchright',读取 抖音初始化.md 安装依赖未检测到浏览器登录状态,运行 python3 scripts/douyin_login.py 进行登录抖音初始化.md 查看技术细节和解决方案抖音初始化.md 查看技术细节和解决方案触发条件:用户说"翻译视频""翻译该视频""配字幕""加字幕"等——最终目标 = 输出一个带中文字幕的视频文件(同时生成 Markdown 文档)。
⛔ 铁律:翻译管线的英文 SRT 只能由 Whisper 转写生成,禁止使用 YouTube 自动字幕(yt-dlp --write-auto-subs)。 原因:YouTube 自动字幕是滚动叠加格式(每条包含前一条文本),无法提取干净逐句文本+精确时间戳,导致烧录后字幕不同步。Whisper word-level timestamps 是唯一可靠的时间源。
输入判断:
<输出根>/tmp/(只下视频,不抓字幕)管线步骤:
用户在对话中明确指定了字幕类型(说了"中文""双语""中英""bilingual"等)→ 按用户指定的类型执行。 其他所有情况 → 必须用 AskUserQuestion 弹选项:
AskUserQuestion(
question="选择字幕类型",
options=["中文字幕(只显示中文)", "中英双语字幕(中文在上,英文在下)"]
)
⛔ 禁止绕过:ARGUMENTS 里的字幕类型不算用户指定——只有用户在对话中亲自说的才算。不弹选项就开始步骤1 = 流程错误。config.json 的 subtitle_type 只是默认值,不能替代用户确认。
ffmpeg -i "<视频文件>" -vn -acodec libmp3lame -q:a 2 "<输出根>/tmp/<文件名>.mp3"
短视频(<2分钟)→ faster-whisper:
python3 scripts/transcribe_srt.py "<输出根>/tmp/<文件名>.mp3" \
--output "<输出根>/tmp/<文件名>.srt"
长视频(≥2分钟)→ MLX Whisper(默认,仍走脚本):
python3 scripts/transcribe_srt.py "<输出根>/tmp/<文件名>.mp3" \
--output "<输出根>/tmp/<文件名>.srt"
多语言混轨/语种误判 → 加 --language en(强制重转):
python3 scripts/transcribe_srt.py "<输出根>/tmp/<文件名>.mp3" \
--output "<输出根>/tmp/<文件名>.srt" \
--language en
⛔ 任何情况下不要在对话里现写 mlx_whisper Python 代码——脚本已经按 Whisper segment 切(merge_words_to_segments 函数:segment 作基础 / 超 6s 才按词拆 / <500ms 才合并),绕开脚本自己写 MAX_CHARS=50 greedy 会把 A 句尾巴+B 句开头挤进同一帧(2026-05-28 ElevenLabs 踩坑根治)。
脚本特性:
--language 支持强制语种(en/zh/ja 等),不传则自动检测--max-line-ms 控制单条最大时长(默认 6000ms)兜底:若 MLX Whisper 未安装(pip3 install --break-system-packages mlx-whisper),用 faster-whisper。若 faster-whisper 也不可用,最后用 whisper-cli -ml 50(精度最低,仅应急)。
输出:<输出根>/tmp/<文件名>.srt(原文 SRT,word-level 精确时间戳)
xiaohu-subtitle-polish 技能翻译润色使用 Skill 工具调用,传入原文 SRT 路径:
# 中文单语
/xiaohu-subtitle-polish <输出根>/tmp/<文件名>.srt --output <输出根>/tmp/<文件名>-zh.srt
# 中英双语(步骤 0 用户选了双语时)
/xiaohu-subtitle-polish <输出根>/tmp/<文件名>.srt --output <输出根>/tmp/<文件名>-zh.srt --bilingual
⛔ subtitle-polish Skill 返回后,立即执行步骤 4(烧录)。不输出任何中间报告,不等用户确认。subtitle-polish 有自己的输出报告步骤,在管线中忽略它,直接继续。
默认行为:从 config.json 的 settings 读取水印配置(watermark_enabled、watermark_text 等)。默认开启水印,用户说"不要水印""去掉水印"时关闭。
字幕 + 水印必须在同一条 ffmpeg 命令中完成(一次编码,避免画质损失)。
双语先转 ASS 再烧:双语模式下,先把双语 SRT 转成双语 ASS(中文大 / 英文小),烧录时用 ass=<双语ASS> 替代下面命令里的 subtitles=<SRT>:force_style=...,其余(水印 drawtext、-c:a aac)不变:
python3 ~/.claude/skills/xiaohu-subtitle-polish/scripts/bilingual_ass.py \
<输出根>/tmp/<文件名>-zh.srt --output <输出根>/tmp/<文件名>-zh.ass [--cn-size N] [--height 高度]
# 烧录:-vf "ass=<输出根>/tmp/<文件名>-zh.ass,drawtext=...水印..."
中文单语仍按下面的 subtitles= + force_style 烧。
音频编码强制 AAC:yt-dlp 下载的音频经常是 Opus 编码,X/微信等平台不支持。烧录时必须用 -c:a aac -b:a 128k,禁止用 -c:a copy(2026-03-17 踩坑:Opus 音频导致 X 上传失败)。
原视频有硬字幕时的避让:如果原视频底部已有烧录过的字幕(如英文原字幕),新字幕需上移避让,不能遮挡原字幕。按分辨率选参数(详见 xiaohu-subtitle-polish SKILL.md 烧录段):
MarginV=42, FontSize=18MarginV=42, FontSize=19MarginV=60, FontSize=19MarginV=42, FontSize=19⚠️ MarginV 单位不是像素——它相对 ASS 默认 PlayResY=288。在 1080p 视频里 MarginV=N 实际渲染高度 ≈ N × 3.75 px from bottom。所以 MarginV=110 → 412px ≈ 画面正中(不是 110px 距底),别按像素直觉调(2026-05-29 陶哲轩视频踩坑根治:我之前按像素直觉设 110 把中文推到画面正中)。
原英文动态变行(一句 1 行一句 2 行):用避两行的 MarginV=60,1 行段中文显得稍高一点点是可接受代价(远好过中文挤进 2 行英文中间)。
烧录后必须抽 ≥2 帧验证——选原英文是 1 行的一帧 + 是 2 行的一帧,两帧都不遮挡才算过。只看一帧就交付 = 文档已警告过的踩坑。
无水印烧录(仅用户明确说"不要水印"时):
ffmpeg -y -i "<视频文件>" \
-vf "subtitles=<输出根>/tmp/<文件名>-zh.srt:force_style='FontName=PingFang SC,Bold=1,FontSize=20,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=30'" \
-c:a aac -b:a 128k ~/Downloads/<视频标题>-中文字幕.mp4
带水印烧录(默认):
macOS drawtext 中文字体问题(重要): ffmpeg 的
drawtext滤镜使用 fontconfig 查找字体,但 macOS 的 fontconfig 无法索引苹方,会回退到 Verdana(不支持中文)→ 中文显示为方块。 必须使用fontfile=指定字体文件绝对路径,禁止使用font=名称查找。 注意:subtitles滤镜用 CoreText 不受此影响,只有drawtext需要fontfile。 水印字体用圆体(Yuanti SC),比苹方更好看,且 index 0 就是 SC 简体,不会出现方块问题。PingFang.ttc 的 index 0 是 HK 变体,简体字符显示为方块。
# 1. 解析水印中文字体路径(圆体 Yuanti SC,index 0 = SC 简体)
FONT=$(find /System/Library/AssetsV2 -name "Yuanti.ttc" 2>/dev/null | head -1)
[ -z "$FONT" ] && FONT=$(find /System/Library/AssetsV2 -name "PingFang.ttc" 2>/dev/null | head -1)
[ -z "$FONT" ] && FONT="/System/Library/Fonts/STHeiti Medium.ttc"
# 2. 字幕 + 间歇水印一次烧录
# 水印默认参数:透明度 28%、每次 4 秒、0.5 秒渐隐渐出、左上角、出现 3 次
# 时间点均匀分布(示例:8s/38s/72s,根据视频时长调整)
ffmpeg -y -i "<视频文件>" \
-vf "subtitles=<SRT路径>:force_style='FontName=PingFang SC,Bold=1,FontSize=20,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=30',\
drawtext=text='<水印文字>':fontfile=${FONT}:fontsize=44:fontcolor=white:alpha='0.28*min(min((t-T1)/0.5,1),(T1+4-t)/0.5)':x=40:y=35:enable='between(t,T1,T1+4)',\
drawtext=text='<水印文字>':fontfile=${FONT}:fontsize=44:fontcolor=white:alpha='0.28*min(min((t-T2)/0.5,1),(T2+4-t)/0.5)':x=40:y=35:enable='between(t,T2,T2+4)',\
drawtext=text='<水印文字>':fontfile=${FONT}:fontsize=44:fontcolor=white:alpha='0.28*min(min((t-T3)/0.5,1),(T3+4-t)/0.5)':x=40:y=35:enable='between(t,T3,T3+4)'" \
-c:a aac -b:a 128k ~/Downloads/<视频标题>-中文字幕.mp4
水印参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
| 透明度 | 0.28(28%) | 淡而可见,不干扰观看 |
| 渐隐渐出 | 0.5 秒 | 自然过渡,不突兀 |
| 每次持续 | 见下方规则 | 按视频时长调整 |
| 最少次数 | 3 次 | 开头、中间、结尾必须各有一次 |
| 位置 | 左上角 (x=40, y=35) | 不遮挡字幕 |
| 字号 | 44 | 4K 视频下小巧;1080p 可降至 24-28 |
水印参数从 config.json 的 settings 读取,用户可在配置面板修改。命令行/对话中指定的参数优先于配置文件。
水印次数和持续时间计算规则(按视频时长动态调整):
| 视频时长 | 水印次数 | 每次持续 |
|---|---|---|
| <5 分钟 | 3 次 | 4 秒 |
| 5-30 分钟 | 3 次 | 4 秒 |
| 30-60 分钟 | 5 次 | 5 秒 |
| >60 分钟 | 每 15 分钟 1 次(最少 5 次) | 10 秒 |
时间点分布规则:
输出保存到 ~/Downloads/。
-otxt 模式获取纯文本,或直接读取 SRT 内容核心原则:
xiaohu-subtitle-polish 技能定义和执行,video-md 不重复定义、不自行实现遵循“通用 Markdown 生成规则”:严禁增删改原文,仅做标点/分段/合并短句,front matter 仅含 title、source_url。
所需依赖:
yt-dlp、ffmpeg;whisper-cpp(可选备份转写引擎,不装也能用)mlx-whisper(Apple 芯片首选)或 faster-whisper(通用首选)、patchright(抖音视频处理必需)scripts/transcribe_srt.py、scripts/vtt_to_text.sh、scripts/douyin_login.py、scripts/douyin_download.pyggml-large-v3-turbo.bin、ggml-medium.bin 下到 ~/.cache/whisper-cpp/依赖检查原则:
初始化.md / 抖音初始化.md 补依赖,默认假设环境已装好当遇到依赖相关错误时:
初始化.md抖音初始化.md临时文件位置:<输出根>/tmp/
临时文件类型:
清理策略:
<输出根>/tmp/ 目录<输出根>/data/,不受影响常见错误及处理方法:
字幕下载失败:
--cookies-from-browser chrome)或切换代理Whisper 转写失败:
磁盘空间不足:
<输出根>/tmp/ 临时文件目录非中文翻译规则.md 并遵守其中规则。AFaffaan-m/ECC299k近 7 天新增 5,106 个 star
ANanthropics/skills189k近 7 天新增 2,932 个 star
SHShubhamsaboo/awesome-llm-apps144k近 7 天新增 526 个 star
HAharry0703/MoneyPrinterTurbo141k近 7 天新增 3,860 个 star
GAgarrytan/gstack141k近 7 天新增 2,189 个 star