AbdulWahab14 commited on
Commit
657baa6
Β·
verified Β·
1 Parent(s): 6e72c7b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -330
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py β€” DeepFake AI Forensics (WhatsApp Voice Note Fix)
2
  import os
3
  import subprocess
4
  import tempfile
@@ -30,12 +30,12 @@ model.to(device)
30
  print(f"[+] Model loaded on {device}")
31
 
32
  # ==========================================
33
- # 2. AUDIO / VIDEO PREPROCESSING (ROBUST)
34
  # ==========================================
35
  def normalize_audio(file_path):
36
  """
37
- Converts ANY audio/video to standard 16kHz mono WAV via FFmpeg.
38
- This fixes WhatsApp voice notes (Opus/OGG disguised as MP3),
39
  corrupt headers, and exotic codecs.
40
  """
41
  out = tempfile.mktemp(suffix=".wav")
@@ -52,7 +52,7 @@ def normalize_audio(file_path):
52
  result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53
 
54
  if result.returncode != 0:
55
- err = result.stderr.decode('utf-8', errors='ignore')[:200]
56
  raise RuntimeError(f"FFmpeg could not decode this file. It may be corrupted or use an unsupported codec.\nDetails: {err}")
57
 
58
  return out
@@ -62,23 +62,17 @@ def convert_to_audio(file_path):
62
 
63
  # Supported audio formats (including WhatsApp Opus)
64
  audio_exts = ["wav", "mp3", "flac", "m4a", "ogg", "opus", "aac", "wma", "oga"]
65
- # Supported video formats
66
- video_exts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "3gp"]
67
 
68
  if ext in audio_exts:
69
  print(f"[+] Audio detected ({ext}) β†’ normalizing via ffmpeg...")
70
  return normalize_audio(file_path)
71
 
72
- if ext in video_exts:
73
- print("[+] Video detected β†’ extracting & normalizing audio via ffmpeg...")
74
- return normalize_audio(file_path)
75
-
76
  # Unknown extension? Try ffmpeg anyway as last resort
77
  print(f"[+] Unknown format ({ext}) β†’ attempting ffmpeg decode...")
78
  try:
79
  return normalize_audio(file_path)
80
  except Exception:
81
- raise ValueError(f"Unsupported file format: {ext}. Please upload MP3, WAV, M4A, OGG, OPUS, or MP4.")
82
 
83
  def load_audio(path):
84
  audio, _ = librosa.load(path, sr=16000)
@@ -93,30 +87,48 @@ def predict(audio):
93
  with torch.no_grad():
94
  logits = model(**inputs).logits
95
  probs = torch.softmax(logits, dim=-1)[0]
96
- return float(probs[0]), float(probs[1])
 
 
 
97
 
98
  def audio_features(audio):
99
- mfcc = librosa.feature.mfcc(y=audio, sr=16000)
100
  return {
101
- "mfcc": float(np.mean(np.var(mfcc, axis=1))),
102
- "energy": float(np.mean(audio ** 2))
 
 
103
  }
104
 
105
  def analyze(file_path):
106
  audio_path = convert_to_audio(file_path)
107
  audio = load_audio(audio_path)
108
- human_p, ai_p = predict(audio)
 
109
  feats = audio_features(audio)
110
- anomaly = (feats["mfcc"] / 500.0) + (feats["energy"] * 2.0)
111
- ai_score = np.clip((ai_p * 0.6 + anomaly * 0.4), 0.0, 1.0)
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- if ai_score < 0.35:
114
  verdict = "HUMAN VOICE"
115
  level = "LOW RISK"
116
  color = "#059669"
117
  icon = "πŸ§‘"
118
  glow = "rgba(5,150,105,0.18)"
119
- elif ai_score < 0.65:
120
  verdict = "UNCERTAIN / MIXED"
121
  level = "MEDIUM RISK"
122
  color = "#d97706"
@@ -129,8 +141,8 @@ def analyze(file_path):
129
  icon = "πŸ€–"
130
  glow = "rgba(220,38,38,0.18)"
131
 
132
- confidence = int(ai_score * 100)
133
- return verdict, level, confidence, ai_score, feats, audio_path, color, icon, glow
134
 
135
  # ==========================================
136
  # 4. VISUALIZATION
@@ -193,6 +205,19 @@ def confidence_circle(percentage, color):
193
  </div>
194
  """
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  EQUALIZER_HTML = """
197
  <div style="display: flex; align-items: flex-end; justify-content: center; height: 50px; gap: 5px; margin: 16px 0;">
198
  <div class="eq-bar" style="width: 6px; height: 40%; background: linear-gradient(to top, #4f46e5, #2563eb); border-radius: 3px; animation: eq-bounce 0.8s infinite ease-in-out 0s;"></div>
@@ -220,12 +245,13 @@ def detect_audio(audio_file):
220
  )
221
 
222
  try:
223
- verdict, level, confidence, ai_score, feats, audio_path, color, icon, glow = analyze(audio_file)
224
  plot_path = generate_audio_plots(audio_path)
225
- percentage = ai_score * 100
 
226
 
227
- status_emoji = "🟒" if percentage < 35 else "🟑" if percentage < 65 else "πŸ”΄"
228
- status_text = "LIKELY REAL" if percentage < 35 else "SUSPICIOUS" if percentage < 65 else "HIGH RISK"
229
 
230
  circle = confidence_circle(confidence, color)
231
 
@@ -246,10 +272,6 @@ def detect_audio(audio_file):
246
  </div>
247
 
248
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 24px;">
249
- <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid {color};">
250
- <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">AI Probability</div>
251
- <div style="font-size: 1.5em; font-weight: 700; color: {color};">{percentage:.1f}%</div>
252
- </div>
253
  <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid {color};">
254
  <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Risk Level</div>
255
  <div style="font-size: 1.2em; font-weight: 700; color: {color};">{level}</div>
@@ -264,135 +286,55 @@ def detect_audio(audio_file):
264
  </div>
265
  </div>
266
 
 
 
 
 
 
 
267
  <div style="background: #f8fafc; border-radius: 12px; padding: 18px; margin-bottom: 20px;">
268
  <div style="font-size: 0.75em; color: #64748b; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 12px;">πŸ”¬ DSP Forensic Signatures</div>
269
  <div style="display: flex; justify-content: space-around; font-family: 'SF Mono', monospace; font-size: 0.9em; flex-wrap: wrap; gap: 12px;">
270
  <div style="text-align: center;">
271
  <div style="color: #94a3b8; font-size: 0.8em;">MFCC Variance</div>
272
- <div style="color: #1e293b; font-weight: 600;">{feats['mfcc']:.4f}</div>
273
  </div>
274
  <div style="text-align: center;">
275
  <div style="color: #94a3b8; font-size: 0.8em;">Signal Energy</div>
276
  <div style="color: #1e293b; font-weight: 600;">{feats['energy']:.6f}</div>
277
  </div>
278
  <div style="text-align: center;">
279
- <div style="color: #94a3b8; font-size: 0.8em;">Neural Score</div>
280
- <div style="color: #1e293b; font-weight: 600;">{ai_score:.4f}</div>
281
  </div>
282
- </div>
283
- </div>
284
-
285
- <div style="font-size: 0.8em; color: #64748b; border-top: 1px solid #e2e8f0; padding-top: 14px; line-height: 1.6;">
286
- <strong style="color: #475569;">Interpretation Guide:</strong><br>
287
- <span style="color: #059669;">● 0–35%</span> Very likely genuine human voice &nbsp;|&nbsp;
288
- <span style="color: #d97706;">● 35–65%</span> Mixed signal, manual review advised &nbsp;|&nbsp;
289
- <span style="color: #dc2626;">● 65–100%</span> Strong synthetic / AI indicators detected
290
- </div>
291
- </div>
292
- """
293
- return plot_path, result_html, f"{percentage:.1f}%", color, ""
294
-
295
- except Exception as e:
296
- err_msg = str(e)
297
- if "FFmpeg" in err_msg:
298
- err_html = f"<div style='color:#dc2626;padding:30px;'><strong>❌ File Decode Error</strong><br><br>{err_msg}<br><br><span style='color:#475569;font-size:0.9em;'>WhatsApp voice notes are often .opus or .ogg files disguised as .mp3. Try renaming the file to .ogg or exporting it differently.</span></div>"
299
- else:
300
- err_html = f"<div style='color:#dc2626;padding:30px;'>❌ Analysis Error: {err_msg}</div>"
301
- return None, err_html, "Error", "#dc2626", ""
302
-
303
-
304
- def detect_video(video_file):
305
- if video_file is None:
306
- return (
307
- None,
308
- '<div style="text-align:center;color:#dc2626;padding:30px;">❌ No video file provided</div>',
309
- "Waiting...",
310
- "#64748b",
311
- EQUALIZER_HTML + '<div style="text-align:center;color:#64748b;font-size:0.9em;">Upload video to extract & analyze audio track</div>'
312
- )
313
-
314
- try:
315
- verdict, level, confidence, ai_score, feats, audio_path, color, icon, glow = analyze(video_file)
316
- plot_path = generate_audio_plots(audio_path)
317
- percentage = ai_score * 100
318
-
319
- status_emoji = "🟒" if percentage < 35 else "🟑" if percentage < 65 else "πŸ”΄"
320
- status_text = "LIKELY REAL" if percentage < 35 else "SUSPICIOUS" if percentage < 65 else "HIGH RISK"
321
-
322
- circle = confidence_circle(confidence, color)
323
-
324
- result_html = f"""
325
- <div style="background: #ffffff;
326
- border: 1px solid {color}35; border-radius: 20px; padding: 28px;
327
- box-shadow: 0 4px 24px {glow}, 0 1px 3px rgba(0,0,0,0.08);">
328
-
329
- <div style="display: flex; align-items: center; gap: 20px; margin-bottom: 24px; flex-wrap: wrap;">
330
- <div style="font-size: 3em; line-height: 1;">{icon}</div>
331
- <div style="flex: 1; min-width: 200px;">
332
- <div style="font-size: 0.8em; color: #64748b; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 4px;">Audio Track Verdict</div>
333
- <div style="font-size: 1.5em; font-weight: 800; color: {color}; letter-spacing: -0.5px;">{verdict}</div>
334
- </div>
335
- <div style="min-width: 140px;">
336
- {circle}
337
- </div>
338
- </div>
339
-
340
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 24px;">
341
- <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid {color};">
342
- <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">AI Probability</div>
343
- <div style="font-size: 1.5em; font-weight: 700; color: {color};">{percentage:.1f}%</div>
344
- </div>
345
- <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid {color};">
346
- <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Risk Level</div>
347
- <div style="font-size: 1.2em; font-weight: 700; color: {color};">{level}</div>
348
- </div>
349
- <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid #2563eb;">
350
- <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Confidence</div>
351
- <div style="font-size: 1.2em; font-weight: 700; color: #2563eb;">{confidence}%</div>
352
- </div>
353
- <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid #d97706;">
354
- <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Status</div>
355
- <div style="font-size: 1em; font-weight: 600; color: #d97706;">{status_emoji} {status_text}</div>
356
- </div>
357
- </div>
358
-
359
- <div style="background: #f8fafc; border-radius: 12px; padding: 18px; margin-bottom: 20px;">
360
- <div style="font-size: 0.75em; color: #64748b; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 12px;">πŸ”¬ DSP Forensic Signatures (Audio Track)</div>
361
- <div style="display: flex; justify-content: space-around; font-family: 'SF Mono', monospace; font-size: 0.9em; flex-wrap: wrap; gap: 12px;">
362
  <div style="text-align: center;">
363
- <div style="color: #94a3b8; font-size: 0.8em;">MFCC Variance</div>
364
- <div style="color: #1e293b; font-weight: 600;">{feats['mfcc']:.4f}</div>
365
- </div>
366
- <div style="text-align: center;">
367
- <div style="color: #94a3b8; font-size: 0.8em;">Signal Energy</div>
368
- <div style="color: #1e293b; font-weight: 600;">{feats['energy']:.6f}</div>
369
- </div>
370
- <div style="text-align: center;">
371
- <div style="color: #94a3b8; font-size: 0.8em;">Neural Score</div>
372
- <div style="color: #1e293b; font-weight: 600;">{ai_score:.4f}</div>
373
  </div>
374
  </div>
375
  </div>
376
 
377
  <div style="font-size: 0.8em; color: #64748b; border-top: 1px solid #e2e8f0; padding-top: 14px; line-height: 1.6;">
378
- <strong style="color: #475569;">Analysis Note:</strong> Deepfake videos frequently contain synthetic audio tracks.
379
- This scan was performed on the extracted audio waveform. Always verify visual cues separately for complete assessment.
 
 
380
  </div>
381
  </div>
382
  """
383
- return plot_path, result_html, f"{percentage:.1f}%", color, ""
384
 
385
  except Exception as e:
386
  err_msg = str(e)
387
  if "FFmpeg" in err_msg:
388
- err_html = f"<div style='color:#dc2626;padding:30px;'><strong>❌ File Decode Error</strong><br><br>{err_msg}</div>"
389
  else:
390
  err_html = f"<div style='color:#dc2626;padding:30px;'>❌ Analysis Error: {err_msg}</div>"
391
  return None, err_html, "Error", "#dc2626", ""
392
 
393
 
394
  # ==========================================
395
- # 7. GRADIO UI β€” LIGHT ROYAL BLUE THEME
396
  # ==========================================
397
  CUSTOM_CSS = """
398
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
@@ -405,31 +347,6 @@ CUSTOM_CSS = """
405
  }
406
  body { background: #e8f0fe !important; }
407
 
408
- /* Tabs */
409
- .tab-nav {
410
- background: #dbeafe !important;
411
- border-radius: 14px !important;
412
- padding: 6px !important;
413
- border: 1px solid #bfdbfe !important;
414
- margin-bottom: 24px !important;
415
- gap: 6px !important;
416
- }
417
- .tab-nav button {
418
- color: #475569 !important;
419
- font-weight: 600 !important;
420
- border-radius: 10px !important;
421
- padding: 10px 28px !important;
422
- border: none !important;
423
- background: transparent !important;
424
- transition: all 0.3s ease !important;
425
- }
426
- .tab-nav button:hover { color: #1e40af !important; }
427
- .tab-nav button.selected {
428
- background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%) !important;
429
- color: #ffffff !important;
430
- box-shadow: 0 4px 20px rgba(37, 99, 235, 0.3) !important;
431
- }
432
-
433
  /* Upload zones */
434
  .upload-container {
435
  background: #ffffff !important;
@@ -490,11 +407,26 @@ input, textarea {
490
  background: #ffffff !important;
491
  border: 1px solid #bfdbfe !important;
492
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  """
494
 
495
  def build_ui():
496
  with gr.Blocks(
497
- title="DeepFake AI Forensics",
498
  theme=gr.themes.Base(
499
  primary_hue="blue",
500
  neutral_hue="slate",
@@ -517,196 +449,136 @@ def build_ui():
517
  </div>
518
  <p style="font-size: 1.05em; color: #475569; margin-top: 14px; max-width: 560px; margin-left: auto; margin-right: auto; line-height: 1.6;">
519
  Neural + DSP ensemble detection for synthetic voice identification.
 
520
  </p>
521
  </div>
522
  """)
523
 
524
- with gr.Tabs():
525
-
526
  # ═══════════════════════════════════════════
527
- # AUDIO TAB
528
  # ═══════════════════════════════════════════
529
- with gr.TabItem("πŸ”Š Audio Detection", id=0):
530
- with gr.Row():
531
- with gr.Column(scale=1, min_width=340):
532
- gr.Markdown("### Upload Audio File", elem_classes="section-title")
533
-
534
- gr.HTML("""
535
- <div style="margin-bottom: 12px; display: flex; flex-wrap: wrap; gap: 6px;">
536
- <span class="format-badge">MP3</span>
537
- <span class="format-badge">WAV</span>
538
- <span class="format-badge">M4A</span>
539
- <span class="format-badge">FLAC</span>
540
- <span class="format-badge">OGG</span>
541
- <span class="format-badge">OPUS</span>
542
- </div>
543
- <div style="font-size: 0.8em; color: #475569; margin-bottom: 16px; display: flex; align-items: center; gap: 6px;">
544
- <span style="font-size: 1.2em;">πŸ“Ž</span>
545
- <span>Maximum file size: <strong style="color: #1e40af;">50 MB</strong></span>
546
- </div>
547
- <div style="font-size: 0.75em; color: #94a3b8; background: #eff6ff; border-radius: 8px; padding: 10px 12px; margin-bottom: 12px; line-height: 1.5;">
548
- πŸ’‘ <strong>WhatsApp voice notes:</strong> If your file fails to upload, try renaming it from <code>.mp3</code> to <code>.ogg</code> or <code>.opus</code> before uploading.
549
- </div>
550
- """)
551
-
552
- audio_input = gr.Audio(
553
- label="",
554
- type="filepath",
555
- elem_classes="upload-container"
556
- )
557
-
558
- audio_waves = gr.HTML(value=EQUALIZER_HTML + '<div style="text-align:center;color:#64748b;font-size:0.85em;">Audio waveform ready for analysis</div>')
559
-
560
- audio_btn = gr.Button("πŸ” Analyze Audio", variant="primary", size="lg")
561
-
562
- audio_score_text = gr.Textbox(
563
- label="",
564
- value="--%",
565
- interactive=False
566
- )
567
-
568
- with gr.Column(scale=2):
569
- gr.Markdown("### Forensic Analysis Report", elem_classes="section-title")
570
-
571
- audio_plot = gr.Image(
572
- label="",
573
- show_label=False,
574
- elem_classes="result-image"
575
- )
576
-
577
- audio_result = gr.HTML(
578
- value="""
579
- <div style="background: #ffffff; border: 2px dashed #bfdbfe; border-radius: 20px; padding: 50px 30px; text-align: center; margin-top: 8px;">
580
- <div style="font-size: 3em; margin-bottom: 16px;">πŸ“Š</div>
581
- <div style="color: #64748b; font-size: 1.1em; font-weight: 600;">Results will appear here</div>
582
- <div style="color: #94a3b8; font-size: 0.9em; margin-top: 8px;">Upload an audio file and click analyze to begin</div>
583
- </div>
584
- """
585
- )
586
-
587
- audio_btn.click(
588
- fn=detect_audio,
589
- inputs=[audio_input],
590
- outputs=[audio_plot, audio_result, audio_score_text, audio_score_text, audio_waves]
591
  )
592
-
593
- # ═══════════════════════════════════════════
594
- # VIDEO TAB
595
- # ═══════════════════════════════════════════
596
- with gr.TabItem("🎬 Video Detection", id=1):
597
- with gr.Row():
598
- with gr.Column(scale=1, min_width=340):
599
- gr.Markdown("### Upload Video File", elem_classes="section-title")
600
-
601
- gr.HTML("""
602
- <div style="margin-bottom: 12px; display: flex; flex-wrap: wrap; gap: 6px;">
603
- <span class="format-badge">MP4</span>
604
- <span class="format-badge">MOV</span>
605
- <span class="format-badge">AVI</span>
606
- <span class="format-badge">MKV</span>
607
- <span class="format-badge">WEBM</span>
608
- </div>
609
- <div style="font-size: 0.8em; color: #475569; margin-bottom: 16px; display: flex; align-items: center; gap: 6px;">
610
- <span style="font-size: 1.2em;">πŸ“Ž</span>
611
- <span>Maximum file size: <strong style="color: #1e40af;">100 MB</strong></span>
612
- </div>
613
- """)
614
-
615
- video_input = gr.Video(
616
- label="",
617
- elem_classes="upload-container"
618
- )
619
-
620
- video_waves = gr.HTML(value=EQUALIZER_HTML + '<div style="text-align:center;color:#64748b;font-size:0.85em;">Video loaded β€” audio track ready for extraction</div>')
621
-
622
- video_btn = gr.Button("πŸ” Analyze Video", variant="primary", size="lg")
623
-
624
- video_score_text = gr.Textbox(
625
- label="",
626
- value="--%",
627
- interactive=False
628
- )
629
-
630
- with gr.Column(scale=2):
631
- gr.Markdown("### Forensic Analysis Report", elem_classes="section-title")
632
-
633
- video_plot = gr.Image(
634
- label="",
635
- show_label=False
636
- )
637
-
638
- video_result = gr.HTML(
639
- value="""
640
- <div style="background: #ffffff; border: 2px dashed #bfdbfe; border-radius: 20px; padding: 50px 30px; text-align: center; margin-top: 8px;">
641
- <div style="font-size: 3em; margin-bottom: 16px;">🎬</div>
642
- <div style="color: #64748b; font-size: 1.1em; font-weight: 600;">Results will appear here</div>
643
- <div style="color: #94a3b8; font-size: 0.9em; margin-top: 8px;">Upload a video to extract & analyze its audio track</div>
644
- </div>
645
- """
646
- )
647
-
648
- video_btn.click(
649
- fn=detect_video,
650
- inputs=[video_input],
651
- outputs=[video_plot, video_result, video_score_text, video_score_text, video_waves]
652
  )
653
 
654
  # ═══════════════════════════════════════════
655
- # ABOUT TAB
656
  # ═══════════════════════════════════════════
657
- with gr.TabItem("ℹ️ How It Works", id=2):
658
- gr.HTML("""
659
- <div style="max-width: 900px; margin: auto; padding: 20px 0 40px 0;">
660
- <h2 style="color: #1e293b; font-size: 1.7em; margin-bottom: 28px; text-align: center; font-weight: 700;">🧠 Detection Pipeline</h2>
661
-
662
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; margin-bottom: 36px;">
663
- <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
664
- <div style="font-size: 2em; margin-bottom: 10px;">🧠</div>
665
- <h3 style="color: #1e40af; margin: 0 0 6px 0; font-size: 1.1em;">Transformer Classifier</h3>
666
- <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
667
- <code style="background: #eff6ff; padding: 2px 6px; border-radius: 4px; color: #2563eb;">Hemgg/Deepfake-audio-detection</code>
668
- base model running on GPU/CPU with HuggingFace Transformers.
669
- </p>
670
- </div>
671
- <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
672
- <div style="font-size: 2em; margin-bottom: 10px;">πŸ“Š</div>
673
- <h3 style="color: #2563eb; margin: 0 0 6px 0; font-size: 1.1em;">DSP Ensemble</h3>
674
- <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
675
- MFCC variance + signal energy anomaly detection fused with neural output (60/40 weighting).
676
- </p>
677
- </div>
678
- <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
679
- <div style="font-size: 2em; margin-bottom: 10px;">🎬</div>
680
- <h3 style="color: #7c3aed; margin: 0 0 6px 0; font-size: 1.1em;">Video Extraction</h3>
681
- <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
682
- FFmpeg extracts mono 16kHz audio from any video format before forensic scanning.
683
- </p>
684
- </div>
685
  </div>
 
 
686
 
687
- <h3 style="color: #1e293b; margin-bottom: 16px; font-size: 1.2em;">πŸ“ˆ Score Interpretation</h3>
688
- <div style="background: #ffffff; border-radius: 14px; padding: 20px; border: 1px solid #bfdbfe; margin-bottom: 28px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
689
- <div style="display: flex; align-items: center; margin-bottom: 10px; padding: 10px 14px; background: #f0fdf4; border-radius: 10px; border-left: 4px solid #059669;">
690
- <span style="color: #059669; font-weight: 700; min-width: 70px; font-size: 0.95em;">0–35%</span>
691
- <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">🟒 Very likely genuine / human-created</span>
692
- </div>
693
- <div style="display: flex; align-items: center; margin-bottom: 10px; padding: 10px 14px; background: #fffbeb; border-radius: 10px; border-left: 4px solid #d97706;">
694
- <span style="color: #d97706; font-weight: 700; min-width: 70px; font-size: 0.95em;">35–65%</span>
695
- <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">🟑 Uncertain / mixed signal β€” manual review recommended</span>
696
- </div>
697
- <div style="display: flex; align-items: center; padding: 10px 14px; background: #fef2f2; border-radius: 10px; border-left: 4px solid #dc2626;">
698
- <span style="color: #dc2626; font-weight: 700; min-width: 70px; font-size: 0.95em;">65–100%</span>
699
- <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">πŸ”΄ Strong AI-generated / synthetic voice indicators</span>
700
- </div>
701
- </div>
702
 
703
- <div style="background: #fff7ed; border: 1px solid #fed7aa; border-radius: 14px; padding: 20px; color: #9a3412; font-size: 0.88em; line-height: 1.6;">
704
- <strong style="color: #c2410c;">⚠️ Important Limitations</strong><br><br>
705
- No automated detector is 100% accurate. Adversarial AI models may evade detection.
706
- Compressed or noisy audio reduces reliability. Always use human expert judgment for critical decisions.
707
- </div>
 
 
 
 
 
 
 
 
 
 
708
  </div>
709
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
 
711
  gr.HTML("""
712
  <div style="text-align: center; padding: 30px 20px; color: #94a3b8; font-size: 0.82em; border-top: 1px solid #bfdbfe; margin-top: 10px;">
 
1
+ # app.py β€” DeepFake AI Forensics (Audio-Only, Fixed Label Mapping)
2
  import os
3
  import subprocess
4
  import tempfile
 
30
  print(f"[+] Model loaded on {device}")
31
 
32
  # ==========================================
33
+ # 2. AUDIO PREPROCESSING (ROBUST)
34
  # ==========================================
35
  def normalize_audio(file_path):
36
  """
37
+ Converts ANY audio to standard 16kHz mono WAV via FFmpeg.
38
+ Fixes WhatsApp voice notes (Opus/OGG disguised as MP3),
39
  corrupt headers, and exotic codecs.
40
  """
41
  out = tempfile.mktemp(suffix=".wav")
 
52
  result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53
 
54
  if result.returncode != 0:
55
+ err = result.stderr.decode('utf-8', errors='ignore')[:300]
56
  raise RuntimeError(f"FFmpeg could not decode this file. It may be corrupted or use an unsupported codec.\nDetails: {err}")
57
 
58
  return out
 
62
 
63
  # Supported audio formats (including WhatsApp Opus)
64
  audio_exts = ["wav", "mp3", "flac", "m4a", "ogg", "opus", "aac", "wma", "oga"]
 
 
65
 
66
  if ext in audio_exts:
67
  print(f"[+] Audio detected ({ext}) β†’ normalizing via ffmpeg...")
68
  return normalize_audio(file_path)
69
 
 
 
 
 
70
  # Unknown extension? Try ffmpeg anyway as last resort
71
  print(f"[+] Unknown format ({ext}) β†’ attempting ffmpeg decode...")
72
  try:
73
  return normalize_audio(file_path)
74
  except Exception:
75
+ raise ValueError(f"Unsupported file format: {ext}. Please upload MP3, WAV, M4A, OGG, OPUS, or FLAC.")
76
 
77
  def load_audio(path):
78
  audio, _ = librosa.load(path, sr=16000)
 
87
  with torch.no_grad():
88
  logits = model(**inputs).logits
89
  probs = torch.softmax(logits, dim=-1)[0]
90
+ # CORRECTED: config.json says id2label: 0="AIVoice", 1="HumanVoice"
91
+ ai_p = float(probs[0])
92
+ human_p = float(probs[1])
93
+ return ai_p, human_p
94
 
95
  def audio_features(audio):
96
+ mfcc = librosa.feature.mfcc(y=audio, sr=16000, n_mfcc=13)
97
  return {
98
+ "mfcc_var": float(np.mean(np.var(mfcc, axis=1))),
99
+ "energy": float(np.mean(audio ** 2)),
100
+ "zcr": float(np.mean(librosa.feature.zero_crossing_rate(audio))),
101
+ "spectral_centroid": float(np.mean(librosa.feature.spectral_centroid(y=audio, sr=16000))),
102
  }
103
 
104
  def analyze(file_path):
105
  audio_path = convert_to_audio(file_path)
106
  audio = load_audio(audio_path)
107
+
108
+ ai_p, human_p = predict(audio)
109
  feats = audio_features(audio)
110
+
111
+ # Calibrated ensemble: neural model is primary (80%), DSP is secondary (20%)
112
+ # DSP anomaly score β€” lower variance in MFCC and unnatural spectral centroid can indicate AI
113
+ dsp_score = min(1.0, max(0.0,
114
+ (feats["mfcc_var"] / 800.0) * 0.5 +
115
+ (1.0 - min(feats["zcr"] * 5, 1.0)) * 0.3 +
116
+ (feats["energy"] * 1.5) * 0.2
117
+ ))
118
+
119
+ # Weighted fusion: trust the neural model more, use DSP as a soft modifier
120
+ ai_score = np.clip((ai_p * 0.80 + dsp_score * 0.20), 0.0, 1.0)
121
+
122
+ # Also compute human confidence for display
123
+ human_score = 1.0 - ai_score
124
 
125
+ if ai_score < 0.40:
126
  verdict = "HUMAN VOICE"
127
  level = "LOW RISK"
128
  color = "#059669"
129
  icon = "πŸ§‘"
130
  glow = "rgba(5,150,105,0.18)"
131
+ elif ai_score < 0.60:
132
  verdict = "UNCERTAIN / MIXED"
133
  level = "MEDIUM RISK"
134
  color = "#d97706"
 
141
  icon = "πŸ€–"
142
  glow = "rgba(220,38,38,0.18)"
143
 
144
+ confidence = int(max(ai_score, human_score) * 100)
145
+ return verdict, level, confidence, ai_score, human_score, feats, audio_path, color, icon, glow
146
 
147
  # ==========================================
148
  # 4. VISUALIZATION
 
205
  </div>
206
  """
207
 
208
+ def probability_bar(label, percentage, color, icon):
209
+ return f"""
210
+ <div style="margin-bottom: 14px;">
211
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
212
+ <span style="font-weight: 600; color: #1e293b; font-size: 0.95em;">{icon} {label}</span>
213
+ <span style="font-weight: 700; color: {color}; font-size: 1em;">{percentage:.1f}%</span>
214
+ </div>
215
+ <div style="width: 100%; height: 10px; background: #e2e8f0; border-radius: 5px; overflow: hidden;">
216
+ <div style="width: {percentage}%; height: 100%; background: linear-gradient(90deg, {color}, {color}aa); border-radius: 5px; transition: width 1s ease-out;"></div>
217
+ </div>
218
+ </div>
219
+ """
220
+
221
  EQUALIZER_HTML = """
222
  <div style="display: flex; align-items: flex-end; justify-content: center; height: 50px; gap: 5px; margin: 16px 0;">
223
  <div class="eq-bar" style="width: 6px; height: 40%; background: linear-gradient(to top, #4f46e5, #2563eb); border-radius: 3px; animation: eq-bounce 0.8s infinite ease-in-out 0s;"></div>
 
245
  )
246
 
247
  try:
248
+ verdict, level, confidence, ai_score, human_score, feats, audio_path, color, icon, glow = analyze(audio_file)
249
  plot_path = generate_audio_plots(audio_path)
250
+ ai_pct = ai_score * 100
251
+ human_pct = human_score * 100
252
 
253
+ status_emoji = "🟒" if ai_pct < 40 else "🟑" if ai_pct < 60 else "πŸ”΄"
254
+ status_text = "LIKELY REAL" if ai_pct < 40 else "SUSPICIOUS" if ai_pct < 60 else "HIGH RISK"
255
 
256
  circle = confidence_circle(confidence, color)
257
 
 
272
  </div>
273
 
274
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 24px;">
 
 
 
 
275
  <div style="background: #f8fafc; border-radius: 12px; padding: 16px; border-left: 3px solid {color};">
276
  <div style="font-size: 0.7em; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Risk Level</div>
277
  <div style="font-size: 1.2em; font-weight: 700; color: {color};">{level}</div>
 
286
  </div>
287
  </div>
288
 
289
+ <div style="background: #f8fafc; border-radius: 12px; padding: 20px; margin-bottom: 20px;">
290
+ <div style="font-size: 0.75em; color: #64748b; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 14px;">πŸ“Š Probability Breakdown</div>
291
+ {probability_bar("AI / Synthetic", ai_pct, "#dc2626", "πŸ€–")}
292
+ {probability_bar("Human / Real", human_pct, "#059669", "πŸ§‘")}
293
+ </div>
294
+
295
  <div style="background: #f8fafc; border-radius: 12px; padding: 18px; margin-bottom: 20px;">
296
  <div style="font-size: 0.75em; color: #64748b; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 12px;">πŸ”¬ DSP Forensic Signatures</div>
297
  <div style="display: flex; justify-content: space-around; font-family: 'SF Mono', monospace; font-size: 0.9em; flex-wrap: wrap; gap: 12px;">
298
  <div style="text-align: center;">
299
  <div style="color: #94a3b8; font-size: 0.8em;">MFCC Variance</div>
300
+ <div style="color: #1e293b; font-weight: 600;">{feats['mfcc_var']:.4f}</div>
301
  </div>
302
  <div style="text-align: center;">
303
  <div style="color: #94a3b8; font-size: 0.8em;">Signal Energy</div>
304
  <div style="color: #1e293b; font-weight: 600;">{feats['energy']:.6f}</div>
305
  </div>
306
  <div style="text-align: center;">
307
+ <div style="color: #94a3b8; font-size: 0.8em;">Zero Crossing</div>
308
+ <div style="color: #1e293b; font-weight: 600;">{feats['zcr']:.4f}</div>
309
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  <div style="text-align: center;">
311
+ <div style="color: #94a3b8; font-size: 0.8em;">Spectral Centroid</div>
312
+ <div style="color: #1e293b; font-weight: 600;">{feats['spectral_centroid']:.1f} Hz</div>
 
 
 
 
 
 
 
 
313
  </div>
314
  </div>
315
  </div>
316
 
317
  <div style="font-size: 0.8em; color: #64748b; border-top: 1px solid #e2e8f0; padding-top: 14px; line-height: 1.6;">
318
+ <strong style="color: #475569;">Interpretation Guide:</strong><br>
319
+ <span style="color: #059669;">● 0–40%</span> Very likely genuine human voice &nbsp;|&nbsp;
320
+ <span style="color: #d97706;">● 40–60%</span> Mixed signal, manual review advised &nbsp;|&nbsp;
321
+ <span style="color: #dc2626;">● 60–100%</span> Strong synthetic / AI indicators detected
322
  </div>
323
  </div>
324
  """
325
+ return plot_path, result_html, f"{ai_pct:.1f}%", color, ""
326
 
327
  except Exception as e:
328
  err_msg = str(e)
329
  if "FFmpeg" in err_msg:
330
+ err_html = f"<div style='color:#dc2626;padding:30px;'><strong>❌ File Decode Error</strong><br><br>{err_msg}<br><br><span style='color:#475569;font-size:0.9em;'>WhatsApp voice notes are often .opus or .ogg files disguised as .mp3. Try renaming the file to .ogg or exporting it differently.</span></div>"
331
  else:
332
  err_html = f"<div style='color:#dc2626;padding:30px;'>❌ Analysis Error: {err_msg}</div>"
333
  return None, err_html, "Error", "#dc2626", ""
334
 
335
 
336
  # ==========================================
337
+ # 7. GRADIO UI β€” ENHANCED AUDIO-ONLY
338
  # ==========================================
339
  CUSTOM_CSS = """
340
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
 
347
  }
348
  body { background: #e8f0fe !important; }
349
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  /* Upload zones */
351
  .upload-container {
352
  background: #ffffff !important;
 
407
  background: #ffffff !important;
408
  border: 1px solid #bfdbfe !important;
409
  }
410
+
411
+ /* Audio player styling */
412
+ audio {
413
+ border-radius: 12px !important;
414
+ width: 100% !important;
415
+ }
416
+
417
+ /* Result cards */
418
+ .result-card {
419
+ background: #ffffff;
420
+ border-radius: 16px;
421
+ padding: 24px;
422
+ border: 1px solid #bfdbfe;
423
+ box-shadow: 0 2px 8px rgba(0,0,0,0.04);
424
+ }
425
  """
426
 
427
  def build_ui():
428
  with gr.Blocks(
429
+ title="DeepFake AI Forensics β€” Audio Detector",
430
  theme=gr.themes.Base(
431
  primary_hue="blue",
432
  neutral_hue="slate",
 
449
  </div>
450
  <p style="font-size: 1.05em; color: #475569; margin-top: 14px; max-width: 560px; margin-left: auto; margin-right: auto; line-height: 1.6;">
451
  Neural + DSP ensemble detection for synthetic voice identification.
452
+ <br><span style="color: #2563eb; font-weight: 600;">Audio-only analysis</span>
453
  </p>
454
  </div>
455
  """)
456
 
457
+ with gr.Row():
 
458
  # ═══════════════════════════════════════════
459
+ # LEFT COLUMN β€” Upload
460
  # ═══════════════════════════════════════════
461
+ with gr.Column(scale=1, min_width=360):
462
+ gr.Markdown("### πŸ“€ Upload Audio File", elem_classes="section-title")
463
+
464
+ gr.HTML("""
465
+ <div style="margin-bottom: 12px; display: flex; flex-wrap: wrap; gap: 6px;">
466
+ <span class="format-badge">MP3</span>
467
+ <span class="format-badge">WAV</span>
468
+ <span class="format-badge">M4A</span>
469
+ <span class="format-badge">FLAC</span>
470
+ <span class="format-badge">OGG</span>
471
+ <span class="format-badge">OPUS</span>
472
+ <span class="format-badge">AAC</span>
473
+ </div>
474
+ <div style="font-size: 0.8em; color: #475569; margin-bottom: 16px; display: flex; align-items: center; gap: 6px;">
475
+ <span style="font-size: 1.2em;">πŸ“Ž</span>
476
+ <span>Maximum file size: <strong style="color: #1e40af;">50 MB</strong></span>
477
+ </div>
478
+ <div style="font-size: 0.75em; color: #94a3b8; background: #eff6ff; border-radius: 8px; padding: 10px 12px; margin-bottom: 12px; line-height: 1.5;">
479
+ πŸ’‘ <strong>WhatsApp voice notes:</strong> If your file fails to upload, try renaming it from <code>.mp3</code> to <code>.ogg</code> or <code>.opus</code> before uploading.
480
+ </div>
481
+ """)
482
+
483
+ audio_input = gr.Audio(
484
+ label="",
485
+ type="filepath",
486
+ elem_classes="upload-container"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  )
488
+
489
+ audio_waves = gr.HTML(value=EQUALIZER_HTML + '<div style="text-align:center;color:#64748b;font-size:0.85em;">Audio waveform ready for analysis</div>')
490
+
491
+ audio_btn = gr.Button("πŸ” Analyze Audio", variant="primary", size="lg")
492
+
493
+ audio_score_text = gr.Textbox(
494
+ label="",
495
+ value="--%",
496
+ interactive=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  )
498
 
499
  # ═══════════════════════════════════════════
500
+ # RIGHT COLUMN β€” Results
501
  # ═══════════════════════════════════════════
502
+ with gr.Column(scale=2):
503
+ gr.Markdown("### πŸ“Š Forensic Analysis Report", elem_classes="section-title")
504
+
505
+ audio_plot = gr.Image(
506
+ label="",
507
+ show_label=False,
508
+ elem_classes="result-image"
509
+ )
510
+
511
+ audio_result = gr.HTML(
512
+ value="""
513
+ <div style="background: #ffffff; border: 2px dashed #bfdbfe; border-radius: 20px; padding: 50px 30px; text-align: center; margin-top: 8px;">
514
+ <div style="font-size: 3em; margin-bottom: 16px;">πŸ“Š</div>
515
+ <div style="color: #64748b; font-size: 1.1em; font-weight: 600;">Results will appear here</div>
516
+ <div style="color: #94a3b8; font-size: 0.9em; margin-top: 8px;">Upload an audio file and click analyze to begin</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  </div>
518
+ """
519
+ )
520
 
521
+ audio_btn.click(
522
+ fn=detect_audio,
523
+ inputs=[audio_input],
524
+ outputs=[audio_plot, audio_result, audio_score_text, audio_score_text, audio_waves]
525
+ )
 
 
 
 
 
 
 
 
 
 
526
 
527
+ # ═══════════════════════════════════════════
528
+ # HOW IT WORKS SECTION
529
+ # ═══════════════════════════════════════════
530
+ gr.HTML("""
531
+ <div style="max-width: 900px; margin: 40px auto 0 auto; padding: 20px 0 40px 0;">
532
+ <h2 style="color: #1e293b; font-size: 1.7em; margin-bottom: 28px; text-align: center; font-weight: 700;">🧠 Detection Pipeline</h2>
533
+
534
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; margin-bottom: 36px;">
535
+ <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
536
+ <div style="font-size: 2em; margin-bottom: 10px;">🧠</div>
537
+ <h3 style="color: #1e40af; margin: 0 0 6px 0; font-size: 1.1em;">Transformer Classifier</h3>
538
+ <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
539
+ <code style="background: #eff6ff; padding: 2px 6px; border-radius: 4px; color: #2563eb;">Hemgg/Deepfake-audio-detection</code>
540
+ Wav2Vec 2.0 base model running on GPU/CPU with HuggingFace Transformers.
541
+ </p>
542
  </div>
543
+ <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
544
+ <div style="font-size: 2em; margin-bottom: 10px;">πŸ“Š</div>
545
+ <h3 style="color: #2563eb; margin: 0 0 6px 0; font-size: 1.1em;">DSP Ensemble</h3>
546
+ <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
547
+ MFCC variance + signal energy + zero-crossing rate + spectral centroid fused with neural output (80/20 weighting).
548
+ </p>
549
+ </div>
550
+ <div style="background: #ffffff; border: 1px solid #bfdbfe; border-radius: 16px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
551
+ <div style="font-size: 2em; margin-bottom: 10px;">πŸ”§</div>
552
+ <h3 style="color: #7c3aed; margin: 0 0 6px 0; font-size: 1.1em;">Universal Decoder</h3>
553
+ <p style="color: #475569; font-size: 0.9em; line-height: 1.5; margin: 0;">
554
+ FFmpeg extracts and normalizes audio from any format β€” including WhatsApp voice notes with disguised extensions.
555
+ </p>
556
+ </div>
557
+ </div>
558
+
559
+ <h3 style="color: #1e293b; margin-bottom: 16px; font-size: 1.2em;">πŸ“ˆ Score Interpretation</h3>
560
+ <div style="background: #ffffff; border-radius: 14px; padding: 20px; border: 1px solid #bfdbfe; margin-bottom: 28px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);">
561
+ <div style="display: flex; align-items: center; margin-bottom: 10px; padding: 10px 14px; background: #f0fdf4; border-radius: 10px; border-left: 4px solid #059669;">
562
+ <span style="color: #059669; font-weight: 700; min-width: 70px; font-size: 0.95em;">0–40%</span>
563
+ <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">🟒 Very likely genuine / human-created</span>
564
+ </div>
565
+ <div style="display: flex; align-items: center; margin-bottom: 10px; padding: 10px 14px; background: #fffbeb; border-radius: 10px; border-left: 4px solid #d97706;">
566
+ <span style="color: #d97706; font-weight: 700; min-width: 70px; font-size: 0.95em;">40–60%</span>
567
+ <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">🟑 Uncertain / mixed signal β€” manual review recommended</span>
568
+ </div>
569
+ <div style="display: flex; align-items: center; padding: 10px 14px; background: #fef2f2; border-radius: 10px; border-left: 4px solid #dc2626;">
570
+ <span style="color: #dc2626; font-weight: 700; min-width: 70px; font-size: 0.95em;">60–100%</span>
571
+ <span style="color: #475569; margin-left: 12px; font-size: 0.9em;">πŸ”΄ Strong AI-generated / synthetic voice indicators</span>
572
+ </div>
573
+ </div>
574
+
575
+ <div style="background: #fff7ed; border: 1px solid #fed7aa; border-radius: 14px; padding: 20px; color: #9a3412; font-size: 0.88em; line-height: 1.6;">
576
+ <strong style="color: #c2410c;">⚠️ Important Limitations</strong><br><br>
577
+ No automated detector is 100% accurate. Adversarial AI models may evade detection.
578
+ Compressed or noisy audio reduces reliability. Always use human expert judgment for critical decisions.
579
+ </div>
580
+ </div>
581
+ """)
582
 
583
  gr.HTML("""
584
  <div style="text-align: center; padding: 30px 20px; color: #94a3b8; font-size: 0.82em; border-top: 1px solid #bfdbfe; margin-top: 10px;">