import gradio as gr import base64 import json import re from datetime import datetime from io import BytesIO # JavaScript code for video processing and OCR VIDEO_PROCESSING_JS = """ function() { const state = { videoLoaded: false, videoFile: null, subtitles: [], currentTime: 0, duration: 0, isPlaying: false, isMuted: false, subtitleBox: { x: 10, y: 70, width: 80, height: 20 }, processing: false, ocrWorker: null, currentSource: 'file', cameraActive: false, liveProcessing: false, liveStartTime: null, liveProcessingInterval: null }; // Language codes for Tesseract const LANGUAGES = { 'English': 'eng', 'Japanese': 'jpn', 'Korean': 'kor', 'Chinese (Simplified)': 'chi_sim', 'Chinese (Traditional)': 'chi_tra', 'Thai': 'tha', 'Vietnamese': 'vie', 'Hindi': 'hin', 'Arabic': 'ara', 'Russian': 'rus', 'Spanish': 'spa', 'French': 'fra', 'German': 'deu', 'Portuguese': 'por', 'Italian': 'ita', 'Dutch': 'dut', 'Polish': 'pol', 'Turkish': 'tur' }; return { state: state, languages: LANGUAGES }; } """ def create_video_ocr_app(): """Create a comprehensive Video Subtitle OCR Extractor application""" with gr.Blocks() as demo: # Header with branding gr.Markdown("""

🎬 Video Subtitle OCR Extractor

Extract hardcoded subtitles from any video using OCR technology

Built with anycoder
""") with gr.Row(): # Left Column - Video Upload and Preview with gr.Column(scale=2): # Video Upload Section gr.Markdown("### 📁 Video Source") with gr.Tabs(): with gr.Tab("📂 Upload File"): video_input = gr.Video( label="Upload Video File", sources=["upload"], height=400, elem_id="main-video" ) gr.Markdown("*Supports MP4, WebM, MOV, AVI formats*") with gr.Tab("📷 Live Camera"): gr.Markdown("""

Use live camera for real-time subtitle extraction

📷 Click "Start Camera" to begin recording

""") live_camera_btn = gr.Button("📷 Start Live Camera", variant="primary") live_camera_status = gr.Textbox(label="Camera Status", value="Not active", interactive=False) # Video Preview gr.Markdown("### ▶️ Video Preview") with gr.Row(): with gr.Column(scale=3): video_preview = gr.Video( label="Video Preview", height=350, interactive=False, elem_id="video-preview" ) with gr.Column(scale=1): gr.Markdown("**Playback Controls**") play_btn = gr.Button("▶ Play", variant="primary") pause_btn = gr.Button("⏸ Pause") restart_btn = gr.Button("⏮ Restart") current_time_display = gr.Textbox( label="Current Time", value="00:00:00", interactive=False ) # Subtitle Region Selection gr.Markdown("### 📐 Subtitle Region Selection") with gr.Row(): with gr.Column(scale=1): x_pos = gr.Slider( minimum=0, maximum=90, value=10, step=1, label="Horizontal Position (X)", info="X-axis position of subtitle box" ) with gr.Column(scale=1): y_pos = gr.Slider( minimum=0, maximum=90, value=70, step=1, label="Vertical Position (Y)", info="Y-axis position of subtitle box" ) with gr.Row(): with gr.Column(scale=1): box_width = gr.Slider( minimum=10, maximum=100, value=80, step=1, label="Width (%)", info="Width of subtitle detection area" ) with gr.Column(scale=1): box_height = gr.Slider( minimum=5, maximum=40, value=20, step=1, label="Height (%)", info="Height of subtitle detection area" ) with gr.Row(): reset_region_btn = gr.Button("↺ Reset Region", variant="secondary") lock_aspect_btn = gr.Button("🔒 Lock Aspect Ratio", variant="secondary") # Time Range Selection gr.Markdown("### ⏱️ Processing Time Range") with gr.Row(): with gr.Column(scale=1): start_time_input = gr.Textbox( label="Start Time", placeholder="Leave empty for start (e.g., 00:01:30)", value="" ) set_start_btn = gr.Button("Set to Current Time") with gr.Column(scale=1): end_time_input = gr.Textbox( label="End Time", placeholder="Leave empty for end (e.g., 00:05:00)", value="" ) set_end_btn = gr.Button("Set to Current Time") # Language Selection gr.Markdown("### 🌐 OCR Language") with gr.Row(): language_select = gr.Dropdown( choices=[ "English", "Japanese", "Korean", "Chinese (Simplified)", "Chinese (Traditional)", "Thai", "Vietnamese", "Hindi", "Arabic", "Russian", "Spanish", "French", "German", "Portuguese", "Italian", "Dutch", "Polish", "Turkish" ], value="English", label="Select OCR Language", info="Prioritizes selected language for better accuracy" ) language_priority = gr.Checkbox( label="Enable Language Priority", value=True, info="Use language-specific OCR optimizations" ) # Process Button with gr.Row(): process_btn = gr.Button( "⚡ Start OCR Processing", variant="primary", size="lg", scale=2 ) stop_process_btn = gr.Button("⏹ Stop", variant="stop", size="lg") progress_bar = gr.Slider( minimum=0, maximum=100, value=0, label="Processing Progress", interactive=False, visible=False ) status_text = gr.Textbox( label="Status", value="Ready", interactive=False ) # Right Column - Results with gr.Column(scale=1): # Options gr.Markdown("### ⚙️ Options") with gr.Row(): show_overlay = gr.Checkbox( label="Show Selection Box", value=True ) auto_play = gr.Checkbox( label="Auto Play Preview", value=True ) with gr.Row(): highlight_active = gr.Checkbox( label="Highlight Active Subtitles", value=True ) preserve_timing = gr.Checkbox( label="Frame-accurate Timing", value=True ) # Results gr.Markdown("### 📝 Extracted Subtitles") subtitle_count = gr.Textbox( value="0 lines extracted", label="Results", interactive=False ) subtitles_output = gr.Dataframe( headers=["#", "Start Time", "End Time", "Subtitle Text"], datatype=["number", "str", "str", "str"], label="Subtitles", interactive=False, height=400 ) # Active subtitle display active_subtitle = gr.Textbox( label="Currently Active Subtitle", value="", interactive=False, lines=3 ) # Export Buttons gr.Markdown("### 💾 Export") with gr.Row(): export_srt = gr.Button("📄 Export SRT", variant="primary") export_txt = gr.Button("📝 Export TXT") export_json = gr.Button("📋 Export JSON") clear_btn = gr.Button("🗑️ Clear All", variant="stop") # Statistics gr.Markdown("### 📊 Statistics") with gr.Row(): total_lines = gr.Number(value=0, label="Total Lines", interactive=False) processing_time = gr.Textbox(label="Processing Time", value="-", interactive=False) detected_language = gr.Textbox(label="Detected Language", value="-", interactive=False) # Event Handlers def update_preview(video_path): """Update video preview when file is uploaded""" if video_path: return video_path return None def set_start_time(current_time): """Set start time from current playback position""" return format_time(current_time) def set_end_time(current_time): """Set end time from current playback position""" return format_time(current_time) def reset_region(): """Reset subtitle region to defaults""" return 10, 70, 80, 20 def format_time(seconds): """Format seconds to HH:MM:SS""" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) return f"{hours:02d}:{minutes:02d}:{secs:02d}" def parse_time(time_str): """Parse time string to seconds""" if not time_str: return None try: parts = time_str.split(':') if len(parts) == 3: return int(parts[0]) * 3600 + int(parts[1]) * 60 + int(parts[2]) elif len(parts) == 2: return int(parts[0]) * 60 + int(parts[1]) return float(time_str) except: return None def generate_srt(subtitles): """Generate SRT format subtitles""" if not subtitles: return "" srt_content = "" for i, sub in enumerate(subtitles, 1): srt_content += f"{i}\\n" srt_content += f"{sub['start']} --> {sub['end']}\\n" srt_content += f"{sub['text']}\\n\\n" return srt_content def generate_txt(subtitles): """Generate plain text subtitles""" if not subtitles: return "" return "\\n".join([sub['text'] for sub in subtitles]) def generate_json(subtitles): """Generate JSON format subtitles""" return json.dumps(subtitles, indent=2, ensure_ascii=False) # Connect video upload to preview video_input.change( fn=update_preview, inputs=[video_input], outputs=[video_preview] ) # Set time buttons set_start_btn.click( fn=set_start_time, inputs=[current_time_display], outputs=[start_time_input] ) set_end_btn.click( fn=set_end_time, inputs=[current_time_display], outputs=[end_time_input] ) # Reset region reset_region_btn.click( fn=reset_region, inputs=[], outputs=[x_pos, y_pos, box_width, box_height] ) # Process button - Main OCR processing def process_video( video_path, x, y, width, height, start_time_str, end_time_str, language, use_priority, highlight, preserve_time ): """Process video and extract subtitles using OCR""" if not video_path: return ( "No video loaded. Please upload a video first.", [], "0 lines extracted", "", 0, "-", "-" ) start_time = parse_time(start_time_str) if start_time_str else 0 end_time = parse_time(end_time_str) # Return processing info - actual OCR happens client-side yield ( "Video loaded. Starting OCR extraction...", [], "0 lines extracted", "", 0, "-", language ) # Note: Actual OCR processing requires client-side JavaScript # This is a placeholder that returns processing parameters processing_params = { 'video_path': video_path, 'region': {'x': x, 'y': y, 'width': width, 'height': height}, 'start_time': start_time, 'end_time': end_time, 'language': language, 'use_priority': use_priority, 'highlight': highlight, 'preserve_time': preserve_time } return ( f"Processing with params: {json.dumps(processing_params)}", [], "Processing...", "", 0, "-", language ) # Export handlers def export_subtitles_srt(subtitles_json): """Export subtitles as SRT""" if isinstance(subtitles_json, str): subtitles = json.loads(subtitles_json) if subtitles_json else [] else: subtitles = subtitles_json return generate_srt(subtitles) def export_subtitles_txt(subtitles_json): """Export subtitles as TXT""" if isinstance(subtitles_json, str): subtitles = json.loads(subtitles_json) if subtitles_json else [] else: subtitles = subtitles_json return generate_txt(subtitles) def export_subtitles_json(subtitles_json): """Export subtitles as JSON""" if isinstance(subtitles_json, str): subtitles = json.loads(subtitles_json) if subtitles_json else [] else: subtitles = subtitles_json return generate_json(subtitles) # Connect export buttons export_srt.click( fn=export_subtitles_srt, inputs=[subtitles_output], outputs=[subtitles_output] ) # Playback controls play_btn.click( fn=lambda: "Playing...", inputs=[], outputs=[status_text] ) pause_btn.click( fn=lambda: "Paused", inputs=[], outputs=[status_text] ) # Clear all def clear_all(): return None, "", "", [], "0 lines extracted", "", 0, "-", "-", "-" clear_btn.click( fn=clear_all, inputs=[], outputs=[ video_input, start_time_input, end_time_input, subtitles_output, subtitle_count, active_subtitle, total_lines, processing_time, detected_language, video_preview ] ) return demo def format_time(seconds): """Format seconds to HH:MM:SS,mmm""" if seconds is None: return "00:00:00,000" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) millis = int((seconds % 1) * 1000) return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}" def format_time_simple(seconds): """Format seconds to HH:MM:SS""" if seconds is None: return "00:00:00" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) return f"{hours:02d}:{minutes:02d}:{secs:02d}" def parse_time(time_str): """Parse time string (HH:MM:SS or HH:MM:SS,mmm) to seconds""" if not time_str: return None try: # Handle both comma and dot milliseconds time_str = time_str.replace(',', '.') parts = time_str.split(':') if len(parts) == 3: hours = int(parts[0]) minutes = int(parts[1]) seconds = float(parts[2]) return hours * 3600 + minutes * 60 + seconds elif len(parts) == 2: minutes = int(parts[0]) seconds = float(parts[1]) return minutes * 60 + seconds return float(time_str) except (ValueError, AttributeError): return None # Create and launch the demo if __name__ == "__main__": demo = create_video_ocr_app() demo.launch( title="Video Subtitle OCR Extractor", theme=gr.themes.Soft( primary_hue="indigo", secondary_hue="pink", neutral_hue="slate", font=gr.themes.GoogleFont("Inter"), text_size="lg", spacing_size="lg", radius_size="md" ), css=""" .gradio-container {max-width: 1600px !important;} #main-video video {max-height: 400px;} #video-preview video {max-height: 350px;} """, footer_links=[ {"label": "Video Subtitle OCR Extractor", "url": "#"}, {"label": "Built with Gradio", "url": "https://gradio.app"}, {"label": "Powered by Tesseract.js", "url": "https://tesseract.projectnaptha.com/"} ] )