Agent Skills › xiaohuailabs/xiaohu-video-translate

xiaohuailabs/xiaohu-video-translate

GitHub

将ASR转写或外语字幕润色为高质量简体中文。修正识别错误,优化断句与表达,严格对齐时间戳并去除标点。支持单语及中英双语模式,符合当代中文习惯。

3 skills 605

Install All Skills

npx skills add xiaohuailabs/xiaohu-video-translate --all -g -y
More Options

List skills in collection

npx skills add xiaohuailabs/xiaohu-video-translate --list

Skills in Collection (3)

将ASR转写或外语字幕润色为高质量简体中文。修正识别错误,优化断句与表达,严格对齐时间戳并去除标点。支持单语及中英双语模式,符合当代中文习惯。
润色字幕 翻译字幕 字幕校对 SRT翻译 双语 中英 bilingual
skills/xiaohu-subtitle-polish/SKILL.md
npx skills add xiaohuailabs/xiaohu-video-translate --skill 字幕润色 -g -y
SKILL.md
Frontmatter
{
    "name": "字幕润色",
    "description": "专业字幕润色技能:将 ASR 转写或外语字幕翻译润色为高质量简体中文字幕。修正识别错误、优化断句、严格对齐时间戳、符合当代中文表达习惯。当用户说'润色字幕''翻译字幕''字幕校对''字幕润色''SRT翻译'时使用。"
}

专业字幕润色技能

适用场景

  • Whisper 等 ASR 转写的英文/中文原始字幕 → 润色为高质量中文字幕
  • YouTube 自动生成字幕 → 纠错 + 翻译
  • 任何 SRT/VTT 字幕文件的翻译和润色
  • 配合 xiaohu-video-download 技能的字幕处理环节使用

使用方法

# 润色/翻译字幕文件
/xiaohu-subtitle-polish /path/to/subtitle.srt

# 指定输出路径
/xiaohu-subtitle-polish /path/to/subtitle.srt --output /path/to/output.srt

# 配合视频使用(可参考视频内容辅助理解上下文)
/xiaohu-subtitle-polish /path/to/subtitle.srt --video /path/to/video.mp4

角色定义

你是一名「专业字幕文案编辑」,擅长中文写作、口语还原与字幕排版规范。

润色规则(核心)

1. 纠正 ASR 识别错误

  • 修正同音字、近音字错误
  • 修正专有名词拼写(人名、地名、公司名、产品名、技术术语)
  • 修正数字、外文缩写、单位等识别错误
  • 如有 --video 参数,可参考视频画面文字辅助判断

2. 标点与书面化

  • 补全标点使口语转为书面表达,但保留说话人的语气风格
  • 最终输出的字幕文本中去掉所有标点符号(逗号、句号、问号、感叹号等全部去掉)
  • 英文和中文之间加一个英文空格(如:这是 Google 的产品

3. 去冗余

  • 去除无信息负载的口头语、重复、语气词(如"那个""呃""嗯""就是说")
  • 删去冗余时确保语义连续,不丢关键信息
  • 避免过度删减——宁可保留略冗余也不要丢失意思

4. 时间戳对齐(严格)

  • 原始时间戳必须严格保留,不得随意偏移
  • 翻译字幕时的细粒度转写:当需要将英文 SRT 翻译为中文字幕时,必须先用 Whisper -ml 50 参数重新生成细粒度分段(约 100+ 段 vs 默认 ~50 段),以获得更精确的时间锚点。命令示例:
    # 此细粒度重转写需备份引擎 whisper-cli(whisper-cpp)+ ggml 模型,安装见 xiaohu-video-md/初始化.md
    whisper-cli -m ~/.cache/whisper-cpp/ggml-large-v3-turbo.bin -t 8 -ml 50 audio.mp3 -osrt -of output_short
    
  • 绝不手动提前 start time:每条中文字幕的 start time 必须 = 对应英文内容所在 Whisper 段的 start time。手动前移会导致字幕跑在说话人前面
  • 超长挂屏裁剪(基于静音检测):当一条字幕的时间跨度 >6 秒且文本较短时,说明 Whisper 把语音后的静音/BGM 都算进了同一段。此时必须用 ffmpeg 静音检测来精确定位语音结束点,而不是用公式估算:
    # 检测该段时间范围内的静音区间(-30dB 以下持续 0.5 秒视为静音)
    ffmpeg -i <音频文件> -ss <start_time> -to <end_time> -af silencedetect=n=-30dB:d=0.5 -f null - 2>&1 | grep silence_start
    
    取第一个 silence_start 的时间点作为裁剪后的 end time(加回 start_time 偏移量)。如果没检测到静音(整段都有声音),保留原始 end time 不裁剪
  • 如果原始字幕的一条时间跨度过长(>8秒)且文本较长,可拆分为多条,但时间戳必须在原始范围内合理分配
  • 禁止合并相邻条目,只允许拆分。短条(3-4字)也不合并,短条比长条的阅读体验好
  • 拆分后重新编号(序号连续)
  • 禁止出现时间戳重叠

5. 断句与分段

  • 核心原则:可以拆,不可以合
  • 翻译时以原文 SRT 分段为节奏锚点:一条原文对应一条译文,超 12 字按语义断点拆成两条(时间戳按比例分配)
  • 以单行为主,只在内容确实太长时才用两行,绝不超过两行
  • 每行不超过 12 个中文字符(含空格和英文)
  • 在语义自然的位置断句,不要把一个完整短语拆到两条里
  • 优先在语气停顿处断开
  • 宁可多拆一条短字幕,也不要在屏幕上堆多行文字

6. 专有名词处理

  • 人名、地名、公司名、产品名保留原文,首字母大写
  • 技术术语保留英文原文(如 API、GPU、AI、ASR)
  • 广为人知的品牌/产品名不翻译(如 Google、iPhone、YouTube)
  • 不常见的专业术语可附带简短中文释义(仅首次出现时)

7. 语言风格

  • 符合当代年轻人用语习惯和网络流行表达
  • 日常用语、网络用语、俚语翻译要自然接地气
  • 避免翻译腔(如"这是非常令人兴奋的"→"这真的很酷")
  • 旁白/解说用简洁书面语,对话/歌词保留口语感

8. 歌词处理(特殊)

  • 视频中的歌词段落用 ♪ 符号包裹(如 ♪ 歌词内容 ♪
  • 歌词翻译注重意境和押韵,不必逐字直译
  • 歌词和旁白交替出现时,确保观众能区分

输出格式

严格 SRT 格式

1
00:00:00,000 --> 00:00:03,500
这是第一条字幕内容

2
00:00:03,500 --> 00:00:07,200
这是第二条字幕内容

格式要求

  • 序号从 1 开始连续编号
  • 时间戳格式:HH:MM:SS,mmm(用逗号分隔毫秒)
  • 字幕文本无标点符号
  • 英文与中文之间有英文空格
  • 每条之间空一行
  • 文件末尾空一行

双语字幕模式(中英对照,中文大 / 英文小)

触发:用户说"双语 / 中英 / 中英双语 / bilingual",或上游 video-md 传 --bilingual

核心目标:中文是主角、英文是辅助。中文用大字号醒目,英文用小字号退居其次,形成反差对比——不是中英同样大小平铺。

双语流程(三步,不要走单语 SRT 的老路)

  1. 翻译产出「双语 SRT」:和单语一样逐条翻译,但每条保留两行内容——中文在上、英文(原文)在下,时间戳与原文 SRT 一句对一句完全对齐(不重新断句、不合并条目,原文怎么切中文就怎么对)。中文行照常去标点、规范术语;英文行保留原文(仅修正 ASR 识别错误,如 Claude 被听成 cloud、MCP 被听成 NCP)。

    1
    00:00:19,239 --> 00:00:21,239
    大家好吗
    Hello everyone, how are you?
    
  2. 转双语 ASS(中文大 / 英文小):调脚本,不要手写 ASS

    python3 scripts/bilingual_ass.py <双语SRT> --output <双语ASS> [--cn-size N] [--height 视频高度]
    
    • 用户在对话里明确给了字号(如"字号 24")→ 传 --cn-size 24,英文自动按 中文 / 1.7 算(=14)。
    • 没给 → 脚本按视频分辨率选默认(见脚本内 SIZE_TABLE)。
    • 中文︰英文 ≈ 1.7 倍是实测干净的反差比例(2026-05-31 定)。
  3. 烧录走 ass= 滤镜(不是 subtitles=):

    ffmpeg -y -i <视频> -vf "ass=<双语ASS>" -c:a aac -b:a 128k <输出>
    

    带水印时把 ass=... 和 drawtext 串在同一条 -vf 里。

⛔ 为什么双语必须走 ASS、不能用 SRT + force_style

subtitles 滤镜的 force_style FontSize 对整条字幕统一生效,没法让一条里中文大、英文小。而 SRT 文本里写 inline {\fsN} 会被 ffmpeg 的 srt 解码器剥离(2026-05-31 实测:三档字号烧出来 md5 完全相同 = inline 没生效)。只有真正的 ASS 文件喂给 libass,才能在一条字幕内 \N 换行 + inline 切字号。bilingual_ass.py 就是干这个的。


工作流程

步骤 1:读取字幕文件

  1. 读取输入字幕文件(SRT 或 VTT 格式)
  2. 如果是 VTT 格式,先转换为 SRT 格式处理
  3. 检测字幕语言(中文/英文/日文/其他)

步骤 2:分析上下文

  1. 通读全部字幕,理解整体内容和主题
  2. 识别说话人风格(正式演讲/日常对话/教程讲解/产品介绍等)
  3. 标记专有名词、技术术语
  4. 区分旁白、对话、歌词等不同内容类型

步骤 3:逐条润色

按润色规则处理每一条字幕:

  1. 纠正识别错误
  2. 翻译为中文(如原文非中文)
  3. 去冗余
  4. 调整断句
  5. 检查时间戳对齐

步骤 4:全局校验

  1. 时间戳校验:确认无重叠、无跳跃、序号连续
  2. 语义校验:通读全文确认翻译连贯、无遗漏
  3. 格式校验:每行字符数、标点已去除、空格正确
  4. 编号校验:从 1 开始连续编号

步骤 5:输出

  1. 将润色后的字幕保存为 SRT 文件
  2. 输出路径:
    • --output 参数:按指定路径保存
    • 无指定:保存为 <原文件名>_zh.srt(同目录)
  3. 报告处理结果:总条数、合并/拆分数、修正项

与 xiaohu-video-download 联动

xiaohu-video-download 技能的字幕处理环节需要翻译时,可直接调用本技能的润色规则:

  1. video-download 完成 Whisper 转写 → 输出原始 SRT
  2. 本技能接手润色 → 输出高质量中文 SRT
  3. video-download 继续烧录字幕流程

联动时无需用户手动触发,由 video-download 在字幕翻译步骤自动应用本技能的规则。

烧录字幕样式(ffmpeg force_style)

默认样式(视频无已有硬字幕)

ffmpeg -y -i input.mp4 \
  -vf "subtitles=subtitle.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 output.mp4

此样式经小互确认为"完美"(2026-02-19)。

避让样式(视频已有硬字幕时)

当视频底部已有烧录过的字幕(如英文原字幕),新字幕需上移避让。按分辨率选参数

720p 避让

ffmpeg -y -i input.mp4 \
  -vf "subtitles=subtitle.srt:force_style='FontName=PingFang SC,Bold=1,FontSize=18,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=80'" \
  -c:a aac -b:a 128k output.mp4

720p + MarginV=80 + FontSize=18 经验证不遮挡(2026-03-17 NVIDIA Olaf 视频)。

1080p 避让

ffmpeg -y -i input.mp4 \
  -vf "subtitles=subtitle.srt:force_style='FontName=PingFang SC,Bold=1,FontSize=19,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=42'" \
  -c:a aac -b:a 128k output.mp4

1080p + MarginV=42 + FontSize=19 经验证不遮挡(2026-03-15 HydraDB 视频)。

烧录后必须截帧确认不遮挡,如有问题微调 MarginV。

样式参数说明

参数 默认值 720p避让 1080p避让 说明
FontName PingFang SC 苹方字体
Bold 1 加粗
FontSize 20 18 19 避让时略小
PrimaryColour &H00FFFFFF 白字
OutlineColour &H40000000 半透明黑边
Outline 1 描边宽度
Shadow 0 无阴影
MarginV 30 80 42 底部边距

质量标准

  • 翻译准确率 > 95%
  • 时间戳偏差 < 200ms
  • 无标点残留
  • 无编号断裂
  • 每条字幕可在显示时间内舒适阅读完毕
视频下载工具,支持下载视频、音频、播放列表及本地视频烧录中文字幕。具备字幕翻译、自动重试及路径安全校验功能,确保文件正确保存。
下载视频 下载这个视频 下载音频 下载播放列表 yt-dlp 烧录 烧字幕 重新烧录 烧录字幕
skills/xiaohu-video-download/SKILL.md
npx skills add xiaohuailabs/xiaohu-video-translate --skill 下载视频 -g -y
SKILL.md
Frontmatter
{
    "name": "下载视频",
    "description": "视频下载工具:下载视频和外挂字幕文件、仅下载音频、批量下载播放列表、为本地视频烧录中文字幕。支持字幕翻译功能。当用户说'下载视频''下载这个视频''下载音频''下载播放列表''yt-dlp''烧录''烧字幕''重新烧录''烧录字幕'时使用。"
}

视频下载工具

适用/能力

  • 下载整段视频(获取素材):下载视频文件和外挂中文字幕,无字幕则仅下载视频。
  • 下载播放列表:批量下载多个视频,支持指定范围或全部。
  • 仅下载音频:提取音频并嵌入封面和元数据。
  • 为本地视频添加字幕:从本地视频提取音频 → Whisper 转写 → 翻译成中文 → 烧录字幕到视频画面

强制规则(先读配置再执行)

  • 执行任何命令/写入任何文件前,必须先读取本技能目录 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/...,不要在命令里手写/粘贴完整长路径。

通用配置

代理设置(可选):

  • 如需访问 YouTube 等受限网站,所有 yt-dlp 命令添加:--proxy http://127.0.0.1:7890

路径说明

  • 输出根目录:要求显式配置;临时目录 <输出根>/tmp/(下文用 <输出根> 指定的根路径)
  • 配置方式:在本技能目录下 config.json 设置 output_dir=<绝对路径>,或命令行 --outdir <绝对路径>(命令行优先)。output_dir 为空/缺失/相对路径时,脚本会强制提示用户输入绝对路径并写回 config.json,不允许默认回落技能目录。

转写引擎(默认走脚本 scripts/transcribe_srt.py,自动下模型、零配置):

  • 默认:MLX Whisper / faster-whisper——首次运行自动下载模型,是烧字幕场景的首选(whisper-cli 没有词级时间戳,字幕会不同步,不要用于烧字幕)。
  • 可选备份:whisper-cli(whisper-cpp)——仅纯转文字、或脚本不可用时才用,需自行 brew install whisper-cpp 并把模型下到 ~/.cache/whisper-cpp/
    • 精确模式:~/.cache/whisper-cpp/ggml-large-v3-turbo.bin(默认模型)
    • 快速模式:~/.cache/whisper-cpp/ggml-medium.bin(用户没有要求不允许使用此模型)

转写模式选择逻辑

  • 精确模式(默认):ggml-large-v3-turbo.bin,95% 精度
  • 快速模式ggml-medium.bin,90% 精度,触发条件为用户指令中包含"快速"、"快"或"快速模式"关键词

输出路径选择逻辑

默认:保存到 <输出根>/data/(临时 <输出根>/tmp/),不再询问。
用户/工作流指定路径:优先使用指定路径。
本地视频加字幕:默认询问保存位置;“随便/默认”→视频目录,“同名”→覆盖需确认。

  • 避免误写到当前目录:所有 -o 示例都包含 <输出根>/...,不要使用裸 data/… 相对路径,否则会写到当前工作目录的 data/
  • 首次未配置/配置无效时:脚本会引导用户手动输入绝对路径并写入 config.jsonoutput_dir(需用户确认,不自动填默认)。若已配置有效值则不再提示,如需重新指定请删除/清空 config.jsonoutput_dir 后运行。

使用指导

下载整段视频(获取素材)

使用场景:下载视频文件和外挂中文字幕,非中文字幕自动翻译成中文。不烧录字幕。

处理流程

  1. 下载视频:
yt-dlp -f "bestvideo[vcodec^=avc1][ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
  --restrict-filenames \
  -o "<输出根>/data/%(title).40s.%(ext)s" \
  "${VIDEO_URL}"
  • 重要:优先选择 H.264 (avc1) 编码。YouTube 的 mp4 容器可能装 AV1 编码,而 X/Twitter 等社交平台不支持 AV1,只认 H.264。vcodec^=avc1 确保下载 H.264 格式,兼容所有主流平台上传。
  • 输出<输出根>/data/<截断标题>.mp4
  • YouTube 失败自动兜底(推荐):如果遇到 HTTP Error 403 / SABR / PO Token 等问题(近一年 YouTube 风控更严格),建议使用本技能脚本自动重试并从浏览器读取 cookies:
python3 scripts/youtube_download.py "${VIDEO_URL}"
  • 首次尝试会走无 cookies(更快);失败后会自动用 --cookies-from-browser chrome 重试。
  • 如你常用浏览器不是 Chrome:python3 scripts/youtube_download.py --browser firefox "${VIDEO_URL}"
  1. 下载字幕文件(VTT 格式):
yt-dlp --skip-download --write-subs --write-auto-subs \
  --sub-lang "zh-Hans,zh-CN,en,ja" \
  -o "<输出根>/tmp/%(id)s.%(ext)s" \
  "${VIDEO_URL}"
  • 临时输出<输出根>/tmp/<视频ID>.<语言代码>.vtt
  • 支持外挂字幕和内嵌字幕流
  1. 按“字幕处理通用规则”整理字幕(见下文),无字幕则仅保留视频。

注意事项

  • 登录/地区限制:提示用户提供 cookies 或切换代理
  • 字幕格式:VTT 格式保留时间戳,AI 翻译时保持结构不变
  • 硬字幕(烧录字幕):无法提取,按"无字幕"处理

仅下载音频

yt-dlp -x --audio-format mp3 --embed-thumbnail --add-metadata \
  --restrict-filenames \
  -o "<输出根>/data/%(title).40s.%(ext)s" \
  "${VIDEO_URL}"
  • 提取音频,嵌入封面和元数据
  • 支持多种音频格式(默认 mp3)
  • 最终输出:保存到 <输出根>/data/<截断标题>.mp3

字幕处理通用规则(单视频/批量共用)

  • 中文字幕:<输出根>/tmp/<视频ID>.zh-Hans.vtt.zh-CN.vtt → 重命名为最终文件名并放入 <输出根>/data/(单视频文件名前缀 <截断标题>,播放列表前缀 <视频标题>-<序号>)。
  • 非中文字幕:AI 翻译原 VTT(保时间戳)→ <输出根>/data/<目标文件名>.zh-Hans.vtt,删除原字幕。
  • 无字幕:保留视频即可。
  • 文件名:单视频用 <截断标题>,播放列表用 <视频标题>-<序号> 对应字幕同名。

批量下载播放列表

使用场景:YouTube/Bilibili 等平台的播放列表、合集、分P。

触发关键词:URL 包含播放列表特征(如 list=、BV 开头的合集等)

交互逻辑

  1. 检测到播放链接 → 查询播放列表信息(视频数量)
  2. 询问用户:"这个播放列表有 X 个视频,要下载哪些?"
  3. 用户回答:
    • "全部"、"所有" → 下载所有视频
    • "前5个"、"1-5"、"1:5" → 下载指定范围
    • "第1、3、5个" → 下载指定视频
    • "只下载第一个" → 下载单个视频(--no-playlist

处理流程

  1. 批量下载视频到 <输出根>/data/(文件名含序号):
yt-dlp -f "bestvideo[vcodec^=avc1][ext=mp4]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
  --restrict-filenames \
  --playlist-items 1:5 \
  -o "<输出根>/data/%(title).40s-%(playlist_index)s.%(ext)s" \
  "${PLAYLIST_URL}"
  • 输出示例:火星移民计划-1.mp4
  • 关键:使用 %(playlist_index)s 直接写入序号,避免重命名。
  1. 对每个视频:下载字幕(同单视频命令)→ 按“字幕处理通用规则”处理,字幕文件名用 <视频标题>-<序号>
  • 无字幕:保留对应视频即可。
  • 不使用 --embed-subs,不烧录字幕。

抖音下载

  • 命令:python3 scripts/douyin_download.py <URL> --audio|--video|--list [--resolution 1080p] [--outdir <绝对路径>]
  • 输出根来自 config.jsonoutput_dir(或 --outdir 覆盖);临时文件写入 <输出根>/tmp/,最终文件写入 <输出根>/data/
  • 脚本会标准化链接为 /video/<ID>,使用可见浏览器抓取 aweme detail,成功后自动关闭。未抓到/提示未登录时,运行 python3 scripts/douyin_login.py 再重试(状态保存在 data/browser_state/douyin_profile/)。
  • 若下载失败:先阅读 抖音初始化.md,按文档检查登录状态(用登录脚本刷新)、代理/网络、依赖是否缺失,然后重试。

为本地视频添加字幕

使用场景:用户已下载视频,需要添加中文字幕并烧录到视频画面。

触发关键词"添加字幕""补救""加上字幕" + 本地视频路径

处理流程

步骤1:检查是否已有字幕文件

检查视频所在目录是否存在字幕文件(按优先级):

  1. <视频标题>.vtt
  2. <视频标题>.zh-Hans.vtt
  3. <视频标题>.srt

步骤2:根据检查结果选择分支

├─ 步骤2A:如果已有字幕文件 │ (跳转到步骤3烧录) │ └─ 步骤2B:如果没有字幕文件 (使用 Whisper 转写后跳转到步骤3烧录)

步骤2A(已有字幕):中文字幕直接用;外语字幕翻译为 <输出根>/tmp/subtitle_zh.srt|.vtt 后使用。

步骤2B(无字幕)

  1. 提取音频(WAV 格式,16kHz 单声道,Whisper 最佳输入):ffmpeg -y -i "/path/to/video.mp4" -vn -acodec pcm_s16le -ar 16000 -ac 1 <输出根>/tmp/audio.wav
  2. Whisper 转写(默认走脚本,自动下模型;烧字幕必须用脚本拿词级时间戳,不能用 whisper-cli):python3 scripts/transcribe_srt.py "<输出根>/tmp/audio.wav" --output "<输出根>/tmp/audio.srt" --language <语言>
    • 可选备份引擎 whisper-cli 仅用于纯转文字,命令与模型路径见上方「转写引擎」
  3. 若转写结果非中文,按 xiaohu-subtitle-polish 技能的润色规则翻译为 <输出根>/tmp/audio_zh.srt,中文则直接使用。
    • 润色要求:去标点、英文中文间加空格、专有名词保留原文、歌词用 ♪ 包裹、去末尾重复/幻觉

步骤2C:检测视频是否已有硬字幕(烧录前必做)

烧录新字幕前,必须检查视频画面中是否已有硬编码(烧录过的)字幕:

  1. 截帧检查:在视频有人说话的时间点截一帧(通常 10s 左右):
ffmpeg -y -i video.mp4 -ss 00:00:10 -vframes 1 -update 1 /tmp/check_frame.jpg
  1. 目视确认:用 Read 工具查看截帧图片,看底部是否有已烧录的文字
  2. 如果有已有硬字幕 → 走"避让烧录"流程(见下方步骤 3B)
  3. 如果没有已有硬字幕 → 走默认烧录流程(步骤 3A,MarginV=30)

步骤3A:烧录字幕到视频(无已有硬字幕)

使用实际验证成功的烧录命令:

cd "$(dirname "/path/to/video.mp4")"

cp "<视频标题>.mp4" temp_video.mp4
cp "<选定字幕文件>" temp_subs.srt

ffmpeg -y -i temp_video.mp4 \
  -vf "subtitles=temp_subs.srt:force_style='FontName=PingFang SC,Bold=1,FontSize=24,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=30'" \
  -c:a copy \
  "<输出文件名>.mp4"

步骤3B:避让烧录(视频已有硬字幕时)

当视频画面底部已有烧录过的字幕(如英文原字幕),新的中文字幕需要放在已有字幕上方,互不遮挡。

关键:ASS 的 MarginV 不等于像素值,不能用像素检测值直接填。必须截帧校准。

流程:

  1. 确定初始 MarginV(根据视频分辨率和已有字幕行数):
视频分辨率 已有字幕单行 已有字幕可能双行
1080p MarginV=38 MarginV=50
720p MarginV=28 MarginV=36
4K MarginV=55 MarginV=70

经验值来源:2026-03-10 Copilot Cowork 视频(1080p,英文字幕单行+双行混合),MarginV=50 验证通过。

  1. 用初始值烧录 → 截帧验证(选一帧已有字幕为双行的时间点):
ffmpeg -y -i output.mp4 -ss <双行字幕时间点> -vframes 1 -update 1 /tmp/verify.jpg
  1. 用 Read 查看截帧

    • 中文和英文之间有间距、互不遮挡 → 完成
    • 中文还是盖住了英文 → MarginV 加 5 重试
    • 中文离英文太远 → MarginV 减 3 重试
  2. 字号建议:已有硬字幕时,新字幕用 FontSize=22(比默认的 24 小一号),视觉上更协调

避让烧录命令模板

cd "$(dirname "/path/to/video.mp4")"

cp "<视频标题>.mp4" temp_video.mp4
cp "<选定字幕文件>" temp_subs.srt

ffmpeg -y -i temp_video.mp4 \
  -vf "subtitles=temp_subs.srt:force_style='FontName=PingFang SC,Bold=1,FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H40000000,Outline=1,Shadow=0,MarginV=50'" \
  -c:a copy \
  "<输出文件名>.mp4"

说明

  • 先复制视频/字幕为简单文件名(temp_video/temp_subs),避免特殊字符;-c:a copy 不重编码。
  • 烧录字幕后无法关闭。
  • 默认样式:苹方加粗、白字、FontSize=24、轻微半透明黑边(Outline=1)、无阴影、底部留白 30px。此样式经用户确认(2026-02-19,2026-04-24 字号从 20 调到 24)。
  • 避让样式:同上但 FontSize=22、MarginV=50(1080p 双行经验值,2026-03-10 验证,2026-04-24 字号从 18 调到 22)。

步骤4:清理临时文件

# 清理 <输出根>/tmp/ 和当前目录临时文件
rm -f "<输出根>/tmp/audio.mp3" "<输出根>/tmp/audio"*.srt "<输出根>/tmp/subtitle_zh".* temp_video.mp4 temp_subs.srt

输出说明

  • 原视频:保持不变
  • 烧录字幕视频:默认文件名 video_with_subs.mp4字幕已烧录到画面
  • 输出路径
    • 默认询问用户:"烧录字幕的视频需要保存到哪里?"
    • 用户回答"随便"、"默认" → 保存到原视频所在目录
    • 用户回答"同名" → 覆盖原视频(需确认)
    • 用户指定路径 → 保存到指定位置

依赖

所需依赖

  • 工具:yt-dlpffmpegwhisper-cpp(可选备份转写引擎,不装也能用)
  • Python 包:mlx-whisper(Apple 芯片首选)或 faster-whisper(通用首选)
  • 模型:脚本引擎首次运行自动从 HuggingFace 下载;whisper-cpp 备份引擎需自行把 ggml-large-v3-turbo.binggml-medium.bin 下到 ~/.cache/whisper-cpp/

首次使用或依赖缺失时: AI 应自动检测依赖,如发现缺失,读取 初始化.md 文档并按指引自动安装

安装完成后返回主流程继续执行用户请求,后续运行无需再次读取初始化文档。

临时文件处理

临时文件位置<输出根>/tmp/

临时文件类型

  • 字幕文件(.vtt, .srt)- YouTube 下载的原始字幕
  • 音频文件(.mp3)- Whisper 转写时提取的音频
  • 临时复制文件(temp_video.mp4, temp_subs.srt)- 烧录字幕时的简单文件名

清理策略

  • 下载/翻译/转写完成后,可清理 tmp/ 中的原始外文字幕或音频;烧录后清理 temp_video/temp_subs。
  • 用户可随时手动清理 <输出根>/tmp/;最终输出文件(.mp4/.mp3/.vtt)在 <输出根>/data/,不受影响。

错误处理

  • 字幕下载失败:视频无字幕/地区限制/需 PO token → 提供 cookies 或切换代理。
  • 转写失败:模型缺失或音频不支持 → 检查模型、转换音频。
  • 视频下载失败:网络/URL/地区限制 → 检查网络、代理或 cookies。
  • 磁盘不足:清理 <输出根>/tmp/
  • ffmpeg 缺失:按 初始化.md 安装依赖后重试。
视频转写与翻译工具,支持音频转文字生成Markdown或执行完整管线(提取、转写、翻译、烧录字幕)输出带字幕视频。支持精确/快速模式及中英双语定制。
用户说'视频转写' 用户说'转写视频' 用户说'翻译视频' 用户说'视频转文字' 用户说'配字幕' 用户说'加字幕' 用户说'视频转markdown'
skills/xiaohu-video-md/SKILL.md
npx skills add xiaohuailabs/xiaohu-video-translate --skill 视频转写 -g -y
SKILL.md
Frontmatter
{
    "name": "视频转写",
    "description": "视频转写与翻译工具:抓取字幕\/音频转写 → 输出 Markdown 文档;翻译视频 → 转写+翻译+烧录中文字幕。支持精确\/快速两种转写模式。当用户说'视频转写''转写视频''翻译视频''视频转文字''配字幕''加字幕''视频转markdown'时必须执行完整翻译管线(提取音频→Whisper转写SRT→调用subtitle-polish翻译→烧录字幕到视频),不能只输出文档。"
}

视频转写工具

适用/能力

  • 转写出文档:抓取字幕或音频转写 → 输出 Markdown 文档(有字幕优先)。
  • 翻译视频:转写 → 翻译 → 烧录中文字幕 → 输出带字幕的视频文件。
  • 支持精确模式(默认,large-v3-turbo,95% 精度)和快速模式(medium,90% 精度,快 81%)。
  • 双版本输出:中文内容生成中文版,非中文内容生成原文版 + 中文翻译版。

意图识别(必须严格遵守)

  • 用户说"转写""转文字""出文档" → 只生成 Markdown 文档
  • 用户说"翻译""翻译视频""配字幕""加字幕" → 必须执行完整的翻译视频管线(提取音频 → Whisper 生成 SRT → 调用 subtitle-polish 翻译 → 烧录字幕到视频 → 同时生成 Markdown 文档),不能只输出文档就结束
  • 输入是 URL → 先下载再处理
  • 输入是本地文件路径 → 直接处理

字幕类型(翻译管线必须先确认)

  • 中文字幕:只显示中文翻译
  • 中英双语字幕:中文在上,英文在下

翻译视频时,除非用户在对话中明确指定了字幕类型(说了"中文""双语""中英"等),否则必须用 AskUserQuestion 弹选项让用户选择。 ARGUMENTS 里的字幕类型不算用户指定。示例:

AskUserQuestion(
  question="选择字幕类型",
  options=["中文字幕(只显示中文)", "中英双语字幕(中文在上,英文在下)"]
)

双语字幕 SRT 格式(每条两行):

1
00:00:03,660 --> 00:00:06,360
中文翻译在上面
English original below

双语烧录走 ASS,不走 subtitles + force_style(中文大 / 英文小,形成反差):

  • subtitle-polish 翻译产出「双语 SRT」(中文在上、英文在下,时间戳一句对一句)后,调脚本转双语 ASS:
    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/(最终产物)。

转写引擎(默认走脚本,零配置开箱即用):

  • 默认:MLX Whisper / faster-whisperscripts/transcribe_srt.py)——首次运行自动从 HuggingFace 下载模型,无需手动准备。所有场景一律首选脚本。
  • 可选备份:whisper-cli(whisper-cpp)——仅当用户明确指定、或上面两个都装不了时才用。需自行 brew install whisper-cpp 并把 ggml 模型下到 ~/.cache/whisper-cpp/(见 初始化.md):
    • 精确模式:~/.cache/whisper-cpp/ggml-large-v3-turbo.bin(默认模型)
    • 快速模式:~/.cache/whisper-cpp/ggml-medium.bin(用户没有要求不允许使用此模型)

输出路径选择逻辑

  • 步骤0:先解析输出根(命令行 --outdirconfig.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

长视频特殊处理(>10分钟)

  • 字幕翻译必须分段并行:每段约 500 条,用并行 Agent 翻译,翻译完合并重新编号
  • YouTube 视频下载不要用 --download-sections(会先下完整视频再裁剪,极慢),直接下完整视频后用 ffmpeg -ss 裁剪
  • 下载分辨率一步到位选 1080p,优先 AV1 格式(体积最小)

通用 Markdown 生成规则(适用于所有场景)

  • 读取指定文本(清洗后的字幕 cleaned.txt 或 Whisper 输出 txt),严禁增删改原文,只做标点、合句、分段。
  • 判定语言:
    • 中文:合并短句、规范标点、按语义分段,输出 1 个文件:<输出根>/data/<视频标题>-中文.md
    • 非中文:先按原文语言生成 <输出根>/data/<视频标题>-原文.md(同样不得增删改),再按 非中文翻译规则.md 翻译该原文生成 <输出根>/data/<视频标题>-中文.md
  • 元信息 front matter:titlesource_url
  • 必须覆盖全部文本内容,不得遗漏。

使用指导

抓取字幕(仅限出文档场景)

使用场景只出文档时,视频有字幕则优先抓取字幕(比 Whisper 快)。 ⛔ 烧字幕场景禁止使用本流程:YouTube 自动字幕是滚动叠加格式,无法提取干净的逐句文本+精确时间戳。烧字幕必须走"翻译视频管线"用 Whisper 转写。

处理流程

  1. 先确保输出根有效(步骤0 已完成)。

  2. 仅抓字幕(不下载视频):

# 下载 .vtt 字幕(最快)
yt-dlp --skip-download --write-subs --write-auto-subs \
  --sub-lang "zh-Hans,zh-CN,en" \
  -o "<输出根>/tmp/%(id)s.%(ext)s" \
  "<视频URL>"
  • 优先下载 .vtt 格式(最快,无需转换)。
  • 兜底机制:如果字幕下载失败,自动切换到"无字幕兜底(音频转写)"流程
  • YouTube 若缺字幕/需 PO token:推荐直接用脚本自动重试并从浏览器读取 cookies
python3 scripts/youtube_subs_download.py "<视频URL>"
  • 首次尝试无 cookies(更快);失败后自动 --cookies-from-browser chrome 重试。
  • 常用浏览器非 Chrome:python3 scripts/youtube_subs_download.py --browser firefox "<视频URL>"
  • 仍失败(PO token / 更强风控):再提示用户提供 --extractor-args "youtube:po_token=..." 或切换代理后重试。
  1. 使用脚本清洗字幕(减少 50% tokens 消耗):
# 清洗 vtt/srt → 纯文本(每行一句)
# <id>.zh-Hans.vtt 为 yt-dlp 实际输出的字幕文件名(语言代码可能是 zh-Hans、zh-CN 等)
scripts/vtt_to_text.sh "<输出根>/tmp/<id>.<语言代码>.vtt" > "<输出根>/tmp/cleaned.txt"
  1. AI 读取清洗后的文本并生成 Markdown:
  • 读取 <输出根>/tmp/cleaned.txt 全部内容,按“通用 Markdown 生成规则”产出。

无字幕兜底(音频转写)

使用场景:当视频没有字幕时,自动提取音频并用 Whisper 转写。

处理流程

  1. 提取音频(截断标题并去符号,文件名更短):
yt-dlp -x --audio-format mp3 --embed-thumbnail --add-metadata \
  --restrict-filenames \
  -o "<输出根>/tmp/%(title).40s.%(ext)s" \
  "<视频URL>"
  • YouTube 若遇 403/SABR:推荐用脚本失败自动读取浏览器 cookies 重试
python3 scripts/youtube_audio_download.py "<视频URL>"
  1. Whisper 转写(默认走脚本,自动下模型;不指定语言让其自动检测中/英/日等):
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
  1. AI 读取转写文本并生成 Markdown:
  • 读取 <输出根>/tmp/<audio>.srt(默认脚本路线)或 <audio>.mp3.txt(whisper-cli 路线),去掉序号和时间戳行得到纯文本,按“通用 Markdown 生成规则”产出(<audio> 即视频ID)。

抖音视频处理

重要原则

  • 不要预先检查依赖和登录状态,直接执行主流程
  • 遇到错误时再检查,这样可以节省时间并提高效率
  • 信任环境配置,假设依赖和登录状态正常
  • 输出根目录为必填:需在本技能目录 config.json 设置 output_dir(绝对路径),或调用时使用 --outdir <绝对路径>(命令行优先)。缺失/无效时脚本会提示用户输入绝对路径并写回 config.json。根目录下使用 tmp/data/
  • 抓取使用可见浏览器(非 headless)以获取 aweme detail,脚本会将链接标准化为 https://www.douyin.com/video/<ID>

链接格式转换(脚本自动处理):短链需先跳转取完整 URL;modal_id 取 ID 后拼 /video/ID;标准 /video/<ID> 可直接使用。

处理流程

  1. 下载音频:
python3 scripts/douyin_download.py <抖音视频URL> --audio

输出:<输出根>/tmp/<视频标题>-音频.m4a

  1. 转换音频格式:
ffmpeg -i "<输出根>/tmp/<视频标题>-音频.m4a" -acodec libmp3lame -q:a 2 "<输出根>/tmp/<视频标题>-音频.mp3"
  1. Whisper 转写(默认走脚本,自动下模型):
python3 scripts/transcribe_srt.py "<输出根>/tmp/<视频标题>-音频.mp3" --output "<输出根>/tmp/<视频标题>-音频.srt" --language zh

(其他语言可改 --language en/ja/...;可选备份引擎 whisper-cli 见上方「转写引擎」)

  1. 生成 Markdown:
    • 读取 <输出根>/tmp/<视频标题>-音频.srt,去掉序号和时间戳行得到纯文本,按“通用 Markdown 生成规则”产出(默认中文)。

错误处理

  • 步骤1失败(下载音频)
    • 如果报错 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 是唯一可靠的时间源。

输入判断

  • URL → 先用 yt-dlp / douyin_download 下载视频文件<输出根>/tmp/(只下视频,不抓字幕)
  • 本地文件路径 → 直接使用

管线步骤

步骤 0:确认字幕类型(⛔ 门控,必须在任何其他步骤之前执行)

用户在对话中明确指定了字幕类型(说了"中文""双语""中英""bilingual"等)→ 按用户指定的类型执行。 其他所有情况 → 必须用 AskUserQuestion 弹选项

AskUserQuestion(
  question="选择字幕类型",
  options=["中文字幕(只显示中文)", "中英双语字幕(中文在上,英文在下)"]
)

⛔ 禁止绕过:ARGUMENTS 里的字幕类型不算用户指定——只有用户在对话中亲自说的才算。不弹选项就开始步骤1 = 流程错误。config.json 的 subtitle_type 只是默认值,不能替代用户确认。

步骤 1:提取音频

ffmpeg -i "<视频文件>" -vn -acodec libmp3lame -q:a 2 "<输出根>/tmp/<文件名>.mp3"

步骤 2:精确转写生成原文 SRT(按时长选方案)

短视频(<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 踩坑根治)。

脚本特性:

  • MLX Whisper Metal GPU 加速,word-level timestamps 精度最高(140 分钟音频约 5-8 分钟完成)
  • --language 支持强制语种(en/zh/ja 等),不传则自动检测
  • --max-line-ms 控制单条最大时长(默认 6000ms)
  • MLX 不可用自动降级 faster-whisper

兜底:若 MLX Whisper 未安装(pip3 install --break-system-packages mlx-whisper),用 faster-whisper。若 faster-whisper 也不可用,最后用 whisper-cli -ml 50(精度最低,仅应急)。

输出:<输出根>/tmp/<文件名>.srt(原文 SRT,word-level 精确时间戳)

步骤 3:调用 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 负责全部字幕工作:翻译、去标点、断句(≤18字/行)、时间戳对齐、去冗余、专有名词处理
  • 双语模式:subtitle-polish 产出「双语 SRT」(中文在上、英文在下,时间戳一句对一句),具体见其 SKILL.md「双语字幕模式」段
  • video-md 不做任何字幕翻译和润色,全部交给 subtitle-polish
  • 输出:润色后的中文 SRT(单语)或双语 SRT(双语)

⛔ subtitle-polish Skill 返回后,立即执行步骤 4(烧录)。不输出任何中间报告,不等用户确认。subtitle-polish 有自己的输出报告步骤,在管线中忽略它,直接继续。

步骤 4:烧录字幕 + 水印(默认带水印)

默认行为:从 config.jsonsettings 读取水印配置(watermark_enabledwatermark_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 烧录段):

  • 720p 单行硬字幕MarginV=42, FontSize=18
  • 1080p 单行硬字幕MarginV=42, FontSize=19
  • 1080p 两行硬字幕(动态变行也按此值):MarginV=60, FontSize=19
  • 4K 单行硬字幕MarginV=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.jsonsettings 读取,用户可在配置面板修改。命令行/对话中指定的参数优先于配置文件。

水印次数和持续时间计算规则(按视频时长动态调整):

视频时长 水印次数 每次持续
<5 分钟 3 次 4 秒
5-30 分钟 3 次 4 秒
30-60 分钟 5 次 5 秒
>60 分钟 每 15 分钟 1 次(最少 5 次) 10 秒

时间点分布规则

  • 第 1 次固定在开头(约 8-15 秒处)
  • 最后 1 次固定在结尾前(结尾前 15-30 秒)
  • 中间的次数在首尾之间均匀分布
  • 例:140 分钟视频 → 9 次(每 15 分钟),T=15s/900s/1800s/2700s/3600s/4500s/5400s/6300s/8400s

输出保存到 ~/Downloads/

步骤 5:生成 Markdown 文档(与字幕管线并行)

  • 同时用 -otxt 模式获取纯文本,或直接读取 SRT 内容
  • 按"通用 Markdown 生成规则"生成文档(非中文 → 原文版 + 中文版)

核心原则

  • 字幕翻译润色的所有规则xiaohu-subtitle-polish 技能定义和执行,video-md 不重复定义、不自行实现
  • video-md 的职责 = 音频提取 + Whisper 转写 + 编排管线 + Markdown 生成
  • 这是管线编排(orchestration),不是规则复制(duplication)
  • ⛔ 管线不停顿:subtitle-polish 返回后必须直接继续烧录步骤(步骤 4),不得停下来报告中间结果等用户确认。整条管线只在最终产物(视频文件 + Markdown)全部完成后报告一次

Markdown 清洗要点

遵循“通用 Markdown 生成规则”:严禁增删改原文,仅做标点/分段/合并短句,front matter 仅含 titlesource_url

依赖

所需依赖

  • 工具:yt-dlpffmpegwhisper-cpp(可选备份转写引擎,不装也能用)
  • Python 包:mlx-whisper(Apple 芯片首选)或 faster-whisper(通用首选)、patchright(抖音视频处理必需)
  • 脚本:scripts/transcribe_srt.pyscripts/vtt_to_text.shscripts/douyin_login.pyscripts/douyin_download.py
  • 模型:mlx-whisper / faster-whisper 首次运行自动从 HuggingFace 下载,无需手动准备;whisper-cpp 备份引擎需自行把 ggml-large-v3-turbo.binggml-medium.bin 下到 ~/.cache/whisper-cpp/

依赖检查原则

  • 直接执行主流程,遇错再查 初始化.md / 抖音初始化.md 补依赖,默认假设环境已装好

当遇到依赖相关错误时

  • 读取相应的初始化文档并按指引自动安装
  • 常规初始化:初始化.md
  • 抖音初始化:抖音初始化.md
  • 安装完成后返回主流程继续执行用户请求

临时文件处理

临时文件位置<输出根>/tmp/

临时文件类型

  • 字幕文件(.vtt, .srt)
  • Whisper 转写输出的文本文件(.mp3.txt)
  • 清洗后的字幕文本文件(cleaned.txt,仅字幕抓取时使用)

清理策略

  • AI 处理完成后,可清理临时字幕文件
  • Whisper 转写结果文件可保留用于调试
  • 用户可随时手动清理 <输出根>/tmp/ 目录
  • 重要:最终输出文件(.md)保存在 <输出根>/data/,不受影响

错误处理

常见错误及处理方法

  1. 字幕下载失败

    • 原因:视频无字幕、地区限制、需要 PO token
    • 处理:提示用户提供 cookies(--cookies-from-browser chrome)或切换代理
    • 兜底:自动切换到音频转写流程
  2. Whisper 转写失败

    • 原因:模型文件缺失、音频格式不支持
    • 处理:检查模型是否已下载,尝试转换音频格式
  3. 磁盘空间不足

    • 原因:临时文件或输出文件占用空间过大
    • 处理:清理 <输出根>/tmp/ 临时文件目录

翻译规则文档

  • 非中文内容翻译时,按需读取 非中文翻译规则.md 并遵守其中规则。
  • 中文内容严禁使用翻译规则;仅做标点、分段、合并短句,不得增删改原文。

Главная - Вики-сайт
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-11 16:31
浙ICP备14020137号-1 $Гость$