video-dubbing · git:20260618.b8c6e9a · 2026-06-18 · sha256 e1b7ab942dd8a36a
video-dubbing git:20260618.b8c6e9aA
Immutable. This exact content is served forever at /api/v1/blob/e1b7ab942dd8a36a.
--- name: video-dubbing description: > Translate video/audio content into another language with dubbed voice and synchronized subtitles. Use this skill whenever the user asks to: translate a video, dub a video, generate foreign-language voiceover for a video, add translated subtitles with dubbed audio, or any workflow involving ASR → translate → TTS → video composition. --- # Video Dubbing & Subtitle Sync Complete workflow: **ASR → translate → TTS → merge audio → burn subtitles** ## Pipeline ``` 视频 → 抽音轨(16kHz) → ASR(whisper) → AI翻译 → TTS(dub_segments) → 合并音轨 → 烧录硬字幕 ``` ## Workflow ### 0. Prerequisite Check Ensure whisper + moviepy + mlx-audio are installed: ```bash pip install openai-whisper moviepy 2>/dev/null # mlx_audio for TTS: pip install mlx-audio 2>/dev/null # Apple Silicon ``` ### 1. Extract Audio & Transcribe (ASR) Extract 16kHz mono audio, then run whisper for **accurate per-segment timestamps**: ```bash ffmpeg -y -i input.mp4 -ar 16000 -ac 1 -c:a pcm_s16le audio16k.wav whisper audio16k.wav \ --model large-v3-turbo \ --language <source_lang> \ --output_format srt \ --output_dir . ``` Output: `audio16k.srt` with native timestamps matching the video. > Use `large-v3-turbo` for best accuracy. If you want even better Chinese recognition, install Qwen3-ASR (`pip install qwen-asr`) and use `scripts/qwen3_asr.py` instead. ### 2. Translate Subtitles (AI does this) Read the SRT, translate each segment's text into the target language. **Output one line per SRT segment**, preserving order exactly. - Count SRT segments first (e.g. 27 segments → 27 translated lines) - Save as `translated.txt` in the working directory - Verify line count matches SRT segment count before proceeding ### 3. Generate Dubbed Audio ```bash python3 scripts/dub_segments.py audio16k.srt translated.txt dubbing.wav subtitle_synced.srt --lang <target_lang> ``` What it does: - Generates TTS per segment with **voice consistency** (first segment = voice reference) - **Preserves original timestamps** — subtitles stay synced with video - Smart speed adjustment: only adjusts segments that overflow their slot (atempo 0.88–1.20) - Bridges adjacent gaps <1s for natural flow ### 4. Merge Dubbed Audio into Video ```bash ffmpeg -y \ -i input.mp4 \ -i dubbing.wav \ -map 0:v:0 -map 1:a:0 \ -c:v copy -c:a aac -b:a 192k \ -shortest \ output_temp.mp4 ``` Verify audio replaced: ```bash ffprobe -v error -show_entries stream=codec_type -of csv=p=0 output_temp.mp4 # Should show: video, audio ``` ### 5. Burn Hard Subtitles into Video ```bash python3 scripts/burn_subtitles.py output_temp.mp4 subtitle_synced.srt output_final.mp4 ``` This renders translated subtitles permanently into the video frame (hardcoded). ### 6. Cleanup (Optional) ```bash rm -f audio16k.wav audio16k.srt translated.txt dubbing.wav subtitle_synced.srt output_temp.mp4 ``` ## Output Files | File | Description | |------|-------------| | `output_final.mp4` | Final video with dubbed audio + hardcoded subtitles | | `subtitle_synced.srt` | Synced subtitles (original timestamps + translated text) | | `dubbing.wav` | Per-segment aligned dubbing track | ## Key Design Decisions | Principle | Why | |-----------|-----| | **Preserve original ASR timestamps** | They match the video's visual cues natively. Re-ASR on dubbing drifts. | | **Per-segment speed adjustment** (0.88–1.20x) | Global atempo on the whole dubbing sounds robotic. Per-segment is natural. | | **Hard subtitles (burned in)** | Soft subtitles don't work on all platforms. Burn them so they always show. | | **16kHz mono for ASR** | Whisper expects this. Higher sample rate wastes compute without improving accuracy. | | **Voice consistency via first segment reference** | mlx-tts supports `--ref_audio`. First segment sets the voice for all others. | | **No Demucs by default** | For typical narration/speech videos, background music removal is unnecessary overhead. | | **large-v3-turbo model** | Best accuracy/speed tradeoff. Significantly better than `base` for proper nouns and fast speech. | ## Dependencies | Component | Install | Purpose | |-----------|---------|---------| | `ffmpeg` | `brew install ffmpeg` | Audio extraction, merging | | `openai-whisper` | `pip install openai-whisper` | Speech-to-text (ASR) | | `moviepy` | `pip install moviepy` | Burn subtitles into video | | `mlx-audio` | `pip install mlx-audio` | TTS on Apple Silicon | | `qwen-asr` | `pip install qwen-asr` (optional) | Better Chinese ASR | ## Common Mistakes to Avoid | Mistake | Why It Fails | Correct Approach | |---------|-------------|------------------| | ffmpeg without `-map` | Picks original audio (video source's audio stream), dubbing ignored | Always use `-map 0:v:0 -map 1:a:0` | | Global atempo on entire dubbing | All speech slows uniformly (EN→ZH ~0.8x), sounds robotic | Per-segment alignment (dub_segments.py) | | Re-ASR on dubbed audio for timestamps | Timestamps drift from visual cues | Reuse original Step 1 timestamps | | Translation line count ≠ SRT segments | `dub_segments.py` requires strict 1:1 mapping | Count SRT segments first, match exactly | | Use whisper `base`/`tiny` model | Proper nouns wrong, fast speech missed | Use `large-v3-turbo` or `turbo` | | Burn subtitles without checking FFmpeg libass | `subtitles=` filter silently fails | Use `scripts/burn_subtitles.py` (moviepy) | | Translate segments out of order | Subtitles play at wrong times | Keep segment order: 1 translated line per SRT segment | | Skip verification | Audio may not have been replaced | Run ffprobe to check both audio + video streams exist |