VeriFake / app.py
Saksham5iu's picture
Upload 5 files
3e5a17e verified
Raw
History Blame Contribute Delete
29.2 kB
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 &copy; 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()