Spaces:
Sleeping
Sleeping
| """ | |
| DeepfakeDetector β orchestrates the full video analysis pipeline | |
| """ | |
| import os, uuid, time | |
| import numpy as np | |
| import torch | |
| import torchvision.transforms as T | |
| from pathlib import Path | |
| from typing import Optional | |
| from model import HybridDeepfakeDetector | |
| from face_extractor import FaceExtractor | |
| from gradcam import GradCAM | |
| # ββ Image pre-processing βββββββββββββββββββββββββββββββββββββββββββββ | |
| MEAN = [0.485, 0.456, 0.406] | |
| STD = [0.229, 0.224, 0.225] | |
| transform = T.Compose([ | |
| T.ToTensor(), | |
| T.Normalize(mean=MEAN, std=STD), | |
| ]) | |
| class DeepfakeDetector: | |
| """ | |
| End-to-end video deepfake detector. | |
| Steps: | |
| 1. Sample frames from video | |
| 2. Crop face regions | |
| 3. Run hybrid model inference | |
| 4. Generate Grad-CAM heatmaps | |
| 5. Aggregate per-frame scores into video-level verdict | |
| """ | |
| def __init__( | |
| self, | |
| model_path: Optional[str] = None, | |
| device: Optional[str] = None, | |
| max_frames: int = 32, | |
| threshold: float = 0.5, | |
| ): | |
| self.device = device or ("cuda" if torch.cuda.is_available() else "cpu") | |
| self.threshold = threshold | |
| self.max_frames = max_frames | |
| # Load model | |
| self.model = HybridDeepfakeDetector(pretrained=(model_path is None)) | |
| if model_path and Path(model_path).exists(): | |
| state = torch.load(model_path, map_location=self.device) | |
| self.model.load_state_dict(state) | |
| print(f"[Detector] Loaded weights from {model_path}") | |
| else: | |
| print("[Detector] Using ImageNet pretrained weights (demo mode). " | |
| "Train on FF++ for research-quality results.") | |
| self.model.to(self.device).eval() | |
| self.extractor = FaceExtractor() | |
| self.gradcam = GradCAM(self.model) | |
| # ββ Single frame inference βββββββββββββββββββββββββββββββββββββββ | |
| def _infer_frame(self, face_rgb: np.ndarray) -> float: | |
| """Returns fake probability for one face crop.""" | |
| tensor = transform(face_rgb).unsqueeze(0).to(self.device) | |
| with torch.no_grad(): | |
| prob = self.model.predict_proba(tensor).item() | |
| return float(prob) | |
| # ββ Main analysis ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def analyze(self, video_path: str, session_id: Optional[str] = None) -> dict: | |
| t0 = time.time() | |
| session_id = session_id or str(uuid.uuid4()) | |
| results_dir = Path("results") / session_id | |
| results_dir.mkdir(parents=True, exist_ok=True) | |
| # 1. Extract faces | |
| faces = self.extractor.extract(video_path, max_frames=self.max_frames) | |
| if not faces: | |
| return { | |
| "session_id": session_id, | |
| "verdict": "UNKNOWN", | |
| "confidence": 0.0, | |
| "error": "No faces detected in video.", | |
| "frames": [], | |
| } | |
| # 2. Per-frame inference + Grad-CAM | |
| frame_results = [] | |
| scores = [] | |
| for i, item in enumerate(faces): | |
| face_rgb = item["face"] | |
| fidx = item["frame_idx"] | |
| prob = self._infer_frame(face_rgb) | |
| scores.append(prob) | |
| # Generate and save Grad-CAM heatmap | |
| cam_path = str(results_dir / f"frame_{i:04d}_cam.jpg") | |
| self.gradcam.generate(face_rgb, cam_path) | |
| frame_results.append({ | |
| "frame_idx": fidx, | |
| "fake_prob": round(prob, 4), | |
| "verdict": "FAKE" if prob >= self.threshold else "REAL", | |
| "cam_path": f"/results/{session_id}/frame_{i:04d}_cam.jpg", | |
| }) | |
| # 3. Video-level score | |
| video_score = float(np.mean(scores)) | |
| verdict = "FAKE" if video_score >= self.threshold else "REAL" | |
| elapsed = round(time.time() - t0, 2) | |
| return { | |
| "session_id": session_id, | |
| "verdict": verdict, | |
| "confidence": round(video_score * 100, 2), | |
| "fake_prob": round(video_score, 4), | |
| "frames_analyzed": len(faces), | |
| "elapsed_sec": elapsed, | |
| "frame_scores": frame_results, | |
| } | |