deepfake-forensics-api / backend /pipeline /video_processor.py
warlord123456
Deploy to Hugging Face
7bbed67
Raw
History Blame Contribute Delete
5.17 kB
import cv2
import os
import subprocess
import imageio_ffmpeg
MAX_DURATION_SEC = 60
NUM_FRAMES = 16
def process_video(video_path: str, job_id: str):
"""
Extracts up to 16 evenly spaced frames and audio from the video.
If the video is longer than 60 seconds, it will be capped at 60 seconds.
Returns the path to the directory containing the frames and the path to the audio file.
"""
frames_dir = f"uploads/{job_id}_frames"
os.makedirs(frames_dir, exist_ok=True)
# Get reliable ffmpeg path
ffmpeg_exe = imageio_ffmpeg.get_ffmpeg_exe()
audio_path = f"uploads/{job_id}.wav"
# Check if it's an image
file_extension = video_path.split('.')[-1].lower()
if file_extension in ['jpg', 'jpeg', 'png']:
frame_path = os.path.join(frames_dir, "frame_0000.jpg")
img = cv2.imread(video_path)
if img is not None:
cv2.imwrite(frame_path, img)
return frames_dir, None
else:
raise ValueError("Could not read image file.")
# Get video duration
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames == 0 or fps == 0:
cap.release()
raise ValueError("Video has no frames or could not be read.")
duration = total_frames / fps
if duration > MAX_DURATION_SEC:
duration = MAX_DURATION_SEC
total_frames = int(duration * fps)
cap.release()
# 1. Extract Audio using FFmpeg directly (much faster than moviepy)
try:
subprocess.run(
[ffmpeg_exe, '-y', '-i', video_path, '-t', str(MAX_DURATION_SEC), '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', audio_path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True
)
if not os.path.exists(audio_path) or os.path.getsize(audio_path) == 0:
audio_path = None
except Exception as e:
# Ffmpeg returns an error if the video has no audio track. We can safely ignore this.
audio_path = None # Audio extraction failed or video is silent
# 2. Extract 16 Frames using PySceneDetect & FFmpeg
try:
from scenedetect import detect, ContentDetector
print(f"Running Scene Detection on {video_path}...")
scene_list = detect(video_path, ContentDetector(threshold=27.0))
frame_indices = []
if scene_list and len(scene_list) > 0:
frames_per_scene = max(1, NUM_FRAMES // len(scene_list))
for scene in scene_list:
start_frame = scene[0].get_frames()
end_frame = scene[1].get_frames()
if start_frame >= total_frames:
break
end_frame = min(end_frame, total_frames)
step = max(1, (end_frame - start_frame) // (frames_per_scene + 1))
for i in range(1, frames_per_scene + 1):
idx = start_frame + (step * i)
if idx < total_frames and len(frame_indices) < NUM_FRAMES:
frame_indices.append(idx)
if len(frame_indices) < NUM_FRAMES:
needed = NUM_FRAMES - len(frame_indices)
uniform_indices = [int(i * total_frames / needed) for i in range(needed)]
frame_indices.extend(uniform_indices)
else:
frame_indices = [int(i * total_frames / NUM_FRAMES) for i in range(NUM_FRAMES)]
frame_indices = sorted(list(set(frame_indices)))[:NUM_FRAMES]
except ImportError:
print("Warning: scenedetect not installed. Falling back to uniform frame extraction.")
frame_indices = [int(i * total_frames / NUM_FRAMES) for i in range(NUM_FRAMES)]
extracted_count = 0
# Fast extraction using ffmpeg with select filter
if frame_indices:
try:
# Create a complex filter string to select specific frames, escaping the comma for ffmpeg
select_expr = '+'.join([rf"eq(n\,{idx})" for idx in frame_indices])
output_pattern = os.path.join(frames_dir, "frame_%04d.jpg")
subprocess.run(
[ffmpeg_exe, '-y', '-i', video_path, '-vf', f"select={select_expr}", '-vsync', '0', '-q:v', '2', output_pattern],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True
)
except Exception as e:
print(f"Error extracting frames with ffmpeg: {e}")
# Fallback to OpenCV if ffmpeg fails
cap = cv2.VideoCapture(video_path)
for idx in frame_indices:
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret, frame = cap.read()
if ret:
frame_path = os.path.join(frames_dir, f"frame_{extracted_count:04d}.jpg")
cv2.imwrite(frame_path, frame)
extracted_count += 1
cap.release()
return frames_dir, audio_path