Spaces:
Running
Running
Download video_utils.py from suraj-ml-projects/Video_editing: direct link, hf CLI and curl.
- Browser
- Download file 3.93 kB
-
https://huggingface.co/spaces/suraj-ml-projects/Video_editing/resolve/main/video_utils.py
- Command line
-
hf download hf://spaces/suraj-ml-projects/Video_editing/video_utils.py
-
curl -L -o video_utils.py https://huggingface.co/spaces/suraj-ml-projects/Video_editing/resolve/main/video_utils.py
3.93 kB
| """ | |
| utils/video_utils.py | |
| ──────────────────── | |
| Merge dubbed Hindi audio back into the original video. | |
| Strategy: | |
| 1. Use ffmpeg to mix dubbed speech + original BGM (at reduced volume) | |
| 2. Replace the video's audio track with the mixed result | |
| 3. Copy video codec (no re-encoding) → fast even on CPU | |
| This avoids MoviePy for the final merge (MoviePy re-encodes, very slow on CPU). | |
| We use raw ffmpeg subprocess calls instead — much faster. | |
| """ | |
| import os | |
| import tempfile | |
| import subprocess | |
| def merge_audio_video( | |
| video_path: str, | |
| dubbed_audio_path: str, | |
| original_audio_path: str, | |
| bgm_volume: float = 0.15, | |
| ) -> str: | |
| """ | |
| Create final dubbed video: | |
| - Video stream: copied from original (no re-encode) | |
| - Audio stream: Hindi speech mixed with faint original BGM | |
| Parameters: | |
| video_path – path to original MP4 | |
| dubbed_audio_path – path to Hindi dubbed WAV | |
| original_audio_path – path to extracted original audio WAV | |
| bgm_volume – 0.0–1.0, volume of background music | |
| Returns path to output MP4. | |
| """ | |
| output_path = os.path.join(tempfile.gettempdir(), "hindi_dubbed_output.mp4") | |
| # ffmpeg filter graph: | |
| # [0:a] = original audio (background music) | |
| # [1:a] = Hindi dubbed speech | |
| # | |
| # Step 1: lower original audio volume → bgm | |
| # Step 2: mix bgm + dubbed speech together | |
| # Step 3: mux with video stream (copy, no re-encode) | |
| bgm_vol_str = str(round(bgm_volume, 3)) | |
| cmd = [ | |
| "ffmpeg", "-y", | |
| "-i", video_path, # input 0: original video (video + audio) | |
| "-i", dubbed_audio_path, # input 1: Hindi dubbed audio | |
| "-filter_complex", | |
| ( | |
| # Lower original audio volume for BGM | |
| f"[0:a]volume={bgm_vol_str}[bgm];" | |
| # Mix BGM with Hindi speech | |
| "[bgm][1:a]amix=inputs=2:duration=first:dropout_transition=3[mixed]" | |
| ), | |
| "-map", "0:v", # video stream from original | |
| "-map", "[mixed]", # audio stream = mixed result | |
| "-c:v", "copy", # copy video codec — NO re-encoding (fast!) | |
| "-c:a", "aac", # encode mixed audio as AAC | |
| "-b:a", "192k", # audio bitrate | |
| "-shortest", # trim to shortest stream | |
| output_path, | |
| ] | |
| result = subprocess.run( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| ) | |
| if result.returncode != 0: | |
| # Try a simpler fallback: just replace audio entirely (no BGM mix) | |
| output_path = _simple_replace_audio(video_path, dubbed_audio_path) | |
| return output_path | |
| def _simple_replace_audio(video_path: str, audio_path: str) -> str: | |
| """ | |
| Fallback: simply replace video audio with dubbed audio. | |
| Used when the complex filter fails. | |
| """ | |
| output_path = os.path.join(tempfile.gettempdir(), "hindi_dubbed_output_simple.mp4") | |
| cmd = [ | |
| "ffmpeg", "-y", | |
| "-i", video_path, | |
| "-i", audio_path, | |
| "-map", "0:v", # take video from original | |
| "-map", "1:a", # take audio from dubbed file | |
| "-c:v", "copy", # copy video stream (fast) | |
| "-c:a", "aac", | |
| "-b:a", "192k", | |
| "-shortest", | |
| output_path, | |
| ] | |
| subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
| return output_path | |
| def get_video_duration(video_path: str) -> float: | |
| """Return video duration in seconds using ffprobe.""" | |
| cmd = [ | |
| "ffprobe", "-v", "error", | |
| "-show_entries", "format=duration", | |
| "-of", "default=noprint_wrappers=1:nokey=1", | |
| video_path, | |
| ] | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| try: | |
| return float(result.stdout.strip()) | |
| except Exception: | |
| return 0.0 | |