import gradio as gr import subprocess import os import re import uuid import threading import time import shutil import hashlib from datetime import datetime, timedelta # --------------------------------------------------------------------------- # 🔒 SECRET ACCESS KEY GATE # --------------------------------------------------------------------------- SECRET_KEY_HASH = "1c107e39306b96cb5e21d4ef3e968eb51397f516313c6a9b522add0193b995e1" ACCESS_KEY = os.environ.get("APP_ACCESS_KEY", "") def is_authorized(): if not ACCESS_KEY or SECRET_KEY_HASH == "PUT_YOUR_SHA256_HASH_HERE": return False return hashlib.sha256(ACCESS_KEY.encode()).hexdigest() == SECRET_KEY_HASH # Directory for storing jobs and results JOBS_DIR = "jobs" os.makedirs(JOBS_DIR, exist_ok=True) # Dictionary to keep track of job status jobs_db = {} # Cache: GPU (nvenc) available hai ya nahi _GPU_AVAILABLE = None def gpu_available(): """Check karta hai ki h264_nvenc encoder actually kaam karta hai is machine par.""" global _GPU_AVAILABLE if _GPU_AVAILABLE is not None: return _GPU_AVAILABLE try: test = subprocess.run( ['ffmpeg', '-f', 'lavfi', '-i', 'color=black:s=64x64:d=0.1', '-c:v', 'h264_nvenc', '-f', 'null', '-'], capture_output=True, text=True, timeout=20 ) _GPU_AVAILABLE = (test.returncode == 0) except Exception: _GPU_AVAILABLE = False return _GPU_AVAILABLE def cleanup_old_jobs(): """Removes jobs and files older than 7 hours.""" while True: now = datetime.now() to_delete = [] for job_code, info in jobs_db.items(): if now - info["timestamp"] > timedelta(hours=7): to_delete.append(job_code) for job_code in to_delete: info = jobs_db.pop(job_code) if info["output"] and os.path.exists(info["output"]): try: os.remove(info["output"]) except: pass input_file = os.path.join(JOBS_DIR, f"input_{job_code}.mp4") if os.path.exists(input_file): try: os.remove(input_file) except: pass time.sleep(3600) threading.Thread(target=cleanup_old_jobs, daemon=True).start() def get_duration(path): """Get total duration (seconds) of input video. Tries multiple methods.""" duration = 0.0 try: result = subprocess.run( ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrapper=1:nokey=1', path], capture_output=True, text=True, timeout=30 ) val = result.stdout.strip() if val and val != 'N/A': duration = float(val) except Exception: duration = 0.0 if duration <= 0: try: result = subprocess.run( ['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=duration', '-of', 'default=noprint_wrapper=1:nokey=1', path], capture_output=True, text=True, timeout=30 ) val = result.stdout.strip() if val and val != 'N/A': duration = float(val) except Exception: pass if duration <= 0: try: result = subprocess.run( ['ffprobe', '-v', 'error', '-count_packets', '-show_entries', 'stream=duration', '-of', 'default=noprint_wrapper=1:nokey=1', path], capture_output=True, text=True, timeout=60 ) for line in result.stdout.strip().split('\n'): line = line.strip() if line and line != 'N/A': try: val = float(line) if val > duration: duration = val except ValueError: pass except Exception: pass if duration <= 0: try: result = subprocess.run( ['ffmpeg', '-i', path], capture_output=True, text=True, timeout=30 ) match = re.search(r'Duration:\s*(\d+):(\d+):(\d+\.\d+)', result.stderr) if match: h, m, s = match.groups() duration = int(h) * 3600 + int(m) * 60 + float(s) except Exception: pass if duration <= 0: try: result = subprocess.run( ['ffmpeg', '-sseof', '-1', '-i', path, '-f', 'null', '-'], capture_output=True, text=True, timeout=60 ) matches = re.findall(r'time=(\d\d:\d\d:\d\d\.\d+)', result.stderr) if matches: last_time = matches[-1] h, m, s = last_time.split(":") duration = int(h) * 3600 + int(m) * 60 + float(s) except Exception: pass return duration def get_embedded_title(path): """Try to read an embedded 'title' tag from the video's metadata.""" try: result = subprocess.run( ['ffprobe', '-v', 'error', '-show_entries', 'format_tags=title', '-of', 'default=noprint_wrapper=1:nokey=1', path], capture_output=True, text=True ) title = result.stdout.strip() if title: return title result = subprocess.run( ['ffprobe', '-v', 'error', '-show_entries', 'stream_tags=title', '-of', 'default=noprint_wrapper=1:nokey=1', path], capture_output=True, text=True ) title = result.stdout.strip().split("\n")[0].strip() return title if title else None except: return None def time_str_to_seconds(t): """Convert ffmpeg HH:MM:SS.xx time string to seconds.""" try: h, m, s = t.split(":") return int(h) * 3600 + int(m) * 60 + float(s) except: return 0.0 def format_eta(seconds): if seconds <= 0 or seconds != seconds: return "--:--" seconds = int(seconds) m, s = divmod(seconds, 60) h, m = divmod(m, 60) if h: return f"{h:02d}:{m:02d}:{s:02d}" return f"{m:02d}:{s:02d}" STATS_RE = re.compile( r'frame=\s*(\d+).*?fps=\s*([\d.]+).*?time=(\d\d:\d\d:\d\d\.\d+).*?speed=\s*([\d.]+)x' ) def clean_base_name(name): """Cleans an original filename into a safe base string.""" base, _ext = os.path.splitext(name) cleaned = re.sub(r'[^a-zA-Z0-9]+', '_', base).strip('_') return cleaned if cleaned else "video" # --------------------------------------------------------------------------- # 🎛️ EQUALIZER PRESETS & FUNCTIONS # --------------------------------------------------------------------------- EQ_PRESETS = { "Custom": {"60Hz": 0, "230Hz": 0, "910Hz": 0, "3.6kHz": 0, "14kHz": 0}, "Normal": {"60Hz": 3, "230Hz": 0, "910Hz": 0, "3.6kHz": 0, "14kHz": 3}, "Classical": {"60Hz": 0, "230Hz": 0, "910Hz": 0, "3.6kHz": 5, "14kHz": 7}, "Dance": {"60Hz": 9, "230Hz": 7, "910Hz": 2, "3.6kHz": 0, "14kHz": 1}, "Flat": {"60Hz": 0, "230Hz": 0, "910Hz": 0, "3.6kHz": 0, "14kHz": 0}, "Bass Boost": {"60Hz": 12, "230Hz": 8, "910Hz": 0, "3.6kHz": -2, "14kHz": -4}, "Vocal": {"60Hz": -3, "230Hz": 0, "910Hz": 5, "3.6kHz": 7, "14kHz": 3}, "Rock": {"60Hz": 8, "230Hz": 4, "910Hz": -2, "3.6kHz": 4, "14kHz": 8}, } def build_eq_filter(hz60, hz230, hz910, khz3_6, khz14): """Build FFmpeg superequalizer/firequalizer filter string from 5-band EQ values.""" eq_filter = ( f"firequalizer=gain_entry=" f"'entry(60,{hz60});entry(230,{hz230});entry(910,{hz910});" f"entry(3600,{khz3_6});entry(14000,{khz14})'" ) return eq_filter def _video_encode_args(variant, crf_or_cq): """GPU (nvenc) ya CPU (libx264) ke liye encoder args deta hai.""" if variant == "gpu": return ['-c:v', 'h264_nvenc', '-preset', 'p1', '-tune', 'll', '-cq', str(crf_or_cq)] return ['-vcodec', 'libx264', '-preset', 'ultrafast', '-crf', str(crf_or_cq)] def eq_preview(input_video, hz60, hz230, hz910, khz3_6, khz14): """Generate a 10-second preview clip with the EQ settings applied.""" if input_video is None: return None, "⚠️ Pehle video upload karo!" eq_filter = build_eq_filter(hz60, hz230, hz910, khz3_6, khz14) preview_id = str(uuid.uuid4())[:6] preview_path = os.path.join(JOBS_DIR, f"eq_preview_{preview_id}.mp4") total_dur = get_duration(input_video) start_time = min(5.0, max(0, total_dur - 15)) if total_dur > 15 else 0 variants = ["gpu", "cpu"] if gpu_available() else ["cpu"] last_err = "" for variant in variants: command = [ 'ffmpeg', '-y', '-ss', str(start_time), '-i', input_video, '-t', '10', '-af', eq_filter, ] + _video_encode_args(variant, 28) + [ '-acodec', 'aac', '-b:a', '128k', '-ar', '44100', '-pix_fmt', 'yuv420p', preview_path ] try: result = subprocess.run(command, capture_output=True, text=True, timeout=30) if result.returncode == 0 and os.path.exists(preview_path): tag = "GPU" if variant == "gpu" else "CPU" return preview_path, f"✅ Preview ready ({tag})! EQ: 60Hz={hz60}dB, 230Hz={hz230}dB, 910Hz={hz910}dB, 3.6kHz={khz3_6}dB, 14kHz={khz14}dB" last_err = result.stderr[-200:] except subprocess.TimeoutExpired: last_err = "Preview timeout — video too heavy for quick preview." except Exception as e: last_err = str(e) return None, f"❌ Preview failed: {last_err}" def apply_preset(preset_name): """Returns slider values for a given preset.""" preset = EQ_PRESETS.get(preset_name, EQ_PRESETS["Flat"]) return preset["60Hz"], preset["230Hz"], preset["910Hz"], preset["3.6kHz"], preset["14kHz"] # --------------------------------------------------------------------------- # MAIN PROCESSING # --------------------------------------------------------------------------- def _build_main_command(variant, input_path, mode, output_filename, obf_opts): mirror = obf_opts.get("mirror", False) zoom = obf_opts.get("zoom", False) color = obf_opts.get("color", False) noise = obf_opts.get("noise", False) speed = obf_opts.get("speed", False) v_actions = [] if mirror: v_actions.append("hflip") if zoom: v_actions.append("crop=iw*0.96:ih*0.96,scale=iw:ih") if color: v_actions.append("eq=saturation=1.05:contrast=1.02:brightness=0.01") if noise: v_actions.append("noise=alls=3:allf=t") if speed: v_actions.append("setpts=PTS/1.006") filter_prep = "" main_v0 = "[0:v]" main_v1 = "[1:v]" if v_actions: v_filter_chain = ",".join(v_actions) filter_prep += f"[0:v]{v_filter_chain}[vobf0];" main_v0 = "[vobf0]" if mode == "Full Screen": filter_prep += f"[1:v]{v_filter_chain}[vobf1];" main_v1 = "[vobf1]" if mode == "Full Screen": fc = filter_prep + ( f"{main_v0}scale=iw:ih[v2];{main_v1}crop=in_w/2:in_h/2,boxblur=1:1,scale=iw*2:ih*2[v1];" f"[v2][v1]overlay=1:enable='gte(mod(t,5),3)':x=0:y=0;" f"[0:a]atempo=1,bass=frequency=200:gain=-90,volume=+20dB,aecho=1:0.6:2:0.4," f"bass=g=3:f=110:w=20,bass=g=10:f=500:w=20,bass=g=3:f=300:w=30,bass=g=10:f=110:w=20,bass=g=20:f=110:w=40," f"firequalizer=gain_entry='entry(0,-23);entry(250,-11.5);entry(6000,0);entry(12000,8);entry(16000,16)'," f"compand=attacks=7:decays=1:points=-90/-90 -70/-60 -15/-15 0/-10:soft-knee=1:volume=-70:gain=3," f"pan=stereo| FL < FL + 0.5*FC + 0.6*BL + 0.6*SL | FR < FR + 2*FC + 1*BR + 2*SR,highpass=f=300,lowpass=f=700,volume=6[a1];" f"amovie=Bg2.mp4:loop=9999,volume=1[a2];[a1][a2]amix=duration=shortest" ) if speed: fc = fc.replace("atempo=1", "atempo=1.006") return [ 'ffmpeg', '-y', '-i', input_path, '-ss', '4', '-i', input_path, '-filter_complex', fc, ] + _video_encode_args(variant, 30) + [ '-pix_fmt', 'yuv420p', '-r', '30', '-g', '60', '-b:v', '1550k', '-shortest', '-acodec', 'aac', '-b:a', '128k', '-ar', '44100', '-threads', '0', output_filename ] else: fc = filter_prep + ( f"{main_v0}scale=410:280,setsar=1:1[vbox];" f"movie=ibg.mp4:loop=999,setpts=N/(FRAME_RATE*TB)[bg];" f"[bg][vbox]overlay=shortest=1:x=18:y=102[vmain2];" f"[0:a]atempo=1,bass=frequency=200:gain=-90,volume=+20dB,aecho=1:0.6:2:0.4," f"bass=g=3:f=110:w=20,bass=g=10:f=500:w=20,bass=g=3:f=300:w=30,bass=g=10:f=110:w=20,bass=g=20:f=110:w=40," f"firequalizer=gain_entry='entry(0,-23);entry(250,-11.5);entry(6000,0);entry(12000,8);entry(16000,16)'," f"compand=attacks=7:decays=1:points=-90/-90 -70/-60 -15/-15 0/-10:soft-knee=1:volume=-70:gain=3," f"pan=stereo| FL < FL + 0.5*FC + 0.6*BL + 0.6*SL | FR < FR + 2*FC + 1*BR + 2*SR,highpass=f=300,lowpass=f=700,volume=6[a1];" f"amovie=Bg2.mp4:loop=9999,volume=1[a2];[a1][a2]amix=duration=shortest[amain]" ) if speed: fc = fc.replace("atempo=1", "atempo=1.006") return [ 'ffmpeg', '-y', '-i', input_path, '-i', 'blocker.mp4', '-i', 'Myvideo.mp4', '-filter_complex', fc, '-map', '[vmain2]', '-map', '[amain]', ] + _video_encode_args(variant, 26) + [ '-pix_fmt', 'yuv420p', '-r', '30', '-g', '60', '-b:v', '2000k', '-shortest', '-acodec', 'aac', '-b:a', '128k', '-ar', '44100', '-threads', '0', output_filename ] def _run_and_track(command, job_code, total_duration, start_time): process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) max_time_seen = 0.0 for line in process.stdout: jobs_db[job_code]["logs"] = (jobs_db[job_code]["logs"] + line)[-2000:] match = STATS_RE.search(line) if match: frame, fps, cur_time_str, speed = match.groups() cur_seconds = time_str_to_seconds(cur_time_str) fps_val = float(fps) speed_val = float(speed) if cur_seconds > max_time_seen: max_time_seen = cur_seconds progress = 0.0 eta = 0.0 if total_duration > 0: progress = min(99.9, (cur_seconds / total_duration) * 100) remaining = max(0.0, total_duration - cur_seconds) if speed_val > 0: eta = remaining / speed_val jobs_db[job_code].update({ "fps": fps_val, "speed": speed_val, "cur_time": cur_seconds, "progress": progress, "eta": eta, "elapsed": time.time() - start_time, }) if total_duration <= 0 and max_time_seen > 0: jobs_db[job_code]["duration"] = max_time_seen process.wait() return process.returncode == 0, max_time_seen def run_ffmpeg(job_code, input_path, mode, clean_base, obf_opts): output_filename = os.path.join(JOBS_DIR, f"{clean_base}_{job_code}.mp4") total_duration = get_duration(input_path) jobs_db[job_code]["duration"] = total_duration start_time = time.time() max_time_seen = 0.0 ok = False variants = ["gpu", "cpu"] if gpu_available() else ["cpu"] for variant in variants: command = _build_main_command(variant, input_path, mode, output_filename, obf_opts) ok, seen = _run_and_track(command, job_code, total_duration, start_time) max_time_seen = max(max_time_seen, seen) if ok: break jobs_db[job_code]["progress"] = 0.0 if ok: jobs_db[job_code]["status"] = "Completed" jobs_db[job_code]["output"] = output_filename jobs_db[job_code]["progress"] = 100.0 jobs_db[job_code]["eta"] = 0.0 if jobs_db[job_code]["duration"] <= 0 and max_time_seen > 0: jobs_db[job_code]["duration"] = max_time_seen else: jobs_db[job_code]["status"] = "Failed" jobs_db[job_code]["total_time_taken"] = time.time() - start_time if os.path.exists(input_path): os.remove(input_path) VALID_VIDEO_EXT = {".mp4", ".mov", ".mkv", ".webm", ".avi", ".m4v", ".3gp", ".flv"} def start_job(input_video, mode, selected_obf_features): if input_video is None: return "⚠️ Please upload a video.", None ext = os.path.splitext(input_video)[1].lower() if ext not in VALID_VIDEO_EXT: return f"⚠️ '{ext}' supported nahi hai. Sirf video files upload karo.", None job_code = str(uuid.uuid4())[:8].upper() original_name = os.path.basename(input_video) temp_input = os.path.join(JOBS_DIR, f"input_{job_code}.mp4") shutil.copy(input_video, temp_input) embedded_title = get_embedded_title(temp_input) display_name = embedded_title if embedded_title else original_name jobs_db[job_code] = { "status": "Processing", "output": None, "logs": "Job started...\n", "timestamp": datetime.now(), "mode": mode, "filename": display_name, "duration": 0.0, "cur_time": 0.0, "fps": 0.0, "speed": 0.0, "progress": 0.0, "eta": 0.0, "elapsed": 0.0, "total_time_taken": 0.0, } obf_opts = { "mirror": "Mirror Flip" in selected_obf_features, "zoom": "Zoom/Crop Slightly" in selected_obf_features, "color": "Color Shift" in selected_obf_features, "noise": "Subtle Noise/Grain" in selected_obf_features, "speed": "Speed Micro-variation" in selected_obf_features } thread = threading.Thread(target=run_ffmpeg, args=(job_code, temp_input, mode, clean_base_name(display_name), obf_opts)) thread.start() return f"🚀 Job started! Your Job Code is: {job_code}\nCopy this code to check status later.", job_code def build_meter_html(info): if not info: return "
No active job yet.
" status = info["status"] progress = info.get("progress", 0.0) fps = info.get("fps", 0.0) speed = info.get("speed", 0.0) cur_time = info.get("cur_time", 0.0) total_duration = info.get("duration", 0.0) eta = info.get("eta", 0.0) elapsed = info.get("elapsed", info.get("total_time_taken", 0.0)) status_color = "#00ff9d" if status == "Completed" else ("#ff4d4d" if status == "Failed" else "#00c3ff") return f"""
● {status}
{progress:.1f}%
FPS
{fps:.1f}
Speed
{speed:.2f}x
Processed
{format_eta(cur_time)}
Total
{format_eta(total_duration)}
ETA
{format_eta(eta)}
Elapsed
{format_eta(elapsed)}
""" def live_meter_update(job_code): if not job_code: return build_meter_html(None) info = jobs_db.get(job_code.strip().upper()) return build_meter_html(info) def check_status(job_code): job_code = job_code.strip().upper() if job_code not in jobs_db: return "Job Code not found.", None, "", build_meter_html(None) info = jobs_db[job_code] status_msg = f"Status: {info['status']}\nFile: {info.get('filename', '-')}\nMode: {info['mode']}\nTime: {info['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}" return status_msg, info["output"], info["logs"], build_meter_html(info) def get_history(): if not jobs_db: return "No history available." history = "| Job Code | File Name | Status | Mode | Time |\n| --- | --- | --- | --- | --- |\n" for code, info in sorted(jobs_db.items(), key=lambda x: x[1]['timestamp'], reverse=True): history += f"| {code} | {info.get('filename', '-')} | {info['status']} | {info['mode']} | {info['timestamp'].strftime('%H:%M:%S')} |\n" return history # --------------------------------------------------------------------------- # PREMIUM UI / CSS # --------------------------------------------------------------------------- CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght=400;600;800&display=swap'); * { font-family: 'Poppins', sans-serif; } body, .gradio-container { background: linear-gradient(160deg, #ffffff 0%, #f3f5ff 45%, #eef0ff 100%) !important; } #rgb-title { text-align: center; font-size: 2.4rem; font-weight: 800; letter-spacing: 1px; margin: 6px 0 2px 0; background: linear-gradient(90deg, #ff0055, #ff9900, #d4c400, #22c55e, #00b8d9, #0066ff, #a855f7, #ff0055); background-size: 400% 400%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; animation: rgbFlow 4s linear infinite; } @keyframes rgbFlow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } #subtitle { text-align: center; color: #4b5066 !important; margin-bottom: 18px; font-size: 0.95rem; } .gr-panel, .block, .form { background: #ffffff !important; border: 1px solid #e3e6f5 !important; border-radius: 18px !important; box-shadow: 0 4px 24px rgba(100,110,220,0.08) !important; } button.primary, .gr-button-primary { background: linear-gradient(90deg, #ff0055, #7000ff) !important; border: none !important; color: white !important; font-weight: 700 !important; box-shadow: 0 6px 20px rgba(112,0,255,0.28); transition: transform 0.15s ease; } button.primary:hover { transform: translateY(-2px); } .meter-box { background: #ffffff; border: 1px solid #e3e6f5; border-radius: 18px; padding: 18px 20px; box-shadow: 0 6px 28px rgba(100,110,220,0.10); } .meter-idle { text-align: center; color: #8a90ab; font-size: 0.9rem; padding: 30px; } .meter-status { font-weight: 800; font-size: 1rem; margin-bottom: 10px; letter-spacing: 0.5px; } .progress-track { width: 100%; height: 14px; border-radius: 10px; background: #eef0fb; overflow: hidden; margin-bottom: 6px; } .progress-fill { height: 100%; border-radius: 10px; background: linear-gradient(90deg, #00b8d9, #7000ff, #ff0055); background-size: 300% 100%; animation: rgbFlow 3s linear infinite; transition: width 0.4s ease; } .progress-pct { text-align: right; font-size: 0.8rem; color: #6b7190; margin-bottom: 14px; } .stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; } .stat-card { background: #f6f7fd; border: 1px solid #e3e6f5; border-radius: 12px; padding: 10px 6px; text-align: center; } .stat-label { font-size: 0.72rem; color: #6b7190; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; font-weight: 700; } .stat-value { font-size: 1.15rem; font-weight: 800; color: #0077aa; } .stat-card:nth-child(2n) .stat-value { color: #d6003d; } #made-by { text-align: center; margin-top: 22px; font-size: 1rem; font-weight: 800; background: linear-gradient(90deg, #ff0055, #ff9900, #d4c400, #22c55e, #00b8d9, #0066ff, #a855f7, #ff0055); background-size: 400% 400%; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; animation: rgbFlow 4s linear infinite; } .eq-section { background: linear-gradient(135deg, #1a1c2e 0%, #2d1f4e 100%); border-radius: 18px; padding: 20px; margin: 10px 0; } .eq-preset-btn { border-radius: 20px !important; font-weight: 700 !important; font-size: 0.85rem !important; } .gradio-container * { color: #1a1c2e; } .gradio-container .tab-nav button, .gradio-container button { color: #1a1c2e !important; } button.primary, button.primary * { color: #ffffff !important; } .gradio-container label span, .gradio-container .label-wrap span { color: #2b2e45 !important; font-weight: 600 !important; } .gradio-container table, .gradio-container th, .gradio-container td { color: #1a1c2e !important; border-color: #e3e6f5 !important; } .gradio-container thead th { background: #eef0fb !important; } .gradio-container tbody tr:nth-child(odd) { background: #f8f9fe !important; } .gradio-container input, .gradio-container textarea { color: #111 !important; background: #ffffff !important; } """ LOCKED_CSS = """ body, .gradio-container { background: linear-gradient(160deg, #ffffff 0%, #f3f5ff 50%, #eef0ff 100%) !important; } #lock-screen { text-align: center; margin-top: 15vh; padding: 40px 20px; } #lock-title { font-size: 2rem; font-weight: 800; color: #d6003d; } #lock-sub { color: #4b5066; margin-top: 12px; font-size: 1rem; } """ if is_authorized(): with gr.Blocks(title="Deepu Video Suite") as demo: gr.HTML("
⚡ MOBILE SERIAL CODING — PRO SUITE ⚡
") gr.HTML("
Upload video to process in background. Use Job Code to retrieve it later.
") with gr.Tabs(): with gr.Tab("🆕 New Task"): with gr.Row(): with gr.Column(): input_vid = gr.File( label="Upload Video (Files chooser, not Gallery)", file_types=None, type="filepath" ) mode_select = gr.Radio(["Full Screen", "Half Screen"], label="Processing Mode", value="Half Screen") opt_obfuscation = gr.CheckboxGroup( choices=[ "Mirror Flip", "Zoom/Crop Slightly", "Color Shift", "Subtle Noise/Grain", "Speed Micro-variation" ], label="🛠️ Fingerprint Obfuscation Settings", value=[] ) start_btn = gr.Button("🚀 Start Background Process", variant="primary") job_output = gr.Markdown("Job status will appear here.") job_code_display = gr.Textbox(label="Your Job Code", interactive=False) with gr.Column(): gr.Markdown("### 📊 Live Speed Meter") live_meter = gr.HTML(build_meter_html(None)) meter_timer = gr.Timer(1.0, active=True) start_btn.click( fn=start_job, inputs=[input_vid, mode_select, opt_obfuscation], outputs=[job_output, job_code_display] ) meter_timer.tick(fn=live_meter_update, inputs=[job_code_display], outputs=[live_meter]) # ----- 🎛️ EQUALIZER TAB ----- with gr.Tab("🎛️ Equalizer"): gr.Markdown("### 🎛️ Custom Audio Equalizer") gr.Markdown("Upload video → Adjust EQ sliders → Preview 10sec clip → Apply to full video") with gr.Row(): with gr.Column(scale=1): eq_video_input = gr.File( label="Upload Video for EQ", file_types=None, type="filepath" ) gr.Markdown("**Presets:**") with gr.Row(): preset_custom = gr.Button("Custom", size="sm") preset_normal = gr.Button("Normal", size="sm") preset_classical = gr.Button("Classical", size="sm") preset_dance = gr.Button("Dance", size="sm") with gr.Row(): preset_flat = gr.Button("Flat", size="sm") preset_bass = gr.Button("Bass Boost", size="sm") preset_vocal = gr.Button("Vocal", size="sm") preset_rock = gr.Button("Rock", size="sm") gr.Markdown("**5-Band EQ (dB):**") eq_60 = gr.Slider(-15, 15, value=0, step=1, label="60 Hz (Sub Bass)") eq_230 = gr.Slider(-15, 15, value=0, step=1, label="230 Hz (Bass)") eq_910 = gr.Slider(-15, 15, value=0, step=1, label="910 Hz (Mid)") eq_3600 = gr.Slider(-15, 15, value=0, step=1, label="3.6 kHz (Upper Mid)") eq_14000 = gr.Slider(-15, 15, value=0, step=1, label="14 kHz (Treble)") with gr.Row(): preview_btn = gr.Button("👁️ Preview (10 sec)", variant="primary") apply_full_btn = gr.Button("✅ Apply to Full Video", variant="primary") with gr.Column(scale=1): gr.Markdown("**Preview Result:**") eq_preview_video = gr.Video(label="EQ Preview (10 sec clip)") eq_status = gr.Markdown("Adjust sliders and click Preview to hear the result.") eq_full_output = gr.Video(label="Full EQ Video (after Apply)") eq_full_status = gr.Markdown("") eq_sliders = [eq_60, eq_230, eq_910, eq_3600, eq_14000] preset_custom.click(fn=lambda: apply_preset("Custom"), outputs=eq_sliders) preset_normal.click(fn=lambda: apply_preset("Normal"), outputs=eq_sliders) preset_classical.click(fn=lambda: apply_preset("Classical"), outputs=eq_sliders) preset_dance.click(fn=lambda: apply_preset("Dance"), outputs=eq_sliders) preset_flat.click(fn=lambda: apply_preset("Flat"), outputs=eq_sliders) preset_bass.click(fn=lambda: apply_preset("Bass Boost"), outputs=eq_sliders) preset_vocal.click(fn=lambda: apply_preset("Vocal"), outputs=eq_sliders) preset_rock.click(fn=lambda: apply_preset("Rock"), outputs=eq_sliders) preview_btn.click( fn=eq_preview, inputs=[eq_video_input, eq_60, eq_230, eq_910, eq_3600, eq_14000], outputs=[eq_preview_video, eq_status] ) def eq_apply_full(input_video, hz60, hz230, hz910, khz3_6, khz14): if input_video is None: return None, "⚠️ Pehle video upload karo!" eq_filter = build_eq_filter(hz60, hz230, hz910, khz3_6, khz14) output_id = str(uuid.uuid4())[:6] output_path = os.path.join(JOBS_DIR, f"eq_full_{output_id}.mp4") variants = ["gpu", "cpu"] if gpu_available() else ["cpu"] last_err = "" for variant in variants: command = [ 'ffmpeg', '-y', '-i', input_video, '-af', eq_filter, ] + _video_encode_args(variant, 28) + [ '-acodec', 'aac', '-b:a', '128k', '-ar', '44100', '-pix_fmt', 'yuv420p', output_path ] try: result = subprocess.run(command, capture_output=True, text=True, timeout=300) if result.returncode == 0 and os.path.exists(output_path): tag = "GPU" if variant == "gpu" else "CPU" return output_path, f"✅ Full video with EQ applied ({tag})! Download ready." last_err = result.stderr[-200:] except subprocess.TimeoutExpired: last_err = "Timeout — video bahut bada hai." except Exception as e: last_err = str(e) return None, f"❌ Failed: {last_err}" apply_full_btn.click( fn=eq_apply_full, inputs=[eq_video_input, eq_60, eq_230, eq_910, eq_3600, eq_14000], outputs=[eq_full_output, eq_full_status] ) with gr.Tab("🔍 Retrieve Job"): with gr.Row(): with gr.Column(): input_code = gr.Textbox(label="Enter Job Code") check_btn = gr.Button("🔍 Check Status", variant="primary") status_display = gr.Markdown("Enter code to see details.") output_vid = gr.Video(label="Download Processed Video") with gr.Column(): gr.Markdown("### 📊 Live Speed Meter") retrieve_meter = gr.HTML(build_meter_html(None)) logs_display = gr.Textbox(label="Job Logs", lines=10, interactive=False) check_btn.click(fn=check_status, inputs=input_code, outputs=[status_display, output_vid, logs_display, retrieve_meter]) with gr.Tab("📜 History"): history_btn = gr.Button("🔄 Refresh History", variant="primary") history_table = gr.Markdown("Click refresh to see recent jobs.") gr.Markdown("*Note: History is automatically cleared every 7 hours.*") history_btn.click(fn=get_history, outputs=history_table) gr.HTML("
✨ Made with ❤️ by DEEPU ✨
") # FIX: Is main authorized wale section mein share=True missing tha, ab sahi kar diya hai demo.launch(theme=gr.themes.Soft(), share=True, css=CUSTOM_CSS) else: with gr.Blocks() as demo: gr.HTML( "
" "
🔒 Access Denied
" "
Ye app ek valid Access Key ke bina nahi chalega.
" "Space Settings me sahi APP_ACCESS_KEY secret set karo.
" "
" ) demo.launch(theme=gr.themes.Soft(), share=True, css=CUSTOM_CSS)