NOBODY204 commited on
Commit
ff288b7
Β·
verified Β·
1 Parent(s): 78f502e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -336
app.py CHANGED
@@ -4,9 +4,9 @@ Antigravity Shield v5.0 β€” ACoNum / Trusted Sound 2026
4
  =====================================================
5
  Fusion : AASIST neural anti-spoofing + Jitter/Flatness + AcoustID
6
  RΓ©sout :
7
- - Faux positifs sur anciennes chansons (vintage)
8
- - Voix off / parlΓ©es non chantΓ©es
9
- - AI Cover complexes (ElevenLabs, YourTTS, RVC)
10
  - Broadcast FM (jitter Γ©levΓ© naturel)
11
  """
12
 
@@ -25,440 +25,187 @@ import gradio as gr
25
  # CONFIG
26
  # ══════════════════════════════════════════════════════════════
27
 
28
- # AcoustID β€” clΓ© API (remplacer par la tienne)
29
  ACOUSTID_KEY = "TY6HUQsigs"
 
30
 
31
- # AASIST — modèle HuggingFace anti-spoofing
32
- AASIST_MODEL_ID = "Mahmoud-Yassen/aasist-antispoof" # fallback si absent: "m-aliabbas/AASIST"
33
-
34
- # Double Jitter β€” seuils v4.4.5 calibrΓ©s broadcast FM
35
  JITTER_BROADCAST_MIN = 0.85
36
  FLATNESS_THRESHOLD = 0.0012
37
  FLATNESS_BROADCAST = 0.0010
38
  CENTROID_BROADCAST = 3500.0
39
  CENTROID_VINTAGE = 3000.0
 
40
  ZCR_VINTAGE = 0.06
41
 
42
-
43
- # ══════════════════════════════════════════════════════════════
44
  class AntigravityShield:
45
  def __init__(self):
46
  print("πŸš€ Antigravity Shield v5.0 β€” AASIST + Jitter + AcoustID")
47
- self.ast_model = None # MIT/ast β€” classification audio gΓ©nΓ©rale
48
- self.aasist = None # AASIST β€” anti-spoofing neural
49
  self.aasist_ok = False
50
 
51
- # ──────────────────────────────────────────────────────────
52
- # MODÈLES
53
- # ──────────────────────────────────────────────────────────
54
  def load_ast(self):
55
  if self.ast_model is None:
56
  try:
57
  from transformers import pipeline
58
- self.ast_model = pipeline(
59
- "audio-classification",
60
- model="MIT/ast-finetuned-audioset-10-10-0.4593"
61
- )
62
  print("βœ… AST chargΓ©")
63
  except Exception as e:
64
  print(f"⚠️ AST non disponible : {e}")
65
- self.ast_model = None
66
 
67
  def load_aasist(self):
68
- """
69
- Charge AASIST pour la dΓ©tection anti-spoofing de bas niveau.
70
- AASIST opère sur la forme d'onde brute → détecte les artefacts
71
- que le jitter seul ne voit pas (TTS modernes, RVC, ElevenLabs).
72
- """
73
  if self.aasist is None:
74
  try:
75
- from transformers import pipeline, AutoFeatureExtractor, AutoModelForAudioClassification
76
- import torch
77
- # Essayer plusieurs modèles anti-spoofing disponibles
78
- candidates = [
79
- "Mahmoud-Yassen/aasist-antispoof",
80
- "m-aliabbas/AASIST",
81
- "fusing/aasist",
82
- ]
83
  for model_id in candidates:
84
  try:
85
- self.aasist = pipeline(
86
- "audio-classification",
87
- model=model_id,
88
- sampling_rate=16000
89
- )
90
  self.aasist_ok = True
91
  print(f"βœ… AASIST chargΓ© : {model_id}")
92
  break
93
- except Exception:
94
- continue
95
- if not self.aasist_ok:
96
- print("⚠️ AASIST non disponible β€” mode fallback jitter seul")
97
  except Exception as e:
98
  print(f"⚠️ AASIST erreur : {e}")
99
 
100
- # ──────────────────────────────────────────────────────────
101
- # SHA256
102
- # ──────────────────────────────────────────────────────────
103
  def get_sha256(self, path):
104
  with open(path, "rb") as f:
105
  return hashlib.sha256(f.read()).hexdigest()
106
 
107
- # ──────────────────────────────────────────────────────────
108
- # ACOUSTID β€” identification musicale
109
- # ──────────────────────────────────────────────────────────
110
  def acoustid_lookup(self, path):
111
- """
112
- Identifie le fichier via AcoustID/MusicBrainz.
113
- Si la chanson est CONNUE β†’ c'est une vraie chanson (bonus authenticitΓ©).
114
- Si la chanson n'est PAS dans la base β†’ potentiellement AI Cover.
115
- """
116
  try:
117
  import acoustid
118
  results = acoustid.match(ACOUSTID_KEY, path)
119
  for score, recording_id, title, artist in results:
120
- if score > 0.8:
121
- return True, f"{artist} β€” {title} (score {score:.0%})", recording_id
122
- return False, "Non identifiΓ© dans MusicBrainz", None
123
- except ImportError:
124
- # Fallback: fpcalc si acoustid module absent
125
- try:
126
- fpcalc = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'fpcalc.exe')
127
- if not os.path.exists(fpcalc):
128
- fpcalc = 'fpcalc'
129
- result = subprocess.run(
130
- [fpcalc, '-json', path],
131
- capture_output=True, timeout=15
132
- )
133
- if result.returncode == 0:
134
- data = json.loads(result.stdout.decode('utf-8'))
135
- fingerprint = data.get('fingerprint', '')
136
- return bool(fingerprint), f"Empreinte : {fingerprint[:24]}...", None
137
- except Exception:
138
- pass
139
- return None, "AcoustID non disponible", None
140
- except Exception as e:
141
- return None, f"AcoustID erreur : {str(e)[:60]}", None
142
 
143
- # ──────────────────────────────────────────────────────────
144
- # SPECTROGRAMME
145
- # ──────────────────────────────────────────────────────────
146
  def generer_spectrogramme(self, y, sr, jitter_a, jitter_b, aasist_score):
147
  fig, axes = plt.subplots(1, 2, figsize=(14, 4))
148
-
149
- # Mel spectrogram
150
- S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)
151
  S_dB = librosa.power_to_db(S, ref=np.max)
152
- librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel',
153
- cmap='magma', ax=axes[0])
154
- axes[0].set_title('Mel Spectrogram β€” Texture vocale')
155
-
156
- # Double Jitter timeline
157
- frame_len = 2048
158
- hop = 512
159
- pitches, mags = librosa.piptrack(y=y, sr=sr, hop_length=hop, fmin=60, fmax=4000)
160
- frames = np.arange(pitches.shape[1])
161
- times = librosa.frames_to_time(frames, sr=sr, hop_length=hop)
162
-
163
- # Jitter par frame
164
- jitter_timeline = []
165
- for i in range(pitches.shape[1]):
166
- col = pitches[:, i]
167
- col_mags = mags[:, i]
168
- m = col_mags > np.median(col_mags)
169
- jitter_timeline.append(np.std(col[m]) / 1000 if np.any(m) else 0)
170
-
171
- axes[1].plot(times[:len(jitter_timeline)], jitter_timeline,
172
- color='cyan', linewidth=0.8, alpha=0.8, label='Jitter frame')
173
- axes[1].axhline(y=jitter_a, color='lime', linestyle='--', linewidth=1.5,
174
- label=f'Jitter-A global={jitter_a:.3f}')
175
- axes[1].axhline(y=jitter_b, color='yellow', linestyle='--', linewidth=1.5,
176
- label=f'Jitter-B dΓ©bruitΓ©={jitter_b:.3f}')
177
- if aasist_score is not None:
178
- axes[1].axhline(y=aasist_score, color='red', linestyle=':', linewidth=2,
179
- label=f'AASIST spoof={aasist_score:.2f}')
180
- axes[1].set_xlabel('Temps (s)')
181
- axes[1].set_ylabel('Jitter')
182
- axes[1].set_title('Double Jitter + AASIST Timeline')
183
- axes[1].legend(fontsize=7)
184
- axes[1].set_ylim(0, max(2.0, max(jitter_timeline) * 1.2) if jitter_timeline else 2.0)
185
-
186
  plt.tight_layout()
187
  plot_path = "spectrum_v5.png"
188
  plt.savefig(plot_path, dpi=120)
189
  plt.close()
190
  return plot_path
191
 
192
- # ──────────────────────────────────────────────────────────
193
- # ANALYSE AASIST β€” neural anti-spoofing
194
- # ──────────────────────────────────────────────────────────
195
  def run_aasist(self, path):
196
- """
197
- Retourne (spoof_score 0-1, label_string)
198
- spoof_score > 0.5 = suspect de spoofing
199
- """
200
- if not self.aasist_ok or self.aasist is None:
201
- return None, "AASIST non chargΓ©"
202
  try:
203
  results = self.aasist(path)
204
- # Cherche le label "spoof" ou "fake"
205
  for r in results:
206
- lbl = r['label'].lower()
207
- if any(k in lbl for k in ['spoof', 'fake', 'synthetic', 'generated']):
208
  return float(r['score']), f"AASIST spoof={r['score']:.1%}"
209
- # Si labels sont 0/1 ou bonafide/spoof
210
- for r in results:
211
- lbl = r['label'].lower()
212
- if any(k in lbl for k in ['1', 'spoof']):
213
- return float(r['score']), f"AASIST score={r['score']:.1%}"
214
- # Retour du premier label
215
- return 1 - float(results[0]['score']), f"AASIST={results[0]['label']}:{results[0]['score']:.1%}"
216
- except Exception as e:
217
- return None, f"AASIST erreur : {str(e)[:60]}"
218
 
219
- # ──────────────────────────────────────────────────────────
220
- # ANALYSE PRINCIPALE
221
- # ──────────────────────────────────────────────────────────
222
  def analyser_expert(self, path):
223
- if path is None:
224
- return None, "En attente...", None
225
  try:
226
  self.load_ast()
227
  self.load_aasist()
228
-
229
  y, sr = librosa.load(path, sr=44100)
230
 
231
- # ── DΓ‰BRUITAGE ADAPTATIF ──────────────────────────
232
- y_light = nr.reduce_noise(y=y, sr=sr, prop_decrease=0.3)
233
- centroid_light = float(np.mean(librosa.feature.spectral_centroid(y=y_light, sr=sr)))
234
-
235
- if centroid_light < CENTROID_BROADCAST:
236
- prop = 0.4
237
- raison_denoise = "DΓ©bruitage doux (broadcast dΓ©tectΓ©)"
238
- else:
239
- prop = 0.7
240
- raison_denoise = "DΓ©bruitage standard"
241
-
242
  y_denoised = nr.reduce_noise(y=y, sr=sr, prop_decrease=prop)
243
 
244
- # ── DOUBLE JITTER (A brut / B dΓ©bruitΓ©) ──────────
245
- def compute_jitter(signal):
246
- pitches, mags = librosa.piptrack(y=signal, sr=sr, fmin=60, fmax=4000)
247
  mask = mags > np.median(mags)
248
  return float(np.std(pitches[mask]) / 1000) if np.any(mask) else 0.0
249
 
250
- jitter_a = compute_jitter(y) # Jitter brut
251
- jitter_b = compute_jitter(y_denoised) # Jitter dΓ©bruitΓ©
252
- jitter_delta = abs(jitter_a - jitter_b)
253
-
254
- flatness = float(np.mean(librosa.feature.spectral_flatness(y=y_denoised)))
255
- centroid = float(np.mean(librosa.feature.spectral_centroid(y=y_denoised, sr=sr)))
256
- zcr = float(np.mean(librosa.feature.zero_crossing_rate(y_denoised)))
257
-
258
- # ── VINTAGE ───────────────────────────────────────
259
- is_vintage = centroid < CENTROID_VINTAGE and zcr < ZCR_VINTAGE
260
-
261
- # ── AST CLASSIFICATION ────────────────────────────
262
- top_label, score_ia = "Inconnu", 0.0
263
- if self.ast_model:
264
- try:
265
- res_ia = self.ast_model(path)
266
- top_label = res_ia[0]['label']
267
- score_ia = res_ia[0]['score'] * 100
268
- except Exception:
269
- pass
270
-
271
- # ── AASIST β€” neural anti-spoofing ─────────────────
272
  aasist_score, aasist_label = self.run_aasist(path)
273
-
274
- # ── ACOUSTID ──────────────────────────────────────
275
  acoustid_known, acoustid_info, _ = self.acoustid_lookup(path)
276
 
277
- # ══════════════════════════════════════════════════
278
- # SCORING FUSIONNÉ v5.0
279
- # PondΓ©ration : AASIST(40%) + Jitter(35%) + Flatness(15%) + Context(10%)
280
- # ══════════════════════════════════════════════════
281
  confiance = 50
282
- raisons = []
283
 
284
- # ── 1. AASIST NEURAL (prioritΓ© maximale) ──────────
285
- if aasist_score is not None:
286
- if aasist_score > 0.80:
287
- # AASIST très sûr → deepfake
288
- confiance -= 40
289
- raisons.append(f"πŸ€– AASIST dΓ©tecte synthΓ¨se ({aasist_score:.0%}) β€” deepfake probable")
290
- elif aasist_score > 0.55:
291
- confiance -= 20
292
- raisons.append(f"⚠️ AASIST : signal suspect ({aasist_score:.0%})")
293
- elif aasist_score < 0.30:
294
- confiance += 20
295
- raisons.append(f"βœ… AASIST : signal authentique ({1-aasist_score:.0%} bona-fide)")
296
- else:
297
- raisons.append(f"πŸ“Š AASIST ambigu ({aasist_score:.0%})")
298
- else:
299
- raisons.append("βš™οΈ AASIST indisponible β€” mode jitter seul")
300
-
301
- # ── 2. VINTAGE (override partiel si vintage) ──────
302
  if is_vintage:
303
- confiance += 12
304
- raisons.append("πŸ•°οΈ Signature vintage (enregistrement ancien)")
305
- # Les AI Covers anciens ont rarement un centroΓ―de aussi bas
306
- if aasist_score and aasist_score > 0.80:
307
- raisons.append("⚠️ Vintage MAIS AASIST dΓ©tecte synthΓ¨se β€” AI Cover vintage possible")
308
-
309
- # ── 3. FLATNESS ────────────────────────────────────
310
- if flatness > FLATNESS_THRESHOLD:
311
  confiance += 15
312
- raisons.append(f"🌿 Texture organique (flatness={flatness:.5f})")
313
- else:
314
- confiance -= 20
315
- raisons.append(f"πŸ”‡ Signal trop pur (flatness={flatness:.5f} < {FLATNESS_THRESHOLD})")
316
- if score_ia < 70:
317
- confiance -= 15
318
- raisons.append("🚫 Signal pur + source IA incertaine β†’ deepfake renforcΓ©")
319
-
320
- # ── 4. DOUBLE JITTER ───────────────────────────────
321
- is_broadcast = (jitter_a >= JITTER_BROADCAST_MIN and centroid_light < CENTROID_BROADCAST) or prop == 0.4
322
-
323
- # 4a. Voix/parole
324
- if "Music" not in top_label and "Singing" not in top_label:
325
- if 0.10 < jitter_b < 0.80:
326
- confiance += 20
327
- raisons.append(f"πŸ—£οΈ Vibration vocale naturelle (jitter-B={jitter_b:.3f})")
328
- elif jitter_b >= JITTER_BROADCAST_MIN:
329
- if is_broadcast and flatness > FLATNESS_BROADCAST:
330
- confiance += 18
331
- raisons.append(f"πŸ“» Jitter broadcast FM validΓ© (jitter-B={jitter_b:.3f})")
332
- elif is_broadcast and flatness <= FLATNESS_BROADCAST:
333
- confiance -= 25
334
- raisons.append(f"❌ Trop pur pour broadcast (jitter-B={jitter_b:.3f})")
335
- elif flatness > 0.0008:
336
- confiance += 12
337
- raisons.append(f"πŸ“‘ Flux radio dΓ©tectΓ© (jitter-B={jitter_b:.3f})")
338
- else:
339
- confiance -= 50
340
- raisons.append(f"🚨 Instabilité artificielle voix off (jitter-B={jitter_b:.3f})")
341
- # Jitter delta trop grand = dΓ©bruitage anormal = synthΓ©tique
342
- if jitter_delta > 0.5:
343
- confiance -= 15
344
- raisons.append(f"⚑ Delta jitter A-B anormal (Ξ”={jitter_delta:.3f}) β€” artefact synthΓ¨se")
345
 
346
- # 4b. Musique / Chant
347
- else:
348
- if jitter_b > 1.0 or jitter_b < 0.09:
349
- if is_vintage:
350
- confiance += 8
351
- raisons.append(f"🎡 Jitter vintage validé (jitter-B={jitter_b:.3f})")
352
- elif flatness < FLATNESS_THRESHOLD:
353
- confiance -= 50
354
- raisons.append(f"🎀 AI Cover β€” jitter anormal + signal pur (jitter-B={jitter_b:.3f})")
355
- else:
356
- confiance -= 15
357
- raisons.append(f"πŸ”Ž Jitter musical suspect β€” analyse inconclusif (jitter-B={jitter_b:.3f})")
358
  else:
359
- confiance += 18
360
- raisons.append(f"🎢 Harmoniques naturelles validées (jitter-B={jitter_b:.3f})")
361
 
362
- # ── 5. ACOUSTID ────────────────────────────────────
363
- if acoustid_known is True:
364
  confiance += 15
365
- raisons.append(f"🎡 Chanson connue MusicBrainz : {acoustid_info}")
366
- # Chanson connue MAIS AASIST détecte synthèse = AI Cover de chanson réelle
367
- if aasist_score and aasist_score > 0.65:
368
- confiance -= 20
369
- raisons.append("🚨 Chanson connue + AASIST suspect = AI Cover d'original")
370
- elif acoustid_known is False:
371
- raisons.append(f"❓ Non identifiΓ© MusicBrainz β€” {acoustid_info}")
372
  else:
373
- raisons.append(f"βš™οΈ {acoustid_info}")
 
 
374
 
 
375
  confiance = max(0, min(100, confiance))
 
 
 
376
 
377
- # ── VERDICT ────────────────────────────────────────
378
- if confiance >= 70:
379
- verdict = "πŸ”’ AUTHENTIQUE CERTIFIΓ‰"
380
- color = "green"
381
- elif 40 <= confiance < 70:
382
- verdict = "⚠️ ANALYSE INCONCLUSIVE"
383
- color = "orange"
384
- else:
385
- verdict = "πŸ›‘ AI COVER / DEEPFAKE DΓ‰TECTΓ‰"
386
- color = "red"
387
-
388
- sha = self.get_sha256(path)
389
-
390
- report = f"\n{'='*50}\nπŸ“Œ RAPPORT Antigravity Shield v5.0\n{'='*50}\n"
391
- report += f" πŸ€– AST SOURCE : {top_label} ({score_ia:.1f}%)\n"
392
- report += f" 🧠 AASIST : {aasist_label}\n"
393
- report += f" 🎯 CONFIANCE : {confiance}%\n"
394
- report += f" πŸŽ›οΈ JITTER-A : {jitter_a:.4f} (brut)\n"
395
- report += f" πŸŽ›οΈ JITTER-B : {jitter_b:.4f} (dΓ©bruitΓ©)\n"
396
- report += f" ⚑ JITTER-Ξ” : {jitter_delta:.4f}\n"
397
- report += f" πŸ“Š FLATNESS : {flatness:.6f}\n"
398
- report += f" πŸ•°οΈ VINTAGE : {'Oui' if is_vintage else 'Non'}\n"
399
- report += f" πŸ“» BROADCAST : {'Oui' if is_broadcast else 'Non'}\n"
400
- report += f" 🎡 ACOUSTID : {acoustid_info}\n"
401
- report += f" 🧹 DΓ‰BRUITAGE : {raison_denoise}\n"
402
- report += f" πŸ”‘ SHA256 : {sha[:24]}...\n"
403
- report += f"{'-'*50}\n"
404
- report += f" >>> VERDICT : {verdict}\n"
405
- report += f" >>> NOTES :\n"
406
- for r in raisons:
407
- report += f" β€’ {r}\n"
408
- report += f"{'='*50}\n"
409
- report += "Antigravity Shield v5.0 Β· ACoNum / Trusted Sound 2026\n"
410
 
411
  return (
412
  self.generer_spectrogramme(y, sr, jitter_a, jitter_b, aasist_score),
413
  report,
414
- {
415
- "Score": f"{confiance}%",
416
- "Verdict": verdict,
417
- "Jitter-A": round(jitter_a, 4),
418
- "Jitter-B": round(jitter_b, 4),
419
- "Jitter-Delta": round(jitter_delta, 4),
420
- "Flatness": round(flatness, 6),
421
- "AASIST": round(aasist_score, 3) if aasist_score is not None else "N/A",
422
- "AcoustID": acoustid_info,
423
- "Vintage": is_vintage,
424
- "Broadcast": is_broadcast,
425
- "Centroid": round(centroid, 1)
426
- }
427
  )
428
-
429
  except Exception as e:
430
- import traceback
431
- return None, f"Erreur : {e}\n{traceback.format_exc()}", None
432
-
433
 
434
  # ══════════════════════════════════════════════════════════════
435
- # INTERFACE GRADIO
436
  # ══════════════════════════════════════════════════════════════
437
  shield = AntigravityShield()
438
-
439
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
440
- gr.Markdown(
441
- "# πŸ›‘οΈ Antigravity Shield v5.0 PRO\n"
442
- "### DΓ©tecteur d'authenticitΓ© audio β€” AASIST + Double Jitter + AcoustID\n"
443
- "_Fusion v5.0 : AASIST neural anti-spoofing + jitter calibrΓ© broadcast FM + AcoustID MusicBrainz_"
444
- )
445
  with gr.Row():
446
  with gr.Column():
447
- audio_input = gr.Audio(type="filepath", label="Audio Input (MP3/WAV/FLAC)")
448
- run_btn = gr.Button("βš™οΈ ANALYSER", variant="primary")
449
  with gr.Column():
450
- image_output = gr.Image(label="Spectrogramme + Double Jitter")
451
- report_output = gr.Textbox(label="Rapport d'expertise", lines=20)
452
- metrics_output = gr.JSON(label="MΓ©triques dΓ©taillΓ©es")
453
 
454
- run_btn.click(
455
- fn=shield.analyser_expert,
456
- inputs=audio_input,
457
- outputs=[image_output, report_output, metrics_output]
458
- )
459
 
460
  if __name__ == "__main__":
461
- demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
462
 
463
 
464
 
 
4
  =====================================================
5
  Fusion : AASIST neural anti-spoofing + Jitter/Flatness + AcoustID
6
  RΓ©sout :
7
+ - Faux positifs sur voix off (is.mp3)
8
+ - Anciennes chansons (vintage/radio)
9
+ - AI Cover complexes (ElevenLabs, RVC)
10
  - Broadcast FM (jitter Γ©levΓ© naturel)
11
  """
12
 
 
25
  # CONFIG
26
  # ══════════════════════════════════════════════════════════════
27
 
 
28
  ACOUSTID_KEY = "TY6HUQsigs"
29
+ AASIST_MODEL_ID = "Mahmoud-Yassen/aasist-antispoof"
30
 
31
+ # Seuils calibrΓ©s pour ACoNum 2026
 
 
 
32
  JITTER_BROADCAST_MIN = 0.85
33
  FLATNESS_THRESHOLD = 0.0012
34
  FLATNESS_BROADCAST = 0.0010
35
  CENTROID_BROADCAST = 3500.0
36
  CENTROID_VINTAGE = 3000.0
37
+ ZCR_SPEECH_THRESHOLD = 0.055 # DΓ©tection voix off/parlΓ©e
38
  ZCR_VINTAGE = 0.06
39
 
 
 
40
  class AntigravityShield:
41
  def __init__(self):
42
  print("πŸš€ Antigravity Shield v5.0 β€” AASIST + Jitter + AcoustID")
43
+ self.ast_model = None
44
+ self.aasist = None
45
  self.aasist_ok = False
46
 
 
 
 
47
  def load_ast(self):
48
  if self.ast_model is None:
49
  try:
50
  from transformers import pipeline
51
+ self.ast_model = pipeline("audio-classification", model="MIT/ast-finetuned-audioset-10-10-0.4593")
 
 
 
52
  print("βœ… AST chargΓ©")
53
  except Exception as e:
54
  print(f"⚠️ AST non disponible : {e}")
 
55
 
56
  def load_aasist(self):
 
 
 
 
 
57
  if self.aasist is None:
58
  try:
59
+ from transformers import pipeline
60
+ candidates = ["Mahmoud-Yassen/aasist-antispoof", "m-aliabbas/AASIST"]
 
 
 
 
 
 
61
  for model_id in candidates:
62
  try:
63
+ self.aasist = pipeline("audio-classification", model=model_id, sampling_rate=16000)
 
 
 
 
64
  self.aasist_ok = True
65
  print(f"βœ… AASIST chargΓ© : {model_id}")
66
  break
67
+ except: continue
 
 
 
68
  except Exception as e:
69
  print(f"⚠️ AASIST erreur : {e}")
70
 
 
 
 
71
  def get_sha256(self, path):
72
  with open(path, "rb") as f:
73
  return hashlib.sha256(f.read()).hexdigest()
74
 
 
 
 
75
  def acoustid_lookup(self, path):
 
 
 
 
 
76
  try:
77
  import acoustid
78
  results = acoustid.match(ACOUSTID_KEY, path)
79
  for score, recording_id, title, artist in results:
80
+ if score > 0.8: return True, f"{artist} β€” {title}", recording_id
81
+ return False, "Non identifiΓ©", None
82
+ except:
83
+ return None, "AcoustID non dispo", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
 
 
 
85
  def generer_spectrogramme(self, y, sr, jitter_a, jitter_b, aasist_score):
86
  fig, axes = plt.subplots(1, 2, figsize=(14, 4))
87
+ S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)
 
 
88
  S_dB = librosa.power_to_db(S, ref=np.max)
89
+ librosa.display.specshow(S_dB, sr=sr, x_axis='time', y_axis='mel', cmap='magma', ax=axes[0])
90
+ axes[0].set_title('Texture Vocale (Mel)')
91
+
92
+ # Timeline simplifiΓ©e
93
+ axes[1].axhline(y=jitter_b, color='yellow', linestyle='--', label=f'Jitter-B: {jitter_b:.3f}')
94
+ if aasist_score: axes[1].axhline(y=aasist_score, color='red', linestyle=':', label=f'AASIST: {aasist_score:.2f}')
95
+ axes[1].set_title('MΓ©triques de StabilitΓ©')
96
+ axes[1].legend()
97
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  plt.tight_layout()
99
  plot_path = "spectrum_v5.png"
100
  plt.savefig(plot_path, dpi=120)
101
  plt.close()
102
  return plot_path
103
 
 
 
 
104
  def run_aasist(self, path):
105
+ if not self.aasist_ok: return None, "AASIST Off"
 
 
 
 
 
106
  try:
107
  results = self.aasist(path)
 
108
  for r in results:
109
+ if any(k in r['label'].lower() for k in ['spoof', 'fake', 'synthetic']):
 
110
  return float(r['score']), f"AASIST spoof={r['score']:.1%}"
111
+ return 1 - float(results[0]['score']), "AASIST Bonafide"
112
+ except: return None, "AASIST Error"
 
 
 
 
 
 
 
113
 
 
 
 
114
  def analyser_expert(self, path):
115
+ if path is None: return None, "En attente...", None
 
116
  try:
117
  self.load_ast()
118
  self.load_aasist()
 
119
  y, sr = librosa.load(path, sr=44100)
120
 
121
+ # Analyse des caractΓ©ristiques
122
+ zcr = float(np.mean(librosa.feature.zero_crossing_rate(y)))
123
+ centroid = float(np.mean(librosa.feature.spectral_centroid(y=y, sr=sr)))
124
+ flatness = float(np.mean(librosa.feature.spectral_flatness(y=y)))
125
+
126
+ # --- DÉTECTION VOIX OFF / SPEECH (Correction is.mp3) ---
127
+ is_speech = zcr < ZCR_SPEECH_THRESHOLD
128
+ is_vintage = centroid < CENTROID_VINTAGE and zcr < ZCR_VINTAGE
129
+
130
+ # DΓ©bruitage adaptatif
131
+ prop = 0.4 if (is_speech or is_vintage) else 0.7
132
  y_denoised = nr.reduce_noise(y=y, sr=sr, prop_decrease=prop)
133
 
134
+ def compute_jitter(sig):
135
+ pitches, mags = librosa.piptrack(y=sig, sr=sr, fmin=60, fmax=4000)
 
136
  mask = mags > np.median(mags)
137
  return float(np.std(pitches[mask]) / 1000) if np.any(mask) else 0.0
138
 
139
+ jitter_a = compute_jitter(y)
140
+ jitter_b = compute_jitter(y_denoised)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  aasist_score, aasist_label = self.run_aasist(path)
 
 
142
  acoustid_known, acoustid_info, _ = self.acoustid_lookup(path)
143
 
144
+ # --- LOGIQUE DE SCORING FUSIONNÉE ---
 
 
 
145
  confiance = 50
146
+ raisons = []
147
 
148
+ # Correction Voix Off / Vintage
149
+ if is_speech:
150
+ confiance += 20
151
+ raisons.append("πŸ—£οΈ DΓ©tection Voix Off : Seuil de tolΓ©rance augmentΓ©")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  if is_vintage:
 
 
 
 
 
 
 
 
153
  confiance += 15
154
+ raisons.append("πŸ•°οΈ Signature Vintage/Radio dΓ©tectΓ©e")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ # AASIST
157
+ if aasist_score and aasist_score > 0.75:
158
+ if not is_speech: # Moins punitif sur la voix off
159
+ confiance -= 40
160
+ raisons.append("πŸ€– AASIST dΓ©tecte une synthΓ¨se neuronale")
 
 
 
 
 
 
 
161
  else:
162
+ confiance -= 15
163
+ raisons.append("⚠️ AASIST suspect, mais contexte Voix Off")
164
 
165
+ # Texture
166
+ if flatness > FLATNESS_THRESHOLD:
167
  confiance += 15
168
+ raisons.append(f"🌿 Texture organique (Flatness OK)")
 
 
 
 
 
 
169
  else:
170
+ if not is_speech:
171
+ confiance -= 20
172
+ raisons.append("πŸ”‡ Signal trop lisse (CaractΓ©ristique IA)")
173
 
174
+ # Verdict final
175
  confiance = max(0, min(100, confiance))
176
+ if confiance >= 70: verdict = "πŸ”’ AUTHENTIQUE CERTIFIΓ‰"
177
+ elif confiance >= 40: verdict = "⚠️ ANALYSE INCONCLUSIVE"
178
+ else: verdict = "πŸ›‘ AI COVER / DEEPFAKE DΓ‰TECTΓ‰"
179
 
180
+ report = f"πŸ“Œ RAPPORT v5.0\n🎯 CONFIANCE : {confiance}%\n>>> VERDICT : {verdict}\n\nNOTES :\n" + "\n".join([f" β€’ {r}" for r in raisons])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
  return (
183
  self.generer_spectrogramme(y, sr, jitter_a, jitter_b, aasist_score),
184
  report,
185
+ { "Score": f"{confiance}%", "Vintage": is_vintage, "VoixOff": is_speech, "Flatness": round(flatness, 6) }
 
 
 
 
 
 
 
 
 
 
 
 
186
  )
 
187
  except Exception as e:
188
+ return None, f"Erreur : {e}", None
 
 
189
 
190
  # ══════════════════════════════════════════════════════════════
191
+ # INTERFACE
192
  # ══════════════════════════════════════════════════════════════
193
  shield = AntigravityShield()
 
194
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
195
+ gr.Markdown("# πŸ›‘οΈ Antigravity Shield v5.0 PRO")
 
 
 
 
196
  with gr.Row():
197
  with gr.Column():
198
+ audio_input = gr.Audio(type="filepath", label="Fichier Audio")
199
+ run_btn = gr.Button("βš™οΈ ANALYSER", variant="primary")
200
  with gr.Column():
201
+ image_output = gr.Image(label="Spectrogramme")
202
+ report_output = gr.Textbox(label="Rapport d'expertise", lines=12)
203
+ metrics_output = gr.JSON(label="MΓ©triques")
204
 
205
+ run_btn.click(fn=shield.analyser_expert, inputs=audio_input, outputs=[image_output, report_output, metrics_output])
 
 
 
 
206
 
207
  if __name__ == "__main__":
208
+ demo.launch(server_name="0.0.0.0", server_port=7860)
209
 
210
 
211