🌍 Global Video Localizer
Break language barriers. Reach global audiences. One video, infinite possibilities.
Works completely free with open source models. Add your ElevenLabs key for premium voice quality.
""" Global Video Localizer Automated video localization using AI-powered transcription, translation, and voice synthesis. """ import gradio as gr from localizer_engine import ( process_video, validate_elevenlabs_api_key, ) def apply_gradio_patch(): """Apply workaround for Gradio's JSON schema parsing bug.""" import gradio_client.utils as gradio_utils original_get_type = gradio_utils.get_type original_json_schema_to_python_type = gradio_utils._json_schema_to_python_type def patched_get_type(schema): if not isinstance(schema, dict): return "any" try: return original_get_type(schema) except TypeError: return "any" def patched_json_schema_to_python_type(schema, defs): if not isinstance(schema, dict): return "Any" try: return original_json_schema_to_python_type(schema, defs) except TypeError: return "Any" gradio_utils.get_type = patched_get_type gradio_utils._json_schema_to_python_type = patched_json_schema_to_python_type import gradio_client.utils gradio_client.utils.get_type = patched_get_type gradio_client.utils._json_schema_to_python_type = patched_json_schema_to_python_type apply_gradio_patch() def localize_video(video_path, target_language, api_key=None, progress=gr.Progress(track_tqdm=True)): """Process video localization request (keys stay per-session and are not persisted).""" if not video_path: return None, "Please upload a video to get started.", "" key = api_key.strip() if api_key and api_key.strip() else None progress(0, desc="Queued...") try: output_path, original_text, translated_text = process_video( video_path, target_language, elevenlabs_api_key=key, progress_callback=progress, ) return output_path, original_text, translated_text except Exception as e: error_message = f"Processing failed: {str(e)}" return None, error_message, "" # Design System CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --peach: #ffad7a; --peach-dark: #e8935c; --lavender: #b8a9d9; --sky-blue: #7ACCFF; --bg-light: #f9fafb; --surface: #ffffff; --text-primary: #1f2937; --text-secondary: #4b5563; --text-muted: #6b7280; --border-default: #e5e7eb; --border-subtle: #f3f4f6; --accent: #ffad7a; --accent-hover: #e8935c; --accent-subtle: rgba(255, 173, 122, 0.1); --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); } body { background: var(--bg-light) !important; color: var(--text-primary) !important; font-family: 'Inter', 'Helvetica Neue', 'Segoe UI', system-ui, -apple-system, sans-serif !important; -webkit-font-smoothing: antialiased; font-weight: 400; letter-spacing: -0.01em; } .gradio-container { max-width: 100% !important; background: var(--bg-light) !important; font-family: 'Inter', 'Helvetica Neue', 'Segoe UI', system-ui, -apple-system, sans-serif !important; } .main-header { text-align: center; padding: 2.5rem 2rem; background: linear-gradient(135deg, var(--peach) 0%, var(--lavender) 50%, var(--sky-blue) 100%); border-radius: 20px; margin: 1rem; box-shadow: var(--shadow-lg), 0 0 30px rgba(255, 173, 122, 0.2); position: relative; overflow: hidden; } .main-header::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(ellipse at 30% 20%, rgba(255,255,255,0.35) 0%, transparent 50%); pointer-events: none; } .main-header h1 { font-size: 2.75rem; font-weight: 600; color: #ffffff; margin-bottom: 0.5rem; text-shadow: 0 2px 8px rgba(0,0,0,0.15); letter-spacing: -0.03em; position: relative; font-family: 'Inter', 'Helvetica Neue', system-ui, sans-serif; } .main-header h3 { color: rgba(255, 255, 255, 0.95); font-size: 1.1rem; font-weight: 450; position: relative; } .main-header p { color: rgba(255, 255, 255, 0.95); font-size: 1rem; font-weight: 400; position: relative; } input, select, textarea { background: var(--bg-light) !important; border: 1px solid var(--border-default) !important; color: var(--text-primary) !important; border-radius: 8px !important; transition: all 0.15s ease !important; font-family: 'Inter', sans-serif !important; } input:focus, select:focus, textarea:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 3px var(--accent-subtle) !important; outline: none !important; } button.primary, button[class*="primary"] { background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%) !important; color: #ffffff !important; font-weight: 600 !important; border: none !important; border-radius: 10px !important; padding: 0.75rem 1.5rem !important; transition: all 0.2s ease !important; box-shadow: 0 2px 8px rgba(255, 173, 122, 0.3) !important; font-family: 'Inter', sans-serif !important; } button.primary:hover, button[class*="primary"]:hover { background: linear-gradient(135deg, var(--accent-hover) 0%, #d67d45 100%) !important; transform: translateY(-1px) !important; box-shadow: 0 4px 16px rgba(255, 173, 122, 0.4) !important; } label { color: var(--text-secondary) !important; font-weight: 500 !important; font-size: 0.875rem !important; font-family: 'Inter', sans-serif !important; } .markdown-text h3, h3 { color: var(--text-primary) !important; font-weight: 600 !important; font-size: 1rem !important; margin-bottom: 0.5rem !important; font-family: 'Inter', sans-serif !important; } .markdown-text, .markdown-text p, .markdown-text span { color: var(--text-primary) !important; font-family: 'Inter', sans-serif !important; } .markdown-text strong { color: var(--text-primary) !important; font-weight: 600 !important; } .gr-video, .gr-image { border-radius: 12px !important; border: 1px solid var(--border-default) !important; box-shadow: var(--shadow-md) !important; background: var(--surface) !important; } .gr-video:hover, .gr-image:hover { border-color: var(--accent) !important; box-shadow: 0 4px 16px rgba(255, 173, 122, 0.2) !important; } .gr-textbox { background: var(--bg-light) !important; border: 1px solid var(--border-default) !important; border-radius: 8px !important; color: var(--text-primary) !important; font-family: 'Inter', sans-serif !important; } .gr-textbox:focus { border-color: var(--accent) !important; box-shadow: 0 0 0 3px var(--accent-subtle) !important; } .gr-dropdown { background: var(--bg-light) !important; border: 1px solid var(--border-default) !important; border-radius: 8px !important; color: var(--text-primary) !important; font-family: 'Inter', sans-serif !important; } .gr-accordion { background: var(--surface) !important; border: 1px solid var(--border-default) !important; border-radius: 8px !important; box-shadow: var(--shadow-sm) !important; } blockquote, .markdown-text blockquote { border-left: 3px solid var(--lavender) !important; background: #faf9fc !important; padding: 0.75rem 1rem !important; margin: 0.5rem 0 !important; border-radius: 0 6px 6px 0 !important; color: var(--text-secondary) !important; } a { color: #2563eb !important; text-decoration: none !important; } a:hover { color: var(--accent-hover) !important; text-decoration: underline !important; } input[type="range"] { accent-color: var(--accent) !important; } .generating { position: relative; overflow: hidden; } .generating::after { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,173,122,0.2), transparent); animation: loading 1.5s infinite; } @keyframes loading { 0% { left: -100%; } 100% { left: 100%; } } .progress-bar { height: 4px; background: linear-gradient(90deg, var(--accent), var(--lavender)); border-radius: 2px; animation: progress 2s ease-in-out infinite; } @keyframes progress { 0%, 100% { transform: scaleX(0.3); transform-origin: left; } 50% { transform: scaleX(1); transform-origin: left; } } .gr-column { background: var(--surface) !important; border-radius: 12px !important; padding: 1.5rem !important; border: 1px solid var(--border-default) !important; box-shadow: var(--shadow-md) !important; } @media (max-width: 1024px) { .main-header h1 { font-size: 2.25rem; } .gr-column { margin-bottom: 1rem; } } @media (max-width: 768px) { .main-header h1 { font-size: 1.75rem; } .main-header h3 { font-size: 0.95rem; } .main-header { padding: 1.5rem 1rem; margin: 0.5rem; border-radius: 12px; } .gr-column { padding: 1rem !important; border-radius: 8px !important; } button.primary, button[class*="primary"] { padding: 0.625rem 1.25rem !important; font-size: 0.9rem !important; } } @media (max-width: 480px) { .main-header h1 { font-size: 1.5rem; } .main-header h3 { font-size: 0.85rem; } .main-header p { font-size: 0.8rem; } .main-header { padding: 1rem 0.75rem; } .gr-column { padding: 0.75rem !important; } } """ def create_interface(): """Build the Gradio interface.""" with gr.Blocks(theme=gr.themes.Soft(), css=CSS, title="Global Video Localizer") as app: gr.HTML("""
Works completely free with open source models. Add your ElevenLabs key for premium voice quality.