NOBODY204 commited on
Commit
c4886d9
Β·
verified Β·
1 Parent(s): c58a8ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -93
app.py CHANGED
@@ -34,7 +34,6 @@ def restore_roi(roi):
34
  # ═══════════════════════════════════════════════════════════
35
  # Γ‰TAPE 2 : MOTEURS V4 (hΓ©ritΓ©s)
36
  # ═══════════════════════════════════════════════════════════
37
-
38
  def test_localised_boundaries(roi):
39
  gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
40
  edges = cv2.Canny(gray, 100, 200)
@@ -66,16 +65,9 @@ def test_fft_frequency(roi):
66
  # ═══════════════════════════════════════════════════════════
67
  # Γ‰TAPE 3 : NOUVEAUX MOTEURS V5 (Sora 2.0 / Runway / Kling)
68
  # ═══════════════════════════════════════════════════════════
69
-
70
  def test_optical_flow(frames):
71
- """
72
- Flux optique Farneback entre frames consΓ©cutives.
73
- Sora 2.0 et Runway Gen-3 produisent un mouvement trop lisse (ratio std/mean < 0.15)
74
- ou incohΓ©rent par Γ -coups. Les vraies vidΓ©os ont une variance naturelle.
75
- """
76
  if len(frames) < 2:
77
  return 0.50
78
-
79
  scores = []
80
  for i in range(min(len(frames) - 1, 8)):
81
  g1 = cv2.cvtColor(frames[i], cv2.COLOR_BGR2GRAY)
@@ -86,85 +78,57 @@ def test_optical_flow(frames):
86
  magnitude, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1])
87
  mag_std = np.std(magnitude)
88
  mag_mean = np.mean(magnitude)
89
-
90
  if mag_mean < 0.01:
91
- scores.append(0.60) # VidΓ©o quasi-statique β†’ neutre
92
  else:
93
  ratio = mag_std / mag_mean
94
  if 0.30 < ratio < 2.50:
95
- scores.append(0.88) # Mouvement naturel βœ…
96
  elif ratio < 0.15:
97
- scores.append(0.18) # Trop lisse β†’ IA 🚨
98
  elif ratio > 4.0:
99
- scores.append(0.22) # Saccades β†’ injection 🚨
100
  else:
101
  scores.append(0.50)
102
-
103
  return float(np.mean(scores)) if scores else 0.50
104
 
105
-
106
  def test_eye_region(frame):
107
- """
108
- Analyse de la rΓ©gion oculaire via cascade Haar.
109
- Sora 2.0 / Kling peinent encore sur la symΓ©trie et la texture de l'iris.
110
- """
111
  eye_cascade = cv2.CascadeClassifier(
112
  cv2.data.haarcascades + "haarcascade_eye.xml"
113
  )
114
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
115
  eyes = eye_cascade.detectMultiScale(gray, 1.1, 4, minSize=(20, 20))
116
-
117
  if len(eyes) == 0:
118
- return 0.38 # Aucun Ε“il dΓ©tectΓ© β†’ suspect
119
-
120
  if len(eyes) == 2:
121
  (x1, y1, w1, h1) = eyes[0]
122
  (x2, y2, w2, h2) = eyes[1]
123
  height_diff = abs(y1 - y2)
124
  size_diff = abs(w1 - w2)
125
  if height_diff < 20 and size_diff < 15:
126
- return 0.90 # SymΓ©trie naturelle βœ…
127
  return 0.52
128
-
129
- return 0.58 # 1 ou 3+ yeux β†’ ambigu
130
-
131
 
132
  def test_color_coherence(roi, frame):
133
- """
134
- CohΓ©rence colorimΓ©trique en espace LAB entre visage et fond.
135
- Les deepfakes modernes présentent une légère discordance de température
136
- (delta-E > 30) car le gΓ©nΓ©rateur composite deux espaces couleur distincts.
137
- """
138
  if roi.size == 0 or frame.size == 0:
139
  return 0.50
140
-
141
  lab_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB).astype(np.float32)
142
  lab_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB).astype(np.float32)
143
-
144
  mean_roi = np.mean(lab_roi, axis=(0, 1))
145
  mean_frame = np.mean(lab_frame, axis=(0, 1))
146
-
147
  delta_E = float(np.sqrt(np.sum((mean_roi - mean_frame) ** 2)))
148
-
149
  if delta_E < 22:
150
- return 0.88 # CohΓ©rent βœ…
151
  elif delta_E < 40:
152
- return 0.55 # Légèrement suspect
153
  else:
154
- return 0.22 # IncohΓ©rence forte β†’ IA 🚨
155
-
156
 
157
  def test_texture_lbp(roi):
158
- """
159
- Approximation LBP (Local Binary Pattern) via numpy sans scikit.
160
- Les visages IA ont une texture trop uniforme (variance LBP faible).
161
- """
162
  if roi.size == 0:
163
  return 0.50
164
-
165
  gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY).astype(np.float32)
166
-
167
- # Approximation LBP : comparaison pixel central avec 8 voisins
168
  shifted = [
169
  np.roll(np.roll(gray, dy, axis=0), dx, axis=1)
170
  for dy, dx in [(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1)]
@@ -172,22 +136,19 @@ def test_texture_lbp(roi):
172
  lbp = np.zeros_like(gray)
173
  for i, s in enumerate(shifted):
174
  lbp += (gray >= s).astype(np.float32) * (2 ** i)
175
-
176
  hist, _ = np.histogram(lbp, bins=64, range=(0, 256))
177
  hist = hist / (hist.sum() + 1e-6)
178
  variance = float(np.var(hist))
179
-
180
  if variance > 0.0003:
181
- return 0.88 # Texture riche et naturelle βœ…
182
  elif variance > 0.0001:
183
  return 0.55
184
  else:
185
- return 0.22 # Texture trop uniforme β†’ synthΓ©tique 🚨
186
 
187
  # ═══════════════════════════════════════════════════════════
188
  # Γ‰TAPE 4 : VERDICT CALIBRΓ‰ V5
189
  # ═══════════════════════════════════════════════════════════
190
-
191
  def get_verdict(score_pct):
192
  if score_pct >= 72:
193
  return "βœ… AUTHENTIQUE", "CohΓ©rence temporelle, colorimΓ©trique et texturale conforme."
@@ -199,14 +160,17 @@ def get_verdict(score_pct):
199
  # ═══════════════════════════════════════════════════════════
200
  # Γ‰TAPE 5 : PIPELINE PRINCIPAL
201
  # ═══════════════════════════════════════════════════════════
202
-
203
  def analyze_video(video_path):
204
  if video_path is None:
205
- return "⚠️ Pas de vidéo fournie.", "{}", ""
206
-
207
  cap = cv2.VideoCapture(video_path)
208
  frames = []
209
  total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
 
 
 
 
210
  step = max(1, total // 16)
211
  for i in range(16):
212
  cap.set(cv2.CAP_PROP_POS_FRAMES, i * step)
@@ -214,43 +178,38 @@ def analyze_video(video_path):
214
  if ret:
215
  frames.append(frame)
216
  cap.release()
217
-
218
  if not frames:
219
- return "❌ Impossible de lire la vidéo.", "{}", ""
220
-
221
  face_cascade = cv2.CascadeClassifier(
222
  cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
223
  )
224
-
225
- # ── Analyse temporelle globale (flux optique sur toutes les frames) ──
226
  flow_score = test_optical_flow(frames)
227
-
228
  per_face_scores = []
229
  engine_log = []
230
-
231
  for idx, frame in enumerate(frames):
232
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
233
  faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(60, 60))
234
-
235
  for (x, y, w, h) in faces:
236
  roi = frame[y:y+h, x:x+w]
237
-
238
  s1 = test_localised_boundaries(roi)
239
  s2 = test_noise_coherence(roi, frame)
240
  s3 = test_fft_frequency(roi)
241
  s5 = test_eye_region(frame)
242
  s6 = test_color_coherence(roi, frame)
243
  s7 = test_texture_lbp(roi)
244
-
245
- # PondΓ©ration V5 β€” flux optique global intΓ©grΓ©
246
  final = (
247
- s1 * 0.10 + # Boundary edges
248
- s2 * 0.18 + # Noise coherence
249
- s3 * 0.12 + # FFT frequency
250
- flow_score * 0.25 + # Optical flow temporel ← clΓ© Sora
251
- s5 * 0.10 + # Eye region
252
- s6 * 0.15 + # Color LAB coherence
253
- s7 * 0.10 # LBP texture
254
  )
255
  per_face_scores.append(final)
256
  engine_log.append({
@@ -264,17 +223,15 @@ def analyze_video(video_path):
264
  "lbp": round(s7, 2),
265
  "score": round(final * 100, 1)
266
  })
267
-
268
  if not per_face_scores:
269
- # Aucun visage β†’ analyse temporelle seule (Sora paysage, etc.)
270
  global_score_pct = round(flow_score * 100, 1)
271
  note = "Aucun visage dΓ©tectΓ© β€” verdict basΓ© sur flux optique uniquement."
272
  else:
273
  global_score_pct = round(float(np.mean(per_face_scores)) * 100, 1)
274
  note = f"{len(per_face_scores)} rΓ©gion(s) de visage analysΓ©e(s) sur {len(frames)} frames."
275
-
276
  verdict, explication = get_verdict(global_score_pct)
277
-
278
  sep = "─" * 52
279
  rapport = (
280
  f"πŸ›‘οΈ VideoShield v5.0 β€” Rapport d'AuthenticitΓ©\n{sep}\n"
@@ -289,38 +246,33 @@ def analyze_video(video_path):
289
  f"Standard : IASA TC-04 | ACoNum Tunisia 2026\n"
290
  f"{sep}"
291
  )
292
-
293
  res_json = {
294
  "version": "VideoShield v5.0",
295
  "score": global_score_pct,
296
  "verdict": verdict,
297
  "flow_global": round(flow_score, 3),
298
- "engines_detail": engine_log[:5], # 5 premières entrées pour lisibilité
299
  "timestamp": str(datetime.datetime.now())
300
  }
301
-
302
- return rapport, json.dumps(res_json, indent=2), ""
303
-
304
 
305
  def generate_iscc(video_path):
306
- """Génère l'empreinte ISCC du fichier vidéo (onglet séparé)."""
307
  if not ISCC_AVAILABLE:
308
  return "❌ iscc-sdk non installΓ©.\nFaire : pip install iscc-sdk --user"
309
  if video_path is None:
310
  return "⚠️ Aucun fichier fourni."
311
-
312
  try:
313
  ext = os.path.splitext(video_path)[1].lower()
314
  if ext in [".wav", ".mp3", ".flac", ".aac", ".ogg"]:
315
  meta = idk.code_audio(video_path)
316
  else:
317
  meta = idk.code_video(video_path)
318
-
319
  iscc_code = meta.get("iscc", "N/A")
320
  content_hash = meta.get("content", "N/A")
321
  data_hash = meta.get("data", "N/A")
322
  structure = meta.get("structure", "N/A")
323
-
324
  sep = "─" * 52
325
  return (
326
  f"πŸ” EMPREINTE ISCC β€” {os.path.basename(video_path)}\n{sep}\n"
@@ -334,19 +286,16 @@ def generate_iscc(video_path):
334
  except Exception as e:
335
  return f"❌ Erreur ISCC : {str(e)}"
336
 
337
-
338
  # ═══════════════════════════════════════════════════════════
339
  # INTERFACE GRADIO V5
340
  # ═══════════════════════════════════════════════════════════
341
-
342
  with gr.Blocks(title="VideoShield v5.0", theme=gr.themes.Soft()) as demo:
343
-
344
  gr.Markdown("# πŸ›‘οΈ VideoShield v5.0 β€” AuthenticitΓ© VidΓ©o IA")
345
  gr.Markdown(
346
  "DΓ©tection de deepfakes gΓ©nΓ©ratifs (Sora 2.0, Runway Gen-3, Kling, Pika 2) "
347
  "via 7 moteurs forensiques OpenCV. Standard IASA TC-04 Β· ACoNum Tunisia 2026."
348
  )
349
-
350
  with gr.Tab("πŸ” Analyse Deepfake"):
351
  with gr.Row():
352
  with gr.Column():
@@ -355,13 +304,13 @@ with gr.Blocks(title="VideoShield v5.0", theme=gr.themes.Soft()) as demo:
355
  with gr.Column():
356
  rapport_out = gr.Textbox(label="Rapport IASA TC-04", lines=14)
357
  json_out = gr.Code(label="Indexation JSON", language="json")
358
-
359
  btn_analyze.click(
360
  analyze_video,
361
  inputs=[video_input],
362
- outputs=[rapport_out, json_out, gr.Textbox(visible=False)]
363
  )
364
-
365
  with gr.Tab("πŸ” ISCC Fingerprint"):
366
  gr.Markdown(
367
  "GΓ©nΓ¨re l'**empreinte de contenu ISCC** du fichier. "
@@ -374,7 +323,6 @@ with gr.Blocks(title="VideoShield v5.0", theme=gr.themes.Soft()) as demo:
374
  btn_iscc = gr.Button("πŸ” GΓ‰NΓ‰RER EMPREINTE ISCC", variant="secondary")
375
  with gr.Column():
376
  iscc_out = gr.Textbox(label="RΓ©sultat ISCC", lines=12)
377
-
378
  btn_iscc.click(generate_iscc, inputs=[video_iscc], outputs=[iscc_out])
379
 
380
  if __name__ == "__main__":
 
34
  # ═══════════════════════════════════════════════════════════
35
  # Γ‰TAPE 2 : MOTEURS V4 (hΓ©ritΓ©s)
36
  # ═══════════════════════════════════════════════════════════
 
37
  def test_localised_boundaries(roi):
38
  gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
39
  edges = cv2.Canny(gray, 100, 200)
 
65
  # ═══════════════════════════════════════════════════════════
66
  # Γ‰TAPE 3 : NOUVEAUX MOTEURS V5 (Sora 2.0 / Runway / Kling)
67
  # ═══════════════════════════════════════════════════════════
 
68
  def test_optical_flow(frames):
 
 
 
 
 
69
  if len(frames) < 2:
70
  return 0.50
 
71
  scores = []
72
  for i in range(min(len(frames) - 1, 8)):
73
  g1 = cv2.cvtColor(frames[i], cv2.COLOR_BGR2GRAY)
 
78
  magnitude, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1])
79
  mag_std = np.std(magnitude)
80
  mag_mean = np.mean(magnitude)
 
81
  if mag_mean < 0.01:
82
+ scores.append(0.60)
83
  else:
84
  ratio = mag_std / mag_mean
85
  if 0.30 < ratio < 2.50:
86
+ scores.append(0.88) # Mouvement naturel
87
  elif ratio < 0.15:
88
+ scores.append(0.18) # Trop lisse β†’ IA
89
  elif ratio > 4.0:
90
+ scores.append(0.22) # Saccades β†’ Injection
91
  else:
92
  scores.append(0.50)
 
93
  return float(np.mean(scores)) if scores else 0.50
94
 
 
95
  def test_eye_region(frame):
 
 
 
 
96
  eye_cascade = cv2.CascadeClassifier(
97
  cv2.data.haarcascades + "haarcascade_eye.xml"
98
  )
99
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
100
  eyes = eye_cascade.detectMultiScale(gray, 1.1, 4, minSize=(20, 20))
 
101
  if len(eyes) == 0:
102
+ return 0.38
 
103
  if len(eyes) == 2:
104
  (x1, y1, w1, h1) = eyes[0]
105
  (x2, y2, w2, h2) = eyes[1]
106
  height_diff = abs(y1 - y2)
107
  size_diff = abs(w1 - w2)
108
  if height_diff < 20 and size_diff < 15:
109
+ return 0.90
110
  return 0.52
111
+ return 0.58
 
 
112
 
113
  def test_color_coherence(roi, frame):
 
 
 
 
 
114
  if roi.size == 0 or frame.size == 0:
115
  return 0.50
 
116
  lab_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2LAB).astype(np.float32)
117
  lab_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB).astype(np.float32)
 
118
  mean_roi = np.mean(lab_roi, axis=(0, 1))
119
  mean_frame = np.mean(lab_frame, axis=(0, 1))
 
120
  delta_E = float(np.sqrt(np.sum((mean_roi - mean_frame) ** 2)))
 
121
  if delta_E < 22:
122
+ return 0.88
123
  elif delta_E < 40:
124
+ return 0.55
125
  else:
126
+ return 0.22
 
127
 
128
  def test_texture_lbp(roi):
 
 
 
 
129
  if roi.size == 0:
130
  return 0.50
 
131
  gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY).astype(np.float32)
 
 
132
  shifted = [
133
  np.roll(np.roll(gray, dy, axis=0), dx, axis=1)
134
  for dy, dx in [(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1)]
 
136
  lbp = np.zeros_like(gray)
137
  for i, s in enumerate(shifted):
138
  lbp += (gray >= s).astype(np.float32) * (2 ** i)
 
139
  hist, _ = np.histogram(lbp, bins=64, range=(0, 256))
140
  hist = hist / (hist.sum() + 1e-6)
141
  variance = float(np.var(hist))
 
142
  if variance > 0.0003:
143
+ return 0.88
144
  elif variance > 0.0001:
145
  return 0.55
146
  else:
147
+ return 0.22
148
 
149
  # ═══════════════════════════════════════════════════════════
150
  # Γ‰TAPE 4 : VERDICT CALIBRΓ‰ V5
151
  # ═══════════════════════════════════════════════════════════
 
152
  def get_verdict(score_pct):
153
  if score_pct >= 72:
154
  return "βœ… AUTHENTIQUE", "CohΓ©rence temporelle, colorimΓ©trique et texturale conforme."
 
160
  # ═══════════════════════════════════════════════════════════
161
  # Γ‰TAPE 5 : PIPELINE PRINCIPAL
162
  # ═══════════════════════════════════════════════════════════
 
163
  def analyze_video(video_path):
164
  if video_path is None:
165
+ return "⚠️ Pas de vidéo fournie.", "{}"
166
+
167
  cap = cv2.VideoCapture(video_path)
168
  frames = []
169
  total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
170
+
171
+ if total <= 0:
172
+ return "❌ Impossible de lire ou fichier vidéo invalide.", "{}"
173
+
174
  step = max(1, total // 16)
175
  for i in range(16):
176
  cap.set(cv2.CAP_PROP_POS_FRAMES, i * step)
 
178
  if ret:
179
  frames.append(frame)
180
  cap.release()
181
+
182
  if not frames:
183
+ return "❌ Impossible de lire les frames de la vidéo.", "{}"
184
+
185
  face_cascade = cv2.CascadeClassifier(
186
  cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
187
  )
188
+
 
189
  flow_score = test_optical_flow(frames)
 
190
  per_face_scores = []
191
  engine_log = []
192
+
193
  for idx, frame in enumerate(frames):
194
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
195
  faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(60, 60))
 
196
  for (x, y, w, h) in faces:
197
  roi = frame[y:y+h, x:x+w]
 
198
  s1 = test_localised_boundaries(roi)
199
  s2 = test_noise_coherence(roi, frame)
200
  s3 = test_fft_frequency(roi)
201
  s5 = test_eye_region(frame)
202
  s6 = test_color_coherence(roi, frame)
203
  s7 = test_texture_lbp(roi)
204
+
 
205
  final = (
206
+ s1 * 0.10 +
207
+ s2 * 0.18 +
208
+ s3 * 0.12 +
209
+ flow_score * 0.25 +
210
+ s5 * 0.10 +
211
+ s6 * 0.15 +
212
+ s7 * 0.10
213
  )
214
  per_face_scores.append(final)
215
  engine_log.append({
 
223
  "lbp": round(s7, 2),
224
  "score": round(final * 100, 1)
225
  })
226
+
227
  if not per_face_scores:
 
228
  global_score_pct = round(flow_score * 100, 1)
229
  note = "Aucun visage dΓ©tectΓ© β€” verdict basΓ© sur flux optique uniquement."
230
  else:
231
  global_score_pct = round(float(np.mean(per_face_scores)) * 100, 1)
232
  note = f"{len(per_face_scores)} rΓ©gion(s) de visage analysΓ©e(s) sur {len(frames)} frames."
233
+
234
  verdict, explication = get_verdict(global_score_pct)
 
235
  sep = "─" * 52
236
  rapport = (
237
  f"πŸ›‘οΈ VideoShield v5.0 β€” Rapport d'AuthenticitΓ©\n{sep}\n"
 
246
  f"Standard : IASA TC-04 | ACoNum Tunisia 2026\n"
247
  f"{sep}"
248
  )
249
+
250
  res_json = {
251
  "version": "VideoShield v5.0",
252
  "score": global_score_pct,
253
  "verdict": verdict,
254
  "flow_global": round(flow_score, 3),
255
+ "engines_detail": engine_log[:5] if engine_log else "Aucun log visage disponible",
256
  "timestamp": str(datetime.datetime.now())
257
  }
258
+
259
+ return rapport, json.dumps(res_json, indent=2)
 
260
 
261
  def generate_iscc(video_path):
 
262
  if not ISCC_AVAILABLE:
263
  return "❌ iscc-sdk non installΓ©.\nFaire : pip install iscc-sdk --user"
264
  if video_path is None:
265
  return "⚠️ Aucun fichier fourni."
 
266
  try:
267
  ext = os.path.splitext(video_path)[1].lower()
268
  if ext in [".wav", ".mp3", ".flac", ".aac", ".ogg"]:
269
  meta = idk.code_audio(video_path)
270
  else:
271
  meta = idk.code_video(video_path)
 
272
  iscc_code = meta.get("iscc", "N/A")
273
  content_hash = meta.get("content", "N/A")
274
  data_hash = meta.get("data", "N/A")
275
  structure = meta.get("structure", "N/A")
 
276
  sep = "─" * 52
277
  return (
278
  f"πŸ” EMPREINTE ISCC β€” {os.path.basename(video_path)}\n{sep}\n"
 
286
  except Exception as e:
287
  return f"❌ Erreur ISCC : {str(e)}"
288
 
 
289
  # ═══════════════════════════════════════════════════════════
290
  # INTERFACE GRADIO V5
291
  # ═══════════════════════════════════════════════════════════
 
292
  with gr.Blocks(title="VideoShield v5.0", theme=gr.themes.Soft()) as demo:
 
293
  gr.Markdown("# πŸ›‘οΈ VideoShield v5.0 β€” AuthenticitΓ© VidΓ©o IA")
294
  gr.Markdown(
295
  "DΓ©tection de deepfakes gΓ©nΓ©ratifs (Sora 2.0, Runway Gen-3, Kling, Pika 2) "
296
  "via 7 moteurs forensiques OpenCV. Standard IASA TC-04 Β· ACoNum Tunisia 2026."
297
  )
298
+
299
  with gr.Tab("πŸ” Analyse Deepfake"):
300
  with gr.Row():
301
  with gr.Column():
 
304
  with gr.Column():
305
  rapport_out = gr.Textbox(label="Rapport IASA TC-04", lines=14)
306
  json_out = gr.Code(label="Indexation JSON", language="json")
307
+
308
  btn_analyze.click(
309
  analyze_video,
310
  inputs=[video_input],
311
+ outputs=[rapport_out, json_out]
312
  )
313
+
314
  with gr.Tab("πŸ” ISCC Fingerprint"):
315
  gr.Markdown(
316
  "GΓ©nΓ¨re l'**empreinte de contenu ISCC** du fichier. "
 
323
  btn_iscc = gr.Button("πŸ” GΓ‰NΓ‰RER EMPREINTE ISCC", variant="secondary")
324
  with gr.Column():
325
  iscc_out = gr.Textbox(label="RΓ©sultat ISCC", lines=12)
 
326
  btn_iscc.click(generate_iscc, inputs=[video_iscc], outputs=[iscc_out])
327
 
328
  if __name__ == "__main__":