dosesnrolls1 commited on
Commit
242c739
·
verified ·
1 Parent(s): fdcf569

Upload 6 files

Browse files
Files changed (6) hide show
  1. .gitignore +3 -0
  2. Dockerfile +17 -0
  3. README.md +27 -8
  4. app.py +154 -0
  5. packages.txt +3 -0
  6. requirements.txt +6 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ tmp/
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1 PIP_NO_CACHE_DIR=1 HOME=/home/user PATH=/home/user/.local/bin:$PATH
4
+
5
+ RUN apt-get update && apt-get install -y python3 python3-pip ffmpeg libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*
6
+
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+ WORKDIR /home/user/app
10
+
11
+ COPY --chown=user requirements.txt ./requirements.txt
12
+ RUN python3 -m pip install --upgrade pip && python3 -m pip install -r requirements.txt
13
+
14
+ COPY --chown=user . /home/user/app
15
+
16
+ EXPOSE 7860
17
+ CMD ["python3", "app.py"]
README.md CHANGED
@@ -1,12 +1,31 @@
1
  ---
2
- title: Fswap
3
- emoji: 🌖
4
- colorFrom: pink
5
- colorTo: pink
6
  sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: Swap
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Video FaceSwap GPU
3
+ emoji: 🎭
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
+ app_port: 7860
8
+ suggested_hardware: a10g-small
9
+ startup_duration_timeout: 1h
10
  ---
11
 
12
+ # Video FaceSwap GPU
13
+
14
+ Docker Space for swapping a face from an uploaded image onto a target video.
15
+
16
+ ## Setup
17
+
18
+ - Create a new Space with Docker SDK.
19
+ - Add the files from this repo.
20
+ - Set hardware to a paid GPU such as A10G small.
21
+ - Add `HF_TOKEN` if your model repo is gated or private.
22
+
23
+ ## Environment variables
24
+
25
+ - `MODEL_REPO_ID`: model repo ID, default `ezioruan/inswapper_128.onnx`
26
+ - `MODEL_FILENAME`: model filename, default `inswapper_128.onnx`
27
+ - `MODEL_REVISION`: optional branch, tag, or commit hash
28
+ - `HF_TOKEN`: optional token for gated/private repos
29
+ - `FACE_MODEL_NAME`: InsightFace detector name, default `buffalo_l`
30
+ - `DETECTION_SIZE`: detector input size, default `640`
31
+ - `MAX_FRAMES`: optional frame cap for testing, default `0`
app.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import tempfile
5
+ from pathlib import Path
6
+
7
+ import cv2
8
+ import gradio as gr
9
+ from huggingface_hub import hf_hub_download
10
+ from insightface.app import FaceAnalysis
11
+ from insightface.model_zoo import get_model
12
+
13
+ APP_DIR = Path(__file__).resolve().parent
14
+ MODELS_DIR = APP_DIR / "models"
15
+ TMP_DIR = APP_DIR / "tmp"
16
+ MODELS_DIR.mkdir(exist_ok=True)
17
+ TMP_DIR.mkdir(exist_ok=True)
18
+
19
+ MODEL_REPO_ID = os.environ.get("MODEL_REPO_ID", "ezioruan/inswapper_128.onnx")
20
+ MODEL_FILENAME = os.environ.get("MODEL_FILENAME", "inswapper_128.onnx")
21
+ MODEL_REVISION = os.environ.get("MODEL_REVISION")
22
+ HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN")
23
+ FACE_MODEL_NAME = os.environ.get("FACE_MODEL_NAME", "buffalo_l")
24
+ DETECTION_SIZE = int(os.environ.get("DETECTION_SIZE", "640"))
25
+ MAX_FRAMES = int(os.environ.get("MAX_FRAMES", "0"))
26
+
27
+ face_analyzer = None
28
+ face_swapper = None
29
+ model_path = None
30
+
31
+
32
+ def ensure_ffmpeg():
33
+ if shutil.which("ffmpeg") is None:
34
+ raise RuntimeError("ffmpeg is required but not available in PATH.")
35
+
36
+
37
+ def get_execution_providers():
38
+ return ["CUDAExecutionProvider", "CPUExecutionProvider"]
39
+
40
+
41
+ def ensure_swap_model() -> str:
42
+ global model_path
43
+ if model_path and os.path.exists(model_path):
44
+ return model_path
45
+ local_target = MODELS_DIR / MODEL_FILENAME
46
+ if local_target.exists():
47
+ model_path = str(local_target)
48
+ return model_path
49
+ downloaded = hf_hub_download(
50
+ repo_id=MODEL_REPO_ID,
51
+ filename=MODEL_FILENAME,
52
+ revision=MODEL_REVISION,
53
+ token=HF_TOKEN,
54
+ local_dir=str(MODELS_DIR),
55
+ )
56
+ model_path = downloaded
57
+ return model_path
58
+
59
+
60
+ def load_models():
61
+ global face_analyzer, face_swapper
62
+ if face_analyzer is not None and face_swapper is not None:
63
+ return face_analyzer, face_swapper
64
+ swap_model_path = ensure_swap_model()
65
+ face_analyzer = FaceAnalysis(name=FACE_MODEL_NAME, providers=get_execution_providers())
66
+ face_analyzer.prepare(ctx_id=0, det_size=(DETECTION_SIZE, DETECTION_SIZE))
67
+ face_swapper = get_model(swap_model_path, providers=get_execution_providers())
68
+ return face_analyzer, face_swapper
69
+
70
+
71
+ def pick_largest_face(faces):
72
+ if not faces:
73
+ return None
74
+ return max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))
75
+
76
+
77
+ def extract_audio(input_video: str, audio_path: str):
78
+ subprocess.run(["ffmpeg", "-y", "-i", input_video, "-vn", "-acodec", "copy", audio_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
79
+
80
+
81
+ def mux_audio(video_path: str, audio_path: str, output_path: str):
82
+ if not os.path.exists(audio_path):
83
+ shutil.move(video_path, output_path)
84
+ return
85
+ subprocess.run(["ffmpeg", "-y", "-i", video_path, "-i", audio_path, "-c:v", "copy", "-c:a", "aac", "-shortest", output_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
86
+ os.remove(video_path)
87
+
88
+
89
+ def swap_frame(frame_bgr, source_face, analyzer, swapper):
90
+ target_faces = analyzer.get(frame_bgr)
91
+ if not target_faces:
92
+ return frame_bgr
93
+ result = frame_bgr.copy()
94
+ for target_face in target_faces:
95
+ result = swapper.get(result, target_face, source_face, paste_back=True)
96
+ return result
97
+
98
+
99
+ def process_video(face_image_path, video_path, progress=gr.Progress(track_tqdm=False)):
100
+ ensure_ffmpeg()
101
+ analyzer, swapper = load_models()
102
+ src = cv2.imread(face_image_path)
103
+ if src is None:
104
+ raise ValueError("Could not read the source face image.")
105
+ source_face = pick_largest_face(analyzer.get(src))
106
+ if source_face is None:
107
+ raise ValueError("No source face detected in the uploaded image.")
108
+
109
+ work_dir = Path(tempfile.mkdtemp(dir=TMP_DIR))
110
+ silent_video = str(work_dir / "silent.mp4")
111
+ audio_file = str(work_dir / "audio.aac")
112
+ final_video = str(work_dir / "faceswapped.mp4")
113
+
114
+ cap = cv2.VideoCapture(video_path)
115
+ if not cap.isOpened():
116
+ raise ValueError("Could not open the uploaded video.")
117
+
118
+ fps = cap.get(cv2.CAP_PROP_FPS) or 24.0
119
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
120
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
121
+ frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) or 1
122
+ limit = min(frame_count, MAX_FRAMES) if MAX_FRAMES > 0 else frame_count
123
+
124
+ writer = cv2.VideoWriter(silent_video, cv2.VideoWriter_fourcc(*"mp4v"), fps, (width, height))
125
+ extract_audio(video_path, audio_file)
126
+
127
+ idx = 0
128
+ while idx < limit:
129
+ ok, frame = cap.read()
130
+ if not ok:
131
+ break
132
+ writer.write(swap_frame(frame, source_face, analyzer, swapper))
133
+ idx += 1
134
+ progress(idx / max(limit, 1), desc=f"Processed {idx}/{limit} frames")
135
+
136
+ cap.release()
137
+ writer.release()
138
+ mux_audio(silent_video, audio_file, final_video)
139
+ return final_video, f"Done. Processed {idx} frame(s)."
140
+
141
+
142
+ with gr.Blocks(theme=gr.themes.Soft(), title="Video FaceSwap GPU") as demo:
143
+ gr.Markdown("# Video FaceSwap GPU
144
+ Upload a source face image and a target video. The app downloads the swap model from the Hugging Face Hub at runtime, then swaps the main source face onto detected faces in the video.")
145
+ with gr.Row():
146
+ face_image = gr.Image(type="filepath", label="Source face image")
147
+ target_video = gr.Video(label="Target video")
148
+ run_btn = gr.Button("Run face swap", variant="primary")
149
+ output_video = gr.Video(label="Output video")
150
+ status = gr.Textbox(label="Status")
151
+ run_btn.click(fn=process_video, inputs=[face_image, target_video], outputs=[output_video, status], api_name="faceswap_video")
152
+
153
+ if __name__ == "__main__":
154
+ demo.queue(max_size=8).launch(server_name="0.0.0.0", server_port=7860)
packages.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ffmpeg
2
+ libgl1
3
+ libglib2.0-0
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=5.0.0
2
+ huggingface_hub>=0.32.0
3
+ insightface
4
+ numpy<2
5
+ onnxruntime-gpu
6
+ opencv-python-headless