关于 wjs-dubbing-video
wjs-dubbing-video 是将视频与目标语言 SRT 转为配音视频的技能,适用于需要为视频替换语言音轨的制作者。它按语音 ID 选择 TTS 引擎,并让配音音频与字幕时间及原视频长度对齐。
主要功能
- 01按语音 ID 路由 TTS 引擎
- 02逐条合成 SRT 配音
- 03超时音频加速或补静音
- 04保留原视频流并合成输出
- 05支持单人及多说话人配音
适用场景
- 01为视频添加目标语言旁白
- 02让访谈中的不同说话者使用不同声音
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jianshuo/ claude-skills --skill wjs-dubbing-video安装后在 Claude Code 中输入/wjs-dubbing-video确认已加载。
附带文件scripts/dub.pyscripts/visual_diarize.py
详细介绍
wjs-dubbing-video
视频 + 目标语言 SRT → *_<lang>_dub.mp4,并配上与时间轴对齐的 TTS 语音。此技能仅处理配音轨。 烧录字幕 + 混合音频底床属于下一项技能(/wjs-burning-subtitles/render.py 会通过一次最终编码合成所有内容)。
何时使用
- 用户已有目标语言 SRT(例如
entrevista.zh-CN.srt),并希望视频能以该语言播放。 - 用户说“中文配音 / 配音 / 帮我做配音 / dub it / voice over”。
- 视频中有多位出镜者,且用户希望为每位说话者使用不同的声音。
何时不使用
- 还没有 SRT → 先运行
/wjs-transcribing-audio,再运行/wjs-translating-subtitles。 - 仅使用源语言进行 TTS(较少见;通常会先翻译)→ 仍使用此技能,但传入源语言 SRT。
- 仅烧录字幕,不更改音频 → 直接使用
/wjs-burning-subtitles。
说话者数量 — 默认一位
默认假设只有一位说话者。 整段配音使用同一个声音。这适用于独白、视频博客、录制的演讲、仅有旁白的片段,以及绝大多数用户提出需求的视频。不要进行说话者区分,不要在 SRT 中添加 [A] / [B] 标签,也不要提及多说话者的复杂性。
仅当用户明确提出时才切换为多说话者模式——例如“有两个人”“访谈”“对话”“两人之间的谈话”“区分说话者”“每个人使用不同的声音”,或直接要求进行说话者区分。触发后,请遵循下文的“多说话者配音”部分。
如果不确定视频中有一位还是多位说话者,先交付单一音色版本。之后添加说话者区分很简单(只需重新生成配音);默认交付混淆的多说话者版本会浪费用户时间。
引擎路由 — 按语音 ID
scripts/dub.py 会根据语音 ID 前缀自动路由:
| 语音 ID 模式 | 引擎 | 身份验证 |
|---|---|---|
zh_..._bigtts | Volcano(字节跳动豆包)TTS | VOLC_TTS_APPID + VOLC_TTS_ACCESS_TOKEN |
zh-CN-...Neural / en-US-...Neural / 等 | edge-tts(Microsoft Edge 神经语音) | 无(免费) |
对于普通话,Volcano 的自然度明显胜过 edge-tts,尤其适用于富有情感或沉思性的内容。没有 Volcano 凭据时,或作为调试备用方案时,使用 edge-tts。
Volcano TTS(仅中文)
端点:https://openspeech.bytedance.com/api/v3/tts/unidirectional(TTS 1.0 和 2.0 均使用此端点;Resource-Id 请求头用于选择后端)。
请求头:
X-Api-App-Id: (env: VOLC_TTS_APPID) # 10-digit speech App ID
X-Api-Access-Key: (env: VOLC_TTS_ACCESS_TOKEN) # 32-char token from speech console
X-Api-Resource-Id: volc.service_type.10029 # see resource ID note below
Content-Type: application/json
加载凭据:大多数用户将凭据保存在 ~/code/.env 中。每次会话开始时,通过以下方式读取:
set -a; source ~/code/.env; set +a
Resource ID — 一个重要的特殊情况
文档将 seed-tts-2.0 列为“TTS 2.0(推荐)”资源,但典型的 TTS-SeedTTS2.0 控制台实例并不包含常见的 *_bigtts 说话者目录(爽快斯斯、高冷御姐、开朗姐姐等)。使用这些说话者调用 seed-tts-2.0 会返回 200 code=55000000 "resource ID is mismatched with speaker related resource"。解决方法是使用 volc.service_type.10029(TTS 1.0 V3 端点)——bigtts 说话者的音质完全相同,而且都能使用此资源。随附的 dub.py 默认使用 volc.service_type.10029;如果你使用的是其他实例,可通过 VOLC_TTS_RESOURCE 环境变量覆盖。
其他 401/403 错误:
401 code=45000010 "load grant: requested grant not found in SaaS storage"— App ID + 密钥组合可通过网关验证,但用户尚未启用此资源。用户必须前往火山引擎 → 语音技术 → 语音合成大模型 → 实例管理并开通服务。没有其他解决办法。403 code=45000030— 用户的实例套餐中不包含该说话者。
响应格式
尽管文档表述比较随意,响应实际为流式 NDJSON,不是单个 JSON 对象,也不是原始音频字节。每一行都是独立的 JSON 事件,其中 data 包含经 Base64 编码的 MP3 音频块。结束事件的 code 为 20000000(在此 API 的成功代码中表示成功,不同于 code: 0)。将解码后的音频块拼接起来即可得到完整的 MP3。
import base64, json, requests
audio = b""
r = requests.post(url, headers=h, json=payload, timeout=60, stream=True)
for line in r.iter_lines():
if not line: continue
evt = json.loads(line)
if evt.get("code") not in (0, None, 20000000):
raise RuntimeError(f"code={evt.get('code')} {evt.get('message')}")
if evt.get("data"):
audio += base64.b64decode(evt["data"])
说话人目录(已确认可在 volc.service_type.10029 下正常使用)
完整列表见 volcengine.com/docs/6561/1257544 — 但可用性取决于你的实例套餐。已确认可在典型的 SeedTTS-2.0 入门实例中使用的女声:
| 说话人 ID | 中文名 | 感觉 |
|---|---|---|
zh_female_gaolengyujie_moon_bigtts | 高冷御姐 | 最适合沉思、灵性内容。 成熟、克制、平静。 |
zh_female_kailangjiejie_moon_bigtts | 开朗姐姐 | 温暖的大姐姐讲述风格。 |
zh_female_shuangkuaisisi_moon_bigtts | 爽快斯斯 | 百搭、对话感强,适合作为基准。 |
zh_female_linjianvhai_moon_bigtts | 邻家女孩 | 轻松随意,适合生活类 vlog。 |
zh_female_yuanqinvyou_moon_bigtts | 元气女友 | 活泼、积极。 |
zh_female_meilinvyou_moon_bigtts | 美丽女友 | 柔和、亲密。 |
zh_female_shuangkuaisisi_emo_v2_mars_bigtts | 斯斯情感版 | 情感表现力丰富 — 搭配明确的情绪和强度使用。 |
尽管文档列出了以下声音,但在典型实例中调用时会返回 55000000:vv_uranus_bigtts、wenroushunv_moon_bigtts、qingxin_moon_bigtts、yingmaoxiaoyuan_moon_bigtts、tianxinxiaoling_moon_bigtts、shaoergushi_moon_bigtts。未经测试,不要承诺这些声音可用。
音频参数
speech_rate 是 Volcano 原生的刻度 [-50, +100],数值表示百分比变化(因此,-8 表示慢 8%)。脚本会将 --rate -8% 作为 -8 传递。
实用的情绪预设:
emotion="calm"、emotion_scale=4— 沉思风格,是此技能灵性内容细分领域的默认设置。emotion="gentle"— 更柔和、更亲密。emotion="neutral"— 平淡、信息传达风格。emotion="sad"— 忧郁风格。请谨慎使用。
无需修改代码,即可通过 VOLC_TTS_EMOTION 和 VOLC_TTS_EMOTION_SCALE 环境变量覆盖 dub.py 的默认设置。
此技能未配置任何 Volcano 英语声音 — 英语内容请使用 edge-tts(下一节)。Volcano 确实提供英语说话人(en_male_*_bigtts、en_female_*_bigtts),但它们通常不包含在 TTS-SeedTTS-2.0 入门实例中。确认可用后,可扩展 dub.py 中的声音路由来添加。
edge-tts(Microsoft Edge 神经网络 TTS)
免费,无需 API 密钥,音质出色,但表现力不如 Volcano。请安装到项目虚拟环境中 — 不要 每个片段都通过 uvx 调用。每次调用 uvx 都会启动一个全新的 Python 进程;快速连续调用几次后,Bing 端点会进行速率限制或重置连接,从而导致渲染中途失败。
uv venv .venv
uv pip install --python .venv/bin/python edge-tts
然后通过单个长期运行的 Python 进程直接调用 edge_tts.Communicate(...),并加入失败重试逻辑。随附的 scripts/dub.py 已实现此功能。
语音选择 — 匹配原说话人
跨语言匹配无法做到完美 — 请仔细选择性别、年龄感和语气,再通过语速和音高进行调整。
中文语音(优先使用 Volcano,备用 edge-tts)
Volcano 的 zh_female_gaolengyujie_moon_bigtts(高冷御姐,沉稳,speech_rate=-8)是成熟、沉思型女性说话者的验证基线,对于这类风格,其表现与任何 edge-tts 选项相当或更好。其余语音请参阅上方的 Volcano 说话者表格。
edge-tts 语音目录(中文):
| 语音 | 性别 | 默认风格 |
|---|---|---|
zh-CN-XiaoxiaoNeural | 女 | 温暖,适合新闻/小说 |
zh-CN-XiaoyiNeural | 女 | 活泼,年轻 |
zh-CN-YunjianNeural | 男 | 激情,适合体育内容 |
zh-CN-YunxiNeural | 男 | 阳光,活泼 |
zh-CN-YunyangNeural | 男 | 专业新闻播报员 |
zh-HK-HiuMaanNeural | 女 | 友好,略显成熟 |
zh-TW-HsiaoChenNeural | 女 | 友好 |
英语语音(edge-tts 神经语音,均支持多语言)
以下所有语音都能流利地说美式/英式/澳大利亚英语;*Multilingual* 语音还可以处理西班牙语姓名、法语/意大利语借词等,不会出现误读。
| 语音 | 性别 | 默认风格 |
|---|---|---|
en-US-AvaMultilingualNeural | 女 | 最适合温暖/成熟/关怀的风格——适合灵性或教练类内容 |
en-US-EmmaMultilingualNeural | 女 | 开朗、健谈,较年轻 |
en-US-AndrewMultilingualNeural | 男 | 温暖、自信、真诚 |
en-US-BrianMultilingualNeural | 男 | 平易近人、随意 |
en-US-AriaNeural | 女 | 清晰的新闻播报员 |
en-US-GuyNeural | 男 | 稳重的男性新闻播报员 |
en-GB-SoniaNeural | 女 | 英式女性(RP) |
en-GB-RyanNeural | 男 | 英式男性(RP) |
en-AU-WilliamMultilingualNeural | 男 | 澳大利亚男性 |
fr-FR-VivienneMultilingualNeural | 女 | 成熟的欧洲女性,也能朗读英语 |
若要匹配成熟、沉思型的西班牙语女性说话者(此技能的典型用例),请从 en-US-AvaMultilingualNeural 开始,并设置 --rate -5% --pitch -3Hz。不要将新闻播报风格的 Aria 或 Guy 用于灵性内容——听起来会很生硬。
选择启发
- 成熟、沉思型女性说话者(瑜伽/灵性/教练):
zh-CN-XiaoxiaoNeural,搭配--rate=-8% --pitch=-10Hz(或 Volcanogaolengyujie)。 - 成熟的专业男性:
zh-CN-YunyangNeural,搭配--rate=-5%。避免使用 Yunjian/Yunxi(过于活跃)。 - 年轻、随意的说话者: 使用默认设置;不调整音高。
- 西式口音风格: 使用
*MultilingualNeural语音之一。
提交前务必先试听
🛑 检查点 — 完整配音前先试听。 完整视频配音是成本最高的步骤(TTS API 调用 + atempo + ffmpeg 混流)。在对整个 SRT 运行 dub.py 前:
- 选取文本最长的字幕条目(最容易超时)和一条较短、较随意的字幕条目(检查音色)。
- 合成 3–4 组音色、语速和音高组合,每段 3–8 秒。
- 向用户展示音频面板并询问:“选哪个 voice?rate/pitch 要调吗?确认后我再跑全片。” 等待用户明确选择。
只有在用户一开始就指定了特定音色,且已经听过该音色在这个视频中的试听时,才跳过此检查点。
脚本中的 scripts/sample_voices.py(如果存在)正是为此提供的轻量封装;否则,使用与配音脚本相同的 Python 循环。
在新账户上使用任何 Volcano 音色前,必须先进行冒烟测试: 先用该说话人 ID 合成一条约 5 个词的字幕。只有冒烟测试返回非空 MP3 时,才能向用户推荐该音色。如果冒烟测试返回 401,且 code=45000010(“grant not found”),请告诉用户他们需要在火山引擎控制台开通资源 — 不要假装重试后就能用。
运行 dub.py
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py [voice] [rate] [pitch]
# Mature Chinese contemplative female (Volcano):
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py \
zh_female_gaolengyujie_moon_bigtts -8% +0Hz
# Warm English caring female (edge-tts, multilingual):
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py \
en-US-AvaMultilingualNeural -5% -3Hz
# Default Chinese fallback (no Volcano creds needed):
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py \
zh-CN-XiaoxiaoNeural -8% -10Hz
该脚本:
- 读取 SRT(自动检测
*.zh-CN.srt、*.en.srt等文件,也可以传入--srt)。 - 在
dub_work/seg_NN.mp3下为每条字幕合成一个 MP3。 - 使用
ffprobe检查每段音频的实际时长。 - 对每条字幕:如果 TTS 音频长于 SRT 时段,则串联
atempo滤镜来加速;如果较短,则在后面补入静音。 - 为 SRT 中的间隙以及末尾多余部分插入静音片段,使输出音频长度与源视频完全一致。
- 将新音频混流到
*_zh_dub.mp4/*_en_dub.mp4,并通过-c:v copy保留原视频流。
输出:<source-stem>_<lang>_dub.mp4(例如 entrevista_zh_dub.mp4)。这是下一步 — /wjs-burning-subtitles/render.py — 的输入文件,该步骤会合成最终视频。
填补不自然的静音
表达相同内容时,普通话所需时间是西班牙语的 60–80%。在严格按字幕条目对齐时,这会导致大多数字幕条目末尾出现 2–4 秒的不自然静音。英语所需时间则接近西班牙语的 ~85%。以下三种方法效果依次增强:
-
降低原生 TTS 语速。 将
--rate从+0%改为-12%或-15%,可以生成清晰、自然的慢速语音(远胜于事后拉伸音频)。先尝试-12%;对于非常沉思的内容,可试试-15%/-20%。 -
对每条字幕进行轻微慢速拉伸。 如果某条字幕的 TTS 音频仍短于对应时段,可将
atempo设为 0.82× 到 0.95× 之间。dub.py会自动处理:当剩余时长 > 0.5 秒时,它会设置atempo = max(0.82, tts_dur / target_dur),并在剩余部分补入静音。低于 0.82× 时,声音会开始听起来像被下了药;高于 0.92× 时,拉伸基本难以察觉。 -
扩充最难处理字幕条目的目标语言文本。 如果时段太长,即使以 0.82× 拉伸后仍留下 >2 秒静音,最妥善的解决方法就是延长译文。加入自然的普通话语气词(“嗯,”“其实”“也就是说”“你知道”),或将压缩的短语展开,表达完整含义。这会改变屏幕上的字幕,因此修改前请先征得用户同意。编辑 SRT 后,删除对应片段的
dub_work/seg_NN.mp3并重新运行dub.py,只重新生成这些片段。
组合使用这些方法:原生语速 -12% + 拉伸至适配时长,可以处理约 80% 的情况。只有对 2–3 个最极端的条目才扩充文本。
多说话人配音(可选)
只有当用户明确表示源视频中有多个说话人时,才执行本节(例如“采访”“两个人”“对话”“区分说话人”“每个人用不同音色”,或直接要求进行说话人分离)。
触发后,为每位说话人使用不同音色生成配音,让听众能分辨是谁在说话。有两种方案:
路径 1(推荐用于出镜说话者):视觉说话人分离
scripts/visual_diarize.py 会逐帧监测每张脸的嘴部动作,并为每条字幕标记主要说话者。无需 API 密钥或音频指纹识别,可独立运行。
uv pip install --python .venv/bin/python mediapipe opencv-python
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/visual_diarize.py \
--video input.mp4 --srt input.en.srt \
--out input.en.diarized.srt \
--report diarization_report.json \
--sample-fps 5 --num-speakers 2
工作原理:
- 每秒采样 N 帧(默认为 5)。
- 使用 MediaPipe FaceLandmarker(Tasks API),每帧最多检测
--num-speakers张脸,每张脸 478 个关键点。 - 通过内侧上唇(idx 13)与内侧下唇(idx 14)之间的垂直距离,测量每张脸的嘴部张开幅度。
- 按脸在屏幕上的水平位置(x 分位数)分组,按从左到右的顺序标记为说话者
A、B,依此类推。 - 对每条字幕的 [start, end] 时间窗口,累计各说话者逐帧嘴部张开幅度的变化量。变化量最大者获得该标签。
- 写入带有
[A]/[B]前缀的 SRT,以及包含每条字幕得分和置信度比率(胜出者 / 第二名)的 JSON 报告。
首次运行时,会将 FaceLandmarker 模型(约 3.6 MB)下载到 /tmp/mp_models/face_landmarker.task。
视觉判断明显优于根据文本猜测。 在一次验证中,基于文本的人工标记将 6/50 条字幕分给一位说话者;视觉说话人分离显示实际分配为 29/27——基于文本的猜测大错特错,因为两人轮流说话的形式很相似。说话者出镜时,请始终优先使用视觉判断。
抽查低置信度字幕。 JSON 报告中任何 confidence_ratio < 1.5 的字幕都属于临界情况,通常是语音重叠或一位说话者短暂离开画面所致。配音前请手动更正。
路径 2(备用):手动标记
适用于非常短的片段(1–2 分钟)、说话者未出镜,或视觉说话人分离失败的情况:
1
00:00:00,000 --> 00:00:03,400
[A] So what about that AI rewrite thing?
2
00:00:03,400 --> 00:00:08,200
[B] Right — let me explain the workflow.
保存为 *.tagged.srt。下游通过 /wjs-burning-subtitles 烧录字幕时,请使用不带标签的干净版 SRT。
在 dub.py 中分配语音
使用 --voice-map 并传入 speaker=voice 键值对。位置参数中的语音是未标记字幕的默认语音。
.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py \
en-US-AndrewMultilingualNeural -3% +0Hz \
--srt input.en.tagged.srt \
--voice-map "A=en-US-BrianMultilingualNeural,B=en-US-AndrewMultilingualNeural"
语音配对建议:
- 两位同性说话者: 选择音色差异明显的语音。Brian(随性)+ Andrew(温和)适合两位美国男性;Ava(温和女声)+ Emma(活泼女声)适合两位女性。
- 不同性别: Ava + Andrew 是稳妥的默认选择。
- 口音对比: 配对
en-US-和en-GB-,以增强区分度。 - 中文: 混用火山语音,例如
zh_female_gaolengyujie_moon_bigtts(成熟)+zh_female_kailangjiejie_moon_bigtts(温暖的大姐姐)。
局限
以下情况会导致视觉说话人分离失败:
- 说话者说话时始终未出镜。
- 镜头切换或变焦导致不同字幕之间的人脸位置不稳定。
- 三位或更多说话者的水平位置相近(x 分位数分组过于粗略——请改用基于 (x, y) 的 k-means,或改用基于音频的说话人分离)。
对于纯音频素材(播客、旁白),请改用 pyannote.audio 或 whisperx --diarize。此工具尚未集成基于音频的说话人分离。
输出
<source-stem>_<lang>_dub.mp4— 视频流直接从源文件复制,音频替换为与时间对齐的配音轨道。可直接作为/wjs-burning-subtitles/render.py的输入。dub_work/seg_NN.mp3— 每条字幕对应的 TTS 片段(保留以便恢复或重新生成单条字幕)。
下游处理
/wjs-burning-subtitles— 用于将原始音频混合作为低音量背景、烧录 SRT 字幕,或同时完成这两项。最终编码在此处通过一次 ffmpeg 处理完成(不会连续多次编码)。向其render.py传入--video <source.mp4> --dub <source_lang_dub.mp4> [--srt <srt>]。- 仅配音文件(
*_<lang>_dub.mp4)在技术上已是成片,可以直接交付,但听起来会像配音(因为它就是配音)。在底层混入原始音频会带来「专业翻译」的效果——请在/wjs-burning-subtitles中完成此操作。
反模式
- ❌ 每个提示都调用一次
uvx edge-tts。 每次都会启动一个 Python 进程;bing 端点会限流,或在渲染过程中发送 RST。请使用dub.py中的持久化库路径。 - ❌ 不试听就相信
audio_source。 提交前务必抽听一段 30 s 的片段。 - ❌ 将速度拉伸到低于 0.82× atempo。 听起来会像嗑了药。请添加静音填充,或扩展文本。
- ❌ 给单人说话者的 SRT 添加
[A]标记。 这会浪费时间,而且配音听起来也一样。默认使用一个音色。 - ❌ 未经用户实例上的冒烟测试,就承诺使用 Volcano 音色。 文档列出了许多音色,但在常见的 SeedTTS-2.0 入门包中会报错
code=55000000。报价前务必合成一个 5 个词的冒烟测试。 - ❌ 将 Volcano 响应解析为一个 JSON 文档。 它是流式 NDJSON;成功终止码是
code=20000000,而不是code=0。请拼接 base64 解码后的data分块,获取完整 MP3。