deepuurf commited on
Commit
4097840
Β·
verified Β·
1 Parent(s): af9d022

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +474 -0
app.py CHANGED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ import shutil
5
+ import json
6
+ import threading
7
+ import random
8
+ import string
9
+ import time
10
+ import multiprocessing
11
+
12
+ CPU_THREADS = str(multiprocessing.cpu_count())
13
+
14
+ BASE_DIR = os.path.join(os.getcwd(), "job_data")
15
+ JOBS_FILE = os.path.join(BASE_DIR, "jobs.json")
16
+ os.makedirs(BASE_DIR, exist_ok=True)
17
+
18
+ _lock = threading.Lock()
19
+ HISTORY_TTL_SECONDS = 5 * 60 * 60 # 5 ghante
20
+
21
+ # ---------- BGM file path (server par permanently) ----------
22
+ BGM_FILE = None
23
+ for ext in [".mp4", ".mp3", ".m4a", ".wav"]:
24
+ potential = os.path.join(os.getcwd(), f"bgm{ext}")
25
+ if os.path.exists(potential):
26
+ BGM_FILE = potential
27
+ break
28
+
29
+ # ---------- Storage helpers ----------
30
+ def _load_jobs():
31
+ if not os.path.exists(JOBS_FILE):
32
+ return {}
33
+ try:
34
+ with open(JOBS_FILE, "r") as f:
35
+ return json.load(f)
36
+ except Exception:
37
+ return {}
38
+
39
+ def _save_jobs(jobs):
40
+ with open(JOBS_FILE, "w") as f:
41
+ json.dump(jobs, f, indent=2)
42
+
43
+ def _add_task(code, task):
44
+ with _lock:
45
+ jobs = _load_jobs()
46
+ jobs.setdefault(code, []).append(task)
47
+ _save_jobs(jobs)
48
+
49
+ def _update_task(code, task_id, **kwargs):
50
+ with _lock:
51
+ jobs = _load_jobs()
52
+ for t in jobs.get(code, []):
53
+ if t["id"] == task_id:
54
+ t.update(kwargs)
55
+ _save_jobs(jobs)
56
+
57
+ def _get_task(code, task_id):
58
+ jobs = _load_jobs()
59
+ for t in jobs.get(code, []):
60
+ if t["id"] == task_id:
61
+ return t
62
+ return None
63
+
64
+ def _cleanup_old_tasks():
65
+ """5 ghante se purani tasks hata do (data + entry dono)."""
66
+ with _lock:
67
+ jobs = _load_jobs()
68
+ now = time.time()
69
+ changed = False
70
+ for code in list(jobs.keys()):
71
+ kept = []
72
+ for t in jobs[code]:
73
+ if now - t.get("created_ts", now) < HISTORY_TTL_SECONDS:
74
+ kept.append(t)
75
+ else:
76
+ changed = True
77
+ task_dir = os.path.join(BASE_DIR, code, t["id"])
78
+ shutil.rmtree(task_dir, ignore_errors=True)
79
+ if kept:
80
+ jobs[code] = kept
81
+ else:
82
+ del jobs[code]
83
+ changed = True
84
+ if changed:
85
+ _save_jobs(jobs)
86
+
87
+ def random_code():
88
+ return "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
89
+
90
+ # ---------- Video helpers ----------
91
+ RES_MAP = {
92
+ "480p (sabse fast)": (854, 480),
93
+ "720p (balanced)": (1280, 720),
94
+ "1080p (best quality, slow)": (1920, 1080),
95
+ }
96
+
97
+ def get_duration(path):
98
+ try:
99
+ out = subprocess.run(
100
+ ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", path],
101
+ capture_output=True, text=True
102
+ )
103
+ return max(0.1, float(out.stdout.strip()))
104
+ except Exception:
105
+ return 0.1
106
+
107
+ def build_audio_filter():
108
+ """Halka audio variation β€” pitch/EQ/echo. Sirf sound texture ke liye, fingerprint-evasion tool nahi hai."""
109
+ pitch_shift = random.uniform(0.98, 1.02)
110
+ eq_freq = random.choice([200, 500, 1000, 3000])
111
+ eq_gain = random.uniform(-1.5, 1.5)
112
+ filters = [
113
+ f"asetrate=44100*{pitch_shift},aresample=44100",
114
+ f"equalizer=f={eq_freq}:t=q:w=1:g={eq_gain:.2f}",
115
+ f"aecho=0.5:0.3:6:0.1",
116
+ ]
117
+ return ",".join(filters)
118
+
119
+
120
+ def run_ffmpeg_with_progress(cmd, code, task_id, total_duration, base_pct, weight_pct, fps_hint=""):
121
+ cmd = [cmd[0], "-y", "-progress", "pipe:1", "-nostats"] + cmd[2:]
122
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
123
+ text=True, bufsize=1)
124
+ error_lines = []
125
+ last_write = 0
126
+ out_time_sec = 0.0
127
+ speed_val = 0.0
128
+ fps_val = 0.0
129
+
130
+ for line in proc.stdout:
131
+ line = line.strip()
132
+ if "=" in line:
133
+ key, _, val = line.partition("=")
134
+ if key == "out_time_ms":
135
+ try:
136
+ out_time_sec = int(val) / 1_000_000
137
+ except ValueError:
138
+ pass
139
+ elif key == "speed":
140
+ try:
141
+ speed_val = float(val.replace("x", "").strip())
142
+ except ValueError:
143
+ speed_val = 0.0
144
+ elif key == "fps":
145
+ try:
146
+ fps_val = float(val)
147
+ except ValueError:
148
+ pass
149
+ else:
150
+ error_lines.append(line)
151
+
152
+ now = time.time()
153
+ if now - last_write > 0.6:
154
+ local_pct = min(99, (out_time_sec / total_duration) * 100) if total_duration > 0 else 0
155
+ overall_pct = round(base_pct + (weight_pct * local_pct / 100), 1)
156
+ remaining = max(0, total_duration - out_time_sec)
157
+ eta = round(remaining / speed_val, 1) if speed_val > 0 else None
158
+ _update_task(code, task_id, progress=overall_pct, speed=round(speed_val, 2),
159
+ fps=round(fps_val, 1), eta_seconds=eta,
160
+ elapsed=round(out_time_sec, 1), total_dur=round(total_duration, 1),
161
+ status="processing")
162
+ last_write = now
163
+
164
+ proc.wait()
165
+ return proc.returncode == 0, "\n".join(error_lines[-15:])
166
+
167
+
168
+ def do_merge(video1, video2, resize_mode, resolution_choice, change_audio_dna, bgm_volume, work_dir, output_path, code, task_id):
169
+ try:
170
+ d1 = get_duration(video1)
171
+ d2 = get_duration(video2)
172
+ total = d1 + d2
173
+
174
+ if resize_mode == "Fast (same resolution/codec required)" and not change_audio_dna and not BGM_FILE:
175
+ list_file = os.path.join(work_dir, "list.txt")
176
+ with open(list_file, "w") as f:
177
+ f.write(f"file '{video1}'\n")
178
+ f.write(f"file '{video2}'\n")
179
+
180
+ cmd = ["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file, "-c", "copy", output_path]
181
+ ok, err = run_ffmpeg_with_progress(cmd, code, task_id, total, 0, 100)
182
+ if ok:
183
+ return True, "Merge ho gaya (fast copy mode)"
184
+
185
+ norm1 = os.path.join(work_dir, "norm1.mp4")
186
+ norm2 = os.path.join(work_dir, "norm2.mp4")
187
+ target_w, target_h = RES_MAP.get(resolution_choice, (1280, 720))
188
+
189
+ w1 = (d1 / total * 90) if total > 0 else 45
190
+ w2 = 90 - w1
191
+
192
+ for src, dst, base, weight, dur in [
193
+ (video1, norm1, 0, w1, d1),
194
+ (video2, norm2, w1, w2, d2),
195
+ ]:
196
+ audio_filter_str = "aresample=44100"
197
+ if change_audio_dna:
198
+ audio_filter_str = build_audio_filter()
199
+
200
+ cmd = [
201
+ "ffmpeg", "-y", "-threads", CPU_THREADS, "-i", src,
202
+ "-vf", f"scale={target_w}:{target_h}:force_original_aspect_ratio=decrease:flags=fast_bilinear,"
203
+ f"pad={target_w}:{target_h}:(ow-iw)/2:(oh-ih)/2,setsar=1",
204
+ "-af", audio_filter_str,
205
+ "-r", "30",
206
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "26",
207
+ "-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2",
208
+ dst
209
+ ]
210
+ ok, err = run_ffmpeg_with_progress(cmd, code, task_id, dur, base, weight)
211
+ if not ok:
212
+ return False, f"Normalize error: {err}"
213
+
214
+ _update_task(code, task_id, progress=95)
215
+
216
+ # Merge normalized videos first
217
+ list_file = os.path.join(work_dir, "list2.txt")
218
+ with open(list_file, "w") as f:
219
+ f.write(f"file '{norm1}'\n")
220
+ f.write(f"file '{norm2}'\n")
221
+
222
+ merged_video = os.path.join(work_dir, "merged_temp.mp4")
223
+ cmd = ["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file, "-c", "copy", merged_video]
224
+ result = subprocess.run(cmd, capture_output=True, text=True)
225
+ if result.returncode != 0:
226
+ return False, f"Merge error: {result.stderr[-400:]}"
227
+
228
+ # Add BGM if server par BGM file exist karta hai
229
+ if BGM_FILE:
230
+ bgm_loop = os.path.join(work_dir, "bgm_loop.mp3")
231
+ # Loop BGM to match video duration
232
+ loop_cmd = [
233
+ "ffmpeg", "-y", "-i", BGM_FILE,
234
+ "-filter_complex", f"aloop=loop=-1:size=2e9,atrim=0:{total}",
235
+ "-c:a", "mp3", "-b:a", "128k", bgm_loop
236
+ ]
237
+ subprocess.run(loop_cmd, capture_output=True, text=True)
238
+
239
+ # Mix BGM with video audio
240
+ vol = max(0, min(1, float(bgm_volume or 0.5)))
241
+ final_cmd = [
242
+ "ffmpeg", "-y", "-i", merged_video, "-i", bgm_loop,
243
+ "-filter_complex", f"[0:a]volume=1.0[a0];[1:a]volume={vol}[a1];[a0][a1]amix=inputs=2:duration=longest",
244
+ "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", output_path
245
+ ]
246
+ subprocess.run(final_cmd, capture_output=True, text=True)
247
+ else:
248
+ shutil.copy(merged_video, output_path)
249
+
250
+ dna_note = " + audio thoda change kiya" if change_audio_dna else ""
251
+ bgm_note = " + BGM add kiya" if BGM_FILE else ""
252
+ return True, f"Merge ho gaya!{dna_note}{bgm_note}"
253
+
254
+ except Exception as e:
255
+ return False, f"Error: {str(e)}"
256
+
257
+
258
+ def run_job_background(code, task_id, v1_saved, v2_saved, resize_mode, resolution_choice, change_audio_dna, bgm_volume, out_name):
259
+ task_dir = os.path.join(BASE_DIR, code, task_id)
260
+ output_path = os.path.join(task_dir, out_name)
261
+ try:
262
+ ok, msg = do_merge(v1_saved, v2_saved, resize_mode, resolution_choice, change_audio_dna, bgm_volume, task_dir, output_path, code, task_id)
263
+ if ok and os.path.exists(output_path):
264
+ size_mb = round(os.path.getsize(output_path) / (1024 * 1024), 2)
265
+ _update_task(code, task_id, status="done", message=msg, output=output_path, progress=100,
266
+ size_mb=size_mb, finished_ts=time.time())
267
+ else:
268
+ _update_task(code, task_id, status="error", message=msg, finished_ts=time.time())
269
+ except Exception as e:
270
+ _update_task(code, task_id, status="error", message=f"Error: {str(e)}", finished_ts=time.time())
271
+
272
+
273
+ def submit_job(video1, video2, resize_mode, resolution_choice, change_audio_dna, bgm_volume, custom_code):
274
+ _cleanup_old_tasks()
275
+
276
+ if video1 is None or video2 is None:
277
+ return "❌ Dono videos upload karo pehle!", ""
278
+
279
+ code = (custom_code or "").strip().upper()
280
+ code = "".join(c for c in code if c.isalnum())[:20]
281
+ if not code:
282
+ code = random_code()
283
+
284
+ task_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
285
+ task_dir = os.path.join(BASE_DIR, code, task_id)
286
+ os.makedirs(task_dir, exist_ok=True)
287
+
288
+ v1_saved = os.path.join(task_dir, "input1" + os.path.splitext(video1)[1])
289
+ v2_saved = os.path.join(task_dir, "input2" + os.path.splitext(video2)[1])
290
+ shutil.copy(video1, v1_saved)
291
+ shutil.copy(video2, v2_saved)
292
+
293
+ out_name = f"output_{task_id}.mp4"
294
+ display_name = os.path.basename(video1)
295
+
296
+ task = {
297
+ "id": task_id, "status": "queued", "message": "Queue me hai...",
298
+ "progress": 0, "speed": 0, "fps": 0, "eta_seconds": None,
299
+ "elapsed": 0, "total_dur": 0, "size_mb": None,
300
+ "display_name": display_name, "audio_dna": change_audio_dna,
301
+ "created_ts": time.time(), "created_at": time.strftime("%H:%M:%S"),
302
+ "output": None,
303
+ }
304
+ _add_task(code, task)
305
+
306
+ t = threading.Thread(
307
+ target=run_job_background,
308
+ args=(code, task_id, v1_saved, v2_saved, resize_mode, resolution_choice, change_audio_dna, bgm_volume, out_name),
309
+ daemon=True
310
+ )
311
+ t.start()
312
+
313
+ return (f"βœ… Queue mein add hua! Task ID: **{task_id}**\n\n"
314
+ f"Job Code: **{code}** β€” isi code se dobara bhi submit kar sakta hai, "
315
+ f"aur isi code se status/history bhi check hoga."), code
316
+
317
+
318
+ def progress_badge_html(pct, status):
319
+ pct = max(0, min(100, pct or 0))
320
+ if status == "done":
321
+ return f'<span style="background:#16a34a;color:white;padding:4px 12px;border-radius:14px;font-weight:bold;">βœ… Done</span>'
322
+ elif status == "error":
323
+ return f'<span style="background:#dc2626;color:white;padding:4px 12px;border-radius:14px;font-weight:bold;">❌ Error</span>'
324
+ elif status == "queued":
325
+ return f'<span style="background:#6b7280;color:white;padding:4px 12px;border-radius:14px;font-weight:bold;">⏳ Queued</span>'
326
+ else:
327
+ return f'<span style="background:#2563eb;color:white;padding:4px 12px;border-radius:14px;font-weight:bold;">βš™οΈ {pct}%</span>'
328
+
329
+
330
+ def render_task_card(t):
331
+ badge = progress_badge_html(t.get("progress", 0), t.get("status"))
332
+ name = t.get("display_name", "video.mp4")
333
+ when = t.get("created_at", "")
334
+ dna_tag = ' <span style="color:#818cf8;">🎡 Audio changed</span>' if t.get("audio_dna") else ""
335
+
336
+ detail = ""
337
+ if t.get("status") == "processing":
338
+ speed = t.get("speed", 0)
339
+ fps = t.get("fps", 0)
340
+ elapsed = t.get("elapsed", 0)
341
+ total_dur = t.get("total_dur", 0)
342
+ eta = t.get("eta_seconds")
343
+ eta_txt = f"{int(eta)}s" if eta is not None else "..."
344
+ detail = (f'<div style="color:#9ca3af;font-family:monospace;margin-top:4px;">'
345
+ f'⚑ {t.get("progress",0)}% | {elapsed}s/{total_dur}s | Speed: {speed}x | FPS: {fps} | ETA: {eta_txt}'
346
+ f'</div>')
347
+ elif t.get("status") == "done":
348
+ size = t.get("size_mb", "?")
349
+ detail = f'<div style="color:#9ca3af;">Size: {size} MB</div>'
350
+ elif t.get("status") == "error":
351
+ detail = f'<div style="color:#f87171;">{t.get("message","")}</div>'
352
+
353
+ return f"""
354
+ <div style="background:#1a1a2e;border-radius:10px;padding:14px;margin-bottom:10px;">
355
+ <div style="display:flex;align-items:center;gap:10px;">
356
+ {badge}
357
+ <span style="color:white;font-family:monospace;font-size:16px;">{name}</span>
358
+ </div>
359
+ <div style="color:#6b7280;font-size:12px;margin-top:4px;">{when}</div>
360
+ {dna_tag}
361
+ {detail}
362
+ </div>
363
+ """
364
+
365
+
366
+ def check_status(code):
367
+ if not code or not code.strip():
368
+ return "⚠️ Code daalo pehle", "", None
369
+ code = "".join(c for c in code.strip().upper() if c.isalnum())
370
+ jobs = _load_jobs()
371
+ tasks = jobs.get(code, [])
372
+ if not tasks:
373
+ return f"❌ Code '{code}' ke liye koi task nahi mila.", "", None
374
+
375
+ tasks_sorted = sorted(tasks, key=lambda t: t.get("created_ts", 0), reverse=True)
376
+ queued = sum(1 for t in tasks if t.get("status") == "queued")
377
+ processing = sum(1 for t in tasks if t.get("status") == "processing")
378
+ done = sum(1 for t in tasks if t.get("status") == "done")
379
+ error = sum(1 for t in tasks if t.get("status") == "error")
380
+
381
+ summary = f"πŸ“Š **{code}** β€” Queue: {queued} waiting | {processing} processing | {done} done | {error} error"
382
+ cards_html = "".join(render_task_card(t) for t in tasks_sorted)
383
+
384
+ latest_done = next((t for t in tasks_sorted if t.get("status") == "done" and t.get("output")), None)
385
+ video_out = latest_done["output"] if latest_done else None
386
+
387
+ return summary, cards_html, video_out
388
+
389
+
390
+ def get_history():
391
+ _cleanup_old_tasks()
392
+ jobs = _load_jobs()
393
+ if not jobs:
394
+ return "Abhi tak koi job nahi hai. (History 5 ghante baad auto-delete ho jati hai)"
395
+ rows = ["| Code | Waiting | Processing | Done | Error |", "|------|---------|-----------|------|-------|"]
396
+ for code, tasks in jobs.items():
397
+ q = sum(1 for t in tasks if t.get("status") == "queued")
398
+ p = sum(1 for t in tasks if t.get("status") == "processing")
399
+ d = sum(1 for t in tasks if t.get("status") == "done")
400
+ e = sum(1 for t in tasks if t.get("status") == "error")
401
+ rows.append(f"| {code} | {q} | {p} | {d} | {e} |")
402
+ rows.append("\n_History 5 ghante baad automatically delete ho jati hai._")
403
+ return "\n".join(rows)
404
+
405
+
406
+ # ---------- UI ----------
407
+ with gr.Blocks(title="Video Merger - 2 Videos Jodo") as demo:
408
+ gr.Markdown("# 🎬 Video Merger\nDo videos upload karo, background me merge hoga β€” tab band karo chahe!")
409
+
410
+ with gr.Tab("πŸ”— Queue Mein Add Karo"):
411
+ with gr.Row():
412
+ video1_input = gr.Video(label="Pehla Video")
413
+ video2_input = gr.Video(label="Dusra Video")
414
+
415
+ bgm_volume = gr.Slider(
416
+ minimum=0.0, maximum=1.0, value=0.5, step=0.05,
417
+ label="BGM Volume (0.0 = mute, 1.0 = full) β€” server par bgm.mp4/bgm.mp3 hona chahiye"
418
+ )
419
+ if BGM_FILE:
420
+ gr.Markdown(f"βœ… Server par BGM file mil gaya: `{os.path.basename(BGM_FILE)}` β€” auto-apply hoga!")
421
+ else:
422
+ gr.Markdown("⚠️ Server par koi BGM file nahi mili. `bgm.mp4` ya `bgm.mp3` upload karo app ke saath.")
423
+
424
+ resize_mode = gr.Radio(
425
+ choices=["Fast (same resolution/codec required)", "Safe (auto resize + re-encode, thoda slow)"],
426
+ value="Fast (same resolution/codec required)",
427
+ label="Mode"
428
+ )
429
+ resolution_choice = gr.Radio(
430
+ choices=list(RES_MAP.keys()),
431
+ value="480p (sabse fast)",
432
+ label="Resolution (sirf Safe mode ke liye)"
433
+ )
434
+ change_audio_dna = gr.Checkbox(
435
+ label="🎡 Audio thoda change karo (pitch/EQ/echo β€” halka variation)",
436
+ value=False
437
+ )
438
+ custom_code_input = gr.Textbox(
439
+ label="Job Code (naya banao ya purana daalo β€” usi code pe bar-bar submit kar sakta hai)",
440
+ placeholder="jaise: DEEPU01"
441
+ )
442
+
443
+ submit_btn = gr.Button("βž• Queue Mein Add Karo", variant="primary")
444
+ submit_status = gr.Markdown()
445
+ code_box = gr.Textbox(label="Tera Job Code (save kar le, dobara isi se submit/status check kar sakta hai)", interactive=False)
446
+
447
+ submit_btn.click(
448
+ fn=submit_job,
449
+ inputs=[video1_input, video2_input, resize_mode, resolution_choice, change_audio_dna, bgm_volume, custom_code_input],
450
+ outputs=[submit_status, code_box]
451
+ )
452
+
453
+ with gr.Tab("πŸ“‹ Task History & Downloads"):
454
+ gr.Markdown("Apna Job Code daalo β€” live digital meter, sab tasks ki history aur download yahin milega.\n\n_(History 5 ghante baad auto-delete ho jati hai)_")
455
+ code_input = gr.Textbox(label="Job Code")
456
+ check_btn = gr.Button("πŸ”„ Refresh History", variant="primary")
457
+ status_output = gr.Markdown()
458
+ cards_output = gr.HTML()
459
+ result_video = gr.Video(label="Latest Ready Video")
460
+
461
+ check_btn.click(fn=check_status, inputs=[code_input], outputs=[status_output, cards_output, result_video])
462
+
463
+ timer = gr.Timer(2.0, active=True)
464
+ timer.tick(fn=check_status, inputs=[code_input], outputs=[status_output, cards_output, result_video])
465
+
466
+ with gr.Tab("πŸ“œ Sabki History"):
467
+ gr.Markdown("Sabhi codes ki summary (5 ghante ke andar wali)")
468
+ history_output = gr.Markdown()
469
+ refresh_all_btn = gr.Button("πŸ”„ Refresh")
470
+ refresh_all_btn.click(fn=get_history, inputs=[], outputs=[history_output])
471
+ demo.load(fn=get_history, inputs=[], outputs=[history_output])
472
+
473
+ if __name__ == "__main__":
474
+ demo.launch()