Loading...
Loading...
Compare original and translation side by side
*_<lang>_dub.mp4/wjs-burning-subtitles/render.py*_<lang>_dub.mp4/wjs-burning-subtitles/render.pyentrevista.zh-CN.srtentrevista.zh-CN.srt/wjs-transcribing-audio/wjs-translating-subtitles/wjs-burning-subtitles/wjs-transcribing-audio/wjs-translating-subtitles/wjs-burning-subtitles[A][B][A][B]scripts/dub.py| Voice ID pattern | Engine | Auth |
|---|---|---|
| Volcano (字节跳动豆包) TTS | |
| edge-tts (Microsoft Edge neural) | none (free) |
scripts/dub.py| 语音ID模式 | 引擎 | 认证方式 |
|---|---|---|
| Volcano(字节跳动豆包)TTS | |
| edge-tts(Microsoft Edge神经语音合成) | 无需认证(免费) |
https://openspeech.bytedance.com/api/v3/tts/unidirectionalX-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/.envset -a; source ~/code/.env; set +ahttps://openspeech.bytedance.com/api/v3/tts/unidirectionalX-Api-App-Id: (环境变量: VOLC_TTS_APPID) # 10位语音应用ID
X-Api-Access-Key: (环境变量: VOLC_TTS_ACCESS_TOKEN) # 语音控制台获取的32位令牌
X-Api-Resource-Id: volc.service_type.10029 # 详见下文资源ID说明
Content-Type: application/json~/code/.envset -a; source ~/code/.env; set +aseed-tts-2.0*_bigttsseed-tts-2.0200 code=55000000 "resource ID is mismatched with speaker related resource"volc.service_type.10029dub.pyvolc.service_type.10029VOLC_TTS_RESOURCE401 code=45000010 "load grant: requested grant not found in SaaS storage"403 code=45000030seed-tts-2.0*_bigttsseed-tts-2.0200 code=55000000 "resource ID is mismatched with speaker related resource"volc.service_type.10029dub.pyvolc.service_type.10029VOLC_TTS_RESOURCE401 code=45000010 "load grant: requested grant not found in SaaS storage"403 code=45000030datacode: 20000000code: 0import 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"])datacode20000000code: 0import 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.10029volc.service_type.10029| Speaker ID | 中文名 | Feel |
|---|---|---|
| 高冷御姐 | Best for contemplative/spiritual content. Mature, restrained, calm. |
| 开朗姐姐 | Warm older-sister storytelling. |
| 爽快斯斯 | Versatile, conversational baseline. |
| 邻家女孩 | Casual, lifestyle-vlog. |
| 元气女友 | Lively, upbeat. |
| 美丽女友 | Soft, intimate. |
| 斯斯情感版 | Full emotional range — pair with explicit emotion + scale. |
vv_uranus_bigttswenroushunv_moon_bigttsqingxin_moon_bigttsyingmaoxiaoyuan_moon_bigttstianxinxiaoling_moon_bigttsshaoergushi_moon_bigtts| 语音ID | 中文名 | 风格 |
|---|---|---|
| 高冷御姐 | 最适合沉思/精神类内容。成熟、克制、沉稳。 |
| 开朗姐姐 | 温暖的大姐姐讲故事风格。 |
| 爽快斯斯 | 通用、对话式基准语音。 |
| 邻家女孩 | 休闲、生活vlog风格。 |
| 元气女友 | 活泼、积极向上。 |
| 美丽女友 | 柔和、亲密。 |
| 斯斯情感版 | 全情感范围 —— 需搭配明确的情感参数和强度。 |
vv_uranus_bigttswenroushunv_moon_bigttsqingxin_moon_bigttsyingmaoxiaoyuan_moon_bigttstianxinxiaoling_moon_bigttsshaoergushi_moon_bigttsspeech_rate-8--rate -8%-8emotion="calm"emotion_scale=4emotion="gentle"emotion="neutral"emotion="sad"dub.pyVOLC_TTS_EMOTIONVOLC_TTS_EMOTION_SCALEen_male_*_bigttsen_female_*_bigttsdub.pyspeech_rate-8--rate -8%-8emotion="calm"emotion_scale=4emotion="gentle"emotion="neutral"emotion="sad"VOLC_TTS_EMOTIONVOLC_TTS_EMOTION_SCALEdub.pyen_male_*_bigttsen_female_*_bigttsdub.pyuvxuvxuv venv .venv
uv pip install --python .venv/bin/python edge-ttsedge_tts.Communicate(...)scripts/dub.pyuvxuvxuv venv .venv
uv pip install --python .venv/bin/python edge-ttsedge_tts.Communicate(...)scripts/dub.pyzh_female_gaolengyujie_moon_bigttsspeech_rate=-8| Voice | Gender | Default feel |
|---|---|---|
| F | Warm, news/novel |
| F | Lively, young |
| M | Passionate, sports |
| M | Sunshine, lively |
| M | Professional newsreader |
| F | Friendly, slightly mature |
| F | Friendly |
zh_female_gaolengyujie_moon_bigttsspeech_rate=-8| 语音 | 性别 | 默认风格 |
|---|---|---|
| 女 | 温暖,新闻/小说播报风格 |
| 女 | 活泼,年轻风格 |
| 男 | 富有激情,体育解说风格 |
| 男 | 阳光,活泼风格 |
| 男 | 专业新闻播报风格 |
| 女 | 友好,略带成熟风格 |
| 女 | 友好风格 |
*Multilingual*| Voice | Gender | Default feel |
|---|---|---|
| F | Best for warm/mature/caring — natural for spiritual or coaching content |
| F | Cheerful, conversational, younger |
| M | Warm, confident, sincere |
| M | Approachable, casual |
| F | Crisp newsreader |
| M | Steady male newsreader |
| F | British female (RP) |
| M | British male (RP) |
| M | Australian male |
| F | Mature European female who also reads English |
en-US-AvaMultilingualNeural--rate -5% --pitch -3HzAriaGuy*Multilingual*| 语音 | 性别 | 默认风格 |
|---|---|---|
| 女 | 最适合温暖/成熟/关怀风格 —— 自然适配精神类或指导类内容 |
| 女 | 开朗,对话式,年轻风格 |
| 男 | 温暖,自信,真诚风格 |
| 男 | 平易近人,休闲风格 |
| 女 | 清晰的新闻播报风格 |
| 男 | 沉稳的男性新闻播报风格 |
| 女 | 英式女性(RP口音) |
| 男 | 英式男性(RP口音) |
| 男 | 澳式男性风格 |
| 女 | 成熟欧洲女性风格,也可朗读英语 |
en-US-AvaMultilingualNeural--rate -5% --pitch -3HzAriaGuyzh-CN-XiaoxiaoNeural--rate=-8% --pitch=-10Hzgaolengyujiezh-CN-YunyangNeural--rate=-5%*MultilingualNeuralzh-CN-XiaoxiaoNeural--rate=-8% --pitch=-10Hz高冷御姐zh-CN-YunyangNeural--rate=-5%*MultilingualNeuraldub.pyscripts/sample_voices.pycode=45000010dub.pyscripts/sample_voices.pycode=45000010.venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py [voice] [rate] [pitch].venv/bin/python ~/.claude/skills/wjs-dubbing-video/scripts/dub.py [voice] [rate] [pitch]
The script:
1. Reads the SRT (auto-detects `*.zh-CN.srt`, `*.en.srt`, etc., or pass `--srt`).
2. Synthesizes one MP3 per cue under `dub_work/seg_NN.mp3`.
3. Probes each clip's actual duration with `ffprobe`.
4. For each cue: if TTS is longer than the SRT slot, chains `atempo` filters to speed it up; if shorter, pads with silence after.
5. Inserts silence segments for SRT gaps and any trailing tail so the output audio length exactly matches the source video.
6. Muxes the new audio into `*_zh_dub.mp4` / `*_en_dub.mp4` keeping the original video stream by `-c:v copy`.
Output: `<source-stem>_<lang>_dub.mp4` (e.g., `entrevista_zh_dub.mp4`). This is the input for the next step — `/wjs-burning-subtitles/render.py` — which composites the final video.
脚本执行流程:
1. 读取SRT字幕(自动检测`*.zh-CN.srt`、`*.en.srt`等,或通过`--srt`参数指定)。
2. 为每个字幕片段合成MP3,保存到`dub_work/seg_NN.mp3`。
3. 使用`ffprobe`探测每个片段的实际时长。
4. 对于每个字幕片段:若TTS音频长于SRT时间槽,通过`atempo`滤镜链加速;若短于,则在末尾填充静音。
5. 为SRT中的间隙和片尾添加静音片段,使输出音频时长与原视频完全匹配。
6. 将新音频混流到`*_zh_dub.mp4`/`*_en_dub.mp4`中,通过`-c:v copy`保留原视频流。
输出文件:`<source-stem>_<lang>_dub.mp4`(例如`entrevista_zh_dub.mp4`)。此文件为下一步的输入——`/wjs-burning-subtitles/render.py`将合成最终视频。--rate+0%-12%-15%-12%-15%-20%atempodub.pyatempo = max(0.82, tts_dur / target_dur)dub_work/seg_NN.mp3dub.py-12%--rate+0%-12%-15%-12%-15%-20%atempodub.pyatempo = max(0.82, tts_dur / target_dur)dub_work/seg_NN.mp3dub.py-12%scripts/visual_diarize.pyuv 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--num-speakersAB[A][B]/tmp/mp_models/face_landmarker.taskconfidence_ratio < 1.5scripts/visual_diarize.pyuv 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--num-speakersAB[A][B]/tmp/mp_models/face_landmarker.taskconfidence_ratio < 1.51
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-subtitles1
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--voice-mapspeaker=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"en-US-en-GB-zh_female_gaolengyujie_moon_bigttszh_female_kailangjiejie_moon_bigtts--voice-mapspeaker=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"en-US-en-GB-zh_female_gaolengyujie_moon_bigttszh_female_kailangjiejie_moon_bigttspyannote.audiowhisperx --diarizepyannote.audiowhisperx --diarize<source-stem>_<lang>_dub.mp4/wjs-burning-subtitles/render.pydub_work/seg_NN.mp3<source-stem>_<lang>_dub.mp4/wjs-burning-subtitles/render.pydub_work/seg_NN.mp3/wjs-burning-subtitles--video <source.mp4> --dub <source_lang_dub.mp4> [--srt <srt>]render.py*_<lang>_dub.mp4/wjs-burning-subtitles/wjs-burning-subtitlesrender.py--video <source.mp4> --dub <source_lang_dub.mp4> [--srt <srt>]*_<lang>_dub.mp4/wjs-burning-subtitlesuvx edge-ttsdub.pyaudio_source[A]code=55000000code=20000000code=0datauvx edge-ttsdub.pyaudio_source[A]code=55000000code=20000000code=0data