DubSync / app.py
gholapeajinkya's picture
Update app.py
90cba0a verified
Raw History Blame Contribute Delete
35.6 kB
import os
# Set environment variables for writable paths
os.environ["XDG_CONFIG_HOME"] = "/tmp/.config"
os.environ["XDG_CACHE_HOME"] = "/tmp/.cache"
os.environ["HF_HOME"] = "/tmp/huggingface"
os.environ["HF_HUB_CACHE"] = "/tmp/huggingface/hub"
os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers"
os.environ["TORCH_HOME"] = "/tmp/torch"
os.environ["MPLCONFIGDIR"] = "/tmp/matplotlib"
os.environ["STREAMLIT_HOME"] = "/tmp/.streamlit"
# Make sure directories exist
for path in [
os.environ["XDG_CONFIG_HOME"],
os.environ["XDG_CACHE_HOME"],
os.environ["HF_HOME"],
os.environ["HF_HUB_CACHE"],
os.environ["TRANSFORMERS_CACHE"],
os.environ["TORCH_HOME"],
os.environ["MPLCONFIGDIR"],
os.environ["STREAMLIT_HOME"],
]:
os.makedirs(path, exist_ok=True)
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from moviepy import VideoFileClip, AudioFileClip, CompositeAudioClip
import whisper
from pydub import AudioSegment
import requests
import shutil
import yt_dlp
from openai import AzureOpenAI
from dotenv import load_dotenv
import ast
import time
import sys
import torch
import pandas as pd
import streamlit as st
import json
print("Streamlit config dir:", os.environ.get("STREAMLIT_CONFIG_DIR"))
device = "cuda" if torch.cuda.is_available() else "cpu"
st.set_page_config(page_title=f"DubSync ({device})", layout="wide")
os.makedirs(os.environ["XDG_CACHE_HOME"], exist_ok=True)
temp_folder = os.path.join("/tmp","resources")
demo_dir = "demos"
sample_output_dir = os.path.join(temp_folder,"sample_outputs")
cropped_audio_dir = os.path.join(temp_folder, "cropped_audio")
cloned_audio_dir = os.path.join(temp_folder, "cloned_audio")
os.makedirs(temp_folder, exist_ok=True)
os.makedirs(cropped_audio_dir, exist_ok=True)
os.makedirs(cloned_audio_dir, exist_ok=True)
os.makedirs(sample_output_dir, exist_ok=True)
# Load environment variables from .env file
load_dotenv()
# Read the API key from the .env file
api_type = "azure"
api_key = os.getenv("OPENAI_API_KEY")
api_base = os.getenv("OPENAI_API_BASE")
api_version = os.getenv("OPENAI_API_VERSION") # "2025-01-01-preview" # or latest supported version
DEPLOYMENT_NAME = os.getenv("OPENAI_DEPLOYMENT_NAME") # "gpt-4-dubbing" # Your Azure OpenAI deployment name
# Create a client using Azure credentials
client = AzureOpenAI(
api_key=api_key,
api_version=api_version,
azure_endpoint=api_base,
)
def clean_up():
shutil.rmtree(temp_folder)
st.session_state.is_processing = False
def extract_audio_from_video(video_path):
with st.spinner("🎬 Extracting audio from the video..."):
try:
video = VideoFileClip(video_path)
audio_path = os.path.join(temp_folder, "extracted_audio.wav")
video.audio.write_audiofile(audio_path)
return audio_path
except Exception as e:
st.error(f"Failed to extract audio: {e}")
clean_up()
sys.exit(1)
return None
def separate_audio_layers(audio_path):
with st.spinner("🎢 Separating audio layers..."):
output_dir = os.path.join(temp_folder, "demucs_output")
subprocess.run(["demucs", "-o", output_dir,
f"--device={device}", audio_path], capture_output=True, text=True)
print(output_dir)
return output_dir
def is_model_cached(model_name):
cache_dir = os.path.expanduser("~/.cache/whisper")
model_files = [f"{model_name}.pt"]
return all(os.path.exists(os.path.join(cache_dir, f)) for f in model_files)
def transcribe_audio(audio_path):
# TODO: Add support for multiple languages
# TODO: Try faster_whisper for faster transcription
if not is_model_cached(selected_model):
message = f"Model {selected_model} not found in cache. It will be downloaded."
else:
message = f"Model {selected_model} is already cached. Transcribing vocals..."
with st.spinner(message):
model = whisper.load_model(selected_model, device=device)
result = model.transcribe(
audio_path, language=input_language_value, word_timestamps=False, fp16=False, condition_on_previous_text=True)
# print(f"[transcribe_audio] {result}", flush=True)
return result["segments"]
def generate_audio_from_segments(segments, original_audio_path):
with st.spinner("🌍 Generating audio from segments..."):
translated_audio = os.path.join(temp_folder, "translated_audio.wav")
final_audio = AudioSegment.silent(duration=0)
last_end_time = 0
for idx, segment in enumerate(segments):
start_ms = segment["start"]*1000
end_ms = segment["end"]*1000
duration_ms = (end_ms - start_ms)
audio_segment_path = os.path.join(
cloned_audio_dir, f"output_{idx}.wav")
spoken = AudioSegment.from_file(audio_segment_path)
# Add silence before this segment if needed
gap_duration = start_ms - int(last_end_time * 1000)
if gap_duration > 0:
original_audio = AudioSegment.from_file(original_audio_path)
gap_start = int(last_end_time * 1000)
gap_end = int(start_ms)
gap_audio = original_audio[gap_start:gap_end]
final_audio += gap_audio
# final_audio += AudioSegment.silent(duration=gap_duration)
# Clip or pad the TTS output to exactly fit segment duration
if len(spoken) > duration_ms:
spoken = spoken[:duration_ms]
else:
spoken += AudioSegment.silent(
duration=duration_ms - len(spoken))
# Add processed speech
final_audio += spoken
last_end_time = segment["end"]
print(f"Segment {idx+1}/{len(segments)} | {segment['start']}", flush=True)
# 5. Save final audio
final_audio.export(translated_audio, format="wav")
return translated_audio
def clean_response_text(text):
text = text.strip()
if text.startswith("```json") and text.endswith("```"):
text = "\n".join(text.strip("`").split("\n")[1:])
unwanted_prefix = f"Here's the rewritten script for the dubbing:"
if text.startswith(unwanted_prefix):
text = text[len(unwanted_prefix):].strip()
return text
def translate_with_gpt(segments, source_lang="ja", target_lang="en"):
with st.spinner(f"Translating segments using AI..."):
# Rewrite the translation using GPT
input_segments = []
for segment in segments:
input_segments.append({
"id": segment["id"],
"text": segment["text"],
"start": segment["start"],
"end": segment["end"]
})
prompt = f"""
You are an expert anime dubbing scriptwriter.
Your job is to take a list of {input_segments} from a {input_language.lower()} anime and rewrite the {output_language} translations to sound natural, expressive, and emotionally aligned with how the lines would be spoken in an English dub.
Each segment contains:
- `start` and `end` timestamps
- `id`: The segment ID
- `text`: The original {input_language.lower()} line
Your goal:
- Translate the {input_language.lower()} line into {output_language.lower()}.
- Make small natural rewrites to the {output_language.lower()} translation.
- Add filler sounds like "uh", "hmm", "ahh", or stuttering where it fits the character's tone.
- Preserve emotional nuance (anger, sarcasm, nervousness, etc).
- Keep the rewritten line short enough to be spoken within the original segment’s timing.
Always consider **previous lines** and **what comes next**, and ensure the dialogue flows naturally across segments.
Return the rewritten translation line and id in json format, and make sure to start the respond with 'Here's the rewritten script for the dubbing:'
"""
response = client.chat.completions.create(
model=DEPLOYMENT_NAME,
messages=[
{"role": "system",
"content": "You are a professional anime dubbing scriptwriter."},
{"role": "user", "content": prompt}
],
temperature=0.7
)
response_segment = clean_response_text(
response.choices[0].message.content.strip())
response_segment = clean_response_text(response_segment)
print(f"translate_with_gpt response => \n{response_segment}", flush=True)
print("Streamlit config dir:", os.environ.get("STREAMLIT_CONFIG_DIR"), flush=True)
ai_segments = ast.literal_eval(str(response_segment))
# Map ai_segments by id for quick lookup
ai_segments_dict = {s["id"]: s for s in ai_segments}
# Add translated text into segments
for segment in segments:
ai_seg = ai_segments_dict.get(segment["id"])
if ai_seg and "translation" in ai_seg:
segment["translation"] = ai_seg["translation"]
elif ai_seg and "text" in ai_seg: # fallback if key is 'text'
segment["translation"] = ai_seg["text"]
return segments
def run_f5_tts_infer(model, ref_audio, ref_text, gen_text, output_dir=None, output_file=None):
# TODO: Add multilingual support
command = [
"f5-tts_infer-cli",
"--model", model,
"--ref_audio", ref_audio,
"--ref_text", ref_text,
"--gen_text", gen_text,
"--speed", "0.8"
]
if output_file:
command += ["--output_file", output_file]
if output_dir:
command += ["--output_dir", output_dir]
try:
result = subprocess.run(
command, capture_output=True, text=True, check=True)
print("Command output:", result.stdout, flush=True)
return result.stdout
except subprocess.CalledProcessError as e:
print("Error:", e.stderr, flush=True)
return None
def list_all_nested_contents(base_path):
print(f"πŸ“‚ Listing all folders and files in: {base_path}\n")
for root, dirs, files in os.walk(base_path):
level = root.replace(base_path, "").count(os.sep)
indent = " " * level
folder_name = os.path.basename(root)
print(f"{indent}πŸ“ {folder_name}", flush=True)
sub_indent = " " * (level + 1)
for f in files:
print(f"{sub_indent}πŸ“„ {f}", flush=True)
def voice_cloning(segments, audio_path, max_threads=2):
audio = AudioSegment.from_file(audio_path)
with st.spinner("πŸ”Š Cropping audio segments..."):
for segment in segments:
start_ms = int(segment["start"] * 1000)
end_ms = int(segment["end"] * 1000)
cropped = audio[start_ms:end_ms]
cropped.export(os.path.join(cropped_audio_dir,
f"cropped_{segment['id']}.wav"), format="wav")
print(f"cropped_{segment['id']}.wav", flush=True)
list_all_nested_contents("/tmp")
with st.spinner("πŸ€– Cloning voice..."):
try:
print("[Voice Cloning] - Initiated", flush=True)
# with ThreadPoolExecutor(max_workers=max_threads) as executor:
# print("[Voice Cloning] - Thread Initiated", flush=True)
# futures = []
for segment in segments:
ref_audio = os.path.join(
cropped_audio_dir, f"cropped_{segment['id']}.wav")
ref_text = segment["text"]
gen_text = segment["translation"]
# Check if ref_text and gen_text are not empty
if not ref_text or not gen_text:
print(f"Skipping segment {segment} due to empty ref_text or gen_text")
continue
run_f5_tts_infer("F5TTS_v1_Base", ref_audio, ref_text, gen_text, output_dir=cloned_audio_dir, output_file=f"output_{segment['id']}.wav")
# if gen_text:
# future = executor.submit(run_f5_tts_infer, "F5TTS_v1_Base",
# ref_audio, ref_text, gen_text,
# output_dir=cloned_audio_dir,
# output_file=f"output_{segment['id']}.wav")
# futures.append(future)
# for future in as_completed(futures):
# output = future.result()
# print(f"F5 TTS Infer output: {output}", flush=True)
print("[Done] Cloning voice", flush=True)
except Exception as e:
print(f"Voice cloning failed: {e}")
st.error(f"Voice cloning failed: {e}")
clean_up()
sys.exit(1)
return None
def set_processing(value=True):
st.session_state.is_processing = value
def generate_srt_subtitles(segments, output_path, subtitle_type="translation"):
"""
Generate SRT subtitle file from segments
subtitle_type: "translation" for dubbed text, "original" for original text
"""
try:
with open(output_path, 'w', encoding='utf-8') as srt_file:
for i, segment in enumerate(segments, 1):
start_time = segment["start"]
end_time = segment["end"]
# Convert seconds to SRT time format (HH:MM:SS,mmm)
start_srt = seconds_to_srt_time(start_time)
end_srt = seconds_to_srt_time(end_time)
# Choose text based on subtitle type
if subtitle_type == "translation" and "translation" in segment:
text = segment["translation"]
elif "text" in segment:
text = segment["text"]
else:
text = "..." # Fallback for empty segments
# Clean up text for SRT format
text = text.replace('\n', ' ').strip()
if not text:
continue # Skip empty segments
# Write SRT entry
srt_file.write(f"{i}\n")
srt_file.write(f"{start_srt} --> {end_srt}\n")
srt_file.write(f"{text}\n\n")
return output_path
except Exception as e:
st.error(f"Failed to generate subtitles: {e}")
return None
def seconds_to_srt_time(seconds):
"""Convert seconds to SRT time format (HH:MM:SS,mmm)"""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
milliseconds = int((seconds % 1) * 1000)
return f"{hours:02d}:{minutes:02d}:{secs:02d},{milliseconds:03d}"
def load_demo_data():
"""Load demo data from demo.json file"""
demo_json_path = os.path.join(demo_dir, "demo.json")
try:
with open(demo_json_path, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
st.error(f"Demo file not found: {demo_json_path}")
return []
except json.JSONDecodeError as e:
st.error(f"Error reading demo file: {e}")
return []
def clean_audio_for_transcription(audio_path, output_path=None, noise_reduction_strength=0.8, enable_cleaning=True):
"""
Clean and preprocess audio for better Whisper transcription quality.
Args:
audio_path: Path to input audio file
output_path: Path for cleaned audio output (optional)
noise_reduction_strength: Strength of noise reduction (0.1 to 1.0)
enable_cleaning: Whether to apply cleaning or return original
Returns:
Path to cleaned audio file
"""
if not enable_cleaning:
return audio_path
with st.spinner("🧹 Cleaning audio for better transcription..."):
try:
# Load audio with librosa for better preprocessing
audio_data, sample_rate = librosa.load(audio_path, sr=16000) # Whisper prefers 16kHz
# 1. Noise reduction using spectral gating
# First, estimate noise from the first 1 second (assuming it contains background noise)
noise_sample_duration = min(1.0, len(audio_data) / sample_rate * 0.1) # 10% or 1 sec max
noise_sample_size = int(noise_sample_duration * sample_rate)
if len(audio_data) > noise_sample_size:
# Use noisereduce if available, otherwise use simple high-pass filter
try:
import noisereduce as nr
audio_data = nr.reduce_noise(
y=audio_data,
sr=sample_rate,
stationary=False, # Non-stationary noise reduction
prop_decrease=noise_reduction_strength # Use configurable strength
)
except ImportError:
# Fallback: Simple high-pass filter to remove low-frequency noise
nyquist = sample_rate * 0.5
low_cutoff = 80 # Remove frequencies below 80Hz
low_cutoff_normalized = low_cutoff / nyquist
b, a = butter(5, low_cutoff_normalized, btype='high')
audio_data = filtfilt(b, a, audio_data)
print("noisereduce not available, using high-pass filter instead")
# 2. Normalize audio levels
# RMS normalization to -20dB to prevent clipping while maintaining dynamics
rms = np.sqrt(np.mean(audio_data**2))
if rms > 0:
target_rms = 10**(-20/20) # -20dB
audio_data = audio_data * (target_rms / rms)
# 3. Remove silence and very quiet parts (optional, be careful not to remove speech pauses)
# Use librosa's voice activity detection
intervals = librosa.effects.split(
audio_data,
top_db=30, # Threshold for silence detection
frame_length=2048,
hop_length=512
)
# 4. Apply gentle compression to even out volume levels
# Simple soft limiting
threshold = 0.95
audio_data = np.where(
np.abs(audio_data) > threshold,
np.sign(audio_data) * (threshold + (np.abs(audio_data) - threshold) * 0.1),
audio_data
)
# 5. Final normalization to prevent clipping
max_amplitude = np.max(np.abs(audio_data))
if max_amplitude > 0.95:
audio_data = audio_data * (0.95 / max_amplitude)
# Save cleaned audio
if output_path is None:
output_path = audio_path.replace('.wav', '_cleaned.wav')
# Convert back to pydub AudioSegment for consistency with your existing code
audio_int16 = (audio_data * 32767).astype(np.int16)
cleaned_audio = AudioSegment(
audio_int16.tobytes(),
frame_rate=sample_rate,
sample_width=2, # 16-bit
channels=1
)
cleaned_audio.export(output_path, format="wav")
print(f"Audio cleaned and saved to: {output_path}")
return output_path
except Exception as e:
st.warning(f"Audio cleaning failed, using original audio: {e}")
return audio_path # Return original if cleaning fails
def enhance_vocals_separation(demucs_output_dir):
"""
Further enhance the separated vocals for better transcription.
"""
try:
separated_dir = os.path.join(
demucs_output_dir, "htdemucs",
os.path.splitext(os.path.basename(audio_path))[0]
)
vocals_path = os.path.join(separated_dir, "vocals.wav")
if os.path.exists(vocals_path):
enhanced_vocals_path = vocals_path.replace('.wav', '_enhanced.wav')
return clean_audio_for_transcription(vocals_path, enhanced_vocals_path)
else:
return vocals_path
except Exception as e:
st.warning(f"Vocal enhancement failed: {e}")
return vocals_path
def analyze_audio_quality(audio_path, label="Audio"):
"""
Analyze audio quality metrics for debugging and optimization.
"""
try:
import librosa
audio_data, sample_rate = librosa.load(audio_path, sr=None)
# Calculate basic metrics
rms = np.sqrt(np.mean(audio_data**2))
peak = np.max(np.abs(audio_data))
snr_estimate = 20 * np.log10(rms / (np.std(audio_data) + 1e-10))
# Dynamic range
dynamic_range = 20 * np.log10(peak / (rms + 1e-10))
metrics = {
"Sample Rate": f"{sample_rate} Hz",
"Duration": f"{len(audio_data) / sample_rate:.2f} seconds",
"RMS Level": f"{20 * np.log10(rms + 1e-10):.2f} dB",
"Peak Level": f"{20 * np.log10(peak + 1e-10):.2f} dB",
"Estimated SNR": f"{snr_estimate:.2f} dB",
"Dynamic Range": f"{dynamic_range:.2f} dB"
};
st.write(f"**{label} Quality Metrics:**");
for metric, value in metrics.items():
st.write(f"- {metric}: {value}");
return metrics
except Exception as e:
st.warning(f"Could not analyze audio quality: {e}")
return {}
# Main Streamlit app
if __name__ == "__main__":
if "is_processing" not in st.session_state:
st.session_state.is_processing = False
with st.sidebar:
st.write("Language Options")
input_languages = [
("Japanese", "ja"), ("English", "en"), ("Chinese", "zh"),
("Korean", "ko"), ("Spanish", "es"), ("French", "fr"),
("German", "de"), ("Italian", "it"), ("Portuguese", "pt"),
("Russian", "ru"), ("Arabic", "ar"), ("Hindi", "hi")
]
output_languages = [("English", "en"), ("Chinese", "zh")]
input_language = st.selectbox(
"Select the language of the original video",
options=[label for label, value in input_languages],
index=0,
disabled=st.session_state.is_processing,
)
input_language_value = dict(input_languages)[input_language]
st.write("Output Language")
output_language = st.selectbox(
"Select the language of the dubbed video",
options=[label for label, value in output_languages],
index=0,
disabled=st.session_state.is_processing,
)
output_language_value = dict(output_languages)[output_language]
st.divider()
# File uploader for MP4 video
st.write("Video Input Options")
# video_file = st.file_uploader("Upload a MP4 video file", type=[
# "mp4"], disabled=st.session_state.is_processing, on_change=set_processing, args=(True,))
video_file = None
if video_file is not None:
# Write uploaded file to a stable location
save_path = os.path.join("/tmp", video_file.name)
with open(save_path, "wb") as f:
f.write(video_file.read())
st.video(save_path) # Use the saved file path, not internal Streamlit ID
st.success(f"Saved and loaded video from: {save_path}")
# st.write("OR")
video_url = st.text_input(
"Enter the URL of a MP4 video (Youtube or other)",
disabled=st.session_state.is_processing,
on_change=set_processing, # Will pass value below
args=(True,)
)
st.divider()
st.write("Transcription Options")
whisper_models = ["tiny", "base", "small",
"medium", "large", "large-v2", "large-v3", "turbo"]
selected_model = st.selectbox(
"Select Whisper model for transcription",
options=whisper_models,
index=7, # Default to 'large'
disabled=st.session_state.is_processing,
help="Larger models provide better accuracy but are slower."
)
st.divider()
st.write("Audio Enhancement Options")
enable_audio_cleaning = st.checkbox(
"Enable advanced audio cleaning",
value=True,
disabled=st.session_state.is_processing,
help="Applies noise reduction, normalization, and other enhancements for better transcription"
)
if enable_audio_cleaning:
noise_reduction_strength = st.slider(
"Noise reduction strength",
min_value=0.1,
max_value=1.0,
value=0.8,
step=0.1,
disabled=st.session_state.is_processing,
help="Higher values remove more noise but may affect speech quality"
)
show_audio_metrics = st.checkbox(
"Show audio quality metrics",
value=False,
disabled=st.session_state.is_processing,
help="Display technical metrics about audio quality before and after cleaning"
)
st.divider()
# Show current device info
st.info(f"πŸ–₯️ Using device: **{device.upper()}**" +
(" (GPU acceleration enabled)" if device == "cuda" else " (CPU only)"))
if video_url:
if "youtube.com" in video_url or "youtu.be" in video_url:
with st.spinner("Downloading video from URL..."):
try:
# Use yt-dlp for YouTube links
ydl_opts = {
'format': 'bestvideo+bestaudio/best',
# Output to stdout
'outtmpl': os.path.join(temp_folder, "uploaded_video.mp4"),
'merge_output_format': 'mp4', # Merge video and audio into mp4
'quiet': False, # Suppress output
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(video_url, download=True)
video_file = ydl.prepare_filename(info).replace(".webm", ".mp4").replace(
# os.path.join(temp_folder, "uploaded_video.mp4")
".mkv", ".mp4")
st.success("YouTube video downloaded successfully! " +
ydl.extract_info(video_url, download=False)['title'])
except Exception as e:
st.error(f"An error occurred: {e}")
else:
try:
with st.spinner("Downloading video from URL..."):
response = requests.get(video_url, stream=True)
if response.status_code == 200:
uploaded_video_path = os.path.join(
temp_folder, "uploaded_video.mp4")
with open(uploaded_video_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
video_file = open(uploaded_video_path, "rb")
st.success("Video downloaded successfully!")
else:
st.error(
"Failed to download video. Please check the URL.")
except Exception as e:
st.error(f"An error occurred: {e}")
if video_file is not None:
st.session_state.is_processing = True
start_time = time.time()
input_video_col, output_video_col = st.columns(2)
st.divider()
with input_video_col:
st.subheader("Input Video")
st.video(video_file, muted=False)
uploaded_video_path = os.path.join(temp_folder, "uploaded_video.mp4")
if not os.path.exists(uploaded_video_path):
with open(uploaded_video_path, "wb") as f:
f.write(video_file.read())
# Extract audio from the video
audio_path = extract_audio_from_video(uploaded_video_path)
# Run Demucs to separate background audio
with output_video_col:
output_dir = separate_audio_layers(audio_path)
# Display the separated audio files
separated_dir = os.path.join(
output_dir, "htdemucs", os.path.splitext(os.path.basename(audio_path))[0])
vocals_path = os.path.join(separated_dir, "vocals.wav")
bg_music_path = os.path.join(separated_dir, "other.wav")
bass_path = os.path.join(separated_dir, "bass.wav")
drums_path = os.path.join(separated_dir, "drums.wav")
with output_video_col:
segments = transcribe_audio(vocals_path)
with output_video_col:
# Translate the segments using AI
segments = translate_with_gpt(segments)
filtered_segments = [
{
"id": s.get("id"),
"start (sec)": s.get("start"),
"end (sec)": s.get("end"),
"translation": s.get("translation"),
"text": s.get("text"),
"no_speech_prob": s.get('no_speech_prob', 0),
"avg_logprob": s.get("avg_logprob", 0),
}
for s in segments
]
df = pd.DataFrame(filtered_segments)
st.subheader("Transcription")
st.data_editor(df, use_container_width=True,
hide_index=True, num_rows="dynamic", disabled=True)
segments_csv_path = os.path.join(
sample_output_dir, f"output_segments_{selected_model}_{output_language.lower()}.csv")
df.to_csv(segments_csv_path, index=False, encoding="utf-8")
with output_video_col:
voice_cloning(segments, vocals_path)
translated_audio = generate_audio_from_segments(segments, audio_path)
video = VideoFileClip(uploaded_video_path)
translated_audio = AudioFileClip(translated_audio)
bg_audio = AudioFileClip(bg_music_path)
drums_audio = AudioFileClip(drums_path)
bass_audio = AudioFileClip(bass_path)
with output_video_col:
with st.spinner("🎬 Generating dubbed video..."):
# Combine audio tracks
combined_audio = CompositeAudioClip(
[translated_audio, bg_audio, drums_audio, bass_audio])
video = video.with_audio(combined_audio)
dubbed_video_file_name = f"dubbed_video_{selected_model}_{output_language.lower()}.mp4"
subtitle_path = os.path.join(
sample_output_dir, f"dubbed_video_{selected_model}_{output_language.lower()}.srt")
# Embed subtitles in the video
dubbed_video_path = os.path.join(
sample_output_dir, f"dubbed_video_{selected_model}_{output_language.lower()}.mp4")
generate_srt_subtitles(segments, subtitle_path)
video.write_videofile(
dubbed_video_path,
codec="libx264",
audio_codec="aac",
temp_audiofile=os.path.join(
sample_output_dir, "temp-audio.m4a"), # explicitly inside /tmp
remove_temp=True
)
st.subheader("Dubbed video")
st.video(dubbed_video_path, subtitles=subtitle_path)
end_time = time.time()
# # Calculate the elapsed time
elapsed_time = end_time - start_time
elapsed_minutes = round(elapsed_time / 60, 2)
# Display the elapsed time
st.success(f"Processing completed in {elapsed_minutes} mins.")
video_file = None
# Clean up temporary files
clean_up()
else:
st.title("🎬 DubSync - AI-Powered Video Dubbing")
st.markdown("""
Welcome to DubSync! Transform your videos with AI-powered dubbing that preserves voice characteristics and emotions.
πŸ“– **[Read the Complete Documentation & Setup Guide](https://github.com/gholapeajinkya/DubSync/blob/main/README.md#dubsync)** - Learn about features, installation, requirements, and how it works.
""")
st.subheader("πŸ“Ί Demo Videos")
# Load demo data from JSON
demo_data = load_demo_data()
if demo_data:
for i, demo in enumerate(demo_data):
# Create columns for videos
if demo['videos']:
with st.expander(f"Demo {i + 1}", expanded=True):
demo_videos = st.columns(len(demo['videos']))
for j, video_info in enumerate(demo['videos']):
video_id = video_info['id']
video_name = video_info['name']
# Construct video path based on demo structure
video_path = os.path.join(
demo_dir, demo['id'], 'videos', video_id, 'video.mp4')
with demo_videos[j]:
st.markdown(f"**{video_name}**")
if os.path.exists(video_path):
st.video(video_path)
else:
st.info("Video will appear here")
# Create columns for transcriptions if they exist
videos_with_transcriptions = [v for v in demo['videos'] if 'transcription_url' in v]
if videos_with_transcriptions:
st.markdown("### πŸ“Š Transcription Results")
transcription_cols = st.columns(len(videos_with_transcriptions))
for k, video_info in enumerate(videos_with_transcriptions):
video_id = video_info['id']
video_name = video_info['name']
# Construct transcription path
transcription_path = os.path.join(
demo_dir, demo['id'], 'videos', video_id, 'transcription.csv')
with transcription_cols[k]:
st.markdown(f"**{video_name} - Transcription**")
if os.path.exists(transcription_path):
try:
transcription_df = pd.read_csv(transcription_path)
st.dataframe(transcription_df, use_container_width=True, hide_index=True, height=400)
except Exception as e:
st.error(f"Error loading transcription: {e}")
else:
st.info("Transcription will appear here")