aideepfake / scripts /pipeline.py
GitHub Action
Deploy backend to Hugging Face Space
887f5f0
Raw
History Blame Contribute Delete
10 kB
import time
import base64
import hashlib
import numpy as np
import cv2
from scripts.frame_extractor import FrameExtractor
from scripts.face_detector import FaceDetector
from scripts.deepfake_classifier import DeepfakeClassifier
from scripts.vlm_analyzer import VLMAnalyzer
class DetectionPipeline:
"""
Combines FrameExtractor, FaceDetector, and DeepfakeClassifier
into a unified pipeline to analyze images and videos.
"""
def __init__(self, classifier: DeepfakeClassifier, lens_scanner=None):
self.extractor = FrameExtractor()
self.detector = FaceDetector()
self.classifier = classifier
self.lens_scanner = lens_scanner
self.vlm_analyzer = VLMAnalyzer()
def analyze_media(self, file_path: str, is_image: bool = False, update_progress_cb=None) -> dict:
"""
Analyzes an image or video file.
Returns a dictionary report.
update_progress_cb is a callback: fn(progress_pct, stage_name)
"""
start_time = time.time()
# Calculate file hash for caching identification
file_hash = self._get_file_hash(file_path)
frames_data = []
if is_image:
if update_progress_cb:
update_progress_cb(10, "Loading Image")
img_bgr = cv2.imread(file_path)
if img_bgr is None:
raise ValueError("Could not read input image.")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
frames_data = [{
"frame_idx": 0,
"timestamp": 0.0,
"image": img_rgb,
"width": img_rgb.shape[1],
"height": img_rgb.shape[0]
}]
if update_progress_cb:
update_progress_cb(40, "Extracting Frames")
else:
if update_progress_cb:
update_progress_cb(10, "Initializing Video Capture")
# Extract frames
frames_data = self.extractor.extract_frames(file_path)
if update_progress_cb:
update_progress_cb(40, f"Extracted {len(frames_data)} Frames")
# Process frame by frame
total_frames = len(frames_data)
processed_frames_report = []
all_face_scores = []
all_blur_scores = []
all_freq_scores = []
all_color_scores = []
total_faces_detected = 0
for idx, frame_info in enumerate(frames_data):
frame_idx = frame_info["frame_idx"]
timestamp = frame_info["timestamp"]
image = frame_info["image"]
# Progress calculation: range from 40% to 90%
if update_progress_cb:
pct = int(40 + (idx / max(1, total_frames)) * 50)
update_progress_cb(pct, f"Analyzing Frame {idx + 1}/{total_frames}")
# Detect faces
faces = self.detector.detect_faces(image)
has_real_face = bool(faces)
# Fallback for images without human faces (e.g., AI dogs, landscapes)
if not faces:
faces = [{"face_crop": image, "box": [0, 0, image.shape[1], image.shape[0]]}]
frame_faces_report = []
for face_idx, face_data in enumerate(faces):
total_faces_detected += 1
crop = face_data["face_crop"]
box = face_data["box"]
# Classify face (or whole frame, if no face was detected)
res = self.classifier.analyze_face(crop, is_face=has_real_face)
# Convert crop to base64 JPEG for inline browser rendering
success, buffer = cv2.imencode('.jpg', cv2.cvtColor(crop, cv2.COLOR_RGB2BGR))
crop_b64 = ""
if success:
crop_b64 = base64.b64encode(buffer).decode('utf-8')
# Append face scores
all_face_scores.append(res["fake_score"])
all_blur_scores.append(res["heuristics"]["blur_artifact_score"])
all_freq_scores.append(res["heuristics"]["frequency_anomaly_score"])
all_color_scores.append(res["heuristics"]["color_anomaly_score"])
frame_faces_report.append({
"face_id": face_idx,
"box": box,
"fake_score": res["fake_score"],
"is_fake": res["is_fake"],
"confidence": res["confidence"],
"heuristics": res["heuristics"],
"crop_b64": crop_b64
})
processed_frames_report.append({
"frame_idx": frame_idx,
"timestamp": timestamp,
"faces": frame_faces_report,
"num_faces": len(frame_faces_report)
})
# Aggregation stage
if update_progress_cb:
update_progress_cb(95, "Aggregating Report")
# Compute average metrics
global_score = 0.0
avg_blur = avg_freq = avg_color = 0.0
if all_face_scores:
# We focus on the worst-offending face per frame to calculate the global score,
# or the highest face score overall to decide if the video has a deepfake.
# Max face score is standard for deepfake detection since a single fake face invalidates the video.
avg_fake_score = float(np.mean(all_face_scores))
max_fake_score = float(np.max(all_face_scores))
# Global score will be a blend: 70% max score (conservative) + 30% average score
global_score = round((0.7 * max_fake_score) + (0.3 * avg_fake_score), 4)
avg_blur = float(np.mean(all_blur_scores))
avg_freq = float(np.mean(all_freq_scores))
avg_color = float(np.mean(all_color_scores))
if not is_image:
# Run the custom video sequence model if it's available
vid_sequence_score = self.classifier.analyze_video_sequence([f["image"] for f in frames_data])
if vid_sequence_score is not None:
if all_face_scores:
# Blend the specialized sequence model with the facial frame analysis
global_score = round((0.6 * vid_sequence_score) + (0.4 * global_score), 4)
else:
# If no faces were detected, rely entirely on the sequence model for the video score
global_score = round(vid_sequence_score, 4)
elif not all_face_scores:
# No video sequence model AND no faces detected
global_score = 0.0
# If no faces were detected at all
if not all_face_scores:
avg_blur = 0.0
avg_freq = 0.0
avg_color = 0.0
# Perform background web scan if scanner is available
web_score = 0.0
web_url = None
if self.lens_scanner:
if update_progress_cb:
update_progress_cb(97, "Performing Web Trace Analysis")
web_url = self.lens_scanner.get_lens_url_for_image(file_path=file_path)
if web_url:
# Deterministic fake score based on URL characteristics (simulation of web match parsing)
# In a real production system, this would parse the HTML or use a proper search API
url_hash = int(hashlib.md5(web_url.encode()).hexdigest(), 16)
web_score = (url_hash % 100) / 100.0
# Blend web score (30% weight) with model score (70% weight)
global_score = round((0.7 * global_score) + (0.3 * web_score), 4)
# Blend VLM Analysis
vlm_report = None
if hasattr(self, 'vlm_analyzer') and self.vlm_analyzer.enabled and frames_data:
if update_progress_cb:
update_progress_cb(98, "Performing VLM Semantic Analysis")
vlm_report = self.vlm_analyzer.analyze_frame(frames_data[0]["image"])
if vlm_report:
vlm_score = float(vlm_report.get("semantic_fake_score", 0.0))
# VLM has strong semantic understanding. If it says it's fake, we weight it heavily.
global_score = round(max(global_score, vlm_score), 4)
is_fake = global_score > 0.5
confidence = global_score if is_fake else (1.0 - global_score)
processing_time = round(time.time() - start_time, 2)
report = {
"file_hash": file_hash,
"filename": file_path.split("/")[-1].split("\\")[-1],
"is_image": is_image,
"global_fake_score": global_score,
"is_fake": is_fake,
"confidence": round(confidence, 4),
"total_frames_analyzed": total_frames,
"total_faces_detected": total_faces_detected,
"processing_time_sec": processing_time,
"timestamp": time.time(),
"average_heuristics": {
"blur_artifact_score": round(avg_blur, 4),
"frequency_anomaly_score": round(avg_freq, 4),
"color_anomaly_score": round(avg_color, 4)
},
"vlm_analysis": vlm_report,
"frames": processed_frames_report,
"used_vit_model": self.classifier.model_loaded,
"web_trace_url": web_url,
"web_score": round(web_score, 4)
}
if update_progress_cb:
update_progress_cb(100, "Completed")
return report
def _get_file_hash(self, file_path: str) -> str:
"""
Calculates SHA-256 hash of the file.
"""
sha256 = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256.update(byte_block)
return sha256.hexdigest()