jayanthbottu commited on
Commit
576594d
·
verified ·
1 Parent(s): 920330b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +474 -1303
app.py CHANGED
@@ -1,1303 +1,474 @@
1
- import streamlit as st
2
- import torch
3
- import torch.nn as nn
4
- import torch.nn.functional as F
5
- import torchaudio
6
- import torchaudio.transforms as T
7
- import torchvision.transforms as transforms
8
- import numpy as np
9
- import cv2
10
- from pathlib import Path
11
- from PIL import Image
12
- import plotly.graph_objects as go
13
- import plotly.express as px
14
- import io
15
- import timm
16
- import warnings
17
- import tempfile
18
-
19
- warnings.filterwarnings('ignore')
20
-
21
- # ======================= PAGE CONFIG ============================
22
- st.set_page_config(
23
- page_title="DFDA - Deepfake Detection & Attribution",
24
- page_icon="https://play-lh.googleusercontent.com/wJEqeRZGZds8tVZKQq-azZ1sp7IjYgF92zXVKYD1vy1LrV6xz4KVriABCjdjS8jQyA",
25
- layout="wide",
26
- initial_sidebar_state="expanded"
27
- )
28
-
29
- # ======================= CUSTOM CSS ============================
30
- st.markdown("""
31
- <style>
32
- /* Import Google Fonts */
33
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
34
-
35
- /* Global Styles */
36
- * {
37
- font-family: 'Inter', sans-serif;
38
- }
39
-
40
- /* Hide Streamlit Branding */
41
- #MainMenu {visibility: hidden;}
42
- footer {visibility: hidden;}
43
- header {visibility: hidden;}
44
-
45
- /* Main Container */
46
- .main {
47
- background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%);
48
- }
49
-
50
- /* Sidebar Styling */
51
- [data-testid="stSidebar"] {
52
- background: linear-gradient(180deg, #16213e 0%, #0f3460 100%);
53
- border-right: 2px solid rgba(94, 129, 244, 0.3);
54
- }
55
-
56
- [data-testid="stSidebar"] [data-testid="stMarkdownContainer"] {
57
- color: #e4e4e7;
58
- }
59
-
60
- /* Header Gradient */
61
- .gradient-header {
62
- background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
63
- padding: 3rem 2rem;
64
- border-radius: 10px;
65
- text-align: center;
66
- margin-bottom: 2rem;
67
- box-shadow: 0 10px 60px rgba(102, 126, 234, 0.4);
68
- animation: fadeInDown 0.8s ease-out;
69
- }
70
-
71
- .gradient-header h1 {
72
- color: white;
73
- font-size: 2.5rem;
74
- font-weight: 800;
75
- margin: 0;
76
- text-shadow: 2px 4px 8px rgba(0,0,0,0.3);
77
- letter-spacing: -1px;
78
- }
79
-
80
- .gradient-header p {
81
- color: rgba(255,255,255,0.95);
82
- font-size: 1.2rem;
83
- margin-top: 0.8rem;
84
- font-weight: 500;
85
- }
86
-
87
- /* Feature Cards */
88
- .feature-card {
89
- background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(118, 75, 162, 0.15) 100%);
90
- border: 2px solid rgba(102, 126, 234, 0.3);
91
- border-radius: 16px;
92
- padding: 2rem;
93
- margin: 1.5rem 0;
94
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
95
- backdrop-filter: blur(10px);
96
- position: relative;
97
- overflow: hidden;
98
- }
99
-
100
- .feature-card::before {
101
- content: '';
102
- position: absolute;
103
- top: 0;
104
- left: -100%;
105
- width: 100%;
106
- height: 100%;
107
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
108
- transition: left 0.5s;
109
- }
110
-
111
- .feature-card:hover::before {
112
- left: 100%;
113
- }
114
-
115
- .feature-card:hover {
116
- transform: translateY(-8px) scale(1.02);
117
- box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
118
- border-color: rgba(102, 126, 234, 0.6);
119
- }
120
-
121
- .feature-title {
122
- color: #a5b4fc;
123
- font-size: 1.4rem;
124
- font-weight: 700;
125
- margin-bottom: 1rem;
126
- letter-spacing: -0.5px;
127
- }
128
-
129
- .feature-desc {
130
- color: #e4e4e7;
131
- font-size: 1.05rem;
132
- line-height: 1.8;
133
- }
134
-
135
- /* Result Cards */
136
- .result-success {
137
- background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(5, 150, 105, 0.2) 100%);
138
- border-left: 6px solid #10b981;
139
- border-radius: 16px;
140
- padding: 2rem;
141
- margin: 2rem 0;
142
- box-shadow: 0 10px 30px rgba(16, 185, 129, 0.2);
143
- animation: slideInLeft 0.6s ease-out;
144
- }
145
-
146
- .result-warning {
147
- background: linear-gradient(135deg, rgba(239, 68, 68, 0.2) 0%, rgba(220, 38, 38, 0.2) 100%);
148
- border-left: 6px solid #ef4444;
149
- border-radius: 16px;
150
- padding: 2rem;
151
- margin: 2rem 0;
152
- box-shadow: 0 10px 30px rgba(239, 68, 68, 0.2);
153
- animation: slideInLeft 0.6s ease-out;
154
- }
155
-
156
- .result-title {
157
- font-size: 2rem;
158
- font-weight: 800;
159
- margin: 0 0 1rem 0;
160
- letter-spacing: -0.5px;
161
- }
162
-
163
- .result-subtitle {
164
- font-size: 1.3rem;
165
- font-weight: 600;
166
- margin: 0.8rem 0;
167
- opacity: 0.95;
168
- }
169
-
170
- /* Metric Cards */
171
- .metric-card {
172
- background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
173
- border: 2px solid rgba(102, 126, 234, 0.3);
174
- border-radius: 16px;
175
- padding: 2rem;
176
- text-align: center;
177
- margin: 1rem 0.5rem;
178
- transition: all 0.3s ease;
179
- backdrop-filter: blur(10px);
180
- }
181
-
182
- .metric-card:hover {
183
- transform: translateY(-5px);
184
- box-shadow: 0 15px 35px rgba(102, 126, 234, 0.3);
185
- }
186
-
187
- .metric-value {
188
- font-size: 3rem;
189
- font-weight: 800;
190
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
191
- -webkit-background-clip: text;
192
- -webkit-text-fill-color: transparent;
193
- background-clip: text;
194
- margin-bottom: 0.5rem;
195
- }
196
-
197
- .metric-label {
198
- font-size: 1rem;
199
- color: #a5b4fc;
200
- font-weight: 600;
201
- text-transform: uppercase;
202
- letter-spacing: 1.5px;
203
- }
204
-
205
- /* Info Boxes */
206
- .info-box {
207
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
208
- border-left: 5px solid #3b82f6;
209
- border-radius: 12px;
210
- padding: 1.5rem;
211
- margin: 1.5rem 0;
212
- backdrop-filter: blur(10px);
213
- }
214
-
215
- .info-box h3 {
216
- color: #93c5fd;
217
- font-size: 1.4rem;
218
- font-weight: 700;
219
- margin: 0 0 0.8rem 0;
220
- }
221
-
222
- .info-box p {
223
- color: #e4e4e7;
224
- font-size: 1.05rem;
225
- line-height: 1.7;
226
- margin: 0;
227
- }
228
-
229
- /* Buttons */
230
- .stButton > button {
231
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
232
- color: white;
233
- border: none;
234
- border-radius: 12px;
235
- padding: 1rem 3rem;
236
- font-weight: 700;
237
- font-size: 1.1rem;
238
- transition: all 0.3s ease;
239
- box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
240
- text-transform: uppercase;
241
- letter-spacing: 1px;
242
- }
243
-
244
- .stButton > button:hover {
245
- transform: translateY(-3px);
246
- box-shadow: 0 12px 30px rgba(102, 126, 234, 0.5);
247
- background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
248
- }
249
-
250
- /* File Uploader */
251
- [data-testid="stFileUploader"] {
252
- border: 3px dashed rgba(102, 126, 234, 0.5);
253
- border-radius: 16px;
254
- padding: 2rem;
255
- background: rgba(102, 126, 234, 0.05);
256
- transition: all 0.3s ease;
257
- }
258
-
259
- [data-testid="stFileUploader"]:hover {
260
- border-color: rgba(102, 126, 234, 0.8);
261
- background: rgba(102, 126, 234, 0.1);
262
- }
263
-
264
- /* Progress Bar */
265
- .stProgress > div > div > div > div {
266
- background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
267
- }
268
-
269
- /* Radio Buttons */
270
- [data-testid="stRadio"] > div {
271
- background: rgba(102, 126, 234, 0.1);
272
- border-radius: 12px;
273
- padding: 1rem;
274
- }
275
-
276
- /* Divider */
277
- hr {
278
- border: none;
279
- height: 2px;
280
- background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.5), transparent);
281
- margin: 2rem 0;
282
- }
283
-
284
- /* Animations */
285
- @keyframes fadeInDown {
286
- from {
287
- opacity: 0;
288
- transform: translateY(-30px);
289
- }
290
- to {
291
- opacity: 1;
292
- transform: translateY(0);
293
- }
294
- }
295
-
296
- @keyframes slideInLeft {
297
- from {
298
- opacity: 0;
299
- transform: translateX(-30px);
300
- }
301
- to {
302
- opacity: 1;
303
- transform: translateX(0);
304
- }
305
- }
306
-
307
- @keyframes pulse {
308
- 0%, 100% {
309
- opacity: 1;
310
- }
311
- 50% {
312
- opacity: 0.7;
313
- }
314
- }
315
-
316
- /* Expander */
317
- [data-testid="stExpander"] {
318
- background: rgba(102, 126, 234, 0.1);
319
- border-radius: 12px;
320
- border: 1px solid rgba(102, 126, 234, 0.3);
321
- }
322
-
323
- /* Slider */
324
- .stSlider > div > div > div {
325
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
326
- }
327
- </style>
328
- """, unsafe_allow_html=True)
329
-
330
- # ======================= CONFIG CLASSES ============================
331
- class AudioConfig:
332
- MODEL_PATH = Path("Audio.pth")
333
- CLASS_NAMES = ["ElevenLabs", "Original", "Tacotron", "Text To Speech", "Voice Conversion"]
334
- SAMPLE_RATE = 16000
335
- N_MELS = 128
336
- N_FFT = 2048
337
- HOP_LENGTH = 512
338
- MAX_AUDIO_LENGTH = 5
339
- DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
340
-
341
- class ImageConfig:
342
- MODEL_PATH = Path("Image.pt")
343
- CLASS_NAMES = ["DALL-E", "DeepFaceLab", "Face2Face", "FaceShifter", "FaceSwap", "Midjourney",
344
- "NeuralTextures", "Real", "Stable Diffusion", "StyleGAN"]
345
- IMG_SIZE = 299
346
- DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
347
-
348
- class VideoConfig:
349
- MODEL_PATH = Path("Video.pt")
350
- CLASS_NAMES = ["Deepfakes", "Face2Face", "FaceShifter", "FaceSwap", "NeuralTextures", "Original"]
351
- DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
352
- FRAME_SKIP = 10
353
-
354
- # ======================= AUDIO MODEL ============================
355
- class AudioDeepfakeCNN(nn.Module):
356
- def __init__(self, num_classes=5):
357
- super().__init__()
358
- self.conv1 = nn.Sequential(
359
- nn.Conv2d(1, 64, 3, padding=1),
360
- nn.BatchNorm2d(64), nn.ReLU(inplace=True),
361
- nn.Conv2d(64, 64, 3, padding=1),
362
- nn.BatchNorm2d(64), nn.ReLU(inplace=True),
363
- nn.MaxPool2d(2,2),
364
- nn.Dropout2d(0.25)
365
- )
366
- self.conv2 = nn.Sequential(
367
- nn.Conv2d(64, 128, 3, padding=1),
368
- nn.BatchNorm2d(128), nn.ReLU(inplace=True),
369
- nn.Conv2d(128, 128, 3, padding=1),
370
- nn.BatchNorm2d(128), nn.ReLU(inplace=True),
371
- nn.MaxPool2d(2,2),
372
- nn.Dropout2d(0.25)
373
- )
374
- self.conv3 = nn.Sequential(
375
- nn.Conv2d(128, 256, 3, padding=1),
376
- nn.BatchNorm2d(256), nn.ReLU(inplace=True),
377
- nn.Conv2d(256, 256, 3, padding=1),
378
- nn.BatchNorm2d(256), nn.ReLU(inplace=True),
379
- nn.MaxPool2d(2,2),
380
- nn.Dropout2d(0.3)
381
- )
382
- self.conv4 = nn.Sequential(
383
- nn.Conv2d(256, 512, 3, padding=1),
384
- nn.BatchNorm2d(512), nn.ReLU(inplace=True),
385
- nn.Conv2d(512, 512, 3, padding=1),
386
- nn.BatchNorm2d(512), nn.ReLU(inplace=True),
387
- nn.MaxPool2d(2,2),
388
- nn.Dropout2d(0.3)
389
- )
390
- self.gap = nn.AdaptiveAvgPool2d((1,1))
391
- self.fc = nn.Sequential(
392
- nn.Linear(512, 256), nn.ReLU(inplace=True), nn.Dropout(0.5),
393
- nn.Linear(256, 128), nn.ReLU(inplace=True), nn.Dropout(0.4),
394
- nn.Linear(128, num_classes)
395
- )
396
- def forward(self, x):
397
- x = self.conv1(x); x = self.conv2(x); x = self.conv3(x); x = self.conv4(x)
398
- x = self.gap(x); x = x.view(x.size(0), -1); x = self.fc(x); return x
399
-
400
- # ======================= IMAGE MODEL ============================
401
- class AttentionBlock(nn.Module):
402
- def __init__(self, in_features):
403
- super(AttentionBlock, self).__init__()
404
- self.attention = nn.Sequential(
405
- nn.Linear(in_features, in_features // 8),
406
- nn.ReLU(),
407
- nn.Linear(in_features // 8, in_features),
408
- nn.Sigmoid()
409
- )
410
- def forward(self, x):
411
- att_weights = self.attention(x)
412
- return x * att_weights
413
-
414
- class EnhancedXceptionNet(nn.Module):
415
- def __init__(self, num_classes, dropout_rate=0.3, use_attention=True):
416
- super(EnhancedXceptionNet, self).__init__()
417
- self.base_model = timm.create_model('xception', pretrained=False)
418
- in_features = self.base_model.get_classifier().in_features
419
- self.base_model.reset_classifier(0)
420
- self.use_attention = use_attention
421
- if use_attention:
422
- self.attention = AttentionBlock(in_features)
423
- self.classifier = nn.Sequential(
424
- nn.Dropout(dropout_rate),
425
- nn.Linear(in_features, 512),
426
- nn.BatchNorm1d(512),
427
- nn.ReLU(),
428
- nn.Dropout(dropout_rate / 2),
429
- nn.Linear(512, num_classes)
430
- )
431
- def forward(self, x):
432
- features = self.base_model(x)
433
- if self.use_attention:
434
- features = self.attention(features)
435
- output = self.classifier(features)
436
- return output
437
-
438
- # ======================= MODEL LOADERS ============================
439
- @st.cache_resource
440
- def load_audio_model():
441
- try:
442
- model = AudioDeepfakeCNN(num_classes=len(AudioConfig.CLASS_NAMES))
443
- checkpoint = torch.load(AudioConfig.MODEL_PATH, map_location=AudioConfig.DEVICE)
444
- model.load_state_dict(checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint)
445
- model.to(AudioConfig.DEVICE).eval()
446
- return model
447
- except Exception as e:
448
- st.error(f"Error loading audio model: {e}")
449
- return None
450
-
451
- @st.cache_resource
452
- def load_image_model():
453
- try:
454
- model = EnhancedXceptionNet(num_classes=len(ImageConfig.CLASS_NAMES), dropout_rate=0.3, use_attention=True)
455
- checkpoint = torch.load(ImageConfig.MODEL_PATH, map_location=ImageConfig.DEVICE)
456
- state_dict = checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint
457
- model_dict = model.state_dict()
458
- filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
459
- model_dict.update(filtered_dict)
460
- model.load_state_dict(model_dict, strict=False)
461
- model.to(ImageConfig.DEVICE).eval()
462
- return model
463
- except Exception as e:
464
- st.error(f"Error loading image model: {e}")
465
- return None
466
-
467
- @st.cache_resource
468
- def load_video_model():
469
- try:
470
- num_classes = len(VideoConfig.CLASS_NAMES)
471
- model = timm.create_model("legacy_xception", pretrained=False, num_classes=num_classes)
472
- checkpoint = torch.load(VideoConfig.MODEL_PATH, map_location=VideoConfig.DEVICE)
473
- state_dict = checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint
474
- model_dict = model.state_dict()
475
- filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
476
- model_dict.update(filtered_dict)
477
- model.load_state_dict(model_dict, strict=False)
478
- model.to(VideoConfig.DEVICE).eval()
479
- return model
480
- except Exception as e:
481
- st.error(f"Error loading video model: {e}")
482
- return None
483
-
484
- # ======================= PREPROCESSING ============================
485
- def preprocess_audio(audio_path, sample_rate, n_mels, n_fft, hop_length, max_length):
486
- waveform, sr = torchaudio.load(str(audio_path))
487
- if waveform.shape[0] > 1:
488
- waveform = torch.mean(waveform, dim=0, keepdim=True)
489
- if sr != sample_rate:
490
- resampler = T.Resample(sr, sample_rate)
491
- waveform = resampler(waveform)
492
- max_len = sample_rate * max_length
493
- if waveform.shape[1] < max_len:
494
- waveform = torch.nn.functional.pad(waveform, (0, max_len - waveform.shape[1]))
495
- else:
496
- waveform = waveform[:, :max_len]
497
- mel_spec = T.MelSpectrogram(sample_rate, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels, power=2.0)(waveform)
498
- mel_spec_db = T.AmplitudeToDB(top_db=80)(mel_spec)
499
- mel_spec_db = (mel_spec_db - mel_spec_db.mean()) / (mel_spec_db.std() + 1e-8)
500
- return mel_spec_db, mel_spec_db.squeeze().numpy()
501
-
502
- def preprocess_image(image_path, size):
503
- trans = transforms.Compose([
504
- transforms.Resize((size, size)),
505
- transforms.ToTensor(),
506
- transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
507
- ])
508
- img = Image.open(image_path).convert('RGB')
509
- return trans(img).unsqueeze(0)
510
-
511
- def extract_frames(video_path, frame_skip=10):
512
- frames = []
513
- cap = cv2.VideoCapture(str(video_path))
514
- count = 0
515
- while cap.isOpened():
516
- ret, frame = cap.read()
517
- if not ret: break
518
- if count % frame_skip == 0:
519
- frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
520
- frames.append(frame)
521
- count += 1
522
- cap.release()
523
- return frames
524
-
525
- def preprocess_frame(frame, size):
526
- trans = transforms.Compose([
527
- transforms.ToPILImage(),
528
- transforms.Resize((size, size)),
529
- transforms.ToTensor(),
530
- transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
531
- ])
532
- return trans(frame).unsqueeze(0)
533
-
534
- # ======================= INFERENCE ============================
535
- def predict_audio(model, audio_path, config):
536
- device = config.DEVICE
537
- try:
538
- x, spec_viz = preprocess_audio(audio_path, config.SAMPLE_RATE, config.N_MELS, config.N_FFT, config.HOP_LENGTH, config.MAX_AUDIO_LENGTH)
539
- x = x.unsqueeze(0).to(device)
540
- with torch.no_grad():
541
- out = F.softmax(model(x), dim=1)[0]
542
- pred_idx = torch.argmax(out).item()
543
- return {
544
- "predicted_class": config.CLASS_NAMES[pred_idx],
545
- "predicted_index": pred_idx,
546
- "confidence": out[pred_idx].item(),
547
- "probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
548
- }, spec_viz
549
- except Exception as e:
550
- return {"error": str(e)}, None
551
-
552
- def predict_image(model, image_path, config):
553
- device = config.DEVICE
554
- try:
555
- x = preprocess_image(image_path, config.IMG_SIZE).to(device)
556
- with torch.no_grad():
557
- out = F.softmax(model(x), dim=1)[0]
558
- pred_idx = torch.argmax(out).item()
559
- return {
560
- "predicted_class": config.CLASS_NAMES[pred_idx],
561
- "predicted_index": pred_idx,
562
- "confidence": out[pred_idx].item(),
563
- "probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
564
- }
565
- except Exception as e:
566
- return {"error": str(e)}
567
-
568
- def predict_video(model, video_path, config):
569
- device = config.DEVICE
570
- try:
571
- frames = extract_frames(video_path, config.FRAME_SKIP)
572
- if len(frames) == 0:
573
- return {"error":"No frames extracted"}
574
- probs = []
575
- progress_bar = st.progress(0)
576
- for i, frame in enumerate(frames):
577
- x = preprocess_frame(frame, 300).to(device)
578
- with torch.no_grad():
579
- p = F.softmax(model(x),dim=1)[0].cpu().numpy()
580
- probs.append(p)
581
- progress_bar.progress((i + 1) / len(frames))
582
- probs = np.stack(probs)
583
- avg_prob = probs.mean(axis=0)
584
- pred_idx = np.argmax(avg_prob)
585
- return {
586
- "predicted_class": config.CLASS_NAMES[pred_idx],
587
- "predicted_index": pred_idx,
588
- "confidence": float(avg_prob[pred_idx]),
589
- "probabilities": {c: float(avg_prob[i]) for i, c in enumerate(config.CLASS_NAMES)},
590
- "frame_count": len(frames)
591
- }
592
- except Exception as e:
593
- return {"error": str(e)}
594
-
595
- # ======================= VISUALIZATION ============================
596
- def plot_probabilities(probs, class_names):
597
- fig = go.Figure(data=[
598
- go.Bar(
599
- x=list(probs.values()),
600
- y=list(probs.keys()),
601
- orientation='h',
602
- marker=dict(
603
- color=list(probs.values()),
604
- colorscale=[[0, '#667eea'], [0.5, '#764ba2'], [1, '#f093fb']],
605
- line=dict(color='rgba(102, 126, 234, 0.8)', width=2)
606
- ),
607
- text=[f'{v*100:.2f}%' for v in probs.values()],
608
- textposition='outside',
609
- textfont=dict(size=14, color='#e4e4e7', family='Inter')
610
- )
611
- ])
612
-
613
- fig.update_layout(
614
- title=dict(text="Class Probability Distribution", font=dict(size=22, color='#a5b4fc', family='Inter')),
615
- xaxis_title="Probability",
616
- yaxis_title="Class",
617
- height=400,
618
- template="plotly_dark",
619
- showlegend=False,
620
- plot_bgcolor='rgba(15,15,30,0.5)',
621
- paper_bgcolor='rgba(0,0,0,0)',
622
- font=dict(color='#e4e4e7', family='Inter'),
623
- xaxis=dict(gridcolor='rgba(102, 126, 234, 0.2)'),
624
- yaxis=dict(gridcolor='rgba(102, 126, 234, 0.2)')
625
- )
626
-
627
- return fig
628
-
629
- def plot_spectrogram(spec_data):
630
- if spec_data is None:
631
- return None
632
-
633
- fig = px.imshow(
634
- spec_data,
635
- labels=dict(x="Time", y="Frequency", color="Amplitude (dB)"),
636
- aspect='auto',
637
- color_continuous_scale=[[0, '#0f0f1e'], [0.25, '#667eea'], [0.5, '#764ba2'], [0.75, '#f093fb'], [1, '#fbbf24']]
638
- )
639
-
640
- fig.update_layout(
641
- title=dict(text="Mel Spectrogram Visualization", font=dict(size=22, color='#a5b4fc', family='Inter')),
642
- height=400,
643
- template="plotly_dark",
644
- plot_bgcolor='rgba(15,15,30,0.5)',
645
- paper_bgcolor='rgba(0,0,0,0)',
646
- font=dict(color='#e4e4e7', family='Inter')
647
- )
648
-
649
- return fig
650
-
651
- def plot_confusion_matrix(probs, predicted_class, class_names):
652
- n_classes = len(class_names)
653
- cm = np.zeros((n_classes, n_classes))
654
- pred_idx = class_names.index(predicted_class)
655
- for i, (cls, prob) in enumerate(probs.items()):
656
- cm[pred_idx, i] = prob
657
-
658
- fig = px.imshow(
659
- cm,
660
- labels=dict(x="Predicted Class", y="True Class", color="Probability"),
661
- x=class_names,
662
- y=class_names,
663
- color_continuous_scale=[[0, '#0f0f1e'], [0.5, '#667eea'], [1, '#f093fb']],
664
- aspect='auto',
665
- text_auto='.2f'
666
- )
667
-
668
- fig.update_layout(
669
- title=dict(text="Prediction Confidence Matrix", font=dict(size=22, color='#a5b4fc', family='Inter')),
670
- height=500,
671
- template="plotly_dark",
672
- plot_bgcolor='rgba(15,15,30,0.5)',
673
- paper_bgcolor='rgba(0,0,0,0)',
674
- font=dict(color='#e4e4e7', family='Inter')
675
- )
676
-
677
- return fig
678
-
679
- # ======================= PAGES ============================
680
- def show_home_page():
681
- st.markdown("""
682
- <div class="gradient-header">
683
- <h1>Deepfake Detection & Attribution System</h1>
684
- <p>Advanced AI-Powered Media Forensics Platform</p>
685
- </div>
686
- """, unsafe_allow_html=True)
687
-
688
- st.markdown("## Comprehensive Multi-Modal Deepfake Analysis System")
689
- 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.")
690
-
691
- col1, col2, col3 = st.columns(3)
692
-
693
- with col1:
694
- st.markdown("""
695
- <div class="feature-card">
696
- <div class="feature-title">Audio Deepfake Detection</div>
697
- <div class="feature-desc">
698
- 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.
699
- <br><br>
700
- <strong>Supported Formats:</strong> WAV, MP3, OGG, FLAC, M4A<br>
701
- <strong>Detection Classes:</strong> ElevenLabs, Tacotron, Voice Conversion, Original Audio, Text-to-Speech<br>
702
- <strong>Accuracy:</strong> 94.7% on validation set
703
- </div>
704
- </div>
705
- """, unsafe_allow_html=True)
706
-
707
- with col2:
708
- st.markdown("""
709
- <div class="feature-card">
710
- <div class="feature-title">Image Synthesis Detection</div>
711
- <div class="feature-desc">
712
- Employs Enhanced XceptionNet with attention mechanisms to detect AI-generated and manipulated images. Identifies various deepfake techniques and distinguishes between multiple generative models.
713
- <br><br>
714
- <strong>Supported Formats:</strong> JPG, PNG, BMP, WEBP<br>
715
- <strong>Detection Classes:</strong> DALL-E, Midjourney, StyleGAN, FaceSwap, DeepFaceLab, Real Images<br>
716
- <strong>Accuracy:</strong> 96.2% on benchmark datasets
717
- </div>
718
- </div>
719
- """, unsafe_allow_html=True)
720
-
721
- with col3:
722
- st.markdown("""
723
- <div class="feature-card">
724
- <div class="feature-title">Video Manipulation Analysis</div>
725
- <div class="feature-desc">
726
- 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.
727
- <br><br>
728
- <strong>Supported Formats:</strong> MP4, AVI, MOV, MKV, WEBM<br>
729
- <strong>Detection Classes:</strong> Deepfakes, Face2Face, FaceSwap, FaceShifter, Original Video<br>
730
- <strong>Accuracy:</strong> 93.8% with temporal aggregation
731
- </div>
732
- </div>
733
- """, unsafe_allow_html=True)
734
-
735
- st.markdown("---")
736
- st.markdown("## System Workflow")
737
-
738
- col1, col2, col3 = st.columns(3)
739
- with col1:
740
- st.markdown("""
741
- <div class="info-box">
742
- <h3>STEP 1: Upload Media</h3>
743
- <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>
744
- </div>
745
- """, unsafe_allow_html=True)
746
-
747
- with col2:
748
- st.markdown("""
749
- <div class="info-box">
750
- <h3>STEP 2: Deep Analysis</h3>
751
- <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>
752
- </div>
753
- """, unsafe_allow_html=True)
754
-
755
- with col3:
756
- st.markdown("""
757
- <div class="info-box">
758
- <h3>STEP 3: Comprehensive Results</h3>
759
- <p>Receive detailed analysis including predicted class, confidence scores, probability distributions, and visual representations. Export results for further investigation or documentation purposes.</p>
760
- </div>
761
- """, unsafe_allow_html=True)
762
-
763
- st.markdown("---")
764
- st.markdown("## Technical Specifications")
765
-
766
- col1, col2, col3, col4 = st.columns(4)
767
-
768
- with col1:
769
- device = "CUDA GPU" if torch.cuda.is_available() else "CPU"
770
- device_name = torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU"
771
- st.markdown(f"""
772
- <div class="metric-card">
773
- <div class="metric-value">{device}</div>
774
- <div class="metric-label">Computation Device</div>
775
- </div>
776
- """, unsafe_allow_html=True)
777
-
778
- with col2:
779
- st.markdown("""
780
- <div class="metric-card">
781
- <div class="metric-value">3</div>
782
- <div class="metric-label">Modalities</div>
783
- </div>
784
- """, unsafe_allow_html=True)
785
-
786
- with col3:
787
- total_classes = len(AudioConfig.CLASS_NAMES) + len(ImageConfig.CLASS_NAMES) + len(VideoConfig.CLASS_NAMES)
788
- st.markdown(f"""
789
- <div class="metric-card">
790
- <div class="metric-value">{total_classes}</div>
791
- <div class="metric-label">Detection Classes</div>
792
- </div>
793
- """, unsafe_allow_html=True)
794
-
795
- with col4:
796
- st.markdown("""
797
- <div class="metric-card">
798
- <div class="metric-value">92.5%</div>
799
- <div class="metric-label">Avg Accuracy</div>
800
- </div>
801
- """, unsafe_allow_html=True)
802
-
803
- st.markdown("---")
804
-
805
- with st.expander("Model Architecture Details"):
806
- st.markdown("""
807
- **Audio Detection Model:**
808
- - Architecture: Custom 4-layer CNN with batch normalization and dropout
809
- - Input: Mel spectrograms (128 mel bins, 5-second audio clips)
810
- - Parameters: ~2.1M trainable parameters
811
- - Training: Adam optimizer, learning rate 0.0001
812
-
813
- **Image Detection Model:**
814
- - Architecture: Enhanced XceptionNet with attention mechanism
815
- - Input: RGB images resized to 299x299 pixels
816
- - Parameters: ~22.8M trainable parameters
817
- - Training: SGD optimizer with momentum 0.9
818
-
819
- **Video Detection Model:**
820
- - Architecture: Legacy Xception with temporal aggregation
821
- - Input: Frame sequences extracted at configurable intervals
822
- - Parameters: ~22.9M trainable parameters
823
- - Training: Frame-level predictions with temporal averaging
824
- """)
825
-
826
- with st.expander("Research & Citations"):
827
- st.markdown("""
828
- This system is built upon cutting-edge research in deepfake detection and media forensics:
829
-
830
- - XceptionNet architecture for image manipulation detection
831
- - Mel-spectrogram analysis for synthetic audio detection
832
- - Temporal consistency analysis for video deepfake detection
833
- - Attention mechanisms for improved feature selection
834
-
835
- **Recommended for:**
836
- - Academic research in digital forensics
837
- - Media verification and fact-checking
838
- - Content authenticity assessment
839
- - Deepfake dataset curation and analysis
840
- """)
841
-
842
- def show_audio_page():
843
- st.markdown("""
844
- <div class="gradient-header">
845
- <h1>AUDIO DEEPFAKE DETECTION</h1>
846
- <p>Synthetic Voice Generation & Manipulation Analysis</p>
847
- </div>
848
- """, unsafe_allow_html=True)
849
-
850
- model = load_audio_model()
851
- if model is None:
852
- st.error("Audio model not loaded. Please ensure Audio.pth is in the correct directory.")
853
- return
854
-
855
- st.markdown("### Upload Audio File for Analysis")
856
- uploaded_file = st.file_uploader(
857
- "Drag and drop your audio file here or click to browse",
858
- type=['wav', 'mp3', 'ogg', 'flac', 'm4a'],
859
- help="Supported formats: WAV, MP3, OGG, FLAC, M4A (Max 5 seconds will be analyzed)"
860
- )
861
-
862
- if uploaded_file is not None:
863
- col1, col2 = st.columns([1, 1])
864
-
865
- with col1:
866
- st.markdown("#### Audio Preview")
867
- st.audio(uploaded_file, format='audio/wav')
868
- st.markdown(f"""
869
- **File Information:**
870
- - Filename: `{uploaded_file.name}`
871
- - Size: `{uploaded_file.size / 1024:.2f} KB`
872
- - Type: `{uploaded_file.type}`
873
- """)
874
-
875
- with col2:
876
- st.markdown("#### Analysis Configuration")
877
- st.markdown(f"""
878
- **Model Settings:**
879
- - Sample Rate: {AudioConfig.SAMPLE_RATE} Hz
880
- - Mel Bins: {AudioConfig.N_MELS}
881
- - Analysis Duration: {AudioConfig.MAX_AUDIO_LENGTH} seconds
882
- - Device: {AudioConfig.DEVICE}
883
- """)
884
-
885
- if st.button("ANALYZE AUDIO", type="primary", use_container_width=True):
886
- with st.spinner("Processing audio file and extracting features..."):
887
- with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
888
- tmp_file.write(uploaded_file.getvalue())
889
- tmp_path = tmp_file.name
890
-
891
- result, spec_viz = predict_audio(model, Path(tmp_path), AudioConfig)
892
-
893
- if "error" not in result:
894
- st.session_state.audio_result = result
895
- st.session_state.audio_spec = spec_viz
896
- st.success("Analysis completed successfully!")
897
- else:
898
- st.error(f"Analysis failed: {result['error']}")
899
-
900
- if 'audio_result' in st.session_state:
901
- result = st.session_state.audio_result
902
-
903
- st.markdown("---")
904
- st.markdown("## DETECTION RESULTS")
905
-
906
- is_fake = result['predicted_class'] != "Original"
907
-
908
- if is_fake:
909
- st.markdown(f"""
910
- <div class="result-warning">
911
- <h2 class="result-title" style="color: #ef4444;">DEEPFAKE AUDIO DETECTED</h2>
912
- <p class="result-subtitle">Detected Synthesis Method: <strong>{result['predicted_class']}</strong></p>
913
- <p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
914
- </div>
915
- """, unsafe_allow_html=True)
916
- else:
917
- st.markdown(f"""
918
- <div class="result-success">
919
- <h2 class="result-title" style="color: #10b981;">AUTHENTIC AUDIO</h2>
920
- <p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p>
921
- <p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
922
- </div>
923
- """, unsafe_allow_html=True)
924
-
925
- col1, col2, col3 = st.columns(3)
926
- with col1:
927
- st.markdown(f"""
928
- <div class="metric-card">
929
- <div class="metric-value">{result['predicted_class']}</div>
930
- <div class="metric-label">Predicted Class</div>
931
- </div>
932
- """, unsafe_allow_html=True)
933
-
934
- with col2:
935
- st.markdown(f"""
936
- <div class="metric-card">
937
- <div class="metric-value">{result['confidence']*100:.1f}%</div>
938
- <div class="metric-label">Confidence Score</div>
939
- </div>
940
- """, unsafe_allow_html=True)
941
-
942
- with col3:
943
- authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
944
- st.markdown(f"""
945
- <div class="metric-card">
946
- <div class="metric-value" style="color: {'#ef4444' if is_fake else '#10b981'};">{authenticity}</div>
947
- <div class="metric-label">Status</div>
948
- </div>
949
- """, unsafe_allow_html=True)
950
-
951
- st.markdown("### Class Probability Distribution")
952
- fig_probs = plot_probabilities(result['probabilities'], AudioConfig.CLASS_NAMES)
953
- st.plotly_chart(fig_probs, use_container_width=True)
954
-
955
- if 'audio_spec' in st.session_state and st.session_state.audio_spec is not None:
956
- st.markdown("### Mel Spectrogram Analysis")
957
- fig_spec = plot_spectrogram(st.session_state.audio_spec)
958
- st.plotly_chart(fig_spec, use_container_width=True)
959
-
960
- with st.expander("Detailed Class Probabilities"):
961
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
962
- for i, (class_name, prob) in enumerate(sorted_probs):
963
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
964
-
965
- def show_image_page():
966
- st.markdown("""
967
- <div class="gradient-header">
968
- <h1>IMAGE DEEPFAKE DETECTION</h1>
969
- <p>AI-Generated & Manipulated Image Analysis</p>
970
- </div>
971
- """, unsafe_allow_html=True)
972
-
973
- model = load_image_model()
974
- if model is None:
975
- st.error("Image model not loaded. Please ensure Image.pt is in the correct directory.")
976
- return
977
-
978
- st.markdown("### Upload Image File for Analysis")
979
- uploaded_file = st.file_uploader(
980
- "Drag and drop your image file here or click to browse",
981
- type=['jpg', 'jpeg', 'png', 'bmp', 'webp'],
982
- help="Supported formats: JPG, PNG, BMP, WEBP"
983
- )
984
-
985
- if uploaded_file is not None:
986
- col1, col2 = st.columns([1, 1])
987
-
988
- with col1:
989
- st.markdown("#### Image Preview")
990
- image = Image.open(uploaded_file)
991
- st.image(image, caption="Uploaded Image")
992
- st.markdown(f"""
993
- **File Information:**
994
- - Filename: `{uploaded_file.name}`
995
- - Size: `{uploaded_file.size / 1024:.2f} KB`
996
- - Dimensions: `{image.size[0]} x {image.size[1]} pixels`
997
- - Mode: `{image.mode}`
998
- """)
999
-
1000
- with col2:
1001
- st.markdown("#### Analysis Configuration")
1002
- st.markdown(f"""
1003
- **Model Settings:**
1004
- - Input Size: {ImageConfig.IMG_SIZE}x{ImageConfig.IMG_SIZE} pixels
1005
- - Architecture: Enhanced XceptionNet
1006
- - Device: {ImageConfig.DEVICE}
1007
- - Attention Mechanism: Enabled
1008
- """)
1009
-
1010
- if st.button("ANALYZE IMAGE", type="primary"):
1011
- with st.spinner("Processing image and extracting features..."):
1012
- with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file:
1013
- image.save(tmp_file.name)
1014
- tmp_path = tmp_file.name
1015
-
1016
- result = predict_image(model, Path(tmp_path), ImageConfig)
1017
-
1018
- if "error" not in result:
1019
- st.session_state.image_result = result
1020
- st.success("Analysis completed successfully!")
1021
- else:
1022
- st.error(f"Analysis failed: {result['error']}")
1023
-
1024
- if 'image_result' in st.session_state:
1025
- result = st.session_state.image_result
1026
-
1027
- st.markdown("---")
1028
- st.markdown("## DETECTION RESULTS")
1029
-
1030
- is_fake = result['predicted_class'] != "Real"
1031
-
1032
- if is_fake:
1033
- st.markdown(f"""
1034
- <div class="result-warning">
1035
- <h2 class="result-title" style="color: #ef4444;">SYNTHETIC IMAGE DETECTED</h2>
1036
- <p class="result-subtitle">Generation Method: <strong>{result['predicted_class']}</strong></p>
1037
- <p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
1038
- </div>
1039
- """, unsafe_allow_html=True)
1040
- else:
1041
- st.markdown(f"""
1042
- <div class="result-success">
1043
- <h2 class="result-title" style="color: #10b981;">AUTHENTIC IMAGE</h2>
1044
- <p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p>
1045
- <p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
1046
- </div>
1047
- """, unsafe_allow_html=True)
1048
-
1049
- col1, col2, col3 = st.columns(3)
1050
- with col1:
1051
- st.markdown(f"""
1052
- <div class="metric-card">
1053
- <div class="metric-value">{result['predicted_class']}</div>
1054
- <div class="metric-label">Predicted Class</div>
1055
- </div>
1056
- """, unsafe_allow_html=True)
1057
-
1058
- with col2:
1059
- st.markdown(f"""
1060
- <div class="metric-card">
1061
- <div class="metric-value">{result['confidence']*100:.1f}%</div>
1062
- <div class="metric-label">Confidence Score</div>
1063
- </div>
1064
- """, unsafe_allow_html=True)
1065
-
1066
- with col3:
1067
- authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
1068
- st.markdown(f"""
1069
- <div class="metric-card">
1070
- <div class="metric-value" style="color: {'#ef4444' if is_fake else '#10b981'};">{authenticity}</div>
1071
- <div class="metric-label">Status</div>
1072
- </div>
1073
- """, unsafe_allow_html=True)
1074
-
1075
- st.markdown("### Class Probability Distribution")
1076
- fig_probs = plot_probabilities(result['probabilities'], ImageConfig.CLASS_NAMES)
1077
- st.plotly_chart(fig_probs, use_container_width=True)
1078
-
1079
- st.markdown("### Prediction Confidence Matrix")
1080
- fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], ImageConfig.CLASS_NAMES)
1081
- st.plotly_chart(fig_cm, use_container_width=True)
1082
-
1083
- with st.expander("Detailed Class Probabilities"):
1084
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
1085
- for i, (class_name, prob) in enumerate(sorted_probs):
1086
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
1087
-
1088
- def show_video_page():
1089
- st.markdown("""
1090
- <div class="gradient-header">
1091
- <h1>VIDEO DEEPFAKE DETECTION</h1>
1092
- <p>Face Manipulation & Video Synthesis Analysis</p>
1093
- </div>
1094
- """, unsafe_allow_html=True)
1095
-
1096
- model = load_video_model()
1097
- if model is None:
1098
- st.error("Video model not loaded. Please ensure Video.pt is in the correct directory.")
1099
- return
1100
-
1101
- st.markdown("### Upload Video File for Analysis")
1102
- uploaded_file = st.file_uploader(
1103
- "Drag and drop your video file here or click to browse",
1104
- type=['mp4', 'avi', 'mov', 'mkv', 'webm'],
1105
- help="Supported formats: MP4, AVI, MOV, MKV, WEBM"
1106
- )
1107
-
1108
- if uploaded_file is not None:
1109
- col1, col2 = st.columns([1, 1])
1110
-
1111
- with col1:
1112
- st.markdown("#### Video Preview")
1113
- st.video(uploaded_file)
1114
- st.markdown(f"""
1115
- **File Information:**
1116
- - Filename: `{uploaded_file.name}`
1117
- - Size: `{uploaded_file.size / (1024*1024):.2f} MB`
1118
- - Type: `{uploaded_file.type}`
1119
- """)
1120
-
1121
- with col2:
1122
- st.markdown("#### Analysis Configuration")
1123
- frame_skip = st.slider(
1124
- "Frame Skip Rate",
1125
- min_value=1,
1126
- max_value=30,
1127
- value=10,
1128
- help="Higher values = faster processing but may reduce accuracy"
1129
- )
1130
-
1131
- st.markdown(f"""
1132
- **Model Settings:**
1133
- - Architecture: Legacy Xception
1134
- - Frame Analysis: Every {frame_skip} frames
1135
- - Input Size: 300x300 pixels per frame
1136
- - Device: {VideoConfig.DEVICE}
1137
- """)
1138
-
1139
- if st.button("ANALYZE VIDEO", type="primary", use_container_width=True):
1140
- with st.spinner("Extracting frames and performing temporal analysis..."):
1141
- with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as tmp_file:
1142
- tmp_file.write(uploaded_file.getvalue())
1143
- tmp_path = tmp_file.name
1144
-
1145
- VideoConfig.FRAME_SKIP = frame_skip
1146
- result = predict_video(model, Path(tmp_path), VideoConfig)
1147
-
1148
- if "error" not in result:
1149
- st.session_state.video_result = result
1150
- st.success("Analysis completed successfully!")
1151
- else:
1152
- st.error(f"Analysis failed: {result['error']}")
1153
-
1154
- if 'video_result' in st.session_state:
1155
- result = st.session_state.video_result
1156
-
1157
- st.markdown("---")
1158
- st.markdown("## DETECTION RESULTS")
1159
-
1160
- is_fake = result['predicted_class'] != "Original"
1161
-
1162
- if is_fake:
1163
- st.markdown(f"""
1164
- <div class="result-warning">
1165
- <h2 class="result-title" style="color: #ef4444;">DEEPFAKE VIDEO DETECTED</h2>
1166
- <p class="result-subtitle">Manipulation Type: <strong>{result['predicted_class']}</strong></p>
1167
- <p class="result-subtitle">Detection Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
1168
- <p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p>
1169
- </div>
1170
- """, unsafe_allow_html=True)
1171
- else:
1172
- st.markdown(f"""
1173
- <div class="result-success">
1174
- <h2 class="result-title" style="color: #10b981;">AUTHENTIC VIDEO</h2>
1175
- <p class="result-subtitle">Classification: <strong>{result['predicted_class']}</strong></p>
1176
- <p class="result-subtitle">Authenticity Confidence: <strong>{result['confidence']*100:.2f}%</strong></p>
1177
- <p class="result-subtitle">Frames Analyzed: <strong>{result.get('frame_count', 'N/A')}</strong></p>
1178
- </div>
1179
- """, unsafe_allow_html=True)
1180
-
1181
- col1, col2, col3, col4 = st.columns(4)
1182
- with col1:
1183
- st.markdown(f"""
1184
- <div class="metric-card">
1185
- <div class="metric-value">{result['predicted_class']}</div>
1186
- <div class="metric-label">Predicted Class</div>
1187
- </div>
1188
- """, unsafe_allow_html=True)
1189
-
1190
- with col2:
1191
- st.markdown(f"""
1192
- <div class="metric-card">
1193
- <div class="metric-value">{result['confidence']*100:.1f}%</div>
1194
- <div class="metric-label">Confidence</div>
1195
- </div>
1196
- """, unsafe_allow_html=True)
1197
-
1198
- with col3:
1199
- authenticity = "SYNTHETIC" if is_fake else "AUTHENTIC"
1200
- st.markdown(f"""
1201
- <div class="metric-card">
1202
- <div class="metric-value" style="color: {'#ef4444' if is_fake else '#10b981'};">{authenticity}</div>
1203
- <div class="metric-label">Status</div>
1204
- </div>
1205
- """, unsafe_allow_html=True)
1206
-
1207
- with col4:
1208
- st.markdown(f"""
1209
- <div class="metric-card">
1210
- <div class="metric-value">{result.get('frame_count', 0)}</div>
1211
- <div class="metric-label">Frames</div>
1212
- </div>
1213
- """, unsafe_allow_html=True)
1214
-
1215
- st.markdown("### Class Probability Distribution")
1216
- fig_probs = plot_probabilities(result['probabilities'], VideoConfig.CLASS_NAMES)
1217
- st.plotly_chart(fig_probs, use_container_width=True)
1218
-
1219
- st.markdown("### Prediction Confidence Matrix")
1220
- fig_cm = plot_confusion_matrix(result['probabilities'], result['predicted_class'], VideoConfig.CLASS_NAMES)
1221
- st.plotly_chart(fig_cm, use_container_width=True)
1222
-
1223
- with st.expander("Detailed Class Probabilities"):
1224
- sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
1225
- for i, (class_name, prob) in enumerate(sorted_probs):
1226
- st.markdown(f"**{i+1}. {class_name}:** `{prob*100:.4f}%`")
1227
-
1228
- # ======================= MAIN APP ============================
1229
- def main():
1230
- with st.sidebar:
1231
- # st.markdown("""
1232
- # <div style="text-align: center; padding: 1.5rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; margin-bottom: 2rem;">
1233
- # <h2 style="color: white; margin: 0; font-size: 1.5rem; font-weight: 800;">DFDA SYSTEM</h2>
1234
- # <p style="color: rgba(255,255,255,0.9); font-size: 0.95rem; margin: 0.5rem 0 0 0;">Deepfake Detection & Attribution</p>
1235
- # </div>
1236
- # """, unsafe_allow_html=True)
1237
-
1238
- page = st.radio(
1239
- "Navigation Menu",
1240
- ["Home", "Audio Detection", "Image Detection", "Video Detection"],
1241
- label_visibility="visible"
1242
- )
1243
-
1244
- st.markdown("---")
1245
-
1246
- st.markdown("""
1247
- <div style="padding: 1.2rem; background: rgba(102, 126, 234, 0.15); border-radius: 10px; border-left: 4px solid #667eea;">
1248
- <h4 style="margin-top: 0; color: #a5b4fc; font-size: 1.1rem;">System Status</h4>
1249
- <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Device:</strong> {}</p>
1250
- <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Models:</strong> Active</p>
1251
- <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Status:</strong> Online</p>
1252
- </div>
1253
- """.format("GPU (CUDA)" if torch.cuda.is_available() else "CPU"), unsafe_allow_html=True)
1254
-
1255
- st.markdown("---")
1256
-
1257
- with st.expander("About DFDA System"):
1258
- st.markdown("""
1259
- **DFDA (Deepfake Detection & Attribution)** is an advanced AI-powered platform designed for detecting and attributing synthetic media content across multiple modalities.
1260
-
1261
- **Core Capabilities:**
1262
- - Multi-modal deepfake detection
1263
- - High-accuracy classification
1264
- - Real-time inference
1265
- - Comprehensive probability analysis
1266
- - Visual interpretability tools
1267
-
1268
- **Applications:**
1269
- - Academic research
1270
- - Media forensics
1271
- - Content verification
1272
- - Digital evidence analysis
1273
-
1274
- **Version:** 1.0.0
1275
- **Framework:** PyTorch 2.0+
1276
- """)
1277
-
1278
- st.markdown("---")
1279
- st.markdown("""
1280
- <div style="text-align: left; color: #a5b4fc; font-size: 0.85rem; padding: 1rem 0;">
1281
- <p style="margin: 0;">Powered by Deep Learning
1282
- Developed by:</p>
1283
- <p style="margin: 0.3rem 0 0 0;">
1284
- 2303A51L99 - Sri Varsha Janagam<br>
1285
- 2303A51LA0 - Sindhu Kodati<br>
1286
- 2303A51LA7 - Jayanth Bottu<br>
1287
- 2303A51LA9 - Mahandra Gaddam<br>
1288
- 2303A51LB0 - Nikhil Kuchana
1289
- </p>
1290
- </div>
1291
- """, unsafe_allow_html=True)
1292
-
1293
- if page == "Home":
1294
- show_home_page()
1295
- elif page == "Audio Detection":
1296
- show_audio_page()
1297
- elif page == "Image Detection":
1298
- show_image_page()
1299
- elif page == "Video Detection":
1300
- show_video_page()
1301
-
1302
- if __name__ == "__main__":
1303
- main()
 
1
+ # app.py — HF Spaces ready
2
+ import streamlit as st
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ import torchaudio
7
+ import torchaudio.transforms as T
8
+ import torchvision.transforms as transforms
9
+ import numpy as np
10
+ import cv2
11
+ from pathlib import Path
12
+ from PIL import Image
13
+ import plotly.graph_objects as go
14
+ import plotly.express as px
15
+ import io
16
+ import timm
17
+ import warnings
18
+ import tempfile
19
+ import shutil
20
+ import os
21
+
22
+ # ensure download_models.py is present next to app.py and exposes download_from_hf and MODELS_DIR
23
+ from download_models import download_from_hf, MODELS_DIR
24
+
25
+ warnings.filterwarnings('ignore')
26
+
27
+ # HF repo & filenames (your repo)
28
+ AUDIO_REPO = "jayanthbottu/DFDA"
29
+ AUDIO_FILENAME = "Audio.pth"
30
+
31
+ VIDEO_REPO = "jayanthbottu/DFDA"
32
+ VIDEO_FILENAME = "Video.pth"
33
+
34
+ IMAGE_REPO = "jayanthbottu/DFDA"
35
+ IMAGE_FILENAME = "Image.pt"
36
+
37
+ MODELS_DIR.mkdir(parents=True, exist_ok=True)
38
+
39
+ # ======================= PAGE CONFIG ============================
40
+ st.set_page_config(
41
+ page_title="DFDA - Deepfake Detection & Attribution",
42
+ page_icon="https://play-lh.googleusercontent.com/wJEqeRZGZds8tVZKQq-azZ1sp7IjYgF92zXVKYD1vy1LrV6xz4KVriABCjdjS8jQyA",
43
+ layout="wide",
44
+ initial_sidebar_state="expanded"
45
+ )
46
+
47
+ # ======================= CUSTOM CSS ============================
48
+ st.markdown("""<style>/* ... keep your CSS unchanged ... */</style>""", unsafe_allow_html=True)
49
+
50
+ # ======================= CONFIG CLASSES ============================
51
+ class AudioConfig:
52
+ MODEL_PATH = MODELS_DIR / "Audio.pth"
53
+ CLASS_NAMES = ["ElevenLabs", "Original", "Tacotron", "Text To Speech", "Voice Conversion"]
54
+ SAMPLE_RATE = 16000
55
+ N_MELS = 128
56
+ N_FFT = 2048
57
+ HOP_LENGTH = 512
58
+ MAX_AUDIO_LENGTH = 5
59
+ DEVICE = torch.device("cpu") # Spaces are CPU only
60
+
61
+ class ImageConfig:
62
+ MODEL_PATH = MODELS_DIR / "Image.pt"
63
+ CLASS_NAMES = ["DALL-E", "DeepFaceLab", "Face2Face", "FaceShifter", "FaceSwap", "Midjourney",
64
+ "NeuralTextures", "Real", "Stable Diffusion", "StyleGAN"]
65
+ IMG_SIZE = 299
66
+ DEVICE = torch.device("cpu")
67
+
68
+ class VideoConfig:
69
+ MODEL_PATH = MODELS_DIR / "Video.pth"
70
+ CLASS_NAMES = ["Deepfakes", "Face2Face", "FaceShifter", "FaceSwap", "NeuralTextures", "Original"]
71
+ DEVICE = torch.device("cpu")
72
+ FRAME_SKIP = 10
73
+
74
+ # ======================= MODEL DOWNLOAD & LOADING HELPERS ============================
75
+ def download_and_load_torch_model(repo_id: str, filename: str, expected_device: torch.device):
76
+ """
77
+ Downloads filename from repo_id into MODELS_DIR (if missing) and returns local Path.
78
+ Raises exception on failure.
79
+ """
80
+ try:
81
+ local_path = download_from_hf(repo_id, filename)
82
+ except Exception as e:
83
+ raise RuntimeError(f"Download failed for {repo_id}/{filename}: {e}")
84
+ if not local_path.exists():
85
+ raise FileNotFoundError(f"After download, file not found: {local_path}")
86
+ # Loading deferred to specific loader functions (so we can instantiate architecture before loading state)
87
+ return local_path
88
+
89
+ # ======================= AUDIO / IMAGE / VIDEO MODEL CLASSES (unchanged) ============================
90
+ # Audio model class
91
+ class AudioDeepfakeCNN(nn.Module):
92
+ def __init__(self, num_classes=5):
93
+ super().__init__()
94
+ self.conv1 = nn.Sequential(
95
+ nn.Conv2d(1, 64, 3, padding=1),
96
+ nn.BatchNorm2d(64), nn.ReLU(inplace=True),
97
+ nn.Conv2d(64, 64, 3, padding=1),
98
+ nn.BatchNorm2d(64), nn.ReLU(inplace=True),
99
+ nn.MaxPool2d(2,2),
100
+ nn.Dropout2d(0.25)
101
+ )
102
+ self.conv2 = nn.Sequential(
103
+ nn.Conv2d(64, 128, 3, padding=1),
104
+ nn.BatchNorm2d(128), nn.ReLU(inplace=True),
105
+ nn.Conv2d(128, 128, 3, padding=1),
106
+ nn.BatchNorm2d(128), nn.ReLU(inplace=True),
107
+ nn.MaxPool2d(2,2),
108
+ nn.Dropout2d(0.25)
109
+ )
110
+ self.conv3 = nn.Sequential(
111
+ nn.Conv2d(128, 256, 3, padding=1),
112
+ nn.BatchNorm2d(256), nn.ReLU(inplace=True),
113
+ nn.Conv2d(256, 256, 3, padding=1),
114
+ nn.BatchNorm2d(256), nn.ReLU(inplace=True),
115
+ nn.MaxPool2d(2,2),
116
+ nn.Dropout2d(0.3)
117
+ )
118
+ self.conv4 = nn.Sequential(
119
+ nn.Conv2d(256, 512, 3, padding=1),
120
+ nn.BatchNorm2d(512), nn.ReLU(inplace=True),
121
+ nn.Conv2d(512, 512, 3, padding=1),
122
+ nn.BatchNorm2d(512), nn.ReLU(inplace=True),
123
+ nn.MaxPool2d(2,2),
124
+ nn.Dropout2d(0.3)
125
+ )
126
+ self.gap = nn.AdaptiveAvgPool2d((1,1))
127
+ self.fc = nn.Sequential(
128
+ nn.Linear(512, 256), nn.ReLU(inplace=True), nn.Dropout(0.5),
129
+ nn.Linear(256, 128), nn.ReLU(inplace=True), nn.Dropout(0.4),
130
+ nn.Linear(128, num_classes)
131
+ )
132
+ def forward(self, x):
133
+ x = self.conv1(x); x = self.conv2(x); x = self.conv3(x); x = self.conv4(x)
134
+ x = self.gap(x); x = x.view(x.size(0), -1); x = self.fc(x); return x
135
+
136
+ # Image model pieces
137
+ class AttentionBlock(nn.Module):
138
+ def __init__(self, in_features):
139
+ super(AttentionBlock, self).__init__()
140
+ self.attention = nn.Sequential(
141
+ nn.Linear(in_features, in_features // 8),
142
+ nn.ReLU(),
143
+ nn.Linear(in_features // 8, in_features),
144
+ nn.Sigmoid()
145
+ )
146
+ def forward(self, x):
147
+ att_weights = self.attention(x)
148
+ return x * att_weights
149
+
150
+ class EnhancedXceptionNet(nn.Module):
151
+ def __init__(self, num_classes, dropout_rate=0.3, use_attention=True):
152
+ super(EnhancedXceptionNet, self).__init__()
153
+ self.base_model = timm.create_model('xception', pretrained=False)
154
+ in_features = self.base_model.get_classifier().in_features
155
+ self.base_model.reset_classifier(0)
156
+ self.use_attention = use_attention
157
+ if use_attention:
158
+ self.attention = AttentionBlock(in_features)
159
+ self.classifier = nn.Sequential(
160
+ nn.Dropout(dropout_rate),
161
+ nn.Linear(in_features, 512),
162
+ nn.BatchNorm1d(512),
163
+ nn.ReLU(),
164
+ nn.Dropout(dropout_rate / 2),
165
+ nn.Linear(512, num_classes)
166
+ )
167
+ def forward(self, x):
168
+ features = self.base_model(x)
169
+ if self.use_attention:
170
+ features = self.attention(features)
171
+ output = self.classifier(features)
172
+ return output
173
+
174
+ # ======================= MODEL LOADERS (use cache_resource for HF Spaces) ============================
175
+ @st.cache_resource
176
+ def load_audio_model():
177
+ # instantiate and then load state_dict
178
+ try:
179
+ model = AudioDeepfakeCNN(num_classes=len(AudioConfig.CLASS_NAMES))
180
+ checkpoint = torch.load(AudioConfig.MODEL_PATH, map_location="cpu")
181
+ state = checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint
182
+ model.load_state_dict(state, strict=False)
183
+ model.to(AudioConfig.DEVICE).eval()
184
+ return model
185
+ except Exception as e:
186
+ st.error(f"Error loading audio model: {e}")
187
+ return None
188
+
189
+ @st.cache_resource
190
+ def load_image_model():
191
+ try:
192
+ model = EnhancedXceptionNet(num_classes=len(ImageConfig.CLASS_NAMES), dropout_rate=0.3, use_attention=True)
193
+ checkpoint = torch.load(ImageConfig.MODEL_PATH, map_location="cpu")
194
+ state_dict = checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint
195
+ model_dict = model.state_dict()
196
+ filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
197
+ model_dict.update(filtered_dict)
198
+ model.load_state_dict(model_dict, strict=False)
199
+ model.to(ImageConfig.DEVICE).eval()
200
+ return model
201
+ except Exception as e:
202
+ st.error(f"Error loading image model: {e}")
203
+ return None
204
+
205
+ @st.cache_resource
206
+ def load_video_model():
207
+ try:
208
+ num_classes = len(VideoConfig.CLASS_NAMES)
209
+ model = timm.create_model("legacy_xception", pretrained=False, num_classes=num_classes)
210
+ checkpoint = torch.load(VideoConfig.MODEL_PATH, map_location="cpu")
211
+ state_dict = checkpoint['model_state_dict'] if 'model_state_dict' in checkpoint else checkpoint
212
+ model_dict = model.state_dict()
213
+ filtered_dict = {k: v for k, v in state_dict.items() if k in model_dict and v.shape == model_dict[k].shape}
214
+ model_dict.update(filtered_dict)
215
+ model.load_state_dict(model_dict, strict=False)
216
+ model.to(VideoConfig.DEVICE).eval()
217
+ return model
218
+ except Exception as e:
219
+ st.error(f"Error loading video model: {e}")
220
+ return None
221
+
222
+ # ======================= PREPROCESSING (unchanged) ============================
223
+ def preprocess_audio(audio_path, sample_rate, n_mels, n_fft, hop_length, max_length):
224
+ waveform, sr = torchaudio.load(str(audio_path))
225
+ if waveform.shape[0] > 1:
226
+ waveform = torch.mean(waveform, dim=0, keepdim=True)
227
+ if sr != sample_rate:
228
+ resampler = T.Resample(sr, sample_rate)
229
+ waveform = resampler(waveform)
230
+ max_len = sample_rate * max_length
231
+ if waveform.shape[1] < max_len:
232
+ waveform = torch.nn.functional.pad(waveform, (0, max_len - waveform.shape[1]))
233
+ else:
234
+ waveform = waveform[:, :max_len]
235
+ mel_spec = T.MelSpectrogram(sample_rate, n_fft=n_fft, hop_length=hop_length, n_mels=n_mels, power=2.0)(waveform)
236
+ mel_spec_db = T.AmplitudeToDB(top_db=80)(mel_spec)
237
+ mel_spec_db = (mel_spec_db - mel_spec_db.mean()) / (mel_spec_db.std() + 1e-8)
238
+ return mel_spec_db, mel_spec_db.squeeze().numpy()
239
+
240
+ def preprocess_image(image_path, size):
241
+ trans = transforms.Compose([
242
+ transforms.Resize((size, size)),
243
+ transforms.ToTensor(),
244
+ transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
245
+ ])
246
+ img = Image.open(image_path).convert('RGB')
247
+ return trans(img).unsqueeze(0)
248
+
249
+ def extract_frames(video_path, frame_skip=10):
250
+ frames = []
251
+ cap = cv2.VideoCapture(str(video_path))
252
+ count = 0
253
+ while cap.isOpened():
254
+ ret, frame = cap.read()
255
+ if not ret: break
256
+ if count % frame_skip == 0:
257
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
258
+ frames.append(frame)
259
+ count += 1
260
+ cap.release()
261
+ return frames
262
+
263
+ def preprocess_frame(frame, size):
264
+ trans = transforms.Compose([
265
+ transforms.ToPILImage(),
266
+ transforms.Resize((size, size)),
267
+ transforms.ToTensor(),
268
+ transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
269
+ ])
270
+ return trans(frame).unsqueeze(0)
271
+
272
+ # ======================= INFERENCE (unchanged) ============================
273
+ def predict_audio(model, audio_path, config):
274
+ device = config.DEVICE
275
+ try:
276
+ x, spec_viz = preprocess_audio(audio_path, config.SAMPLE_RATE, config.N_MELS, config.N_FFT, config.HOP_LENGTH, config.MAX_AUDIO_LENGTH)
277
+ x = x.unsqueeze(0).to(device)
278
+ with torch.no_grad():
279
+ out = F.softmax(model(x), dim=1)[0]
280
+ pred_idx = torch.argmax(out).item()
281
+ return {
282
+ "predicted_class": config.CLASS_NAMES[pred_idx],
283
+ "predicted_index": pred_idx,
284
+ "confidence": out[pred_idx].item(),
285
+ "probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
286
+ }, spec_viz
287
+ except Exception as e:
288
+ return {"error": str(e)}, None
289
+
290
+ def predict_image(model, image_path, config):
291
+ device = config.DEVICE
292
+ try:
293
+ x = preprocess_image(image_path, config.IMG_SIZE).to(device)
294
+ with torch.no_grad():
295
+ out = F.softmax(model(x), dim=1)[0]
296
+ pred_idx = torch.argmax(out).item()
297
+ return {
298
+ "predicted_class": config.CLASS_NAMES[pred_idx],
299
+ "predicted_index": pred_idx,
300
+ "confidence": out[pred_idx].item(),
301
+ "probabilities": {c: out[i].item() for i, c in enumerate(config.CLASS_NAMES)}
302
+ }
303
+ except Exception as e:
304
+ return {"error": str(e)}
305
+
306
+ def predict_video(model, video_path, config):
307
+ device = config.DEVICE
308
+ try:
309
+ frames = extract_frames(video_path, config.FRAME_SKIP)
310
+ if len(frames) == 0:
311
+ return {"error":"No frames extracted"}
312
+ probs = []
313
+ progress_bar = st.progress(0)
314
+ for i, frame in enumerate(frames):
315
+ x = preprocess_frame(frame, 300).to(device)
316
+ with torch.no_grad():
317
+ p = F.softmax(model(x),dim=1)[0].cpu().numpy()
318
+ probs.append(p)
319
+ progress_bar.progress((i + 1) / len(frames))
320
+ probs = np.stack(probs)
321
+ avg_prob = probs.mean(axis=0)
322
+ pred_idx = np.argmax(avg_prob)
323
+ return {
324
+ "predicted_class": config.CLASS_NAMES[pred_idx],
325
+ "predicted_index": pred_idx,
326
+ "confidence": float(avg_prob[pred_idx]),
327
+ "probabilities": {c: float(avg_prob[i]) for i, c in enumerate(config.CLASS_NAMES)},
328
+ "frame_count": len(frames)
329
+ }
330
+ except Exception as e:
331
+ return {"error": str(e)}
332
+
333
+ # ======================= VISUALIZATION (unchanged) ============================
334
+ def plot_probabilities(probs, class_names):
335
+ fig = go.Figure(data=[
336
+ go.Bar(
337
+ x=list(probs.values()),
338
+ y=list(probs.keys()),
339
+ orientation='h',
340
+ marker=dict(
341
+ color=list(probs.values()),
342
+ colorscale=[[0, '#667eea'], [0.5, '#764ba2'], [1, '#f093fb']],
343
+ line=dict(color='rgba(102, 126, 234, 0.8)', width=2)
344
+ ),
345
+ text=[f'{v*100:.2f}%' for v in probs.values()],
346
+ textposition='outside',
347
+ textfont=dict(size=14, color='#e4e4e7', family='Inter')
348
+ )
349
+ ])
350
+ fig.update_layout(
351
+ title=dict(text="Class Probability Distribution", font=dict(size=22, color='#a5b4fc', family='Inter')),
352
+ xaxis_title="Probability",
353
+ yaxis_title="Class",
354
+ height=400,
355
+ template="plotly_dark",
356
+ showlegend=False,
357
+ plot_bgcolor='rgba(15,15,30,0.5)',
358
+ paper_bgcolor='rgba(0,0,0,0)',
359
+ font=dict(color='#e4e4e7', family='Inter'),
360
+ xaxis=dict(gridcolor='rgba(102, 126, 234, 0.2)'),
361
+ yaxis=dict(gridcolor='rgba(102, 126, 234, 0.2)')
362
+ )
363
+ return fig
364
+
365
+ def plot_spectrogram(spec_data):
366
+ if spec_data is None:
367
+ return None
368
+ fig = px.imshow(
369
+ spec_data,
370
+ labels=dict(x="Time", y="Frequency", color="Amplitude (dB)"),
371
+ aspect='auto',
372
+ color_continuous_scale=[[0, '#0f0f1e'], [0.25, '#667eea'], [0.5, '#764ba2'], [0.75, '#f093fb'], [1, '#fbbf24']]
373
+ )
374
+ fig.update_layout(
375
+ title=dict(text="Mel Spectrogram Visualization", font=dict(size=22, color='#a5b4fc', family='Inter')),
376
+ height=400,
377
+ template="plotly_dark",
378
+ plot_bgcolor='rgba(15,15,30,0.5)',
379
+ paper_bgcolor='rgba(0,0,0,0)',
380
+ font=dict(color='#e4e4e7', family='Inter')
381
+ )
382
+ return fig
383
+
384
+ def plot_confusion_matrix(probs, predicted_class, class_names):
385
+ n_classes = len(class_names)
386
+ cm = np.zeros((n_classes, n_classes))
387
+ pred_idx = class_names.index(predicted_class)
388
+ for i, (cls, prob) in enumerate(probs.items()):
389
+ cm[pred_idx, i] = prob
390
+ fig = px.imshow(
391
+ cm,
392
+ labels=dict(x="Predicted Class", y="True Class", color="Probability"),
393
+ x=class_names,
394
+ y=class_names,
395
+ color_continuous_scale=[[0, '#0f0f1e'], [0.5, '#667eea'], [1, '#f093fb']],
396
+ aspect='auto',
397
+ text_auto='.2f'
398
+ )
399
+ fig.update_layout(
400
+ title=dict(text="Prediction Confidence Matrix", font=dict(size=22, color='#a5b4fc', family='Inter')),
401
+ height=500,
402
+ template="plotly_dark",
403
+ plot_bgcolor='rgba(15,15,30,0.5)',
404
+ paper_bgcolor='rgba(0,0,0,0)',
405
+ font=dict(color='#e4e4e7', family='Inter')
406
+ )
407
+ return fig
408
+
409
+ # ======================= STARTUP: download and set model paths ============================
410
+ # This runs on import/startup — keeps UI responsive by using messages
411
+ with st.spinner("Downloading models from Hugging Face (cached to /app/models)... This will happen once per Space runtime."):
412
+ try:
413
+ audio_path = download_and_load_torch_model(AUDIO_REPO, AUDIO_FILENAME, AudioConfig.DEVICE)
414
+ AudioConfig.MODEL_PATH = audio_path
415
+ except Exception as e:
416
+ st.error(f"Audio model download/load error: {e}")
417
+ # do not stop app — keep UI showing error
418
+ try:
419
+ image_path = download_and_load_torch_model(IMAGE_REPO, IMAGE_FILENAME, ImageConfig.DEVICE)
420
+ ImageConfig.MODEL_PATH = image_path
421
+ except Exception as e:
422
+ st.error(f"Image model download/load error: {e}")
423
+ try:
424
+ video_path = download_and_load_torch_model(VIDEO_REPO, VIDEO_FILENAME, VideoConfig.DEVICE)
425
+ VideoConfig.MODEL_PATH = video_path
426
+ except Exception as e:
427
+ st.error(f"Video model download/load error: {e}")
428
+
429
+ # Load models (cached resource)
430
+ audio_model = load_audio_model()
431
+ image_model = load_image_model()
432
+ video_model = load_video_model()
433
+
434
+ # ======================= PAGES ============================
435
+ # (keep all your page functions unchanged: show_home_page, show_audio_page, show_image_page, show_video_page)
436
+ # ... paste the rest of your existing functions here (they already reference load_* and configs) ...
437
+
438
+ # For brevity in this listing, we assume the rest of your UI functions are unchanged and present
439
+ # (You can reuse the exact definitions of show_home_page, show_audio_page, show_image_page, show_video_page
440
+ # from your original file — they will work as-is because AudioConfig, ImageConfig, VideoConfig were updated.)
441
+
442
+ def main():
443
+ with st.sidebar:
444
+ page = st.radio(
445
+ "Navigation Menu",
446
+ ["Home", "Audio Detection", "Image Detection", "Video Detection"],
447
+ label_visibility="visible"
448
+ )
449
+ st.markdown("---")
450
+ st.markdown(f"""
451
+ <div style="padding: 1.2rem; background: rgba(102, 126, 234, 0.15); border-radius: 10px; border-left: 4px solid #667eea;">
452
+ <h4 style="margin-top: 0; color: #a5b4fc; font-size: 1.1rem;">System Status</h4>
453
+ <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Device:</strong> {"CPU"}</p>
454
+ <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Models:</strong> Active</p>
455
+ <p style="margin: 0.5rem 0; color: #e4e4e7;"><strong>Status:</strong> Online</p>
456
+ </div>
457
+ """, unsafe_allow_html=True)
458
+
459
+ if st.session_state.get("initialized") is None:
460
+ st.session_state["initialized"] = True
461
+
462
+ # route pages
463
+ page = st.sidebar.radio("Navigation Menu", ["Home", "Audio Detection", "Image Detection", "Video Detection"])
464
+ if page == "Home":
465
+ show_home_page()
466
+ elif page == "Audio Detection":
467
+ show_audio_page()
468
+ elif page == "Image Detection":
469
+ show_image_page()
470
+ elif page == "Video Detection":
471
+ show_video_page()
472
+
473
+ if __name__ == "__main__":
474
+ main()