farouk04 commited on
Commit
7fb4ec5
Β·
verified Β·
1 Parent(s): 043203b

Update detector.py

Browse files
Files changed (1) hide show
  1. detector.py +58 -46
detector.py CHANGED
@@ -1,5 +1,4 @@
1
- """
2
- Deepfake Detector β€” Hugging Face ZeroGPU Space
3
  V-JEPA 2.1 ViT-Giant + AttentiveClassifier probe
4
  """
5
  import glob
@@ -11,6 +10,7 @@ import time
11
  import uuid
12
  import json
13
  import requests
 
14
  from datetime import datetime
15
 
16
  warnings.filterwarnings("ignore", category=FutureWarning)
@@ -32,7 +32,6 @@ ENCODER_BATCH = 3
32
  HF_TOKEN = os.environ.get("HF_TOKEN")
33
  ENCODER_REPO = os.environ.get("ENCODER_REPO", "")
34
  PROBE_REPO = os.environ.get("PROBE_REPO", ENCODER_REPO)
35
-
36
  DATASET_REPO_ID = os.environ.get("DATASET_REPO_ID", "")
37
  TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
38
  TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
@@ -56,13 +55,11 @@ encoder_weights_path = hf_hub_download(
56
  filename="vjepa2_1_vitg_384.pt",
57
  token=HF_TOKEN,
58
  )
59
-
60
  ckpt = torch.load(encoder_weights_path, map_location="cpu", weights_only=True)
61
  ckpt = ckpt.get("ema_encoder", ckpt.get("encoder", ckpt))
62
  ckpt = {k.replace("module.", "").replace("backbone.", ""): v for k, v in ckpt.items()}
63
  encoder.load_state_dict(ckpt, strict=False)
64
  del ckpt
65
-
66
  encoder = encoder.to(DEVICE).to(torch.bfloat16)
67
  encoder.eval()
68
  print("Encoder ready.")
@@ -73,11 +70,9 @@ probe_path = hf_hub_download(
73
  filename="attentive_probe_optimized.pt",
74
  token=HF_TOKEN,
75
  )
76
-
77
  probe_state = torch.load(probe_path, map_location="cpu")
78
  if "probe_state_dict" in probe_state:
79
  probe_state = probe_state["probe_state_dict"]
80
-
81
  probe = AttentiveClassifier(embed_dim=FEATURE_DIM)
82
  probe.load_state_dict(probe_state)
83
  probe = probe.to(DEVICE)
@@ -105,7 +100,6 @@ def extract_platform(url: str) -> str:
105
  def download_video(url: str) -> tuple[bool, str]:
106
  tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
107
  tmp.close()
108
-
109
  cmd = [
110
  "yt-dlp",
111
  "-f", "best[height<=720]/best",
@@ -117,17 +111,14 @@ def download_video(url: str) -> tuple[bool, str]:
117
  "-o", tmp.name,
118
  url,
119
  ]
120
-
121
  try:
122
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
123
  candidates = sorted(glob.glob(tmp.name + "*") + [tmp.name])
124
  actual_file = next((f for f in candidates if os.path.exists(f) and os.path.getsize(f) > 0), None)
125
-
126
  if actual_file is None:
127
  if "does not pass filter" in result.stdout or "does not pass filter" in result.stderr:
128
  return False, "Video length exceeds the maximum 2-minute limit."
129
  return False, f"yt-dlp wrote no output. stderr: {result.stderr[:400]}"
130
-
131
  return True, actual_file
132
  except subprocess.TimeoutExpired:
133
  return False, "Download timed out (90s limit)."
@@ -166,7 +157,6 @@ def upload_and_notify(video_path: str, meta: dict):
166
  if not DATASET_REPO_ID or not HF_TOKEN:
167
  print("Upload skipped: Credentials missing.")
168
  return
169
-
170
  try:
171
  api = HfApi(token=HF_TOKEN)
172
  unique_id = str(uuid.uuid4())[:8]
@@ -175,13 +165,13 @@ def upload_and_notify(video_path: str, meta: dict):
175
 
176
  repo_video_path = f"data/video_{base_filename}.mp4"
177
  repo_json_path = f"data/meta_{base_filename}.json"
178
-
179
  # Generate the direct download/view asset URL for the video
180
  direct_video_url = f"https://huggingface.co/datasets/{DATASET_REPO_ID}/resolve/main/{repo_video_path}"
181
 
182
  # Store the video direct link inside the metadata dictionary itself
183
  meta["stored_video_url"] = direct_video_url
184
-
185
  # 1. Upload Video File
186
  api.upload_file(
187
  path_or_fileobj=video_path,
@@ -197,7 +187,7 @@ def upload_and_notify(video_path: str, meta: dict):
197
  repo_id=DATASET_REPO_ID,
198
  repo_type="dataset"
199
  )
200
-
201
  # 3. Send Telegram alert containing the direct video asset link
202
  if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
203
  tele_msg = (
@@ -221,11 +211,11 @@ def upload_and_notify(video_path: str, meta: dict):
221
  # ─────────────────────────────────────────────
222
  # GRADIO PREDICT
223
  # ─────────────────────────────────────────────
224
- def predict(url: str, consent: bool, current_state: dict) -> tuple[str, str, str, dict]:
225
  # Check mandatory consent gate
226
  if not consent:
227
  return "❌ Required: You must check the data collection consent box to process videos.", "", "", current_state
228
-
229
  start_time = time.time()
230
  url = (url or "").strip()
231
 
@@ -233,16 +223,27 @@ def predict(url: str, consent: bool, current_state: dict) -> tuple[str, str, str
233
  old_path = current_state["video_path"]
234
  for f in glob.glob(old_path + "*") + [old_path]:
235
  if os.path.exists(f):
236
- os.remove(f)
237
-
238
- if not url:
239
- return "Please enter a URL.", "", "", {}
240
-
241
- ok, path_or_err = download_video(url)
242
- if not ok:
243
- return f"Download failed: {path_or_err}", "", "", {}
244
-
245
- video_path = path_or_err
 
 
 
 
 
 
 
 
 
 
 
246
 
247
  try:
248
  windows, total_frames, fps, duration_sec = get_video_windows(video_path)
@@ -250,14 +251,14 @@ def predict(url: str, consent: bool, current_state: dict) -> tuple[str, str, str
250
  if os.path.exists(video_path):
251
  os.remove(video_path)
252
  return f"Video decode failed: {e}", "", "", {}
253
-
254
  scores = encode_and_classify(windows)
255
-
256
  if not scores:
257
  if os.path.exists(video_path):
258
  os.remove(video_path)
259
  return "No windows processed.", "", "", {}
260
-
261
  final_prob = max(s["prob"] for s in scores)
262
  label = "⚠️ AI-GENERATED (FAKE)" if final_prob > 0.5 else "βœ… REAL"
263
  elapsed_time = time.time() - start_time
@@ -270,26 +271,29 @@ def predict(url: str, consent: bool, current_state: dict) -> tuple[str, str, str
270
  for s in scores:
271
  flag = " ← peak" if (s["prob"] == final_prob and final_prob > 0.5) else ""
272
  lines.append(f" [{s['start_sec']:.1f}s – {s['end_sec']:.1f}s] β†’ {s['prob'] * 100:.1f}% fake{flag}")
273
-
274
  # Build base metadata dict
275
  meta = {
276
- "url": url,
277
- "platform": extract_platform(url),
278
  "label": label,
279
  "confidence": confidence,
280
  "processing_time_sec": round(elapsed_time, 2),
281
  "video_duration_sec": round(duration_sec, 2),
282
  "total_frames": total_frames
283
  }
284
-
285
  # Execute backend background syncing task
286
  upload_and_notify(video_path, meta)
287
-
288
  # Clean up local system file tracking loop paths
289
  for f in glob.glob(video_path + "*") + [video_path]:
290
  if os.path.exists(f):
291
- os.remove(f)
292
-
 
 
 
293
  return label, confidence, "\n".join(lines), {}
294
 
295
  # ─────────────────────────────────────────────
@@ -297,20 +301,27 @@ def predict(url: str, consent: bool, current_state: dict) -> tuple[str, str, str
297
  # ─────────────────────────────────────────────
298
  with gr.Blocks(title="Deepfake Detector") as demo:
299
  session_state = gr.State(value={})
300
-
301
  gr.Markdown(
302
  """
303
  ## 🎬 Deepfake Video Detector
304
- Paste a public Instagram, TikTok, or direct `.mp4` URL. (Maximum duration: 2 minutes).
305
  Uses **V-JEPA 2.1 ViT-Giant** + **AttentiveClassifier** probe.
306
  """
307
  )
308
 
309
- url_box = gr.Textbox(
310
- label="Video URL",
311
- placeholder="https://www.instagram.com/reels/...",
312
- autofocus=True,
313
- )
 
 
 
 
 
 
 
314
 
315
  # Mandatory Consent Checkbox
316
  consent_box = gr.Checkbox(
@@ -329,12 +340,13 @@ with gr.Blocks(title="Deepfake Detector") as demo:
329
 
330
  run_btn.click(
331
  fn=predict,
332
- inputs=[url_box, consent_box, session_state],
333
  outputs=[label_out, conf_out, breakdown_out, session_state],
334
  )
 
335
  url_box.submit(
336
  fn=predict,
337
- inputs=[url_box, consent_box, session_state],
338
  outputs=[label_out, conf_out, breakdown_out, session_state],
339
  )
340
 
 
1
+ """Deepfake Detector β€” Hugging Face ZeroGPU Space
 
2
  V-JEPA 2.1 ViT-Giant + AttentiveClassifier probe
3
  """
4
  import glob
 
10
  import uuid
11
  import json
12
  import requests
13
+ import shutil
14
  from datetime import datetime
15
 
16
  warnings.filterwarnings("ignore", category=FutureWarning)
 
32
  HF_TOKEN = os.environ.get("HF_TOKEN")
33
  ENCODER_REPO = os.environ.get("ENCODER_REPO", "")
34
  PROBE_REPO = os.environ.get("PROBE_REPO", ENCODER_REPO)
 
35
  DATASET_REPO_ID = os.environ.get("DATASET_REPO_ID", "")
36
  TELEGRAM_BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
37
  TELEGRAM_CHAT_ID = os.environ.get("TELEGRAM_CHAT_ID", "")
 
55
  filename="vjepa2_1_vitg_384.pt",
56
  token=HF_TOKEN,
57
  )
 
58
  ckpt = torch.load(encoder_weights_path, map_location="cpu", weights_only=True)
59
  ckpt = ckpt.get("ema_encoder", ckpt.get("encoder", ckpt))
60
  ckpt = {k.replace("module.", "").replace("backbone.", ""): v for k, v in ckpt.items()}
61
  encoder.load_state_dict(ckpt, strict=False)
62
  del ckpt
 
63
  encoder = encoder.to(DEVICE).to(torch.bfloat16)
64
  encoder.eval()
65
  print("Encoder ready.")
 
70
  filename="attentive_probe_optimized.pt",
71
  token=HF_TOKEN,
72
  )
 
73
  probe_state = torch.load(probe_path, map_location="cpu")
74
  if "probe_state_dict" in probe_state:
75
  probe_state = probe_state["probe_state_dict"]
 
76
  probe = AttentiveClassifier(embed_dim=FEATURE_DIM)
77
  probe.load_state_dict(probe_state)
78
  probe = probe.to(DEVICE)
 
100
  def download_video(url: str) -> tuple[bool, str]:
101
  tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
102
  tmp.close()
 
103
  cmd = [
104
  "yt-dlp",
105
  "-f", "best[height<=720]/best",
 
111
  "-o", tmp.name,
112
  url,
113
  ]
 
114
  try:
115
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
116
  candidates = sorted(glob.glob(tmp.name + "*") + [tmp.name])
117
  actual_file = next((f for f in candidates if os.path.exists(f) and os.path.getsize(f) > 0), None)
 
118
  if actual_file is None:
119
  if "does not pass filter" in result.stdout or "does not pass filter" in result.stderr:
120
  return False, "Video length exceeds the maximum 2-minute limit."
121
  return False, f"yt-dlp wrote no output. stderr: {result.stderr[:400]}"
 
122
  return True, actual_file
123
  except subprocess.TimeoutExpired:
124
  return False, "Download timed out (90s limit)."
 
157
  if not DATASET_REPO_ID or not HF_TOKEN:
158
  print("Upload skipped: Credentials missing.")
159
  return
 
160
  try:
161
  api = HfApi(token=HF_TOKEN)
162
  unique_id = str(uuid.uuid4())[:8]
 
165
 
166
  repo_video_path = f"data/video_{base_filename}.mp4"
167
  repo_json_path = f"data/meta_{base_filename}.json"
168
+
169
  # Generate the direct download/view asset URL for the video
170
  direct_video_url = f"https://huggingface.co/datasets/{DATASET_REPO_ID}/resolve/main/{repo_video_path}"
171
 
172
  # Store the video direct link inside the metadata dictionary itself
173
  meta["stored_video_url"] = direct_video_url
174
+
175
  # 1. Upload Video File
176
  api.upload_file(
177
  path_or_fileobj=video_path,
 
187
  repo_id=DATASET_REPO_ID,
188
  repo_type="dataset"
189
  )
190
+
191
  # 3. Send Telegram alert containing the direct video asset link
192
  if TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID:
193
  tele_msg = (
 
211
  # ─────────────────────────────────────────────
212
  # GRADIO PREDICT
213
  # ─────────────────────────────────────────────
214
+ def predict(url: str, uploaded_video: str, consent: bool, current_state: dict) -> tuple[str, str, str, dict]:
215
  # Check mandatory consent gate
216
  if not consent:
217
  return "❌ Required: You must check the data collection consent box to process videos.", "", "", current_state
218
+
219
  start_time = time.time()
220
  url = (url or "").strip()
221
 
 
223
  old_path = current_state["video_path"]
224
  for f in glob.glob(old_path + "*") + [old_path]:
225
  if os.path.exists(f):
226
+ try:
227
+ os.remove(f)
228
+ except Exception:
229
+ pass
230
+
231
+ # Source priority: Direct upload over URL
232
+ if uploaded_video:
233
+ tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
234
+ shutil.copy(uploaded_video, tmp.name)
235
+ video_path = tmp.name
236
+ platform_name = "Direct Upload"
237
+ stored_url = "Uploaded File"
238
+ elif url:
239
+ ok, path_or_err = download_video(url)
240
+ if not ok:
241
+ return f"Download failed: {path_or_err}", "", "", {}
242
+ video_path = path_or_err
243
+ platform_name = extract_platform(url)
244
+ stored_url = url
245
+ else:
246
+ return "Please enter a URL or upload a video.", "", "", {}
247
 
248
  try:
249
  windows, total_frames, fps, duration_sec = get_video_windows(video_path)
 
251
  if os.path.exists(video_path):
252
  os.remove(video_path)
253
  return f"Video decode failed: {e}", "", "", {}
254
+
255
  scores = encode_and_classify(windows)
256
+
257
  if not scores:
258
  if os.path.exists(video_path):
259
  os.remove(video_path)
260
  return "No windows processed.", "", "", {}
261
+
262
  final_prob = max(s["prob"] for s in scores)
263
  label = "⚠️ AI-GENERATED (FAKE)" if final_prob > 0.5 else "βœ… REAL"
264
  elapsed_time = time.time() - start_time
 
271
  for s in scores:
272
  flag = " ← peak" if (s["prob"] == final_prob and final_prob > 0.5) else ""
273
  lines.append(f" [{s['start_sec']:.1f}s – {s['end_sec']:.1f}s] β†’ {s['prob'] * 100:.1f}% fake{flag}")
274
+
275
  # Build base metadata dict
276
  meta = {
277
+ "url": stored_url,
278
+ "platform": platform_name,
279
  "label": label,
280
  "confidence": confidence,
281
  "processing_time_sec": round(elapsed_time, 2),
282
  "video_duration_sec": round(duration_sec, 2),
283
  "total_frames": total_frames
284
  }
285
+
286
  # Execute backend background syncing task
287
  upload_and_notify(video_path, meta)
288
+
289
  # Clean up local system file tracking loop paths
290
  for f in glob.glob(video_path + "*") + [video_path]:
291
  if os.path.exists(f):
292
+ try:
293
+ os.remove(f)
294
+ except Exception:
295
+ pass
296
+
297
  return label, confidence, "\n".join(lines), {}
298
 
299
  # ─────────────────────────────────────────────
 
301
  # ─────────────────────────────────────────────
302
  with gr.Blocks(title="Deepfake Detector") as demo:
303
  session_state = gr.State(value={})
304
+
305
  gr.Markdown(
306
  """
307
  ## 🎬 Deepfake Video Detector
308
+ Paste a public Instagram, TikTok, or direct `.mp4` URL, **OR** upload a video directly. (Maximum duration: 2 minutes).
309
  Uses **V-JEPA 2.1 ViT-Giant** + **AttentiveClassifier** probe.
310
  """
311
  )
312
 
313
+ with gr.Row():
314
+ url_box = gr.Textbox(
315
+ label="Video URL",
316
+ placeholder="https://www.instagram.com/reels/...",
317
+ autofocus=True,
318
+ scale=1
319
+ )
320
+ upload_box = gr.Video(
321
+ label="Or Upload Video directly",
322
+ sources=["upload"],
323
+ scale=1
324
+ )
325
 
326
  # Mandatory Consent Checkbox
327
  consent_box = gr.Checkbox(
 
340
 
341
  run_btn.click(
342
  fn=predict,
343
+ inputs=[url_box, upload_box, consent_box, session_state],
344
  outputs=[label_out, conf_out, breakdown_out, session_state],
345
  )
346
+
347
  url_box.submit(
348
  fn=predict,
349
+ inputs=[url_box, upload_box, consent_box, session_state],
350
  outputs=[label_out, conf_out, breakdown_out, session_state],
351
  )
352