# app.py - Main application file for Hugging Face Spaces import gradio as gr import cv2 import numpy as np import insightface from insightface.app import FaceAnalysis import tempfile import os from moviepy.editor import VideoFileClip import warnings warnings.filterwarnings('ignore') # Global variables for models app = None swapper = None def initialize_models(): """Initialize face detection and swapping models""" global app, swapper try: # Initialize face analysis app = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider']) app.prepare(ctx_id=-1, det_size=(320, 320)) # Download and load face swapper model model_path = "inswapper_128.onnx" if not os.path.exists(model_path): import wget print("Downloading face swap model...") wget.download("https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx", model_path) swapper = insightface.model_zoo.get_model(model_path, download=False, download_zip=False) return "✅ Models loaded successfully!" except Exception as e: return f"❌ Error loading models: {str(e)}" def detect_faces_in_image(image): """Detect and return number of faces in image""" if image is None: return "No image provided" try: faces = app.get(image) return f"✅ Detected {len(faces)} face(s) in the image" except Exception as e: return f"❌ Error detecting faces: {str(e)}" def swap_faces_in_video(source_image, target_video, face_index=0, progress=gr.Progress()): """Main face swapping function""" if source_image is None or target_video is None: return None, "❌ Please provide both source image and target video" try: progress(0.1, desc="Analyzing source image...") # Extract face from source image faces = app.get(source_image) if len(faces) == 0: return None, "❌ No face detected in source image. Please use a clear photo with a visible face." source_face = faces[0] progress(0.2, desc="Loading video...") # Create temporary files temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') temp_output_path = temp_output.name temp_output.close() # Process video cap = cv2.VideoCapture(target_video) fps = int(cap.get(cv2.CAP_PROP_FPS)) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Setup video writer fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(temp_output_path, fourcc, fps, (width, height)) progress(0.3, desc="Processing frames...") frame_count = 0 while True: ret, frame = cap.read() if not ret: break try: # Detect faces in frame frame_faces = app.get(frame) if len(frame_faces) > face_index: # Swap face result_frame = swapper.get(frame, frame_faces[face_index], source_face, paste_back=True) out.write(result_frame) else: # No face to swap, use original frame out.write(frame) except: # If frame processing fails, use original out.write(frame) frame_count += 1 # Update progress if frame_count % 10 == 0: progress_val = 0.3 + (frame_count / total_frames) * 0.6 progress(progress_val, desc=f"Processing frame {frame_count}/{total_frames}") cap.release() out.release() progress(0.9, desc="Adding audio...") # Add audio back to video final_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') final_output_path = final_output.name final_output.close() try: video_clip = VideoFileClip(temp_output_path) audio_clip = VideoFileClip(target_video).audio if audio_clip is not None: final_clip = video_clip.set_audio(audio_clip) final_clip.write_videofile(final_output_path, codec='libx264', audio_codec='aac', verbose=False, logger=None) final_clip.close() else: # No audio in original os.rename(temp_output_path, final_output_path) video_clip.close() except Exception as audio_error: # If audio processing fails, return video without audio os.rename(temp_output_path, final_output_path) # Cleanup if os.path.exists(temp_output_path): os.remove(temp_output_path) progress(1.0, desc="Complete!") return final_output_path, "✅ Face swap completed successfully!" except Exception as e: return None, f"❌ Error during face swap: {str(e)}" def create_interface(): """Create the Gradio interface""" # Custom CSS for better styling css = """ .gradio-container { max-width: 1200px; margin: auto; } .title { text-align: center; font-size: 2.5em; font-weight: bold; margin-bottom: 1em; background: linear-gradient(45deg, #ff6b6b, #4ecdc4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .subtitle { text-align: center; font-size: 1.2em; color: #666; margin-bottom: 2em; } """ with gr.Blocks(css=css, title="AI Face Swap Studio") as demo: # Title and description gr.HTML("""