Spaces:
Sleeping
Sleeping
Upload main.py
Browse files
main.py
CHANGED
|
@@ -61,16 +61,16 @@ def extract_clip(video_path, start_frame):
|
|
| 61 |
"""
|
| 62 |
Open a fresh cap, seek ONCE to start_frame, then read frames
|
| 63 |
SEQUENTIALLY (no cap.set inside loop). This is reliable for all codecs.
|
| 64 |
-
Collects SEQUENCE_LENGTH face crops.
|
| 65 |
"""
|
| 66 |
cap = cv2.VideoCapture(video_path)
|
| 67 |
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) # Seek exactly ONCE
|
| 68 |
|
| 69 |
faces = []
|
| 70 |
-
|
| 71 |
attempts = 0
|
| 72 |
|
| 73 |
-
while len(faces) < SEQUENCE_LENGTH and attempts <
|
| 74 |
ret, frame = cap.read() # Sequential β no random seeking
|
| 75 |
if not ret:
|
| 76 |
break
|
|
@@ -79,19 +79,25 @@ def extract_clip(video_path, start_frame):
|
|
| 79 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 80 |
pil_img = Image.fromarray(frame_rgb)
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
cap.release()
|
| 97 |
|
|
@@ -153,28 +159,45 @@ async def analyze_video(file: UploadFile = File(...)):
|
|
| 153 |
return {"isFake": False, "confidence": 0,
|
| 154 |
"explanation": "Could not read the video file.", "details": []}
|
| 155 |
|
| 156 |
-
# ββ
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
return {
|
| 164 |
"isFake": False, "confidence": 0,
|
| 165 |
"explanation": "Could not detect a clear face in the video. Ensure the subject's face is visible.",
|
| 166 |
"details": []
|
| 167 |
}
|
| 168 |
|
| 169 |
-
print(f"Extracted {len(faces)} face frames. Running inference...")
|
| 170 |
-
fake_prob = run_inference(faces)
|
| 171 |
-
|
| 172 |
# ββ Apply calibrated threshold βββββββββββββββββββββββββββββββββββββββββ
|
| 173 |
# This model scores ~60-75% for real videos, so we require 80%+ to call FAKE.
|
| 174 |
-
is_fake =
|
| 175 |
-
confidence = round((
|
| 176 |
|
| 177 |
-
print(f"FINAL β fake_prob={
|
| 178 |
|
| 179 |
explanation = (
|
| 180 |
"Our Temporal AI detected strong evidence of facial manipulation β "
|
|
@@ -182,7 +205,7 @@ async def analyze_video(file: UploadFile = File(...)):
|
|
| 182 |
"characteristic of deepfake synthesis."
|
| 183 |
if is_fake else
|
| 184 |
"Our Temporal AI found no significant manipulation artifacts. "
|
| 185 |
-
"Facial motion, micro-expressions, and temporal consistency appear natural."
|
| 186 |
)
|
| 187 |
|
| 188 |
return {
|
|
@@ -191,16 +214,16 @@ async def analyze_video(file: UploadFile = File(...)):
|
|
| 191 |
"explanation": explanation,
|
| 192 |
"details": [
|
| 193 |
{
|
| 194 |
-
"title": "Temporal Analysis",
|
| 195 |
-
"desc": f"Analyzed
|
| 196 |
},
|
| 197 |
{
|
| 198 |
-
"title": "
|
| 199 |
-
"desc": "Frames read sequentially with
|
| 200 |
},
|
| 201 |
{
|
| 202 |
"title": "Calibrated Threshold",
|
| 203 |
-
"desc": f"
|
| 204 |
}
|
| 205 |
]
|
| 206 |
}
|
|
|
|
| 61 |
"""
|
| 62 |
Open a fresh cap, seek ONCE to start_frame, then read frames
|
| 63 |
SEQUENTIALLY (no cap.set inside loop). This is reliable for all codecs.
|
| 64 |
+
Collects SEQUENCE_LENGTH face crops using bounding box caching for speed.
|
| 65 |
"""
|
| 66 |
cap = cv2.VideoCapture(video_path)
|
| 67 |
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame) # Seek exactly ONCE
|
| 68 |
|
| 69 |
faces = []
|
| 70 |
+
base_box = None
|
| 71 |
attempts = 0
|
| 72 |
|
| 73 |
+
while len(faces) < SEQUENCE_LENGTH and attempts < 60:
|
| 74 |
ret, frame = cap.read() # Sequential β no random seeking
|
| 75 |
if not ret:
|
| 76 |
break
|
|
|
|
| 79 |
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 80 |
pil_img = Image.fromarray(frame_rgb)
|
| 81 |
|
| 82 |
+
if base_box is None:
|
| 83 |
+
# Downscale for faster MTCNN detection
|
| 84 |
+
detect_img = pil_img.copy()
|
| 85 |
+
if detect_img.width > 640:
|
| 86 |
+
ratio = 640.0 / detect_img.width
|
| 87 |
+
detect_img = detect_img.resize((640, int(detect_img.height * ratio)))
|
| 88 |
+
boxes, _ = mtcnn.detect(detect_img)
|
| 89 |
+
if boxes is not None and len(boxes) > 0:
|
| 90 |
+
base_box = [b / ratio for b in boxes[0].tolist()]
|
| 91 |
+
else:
|
| 92 |
+
boxes, _ = mtcnn.detect(pil_img)
|
| 93 |
+
if boxes is not None and len(boxes) > 0:
|
| 94 |
+
base_box = boxes[0].tolist()
|
| 95 |
+
|
| 96 |
+
if base_box is not None:
|
| 97 |
+
# We use a larger padding (0.5) because we are caching the box and the face might move slightly
|
| 98 |
+
crop = crop_face(pil_img, base_box, padding=0.5)
|
| 99 |
+
if crop is not None:
|
| 100 |
+
faces.append(crop)
|
| 101 |
|
| 102 |
cap.release()
|
| 103 |
|
|
|
|
| 159 |
return {"isFake": False, "confidence": 0,
|
| 160 |
"explanation": "Could not read the video file.", "details": []}
|
| 161 |
|
| 162 |
+
# ββ Extract multiple clips from different parts of the video βββββββββββββββ
|
| 163 |
+
segments = [
|
| 164 |
+
max(0, int(total_frames * 0.2) - (SEQUENCE_LENGTH // 2)),
|
| 165 |
+
max(0, int(total_frames * 0.5) - (SEQUENCE_LENGTH // 2)),
|
| 166 |
+
max(0, int(total_frames * 0.8) - (SEQUENCE_LENGTH // 2))
|
| 167 |
+
]
|
| 168 |
+
|
| 169 |
+
# Ensure unique starting frames if the video is very short
|
| 170 |
+
segments = sorted(list(set(segments)))
|
| 171 |
+
|
| 172 |
+
highest_fake_prob = 0.0
|
| 173 |
+
successful_clips = 0
|
| 174 |
+
|
| 175 |
+
for start_frame in segments:
|
| 176 |
+
print(f"Extracting clip from frame {start_frame}...")
|
| 177 |
+
faces = extract_clip(temp_path, start_frame)
|
| 178 |
+
|
| 179 |
+
if faces:
|
| 180 |
+
prob = run_inference(faces)
|
| 181 |
+
highest_fake_prob = max(highest_fake_prob, prob)
|
| 182 |
+
successful_clips += 1
|
| 183 |
+
|
| 184 |
+
# If we already found very strong evidence of a fake, we can short-circuit to save time
|
| 185 |
+
if highest_fake_prob > 0.95:
|
| 186 |
+
break
|
| 187 |
+
|
| 188 |
+
if successful_clips == 0:
|
| 189 |
return {
|
| 190 |
"isFake": False, "confidence": 0,
|
| 191 |
"explanation": "Could not detect a clear face in the video. Ensure the subject's face is visible.",
|
| 192 |
"details": []
|
| 193 |
}
|
| 194 |
|
|
|
|
|
|
|
|
|
|
| 195 |
# ββ Apply calibrated threshold βββββββββββββββββββββββββββββββββββββββββ
|
| 196 |
# This model scores ~60-75% for real videos, so we require 80%+ to call FAKE.
|
| 197 |
+
is_fake = highest_fake_prob >= FAKE_THRESHOLD
|
| 198 |
+
confidence = round((highest_fake_prob if is_fake else 1.0 - highest_fake_prob) * 100, 2)
|
| 199 |
|
| 200 |
+
print(f"FINAL β fake_prob={highest_fake_prob:.3f}, threshold={FAKE_THRESHOLD}, isFake={is_fake}, confidence={confidence}%")
|
| 201 |
|
| 202 |
explanation = (
|
| 203 |
"Our Temporal AI detected strong evidence of facial manipulation β "
|
|
|
|
| 205 |
"characteristic of deepfake synthesis."
|
| 206 |
if is_fake else
|
| 207 |
"Our Temporal AI found no significant manipulation artifacts. "
|
| 208 |
+
"Facial motion, micro-expressions, and temporal consistency appear natural across the analyzed segments."
|
| 209 |
)
|
| 210 |
|
| 211 |
return {
|
|
|
|
| 214 |
"explanation": explanation,
|
| 215 |
"details": [
|
| 216 |
{
|
| 217 |
+
"title": "Multi-Segment Temporal Analysis",
|
| 218 |
+
"desc": f"Analyzed {successful_clips} separate 16-frame consecutive clips across the video using a VideoMAE 3D Transformer."
|
| 219 |
},
|
| 220 |
{
|
| 221 |
+
"title": "Optimized Face Tracking",
|
| 222 |
+
"desc": "Frames read sequentially with bounding-box caching and downscaled initial detection for extremely fast analysis."
|
| 223 |
},
|
| 224 |
{
|
| 225 |
"title": "Calibrated Threshold",
|
| 226 |
+
"desc": f"Peak raw model score: {highest_fake_prob*100:.1f}%. Detection threshold: {FAKE_THRESHOLD*100:.0f}% (tuned to reduce false positives)."
|
| 227 |
}
|
| 228 |
]
|
| 229 |
}
|