Spaces:
Sleeping
Sleeping
Download app.py from jayanthbottu/DFDA: direct link, hf CLI and curl.
- Browser
- Download file 49.3 kB
-
https://huggingface.co/spaces/jayanthbottu/DFDA/resolve/main/app.py
- Command line
-
hf download hf://spaces/jayanthbottu/DFDA/app.py
-
curl -L -o app.py https://huggingface.co/spaces/jayanthbottu/DFDA/resolve/main/app.py
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) ============================ | |
| 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 | |
| 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 | |
| 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() |