jayanthbottu commited on
Commit
58a92cd
·
verified ·
1 Parent(s): 79bc1a2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -374
app.py CHANGED
@@ -1,20 +1,4 @@
1
- # DIAGNOSTIC HELPERS - paste near top of app.py, after imports
2
- import sys, json, logging
3
- logging.basicConfig(level=logging.INFO)
4
-
5
- def debug_upload_state():
6
- import streamlit as _st, os
7
- _st.write("=== DEBUG INFO ===")
8
- _st.write("Space visibility (ENV):", os.environ.get("SPACE_VISIBILITY", "unknown"))
9
- _st.write("HF_TOKEN present?:", bool(os.environ.get("HF_TOKEN")))
10
- _st.write("Python version:", sys.version)
11
- _st.write("Streamlit version:", _st.__version__)
12
- _st.write("==================")
13
-
14
- # call it on Home page so you can see values in UI
15
- # call debug_upload_state() somewhere visible (e.g. under Home)
16
-
17
- # app.py — HF Spaces ready (full file)
18
  import streamlit as st
19
  import torch
20
  import torch.nn as nn
@@ -36,17 +20,20 @@ import shutil
36
  import os
37
  import importlib.util
38
  import traceback
 
 
 
 
39
 
40
  warnings.filterwarnings('ignore')
41
 
42
- # Robust import for download_models (works if file is in repo root or src/)
43
- _download_mod = None
44
  try:
45
  import download_models as _dm
46
  download_from_hf = _dm.download_from_hf
47
  MODELS_DIR = _dm.MODELS_DIR
48
  except Exception:
49
- # fallback search
50
  _base = Path(__file__).parent
51
  candidates = [
52
  _base / "download_models.py",
@@ -72,17 +59,20 @@ except Exception:
72
  st.text(tb_text)
73
  raise ImportError("download_models import failed")
74
 
75
- # HF repo & filenames (your repo)
76
- AUDIO_REPO = "jayanthbottu/DFDA"
77
- AUDIO_FILENAME = "Audio.pth"
78
-
79
- VIDEO_REPO = "jayanthbottu/DFDA"
80
- VIDEO_FILENAME = "Video.pt"
81
 
82
- IMAGE_REPO = "jayanthbottu/DFDA"
83
- IMAGE_FILENAME = "Image.pt"
 
84
 
85
- MODELS_DIR.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
86
 
87
  # ======================= PAGE CONFIG ============================
88
  st.set_page_config(
@@ -92,48 +82,16 @@ st.set_page_config(
92
  initial_sidebar_state="expanded"
93
  )
94
 
95
- # ======================= CUSTOM CSS ============================
96
  st.markdown("""
97
  <style>
98
- /* Import Google Fonts */
99
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
100
- * { font-family: 'Inter', sans-serif; }
101
- #MainMenu {visibility: hidden;} footer {visibility: hidden;} header {visibility: hidden;}
102
- .main { background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%); }
103
- [data-testid="stSidebar"] { background: linear-gradient(180deg, #16213e 0%, #0f3460 100%); border-right: 2px solid rgba(94, 129, 244, 0.3); }
104
- [data-testid="stSidebar"] [data-testid="stMarkdownContainer"] { color: #e4e4e7; }
105
- .gradient-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); padding: 3rem 2rem; border-radius: 10px; text-align: center; margin-bottom: 2rem; box-shadow: 0 10px 60px rgba(102, 126, 234, 0.4); animation: fadeInDown 0.8s ease-out; }
106
- .gradient-header h1 { color: white; font-size: 2.5rem; font-weight: 800; margin: 0; text-shadow: 2px 4px 8px rgba(0,0,0,0.3); letter-spacing: -1px; }
107
- .gradient-header p { color: rgba(255,255,255,0.95); font-size: 1.2rem; margin-top: 0.8rem; font-weight: 500; }
108
- .feature-card { background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(118, 75, 162, 0.15) 100%); border: 2px solid rgba(102, 126, 234, 0.3); border-radius: 16px; padding: 2rem; margin: 1.5rem 0; transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); position: relative; overflow: hidden; }
109
- .feature-card::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); transition: left 0.5s; }
110
- .feature-card:hover::before { left: 100%; }
111
- .feature-card:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3); border-color: rgba(102, 126, 234, 0.6); }
112
- .feature-title { color: #a5b4fc; font-size: 1.4rem; font-weight: 700; margin-bottom: 1rem; letter-spacing: -0.5px; }
113
- .feature-desc { color: #e4e4e7; font-size: 1.05rem; line-height: 1.8; }
114
- .result-success { background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.2) 100%); border-left: 6px solid #10b981; border-radius: 16px; padding: 2rem; margin: 2rem 0; box-shadow: 0 10px 30px rgba(16, 185, 129, 0.2); animation: slideInLeft 0.6s ease-out; }
115
- .result-warning { background: linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%); border-left: 6px solid #ef4444; border-radius: 16px; padding: 2rem; margin: 2rem 0; box-shadow: 0 10px 30px rgba(239, 68, 68, 0.2); animation: slideInLeft 0.6s ease-out; }
116
- .result-title { font-size: 2rem; font-weight: 800; margin: 0 0 1rem 0; letter-spacing: -0.5px; }
117
- .result-subtitle { font-size: 1.3rem; font-weight: 600; margin: 0.8rem 0; opacity: 0.95; }
118
- .metric-card { background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border: 2px solid rgba(102, 126, 234, 0.3); border-radius: 16px; padding: 2rem; text-align: center; margin: 1rem 0.5rem; transition: all 0.3s ease; backdrop-filter: blur(10px); }
119
- .metric-card:hover { transform: translateY(-5px); box-shadow: 0 15px 35px rgba(102, 126, 234, 0.3); }
120
- .metric-value { font-size: 3rem; font-weight: 800; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 0.5rem; }
121
- .metric-label { font-size: 1rem; color: #a5b4fc; font-weight: 600; text-transform: uppercase; letter-spacing: 1.5px; }
122
- .info-box { background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%); border-left: 5px solid #3b82f6; border-radius: 12px; padding: 1.5rem; margin: 1.5rem 0; backdrop-filter: blur(10px); }
123
- .info-box h3 { color: #93c5fd; font-size: 1.4rem; font-weight: 700; margin: 0 0 0.8rem 0; }
124
- .info-box p { color: #e4e4e7; font-size: 1.05rem; line-height: 1.7; margin: 0; }
125
- .stButton > button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 12px; padding: 1rem 3rem; font-weight: 700; font-size: 1.1rem; transition: all 0.3s ease; box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4); text-transform: uppercase; letter-spacing: 1px; }
126
- .stButton > button:hover { transform: translateY(-3px); box-shadow: 0 12px 30px rgba(102, 126, 234, 0.5); background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); }
127
- [data-testid="stFileUploader"] { border: 3px dashed rgba(102, 126, 234, 0.5); border-radius: 16px; padding: 2rem; background: rgba(102, 126, 234, 0.05); transition: all 0.3s ease; }
128
- [data-testid="stFileUploader"]:hover { border-color: rgba(102, 126, 234, 0.8); background: rgba(102, 126, 234, 0.1); }
129
- .stProgress > div > div > div > div { background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%); }
130
- [data-testid="stRadio"] > div { background: rgba(102, 126, 234, 0.1); border-radius: 12px; padding: 1rem; }
131
- hr { border: none; height: 2px; background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.5), transparent); margin: 2rem 0; }
132
- @keyframes fadeInDown { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } }
133
- @keyframes slideInLeft { from { opacity: 0; transform: translateX(-30px); } to { opacity: 1; transform: translateX(0); } }
134
- @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
135
- [data-testid="stExpander"] { background: rgba(102, 126, 234, 0.1); border-radius: 12px; border: 1px solid rgba(102, 126, 234, 0.3); }
136
- .stSlider > div > div > div { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); }
137
  </style>
138
  """, unsafe_allow_html=True)
139
 
@@ -146,7 +104,7 @@ class AudioConfig:
146
  N_FFT = 2048
147
  HOP_LENGTH = 512
148
  MAX_AUDIO_LENGTH = 5
149
- DEVICE = torch.device("cpu") # Spaces are CPU only
150
 
151
  class ImageConfig:
152
  MODEL_PATH = MODELS_DIR / "Image.pt"
@@ -163,19 +121,15 @@ class VideoConfig:
163
 
164
  # ======================= MODEL DOWNLOAD & LOADING HELPERS ============================
165
  def download_and_load_torch_model(repo_id: str, filename: str, expected_device: torch.device):
166
- """
167
- Downloads filename from repo_id into MODELS_DIR (if missing) and returns local Path.
168
- Raises exception on failure.
169
- """
170
  try:
171
  local_path = download_from_hf(repo_id, filename)
172
  except Exception as e:
173
  raise RuntimeError(f"Download failed for {repo_id}/{filename}: {e}")
174
- if not local_path.exists():
175
  raise FileNotFoundError(f"After download, file not found: {local_path}")
176
- return local_path
177
 
178
- # ======================= AUDIO / IMAGE / VIDEO MODEL CLASSES (unchanged) ============================
179
  class AudioDeepfakeCNN(nn.Module):
180
  def __init__(self, num_classes=5):
181
  super().__init__()
@@ -258,7 +212,7 @@ class EnhancedXceptionNet(nn.Module):
258
  output = self.classifier(features)
259
  return output
260
 
261
- # ======================= MODEL LOADERS (use cache_resource for HF Spaces) ============================
262
  @st.cache_resource
263
  def load_audio_model():
264
  try:
@@ -492,20 +446,87 @@ def plot_confusion_matrix(probs, predicted_class, class_names):
492
  )
493
  return fig
494
 
495
- # ======================= STARTUP: download and set model paths ============================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  with st.spinner("Downloading models from Hugging Face (cached to /app/models)... This will happen once per Space runtime."):
497
  try:
498
- audio_path = download_and_load_torch_model(AUDIO_REPO, AUDIO_FILENAME, AudioConfig.DEVICE)
499
  AudioConfig.MODEL_PATH = audio_path
500
  except Exception as e:
501
  st.error(f"Audio model download/load error: {e}")
502
  try:
503
- image_path = download_and_load_torch_model(IMAGE_REPO, IMAGE_FILENAME, ImageConfig.DEVICE)
504
  ImageConfig.MODEL_PATH = image_path
505
  except Exception as e:
506
  st.error(f"Image model download/load error: {e}")
507
  try:
508
- video_path = download_and_load_torch_model(VIDEO_REPO, VIDEO_FILENAME, VideoConfig.DEVICE)
509
  VideoConfig.MODEL_PATH = video_path
510
  except Exception as e:
511
  st.error(f"Video model download/load error: {e}")
@@ -515,7 +536,7 @@ audio_model = load_audio_model()
515
  image_model = load_image_model()
516
  video_model = load_video_model()
517
 
518
- # ======================= PAGES (full implementations) ============================
519
  def show_home_page():
520
  st.markdown("""
521
  <div class="gradient-header">
@@ -524,132 +545,114 @@ def show_home_page():
524
  </div>
525
  """, unsafe_allow_html=True)
526
  st.markdown("## Comprehensive Multi-Modal Deepfake Analysis System")
527
- st.markdown("This cutting-edge research platform leverages state-of-the-art deep learning architectures to detect and attribute synthetic media across audio, image, and video modalities with unparalleled accuracy.")
528
- col1, col2, col3 = st.columns(3)
529
- with col1:
530
- st.markdown("""
531
- <div class="feature-card">
532
- <div class="feature-title">Audio Deepfake Detection</div>
533
- <div class="feature-desc">
534
- Utilizes advanced mel-spectrogram analysis combined with custom CNN architecture to identify synthetic audio generation techniques. Our model detects voice cloning, text-to-speech systems, and voice conversion with exceptional precision.
535
- <br><br>
536
- <strong>Supported Formats:</strong> WAV, MP3, OGG, FLAC, M4A<br>
537
- <strong>Detection Classes:</strong> ElevenLabs, Tacotron, Voice Conversion, Original Audio, Text-to-Speech<br>
538
- <strong>Accuracy:</strong> 94.7% on validation set
539
- </div>
540
- </div>
541
- """, unsafe_allow_html=True)
542
- with col2:
543
- st.markdown("""
544
- <div class="feature-card">
545
- <div class="feature-title">Image Synthesis Detection</div>
546
- <div class="feature-desc">
547
- Employs Enhanced XceptionNet with attention mechanisms to detect AI-generated and manipulated images. Identifies various deepfake techniques and distinguishes between multiple generative models.
548
- <br><br>
549
- <strong>Supported Formats:</strong> JPG, PNG, BMP, WEBP<br>
550
- <strong>Detection Classes:</strong> DALL-E, Midjourney, StyleGAN, FaceSwap, DeepFaceLab, Real Images<br>
551
- <strong>Accuracy:</strong> 96.2% on benchmark datasets
552
- </div>
553
- </div>
554
- """, unsafe_allow_html=True)
555
- with col3:
556
- st.markdown("""
557
- <div class="feature-card">
558
- <div class="feature-title">Video Manipulation Analysis</div>
559
- <div class="feature-desc">
560
- Performs temporal consistency analysis across video frames using Xception-based architecture. Detects face manipulation, deepfake videos, and various facial reenactment techniques through frame-by-frame analysis.
561
- <br><br>
562
- <strong>Supported Formats:</strong> MP4, AVI, MOV, MKV, WEBM<br>
563
- <strong>Detection Classes:</strong> Deepfakes, Face2Face, FaceSwap, FaceShifter, Original Video<br>
564
- <strong>Accuracy:</strong> 93.8% with temporal aggregation
565
- </div>
566
- </div>
567
- """, unsafe_allow_html=True)
568
- st.markdown("---")
569
- st.markdown("## System Workflow")
570
- col1, col2, col3 = st.columns(3)
571
- with col1:
572
- st.markdown("""
573
- <div class="info-box">
574
- <h3>STEP 1: Upload Media</h3>
575
- <p>Select your analysis mode from the sidebar navigation menu. Upload your media file using the intuitive drag-and-drop interface or browse your local filesystem. The system supports multiple file formats across all modalities.</p>
576
- </div>
577
- """, unsafe_allow_html=True)
578
- with col2:
579
- st.markdown("""
580
- <div class="info-box">
581
- <h3>STEP 2: Deep Analysis</h3>
582
- <p>Our advanced neural networks process your media through multiple layers of feature extraction and classification. The models analyze spectral features, pixel patterns, and temporal inconsistencies to detect manipulation.</p>
583
- </div>
584
- """, unsafe_allow_html=True)
585
- with col3:
586
- st.markdown("""
587
- <div class="info-box">
588
- <h3>STEP 3: Comprehensive Results</h3>
589
- <p>Receive detailed analysis including predicted class, confidence scores, probability distributions, and visual representations. Export results for further investigation or documentation purposes.</p>
590
- </div>
591
- """, unsafe_allow_html=True)
592
- st.markdown("---")
593
- st.markdown("## Technical Specifications")
594
- col1, col2, col3, col4 = st.columns(4)
595
- with col1:
596
- device = "CPU"
597
- st.markdown(f"""
598
- <div class="metric-card">
599
- <div class="metric-value">{device}</div>
600
- <div class="metric-label">Computation Device</div>
601
- </div>
602
- """, unsafe_allow_html=True)
603
- with col2:
604
- st.markdown("""
605
- <div class="metric-card">
606
- <div class="metric-value">3</div>
607
- <div class="metric-label">Modalities</div>
608
- </div>
609
- """, unsafe_allow_html=True)
610
- with col3:
611
- total_classes = len(AudioConfig.CLASS_NAMES) + len(ImageConfig.CLASS_NAMES) + len(VideoConfig.CLASS_NAMES)
612
- st.markdown(f"""
613
- <div class="metric-card">
614
- <div class="metric-value">{total_classes}</div>
615
- <div class="metric-label">Detection Classes</div>
616
- </div>
617
- """, unsafe_allow_html=True)
618
- with col4:
619
- st.markdown("""
620
- <div class="metric-card">
621
- <div class="metric-value">92.5%</div>
622
- <div class="metric-label">Avg Accuracy</div>
623
- </div>
624
- """, unsafe_allow_html=True)
625
- st.markdown("---")
626
- with st.expander("Model Architecture Details"):
627
- st.markdown("""
628
- **Audio Detection Model:**
629
- - Architecture: Custom 4-layer CNN with batch normalization and dropout
630
- - Input: Mel spectrograms (128 mel bins, 5-second audio clips)
631
- - Parameters: ~2.1M trainable parameters
632
- - Training: Adam optimizer, learning rate 0.0001
633
- **Image Detection Model:**
634
- - Architecture: Enhanced XceptionNet with attention mechanism
635
- - Input: RGB images resized to 299x299 pixels
636
- - Parameters: ~22.8M trainable parameters
637
- - Training: SGD optimizer with momentum 0.9
638
- **Video Detection Model:**
639
- - Architecture: Legacy Xception with temporal aggregation
640
- - Input: Frame sequences extracted at configurable intervals
641
- - Parameters: ~22.9M trainable parameters
642
- - Training: Frame-level predictions with temporal averaging
643
- """)
644
- with st.expander("Research & Citations"):
645
- st.markdown("""
646
- This system is built upon cutting-edge research in deepfake detection and media forensics:
647
- - XceptionNet architecture for image manipulation detection
648
- - Mel-spectrogram analysis for synthetic audio detection
649
- - Temporal consistency analysis for video deepfake detection
650
- - Attention mechanisms for improved feature selection
651
- """)
652
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  def show_audio_page():
654
  st.markdown("""
655
  <div class="gradient-header">
@@ -657,58 +660,22 @@ def show_audio_page():
657
  <p>Synthetic Voice Generation & Manipulation Analysis</p>
658
  </div>
659
  """, unsafe_allow_html=True)
660
- model = audio_model
661
- if model is None:
662
  st.error("Audio model not loaded. Please ensure Audio.pth is available or HF download succeeded.")
663
  return
664
- st.markdown("### Upload Audio File for Analysis")
665
- uploaded_file = st.file_uploader(
666
- "Drag and drop your audio file here or click to browse",
667
- type=['wav', 'mp3', 'ogg', 'flac', 'm4a'],
668
- help="Supported formats: WAV, MP3, OGG, FLAC, M4A (Max 5 seconds will be analyzed)"
669
- )
670
- if uploaded_file is not None:
671
- col1, col2 = st.columns([1, 1])
672
- with col1:
673
- st.markdown("#### Audio Preview")
674
- st.audio(uploaded_file, format='audio/wav')
675
- st.markdown(f"**File Information:** - Filename: `{uploaded_file.name}` - Size: `{uploaded_file.size / 1024:.2f} KB` - Type: `{uploaded_file.type}`")
676
- with col2:
677
- st.markdown("#### Analysis Configuration")
678
- st.markdown(f"**Model Settings:** - Sample Rate: {AudioConfig.SAMPLE_RATE} Hz - Mel Bins: {AudioConfig.N_MELS} - Analysis Duration: {AudioConfig.MAX_AUDIO_LENGTH} seconds - Device: {AudioConfig.DEVICE}")
679
- if st.button("ANALYZE AUDIO", type="primary", use_container_width=True):
680
- with st.spinner("Processing audio file and extracting features..."):
681
- with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
682
- tmp_file.write(uploaded_file.getvalue())
683
- tmp_path = tmp_file.name
684
- result, spec_viz = predict_audio(model, Path(tmp_path), AudioConfig)
685
- if "error" not in result:
686
- st.session_state.audio_result = result
687
- st.session_state.audio_spec = spec_viz
688
- st.success("Analysis completed successfully!")
689
- else:
690
- st.error(f"Analysis failed: {result['error']}")
691
- if 'audio_result' in st.session_state:
692
- result = st.session_state.audio_result
693
  st.markdown("---")
694
  st.markdown("## DETECTION RESULTS")
695
  is_fake = result['predicted_class'] != "Original"
696
  if is_fake:
697
- st.markdown(f"""
698
- <div class="result-warning">
699
- <h2 class="result-title" style="color: #ef4444;">DEEPFAKE AUDIO DETECTED</h2>
700
- <p class="result-subtitle">Detected Synthesis Method: <strong>{result['predicted_class']}</strong></p>
701
- <p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
702
- </div>
703
- """, unsafe_allow_html=True)
704
  else:
705
- st.markdown(f"""
706
- <div class="result-success">
707
- <h2 class="result-title" style="color: #10b981;">AUTHENTIC AUDIO</h2>
708
- <p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p>
709
- <p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
710
- </div>
711
- """, unsafe_allow_html=True)
712
  col1, col2, col3 = st.columns(3)
713
  with col1:
714
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
@@ -716,18 +683,15 @@ def show_audio_page():
716
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence Score</div></div>", unsafe_allow_html=True)
717
  with col3:
718
  authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
719
- st.markdown(f"<div class='metric-card'><div class='metric-value' style='color: {'#ef4444' if is_fake else '#10b981'};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
 
720
  st.markdown("### Class Probability Distribution")
721
  fig_probs = plot_probabilities(result['probabilities'], AudioConfig.CLASS_NAMES)
722
  st.plotly_chart(fig_probs, use_container_width=True)
723
- if 'audio_spec' in st.session_state and st.session_state.audio_spec is not None:
724
- st.markdown("### Mel Spectrogram Analysis")
725
- fig_spec = plot_spectrogram(st.session_state.audio_spec)
726
  st.plotly_chart(fig_spec, use_container_width=True)
727
- with st.expander("Detailed Class Probabilities"):
728
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
729
- for i, (class_name, prob) in enumerate(sorted_probs):
730
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
731
 
732
  def show_image_page():
733
  st.markdown("""
@@ -736,46 +700,20 @@ def show_image_page():
736
  <p>AI-Generated & Manipulated Image Analysis</p>
737
  </div>
738
  """, unsafe_allow_html=True)
739
- model = image_model
740
- if model is None:
741
  st.error("Image model not loaded. Please ensure Image.pt is available or HF download succeeded.")
742
  return
743
- st.markdown("### Upload Image File for Analysis")
744
- uploaded_file = st.file_uploader(
745
- "Drag and drop your image file here or click to browse",
746
- type=['jpg', 'jpeg', 'png', 'bmp', 'webp'],
747
- help="Supported formats: JPG, PNG, BMP, WEBP"
748
- )
749
- if uploaded_file is not None:
750
- col1, col2 = st.columns([1, 1])
751
- with col1:
752
- st.markdown("#### Image Preview")
753
- image = Image.open(uploaded_file)
754
- st.image(image, caption="Uploaded Image")
755
- st.markdown(f"**File Information:** - Filename: `{uploaded_file.name}` - Size: `{uploaded_file.size / 1024:.2f} KB` - Dimensions: `{image.size[0]} x {image.size[1]} pixels` - Mode: `{image.mode}`")
756
- with col2:
757
- st.markdown("#### Analysis Configuration")
758
- st.markdown(f"**Model Settings:** - Input Size: {ImageConfig.IMG_SIZE}x{ImageConfig.IMG_SIZE} pixels - Architecture: Enhanced XceptionNet - Device: {ImageConfig.DEVICE} - Attention Mechanism: Enabled")
759
- if st.button("ANALYZE IMAGE", type="primary"):
760
- with st.spinner("Processing image and extracting features..."):
761
- with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file:
762
- image.save(tmp_file.name)
763
- tmp_path = tmp_file.name
764
- result = predict_image(model, Path(tmp_path), ImageConfig)
765
- if "error" not in result:
766
- st.session_state.image_result = result
767
- st.success("Analysis completed successfully!")
768
- else:
769
- st.error(f"Analysis failed: {result['error']}")
770
- if 'image_result' in st.session_state:
771
- result = st.session_state.image_result
772
  st.markdown("---")
773
  st.markdown("## DETECTION RESULTS")
774
  is_fake = result['predicted_class'] != "Real"
775
  if is_fake:
776
- st.markdown(f"""<div class="result-warning"><h2 class="result-title" style="color: #ef4444;">SYNTHETIC IMAGE DETECTED</h2><p class="result-subtitle">Generation Method: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
777
  else:
778
- st.markdown(f"""<div class="result-success"><h2 class="result-title" style="color: #10b981;">AUTHENTIC IMAGE</h2><p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
779
  col1, col2, col3 = st.columns(3)
780
  with col1:
781
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
@@ -783,17 +721,14 @@ def show_image_page():
783
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence Score</div></div>", unsafe_allow_html=True)
784
  with col3:
785
  authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
786
- st.markdown(f"<div class='metric-card'><div class='metric-value' style='color: {'#ef4444' if is_fake else '#10b981'};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
 
787
  st.markdown("### Class Probability Distribution")
788
  fig_probs = plot_probabilities(result['probabilities'], ImageConfig.CLASS_NAMES)
789
  st.plotly_chart(fig_probs, use_container_width=True)
790
  st.markdown("### Prediction Confidence Matrix")
791
  fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], ImageConfig.CLASS_NAMES)
792
  st.plotly_chart(fig_cm, use_container_width=True)
793
- with st.expander("Detailed Class Probabilities"):
794
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
795
- for i, (class_name, prob) in enumerate(sorted_probs):
796
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
797
 
798
  def show_video_page():
799
  st.markdown("""
@@ -802,55 +737,29 @@ def show_video_page():
802
  <p>Face Manipulation & Video Synthesis Analysis</p>
803
  </div>
804
  """, unsafe_allow_html=True)
805
- model = video_model
806
- if model is None:
807
  st.error("Video model not loaded. Please ensure Video.pt is available or HF download succeeded.")
808
  return
809
- st.markdown("### Upload Video File for Analysis")
810
- uploaded_file = st.file_uploader(
811
- "Drag and drop your video file here or click to browse",
812
- type=['mp4', 'avi', 'mov', 'mkv', 'webm'],
813
- help="Supported formats: MP4, AVI, MOV, MKV, WEBM"
814
- )
815
- if uploaded_file is not None:
816
- col1, col2 = st.columns([1, 1])
817
- with col1:
818
- st.markdown("#### Video Preview")
819
- st.video(uploaded_file)
820
- st.markdown(f"**File Information:** - Filename: `{uploaded_file.name}` - Size: `{uploaded_file.size / (1024*1024):.2f} MB` - Type: `{uploaded_file.type}`")
821
- with col2:
822
- st.markdown("#### Analysis Configuration")
823
- frame_skip = st.slider("Frame Skip Rate", min_value=1, max_value=30, value=10, help="Higher values = faster processing but may reduce accuracy")
824
- st.markdown(f"**Model Settings:** - Architecture: Legacy Xception - Frame Analysis: Every {frame_skip} frames - Input Size: 300x300 pixels per frame - Device: {VideoConfig.DEVICE}")
825
- if st.button("ANALYZE VIDEO", type="primary", use_container_width=True):
826
- with st.spinner("Extracting frames and performing temporal analysis..."):
827
- with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
828
- tmp_file.write(uploaded_file.getvalue())
829
- tmp_path = tmp_file.name
830
- VideoConfig.FRAME_SKIP = frame_skip
831
- result = predict_video(model, Path(tmp_path), VideoConfig)
832
- if "error" not in result:
833
- st.session_state.video_result = result
834
- st.success("Analysis completed successfully!")
835
- else:
836
- st.error(f"Analysis failed: {result['error']}")
837
- if 'video_result' in st.session_state:
838
- result = st.session_state.video_result
839
  st.markdown("---")
840
  st.markdown("## DETECTION RESULTS")
841
  is_fake = result['predicted_class'] != "Original"
842
  if is_fake:
843
- st.markdown(f"""<div class="result-warning"><h2 class="result-title" style="color: #ef4444;">DEEPFAKE VIDEO DETECTED</h2><p class="result-subtitle">Manipulation Type: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p><p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p></div>""", unsafe_allow_html=True)
844
  else:
845
- st.markdown(f"""<div class="result-success"><h2 class="result-title" style="color: #10b981;">AUTHENTIC VIDEO</h2><p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p><p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p></div>""", unsafe_allow_html=True)
846
  col1, col2, col3, col4 = st.columns(4)
847
  with col1:
848
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
849
  with col2:
850
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence</div></div>", unsafe_allow_html=True)
851
  with col3:
852
- authenticity = "SYNTHETIC' if is_fake else 'AUTHENTIC"
853
- st.markdown(f"<div class='metric-card'><div class='metric-value' style='color: {'#ef4444' if is_fake else '#10b981'};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
 
854
  with col4:
855
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result.get('frame_count', 0)}</div><div class='metric-label'>Frames</div></div>", unsafe_allow_html=True)
856
  st.markdown("### Class Probability Distribution")
@@ -859,24 +768,6 @@ def show_video_page():
859
  st.markdown("### Prediction Confidence Matrix")
860
  fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], VideoConfig.CLASS_NAMES)
861
  st.plotly_chart(fig_cm, use_container_width=True)
862
- with st.expander("Detailed Class Probabilities"):
863
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
864
- for i, (class_name, prob) in enumerate(sorted_probs):
865
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
866
-
867
- # --- Temporary minimal page functions in case something fails (keeps app stable)
868
- def _stub_home():
869
- st.markdown("# Home")
870
- st.write("Home page placeholder — full content not loaded yet.")
871
- def _stub_audio():
872
- st.markdown("# Audio Detection")
873
- st.write("Audio page placeholder. Model status:", "Loaded" if audio_model else "Not loaded")
874
- def _stub_image():
875
- st.markdown("# Image Detection")
876
- st.write("Image page placeholder. Model status:", "Loaded" if image_model else "Not loaded")
877
- def _stub_video():
878
- st.markdown("# Video Detection")
879
- st.write("Video page placeholder. Model status:", "Loaded" if video_model else "Not loaded")
880
 
881
  # ======================= MAIN APP ============================
882
  def main():
@@ -897,29 +788,18 @@ def main():
897
  </div>
898
  """, unsafe_allow_html=True)
899
 
900
- if st.session_state.get("initialized") is None:
901
- st.session_state["initialized"] = True
902
 
903
  # route pages
904
- try:
905
- if page == "Home":
906
- show_home_page()
907
- elif page == "Audio Detection":
908
- show_audio_page()
909
- elif page == "Image Detection":
910
- show_image_page()
911
- elif page == "Video Detection":
912
- show_video_page()
913
- except NameError:
914
- # in case a function was lost, use stubs
915
- if page == "Home":
916
- _stub_home()
917
- elif page == "Audio Detection":
918
- _stub_audio()
919
- elif page == "Image Detection":
920
- _stub_image()
921
- elif page == "Video Detection":
922
- _stub_video()
923
 
924
  if __name__ == "__main__":
925
  main()
 
1
+ # app.py — HF Spaces ready (full)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import streamlit as st
3
  import torch
4
  import torch.nn as nn
 
20
  import os
21
  import importlib.util
22
  import traceback
23
+ import requests
24
+ import base64
25
+ import time
26
+ from urllib.parse import urlparse
27
 
28
  warnings.filterwarnings('ignore')
29
 
30
+ # ---------------- robust import for download_models ----------------
 
31
  try:
32
  import download_models as _dm
33
  download_from_hf = _dm.download_from_hf
34
  MODELS_DIR = _dm.MODELS_DIR
35
  except Exception:
36
+ # fallback: try repo root and src/
37
  _base = Path(__file__).parent
38
  candidates = [
39
  _base / "download_models.py",
 
59
  st.text(tb_text)
60
  raise ImportError("download_models import failed")
61
 
62
+ MODELS_DIR.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
63
 
64
+ # ---------------- diagnostic helper (call on Home page to inspect environment) ----------------
65
+ import sys, json, logging
66
+ logging.basicConfig(level=logging.INFO)
67
 
68
+ def debug_upload_state():
69
+ import streamlit as _st, os as _os
70
+ _st.write("=== DEBUG INFO ===")
71
+ _st.write("Space visibility (ENV):", _os.environ.get("SPACE_VISIBILITY", "unknown"))
72
+ _st.write("HF_TOKEN present?:", bool(_os.environ.get("HF_TOKEN")))
73
+ _st.write("Python version:", sys.version)
74
+ _st.write("Streamlit version:", _st.__version__)
75
+ _st.write("==================")
76
 
77
  # ======================= PAGE CONFIG ============================
78
  st.set_page_config(
 
82
  initial_sidebar_state="expanded"
83
  )
84
 
85
+ # ======================= STYLES (kept trimmed for brevity) ============================
86
  st.markdown("""
87
  <style>
88
+ /* (Your full CSS from earlier — trimmed here for reading) */
89
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
90
+ *{font-family:Inter, sans-serif}
91
+ #MainMenu{visibility:hidden} footer{visibility:hidden} header{visibility:hidden}
92
+ [data-testid="stSidebar"]{background:linear-gradient(180deg,#16213e 0%,#0f3460 100%);}
93
+ .feature-card{padding:1rem;border-radius:12px}
94
+ .metric-card{padding:1rem;border-radius:12px}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  </style>
96
  """, unsafe_allow_html=True)
97
 
 
104
  N_FFT = 2048
105
  HOP_LENGTH = 512
106
  MAX_AUDIO_LENGTH = 5
107
+ DEVICE = torch.device("cpu")
108
 
109
  class ImageConfig:
110
  MODEL_PATH = MODELS_DIR / "Image.pt"
 
121
 
122
  # ======================= MODEL DOWNLOAD & LOADING HELPERS ============================
123
  def download_and_load_torch_model(repo_id: str, filename: str, expected_device: torch.device):
 
 
 
 
124
  try:
125
  local_path = download_from_hf(repo_id, filename)
126
  except Exception as e:
127
  raise RuntimeError(f"Download failed for {repo_id}/{filename}: {e}")
128
+ if not Path(local_path).exists():
129
  raise FileNotFoundError(f"After download, file not found: {local_path}")
130
+ return Path(local_path)
131
 
132
+ # ======================= MODEL CLASSES ============================
133
  class AudioDeepfakeCNN(nn.Module):
134
  def __init__(self, num_classes=5):
135
  super().__init__()
 
212
  output = self.classifier(features)
213
  return output
214
 
215
+ # ======================= MODEL LOADERS ============================
216
  @st.cache_resource
217
  def load_audio_model():
218
  try:
 
446
  )
447
  return fig
448
 
449
+ # ======================= UPLOAD CACHE (per-session temp dir) & helpers ============================
450
+ def ensure_session_upload_dir():
451
+ """
452
+ Create a temp uploads dir per Streamlit session and store in st.session_state['UPLOAD_DIR'].
453
+ Also ensure it's cleared at the start of each page render (to meet 'delete on navigation/refresh' requirement).
454
+ """
455
+ if "UPLOAD_DIR" not in st.session_state:
456
+ tmpdir = Path(tempfile.mkdtemp(prefix="dfda_uploads_"))
457
+ st.session_state["UPLOAD_DIR"] = tmpdir
458
+ upload_dir = Path(st.session_state["UPLOAD_DIR"])
459
+ # Clear files at start of page run so files are effectively ephemeral per navigation
460
+ for f in list(upload_dir.glob("*")):
461
+ try:
462
+ if f.is_file():
463
+ f.unlink()
464
+ elif f.is_dir():
465
+ shutil.rmtree(f)
466
+ except Exception:
467
+ pass
468
+ upload_dir.mkdir(parents=True, exist_ok=True)
469
+ return upload_dir
470
+
471
+ def download_file_to_temp(url: str, timeout: int = 30):
472
+ try:
473
+ headers = {}
474
+ hf_token = os.environ.get("HF_TOKEN")
475
+ if hf_token:
476
+ headers["Authorization"] = f"Bearer {hf_token}"
477
+ r = requests.get(url, stream=True, headers=headers, timeout=timeout)
478
+ r.raise_for_status()
479
+ suffix = Path(urlparse(url).path).suffix or ".bin"
480
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
481
+ for chunk in r.iter_content(chunk_size=8192):
482
+ if chunk:
483
+ tmp.write(chunk)
484
+ return Path(tmp.name)
485
+ except Exception as e:
486
+ st.error(f"Failed to download from URL: {e}")
487
+ return None
488
+
489
+ def save_base64_to_path(b64_text: str, filename_hint: str, dest_dir: Path):
490
+ try:
491
+ if "," in b64_text and b64_text.strip().startswith("data:"):
492
+ b64_text = b64_text.split(",", 1)[1]
493
+ b64_text = "".join(b64_text.split())
494
+ raw = base64.b64decode(b64_text)
495
+ ext = Path(filename_hint).suffix or ".bin"
496
+ out_path = dest_dir / f"{int(time.time())}_{Path(filename_hint).stem}{ext}"
497
+ with open(out_path, "wb") as f:
498
+ f.write(raw)
499
+ return out_path
500
+ except Exception as e:
501
+ st.error(f"Failed to decode base64 file: {e}")
502
+ return None
503
+
504
+ def list_cached_uploads(upload_dir: Path):
505
+ files = sorted(upload_dir.glob("*"), key=lambda p: p.stat().st_mtime, reverse=True)
506
+ return files
507
+
508
+ def remove_cached_file(path: Path):
509
+ try:
510
+ path.unlink()
511
+ return True
512
+ except Exception as e:
513
+ st.error(f"Failed to delete {path.name}: {e}")
514
+ return False
515
+
516
+ # ======================= STARTUP: download models (attempt) ============================
517
  with st.spinner("Downloading models from Hugging Face (cached to /app/models)... This will happen once per Space runtime."):
518
  try:
519
+ audio_path = download_and_load_torch_model("jayanthbottu/DFDA", "Audio.pth", AudioConfig.DEVICE)
520
  AudioConfig.MODEL_PATH = audio_path
521
  except Exception as e:
522
  st.error(f"Audio model download/load error: {e}")
523
  try:
524
+ image_path = download_and_load_torch_model("jayanthbottu/DFDA", "Image.pt", ImageConfig.DEVICE)
525
  ImageConfig.MODEL_PATH = image_path
526
  except Exception as e:
527
  st.error(f"Image model download/load error: {e}")
528
  try:
529
+ video_path = download_and_load_torch_model("jayanthbottu/DFDA", "Video.pt", VideoConfig.DEVICE)
530
  VideoConfig.MODEL_PATH = video_path
531
  except Exception as e:
532
  st.error(f"Video model download/load error: {e}")
 
536
  image_model = load_image_model()
537
  video_model = load_video_model()
538
 
539
+ # ======================= PAGES (with unified upload/cached UI) ============================
540
  def show_home_page():
541
  st.markdown("""
542
  <div class="gradient-header">
 
545
  </div>
546
  """, unsafe_allow_html=True)
547
  st.markdown("## Comprehensive Multi-Modal Deepfake Analysis System")
548
+ st.markdown("If you want to see debug info about upload environment, enable diagnostics below.")
549
+ if st.checkbox("Show diagnostic upload state"):
550
+ debug_upload_state()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
 
552
+ # Reusable upload UI generator for each modality
553
+ def upload_ui_for_modality(mode:str, allowed_types:list, handle_fn, config):
554
+ """
555
+ mode: label string (Audio / Image / Video)
556
+ allowed_types: list of extensions for file_uploader
557
+ handle_fn: callback to call like handle_fn(path:Path) -> (result, extra)
558
+ config: config class used by handle_fn (for display)
559
+ """
560
+ upload_dir = ensure_session_upload_dir()
561
+ st.markdown(f"### Provide {mode} File for Analysis")
562
+ tab_upload, tab_url, tab_base64, tab_cached = st.tabs(["Upload (browser)", "Download from URL", "Paste base64", "Cached files"])
563
+ selected_path = None
564
+
565
+ with tab_upload:
566
+ uploaded_file = st.file_uploader(
567
+ f"Drag and drop your {mode.lower()} file here or click to browse",
568
+ type=allowed_types,
569
+ help=f"If browser upload fails with Axios 403, use the URL or base64 tabs."
570
+ )
571
+ if uploaded_file is not None:
572
+ # save to session upload dir
573
+ save_path = upload_dir / f"{int(time.time())}_{uploaded_file.name}"
574
+ with open(save_path, "wb") as f:
575
+ f.write(uploaded_file.getvalue())
576
+ st.success(f"Saved to session cache: {save_path.name}")
577
+ selected_path = save_path
578
+ # preview small previews per modality
579
+ if mode == "Audio":
580
+ st.audio(uploaded_file, format='audio/wav')
581
+ elif mode == "Image":
582
+ img = Image.open(uploaded_file)
583
+ st.image(img, caption="Uploaded Image")
584
+ elif mode == "Video":
585
+ st.video(uploaded_file)
586
+
587
+ with tab_url:
588
+ audio_url = st.text_input(f"Public HTTP/HTTPS URL to {mode} file", value="", key=f"url_{mode}")
589
+ if st.button(f"Download & Cache {mode} from URL", key=f"download_{mode}"):
590
+ if audio_url:
591
+ tmp = download_file_to_temp(audio_url)
592
+ if tmp:
593
+ dest = upload_dir / f"{int(time.time())}_{Path(tmp).name}"
594
+ shutil.move(str(tmp), str(dest))
595
+ st.success(f"Downloaded and cached as: {dest.name}")
596
+ selected_path = dest
597
+
598
+ with tab_base64:
599
+ st.markdown("If direct upload fails you can base64-encode your file locally and paste it here.")
600
+ b64_text = st.text_area("Paste base64-encoded file here (or data URI)", height=200, key=f"b64_{mode}")
601
+ filename_hint = st.text_input("Filename hint (e.g. myfile.wav)", value=f"pasted.{allowed_types[0]}", key=f"hint_{mode}")
602
+ if st.button("Save pasted base64 file", key=f"save_b64_{mode}"):
603
+ if b64_text.strip():
604
+ out = save_base64_to_path(b64_text, filename_hint, upload_dir)
605
+ if out:
606
+ st.success(f"Saved base64 to session cache: {out.name}")
607
+ selected_path = out
608
+
609
+ with tab_cached:
610
+ st.markdown("Cached files (session-only). Select one to analyze or delete.")
611
+ files = list_cached_uploads(upload_dir)
612
+ if files:
613
+ options = {f.name: f for f in files}
614
+ chosen = st.selectbox("Choose cached file", options.keys(), key=f"choose_{mode}")
615
+ if st.button("Use selected cached file", key=f"use_cached_{mode}"):
616
+ selected_path = options[chosen]
617
+ st.success(f"Selected {selected_path.name} for analysis")
618
+ if st.button("Delete selected cached file", key=f"del_cached_{mode}"):
619
+ ok = remove_cached_file(options[chosen])
620
+ if ok:
621
+ st.success("Deleted.")
622
+ st.experimental_rerun()
623
+ else:
624
+ st.info("No cached files found in this session.")
625
+
626
+ if selected_path is not None:
627
+ st.markdown("#### Selected file details")
628
+ try:
629
+ st.write("Filename:", selected_path.name)
630
+ st.write("Size (KB):", selected_path.stat().st_size / 1024)
631
+ except Exception:
632
+ pass
633
+
634
+ if st.button(f"ANALYZE SELECTED {mode.upper()}", type="primary", use_container_width=True, key=f"analyze_{mode}"):
635
+ with st.spinner(f"Processing {mode.lower()} file..."):
636
+ result = None
637
+ extra = None
638
+ try:
639
+ if mode == "Audio":
640
+ result, extra = handle_fn(audio_model, selected_path, config)
641
+ else:
642
+ result = handle_fn(image_model if mode=="Image" else video_model, selected_path, config)
643
+ except Exception as e:
644
+ st.error(f"Analysis error: {e}")
645
+ return
646
+ if isinstance(result, dict) and "error" in result:
647
+ st.error(f"Analysis failed: {result['error']}")
648
+ else:
649
+ # store results in session for display
650
+ st.session_state[f"{mode.lower()}_result"] = result
651
+ if extra is not None:
652
+ st.session_state[f"{mode.lower()}_extra"] = extra
653
+ st.success("Analysis completed successfully!")
654
+
655
+ # ======================= PAGE IMPLEMENTATIONS (use upload_ui_for_modality) ============================
656
  def show_audio_page():
657
  st.markdown("""
658
  <div class="gradient-header">
 
660
  <p>Synthetic Voice Generation & Manipulation Analysis</p>
661
  </div>
662
  """, unsafe_allow_html=True)
663
+ if audio_model is None:
 
664
  st.error("Audio model not loaded. Please ensure Audio.pth is available or HF download succeeded.")
665
  return
666
+ upload_ui_for_modality("Audio", ['wav','mp3','ogg','flac','m4a'], predict_audio, AudioConfig)
667
+
668
+ # display results if present
669
+ if st.session_state.get("audio_result"):
670
+ result = st.session_state["audio_result"]
671
+ spec_viz = st.session_state.get("audio_extra")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  st.markdown("---")
673
  st.markdown("## DETECTION RESULTS")
674
  is_fake = result['predicted_class'] != "Original"
675
  if is_fake:
676
+ st.markdown(f"""<div class="result-warning"><h2 class="result-title" style="color:#ef4444;">DEEPFAKE AUDIO DETECTED</h2><p class="result-subtitle">Detected Synthesis Method: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
 
 
 
 
 
 
677
  else:
678
+ st.markdown(f"""<div class="result-success"><h2 class="result-title" style="color:#10b981;">AUTHENTIC AUDIO</h2><p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
 
 
 
 
 
 
679
  col1, col2, col3 = st.columns(3)
680
  with col1:
681
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
 
683
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence Score</div></div>", unsafe_allow_html=True)
684
  with col3:
685
  authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
686
+ color = '#ef4444' if is_fake else '#10b981'
687
+ st.markdown(f"<div class='metric-card'><div class='metric-value' style='color:{color};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
688
  st.markdown("### Class Probability Distribution")
689
  fig_probs = plot_probabilities(result['probabilities'], AudioConfig.CLASS_NAMES)
690
  st.plotly_chart(fig_probs, use_container_width=True)
691
+ if spec_viz is not None:
692
+ st.markdown("### Mel Spectrogram")
693
+ fig_spec = plot_spectrogram(spec_viz)
694
  st.plotly_chart(fig_spec, use_container_width=True)
 
 
 
 
695
 
696
  def show_image_page():
697
  st.markdown("""
 
700
  <p>AI-Generated & Manipulated Image Analysis</p>
701
  </div>
702
  """, unsafe_allow_html=True)
703
+ if image_model is None:
 
704
  st.error("Image model not loaded. Please ensure Image.pt is available or HF download succeeded.")
705
  return
706
+ upload_ui_for_modality("Image", ['jpg','jpeg','png','bmp','webp'], predict_image, ImageConfig)
707
+
708
+ if st.session_state.get("image_result"):
709
+ result = st.session_state["image_result"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  st.markdown("---")
711
  st.markdown("## DETECTION RESULTS")
712
  is_fake = result['predicted_class'] != "Real"
713
  if is_fake:
714
+ st.markdown(f"""<div class="result-warning"><h2 class="result-title" style="color:#ef4444;">SYNTHETIC IMAGE DETECTED</h2><p class="result-subtitle">Generation Method: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
715
  else:
716
+ st.markdown(f"""<div class="result-success"><h2 class="result-title" style="color:#10b981;">AUTHENTIC IMAGE</h2><p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p></div>""", unsafe_allow_html=True)
717
  col1, col2, col3 = st.columns(3)
718
  with col1:
719
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
 
721
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence Score</div></div>", unsafe_allow_html=True)
722
  with col3:
723
  authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
724
+ color = '#ef4444' if is_fake else '#10b981'
725
+ st.markdown(f"<div class='metric-card'><div class='metric-value' style='color:{color};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
726
  st.markdown("### Class Probability Distribution")
727
  fig_probs = plot_probabilities(result['probabilities'], ImageConfig.CLASS_NAMES)
728
  st.plotly_chart(fig_probs, use_container_width=True)
729
  st.markdown("### Prediction Confidence Matrix")
730
  fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], ImageConfig.CLASS_NAMES)
731
  st.plotly_chart(fig_cm, use_container_width=True)
 
 
 
 
732
 
733
  def show_video_page():
734
  st.markdown("""
 
737
  <p>Face Manipulation & Video Synthesis Analysis</p>
738
  </div>
739
  """, unsafe_allow_html=True)
740
+ if video_model is None:
 
741
  st.error("Video model not loaded. Please ensure Video.pt is available or HF download succeeded.")
742
  return
743
+ upload_ui_for_modality("Video", ['mp4','avi','mov','mkv','webm'], predict_video, VideoConfig)
744
+
745
+ if st.session_state.get("video_result"):
746
+ result = st.session_state["video_result"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
747
  st.markdown("---")
748
  st.markdown("## DETECTION RESULTS")
749
  is_fake = result['predicted_class'] != "Original"
750
  if is_fake:
751
+ st.markdown(f"""<div class="result-warning"><h2 class="result-title" style="color:#ef4444;">DEEPFAKE VIDEO DETECTED</h2><p class="result-subtitle">Manipulation Type: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p><p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p></div>""", unsafe_allow_html=True)
752
  else:
753
+ st.markdown(f"""<div class="result-success"><h2 class="result-title" style="color:#10b981;">AUTHENTIC VIDEO</h2><p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p><p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p><p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p></div>""", unsafe_allow_html=True)
754
  col1, col2, col3, col4 = st.columns(4)
755
  with col1:
756
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['predicted_class']}</div><div class='metric-label'>Predicted Class</div></div>", unsafe_allow_html=True)
757
  with col2:
758
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result['confidence']*100:.1f}%</div><div class='metric-label'>Confidence</div></div>", unsafe_allow_html=True)
759
  with col3:
760
+ authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
761
+ color = '#ef4444' if is_fake else '#10b981'
762
+ st.markdown(f"<div class='metric-card'><div class='metric-value' style='color:{color};'>{authenticity}</div><div class='metric-label'>Status</div></div>", unsafe_allow_html=True)
763
  with col4:
764
  st.markdown(f"<div class='metric-card'><div class='metric-value'>{result.get('frame_count', 0)}</div><div class='metric-label'>Frames</div></div>", unsafe_allow_html=True)
765
  st.markdown("### Class Probability Distribution")
 
768
  st.markdown("### Prediction Confidence Matrix")
769
  fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], VideoConfig.CLASS_NAMES)
770
  st.plotly_chart(fig_cm, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
771
 
772
  # ======================= MAIN APP ============================
773
  def main():
 
788
  </div>
789
  """, unsafe_allow_html=True)
790
 
791
+ # ensure upload dir exists and cleared at page start
792
+ ensure_session_upload_dir()
793
 
794
  # route pages
795
+ if page == "Home":
796
+ show_home_page()
797
+ elif page == "Audio Detection":
798
+ show_audio_page()
799
+ elif page == "Image Detection":
800
+ show_image_page()
801
+ elif page == "Video Detection":
802
+ show_video_page()
 
 
 
 
 
 
 
 
 
 
 
803
 
804
  if __name__ == "__main__":
805
  main()