import os import tempfile import re import httpx from datetime import datetime import gradio as gr from dotenv import load_dotenv # Load local environment variables load_dotenv() # Import core logic from main.py from main import cleanup_srt_punctuation, translate_srt_content from deepgram import DeepgramClient, PrerecordedOptions from deepgram_captions import DeepgramConverter, srt from moviepy.video.io.VideoFileClip import VideoFileClip # CSS styling for a premium glassmorphism dark-mode look custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;800&family=Inter:wght@400;500;600&display=swap'); body, .gradio-container { font-family: 'Inter', sans-serif !important; background: #0b0f19 !important; } /* Main card styling */ .glass-container { background: rgba(17, 24, 39, 0.7) !important; backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.08) !important; border-radius: 20px !important; padding: 30px !important; box-shadow: 0 10px 40px 0 rgba(0, 0, 0, 0.5) !important; } /* Glowing text title */ .glow-title { background: linear-gradient(135deg, #a5b4fc 0%, #c084fc 50%, #818cf8 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; text-align: center; font-size: 2.8rem; margin-bottom: 8px; font-family: 'Outfit', sans-serif; letter-spacing: -0.5px; } .sub-title { color: #9ca3af; text-align: center; font-size: 1.15rem; margin-bottom: 30px; font-family: 'Inter', sans-serif; } /* Styled primary action button */ .action-btn { background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 100%) !important; color: white !important; border: none !important; font-weight: 600 !important; border-radius: 12px !important; padding: 12px 24px !important; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; box-shadow: 0 4px 20px rgba(99, 102, 241, 0.3) !important; } .action-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(99, 102, 241, 0.5) !important; opacity: 0.95; } .action-btn:active { transform: translateY(1px); } /* Inputs styling */ input, textarea, select { background: #1f2937 !important; border: 1px solid #374151 !important; border-radius: 8px !important; color: #f3f4f6 !important; } input:focus, textarea:focus, select:focus { border-color: #818cf8 !important; } /* Tab styling */ .tabs { border-bottom: 2px solid #1f2937 !important; margin-bottom: 20px; } .tab-nav button { font-family: 'Outfit', sans-serif; font-size: 1.05rem !important; color: #9ca3af !important; padding: 10px 20px !important; } .tab-nav button.selected { color: #818cf8 !important; border-bottom: 2px solid #818cf8 !important; } /* Footer styling */ .footer-text { text-align: center; color: #4b5563; font-size: 0.85rem; margin-top: 40px; } """ def extract_audio(video_path): """Extract audio track from video file using MoviePy.""" temp_dir = tempfile.gettempdir() audio_path = os.path.join(temp_dir, f"extracted_{os.path.basename(video_path)}.mp3") try: with VideoFileClip(video_path) as video_clip: audio_clip = video_clip.audio # Write audio without verbose logging audio_clip.write_audiofile(audio_path, logger=None) return audio_path except Exception as e: raise gr.Error(f"Failed to extract audio from video: {str(e)}") def process_transcribe( file_path, model, language, diarize, translate_to, dg_key_override, dl_key_override ): """Core transcription and translation pipeline for audio/video input.""" if not file_path: raise gr.Error("Please upload a file first.") # Resolve Deepgram API Key dg_key = dg_key_override.strip() if dg_key_override else os.getenv("DEEPGRAM_API_KEY") if not dg_key: raise gr.Error("Deepgram API Key is required. Please provide it in the UI or environment.") # Resolve DeepL API Key (if translation requested) dl_key = None if translate_to: dl_key = dl_key_override.strip() if dl_key_override else (os.getenv("DEEPL_API_KEY") or os.getenv("DEEPL_AUTH_KEY")) if not dl_key: raise gr.Error("DeepL API Key is required for translation. Please provide it in the UI or environment.") # Check extension to determine if audio extraction is needed _, ext = os.path.splitext(file_path.lower()) is_audio = ext in {'.mp3', '.wav', '.m4a', '.flac', '.ogg', '.aac', '.wma', '.opus', '.webm', '.m4b', '.mp4a', '.aiff', '.aif', '.mp2'} audio_filepath = file_path temp_audio_to_cleanup = None if not is_audio: gr.Info("Video file detected. Extracting audio track...") audio_filepath = extract_audio(file_path) temp_audio_to_cleanup = audio_filepath try: # Read the file data with open(audio_filepath, "rb") as file: buffer_data = file.read() payload = {"buffer": buffer_data} # Configure Deepgram options options_dict = { "model": model, "smart_format": True, "utterances": True, "punctuate": True, "diarize": diarize, } if language: if language.lower() in {"auto", "detect"}: options_dict["detect_language"] = True else: options_dict["language"] = language options = PrerecordedOptions(**options_dict) deepgram = DeepgramClient(dg_key) gr.Info("Transcribing audio via Deepgram...") response = deepgram.listen.rest.v("1").transcribe_file( payload, options, timeout=httpx.Timeout(30000.0, connect=10.0) ) # Process words check has_words = False try: if hasattr(response, 'results') and response.results: if response.results.channels and response.results.channels[0].alternatives: if response.results.channels[0].alternatives[0].words: has_words = True except Exception: pass if not has_words: original_srt = "" gr.Warning("No speech detected in the audio file.") else: transcription = DeepgramConverter(response) original_srt = srt(transcription) original_srt = cleanup_srt_punctuation(original_srt) # Write original SRT to temp file temp_dir = tempfile.gettempdir() timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") orig_file_path = os.path.join(temp_dir, f"transcription_{timestamp}.srt") with open(orig_file_path, "w", encoding="utf-8") as f: f.write(original_srt) translated_srt = "" trans_file_path = None # Handle translation if requested if translate_to and original_srt: gr.Info(f"Translating subtitles to {translate_to} using DeepL...") target_lang = translate_to.upper() if target_lang == "EN": target_lang = "EN-US" elif target_lang == "PT": target_lang = "PT-BR" translated_srt = translate_srt_content(original_srt, dl_key, target_lang) translated_srt = cleanup_srt_punctuation(translated_srt) trans_file_path = os.path.join(temp_dir, f"transcription_{timestamp}.{translate_to.lower()}.srt") with open(trans_file_path, "w", encoding="utf-8") as f: f.write(translated_srt) return original_srt, orig_file_path, translated_srt, trans_file_path except Exception as e: raise gr.Error(f"An error occurred: {str(e)}") finally: # Cleanup temporary extracted audio if temp_audio_to_cleanup and os.path.exists(temp_audio_to_cleanup): try: os.remove(temp_audio_to_cleanup) except Exception: pass def process_translate_srt(srt_file, translate_to, dl_key_override): """Translate an existing SRT file.""" if not srt_file: raise gr.Error("Please upload an SRT file.") dl_key = dl_key_override.strip() if dl_key_override else (os.getenv("DEEPL_API_KEY") or os.getenv("DEEPL_AUTH_KEY")) if not dl_key: raise gr.Error("DeepL API Key is required. Please provide it in the UI or environment.") try: with open(srt_file.name, "r", encoding="utf-8") as f: original_content = f.read() target_lang = translate_to.upper() if target_lang == "EN": target_lang = "EN-US" elif target_lang == "PT": target_lang = "PT-BR" gr.Info(f"Translating SRT file to {translate_to} using DeepL...") translated_content = translate_srt_content(original_content, dl_key, target_lang) cleaned_content = cleanup_srt_punctuation(translated_content) # Write to temp file temp_dir = tempfile.gettempdir() timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") translated_path = os.path.join(temp_dir, f"translated_{timestamp}.srt") with open(translated_path, "w", encoding="utf-8") as f: f.write(cleaned_content) return cleaned_content, translated_path except Exception as e: raise gr.Error(f"Translation error: {str(e)}") # ------------------ Build Interface ------------------ # Supported languages list language_choices = [ ("Auto Detect", "auto"), ("English", "en"), ("Korean", "ko"), ("Spanish", "es"), ("French", "fr"), ("German", "de"), ("Italian", "it"), ("Japanese", "ja"), ("Chinese", "zh"), ("Portuguese", "pt"), ] translation_choices = [ ("None", ""), ("Korean", "ko"), ("English", "en"), ("Japanese", "ja"), ("Spanish", "es"), ("French", "fr"), ("German", "de"), ("Italian", "it"), ("Chinese", "zh"), ("Portuguese", "pt"), ] model_choices = [ ("Nova-3 (Latest / Recommended)", "nova-3"), ("Nova-2 (Fast & Accurate)", "nova-2"), ("Enhanced", "enhanced"), ("Base", "base"), ] with gr.Blocks(title="Deepgram SRT Generator & Translator") as demo: with gr.Column(elem_classes="glass-container"): gr.HTML("

Deepgram SRT Subtitles

") gr.HTML("

Generate and translate SRT subtitles with state-of-the-art accuracy

") # API Keys Accordion (Collapsible for cleaner layout) with gr.Accordion("🔑 API Credentials (Optional Override)", open=False): with gr.Row(): dg_key_input = gr.Textbox( label="Deepgram API Key", placeholder="Enter key to override DEEPGRAM_API_KEY environment variable", type="password" ) dl_key_input = gr.Textbox( label="DeepL API Key", placeholder="Enter key to override DEEPL_API_KEY environment variable", type="password" ) with gr.Tabs(elem_classes="tabs"): # --- Tab 1: Video Transcription --- with gr.TabItem("🎥 Transcribe Video"): with gr.Row(): with gr.Column(scale=1): video_input = gr.Video(label="Upload Video", sources=["upload"]) with gr.Row(): video_model = gr.Dropdown( choices=model_choices, value="nova-3", label="Deepgram Model" ) video_lang = gr.Dropdown( choices=language_choices, value="auto", label="Audio Language", allow_custom_value=True ) with gr.Row(): video_diarize = gr.Checkbox(label="Speaker Diarization", value=True) video_trans = gr.Dropdown( choices=translation_choices, value="", label="Translate Subtitles to (DeepL)" ) video_btn = gr.Button("Generate Subtitles", elem_classes="action-btn") with gr.Column(scale=1): with gr.Tabs(): with gr.TabItem("Original Subtitles"): video_original_srt = gr.Textbox(label="SRT Output", buttons=["copy"], lines=15) video_original_file = gr.File(label="Download original SRT") with gr.TabItem("Translated Subtitles"): video_translated_srt = gr.Textbox(label="Translated SRT Output", buttons=["copy"], lines=15) video_translated_file = gr.File(label="Download translated SRT") video_btn.click( fn=process_transcribe, inputs=[ video_input, video_model, video_lang, video_diarize, video_trans, dg_key_input, dl_key_input ], outputs=[ video_original_srt, video_original_file, video_translated_srt, video_translated_file ], api_name="transcribe_video" ) # --- Tab 2: Audio Transcription --- with gr.TabItem("🎵 Transcribe Audio"): with gr.Row(): with gr.Column(scale=1): audio_input = gr.Audio(label="Upload Audio", type="filepath", sources=["upload"]) with gr.Row(): audio_model = gr.Dropdown( choices=model_choices, value="nova-3", label="Deepgram Model" ) audio_lang = gr.Dropdown( choices=language_choices, value="auto", label="Audio Language", allow_custom_value=True ) with gr.Row(): audio_diarize = gr.Checkbox(label="Speaker Diarization", value=True) audio_trans = gr.Dropdown( choices=translation_choices, value="", label="Translate Subtitles to (DeepL)" ) audio_btn = gr.Button("Generate Subtitles", elem_classes="action-btn") with gr.Column(scale=1): with gr.Tabs(): with gr.TabItem("Original Subtitles"): audio_original_srt = gr.Textbox(label="SRT Output", buttons=["copy"], lines=15) audio_original_file = gr.File(label="Download original SRT") with gr.TabItem("Translated Subtitles"): audio_translated_srt = gr.Textbox(label="Translated SRT Output", buttons=["copy"], lines=15) audio_translated_file = gr.File(label="Download translated SRT") audio_btn.click( fn=process_transcribe, inputs=[ audio_input, audio_model, audio_lang, audio_diarize, audio_trans, dg_key_input, dl_key_input ], outputs=[ audio_original_srt, audio_original_file, audio_translated_srt, audio_translated_file ], api_name="transcribe_audio" ) # --- Tab 3: SRT Translation --- with gr.TabItem("📄 Translate SRT File"): with gr.Row(): with gr.Column(scale=1): srt_input = gr.File(label="Upload SRT File", file_types=[".srt"]) srt_trans_lang = gr.Dropdown( choices=translation_choices[1:], value="ko", label="Translate Subtitles to (DeepL)" ) srt_btn = gr.Button("Translate File", elem_classes="action-btn") with gr.Column(scale=1): srt_output_text = gr.Textbox(label="Translated SRT Output", buttons=["copy"], lines=15) srt_output_file = gr.File(label="Download translated SRT") srt_btn.click( fn=process_translate_srt, inputs=[ srt_input, srt_trans_lang, dl_key_input ], outputs=[ srt_output_text, srt_output_file ], api_name="translate_srt" ) gr.HTML("") if __name__ == "__main__": demo.queue() demo.launch(server_name="0.0.0.0", server_port=7860, css=custom_css)