视频转写工具
适用/能力
- 转写出文档:抓取字幕或音频转写 → 输出 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(中文大 / 英文小,形成反差):
⛔ 为什么不用 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-whisper(
scripts/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:先解析输出根(命令行
--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
长视频特殊处理(>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:
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>"
- 优先下载 .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=..." 或切换代理后重试。
- 使用脚本清洗字幕(减少 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"
- AI 读取清洗后的文本并生成 Markdown:
- 读取
<输出根>/tmp/cleaned.txt 全部内容,按“通用 Markdown 生成规则”产出。
无字幕兜底(音频转写)
使用场景:当视频没有字幕时,自动提取音频并用 Whisper 转写。
处理流程:
- 提取音频(截断标题并去符号,文件名更短):
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>"
- 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
- 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> 可直接使用。
处理流程:
- 下载音频:
python3 scripts/douyin_download.py <抖音视频URL> --audio
输出:<输出根>/tmp/<视频标题>-音频.m4a
- 转换音频格式:
ffmpeg -i "<输出根>/tmp/<视频标题>-音频.m4a" -acodec libmp3lame -q:a 2 "<输出根>/tmp/<视频标题>-音频.mp3"
- Whisper 转写(默认走脚本,自动下模型):
python3 scripts/transcribe_srt.py "<输出根>/tmp/<视频标题>-音频.mp3" --output "<输出根>/tmp/<视频标题>-音频.srt" --language zh
(其他语言可改 --language en/ja/...;可选备份引擎 whisper-cli 见上方「转写引擎」)
- 生成 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.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 烧录段):
- 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.json 的 settings 读取,用户可在配置面板修改。命令行/对话中指定的参数优先于配置文件。
水印次数和持续时间计算规则(按视频时长动态调整):
| 视频时长 |
水印次数 |
每次持续 |
| <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 仅含 title、source_url。
依赖
所需依赖:
- 工具:
yt-dlp、ffmpeg;whisper-cpp(可选备份转写引擎,不装也能用)
- Python 包:
mlx-whisper(Apple 芯片首选)或 faster-whisper(通用首选)、patchright(抖音视频处理必需)
- 脚本:
scripts/transcribe_srt.py、scripts/vtt_to_text.sh、scripts/douyin_login.py、scripts/douyin_download.py
- 模型:mlx-whisper / faster-whisper 首次运行自动从 HuggingFace 下载,无需手动准备;whisper-cpp 备份引擎需自行把
ggml-large-v3-turbo.bin、ggml-medium.bin 下到 ~/.cache/whisper-cpp/
依赖检查原则:
- 直接执行主流程,遇错再查
初始化.md / 抖音初始化.md 补依赖,默认假设环境已装好
当遇到依赖相关错误时:
- 读取相应的初始化文档并按指引自动安装
- 常规初始化:
初始化.md
- 抖音初始化:
抖音初始化.md
- 安装完成后返回主流程继续执行用户请求
临时文件处理
临时文件位置:<输出根>/tmp/
临时文件类型:
- 字幕文件(.vtt, .srt)
- Whisper 转写输出的文本文件(.mp3.txt)
- 清洗后的字幕文本文件(cleaned.txt,仅字幕抓取时使用)
清理策略:
- AI 处理完成后,可清理临时字幕文件
- Whisper 转写结果文件可保留用于调试
- 用户可随时手动清理
<输出根>/tmp/ 目录
- 重要:最终输出文件(.md)保存在
<输出根>/data/,不受影响
错误处理
常见错误及处理方法:
-
字幕下载失败:
- 原因:视频无字幕、地区限制、需要 PO token
- 处理:提示用户提供 cookies(
--cookies-from-browser chrome)或切换代理
- 兜底:自动切换到音频转写流程
-
Whisper 转写失败:
- 原因:模型文件缺失、音频格式不支持
- 处理:检查模型是否已下载,尝试转换音频格式
-
磁盘空间不足:
- 原因:临时文件或输出文件占用空间过大
- 处理:清理
<输出根>/tmp/ 临时文件目录
翻译规则文档
- 非中文内容翻译时,按需读取
非中文翻译规则.md 并遵守其中规则。
- 中文内容严禁使用翻译规则;仅做标点、分段、合并短句,不得增删改原文。