Movie-dubbing-grade lip sync: given a video and a replacement audio
track (a dub, a voiceover, a translated line reading), it detects the
speaking face, syncs the mouth to the new audio with LatentSync
(diffusion-based, Whisper-conditioned), and restores/sharpens the
result. Rewritten from the original lipsync_pro.sh / lipsync0.2_pro.sh
bash scripts as a proper Python package — same pipeline, four bugs fixed.
bash setup.sh # creates .venv, installs everything correctly
source .venv/bin/activate
python -m lipsync_pipeline video.mp4 dub.wav out.mp4
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --face pick # interactive
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --face 1 # force face index
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --skip-restore --skip-sr
Run python -m lipsync_pipeline --help for the full flag list.
python app.py
Opens a local Gradio UI (http://127.0.0.1:7860): upload a video and an
audio track, click Analyze to see the detected faces (auto-picked by
mouth motion, or pick a different one from the radio list), then Run
lip sync. Same pipeline as the CLI, just point-and-click.
| # | Bug | Fix |
|---|---|---|
| 1 | pip install ... onnxruntime-gpu || pip install onnxruntime <rest> silently skipped imageio, realesrgan, basicsr, facexlib, gfpgan, etc. whenever onnxruntime-gpu installed successfully (the common case), because those packages were only on the || fallback line. | setup.sh installs onnxruntime separately from everything else; requirements.txt lists the rest explicitly. |
| 2 | --face pick saved a preview image, told you to re-run with an index, then continued the full pipeline with the auto-picked face anyway — the preview was decorative. | face.py actually blocks on input() after showing the preview (or falls back cleanly with --non-interactive). |
| 3 | Whisper embedding extraction ran twice (a leftover duplicate code block) — wasted GPU/CPU time every run. | Runs once. |
| 4 | If LatentSync failed via both the CLI and Python API, the old script copied the unsynced crop through as if nothing had gone wrong — you'd only notice by watching the final video. | sync.py raises LatentSyncFailed and the CLI exits with an error instead of shipping unsynced footage. |
This does face-region video editing — the same underlying capability as face-swap/deepfake tools, just aimed at dubbing. It has no consent or provenance checks built in; that's on you. If you're distributing output or attaching your name to it, it's worth: using it on your own or licensed footage with willing subjects, and considering a metadata tag or watermark on output so it's clear the audio was replaced. None of that is enforced here — it's just the responsible-use context that's easy to skip when you're just trying to get a pipeline running.
4 commits
Python
93.8%
Shell
3.9%
Dockerfile
2.3%
Movie-dubbing-grade lip sync: given a video and a replacement audio
track (a dub, a voiceover, a translated line reading), it detects the
speaking face, syncs the mouth to the new audio with LatentSync
(diffusion-based, Whisper-conditioned), and restores/sharpens the
result. Rewritten from the original lipsync_pro.sh / lipsync0.2_pro.sh
bash scripts as a proper Python package — same pipeline, four bugs fixed.
bash setup.sh # creates .venv, installs everything correctly
source .venv/bin/activate
python -m lipsync_pipeline video.mp4 dub.wav out.mp4
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --face pick # interactive
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --face 1 # force face index
python -m lipsync_pipeline video.mp4 dub.wav out.mp4 --skip-restore --skip-sr
Run python -m lipsync_pipeline --help for the full flag list.
python app.py
Opens a local Gradio UI (http://127.0.0.1:7860): upload a video and an
audio track, click Analyze to see the detected faces (auto-picked by
mouth motion, or pick a different one from the radio list), then Run
lip sync. Same pipeline as the CLI, just point-and-click.
| # | Bug | Fix |
|---|---|---|
| 1 | pip install ... onnxruntime-gpu || pip install onnxruntime <rest> silently skipped imageio, realesrgan, basicsr, facexlib, gfpgan, etc. whenever onnxruntime-gpu installed successfully (the common case), because those packages were only on the || fallback line. | setup.sh installs onnxruntime separately from everything else; requirements.txt lists the rest explicitly. |
| 2 | --face pick saved a preview image, told you to re-run with an index, then continued the full pipeline with the auto-picked face anyway — the preview was decorative. | face.py actually blocks on input() after showing the preview (or falls back cleanly with --non-interactive). |
| 3 | Whisper embedding extraction ran twice (a leftover duplicate code block) — wasted GPU/CPU time every run. | Runs once. |
| 4 | If LatentSync failed via both the CLI and Python API, the old script copied the unsynced crop through as if nothing had gone wrong — you'd only notice by watching the final video. | sync.py raises LatentSyncFailed and the CLI exits with an error instead of shipping unsynced footage. |
This does face-region video editing — the same underlying capability as face-swap/deepfake tools, just aimed at dubbing. It has no consent or provenance checks built in; that's on you. If you're distributing output or attaching your name to it, it's worth: using it on your own or licensed footage with willing subjects, and considering a metadata tag or watermark on output so it's clear the audio was replaced. None of that is enforced here — it's just the responsible-use context that's easy to skip when you're just trying to get a pipeline running.
4 commits
Python
93.8%
Shell
3.9%
Dockerfile
2.3%