DFDA / app.py
jayanthbottu's picture
Update app.py
8f9e536 verified
Raw History Blame Contribute Delete
49.3 kB
# app.py β€” DFDA Streamlit app with Premium UI/UX Design
import streamlit as st
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchaudio
import torchaudio.transforms as T
import torchvision.transforms as transforms
import numpy as np
import cv2
from pathlib import Path
from PIL import Image
import plotly.graph_objects as go
import plotly.express as px
import io
import timm
import warnings
import tempfile
import shutil
import os
import importlib.util
import traceback
warnings.filterwarnings('ignore')
# Robust import for download_models
_download_mod = None
try:
import download_models as _dm
download_from_hf = _dm.download_from_hf
MODELS_DIR = _dm.MODELS_DIR
except Exception:
_base = Path(__file__).parent
candidates = [
_base / "download_models.py",
_base / "src" / "download_models.py"
]
loaded = False
tb_text = None
for cand in candidates:
if cand.exists():
try:
spec = importlib.util.spec_from_file_location("download_models", str(cand))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
download_from_hf = mod.download_from_hf
MODELS_DIR = mod.MODELS_DIR
loaded = True
break
except Exception:
tb_text = traceback.format_exc()
if not loaded:
st.error("Could not import download_models.py. Make sure it exists in the repo root or src/ folder.")
if tb_text:
st.text(tb_text)
raise ImportError("download_models import failed")
# HF repo & filenames
AUDIO_REPO = "jayanthbottu/DFDA"
AUDIO_FILENAME = "Audio.pth"
VIDEO_REPO = "jayanthbottu/DFDA"
VIDEO_FILENAME = "Video.pt"
IMAGE_REPO = "jayanthbottu/DFDA"
IMAGE_FILENAME = "Image.pt"
MODELS_DIR.mkdir(parents=True, exist_ok=True)
# ======================= PAGE CONFIG ============================
st.set_page_config(
page_title="DFDA - Deepfake Detection & Attribution",
page_icon="🎭",
layout="wide",
initial_sidebar_state="expanded"
)
# ======================= PREMIUM CSS STYLING ============================
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap');
/* ===== GLOBAL RESETS ===== */
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
header {visibility: hidden;}
/* ===== ROOT VARIABLES ===== */
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--glass-bg: rgba(255, 255, 255, 0.05);
--glass-border: rgba(255, 255, 255, 0.1);
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.3);
--shadow-xl: 0 25px 80px rgba(102, 126, 234, 0.2);
}
/* ===== MAIN CONTAINER ===== */
.main {
background: linear-gradient(180deg, #0a0e27 0%, #16213e 50%, #0a0e27 100%);
padding: 0 !important;
}
.block-container {
padding: 2rem 3rem !important;
max-width: 1400px;
}
/* ===== HERO HEADER ===== */
.hero-header {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(118, 75, 162, 0.15) 100%);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 24px;
padding: 3rem 2.5rem;
margin-bottom: 3rem;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-xl);
}
.hero-header::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(102, 126, 234, 0.15) 0%, transparent 70%);
animation: pulse 8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1) translate(0, 0); opacity: 0.5; }
50% { transform: scale(1.2) translate(-20px, 20px); opacity: 0.8; }
}
.hero-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 3.5rem;
font-weight: 700;
background: linear-gradient(135deg, #ffffff 0%, #a5b4fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 0 1rem 0;
letter-spacing: -0.02em;
position: relative;
z-index: 1;
}
.hero-subtitle {
font-size: 1.25rem;
color: rgba(255, 255, 255, 0.7);
font-weight: 400;
margin: 0;
letter-spacing: 0.02em;
position: relative;
z-index: 1;
}
.hero-badge {
display: inline-block;
background: rgba(102, 126, 234, 0.2);
border: 1px solid rgba(102, 126, 234, 0.3);
padding: 0.5rem 1.5rem;
border-radius: 50px;
font-size: 0.9rem;
font-weight: 600;
color: #a5b4fc;
margin-top: 1.5rem;
position: relative;
z-index: 1;
}
/* ===== GLASS CARDS ===== */
.glass-card {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
padding: 2rem;
margin: 1.5rem 0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
position: relative;
overflow: hidden;
}
.glass-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.5), transparent);
}
.glass-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 48px rgba(102, 126, 234, 0.2);
border-color: rgba(102, 126, 234, 0.3);
}
/* ===== FEATURE CARDS ===== */
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.feature-card {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.05) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 2rem;
position: relative;
overflow: hidden;
cursor: pointer;
}
.feature-card::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 4px;
background: var(--primary-gradient);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.feature-card:hover::after {
transform: scaleX(1);
}
.feature-icon {
font-size: 3rem;
margin-bottom: 1rem;
display: block;
filter: drop-shadow(0 4px 12px rgba(102, 126, 234, 0.3));
}
.feature-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.5rem;
font-weight: 700;
color: #ffffff;
margin: 1rem 0 0.8rem 0;
}
.feature-desc {
font-size: 1rem;
color: rgba(255, 255, 255, 0.6);
line-height: 1.6;
font-weight: 400;
}
/* ===== METRIC CARDS ===== */
.metrics-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.metric-card {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 2rem;
text-align: center;
position: relative;
overflow: hidden;
}
.metric-card::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
pointer-events: none;
}
.metric-value {
font-family: 'Space Grotesk', sans-serif;
font-size: 2.5rem;
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
display: block;
margin-bottom: 0.5rem;
position: relative;
z-index: 1;
}
.metric-label {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.5);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
position: relative;
z-index: 1;
}
/* ===== RESULT BOXES ===== */
.result-success {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, rgba(22, 163, 74, 0.05) 100%);
border: 1px solid rgba(34, 197, 94, 0.3);
border-left: 4px solid #22c55e;
border-radius: 16px;
padding: 2rem;
margin: 2rem 0;
backdrop-filter: blur(10px);
}
.result-warning {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(220, 38, 38, 0.05) 100%);
border: 1px solid rgba(239, 68, 68, 0.3);
border-left: 4px solid #ef4444;
border-radius: 16px;
padding: 2rem;
margin: 2rem 0;
backdrop-filter: blur(10px);
}
.result-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.75rem;
font-weight: 700;
color: #ffffff;
margin: 0 0 1rem 0;
}
.result-subtitle {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.7);
margin: 0.5rem 0;
}
/* ===== BUTTONS ===== */
.stButton > button {
width: 100%;
background: var(--primary-gradient);
color: white;
border: none;
border-radius: 12px;
padding: 1rem 2rem;
font-size: 1.1rem;
font-weight: 600;
letter-spacing: 0.5px;
cursor: pointer;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
}
.stButton > button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
transition: width 0.6s, height 0.6s;
}
.stButton > button:hover::before {
width: 300px;
height: 300px;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 12px 36px rgba(102, 126, 234, 0.4);
}
/* ===== FILE UPLOADER ===== */
[data-testid="stFileUploader"] {
background: rgba(255, 255, 255, 0.02);
border: 2px dashed rgba(102, 126, 234, 0.4);
border-radius: 16px;
padding: 2rem;
transition: all 0.3s ease;
}
[data-testid="stFileUploader"]:hover {
border-color: rgba(102, 126, 234, 0.7);
background: rgba(102, 126, 234, 0.05);
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.2);
}
/* ===== SIDEBAR ===== */
[data-testid="stSidebar"] {
background: linear-gradient(180deg, #0f1419 0%, #1a1f2e 100%);
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
[data-testid="stSidebar"] [data-testid="stMarkdownContainer"] {
color: rgba(255, 255, 255, 0.8);
}
/* ===== RADIO BUTTONS ===== */
.stRadio > label {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
padding: 1rem;
margin: 0.5rem 0;
cursor: pointer;
display: block;
transition: all 0.3s ease;
}
.stRadio > label:hover {
background: rgba(102, 126, 234, 0.1);
border-color: rgba(102, 126, 234, 0.3);
transform: translateX(4px);
}
/* ===== DIVIDER ===== */
hr {
border: none;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.3), transparent);
margin: 2rem 0;
}
/* ===== PROGRESS BAR ===== */
.stProgress > div > div {
background: var(--primary-gradient);
border-radius: 10px;
}
/* ===== SLIDER ===== */
.stSlider > div > div {
background: rgba(102, 126, 234, 0.2);
}
.stSlider > div > div > div {
background: var(--primary-gradient);
}
/* ===== EXPANDER ===== */
.streamlit-expanderHeader {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
padding: 1rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}
.streamlit-expanderHeader:hover {
background: rgba(102, 126, 234, 0.1);
border-color: rgba(102, 126, 234, 0.3);
}
/* ===== INFO BOXES ===== */
.info-box {
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(37, 99, 235, 0.05) 100%);
border: 1px solid rgba(59, 130, 246, 0.3);
border-left: 4px solid #3b82f6;
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
}
.info-box h3 {
font-size: 1.1rem;
font-weight: 700;
color: #93c5fd;
margin: 0 0 0.8rem 0;
}
.info-box p {
font-size: 0.95rem;
color: rgba(255, 255, 255, 0.7);
line-height: 1.6;
margin: 0;
}
/* ===== SECTION HEADERS ===== */
.section-header {
font-family: 'Space Grotesk', sans-serif;
font-size: 2rem;
font-weight: 700;
color: #ffffff;
margin: 3rem 0 1.5rem 0;
position: relative;
display: inline-block;
}
.section-header::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
width: 60px;
height: 4px;
background: var(--primary-gradient);
border-radius: 2px;
}
/* ===== ANIMATIONS ===== */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeInUp 0.6s ease-out;
}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #667eea, #764ba2);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #764ba2, #f093fb);
}
</style>
""", unsafe_allow_html=True)
# ======================= CONFIG CLASSES (unchanged) ============================
class AudioConfig:
MODEL_PATH = MODELS_DIR / "Audio.pth"
CLASS_NAMES = ["ElevenLabs", "Original", "Tacotron", "Text To Speech", "Voice Conversion"]
SAMPLE_RATE = 16000
N_MELS = 128
N_FFT = 2048
HOP_LENGTH = 512
MAX_AUDIO_LENGTH = 5
DEVICE = torch.device("cpu")
class ImageConfig:
MODEL_PATH = MODELS_DIR / "Image.pt"
CLASS_NAMES = ["DALL-E", "DeepFaceLab", "Face2Face", "FaceShifter", "FaceSwap", "Midjourney",
"NeuralTextures", "Real", "Stable Diffusion", "StyleGAN"]
IMG_SIZE = 299
DEVICE = torch.device("cpu")
class VideoConfig:
MODEL_PATH = MODELS_DIR / "Video.pt"
CLASS_NAMES = ["Deepfakes", "Face2Face", "FaceShifter", "FaceSwap", "NeuralTextures", "Original"]
DEVICE = torch.device("cpu")
FRAME_SKIP = 10
# ======================= HELPER FUNCTIONS (unchanged) ============================
def safe_torch_load(path, map_location="cpu"):
p = str(path)
try:
if hasattr(torch.serialization, "safe_globals"):
with torch.serialization.safe_globals([np.core.multiarray.scalar]):
ckpt = torch.load(p, map_location=map_location)
else:
if hasattr(torch.serialization, "add_safe_globals"):
torch.serialization.add_safe_globals([np.core.multiarray.scalar])
ckpt = torch.load(p, map_location=map_location)
return ckpt
except Exception as e:
try:
ckpt = torch.load(p, map_location=map_location, weights_only=False)
return ckpt
except Exception as e2:
raise RuntimeError(f"torch.load failed: {e} ; fallback failed: {e2}")
def download_and_load_torch_model(repo_id: str, filename: str, expected_device: torch.device):
try:
local_path = download_from_hf(repo_id, filename)
except Exception as e:
raise RuntimeError(f"Download failed for {repo_id}/{filename}: {e}")
if not local_path.exists():
raise FileNotFoundError(f"After download, file not found: {local_path}")
return local_path
# ======================= MODEL CLASSES (unchanged) ============================
class AudioDeepfakeCNN(nn.Module):
def __init__(self, num_classes=5):
super().__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1, 64, 3, padding=1),
nn.BatchNorm2d(64), nn.ReLU(inplace=True),
nn.Conv2d(64, 64, 3, padding=1),
nn.BatchNorm2d(64), nn.ReLU(inplace=True),
nn.MaxPool2d(2,2),
nn.Dropout2d(0.25)
)
self.conv2 = nn.Sequential(
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128), nn.ReLU(inplace=True),
nn.Conv2d(128, 128, 3, padding=1),
nn.BatchNorm2d(128), nn.ReLU(inplace=True),
nn.MaxPool2d(2,2),
nn.Dropout2d(0.25)
)
self.conv3 = nn.Sequential(
nn.Conv2d(128, 256, 3, padding=1),
nn.BatchNorm2d(256), nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1),
nn.BatchNorm2d(256), nn.ReLU(inplace=True),
nn.MaxPool2d(2,2),
nn.Dropout2d(0.3)
)
self.conv4 = nn.Sequential(
nn.Conv2d(256, 512, 3, padding=1),
nn.BatchNorm2d(512), nn.ReLU(inplace=True),
nn.Conv2d(512, 512, 3, padding=1),
nn.BatchNorm2d(512), nn.ReLU(inplace=True),
nn.MaxPool2d(2,2),
nn.Dropout2d(0.3)
)
self.gap = nn.AdaptiveAvgPool2d((1,1))
self.fc = nn.Sequential(
nn.Linear(512, 256), nn.ReLU(inplace=True), nn.Dropout(0.5),
nn.Linear(256, 128), nn.ReLU(inplace=True), nn.Dropout(0.4),
nn.Linear(128, num_classes)
)
def forward(self, x):
x = self.conv1(x); x = self.conv2(x); x = self.conv3(x); x = self.conv4(x)
x = self.gap(x); x = x.view(x.size(0), -1); x = self.fc(x); return x
class AttentionBlock(nn.Module):
def __init__(self, in_features):
super(AttentionBlock, self).__init__()
self.attention = nn.Sequential(
nn.Linear(in_features, in_features // 8),
nn.ReLU(),
nn.Linear(in_features // 8, in_features),
nn.Sigmoid()
)
def forward(self, x):
att_weights = self.attention(x)
return x * att_weights
class EnhancedXceptionNet(nn.Module):
def __init__(self, num_classes, dropout_rate=0.3, use_attention=True):
super(EnhancedXceptionNet, self).__init__()
try:
self.base_model = timm.create_model('xception', pretrained=False)
in_features = self.base_model.get_classifier().in_features
self.base_model.reset_classifier(0)
except Exception:
class DummyNet(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten()
)
def forward(self, x):
return self.net(x)
self.base_model = DummyNet()
in_features = 32
self.use_attention = use_attention
if use_attention:
self.attention = AttentionBlock(in_features)
self.classifier = nn.Sequential(
nn.Dropout(dropout_rate),
nn.Linear(in_features, 512),
nn.BatchNorm1d(512),
nn.ReLU(),
nn.Dropout(dropout_rate / 2),
nn.Linear(512, num_classes)
)
def forward(self, x):
features = self.base_model(x)
if self.use_attention:
features = self.attention(features)
output = self.classifier(features)
return output
# ======================= MODEL LOADERS (unchanged) ============================
@st.cache_resource
def load_audio_model():
try:
model = AudioDeepfakeCNN(num_classes=len(AudioConfig.CLASS_NAMES))
checkpoint = safe_torch_load(AudioConfig.MODEL_PATH, map_location=str(AudioConfig.DEVICE))
state = checkpoint['model_state_dict'] if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint else checkpoint
if isinstance(state, dict):
model_dict = model.state_dict()
filtered_dict = {k: v for k, v in state.items() if k in model_dict and v.shape == model_dict[k].shape}
model_dict.update(filtered_dict)
model.load_state_dict(model_dict, strict=False)
else:
model.load_state_dict(state)
model.to(AudioConfig.DEVICE).eval()
return model
except Exception as e:
st.error(f"Error loading audio model: {e}")
return None
@st.cache_resource
def load_image_model():
try:
model = EnhancedXceptionNet(num_classes=len(ImageConfig.CLASS_NAMES), dropout_rate=0.3, use_attention=True)
checkpoint = safe_torch_load(ImageConfig.MODEL_PATH, map_location=str(ImageConfig.DEVICE))
state_dict = checkpoint['model_state_dict'] if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint else checkpoint
if isinstance(state_dict, dict):
model_dict = model.state_dict()
filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
model_dict.update(filtered_dict)
model.load_state_dict(model_dict, strict=False)
else:
model.load_state_dict(state_dict, strict=False)
model.to(ImageConfig.DEVICE).eval()
return model
except Exception as e:
st.error(f"Error loading image model: {e}")
return None
@st.cache_resource
def load_video_model():
try:
num_classes = len(VideoConfig.CLASS_NAMES)
try:
model = timm.create_model("legacy_xception", pretrained=False, num_classes=num_classes)
except Exception:
class SmallNet(nn.Module):
def __init__(self, n):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(),
nn.Linear(16, n)
)
def forward(self, x):
return self.net(x)
model = SmallNet(num_classes)
checkpoint = safe_torch_load(VideoConfig.MODEL_PATH, map_location=str(VideoConfig.DEVICE))
state_dict = checkpoint['model_state_dict'] if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint else checkpoint
if isinstance(state_dict, dict):
model_dict = model.state_dict()
filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
model_dict.update(filtered_dict)
model.load_state_dict(model_dict, strict=False)
else:
model.load_state_dict(state_dict, strict=False)
model.to(VideoConfig.DEVICE).eval()
return model
except Exception as e:
st.error(f"Error loading video model: {e}")
return None
# ======================= PREPROCESSING (unchanged) ============================
def preprocess_audio(audio_path, sample_rate, n_mels, n_fft, hop_length, max_length):
waveform, sr = torchaudio.load(str(audio_path))
if waveform.shape[0] > 1:
waveform = torch.mean(waveform, dim=0, keepdim=True)
if sr != sample_rate:
resampler = T.Resample(sr, sample_rate)
waveform = resampler(waveform)
max_len = sample_rate * max_length
if waveform.shape[1] < max_len:
waveform = torch.nn.functional.pad(waveform, (0, max_len - waveform.shape[1]))
else:
waveform = waveform[:, :max_len]
mel_spec = T.MelSpectrogram(sample_rate, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels, power=2.0)(waveform)
mel_spec_db = T.AmplitudeToDB(top_db=80)(mel_spec)
mel_spec_db = (mel_spec_db - mel_spec_db.mean()) / (mel_spec_db.std() + 1e-8)
return mel_spec_db, mel_spec_db.squeeze().numpy()
def preprocess_image(image_path, size):
trans = transforms.Compose([
transforms.Resize((size, size)),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
img = Image.open(image_path).convert('RGB')
return trans(img).unsqueeze(0)
def extract_frames(video_path, frame_skip=10):
frames = []
cap = cv2.VideoCapture(str(video_path))
count = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret: break
if count % frame_skip == 0:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frames.append(frame)
count += 1
cap.release()
return frames
def preprocess_frame(frame, size):
trans = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize((size, size)),
transforms.ToTensor(),
transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
])
return trans(frame).unsqueeze(0)
# ======================= INFERENCE (unchanged) ============================
def predict_audio(model, audio_path, config):
device = config.DEVICE
try:
x, spec_viz = preprocess_audio(audio_path, config.SAMPLE_RATE, config.N_MELS, config.N_FFT, config.HOP_LENGTH, config.MAX_AUDIO_LENGTH)
x = x.unsqueeze(0).to(device)
with torch.no_grad():
out = F.softmax(model(x), dim=1)[0]
pred_idx = torch.argmax(out).item()
return {
"predicted_class": config.CLASS_NAMES[pred_idx],
"predicted_index": pred_idx,
"confidence": out[pred_idx].item(),
"probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
}, spec_viz
except Exception as e:
return {"error": str(e)}, None
def predict_image(model, image_path, config):
device = config.DEVICE
try:
x = preprocess_image(image_path, config.IMG_SIZE).to(device)
with torch.no_grad():
out = F.softmax(model(x), dim=1)[0]
pred_idx = torch.argmax(out).item()
return {
"predicted_class": config.CLASS_NAMES[pred_idx],
"predicted_index": pred_idx,
"confidence": out[pred_idx].item(),
"probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
}
except Exception as e:
return {"error": str(e)}
def predict_video(model, video_path, config):
device = config.DEVICE
try:
frames = extract_frames(video_path, config.FRAME_SKIP)
if len(frames) == 0:
return {"error":"No frames extracted"}
probs = []
progress_bar = st.progress(0)
for i, frame in enumerate(frames):
x = preprocess_frame(frame, 300).to(device)
with torch.no_grad():
p = F.softmax(model(x),dim=1)[0].cpu().numpy()
probs.append(p)
progress_bar.progress((i + 1) / len(frames))
probs = np.stack(probs)
avg_prob = probs.mean(axis=0)
pred_idx = np.argmax(avg_prob)
return {
"predicted_class": config.CLASS_NAMES[pred_idx],
"predicted_index": pred_idx,
"confidence": float(avg_prob[pred_idx]),
"probabilities": {c: float(avg_prob[i]) for i, c in enumerate(config.CLASS_NAMES)},
"frame_count": len(frames)
}
except Exception as e:
return {"error": str(e)}
# ======================= VISUALIZATION (enhanced) ============================
def plot_probabilities(probs, class_names):
sorted_items = sorted(probs.items(), key=lambda x: x[1], reverse=True)
classes = [item[0] for item in sorted_items]
values = [item[1] for item in sorted_items]
colors = ['rgba(102, 126, 234, 0.8)' if v == max(values) else 'rgba(118, 75, 162, 0.5)' for v in values]
fig = go.Figure(data=[
go.Bar(
x=values,
y=classes,
orientation='h',
marker=dict(
color=colors,
line=dict(color='rgba(102, 126, 234, 1)', width=2)
),
text=[f'{v*100:.1f}%' for v in values],
textposition='outside',
textfont=dict(size=13, color='#ffffff', family='Space Grotesk', weight=600),
hovertemplate='<b>%{y}</b><br>Confidence: %{x:.1%}<extra></extra>'
)
])
fig.update_layout(
title=dict(
text="<b>Classification Confidence</b>",
font=dict(size=18, color='#ffffff', family='Space Grotesk'),
x=0.5,
xanchor='center'
),
xaxis_title="Confidence Score",
yaxis_title="",
height=400,
template="plotly_dark",
showlegend=False,
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(color='#e4e4e7', family='Inter'),
xaxis=dict(
gridcolor='rgba(102, 126, 234, 0.15)',
showgrid=True,
zeroline=False,
tickformat='.0%'
),
yaxis=dict(
gridcolor='rgba(102, 126, 234, 0.15)',
showgrid=False
),
margin=dict(l=20, r=20, t=60, b=40)
)
return fig
def plot_spectrogram(spec_data):
if spec_data is None:
return None
fig = px.imshow(
spec_data,
labels=dict(x="Time Frame", y="Mel Frequency", color="dB"),
aspect='auto',
color_continuous_scale=[
[0, '#0f0f1e'],
[0.2, '#667eea'],
[0.4, '#764ba2'],
[0.6, '#f093fb'],
[0.8, '#f5576c'],
[1, '#fbbf24']
]
)
fig.update_layout(
title=dict(
text="<b>Mel Spectrogram Analysis</b>",
font=dict(size=18, color='#ffffff', family='Space Grotesk'),
x=0.5,
xanchor='center'
),
height=400,
template="plotly_dark",
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(color='#e4e4e7', family='Inter'),
margin=dict(l=20, r=20, t=60, b=40)
)
return fig
def plot_confusion_matrix(probs, predicted_class, class_names):
n_classes = len(class_names)
cm = np.zeros((n_classes, n_classes))
try:
pred_idx = class_names.index(predicted_class)
except ValueError:
pred_idx = 0
for i, (cls, prob) in enumerate(probs.items()):
cm[pred_idx, i] = prob
fig = px.imshow(
cm,
labels=dict(x="Predicted Class", y="True Class", color="Confidence"),
x=class_names,
y=class_names,
color_continuous_scale=[
[0, '#0f0f1e'],
[0.3, '#667eea'],
[0.6, '#764ba2'],
[1, '#f093fb']
],
aspect='auto',
text_auto='.2f'
)
fig.update_layout(
title=dict(
text="<b>Prediction Confidence Matrix</b>",
font=dict(size=18, color='#ffffff', family='Space Grotesk'),
x=0.5,
xanchor='center'
),
height=500,
template="plotly_dark",
plot_bgcolor='rgba(0,0,0,0)',
paper_bgcolor='rgba(0,0,0,0)',
font=dict(color='#e4e4e7', family='Inter', size=10),
margin=dict(l=20, r=20, t=60, b=40)
)
fig.update_xaxes(side="bottom", tickangle=-45)
return fig
# ======================= STARTUP ============================
with st.spinner("πŸ”„ Initializing models from Hugging Face..."):
try:
audio_path = download_and_load_torch_model(AUDIO_REPO, AUDIO_FILENAME, AudioConfig.DEVICE)
AudioConfig.MODEL_PATH = audio_path
except Exception as e:
st.error(f"Audio model error: {e}")
try:
image_path = download_and_load_torch_model(IMAGE_REPO, IMAGE_FILENAME, ImageConfig.DEVICE)
ImageConfig.MODEL_PATH = image_path
except Exception as e:
st.error(f"Image model error: {e}")
try:
video_path = download_and_load_torch_model(VIDEO_REPO, VIDEO_FILENAME, VideoConfig.DEVICE)
VideoConfig.MODEL_PATH = video_path
except Exception as e:
st.error(f"Video model error: {e}")
audio_model = load_audio_model()
image_model = load_image_model()
video_model = load_video_model()
# ======================= PAGES ============================
def show_home_page():
st.markdown("""
<div class="hero-header fade-in">
<h1 class="hero-title">🎭 Deepfake Detection & Attribution</h1>
<p class="hero-subtitle">Advanced multi-modal forensic analysis powered by deep learning</p>
<span class="hero-badge">✨ State-of-the-Art Neural Networks</span>
</div>
""", unsafe_allow_html=True)
st.markdown('<h2 class="section-header">Detection Capabilities</h2>', unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("""
<div class="feature-card">
<span class="feature-icon">🎡</span>
<div class="feature-title">Audio Forensics</div>
<div class="feature-desc">
Mel-spectrogram CNN architecture detects voice synthesis, TTS engines, and voice conversion techniques.
Supports WAV, MP3, FLAC, and OGG formats.
</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="feature-card">
<span class="feature-icon">πŸ–ΌοΈ</span>
<div class="feature-title">Image Analysis</div>
<div class="feature-desc">
Enhanced Xception with attention mechanisms identifies GAN-generated content, face swaps, and AI art.
Detects DALL-E, Midjourney, Stable Diffusion outputs.
</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown("""
<div class="feature-card">
<span class="feature-icon">🎬</span>
<div class="feature-title">Video Detection</div>
<div class="feature-desc">
Temporal frame analysis with Xception backbone identifies deepfake videos, face manipulations, and neural texture synthesis.
</div>
</div>
""", unsafe_allow_html=True)
st.markdown('<h2 class="section-header">System Overview</h2>', unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown(f"""
<div class='metric-card fade-in'>
<span class='metric-value'>{'⚑ GPU' if torch.cuda.is_available() else 'πŸ’» CPU'}</span>
<span class='metric-label'>Processing Unit</span>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div class='metric-card fade-in'>
<span class='metric-value'>3</span>
<span class='metric-label'>Modalities</span>
</div>
""", unsafe_allow_html=True)
with col3:
total_classes = len(AudioConfig.CLASS_NAMES) + len(ImageConfig.CLASS_NAMES) + len(VideoConfig.CLASS_NAMES)
st.markdown(f"""
<div class='metric-card fade-in'>
<span class='metric-value'>{total_classes}</span>
<span class='metric-label'>Detection Classes</span>
</div>
""", unsafe_allow_html=True)
with col4:
st.markdown("""
<div class='metric-card fade-in'>
<span class='metric-value'>~96%</span>
<span class='metric-label'>Avg Accuracy</span>
</div>
""", unsafe_allow_html=True)
st.markdown("---")
st.markdown("""
<div class="info-box fade-in">
<h3>πŸ”¬ How It Works</h3>
<p>
Our system employs deep convolutional neural networks trained on extensive datasets of authentic and manipulated media.
Each modality uses specialized architectures optimized for detecting artifacts specific to different deepfake generation techniques.
</p>
</div>
""", unsafe_allow_html=True)
def show_audio_page():
st.markdown("""
<div class="hero-header fade-in">
<h1 class="hero-title">🎡 Audio Deepfake Detection</h1>
<p class="hero-subtitle">Analyze audio samples for voice synthesis and manipulation</p>
</div>
""", unsafe_allow_html=True)
model = audio_model
if model is None:
st.error("⚠️ Audio model not loaded. Please refresh the page.")
return
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
uploaded_file = st.file_uploader(
"Upload your audio file",
type=['wav', 'mp3', 'ogg', 'flac', 'm4a'],
help="Supported formats: WAV, MP3, OGG, FLAC, M4A"
)
st.markdown('</div>', unsafe_allow_html=True)
if uploaded_file is not None:
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
st.audio(uploaded_file, format='audio/wav')
st.markdown('</div>', unsafe_allow_html=True)
if st.button("πŸ” Analyze Audio", use_container_width=True):
with st.spinner("πŸ”„ Processing audio sample..."):
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as t:
t.write(uploaded_file.getvalue())
tmp_path = t.name
result, spec = predict_audio(model, Path(tmp_path), AudioConfig)
if "error" in result:
st.error(f"❌ Error: {result['error']}")
else:
is_original = result['predicted_class'] == 'Original'
result_class = "result-success" if is_original else "result-warning"
icon = "βœ…" if is_original else "⚠️"
st.markdown(f"""
<div class="{result_class} fade-in">
<div class="result-title">{icon} Detection Result</div>
<div class="result-subtitle">
<strong>Classification:</strong> {result['predicted_class']}<br>
<strong>Confidence:</strong> {result['confidence']*100:.2f}%
</div>
</div>
""", unsafe_allow_html=True)
st.plotly_chart(plot_probabilities(result['probabilities'], AudioConfig.CLASS_NAMES), use_container_width=True)
if spec is not None:
st.plotly_chart(plot_spectrogram(spec), use_container_width=True)
with st.expander("πŸ“Š Detailed Analysis"):
st.json(result['probabilities'])
def show_image_page():
st.markdown("""
<div class="hero-header fade-in">
<h1 class="hero-title">πŸ–ΌοΈ Image Deepfake Detection</h1>
<p class="hero-subtitle">Identify AI-generated and manipulated images</p>
</div>
""", unsafe_allow_html=True)
model = image_model
if model is None:
st.error("⚠️ Image model not loaded. Please refresh the page.")
return
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
uploaded_file = st.file_uploader(
"Upload your image file",
type=['jpg', 'jpeg', 'png', 'webp', 'bmp'],
help="Supported formats: JPG, PNG, WEBP, BMP"
)
st.markdown('</div>', unsafe_allow_html=True)
if uploaded_file is not None:
image = Image.open(uploaded_file).convert('RGB')
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
st.image(image, use_container_width=True)
st.markdown('</div>', unsafe_allow_html=True)
if st.button("πŸ” Analyze Image", use_container_width=True):
with st.spinner("πŸ”„ Processing image..."):
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as t:
image.save(t.name)
tmp_path = t.name
result = predict_image(model, Path(tmp_path), ImageConfig)
if "error" in result:
st.error(f"❌ Error: {result['error']}")
else:
is_real = result['predicted_class'] == 'Real'
result_class = "result-success" if is_real else "result-warning"
icon = "βœ…" if is_real else "⚠️"
st.markdown(f"""
<div class="{result_class} fade-in">
<div class="result-title">{icon} Detection Result</div>
<div class="result-subtitle">
<strong>Classification:</strong> {result['predicted_class']}<br>
<strong>Confidence:</strong> {result['confidence']*100:.2f}%
</div>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.plotly_chart(plot_probabilities(result['probabilities'], ImageConfig.CLASS_NAMES), use_container_width=True)
with col2:
st.plotly_chart(plot_confusion_matrix(result['probabilities'], result['predicted_class'], ImageConfig.CLASS_NAMES), use_container_width=True)
with st.expander("πŸ“Š Detailed Analysis"):
st.json(result['probabilities'])
def show_video_page():
st.markdown("""
<div class="hero-header fade-in">
<h1 class="hero-title">🎬 Video Deepfake Detection</h1>
<p class="hero-subtitle">Temporal analysis for video manipulation detection</p>
</div>
""", unsafe_allow_html=True)
model = video_model
if model is None:
st.error("⚠️ Video model not loaded. Please refresh the page.")
return
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
uploaded_file = st.file_uploader(
"Upload your video file",
type=['mp4', 'avi', 'mov', 'mkv', 'webm'],
help="Supported formats: MP4, AVI, MOV, MKV, WEBM"
)
st.markdown('</div>', unsafe_allow_html=True)
if uploaded_file is not None:
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
st.video(uploaded_file)
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('<div class="glass-card">', unsafe_allow_html=True)
frame_skip = st.slider(
"Frame Skip Rate",
min_value=1,
max_value=30,
value=10,
help="Higher values = faster processing, lower accuracy"
)
st.markdown('</div>', unsafe_allow_html=True)
if st.button("πŸ” Analyze Video", use_container_width=True):
with st.spinner("πŸ”„ Extracting and analyzing frames..."):
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as t:
t.write(uploaded_file.getvalue())
tmp_path = t.name
VideoConfig.FRAME_SKIP = frame_skip
result = predict_video(model, Path(tmp_path), VideoConfig)
if "error" in result:
st.error(f"❌ Error: {result['error']}")
else:
is_original = result['predicted_class'] == 'Original'
result_class = "result-success" if is_original else "result-warning"
icon = "βœ…" if is_original else "⚠️"
st.markdown(f"""
<div class="{result_class} fade-in">
<div class="result-title">{icon} Detection Result</div>
<div class="result-subtitle">
<strong>Classification:</strong> {result['predicted_class']}<br>
<strong>Confidence:</strong> {result['confidence']*100:.2f}%<br>
<strong>Frames Analyzed:</strong> {result['frame_count']}
</div>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.plotly_chart(plot_probabilities(result['probabilities'], VideoConfig.CLASS_NAMES), use_container_width=True)
with col2:
st.plotly_chart(plot_confusion_matrix(result['probabilities'], result['predicted_class'], VideoConfig.CLASS_NAMES), use_container_width=True)
with st.expander("πŸ“Š Detailed Analysis"):
st.json(result['probabilities'])
# ======================= MAIN APP ============================
def main():
with st.sidebar:
st.markdown("""
<div style="text-align: center; padding: 2rem 0 1rem 0;">
<h1 style="font-size: 2rem; margin: 0;">🎭</h1>
<p style="color: rgba(255,255,255,0.6); font-size: 0.9rem; margin: 0.5rem 0 0 0;">DFDA System</p>
</div>
""", unsafe_allow_html=True)
page = st.radio(
"Navigation",
["🏠 Home", "🎡 Audio", "πŸ–ΌοΈ Image", "🎬 Video"],
label_visibility="collapsed"
)
st.markdown("---")
device_icon = "⚑" if torch.cuda.is_available() else "πŸ’»"
device_name = "GPU" if torch.cuda.is_available() else "CPU"
st.markdown(f"""
<div style="text-align: center; padding: 1rem;">
<div style="font-size: 2rem;">{device_icon}</div>
<div style="color: rgba(255,255,255,0.8); font-weight: 600; margin-top: 0.5rem;">{device_name}</div>
<div style="color: rgba(255,255,255,0.4); font-size: 0.8rem;">Processing Unit</div>
</div>
""", unsafe_allow_html=True)
st.markdown("---")
with st.expander("ℹ️ About DFDA"):
st.markdown("""
**DFDA** is a state-of-the-art deepfake detection system utilizing deep learning
to identify manipulated media across audio, image, and video modalities.
**Key Features:**
- Multi-modal detection
- Real-time analysis
- High accuracy
- Detailed attribution
""")
with st.expander("πŸ“š Model Info"):
st.markdown("""
**Audio:** CNN with Mel-spectrogram analysis
**Image:** Enhanced Xception with attention
**Video:** Temporal Xception backbone
All models trained on extensive deepfake datasets.
""")
# Route to appropriate page
if "Home" in page:
show_home_page()
elif "Audio" in page:
show_audio_page()
elif "Image" in page:
show_image_page()
elif "Video" in page:
show_video_page()
if __name__ == "__main__":
main()