Video_editing / audio_utils.py
suraj-ml-projects's picture
Upload 5 files
4383d5e verified
Raw History Blame Contribute Delete
3.63 kB
"""
utils/audio_utils.py
────────────────────
Audio extraction, silence detection, and audio mixing helpers.
Libraries: ffmpeg-python, librosa, pydub
All CPU-friendly, no GPU needed.
"""
import os
import tempfile
import subprocess
import numpy as np
import librosa
from pydub import AudioSegment
from pydub.silence import detect_nonsilent
def extract_audio(video_path: str) -> str:
"""
Pull the audio track out of a video file using ffmpeg.
Returns path to a WAV file (16kHz mono — ideal for Whisper).
"""
out_path = os.path.join(tempfile.gettempdir(), "extracted_audio.wav")
# ffmpeg command: extract audio, convert to 16kHz mono WAV
cmd = [
"ffmpeg", "-y", # -y = overwrite without asking
"-i", video_path, # input video
"-vn", # drop video stream
"-acodec", "pcm_s16le", # uncompressed WAV
"-ar", "16000", # 16 kHz sample rate (Whisper prefers this)
"-ac", "1", # mono channel
out_path,
]
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return out_path
def detect_silence_segments(audio_path: str, min_silence_ms: int = 700, silence_thresh_db: int = -40):
"""
Detect silence / low-energy sections in the audio.
Returns list of (start_sec, end_sec) tuples for silent regions.
These mark where background music or ambient sound lives —
useful so we don't try to dub over music sections.
Parameters:
min_silence_ms – minimum duration to count as silence (ms)
silence_thresh_db – dB level below which is considered silence
"""
audio = AudioSegment.from_wav(audio_path)
# detect_nonsilent returns [(start_ms, end_ms), ...] of SPEECH
speech_intervals = detect_nonsilent(
audio,
min_silence_len=min_silence_ms,
silence_thresh=silence_thresh_db,
)
# Invert to get silence intervals
total_ms = len(audio)
silence_segs = []
prev_end = 0
for start, end in speech_intervals:
if start > prev_end + 200: # gap > 200ms counts as silence
silence_segs.append((prev_end / 1000, start / 1000))
prev_end = end
if prev_end < total_ms - 200:
silence_segs.append((prev_end / 1000, total_ms / 1000))
return silence_segs
def mix_audio_with_video(video_path: str, speech_audio_path: str, bgm_volume: float = 0.15) -> str:
"""
Mix Hindi speech with the original video's background audio.
Strategy:
- Extract original audio, lower its volume (bgm_volume)
- Overlay the Hindi speech track on top
- Result: faint original BGM + clear Hindi speech
"""
bgm_path = os.path.join(tempfile.gettempdir(), "bgm_only.wav")
# Extract original audio
subprocess.run([
"ffmpeg", "-y", "-i", video_path,
"-vn", "-acodec", "pcm_s16le", "-ar", "44100", "-ac", "2",
bgm_path,
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Load both
bgm = AudioSegment.from_wav(bgm_path)
speech = AudioSegment.from_wav(speech_audio_path)
# Lower BGM volume
bgm = bgm + (20 * np.log10(bgm_volume)) # dB adjustment
# Pad speech if shorter than BGM
if len(speech) < len(bgm):
speech = speech + AudioSegment.silent(duration=len(bgm) - len(speech))
# Overlay speech on BGM
mixed = bgm.overlay(speech)
mixed_path = os.path.join(tempfile.gettempdir(), "mixed_audio.wav")
mixed.export(mixed_path, format="wav")
return mixed_path