Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,226 +1,117 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
| 4 |
-
import tempfile
|
| 5 |
-
import os
|
| 6 |
import json
|
| 7 |
-
from PIL import Image
|
| 8 |
-
import time
|
| 9 |
-
import math
|
| 10 |
|
| 11 |
-
|
| 12 |
-
# 📋 OCR FUNCTION (Video Hardsub Extractor)
|
| 13 |
-
# ============================================================================
|
| 14 |
-
|
| 15 |
-
def extract_subtitles(video_file, language, start_time, end_time,
|
| 16 |
-
box_x, box_y, box_w, box_h, aspect_lock):
|
| 17 |
"""
|
| 18 |
-
|
| 19 |
"""
|
| 20 |
-
|
| 21 |
-
|
| 22 |
|
| 23 |
-
#
|
|
|
|
| 24 |
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 25 |
-
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 26 |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 27 |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
start_frame =
|
| 31 |
-
end_frame =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
# စာတန်းတွေသိမ်းမယ့် list
|
| 34 |
subtitles = []
|
| 35 |
last_text = ""
|
| 36 |
-
current_start =
|
| 37 |
-
frame_count = 0
|
| 38 |
-
total_frames_to_process = end_frame - start_frame
|
| 39 |
-
|
| 40 |
-
# Progress updates
|
| 41 |
-
progress = []
|
| 42 |
-
|
| 43 |
-
# Frame အလိုက်ဖတ်မယ် (1 FPS sampling)
|
| 44 |
-
sample_interval = int(fps) # 1 frame per second
|
| 45 |
-
|
| 46 |
-
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
| 47 |
|
|
|
|
| 48 |
for frame_num in range(start_frame, end_frame, sample_interval):
|
| 49 |
ret, frame = cap.read()
|
| 50 |
if not ret:
|
| 51 |
break
|
| 52 |
-
|
| 53 |
frame_count += 1
|
| 54 |
current_time = frame_num / fps
|
| 55 |
|
| 56 |
-
#
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
y1 = int((box_y / 100) * height)
|
| 62 |
-
x2 = int(((box_x + box_w) / 100) * width)
|
| 63 |
-
y2 = int(((box_y + box_h) / 100) * height)
|
| 64 |
-
|
| 65 |
-
# Ensure coordinates are within frame
|
| 66 |
-
x1 = max(0, min(x1, width-1))
|
| 67 |
-
y1 = max(0, min(y1, height-1))
|
| 68 |
-
x2 = max(x1+1, min(x2, width))
|
| 69 |
-
y2 = max(y1+1, min(y2, height))
|
| 70 |
-
|
| 71 |
-
crop = frame[y1:y2, x1:x2]
|
| 72 |
|
| 73 |
-
# OCR
|
| 74 |
-
|
|
|
|
| 75 |
|
| 76 |
-
#
|
| 77 |
if text and text != last_text:
|
| 78 |
if last_text:
|
| 79 |
subtitles.append({
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
'status': 'exited'
|
| 84 |
})
|
| 85 |
current_start = current_time
|
| 86 |
last_text = text
|
| 87 |
-
|
| 88 |
-
# Add entered status
|
| 89 |
-
subtitles.append({
|
| 90 |
-
'start': current_time,
|
| 91 |
-
'end': current_time,
|
| 92 |
-
'text': text,
|
| 93 |
-
'status': 'entered'
|
| 94 |
-
})
|
| 95 |
|
| 96 |
-
#
|
| 97 |
if last_text:
|
| 98 |
subtitles.append({
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
'status': 'exited'
|
| 103 |
})
|
| 104 |
|
| 105 |
cap.release()
|
| 106 |
|
| 107 |
-
#
|
| 108 |
-
srt_content =
|
|
|
|
|
|
|
| 109 |
|
| 110 |
-
#
|
| 111 |
-
json_content =
|
| 112 |
|
| 113 |
-
#
|
| 114 |
-
txt_content =
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
#
|
| 117 |
-
srt_path = "
|
| 118 |
-
json_path = "
|
| 119 |
-
txt_path = "
|
| 120 |
|
| 121 |
with open(srt_path, "w", encoding="utf-8") as f:
|
| 122 |
f.write(srt_content)
|
| 123 |
-
|
| 124 |
with open(json_path, "w", encoding="utf-8") as f:
|
| 125 |
f.write(json_content)
|
| 126 |
-
|
| 127 |
with open(txt_path, "w", encoding="utf-8") as f:
|
| 128 |
f.write(txt_content)
|
| 129 |
|
| 130 |
-
#
|
| 131 |
-
|
| 132 |
-
for s in subtitles:
|
| 133 |
-
if s['status'] != 'entered': # Only show actual subtitle lines
|
| 134 |
-
subtitle_list.append([
|
| 135 |
-
format_time(s['start']),
|
| 136 |
-
format_time(s['end']),
|
| 137 |
-
s['text'][:50] + "..." if len(s['text']) > 50 else s['text']
|
| 138 |
-
])
|
| 139 |
|
| 140 |
-
return srt_path, json_path, txt_path,
|
| 141 |
-
|
| 142 |
-
def simulate_ocr(image, language, current_time):
|
| 143 |
-
"""
|
| 144 |
-
OCR Simulation - တကယ့် OCR အတွက် placeholder
|
| 145 |
-
"""
|
| 146 |
-
# Sample texts for demonstration
|
| 147 |
-
samples = [
|
| 148 |
-
"Hello and welcome",
|
| 149 |
-
"နမူနာစာသား",
|
| 150 |
-
"ตัวอย่างข้อความ",
|
| 151 |
-
"サンプルテキスト",
|
| 152 |
-
"샘플 텍스트",
|
| 153 |
-
"示例文本",
|
| 154 |
-
"ข้อความตัวอย่าง"
|
| 155 |
-
]
|
| 156 |
-
|
| 157 |
-
# Return sample text based on time
|
| 158 |
-
idx = int(current_time) % len(samples)
|
| 159 |
-
return samples[idx] if idx < len(samples) else ""
|
| 160 |
-
|
| 161 |
-
def generate_srt(subtitles):
|
| 162 |
-
"""SRT format ပြောင်းမယ်"""
|
| 163 |
-
srt_content = ""
|
| 164 |
-
counter = 1
|
| 165 |
-
|
| 166 |
-
for i, sub in enumerate(subtitles):
|
| 167 |
-
if sub['status'] != 'entered':
|
| 168 |
-
start = format_time(sub['start'])
|
| 169 |
-
end = format_time(sub['end'])
|
| 170 |
-
srt_content += f"{counter}\n{start} --> {end}\n{sub['text']}\n\n"
|
| 171 |
-
counter += 1
|
| 172 |
-
|
| 173 |
-
return srt_content
|
| 174 |
-
|
| 175 |
-
def generate_json(subtitles):
|
| 176 |
-
"""JSON format ပြောင်းမယ်"""
|
| 177 |
-
json_list = []
|
| 178 |
-
for sub in subtitles:
|
| 179 |
-
if sub['status'] != 'entered':
|
| 180 |
-
json_list.append({
|
| 181 |
-
'start': sub['start'],
|
| 182 |
-
'start_formatted': format_time(sub['start']),
|
| 183 |
-
'end': sub['end'],
|
| 184 |
-
'end_formatted': format_time(sub['end']),
|
| 185 |
-
'text': sub['text']
|
| 186 |
-
})
|
| 187 |
-
return json.dumps(json_list, indent=2, ensure_ascii=False)
|
| 188 |
-
|
| 189 |
-
def generate_txt(subtitles):
|
| 190 |
-
"""TXT format ပြောင်းမယ်"""
|
| 191 |
-
txt_content = ""
|
| 192 |
-
for sub in subtitles:
|
| 193 |
-
if sub['status'] != 'entered':
|
| 194 |
-
txt_content += f"[{format_time(sub['start'])} --> {format_time(sub['end'])}] {sub['text']}\n"
|
| 195 |
-
return txt_content
|
| 196 |
|
| 197 |
def format_time(seconds):
|
| 198 |
-
"""အချိန်ကို SRT format (HH:MM:SS,mmm) ပြောင်းမယ်"""
|
| 199 |
h = int(seconds // 3600)
|
| 200 |
m = int((seconds % 3600) // 60)
|
| 201 |
s = int(seconds % 60)
|
| 202 |
ms = int((seconds - int(seconds)) * 1000)
|
| 203 |
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
|
| 204 |
|
| 205 |
-
def set_start_from_video(current_time):
|
| 206 |
-
return current_time
|
| 207 |
-
|
| 208 |
-
def set_end_from_video(current_time):
|
| 209 |
-
return current_time
|
| 210 |
-
|
| 211 |
-
def reset_adjustments():
|
| 212 |
-
return 10, 80, 80, 15
|
| 213 |
-
|
| 214 |
-
def toggle_aspect_lock(current_lock):
|
| 215 |
-
return not current_lock
|
| 216 |
-
|
| 217 |
def update_duration(start, end):
|
| 218 |
return max(0, end - start)
|
| 219 |
|
| 220 |
def set_full_video(duration):
|
| 221 |
return 0, duration
|
| 222 |
|
| 223 |
-
def
|
| 224 |
if video:
|
| 225 |
cap = cv2.VideoCapture(video)
|
| 226 |
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
@@ -230,148 +121,75 @@ def update_video_info(video):
|
|
| 230 |
return duration, duration, 0
|
| 231 |
return 0, 0, 0
|
| 232 |
|
| 233 |
-
#
|
| 234 |
-
|
| 235 |
-
#
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
.
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
current_time_state = gr.State(0)
|
| 261 |
-
video_duration_state = gr.State(0)
|
| 262 |
-
aspect_lock_state = gr.State(False)
|
| 263 |
-
|
| 264 |
-
with gr.Tabs():
|
| 265 |
-
# ====================================================================
|
| 266 |
-
# PAGE 1: SETUP PAGE
|
| 267 |
-
# ====================================================================
|
| 268 |
-
with gr.TabItem("🎥 Setup", id="setup"):
|
| 269 |
with gr.Row():
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
with gr.Row():
|
| 274 |
-
play_btn = gr.Button("▶ Play", variant="secondary")
|
| 275 |
-
pause_btn = gr.Button("⏸ Pause", variant="secondary")
|
| 276 |
-
current_time_display = gr.Number(label="Current Time (s)", value=0, interactive=False)
|
| 277 |
-
duration_display = gr.Number(label="Duration (s)", value=0, interactive=False)
|
| 278 |
-
|
| 279 |
-
with gr.Column(scale=1):
|
| 280 |
-
gr.Markdown("### 🌐 Language Selection")
|
| 281 |
-
language = gr.Dropdown(
|
| 282 |
-
choices=["English", "Japanese", "Korean", "Chinese (Simplified)", "Chinese (Traditional)", "Thai"],
|
| 283 |
-
label="Language", value="English"
|
| 284 |
-
)
|
| 285 |
-
|
| 286 |
-
gr.Markdown("### 📐 Adjust Subtitle Area")
|
| 287 |
-
with gr.Row():
|
| 288 |
-
box_x = gr.Slider(0, 100, value=10, step=0.5, label="X Position %")
|
| 289 |
-
box_y = gr.Slider(0, 100, value=80, step=0.5, label="Y Position %")
|
| 290 |
-
with gr.Row():
|
| 291 |
-
box_w = gr.Slider(0, 100, value=80, step=0.5, label="Width %")
|
| 292 |
-
box_h = gr.Slider(0, 100, value=15, step=0.5, label="Height %")
|
| 293 |
-
|
| 294 |
-
with gr.Row():
|
| 295 |
-
reset_btn = gr.Button("↺ Reset", variant="secondary")
|
| 296 |
-
aspect_lock_btn = gr.Button("🔒 Aspect Lock", variant="secondary")
|
| 297 |
-
|
| 298 |
-
gr.Markdown("### ⏱️ Time Range")
|
| 299 |
-
with gr.Row():
|
| 300 |
-
start_time = gr.Number(label="Start (s)", value=0, step=0.1)
|
| 301 |
-
set_start_btn = gr.Button("Set Start", variant="secondary")
|
| 302 |
-
with gr.Row():
|
| 303 |
-
end_time = gr.Number(label="End (s)", value=10, step=0.1)
|
| 304 |
-
set_end_btn = gr.Button("Set End", variant="secondary")
|
| 305 |
-
|
| 306 |
-
duration_display_small = gr.Number(label="Duration", value=10, interactive=False)
|
| 307 |
-
full_video_btn = gr.Button("📽️ Full Video", variant="secondary")
|
| 308 |
-
|
| 309 |
-
process_btn = gr.Button("⚡ PROCESS VIDEO", variant="primary", size="lg")
|
| 310 |
-
|
| 311 |
-
# ====================================================================
|
| 312 |
-
# PAGE 2: RESULTS PAGE
|
| 313 |
-
# ====================================================================
|
| 314 |
-
with gr.TabItem("📄 Results", id="results"):
|
| 315 |
-
gr.Markdown("## 📊 Extraction Results")
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
with gr.Row():
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
back_btn = gr.Button("↩ Back to Setup", variant="secondary")
|
| 334 |
-
|
| 335 |
-
# ========================================================================
|
| 336 |
-
# EVENT HANDLERS
|
| 337 |
-
# ========================================================================
|
| 338 |
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
reset_btn.click(fn=reset_adjustments, inputs=[], outputs=[box_x, box_y, box_w, box_h])
|
| 342 |
-
aspect_lock_btn.click(fn=toggle_aspect_lock, inputs=[aspect_lock_state], outputs=[aspect_lock_state])
|
| 343 |
|
| 344 |
-
|
| 345 |
-
|
| 346 |
|
| 347 |
-
|
| 348 |
|
| 349 |
-
|
| 350 |
fn=extract_subtitles,
|
| 351 |
-
inputs=[
|
| 352 |
-
outputs=[
|
| 353 |
-
).then(
|
| 354 |
-
fn=lambda: gr.Tabs(selected="results"),
|
| 355 |
-
inputs=[],
|
| 356 |
-
outputs=None,
|
| 357 |
-
_js="() => { document.querySelector('#results-tab').click(); }"
|
| 358 |
-
)
|
| 359 |
-
|
| 360 |
-
back_btn.click(
|
| 361 |
-
fn=lambda: gr.Tabs(selected="setup"),
|
| 362 |
-
inputs=[],
|
| 363 |
-
outputs=None,
|
| 364 |
-
_js="() => { document.querySelector('#setup-tab').click(); }"
|
| 365 |
)
|
| 366 |
-
|
| 367 |
-
video_input.change(
|
| 368 |
-
fn=update_video_info,
|
| 369 |
-
inputs=[video_input],
|
| 370 |
-
outputs=[duration_display, end_time, current_time_display]
|
| 371 |
-
)
|
| 372 |
-
|
| 373 |
-
# ============================================================================
|
| 374 |
-
# 📋 LAUNCH
|
| 375 |
-
# ============================================================================
|
| 376 |
|
| 377 |
demo.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import cv2
|
| 3 |
import numpy as np
|
|
|
|
|
|
|
| 4 |
import json
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
def extract_subtitles(video, language, start, end, x, y, w, h, lock):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
"""
|
| 8 |
+
Extract hardcoded subtitles from video
|
| 9 |
"""
|
| 10 |
+
if video is None:
|
| 11 |
+
return None, None, None, [], "No video uploaded"
|
| 12 |
|
| 13 |
+
# Open video
|
| 14 |
+
cap = cv2.VideoCapture(video)
|
| 15 |
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
|
|
| 16 |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 17 |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 18 |
|
| 19 |
+
# Calculate frames
|
| 20 |
+
start_frame = int(start * fps)
|
| 21 |
+
end_frame = int(end * fps)
|
| 22 |
+
|
| 23 |
+
# Sample every 1 second
|
| 24 |
+
sample_interval = int(fps)
|
| 25 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
| 26 |
|
|
|
|
| 27 |
subtitles = []
|
| 28 |
last_text = ""
|
| 29 |
+
current_start = start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
frame_count = 0
|
| 32 |
for frame_num in range(start_frame, end_frame, sample_interval):
|
| 33 |
ret, frame = cap.read()
|
| 34 |
if not ret:
|
| 35 |
break
|
| 36 |
+
|
| 37 |
frame_count += 1
|
| 38 |
current_time = frame_num / fps
|
| 39 |
|
| 40 |
+
# Crop subtitle area
|
| 41 |
+
x1 = int((x / 100) * width)
|
| 42 |
+
y1 = int((y / 100) * height)
|
| 43 |
+
x2 = int(((x + w) / 100) * width)
|
| 44 |
+
y2 = int(((y + h) / 100) * height)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
# Simulate OCR (replace with actual OCR later)
|
| 47 |
+
sample_texts = ["Hello", "Welcome", "ဟယ်လို", "สวัสดี", "こんにちは", "안녕하세요", "你好"]
|
| 48 |
+
text = sample_texts[frame_count % len(sample_texts)]
|
| 49 |
|
| 50 |
+
# Merge duplicates
|
| 51 |
if text and text != last_text:
|
| 52 |
if last_text:
|
| 53 |
subtitles.append({
|
| 54 |
+
"start": current_start,
|
| 55 |
+
"end": current_time,
|
| 56 |
+
"text": last_text
|
|
|
|
| 57 |
})
|
| 58 |
current_start = current_time
|
| 59 |
last_text = text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
# Add last subtitle
|
| 62 |
if last_text:
|
| 63 |
subtitles.append({
|
| 64 |
+
"start": current_start,
|
| 65 |
+
"end": end,
|
| 66 |
+
"text": last_text
|
|
|
|
| 67 |
})
|
| 68 |
|
| 69 |
cap.release()
|
| 70 |
|
| 71 |
+
# Generate SRT
|
| 72 |
+
srt_content = ""
|
| 73 |
+
for i, sub in enumerate(subtitles, 1):
|
| 74 |
+
srt_content += f"{i}\n{format_time(sub['start'])} --> {format_time(sub['end'])}\n{sub['text']}\n\n"
|
| 75 |
|
| 76 |
+
# Generate JSON
|
| 77 |
+
json_content = json.dumps(subtitles, indent=2, ensure_ascii=False)
|
| 78 |
|
| 79 |
+
# Generate TXT
|
| 80 |
+
txt_content = ""
|
| 81 |
+
for sub in subtitles:
|
| 82 |
+
txt_content += f"[{format_time(sub['start'])} --> {format_time(sub['end'])}] {sub['text']}\n"
|
| 83 |
|
| 84 |
+
# Save files
|
| 85 |
+
srt_path = "subtitles.srt"
|
| 86 |
+
json_path = "subtitles.json"
|
| 87 |
+
txt_path = "subtitles.txt"
|
| 88 |
|
| 89 |
with open(srt_path, "w", encoding="utf-8") as f:
|
| 90 |
f.write(srt_content)
|
|
|
|
| 91 |
with open(json_path, "w", encoding="utf-8") as f:
|
| 92 |
f.write(json_content)
|
|
|
|
| 93 |
with open(txt_path, "w", encoding="utf-8") as f:
|
| 94 |
f.write(txt_content)
|
| 95 |
|
| 96 |
+
# Format for display
|
| 97 |
+
display_list = [[format_time(s["start"]), format_time(s["end"]), s["text"]] for s in subtitles]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
return srt_path, json_path, txt_path, display_list, f"Processed {frame_count} frames"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
def format_time(seconds):
|
|
|
|
| 102 |
h = int(seconds // 3600)
|
| 103 |
m = int((seconds % 3600) // 60)
|
| 104 |
s = int(seconds % 60)
|
| 105 |
ms = int((seconds - int(seconds)) * 1000)
|
| 106 |
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
def update_duration(start, end):
|
| 109 |
return max(0, end - start)
|
| 110 |
|
| 111 |
def set_full_video(duration):
|
| 112 |
return 0, duration
|
| 113 |
|
| 114 |
+
def get_video_duration(video):
|
| 115 |
if video:
|
| 116 |
cap = cv2.VideoCapture(video)
|
| 117 |
fps = cap.get(cv2.CAP_PROP_FPS)
|
|
|
|
| 121 |
return duration, duration, 0
|
| 122 |
return 0, 0, 0
|
| 123 |
|
| 124 |
+
# Create Gradio interface
|
| 125 |
+
with gr.Blocks(title="Video Hardsub OCR") as demo:
|
| 126 |
+
gr.Markdown("# 🎬 Video Hardsub OCR Extractor")
|
| 127 |
+
|
| 128 |
+
with gr.Row():
|
| 129 |
+
with gr.Column(scale=1):
|
| 130 |
+
# Inputs
|
| 131 |
+
video = gr.Video(label="Upload Video")
|
| 132 |
+
lang = gr.Dropdown(
|
| 133 |
+
choices=["English", "Japanese", "Korean", "Chinese (Simplified)", "Chinese (Traditional)", "Thai"],
|
| 134 |
+
label="Language", value="English"
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
# Subtitle area
|
| 138 |
+
gr.Markdown("### 📐 Subtitle Area")
|
| 139 |
+
x_pos = gr.Slider(0, 100, value=10, label="X Position %")
|
| 140 |
+
y_pos = gr.Slider(0, 100, value=80, label="Y Position %")
|
| 141 |
+
width = gr.Slider(0, 100, value=80, label="Width %")
|
| 142 |
+
height = gr.Slider(0, 100, value=15, label="Height %")
|
| 143 |
+
|
| 144 |
+
# Time range
|
| 145 |
+
gr.Markdown("### ⏱️ Time Range")
|
| 146 |
+
start = gr.Number(label="Start (s)", value=0)
|
| 147 |
+
end = gr.Number(label="End (s)", value=10)
|
| 148 |
+
duration_disp = gr.Number(label="Duration", value=10, interactive=False)
|
| 149 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
with gr.Row():
|
| 151 |
+
set_start = gr.Button("Set Start")
|
| 152 |
+
set_end = gr.Button("Set End")
|
| 153 |
+
full_video = gr.Button("Full Video")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
# Process button
|
| 156 |
+
process = gr.Button("Process Video", variant="primary")
|
| 157 |
+
|
| 158 |
+
with gr.Column(scale=1):
|
| 159 |
+
# Outputs
|
| 160 |
+
video_out = gr.Video(label="Video")
|
| 161 |
+
subtitles_out = gr.Dataframe(
|
| 162 |
+
headers=["Start", "End", "Text"],
|
| 163 |
+
label="Extracted Subtitles"
|
| 164 |
+
)
|
| 165 |
with gr.Row():
|
| 166 |
+
srt_file = gr.File(label="SRT")
|
| 167 |
+
json_file = gr.File(label="JSON")
|
| 168 |
+
txt_file = gr.File(label="TXT")
|
| 169 |
+
|
| 170 |
+
# Current time display
|
| 171 |
+
current_time = gr.Number(value=0, visible=False)
|
| 172 |
+
duration = gr.Number(value=0, visible=False)
|
| 173 |
+
|
| 174 |
+
# Event handlers
|
| 175 |
+
video.change(
|
| 176 |
+
fn=get_video_duration,
|
| 177 |
+
inputs=[video],
|
| 178 |
+
outputs=[duration, end, current_time]
|
| 179 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
set_start.click(fn=lambda t: t, inputs=[current_time], outputs=[start])
|
| 182 |
+
set_end.click(fn=lambda t: t, inputs=[current_time], outputs=[end])
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
start.change(fn=update_duration, inputs=[start, end], outputs=[duration_disp])
|
| 185 |
+
end.change(fn=update_duration, inputs=[start, end], outputs=[duration_disp])
|
| 186 |
|
| 187 |
+
full_video.click(fn=set_full_video, inputs=[duration], outputs=[start, end])
|
| 188 |
|
| 189 |
+
process.click(
|
| 190 |
fn=extract_subtitles,
|
| 191 |
+
inputs=[video, lang, start, end, x_pos, y_pos, width, height, gr.State(False)],
|
| 192 |
+
outputs=[srt_file, json_file, txt_file, subtitles_out, current_time]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
demo.launch()
|