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/"}
]
)