Spaces:
Sleeping
Sleeping
| 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("<h1 class='glow-title'>Deepgram SRT Subtitles</h1>") | |
| gr.HTML("<p class='sub-title'>Generate and translate SRT subtitles with state-of-the-art accuracy</p>") | |
| # 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("<div class='footer-text'>Deepgram SRT Subtitle Tool • Powered by Deepgram & DeepL</div>") | |
| if __name__ == "__main__": | |
| demo.queue() | |
| demo.launch(server_name="0.0.0.0", server_port=7860, css=custom_css) | |