Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from PIL import Image | |
| import os | |
| import torch | |
| import librosa | |
| from scipy.ndimage import gaussian_filter | |
| # Import backends | |
| from img import pipe | |
| from new import extract_frames, analyze | |
| from test import CRNN, extract_features | |
| VOICE_MODEL_PATH = "crnn_transformer_model.pth" | |
| voice_device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| voice_model = None | |
| if os.path.exists(VOICE_MODEL_PATH): | |
| try: | |
| voice_model = CRNN().to(voice_device) | |
| voice_model.load_state_dict(torch.load(VOICE_MODEL_PATH, map_location=voice_device)) | |
| voice_model.eval() | |
| except Exception as e: | |
| print(f"Error loading voice model: {e}") | |
| API_KEY = "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" | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Orbitron:wght@400;700;900&display=swap'); | |
| :root { | |
| --bg-color: #050508; | |
| --cyan: #00f5ff; | |
| --red: #ff0033; | |
| --purple: #9b30ff; | |
| --dim-white: #e0e0ff; | |
| --font-heading: 'Orbitron', sans-serif; | |
| --font-body: 'Inter', sans-serif; | |
| } | |
| body, .gradio-container { | |
| background-color: var(--bg-color) !important; | |
| font-family: var(--font-body) !important; | |
| color: var(--dim-white) !important; | |
| margin: 0; padding: 0; | |
| } | |
| @keyframes sectionFadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } | |
| .hero-wrapper { animation: sectionFadeIn 1s ease-out forwards; } | |
| .tabs-wrapper { opacity: 0; animation: sectionFadeIn 1s ease-out 0.5s forwards; } | |
| .footer-wrapper { opacity: 0; animation: sectionFadeIn 1s ease-out 1s forwards; } | |
| .scanlines { | |
| position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; | |
| background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06)); | |
| background-size: 100% 2px, 3px 100%; | |
| z-index: 9999; pointer-events: none; opacity: 0.3; | |
| } | |
| .noise { | |
| position: fixed; top:0; left:0; width:100vw; height:100vh; | |
| pointer-events: none; z-index: 9998; opacity: 0.05; | |
| background: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); | |
| } | |
| .sys-online { | |
| position: fixed; top: 20px; right: 30px; z-index: 10000; | |
| font-family: var(--font-heading); color: var(--red); font-size: 0.8em; | |
| display: flex; align-items: center; gap: 8px; text-shadow: 0 0 5px var(--red); letter-spacing: 2px; | |
| } | |
| .sys-online .dot { | |
| width: 10px; height: 10px; border-radius: 50%; background: var(--red); | |
| box-shadow: 0 0 10px var(--red); animation: pulseDot 1.5s infinite; | |
| } | |
| .watermark { | |
| position: fixed; bottom: 20px; right: 30px; z-index: 10000; | |
| font-family: var(--font-body); color: rgba(255,255,255,0.2); font-size: 0.7em; | |
| } | |
| .hero-wrapper { | |
| position: relative; padding: 80px 20px; border-radius: 10px; | |
| border: 1px solid rgba(0, 245, 255, 0.2); margin-bottom: 30px; | |
| text-align: center; overflow: hidden; box-shadow: inset 0 0 50px rgba(0,0,0,0.8); | |
| background: #050508; | |
| } | |
| #particle-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.5; } | |
| .hero-eye-bg { | |
| position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| z-index: 2; opacity: 0.1; animation: eyePulse 10s infinite alternate ease-in-out; | |
| } | |
| .hero-eye-bg svg { width: 400px; fill: var(--cyan); } | |
| @keyframes eyePulse { 0% { transform: translate(-50%, -50%) scale(1); opacity: 0.05; } 100% { transform: translate(-50%, -50%) scale(1.1); opacity: 0.15; } } | |
| .hero-gradient { | |
| position: absolute; top:0; left:0; width:100%; height:100%; z-index: 3; | |
| background: radial-gradient(circle, rgba(5,5,8,0.3) 0%, rgba(5,5,8,1) 100%); | |
| } | |
| .hero-content { position: relative; z-index: 4; } | |
| .hero-title { | |
| font-family: var(--font-heading); font-size: 6rem; font-weight: 900; margin: 0; color: white; | |
| -webkit-text-stroke: 2px var(--cyan); text-shadow: 0 0 20px var(--cyan), 0 0 40px var(--cyan); letter-spacing: 5px; | |
| } | |
| .hero-subtitle { | |
| font-family: var(--font-body); font-size: 1.5rem; color: var(--cyan); margin: 10px 0 30px; letter-spacing: 2px; text-transform: uppercase; | |
| } | |
| .badges-container { display: flex; justify-content: center; gap: 20px; flex-wrap: wrap; } | |
| .hero-badge { | |
| padding: 8px 16px; border: 1px solid var(--cyan); border-radius: 50px; background: rgba(0,245,255,0.1); | |
| color: var(--cyan); font-family: var(--font-heading); font-size: 0.9em; text-shadow: 0 0 5px var(--cyan); box-shadow: 0 0 10px rgba(0,245,255,0.2); | |
| } | |
| div.tabs { border: none !important; background: transparent !important; } | |
| div.tab-nav { border-bottom: 2px solid rgba(0,245,255,0.2) !important; gap: 10px !important; margin-bottom: 30px !important; } | |
| div.tab-nav button { | |
| font-family: var(--font-heading) !important; background: transparent !important; border: 1px solid transparent !important; | |
| color: rgba(255,255,255,0.5) !important; text-transform: uppercase !important; letter-spacing: 2px !important; | |
| padding: 15px 30px !important; transition: all 0.3s !important; border-radius: 0 !important; border-top: 2px solid transparent !important; | |
| } | |
| div.tab-nav button:hover { color: var(--cyan) !important; text-shadow: 0 0 10px var(--cyan) !important; } | |
| div.tab-nav button.selected { | |
| background: rgba(0,245,255,0.15) !important; color: #000 !important; text-shadow: none !important; | |
| font-weight: 700 !important; border-top: 2px solid var(--cyan) !important; box-shadow: inset 0 10px 20px rgba(0,245,255,0.3) !important; | |
| } | |
| .upload-zone-wrapper { | |
| position: relative; padding: 5px; border-radius: 4px; | |
| background: repeating-linear-gradient(0deg, rgba(255,255,255,0.02) 0px, rgba(255,255,255,0.02) 1px, transparent 1px, transparent 20px), | |
| repeating-linear-gradient(90deg, rgba(255,255,255,0.02) 0px, rgba(255,255,255,0.02) 1px, transparent 1px, transparent 20px); | |
| cursor: crosshair !important; | |
| } | |
| .upload-zone-wrapper::before, .upload-zone-wrapper::after { content: ''; position: absolute; width: 30px; height: 30px; border: 2px solid var(--cyan); transition: all 0.3s; } | |
| .upload-zone-wrapper::before { top: 0; left: 0; border-right: none; border-bottom: none; } | |
| .upload-zone-wrapper::after { bottom: 0; right: 0; border-left: none; border-top: none; } | |
| .upload-zone-wrapper:hover::before { top: -5px; left: -5px; } | |
| .upload-zone-wrapper:hover::after { bottom: -5px; right: -5px; } | |
| .upload-zone-wrapper:hover { background-color: rgba(0,245,255,0.05); } | |
| .upload-box { border: none !important; background: transparent !important; } | |
| .upload-box > div { | |
| border: 2px dashed rgba(0,245,255,0.4) !important; | |
| border-image: repeating-linear-gradient(45deg, var(--cyan), var(--cyan) 10px, transparent 10px, transparent 20px) 1 !important; | |
| animation: dashRotate 20s linear infinite !important; | |
| } | |
| @keyframes dashRotate { 100% { border-image-source: repeating-linear-gradient(405deg, var(--cyan), var(--cyan) 10px, transparent 10px, transparent 20px); } } | |
| .upload-box button.upload-button::before { | |
| content: "INITIATE SCAN"; position: absolute; top: 60%; left: 50%; transform: translate(-50%, -50%); | |
| color: var(--cyan); font-family: var(--font-heading); font-size: 1.5em; letter-spacing: 2px; | |
| pointer-events: none; text-shadow: 0 0 10px var(--cyan); | |
| } | |
| button.primary { | |
| height: 60px !important; background: linear-gradient(90deg, var(--cyan), var(--purple)) !important; | |
| border: none !important; border-radius: 0 !important; font-family: var(--font-heading) !important; | |
| letter-spacing: 4px !important; font-weight: 700 !important; font-size: 1.2rem !important; | |
| color: #fff !important; text-shadow: 0 0 5px rgba(255,255,255,0.5) !important; | |
| box-shadow: 0 0 20px rgba(0,245,255,0.4) !important; position: relative; overflow: hidden; | |
| animation: pulseBtn 2s infinite alternate; | |
| } | |
| @keyframes pulseBtn { 0% { box-shadow: 0 0 10px rgba(0,245,255,0.4); } 100% { box-shadow: 0 0 30px rgba(0,245,255,0.8); } } | |
| button.primary:hover { background: linear-gradient(90deg, var(--purple), var(--cyan)) !important; transform: translateY(-2px) !important; } | |
| button.primary::after { | |
| content: ''; position: absolute; top:0; left:-100%; width: 50%; height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); animation: shimmer 3s infinite; | |
| } | |
| @keyframes shimmer { 100% { left: 200%; } } | |
| .verdict-card { | |
| padding: 30px; border-radius: 8px; text-align: center; position: relative; overflow: hidden; | |
| font-family: var(--font-heading); box-shadow: inset 0 0 20px rgba(0,0,0,0.5); min-height: 200px; | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| } | |
| .real-card { border: 2px solid var(--cyan); background: rgba(0, 245, 255, 0.05); box-shadow: 0 0 30px rgba(0,245,255,0.3); } | |
| .check-icon { font-size: 4em; color: var(--cyan); animation: popIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; transform: scale(0); margin-bottom: 15px; } | |
| @keyframes popIn { to { transform: scale(1) rotate(360deg); } } | |
| .real-card .verdict-title { font-size: 2.5em; color: var(--cyan); text-shadow: 0 0 15px var(--cyan); margin-bottom: 20px;} | |
| .real-card .progress-bg { width: 80%; height: 10px; background: rgba(255,255,255,0.1); border-radius: 5px; overflow: hidden; margin-bottom: 10px;} | |
| .real-fill { height: 100%; background: var(--cyan); box-shadow: 0 0 10px var(--cyan); transition: width 1.5s ease-out; } | |
| .bg-particles { position: absolute; width: 100%; height: 100%; top: 0; left: 0; pointer-events: none; background-image: radial-gradient(circle, var(--cyan) 1px, transparent 1px); background-size: 20px 20px; opacity: 0.2; animation: particleDrift 10s linear infinite; } | |
| @keyframes particleDrift { 0% {background-position: 0 0;} 100% {background-position: 100px 100px;} } | |
| .fake-card { border: 2px solid var(--red); background: rgba(255, 0, 51, 0.08); box-shadow: 0 0 30px rgba(255,0,51,0.4); animation: flashRed 1s ease-out; } | |
| @keyframes flashRed { 0% { background: rgba(255,0,51,0.5); } 100% { background: rgba(255,0,51,0.08); } } | |
| .warning-icon { font-size: 4em; color: var(--red); margin-bottom: 15px; animation: pulseIcon 0.5s infinite alternate; } | |
| @keyframes pulseIcon { from { transform: scale(1); } to { transform: scale(1.1); } } | |
| .fake-card .verdict-title { font-size: 2.5em; color: var(--red); text-shadow: 0 0 10px var(--red); margin-bottom: 20px; } | |
| .fake-card .progress-bg { width: 80%; height: 10px; background: rgba(255,255,255,0.1); border-radius: 5px; overflow: hidden; margin-bottom: 10px; } | |
| .fake-fill { height: 100%; background: var(--red); box-shadow: 0 0 10px var(--red); transition: width 1.5s ease-out; } | |
| .bg-scanlines { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; background: repeating-linear-gradient(0deg, transparent, transparent 10px, rgba(255,0,51,0.2) 10px, rgba(255,0,51,0.2) 11px); animation: scanSlide 5s linear infinite; } | |
| @keyframes scanSlide { 0% {background-position: 0 0;} 100% {background-position: 0 100px;} } | |
| .glitch { position: relative; } | |
| .glitch::before, .glitch::after { content: attr(data-text); position: absolute; top: 0; left: 0; width: 100%; height: 100%; } | |
| .glitch::before { left: 2px; text-shadow: -2px 0 var(--cyan); animation: glitch-anim-1 2s infinite linear alternate-reverse; } | |
| .glitch::after { left: -2px; text-shadow: -2px 0 var(--purple); animation: glitch-anim-2 3s infinite linear alternate-reverse; } | |
| @keyframes glitch-anim-1 { 0% { clip-path: inset(20% 0 80% 0); } 20% { clip-path: inset(60% 0 10% 0); } 40% { clip-path: inset(40% 0 50% 0); } 60% { clip-path: inset(80% 0 5% 0); } 80% { clip-path: inset(10% 0 70% 0); } 100% { clip-path: inset(30% 0 50% 0); } } | |
| @keyframes glitch-anim-2 { 0% { clip-path: inset(10% 0 60% 0); } 20% { clip-path: inset(30% 0 20% 0); } 40% { clip-path: inset(70% 0 10% 0); } 60% { clip-path: inset(20% 0 50% 0); } 80% { clip-path: inset(50% 0 30% 0); } 100% { clip-path: inset(80% 0 5% 0); } } | |
| .conf-text { font-family: var(--font-body); font-weight: bold; font-size: 1.1em; color: var(--dim-white); } | |
| .table-wrap table { border-collapse: collapse; width: 100%; font-family: monospace; } | |
| .table-wrap thead th { background: #0a0a14 !important; color: var(--cyan) !important; text-transform: uppercase; letter-spacing: 1px; border-bottom: 1px solid var(--cyan); } | |
| .table-wrap tbody tr { background: #0a0a14 !important; transition: all 0.2s; } | |
| .table-wrap tbody tr:nth-child(even) { background: #0f0f1e !important; } | |
| .table-wrap tbody tr.row-fake { background: rgba(255, 0, 51, 0.15) !important; } | |
| .table-wrap tbody tr.row-real { background: rgba(0, 245, 255, 0.1) !important; } | |
| .table-wrap tbody td { border-color: rgba(255,255,255,0.05) !important; } | |
| .footer-wrapper { | |
| margin-top: 50px; padding: 20px 0; border-top: 1px solid var(--cyan); | |
| display: flex; justify-content: space-between; align-items: center; | |
| font-size: 0.8em; font-family: var(--font-heading); background: rgba(10,10,20,0.5); | |
| position: relative; overflow: hidden; padding-left: 20px; padding-right: 20px; | |
| } | |
| .footer-scan { | |
| position: absolute; top:0; left:0; width:100%; height:10%; | |
| background: var(--cyan); opacity: 0.2; animation: footerScanline 4s linear infinite; | |
| } | |
| @keyframes footerScanline { 0% { top: -10%; } 100% { top: 110%; } } | |
| .footer-center { display: flex; gap: 15px; } | |
| .status-indicator { display: flex; align-items: center; gap: 5px; color: #a0a0c0; } | |
| .status-indicator .circle { width: 8px; height: 8px; background: var(--cyan); border-radius: 50%; animation: pulseDot 2s infinite alternate; } | |
| .ripple { position: absolute; border-radius: 50%; transform: scale(0); animation: ripple-effect 0.6s linear; background: rgba(255, 255, 255, 0.4); pointer-events: none;} | |
| @keyframes ripple-effect { to { transform: scale(4); opacity: 0; } } | |
| @keyframes pulseDot { 0% { opacity: 0.5; box-shadow: 0 0 2px var(--cyan); } 100% { opacity: 1; box-shadow: 0 0 10px var(--cyan); } } | |
| """ | |
| def generate_real_verdict(conf): | |
| return f""" | |
| <div class='verdict-card real-card'> | |
| <div class='check-icon'>✅</div> | |
| <div class='verdict-title'>✦ AUTHENTIC ✦</div> | |
| <div class='progress-bg'><div class='progress-fill real-fill' style='width: 0%;' data-target='{conf}'></div></div> | |
| <div class='conf-text'>{conf:.1f}% Confidence</div> | |
| <div class='bg-particles'></div> | |
| </div> | |
| """ | |
| def generate_fake_verdict(conf): | |
| return f""" | |
| <div class='verdict-card fake-card'> | |
| <div class='warning-icon'>âš </div> | |
| <div class='verdict-title glitch' data-text='âš SYNTHETIC DETECTED âš '>âš SYNTHETIC DETECTED âš </div> | |
| <div class='progress-bg'><div class='progress-fill fake-fill' style='width: 0%;' data-target='{conf}'></div></div> | |
| <div class='conf-text'>{conf:.1f}% Confidence</div> | |
| <div class='bg-scanlines'></div> | |
| </div> | |
| """ | |
| def process_image(img_path): | |
| if not img_path: | |
| return "<h2 style='color: gray; text-align: center'>Please upload an image</h2>" | |
| try: | |
| img = Image.open(img_path).convert("RGB") | |
| result = pipe(img) | |
| top = max(result, key=lambda x: x["score"]) | |
| label = top["label"].upper() | |
| conf = top["score"] * 100 | |
| is_fake = "FAKE" in label | |
| return generate_fake_verdict(conf) if is_fake else generate_real_verdict(conf) | |
| except Exception as e: | |
| return f"<h2 style='color: red; text-align: center'>Error Occurred: {str(e)}</h2>" | |
| def process_video(video_path, num_frames): | |
| if not video_path: | |
| return "<h2 style='color: gray; text-align: center'>No Video Provided</h2>", None, [] | |
| try: | |
| frames = extract_frames(video_path, int(num_frames)) | |
| if not frames: | |
| return "<h2 style='color: gray; text-align: center'>Error: No Frames</h2>", None, [] | |
| results, scores = analyze(frames, API_KEY) | |
| if not results: | |
| return "<h2 style='color: gray; text-align: center'>Unknown (No Faces Detected)</h2>", None, [] | |
| mean_fake = float(np.mean(scores)) | |
| mean_real = 1.0 - mean_fake | |
| fake_votes = sum(1 for r in results if r[3] == "FAKE") | |
| real_votes = len(results) - fake_votes | |
| is_fake = (fake_votes >= real_votes) or (mean_fake >= 0.4) | |
| confidence = (mean_fake if is_fake else mean_real) * 100 | |
| verdict_html = generate_fake_verdict(confidence) if is_fake else generate_real_verdict(confidence) | |
| table_data = [] | |
| for ts, real, fake, label in results: | |
| status = "FAKE" if label == "FAKE" else "REAL" | |
| table_data.append([f"{ts:.2f}s", f"{real*100:.1f}%", f"{fake*100:.1f}%", status]) | |
| fig, ax = plt.subplots(figsize=(10, 5)) | |
| fig.patch.set_facecolor('#080810') | |
| ax.set_facecolor('#080810') | |
| ax.tick_params(colors='#9090b0') | |
| ax.xaxis.label.set_color('#9090b0') | |
| ax.yaxis.label.set_color('#9090b0') | |
| ax.title.set_color('white') | |
| ax.yaxis.grid(True, color='#00f5ff', alpha=0.1) | |
| for spine in ax.spines.values(): spine.set_edgecolor((0, 245/255, 1.0, 0.2)) | |
| timestamps = [f"{r[0]:.2f}s" for r in results] | |
| real_sc = [r[1] * 100 for r in results] | |
| fake_sc = [r[2] * 100 for r in results] | |
| x = np.arange(len(timestamps)) | |
| width = 0.4 | |
| ax.bar(x - width/2, real_sc, width, label='Real %', color='#00f5ff', edgecolor='#00f5ff') | |
| ax.bar(x + width/2, fake_sc, width, label='Fake %', color='#ff0033', edgecolor='#ff0033') | |
| ax.set_ylabel('Confidence (%)') | |
| ax.set_title('Real vs Fake Scores Per Frame') | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(timestamps, rotation=45) | |
| legend = ax.legend(facecolor='#080810', edgecolor=(0, 245/255, 1.0, 0.2)) | |
| for text in legend.get_texts(): text.set_color('white') | |
| fig.tight_layout() | |
| return verdict_html, fig, table_data | |
| except Exception as e: | |
| return f"<h2 style='color: var(--danger); text-align: center'>Error Occurred: {str(e)}</h2>", None, [] | |
| def process_voice(audio_path): | |
| if not audio_path: | |
| return "<h2 style='color: gray; text-align: center'>No Audio Provided</h2>", None | |
| if voice_model is None: | |
| return "<h2 style='color: red; text-align: center'>Voice Model Not Loaded</h2>", None | |
| try: | |
| try: | |
| audio, sr = librosa.load(audio_path, sr=16000) | |
| if len(audio) == 0: audio = np.zeros(16000) | |
| except Exception as e: | |
| audio = np.zeros(16000) | |
| mel, lfcc = extract_features(audio) | |
| features = np.stack([mel, lfcc], axis=0) | |
| feat_t = torch.tensor(features, dtype=torch.float32).unsqueeze(0).to(voice_device) | |
| with torch.no_grad(): | |
| prob = torch.softmax(voice_model(feat_t), dim=1) | |
| fake_prob = prob[0][1].item() | |
| real_prob = prob[0][0].item() | |
| threshold = 0.2 | |
| is_fake = fake_prob > threshold | |
| confidence = (fake_prob if is_fake else real_prob) * 100 | |
| verdict_html = generate_fake_verdict(confidence) if is_fake else generate_real_verdict(confidence) | |
| voice_model.eval() | |
| feat_grad = feat_t.clone().requires_grad_(True) | |
| output = voice_model(feat_grad) | |
| pred_class = output.argmax(dim=1).item() | |
| voice_model.zero_grad() | |
| output[0, pred_class].backward() | |
| saliency = feat_grad.grad.abs().mean(dim=1).squeeze().cpu().numpy() | |
| saliency_smooth = gaussian_filter(saliency, sigma=1.5) | |
| saliency_norm = (saliency_smooth - saliency_smooth.min()) / (saliency_smooth.max() - saliency_smooth.min() + 1e-8) | |
| extent = [0, 200 * 512 / 16000, 0, 8] | |
| fig, axes = plt.subplots(3, 1, figsize=(10, 8)) | |
| fig.patch.set_facecolor('#080810') | |
| for ax in axes: | |
| ax.set_facecolor('#080810') | |
| ax.tick_params(colors='#9090b0') | |
| ax.xaxis.label.set_color('#9090b0') | |
| ax.yaxis.label.set_color('#9090b0') | |
| ax.title.set_color('white') | |
| for spine in ax.spines.values(): spine.set_edgecolor((0, 245/255, 1.0, 0.2)) | |
| im0 = axes[0].imshow(mel, aspect='auto', origin='lower', cmap='magma', extent=extent) | |
| axes[0].set_title("Channel 1: Mel Spectrogram") | |
| cb0 = plt.colorbar(im0, ax=axes[0]) | |
| cb0.ax.yaxis.set_tick_params(color='white') | |
| im1 = axes[1].imshow(lfcc, aspect='auto', origin='lower', cmap='viridis', extent=extent) | |
| axes[1].set_title("Channel 2: LFCC") | |
| cb1 = plt.colorbar(im1, ax=axes[1]) | |
| cb1.ax.yaxis.set_tick_params(color='white') | |
| im2 = axes[2].imshow(saliency_norm, aspect='auto', origin='lower', cmap='hot', extent=extent) | |
| axes[2].set_title("Saliency Map") | |
| cb2 = plt.colorbar(im2, ax=axes[2]) | |
| cb2.ax.yaxis.set_tick_params(color='white') | |
| fig.tight_layout() | |
| return verdict_html, fig | |
| except Exception as e: | |
| return f"<h2 style='color: var(--danger); text-align: center'>Error Occurred: {str(e)}</h2>", None | |
| js_script = """ | |
| <script> | |
| const cvs = document.getElementById('particle-canvas'); | |
| if(cvs) { | |
| const ctx = cvs.getContext('2d'); | |
| cvs.width = cvs.parentElement.clientWidth; cvs.height = cvs.parentElement.clientHeight; | |
| let particles = []; | |
| for(let i=0; i<50; i++) particles.push({ x: Math.random()*cvs.width, y: Math.random()*cvs.height, vx: (Math.random()-0.5)*0.5, vy: (Math.random()-0.5)*0.5 }); | |
| function draw() { | |
| ctx.clearRect(0,0,cvs.width,cvs.height); ctx.fillStyle = '#00f5ff'; | |
| particles.forEach(p => { | |
| p.x += p.vx; p.y += p.vy; | |
| if(p.x<0||p.x>cvs.width) p.vx*=-1; if(p.y<0||p.y>cvs.height) p.vy*=-1; | |
| ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI*2); ctx.fill(); | |
| }); | |
| ctx.strokeStyle = 'rgba(0, 245, 255, 0.1)'; | |
| for(let i=0; i<particles.length; i++) { | |
| for(let j=i+1; j<particles.length; j++) { | |
| let dx = particles[i].x - particles[j].x; let dy = particles[i].y - particles[j].y; | |
| if(Math.sqrt(dx*dx + dy*dy) < 100) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); } | |
| } | |
| } | |
| requestAnimationFrame(draw); | |
| } | |
| draw(); | |
| } | |
| const subtitle = document.getElementById('typewriter'); | |
| if(subtitle) { | |
| const texts = ["Unmasking AI-Generated Images", "Exposing Synthetic Voices", "Revealing Manipulated Videos"]; | |
| let textIdx = 0; let charIdx = 0; let isDeleting = false; | |
| function typeWriter() { | |
| const txt = texts[textIdx]; | |
| if(isDeleting) charIdx--; else charIdx++; | |
| subtitle.textContent = txt.substring(0, charIdx); | |
| let speed = isDeleting ? 30 : 70; | |
| if(!isDeleting && charIdx === txt.length) { speed = 2000; isDeleting = true; } | |
| else if(isDeleting && charIdx === 0) { isDeleting = false; textIdx = (textIdx+1)%texts.length; speed = 500; } | |
| setTimeout(typeWriter, speed); | |
| } | |
| typeWriter(); | |
| } | |
| const observer = new MutationObserver(() => { | |
| document.querySelectorAll('.progress-fill').forEach(el => { | |
| if(el.style.width === '0%') setTimeout(() => { el.style.width = el.getAttribute('data-target') + '%'; }, 100); | |
| }); | |
| document.querySelectorAll('table tbody tr').forEach(tr => { | |
| if(!tr.classList.contains('row-fake') && !tr.classList.contains('row-real')) { | |
| const tds = tr.querySelectorAll('td'); | |
| if(tds.length) { | |
| const lastText = tds[tds.length-1].textContent.trim(); | |
| if(lastText === 'FAKE') tr.classList.add('row-fake'); | |
| else if(lastText === 'REAL') tr.classList.add('row-real'); | |
| } | |
| } | |
| }); | |
| // Add crosshair cursor styles dynamically to gradio upload wrappers | |
| document.querySelectorAll('.upload-box, input[type="file"]').forEach(el => { | |
| el.style.cursor = 'crosshair'; | |
| }); | |
| }); | |
| observer.observe(document.body, {childList: true, subtree: true}); | |
| document.addEventListener('click', function(e) { | |
| const btn = e.target.closest('button'); | |
| if(btn) { | |
| let r = document.createElement('span'); r.className = 'ripple'; | |
| r.style.left = (e.clientX - btn.getBoundingClientRect().left) + 'px'; | |
| r.style.top = (e.clientY - btn.getBoundingClientRect().top) + 'px'; | |
| btn.appendChild(r); setTimeout(() => r.remove(), 600); | |
| } | |
| }); | |
| const clock = document.getElementById('live-clock'); | |
| if(clock) { setInterval(() => { clock.textContent = new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; }, 1000); } | |
| </script> | |
| """ | |
| with gr.Blocks(title="VeriFake Neural Surveillance", theme=gr.themes.Base(), css=custom_css) as app: | |
| gr.HTML('<div class="scanlines"></div><div class="noise"></div><div class="sys-online"><div class="dot"></div>SYSTEM ONLINE</div><div class="watermark">VeriFake v1.0</div>') | |
| gr.HTML(''' | |
| <div class="hero-wrapper"> | |
| <canvas id="particle-canvas"></canvas> | |
| <div class="hero-eye-bg"><svg viewBox="0 0 24 24"><path d="M12 4C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg></div> | |
| <div class="hero-gradient"></div> | |
| <div class="hero-content"> | |
| <h1 class="hero-title">VERIFAKE</h1> | |
| <div class="hero-subtitle" id="typewriter"></div> | |
| <div class="badges-container"> | |
| <div class="hero-badge">âš¡ Powered by Deep Learning</div> | |
| <div class="hero-badge">🧠Neural Detection</div> | |
| <div class="hero-badge">🔒 Real-Time Analysis</div> | |
| </div> | |
| </div> | |
| </div> | |
| ''') | |
| with gr.Tabs(elem_classes="tabs"): | |
| with gr.Tab("[ 🖼 IMAGE ]"): | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes="upload-zone-wrapper"): | |
| img_input = gr.Image(type="filepath", label="Upload Image", elem_classes="upload-box") | |
| img_analyze_btn = gr.Button("â–¶ ANALYZE", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| img_verdict = gr.HTML(value="<div class='verdict-card'><h2 style='color: #666;'>Awaiting Visual Data...</h2></div>") | |
| img_analyze_btn.click(fn=process_image, inputs=[img_input], outputs=[img_verdict]) | |
| with gr.Tab("[ 🎬 VIDEO ]"): | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes="upload-zone-wrapper"): | |
| video_input = gr.Video(label="Upload Video", elem_classes="upload-box") | |
| frames_slider = gr.Slider(minimum=5, maximum=30, value=15, step=1, label="Frames to Analyze") | |
| vid_analyze_btn = gr.Button("â–¶ ANALYZE", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| vid_verdict = gr.HTML(value="<div class='verdict-card'><h2 style='color: #666;'>Awaiting Video Data...</h2></div>") | |
| vid_chart = gr.Plot() | |
| vid_results = gr.Dataframe(headers=["Timestamp", "Real %", "Fake %", "Label"], datatype=["str", "str", "str", "str"], elem_classes="table-wrap") | |
| vid_analyze_btn.click(fn=process_video, inputs=[video_input, frames_slider], outputs=[vid_verdict, vid_chart, vid_results]) | |
| with gr.Tab("[ 🎙 VOICE ]"): | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes="upload-zone-wrapper"): | |
| audio_input = gr.Audio(type="filepath", label="Upload Audio", elem_classes="upload-box") | |
| voice_analyze_btn = gr.Button("â–¶ ANALYZE", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| voice_verdict = gr.HTML(value="<div class='verdict-card'><h2 style='color: #666;'>Awaiting Audio Data...</h2></div>") | |
| voice_chart = gr.Plot() | |
| voice_analyze_btn.click(fn=process_voice, inputs=[audio_input], outputs=[voice_verdict, voice_chart]) | |
| gr.HTML(''' | |
| <div class="footer-wrapper"> | |
| <div class="footer-scan"></div> | |
| <div>VeriFake © 2025 | Neural Surveillance System</div> | |
| <div class="footer-center"> | |
| <div class="status-indicator"><div class="circle"></div> IMAGE ENGINE</div> | |
| <div class="status-indicator"><div class="circle"></div> VOICE ENGINE</div> | |
| <div class="status-indicator"><div class="circle"></div> VIDEO ENGINE</div> | |
| </div> | |
| <div id="live-clock">00:00:00 UTC</div> | |
| </div> | |
| ''') | |
| gr.HTML(js_script) | |
| if __name__ == "__main__": | |
| app.launch() | |