Fs666 / app.py
dosesnrolls1's picture
Update app.py
4db5bbe verified
Raw
History Blame Contribute Delete
8.04 kB
import os
# Speed up Hub downloads a lot (parallel chunked transfer instead of a
# single stream). Must be set before huggingface_hub is imported/used.
os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
import cv2
import gradio as gr
import numpy as np
import insightface
from insightface.app import FaceAnalysis
from huggingface_hub import hf_hub_download
import subprocess
import tempfile
# ---------------------------------------------------------------------------
# Model loading
# ---------------------------------------------------------------------------
# inswapper_128.onnx isn't bundled in this repo (it's a large binary model
# file). We try to fetch it automatically from a couple of known public
# mirrors on the Hugging Face Hub. If that ever stops working (mirrors can
# disappear), just upload `inswapper_128.onnx` yourself to the Space's root
# folder and the app will pick it up automatically. See README.md.
INSWAPPER_MIRRORS = [
("ezioruan/inswapper_128.onnx", "inswapper_128.onnx"),
("Devrim/inswapper_128", "inswapper_128.onnx"),
("countfloyd/deepfake", "inswapper_128.onnx"),
]
LOCAL_MODEL_NAME = "inswapper_128.onnx"
def get_swapper_model_path() -> str:
if os.path.exists(LOCAL_MODEL_NAME):
return LOCAL_MODEL_NAME
last_err = None
for repo_id, filename in INSWAPPER_MIRRORS:
try:
print(f"[model-download] trying {repo_id} ...")
path = hf_hub_download(repo_id=repo_id, filename=filename)
print(f"[model-download] success from {repo_id} -> {path}")
return path
except Exception as e: # noqa: BLE001
print(f"[model-download] failed from {repo_id}: {e}")
last_err = e
raise RuntimeError(
"Could not auto-download inswapper_128.onnx from any mirror. "
"Please upload the file manually to the Space's root directory "
f"(same folder as app.py). Last error: {last_err}"
)
_face_analyser = None
_swapper = None
def get_models():
"""Lazily load and cache the face-analysis and face-swap models."""
global _face_analyser, _swapper
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
if _face_analyser is None:
_face_analyser = FaceAnalysis(name="buffalo_l", providers=providers)
_face_analyser.prepare(ctx_id=0, det_size=(640, 640))
if _swapper is None:
model_path = get_swapper_model_path()
_swapper = insightface.model_zoo.get_model(model_path, providers=providers)
return _face_analyser, _swapper
# ---------------------------------------------------------------------------
# Core face-swap logic
# ---------------------------------------------------------------------------
def get_source_face(face_analyser, image_bgr):
faces = face_analyser.get(image_bgr)
if not faces:
return None
# If several faces are in the source photo, use the largest one.
faces = sorted(
faces,
key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]),
reverse=True,
)
return faces[0]
def swap_face_in_frame(face_analyser, swapper, source_face, frame):
faces = face_analyser.get(frame)
if not faces:
return frame
result = frame
for face in faces:
result = swapper.get(result, face, source_face, paste_back=True)
return result
def merge_audio(original_video_path, silent_video_path, output_path) -> bool:
"""Copy the audio track from the original video onto the swapped video."""
try:
cmd = [
"ffmpeg", "-y",
"-i", silent_video_path,
"-i", original_video_path,
"-c:v", "copy",
"-map", "0:v:0",
"-map", "1:a:0?",
"-c:a", "aac",
"-shortest",
output_path,
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0 or not os.path.exists(output_path):
print("[ffmpeg] audio merge failed:", result.stderr)
return False
return True
except Exception as e: # noqa: BLE001
print("[ffmpeg] audio merge error:", e)
return False
def process_video(source_image, target_video_path, progress=gr.Progress()):
if source_image is None or target_video_path is None:
raise gr.Error("Please upload both a source face image and a target video.")
progress(0, desc="Loading models (first run downloads them, ~1-2 min)...")
face_analyser, swapper = get_models()
source_bgr = cv2.cvtColor(source_image, cv2.COLOR_RGB2BGR)
source_face = get_source_face(face_analyser, source_bgr)
if source_face is None:
raise gr.Error(
"No face was detected in the uploaded image. "
"Try a clearer, front-facing, well-lit photo."
)
cap = cv2.VideoCapture(target_video_path)
if not cap.isOpened():
raise gr.Error("Could not open the uploaded video.")
fps = cap.get(cv2.CAP_PROP_FPS) or 25
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)) or 0
tmp_dir = tempfile.mkdtemp()
silent_path = os.path.join(tmp_dir, "swapped_silent.mp4")
final_path = os.path.join(tmp_dir, "swapped_final.mp4")
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
writer = cv2.VideoWriter(silent_path, fourcc, fps, (width, height))
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
swapped = swap_face_in_frame(face_analyser, swapper, source_face, frame)
writer.write(swapped)
frame_idx += 1
if total_frames:
progress(
min(frame_idx / total_frames, 0.95),
desc=f"Swapping frame {frame_idx}/{total_frames}",
)
else:
progress(0.5, desc=f"Swapping frame {frame_idx}")
cap.release()
writer.release()
progress(0.97, desc="Merging original audio track...")
ok = merge_audio(target_video_path, silent_path, final_path)
result_path = final_path if ok else silent_path
progress(1.0, desc="Done!")
return result_path
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
with gr.Blocks(title="Face Swap: Image → Video") as demo:
gr.Markdown(
"""
# 🎭 Face Swap — Image onto Video
Upload a clear photo of a face and a target video. The face from your
photo will be swapped onto every face detected in the video.
**Please only use this on content you have the rights and consent to edit.**
This tool is for creative and educational use — don't use it to
impersonate real people without their permission.
"""
)
with gr.Row():
with gr.Column():
source_image = gr.Image(label="Source face image", type="numpy")
target_video = gr.Video(label="Target video", sources=["upload"])
run_btn = gr.Button("Swap Face", variant="primary")
with gr.Column():
output_video = gr.Video(label="Result")
run_btn.click(
fn=process_video,
inputs=[source_image, target_video],
outputs=[output_video],
)
gr.Markdown(
"""
---
### Notes
- First run downloads the face-analysis + face-swap models (~300 MB
total), which can take a minute or two.
- This app needs a **GPU Space** (T4 or better) for reasonable speed.
On CPU it will still work but be much slower.
- Every frame of the video is processed, so long videos take a while.
- Output keeps the original video's audio track when possible.
"""
)
if __name__ == "__main__":
demo.queue(max_size=10).launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)),
)