import torch from torch.utils.model_zoo import load_url import numpy as np from flask import Flask, request, jsonify from flask_cors import CORS import matplotlib.pyplot as plt from scipy.special import expit import sys import os import uuid # Add the icpr2020dfdc submodule to the path so we can import its modules # sys.path.append(os.path.join(os.path.dirname(__file__), 'icpr2020dfdc')) from blazeface import FaceExtractor, BlazeFace, VideoReader from architectures import fornet,weights from isplutils import utils # Model and Dataset Configuration net_model = 'EfficientNetAutoAttB4' train_db = 'DFDC' app = Flask(__name__) # CORS is still good to have, though minimal need if only strict backend-to-backend CORS(app) device = torch.device('cpu') face_policy = 'scale' face_size = 224 frames_per_video = 100 model_url = weights.weight_url['{:s}_{:s}'.format(net_model,train_db)] net = getattr(fornet,net_model)().eval().to(device) net.load_state_dict(load_url(model_url,map_location=device,check_hash=True)) transf = utils.get_transformer(face_policy, face_size, net.get_normalizer(), train=False) facedet = BlazeFace().to(device) # Update paths to point to the submodule location facedet.load_weights(os.path.join(os.path.dirname(__file__), "blazeface/blazeface.pth")) facedet.load_anchors(os.path.join(os.path.dirname(__file__), "blazeface/anchors.npy")) @app.route('/predict', methods=['POST']) def predict(): video_path = None try: if 'video' not in request.files: return jsonify({"error": "No video uploaded"}), 400 # Optional: Allow override of frames per video, but default to global current_frames_per_video = frames_per_video if 'frames_per_video' in request.form: current_frames_per_video = int(request.form['frames_per_video']) video = request.files['video'] # 1. Generate unique temp filename (Concurrency fix) unique_id = str(uuid.uuid4()) video_path = f"temp_{unique_id}.mp4" video.save(video_path) # 2. Process the video videoreader = VideoReader(verbose=False) video_read_fn = lambda x: videoreader.read_frames(x, num_frames=current_frames_per_video) face_extractor = FaceExtractor(video_read_fn=video_read_fn, facedet=facedet) vid_face_extractor = face_extractor.process_video(video_path) im_face = torch.stack([transf(image=frame['faces'][0])['image'] for frame in vid_face_extractor if len(frame['faces'])]) with torch.no_grad(): faces_pred = net(im_face.to(device)).cpu().numpy().flatten() mean_score = expit(faces_pred.mean()) faces_pred = expit(faces_pred) return jsonify({ "pred_scores": faces_pred.tolist(), "mean_score": float(mean_score) }), 200 except Exception as e: print("Error:", str(e)) return jsonify({"error": str(e)}), 500 finally: # 3. Cleanup logic (Mandatory) if video_path and os.path.exists(video_path): try: os.remove(video_path) except OSError as cleanup_err: print(f"Error deleting temp file {video_path}: {cleanup_err}") @app.after_request def add_headers(response): response.headers['Content-Type'] = 'application/json' return response if __name__ == '__main__': # Running on port 8080 as expected # Use PORT env var for deployment (default 7860 for HF Spaces), listen on all interfaces port = int(os.environ.get("PORT", 7860)) app.run(host='0.0.0.0', port=port)