| |
| 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') |
|
|
| |
| app = None |
| swapper = None |
|
|
| def initialize_models(): |
| """Initialize face detection and swapping models""" |
| global app, swapper |
| |
| try: |
| |
| app = FaceAnalysis(name='buffalo_l', providers=['CPUExecutionProvider']) |
| app.prepare(ctx_id=-1, det_size=(320, 320)) |
| |
| |
| 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...") |
| |
| |
| 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...") |
| |
| |
| temp_output = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') |
| temp_output_path = temp_output.name |
| temp_output.close() |
| |
| |
| 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)) |
| |
| |
| 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: |
| |
| frame_faces = app.get(frame) |
| |
| if len(frame_faces) > face_index: |
| |
| result_frame = swapper.get(frame, frame_faces[face_index], source_face, paste_back=True) |
| out.write(result_frame) |
| else: |
| |
| out.write(frame) |
| |
| except: |
| |
| out.write(frame) |
| |
| frame_count += 1 |
| |
| |
| 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...") |
| |
| |
| 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: |
| |
| os.rename(temp_output_path, final_output_path) |
| |
| video_clip.close() |
| |
| except Exception as audio_error: |
| |
| os.rename(temp_output_path, final_output_path) |
| |
| |
| 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""" |
| |
| |
| 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: |
| |
| |
| gr.HTML(""" |
| <div class="title">π AI Face Swap Studio</div> |
| <div class="subtitle">Upload a face image and a video to swap faces using AI</div> |
| """) |
| |
| |
| with gr.Row(): |
| init_btn = gr.Button("π Initialize AI Models", variant="primary", size="lg") |
| init_status = gr.Textbox(label="Status", value="Click to initialize models", interactive=False) |
| |
| init_btn.click(initialize_models, outputs=init_status) |
| |
| gr.Markdown("---") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| gr.Markdown("### π· Source Face Image") |
| source_img = gr.Image( |
| label="Upload the face you want to use", |
| type="numpy", |
| height=300 |
| ) |
| |
| |
| detect_btn = gr.Button("π Detect Faces", size="sm") |
| face_detection_result = gr.Textbox( |
| label="Face Detection Result", |
| interactive=False |
| ) |
| |
| with gr.Column(scale=1): |
| gr.Markdown("### π¬ Target Video") |
| target_vid = gr.Video( |
| label="Upload the video where you want to swap faces", |
| height=300 |
| ) |
| |
| with gr.Row(): |
| gr.Markdown("### βοΈ Settings") |
| |
| with gr.Row(): |
| face_index = gr.Slider( |
| minimum=0, |
| maximum=5, |
| value=0, |
| step=1, |
| label="Face Index (which face to swap if multiple faces)", |
| info="0 = first face, 1 = second face, etc." |
| ) |
| |
| |
| with gr.Row(): |
| process_btn = gr.Button("π Start Face Swap", variant="primary", size="lg") |
| |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| output_video = gr.Video( |
| label="π Result Video", |
| height=400 |
| ) |
| with gr.Column(scale=1): |
| output_status = gr.Textbox( |
| label="Processing Status", |
| lines=5, |
| interactive=False |
| ) |
| |
| |
| gr.Markdown("---") |
| gr.Markdown("### π‘ Tips for Best Results") |
| gr.Markdown(""" |
| - **Source Image**: Use a clear, front-facing photo with good lighting |
| - **Video Quality**: Higher resolution videos give better results |
| - **Face Visibility**: Make sure faces are clearly visible in both image and video |
| - **Processing Time**: Longer videos will take more time to process |
| - **Multiple Faces**: Use the face index slider to choose which face to swap |
| """) |
| |
| gr.Markdown("### π¨ Important Notes") |
| gr.Markdown(""" |
| - This tool is for entertainment and educational purposes only |
| - Please respect privacy and obtain consent before using someone's likeness |
| - Processing may take several minutes depending on video length |
| - Maximum video length recommended: 30 seconds for faster processing |
| """) |
| |
| |
| detect_btn.click( |
| detect_faces_in_image, |
| inputs=source_img, |
| outputs=face_detection_result |
| ) |
| |
| process_btn.click( |
| swap_faces_in_video, |
| inputs=[source_img, target_vid, face_index], |
| outputs=[output_video, output_status] |
| ) |
| |
| return demo |
|
|
| |
| if __name__ == "__main__": |
| demo = create_interface() |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=True |
| ) |