Arrcttacsrks commited on
Commit
be56d2f
·
verified ·
1 Parent(s): 6fdb4ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +329 -181
app.py CHANGED
@@ -1,37 +1,59 @@
1
- # -*- coding: UTF-8 -*-
2
  #!/usr/bin/env python
 
3
 
4
  import os
5
  import json
6
  import shutil
 
 
7
  from datetime import datetime
8
- from dotenv import load_dotenv
 
9
  import numpy as np
10
  import cv2
11
  from PIL import Image
12
  import gradio as gr
 
13
  from huggingface_hub import HfApi, login
14
- from roop.globals import (
 
 
 
15
  start,
16
  decode_execution_providers,
17
  suggest_max_memory,
18
  suggest_execution_threads,
19
  )
20
- from roop.core import normalize_output_path
21
  from roop.processors.frame.core import get_frame_processors_modules
22
- from insightface.app import FaceAnalysis
 
 
 
23
 
24
  # Load environment variables
25
  load_dotenv()
26
 
27
- # Cosine similarity function
28
- def cosine_similarity(a, b):
 
 
 
 
 
 
 
 
 
 
29
  return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-6)
30
 
31
- # Dataset handler class
32
  class FaceIntegrDataset:
33
- def __init__(self, repo_id="Arrcttacsrks/face_integrData"):
34
- self.token = os.getenv("hf_token")
 
 
 
35
  if not self.token:
36
  raise ValueError("HF_TOKEN environment variable is not set")
37
  self.repo_id = repo_id
@@ -40,208 +62,287 @@ class FaceIntegrDataset:
40
  self.temp_dir = "temp_dataset"
41
  os.makedirs(self.temp_dir, exist_ok=True)
42
 
43
- def create_date_folder(self):
 
 
 
 
 
 
44
  current_date = datetime.now().strftime("%Y-%m-%d")
45
  folder_path = os.path.join(self.temp_dir, current_date)
46
  os.makedirs(folder_path, exist_ok=True)
47
  return folder_path, current_date
48
 
49
- def save_metadata(self, source_path, target_path, output_path, timestamp):
 
 
 
 
 
 
 
 
 
 
 
 
50
  metadata = {
51
  "timestamp": timestamp,
52
  "source_image": source_path,
53
  "target_image": target_path,
54
  "output_image": output_path,
55
- "date_created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
56
  }
57
  return metadata
58
 
59
- def upload_to_hf(self, local_folder, date_folder):
 
 
 
 
 
 
 
 
 
 
60
  try:
61
  self.api.upload_folder(
62
  folder_path=local_folder,
63
  repo_id=self.repo_id,
64
  repo_type="dataset",
65
- path_in_repo=date_folder,
66
  )
 
67
  return True
68
  except Exception as e:
69
- print(f"Error uploading to Hugging Face: {str(e)}")
70
  return False
71
 
72
- # Image face swap function
73
- def swap_face(source_file, target_file, doFaceEnhancer):
74
- dataset_handler = FaceIntegrDataset()
75
- folder_path, date_folder = dataset_handler.create_date_folder()
76
- timestamp = datetime.now().strftime("%S-%M-%H-%d-%m-%Y")
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  try:
79
- # Save source and target images
 
 
80
  source_path = os.path.join(folder_path, f"source_{timestamp}.jpg")
81
  target_path = os.path.join(folder_path, f"target_{timestamp}.jpg")
82
- output_path = os.path.join(folder_path, f"OutputImage_{timestamp}.jpg")
83
 
84
  if source_file is None or target_file is None:
85
  raise ValueError("Source and target images are required")
86
-
87
  Image.fromarray(source_file).save(source_path)
88
  Image.fromarray(target_file).save(target_path)
89
-
90
- # Configure Roop globals
91
- roop.globals.source_path = source_path
92
- roop.globals.target_path = target_path
93
- roop.globals.output_path = normalize_output_path(
94
- roop.globals.source_path, roop.globals.target_path, output_path
95
- )
96
-
97
- roop.globals.frame_processors = (
98
- ["face_swapper", "face_enhancer"] if doFaceEnhancer else ["face_swapper"]
99
- )
100
- roop.globals.headless = True
101
- roop.globals.keep_fps = True
102
- roop.globals.keep_audio = True
103
- roop.globals.keep_frames = False
104
- roop.globals.many_faces = False
105
- roop.globals.video_encoder = "libx264"
106
- roop.globals.video_quality = 18
107
- roop.globals.max_memory = suggest_max_memory()
108
- roop.globals.execution_providers = decode_execution_providers(["cuda"])
109
- roop.globals.execution_threads = suggest_execution_threads()
110
-
111
  # Pre-check frame processors
112
  for frame_processor in get_frame_processors_modules(roop.globals.frame_processors):
113
  if not frame_processor.pre_check():
114
- raise RuntimeError("Frame processor pre-check failed")
115
-
116
- # Start face swap process
 
117
  start()
118
-
119
- # Save metadata
120
  metadata = dataset_handler.save_metadata(
121
- f"source_{timestamp}.jpg",
122
- f"target_{timestamp}.jpg",
123
- f"OutputImage_{timestamp}.jpg",
124
- timestamp,
125
  )
 
126
  metadata_path = os.path.join(folder_path, f"metadata_{timestamp}.json")
127
- with open(metadata_path, "w") as f:
128
  json.dump(metadata, f, indent=4)
129
-
130
- # Upload to Hugging Face
131
  upload_success = dataset_handler.upload_to_hf(folder_path, date_folder)
132
- if not upload_success:
133
- print("Failed to upload files to Hugging Face dataset")
134
-
135
- # Return output image
136
- if os.path.exists(output_path):
137
- output_image = Image.open(output_path)
138
- return np.array(output_image)
139
  else:
140
- raise FileNotFoundError("Output image not found")
141
-
 
 
 
 
 
 
 
 
 
 
142
  except Exception as e:
143
- print(f"Error in face swap process: {str(e)}")
144
- raise gr.Error(f"Face swap failed: {str(e)}")
145
- finally:
146
  if folder_path and os.path.exists(folder_path):
147
- shutil.rmtree(folder_path)
 
 
148
 
149
- # Video face swap helper function
150
- def swap_face_frame(frame_bgr, replacement_face_rgb, doFaceEnhancer):
 
 
 
 
 
 
 
 
 
 
 
151
  frame_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
152
- temp_dir = "temp_faceswap_frame"
153
- os.makedirs(temp_dir, exist_ok=True)
154
  timestamp = datetime.now().strftime("%S-%M-%H-%d-%m-%Y")
155
-
 
 
 
156
  try:
157
- source_path = os.path.join(temp_dir, f"source_{timestamp}.jpg")
158
- target_path = os.path.join(temp_dir, f"target_{timestamp}.jpg")
159
- output_path = os.path.join(temp_dir, f"OutputImage_{timestamp}.jpg")
160
-
161
  Image.fromarray(frame_rgb).save(source_path)
162
  Image.fromarray(replacement_face_rgb).save(target_path)
163
-
164
- # Configure Roop globals
165
- roop.globals.source_path = source_path
166
- roop.globals.target_path = target_path
167
- roop.globals.output_path = normalize_output_path(
168
- source_path, target_path, output_path
169
- )
170
- roop.globals.frame_processors = (
171
- ["face_swapper", "face_enhancer"] if doFaceEnhancer else ["face_swapper"]
172
- )
173
- roop.globals.headless = True
174
- roop.globals.keep_fps = True
175
- roop.globals.keep_audio = True
176
- roop.globals.keep_frames = False
177
- roop.globals.many_faces = False
178
- roop.globals.video_encoder = "libx264"
179
- roop.globals.video_quality = 18
180
- roop.globals.max_memory = suggest_max_memory()
181
- roop.globals.execution_providers = decode_execution_providers(["cuda"])
182
- roop.globals.execution_threads = suggest_execution_threads()
183
-
184
  start()
185
-
186
- # Return swapped frame
187
- if os.path.exists(output_path):
188
- return np.array(Image.open(output_path))
189
  else:
190
- return frame_rgb
 
 
 
 
191
  finally:
192
- shutil.rmtree(temp_dir)
193
-
194
- # Video face swap function
195
- def swap_face_video(reference_face, replacement_face, video_input, similarity_threshold, doFaceEnhancer):
196
- fa = FaceAnalysis()
197
- fa.prepare(ctx_id=0)
198
 
199
- ref_detections = fa.get(reference_face)
200
- if not ref_detections:
201
- raise gr.Error("No face detected in the reference image!")
202
- ref_embedding = ref_detections[0].embedding
203
 
204
- cap = cv2.VideoCapture(video_input)
205
- if not cap.isOpened():
206
- raise gr.Error("Failed to open input video!")
207
-
208
- fps = cap.get(cv2.CAP_PROP_FPS)
209
- width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
210
- height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
211
-
212
- output_video_path = "temp_faceswap_video.mp4"
213
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
214
- out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
215
-
216
- frame_index = 0
217
- while True:
218
- ret, frame = cap.read()
219
- if not ret:
220
- break
221
-
222
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
223
- detections = fa.get(frame_rgb)
224
- swap_this_frame = any(
225
- cosine_similarity(det.embedding, ref_embedding) >= similarity_threshold
226
- for det in detections
227
- )
228
-
229
- if swap_this_frame:
230
- swapped_frame_rgb = swap_face_frame(frame, replacement_face, doFaceEnhancer)
231
- swapped_frame = cv2.cvtColor(swapped_frame_rgb, cv2.COLOR_RGB2BGR)
232
- else:
233
- swapped_frame = frame
234
-
235
- out.write(swapped_frame)
236
- frame_index += 1
237
- print(f"Processed frame {frame_index}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- cap.release()
240
- out.release()
241
- return output_video_path
242
 
243
- # Gradio interface
244
- def create_interface():
 
 
 
 
 
245
  custom_css = """
246
  .container {
247
  max-width: 1200px;
@@ -262,55 +363,102 @@ def create_interface():
262
  <p>This tool performs face swapping with optional enhancement.</p>
263
  </div>
264
  """
265
-
266
  with gr.Blocks(title=title, css=custom_css) as app:
267
  gr.Markdown(f"<h1 style='text-align: center;'>{title}</h1>")
268
  gr.Markdown(description)
269
-
270
  with gr.Tabs():
271
  with gr.TabItem("FaceSwap Image"):
272
  with gr.Row():
273
- source_image = gr.Image(label="Source Image", type="numpy", sources=["upload"])
274
- target_image = gr.Image(label="Target Image", type="numpy", sources=["upload"])
275
- output_image = gr.Image(label="Output Image", type="numpy", interactive=False, elem_classes="output-image")
276
-
277
- enhance_checkbox = gr.Checkbox(label="Apply Enhancement?", info="Improve image quality", value=False)
278
- process_btn = gr.Button("Process Face Swap", variant="primary", size="lg")
279
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  process_btn.click(
281
  fn=swap_face,
282
  inputs=[source_image, target_image, enhance_checkbox],
283
  outputs=output_image,
284
- api_name="swap_face",
285
  )
286
-
287
  with gr.TabItem("FaceSwap Video"):
288
  gr.Markdown("<h2 style='text-align:center;'>FaceSwap Video</h2>")
289
  with gr.Row():
290
- ref_image = gr.Image(label="Reference Face", type="numpy", sources=["upload"])
291
- swap_image = gr.Image(label="Replacement Face", type="numpy", sources=["upload"])
292
- video_input = gr.Video(label="Input Video")
293
- similarity_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.7, label="Similarity Threshold")
294
- enhance_checkbox_video = gr.Checkbox(label="Apply Enhancement?", info="Improve image quality", value=False)
295
- video_output = gr.Video(label="Output Video")
296
-
297
- process_video_btn = gr.Button("Process FaceSwap Video", variant="primary", size="lg")
298
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  process_video_btn.click(
300
  fn=swap_face_video,
301
  inputs=[ref_image, swap_image, video_input, similarity_threshold, enhance_checkbox_video],
302
  outputs=video_output,
303
- api_name="swap_face_video",
304
  )
305
-
306
  gr.Markdown(article)
307
-
308
  return app
309
 
310
- # Main function
311
- def main():
 
 
 
312
  app = create_interface()
313
  app.launch(share=False)
314
 
 
315
  if __name__ == "__main__":
316
- main()
 
 
1
  #!/usr/bin/env python
2
+ # -*- coding: UTF-8 -*-
3
 
4
  import os
5
  import json
6
  import shutil
7
+ import logging
8
+ import tempfile
9
  from datetime import datetime
10
+ from typing import Tuple, Optional
11
+
12
  import numpy as np
13
  import cv2
14
  from PIL import Image
15
  import gradio as gr
16
+ from dotenv import load_dotenv
17
  from huggingface_hub import HfApi, login
18
+ from insightface.app import FaceAnalysis
19
+
20
+ import roop.globals
21
+ from roop.core import (
22
  start,
23
  decode_execution_providers,
24
  suggest_max_memory,
25
  suggest_execution_threads,
26
  )
 
27
  from roop.processors.frame.core import get_frame_processors_modules
28
+ from roop.utilities import normalize_output_path
29
+
30
+ # Configure logging
31
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
32
 
33
  # Load environment variables
34
  load_dotenv()
35
 
36
+
37
+ def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
38
+ """
39
+ Calculate the cosine similarity between two vectors.
40
+
41
+ Parameters:
42
+ a (np.ndarray): First vector.
43
+ b (np.ndarray): Second vector.
44
+
45
+ Returns:
46
+ float: Cosine similarity.
47
+ """
48
  return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-6)
49
 
50
+
51
  class FaceIntegrDataset:
52
+ """
53
+ Handler for face integration dataset upload to Hugging Face.
54
+ """
55
+ def __init__(self, repo_id: str = "Arrcttacsrks/face_integrData") -> None:
56
+ self.token = os.getenv('hf_token')
57
  if not self.token:
58
  raise ValueError("HF_TOKEN environment variable is not set")
59
  self.repo_id = repo_id
 
62
  self.temp_dir = "temp_dataset"
63
  os.makedirs(self.temp_dir, exist_ok=True)
64
 
65
+ def create_date_folder(self) -> Tuple[str, str]:
66
+ """
67
+ Create a folder based on the current date inside the temporary directory.
68
+
69
+ Returns:
70
+ Tuple[str, str]: The folder path and the current date string.
71
+ """
72
  current_date = datetime.now().strftime("%Y-%m-%d")
73
  folder_path = os.path.join(self.temp_dir, current_date)
74
  os.makedirs(folder_path, exist_ok=True)
75
  return folder_path, current_date
76
 
77
+ def save_metadata(self, source_path: str, target_path: str, output_path: str, timestamp: str) -> dict:
78
+ """
79
+ Create metadata dictionary for the face swap process.
80
+
81
+ Parameters:
82
+ source_path (str): Filename of the source image.
83
+ target_path (str): Filename of the target image.
84
+ output_path (str): Filename of the output image.
85
+ timestamp (str): Timestamp string.
86
+
87
+ Returns:
88
+ dict: Metadata information.
89
+ """
90
  metadata = {
91
  "timestamp": timestamp,
92
  "source_image": source_path,
93
  "target_image": target_path,
94
  "output_image": output_path,
95
+ "date_created": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
96
  }
97
  return metadata
98
 
99
+ def upload_to_hf(self, local_folder: str, date_folder: str) -> bool:
100
+ """
101
+ Upload a local folder to the Hugging Face dataset repository.
102
+
103
+ Parameters:
104
+ local_folder (str): The local folder path.
105
+ date_folder (str): The subfolder in the repository.
106
+
107
+ Returns:
108
+ bool: True if upload is successful, False otherwise.
109
+ """
110
  try:
111
  self.api.upload_folder(
112
  folder_path=local_folder,
113
  repo_id=self.repo_id,
114
  repo_type="dataset",
115
+ path_in_repo=date_folder
116
  )
117
+ logging.info("Successfully uploaded files to Hugging Face repository.")
118
  return True
119
  except Exception as e:
120
+ logging.error(f"Error uploading to Hugging Face: {str(e)}")
121
  return False
122
 
 
 
 
 
 
123
 
124
+ def configure_roop_globals(source_path: str, target_path: str, output_path: str, do_face_enhancer: bool) -> None:
125
+ """
126
+ Configure global variables required for the face swap process.
127
+
128
+ Parameters:
129
+ source_path (str): Path to the source image.
130
+ target_path (str): Path to the target image.
131
+ output_path (str): Path to save the output image.
132
+ do_face_enhancer (bool): Flag to determine if face enhancer should be used.
133
+ """
134
+ roop.globals.source_path = source_path
135
+ roop.globals.target_path = target_path
136
+ roop.globals.output_path = normalize_output_path(source_path, target_path, output_path)
137
+ roop.globals.frame_processors = ["face_swapper", "face_enhancer"] if do_face_enhancer else ["face_swapper"]
138
+ roop.globals.headless = True
139
+ roop.globals.keep_fps = True
140
+ roop.globals.keep_audio = True
141
+ roop.globals.keep_frames = False
142
+ roop.globals.many_faces = False
143
+ roop.globals.video_encoder = "libx264"
144
+ roop.globals.video_quality = 18
145
+ roop.globals.max_memory = suggest_max_memory()
146
+ roop.globals.execution_providers = decode_execution_providers(["cuda"])
147
+ roop.globals.execution_threads = suggest_execution_threads()
148
+
149
+
150
+ def swap_face(source_file: np.ndarray, target_file: np.ndarray, doFaceEnhancer: bool) -> Optional[np.ndarray]:
151
+ """
152
+ Perform face swapping on static images.
153
+
154
+ Parameters:
155
+ source_file (np.ndarray): Source image array.
156
+ target_file (np.ndarray): Target image array.
157
+ doFaceEnhancer (bool): Flag to apply face enhancer.
158
+
159
+ Returns:
160
+ Optional[np.ndarray]: The output image array if successful, otherwise None.
161
+ """
162
+ folder_path = None
163
  try:
164
+ dataset_handler = FaceIntegrDataset()
165
+ folder_path, date_folder = dataset_handler.create_date_folder()
166
+ timestamp = datetime.now().strftime("%S-%M-%H-%d-%m-%Y")
167
  source_path = os.path.join(folder_path, f"source_{timestamp}.jpg")
168
  target_path = os.path.join(folder_path, f"target_{timestamp}.jpg")
169
+ output_path = os.path.join(folder_path, f"OutputImage{timestamp}.jpg")
170
 
171
  if source_file is None or target_file is None:
172
  raise ValueError("Source and target images are required")
173
+
174
  Image.fromarray(source_file).save(source_path)
175
  Image.fromarray(target_file).save(target_path)
176
+
177
+ logging.info(f"Source image saved at: {source_path}")
178
+ logging.info(f"Target image saved at: {target_path}")
179
+
180
+ # Configure global parameters for roop
181
+ configure_roop_globals(source_path, target_path, output_path, doFaceEnhancer)
182
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  # Pre-check frame processors
184
  for frame_processor in get_frame_processors_modules(roop.globals.frame_processors):
185
  if not frame_processor.pre_check():
186
+ logging.error("Pre-check failed for frame processor.")
187
+ return None
188
+
189
+ logging.info("Starting face swap process...")
190
  start()
191
+
 
192
  metadata = dataset_handler.save_metadata(
193
+ os.path.basename(source_path),
194
+ os.path.basename(target_path),
195
+ os.path.basename(output_path),
196
+ timestamp
197
  )
198
+
199
  metadata_path = os.path.join(folder_path, f"metadata_{timestamp}.json")
200
+ with open(metadata_path, 'w') as f:
201
  json.dump(metadata, f, indent=4)
202
+
 
203
  upload_success = dataset_handler.upload_to_hf(folder_path, date_folder)
204
+ if upload_success:
205
+ logging.info(f"Successfully uploaded files to dataset {dataset_handler.repo_id}")
 
 
 
 
 
206
  else:
207
+ logging.error("Failed to upload files to Hugging Face dataset")
208
+
209
+ if os.path.exists(roop.globals.output_path):
210
+ output_image = Image.open(roop.globals.output_path)
211
+ output_array = np.array(output_image)
212
+ shutil.rmtree(folder_path, ignore_errors=True)
213
+ return output_array
214
+ else:
215
+ logging.error("Output image not found")
216
+ shutil.rmtree(folder_path, ignore_errors=True)
217
+ return None
218
+
219
  except Exception as e:
220
+ logging.exception(f"Error in face swap process: {str(e)}")
 
 
221
  if folder_path and os.path.exists(folder_path):
222
+ shutil.rmtree(folder_path, ignore_errors=True)
223
+ raise gr.Error(f"Face swap failed: {str(e)}")
224
+
225
 
226
+ def swap_face_frame(frame_bgr: np.ndarray, replacement_face_rgb: np.ndarray, doFaceEnhancer: bool) -> np.ndarray:
227
+ """
228
+ Swap face in a single video frame.
229
+
230
+ Parameters:
231
+ frame_bgr (np.ndarray): Video frame in BGR format.
232
+ replacement_face_rgb (np.ndarray): Replacement face image in RGB format.
233
+ doFaceEnhancer (bool): Flag to apply face enhancer.
234
+
235
+ Returns:
236
+ np.ndarray: Processed frame with face swapped (in RGB format).
237
+ """
238
+ # Convert BGR to RGB for processing
239
  frame_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
240
+ temp_dir = tempfile.mkdtemp(prefix="temp_faceswap_frame_")
 
241
  timestamp = datetime.now().strftime("%S-%M-%H-%d-%m-%Y")
242
+ source_path = os.path.join(temp_dir, f"source_{timestamp}.jpg")
243
+ target_path = os.path.join(temp_dir, f"target_{timestamp}.jpg")
244
+ output_path = os.path.join(temp_dir, f"OutputImage_{timestamp}.jpg")
245
+
246
  try:
 
 
 
 
247
  Image.fromarray(frame_rgb).save(source_path)
248
  Image.fromarray(replacement_face_rgb).save(target_path)
249
+
250
+ configure_roop_globals(source_path, target_path, output_path, doFaceEnhancer)
251
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  start()
253
+
254
+ if os.path.exists(roop.globals.output_path):
255
+ swapped_img = np.array(Image.open(roop.globals.output_path))
 
256
  else:
257
+ logging.warning("Output image not found after face swap; returning original frame.")
258
+ swapped_img = frame_rgb
259
+ except Exception as e:
260
+ logging.exception(f"Error in processing frame for face swap: {str(e)}")
261
+ swapped_img = frame_rgb
262
  finally:
263
+ shutil.rmtree(temp_dir, ignore_errors=True)
264
+
265
+ return swapped_img
 
 
 
266
 
 
 
 
 
267
 
268
+ def swap_face_video(reference_face: np.ndarray, replacement_face: np.ndarray, video_input: str,
269
+ similarity_threshold: float, doFaceEnhancer: bool) -> str:
270
+ """
271
+ Perform face swapping on a video frame-by-frame.
272
+
273
+ Parameters:
274
+ reference_face (np.ndarray): Reference face image (RGB) for face locking.
275
+ replacement_face (np.ndarray): Replacement face image (RGB).
276
+ video_input (str): Path to the input video file.
277
+ similarity_threshold (float): Threshold for face similarity (0.0 - 1.0).
278
+ doFaceEnhancer (bool): Flag to apply face enhancer.
279
+
280
+ Returns:
281
+ str: Path to the output video file.
282
+
283
+ Raises:
284
+ gr.Error: If face detection fails or video cannot be processed.
285
+ """
286
+ try:
287
+ # Initialize insightface face analysis
288
+ fa = FaceAnalysis()
289
+ fa.prepare(ctx_id=0)
290
+
291
+ # Get embedding for the reference face
292
+ ref_detections = fa.get(reference_face)
293
+ if not ref_detections:
294
+ raise gr.Error("No face detected in the reference image!")
295
+ ref_embedding = ref_detections[0].embedding
296
+
297
+ # Open video input
298
+ cap = cv2.VideoCapture(video_input)
299
+ if not cap.isOpened():
300
+ raise gr.Error("Cannot open the input video!")
301
+ fps = cap.get(cv2.CAP_PROP_FPS)
302
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
303
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
304
+
305
+ output_video_path = "temp_faceswap_video.mp4"
306
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
307
+ out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
308
+
309
+ frame_index = 0
310
+ while True:
311
+ ret, frame = cap.read()
312
+ if not ret:
313
+ break
314
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
315
+ detections = fa.get(frame_rgb)
316
+ swap_this_frame = any(
317
+ cosine_similarity(det.embedding, ref_embedding) >= similarity_threshold
318
+ for det in detections
319
+ )
320
+
321
+ if swap_this_frame:
322
+ swapped_frame_rgb = swap_face_frame(frame, replacement_face, doFaceEnhancer)
323
+ swapped_frame = cv2.cvtColor(swapped_frame_rgb, cv2.COLOR_RGB2BGR)
324
+ else:
325
+ swapped_frame = frame
326
+
327
+ out.write(swapped_frame)
328
+ frame_index += 1
329
+ logging.info(f"Processed frame {frame_index}")
330
+
331
+ cap.release()
332
+ out.release()
333
+ return output_video_path
334
+ except Exception as e:
335
+ logging.exception(f"Error processing video: {str(e)}")
336
+ raise gr.Error(f"Face swap video failed: {str(e)}")
337
 
 
 
 
338
 
339
+ def create_interface() -> gr.Blocks:
340
+ """
341
+ Create and return the Gradio interface for face swapping.
342
+
343
+ Returns:
344
+ gr.Blocks: The Gradio interface.
345
+ """
346
  custom_css = """
347
  .container {
348
  max-width: 1200px;
 
363
  <p>This tool performs face swapping with optional enhancement.</p>
364
  </div>
365
  """
 
366
  with gr.Blocks(title=title, css=custom_css) as app:
367
  gr.Markdown(f"<h1 style='text-align: center;'>{title}</h1>")
368
  gr.Markdown(description)
 
369
  with gr.Tabs():
370
  with gr.TabItem("FaceSwap Image"):
371
  with gr.Row():
372
+ with gr.Column(scale=1):
373
+ source_image = gr.Image(
374
+ label="Source Image",
375
+ type="numpy",
376
+ sources=["upload"]
377
+ )
378
+ with gr.Column(scale=1):
379
+ target_image = gr.Image(
380
+ label="Target Image",
381
+ type="numpy",
382
+ sources=["upload"]
383
+ )
384
+ with gr.Column(scale=1):
385
+ output_image = gr.Image(
386
+ label="Output Image",
387
+ type="numpy",
388
+ interactive=False,
389
+ elem_classes="output-image"
390
+ )
391
+ with gr.Row():
392
+ enhance_checkbox = gr.Checkbox(
393
+ label="Apply Face Enhancer",
394
+ info="Improve image quality",
395
+ value=False
396
+ )
397
+ with gr.Row():
398
+ process_btn = gr.Button(
399
+ "Process Face Swap",
400
+ variant="primary",
401
+ size="lg"
402
+ )
403
  process_btn.click(
404
  fn=swap_face,
405
  inputs=[source_image, target_image, enhance_checkbox],
406
  outputs=output_image,
407
+ api_name="swap_face"
408
  )
 
409
  with gr.TabItem("FaceSwap Video"):
410
  gr.Markdown("<h2 style='text-align:center;'>FaceSwap Video</h2>")
411
  with gr.Row():
412
+ ref_image = gr.Image(
413
+ label="Reference Face Image (Lock Face)",
414
+ type="numpy",
415
+ sources=["upload"]
416
+ )
417
+ swap_image = gr.Image(
418
+ label="Replacement Face Image",
419
+ type="numpy",
420
+ sources=["upload"]
421
+ )
422
+ video_input = gr.Video(
423
+ label="Input Video"
424
+ )
425
+ similarity_threshold = gr.Slider(
426
+ minimum=0.0,
427
+ maximum=1.0,
428
+ step=0.01,
429
+ value=0.7,
430
+ label="Similarity Threshold"
431
+ )
432
+ enhance_checkbox_video = gr.Checkbox(
433
+ label="Apply Face Enhancer",
434
+ info="Optional quality enhancement",
435
+ value=False
436
+ )
437
+ process_video_btn = gr.Button(
438
+ "Process FaceSwap Video",
439
+ variant="primary",
440
+ size="lg"
441
+ )
442
+ video_output = gr.Video(
443
+ label="Output Video"
444
+ )
445
  process_video_btn.click(
446
  fn=swap_face_video,
447
  inputs=[ref_image, swap_image, video_input, similarity_threshold, enhance_checkbox_video],
448
  outputs=video_output,
449
+ api_name="swap_face_video"
450
  )
 
451
  gr.Markdown(article)
 
452
  return app
453
 
454
+
455
+ def main() -> None:
456
+ """
457
+ Launch the Gradio interface.
458
+ """
459
  app = create_interface()
460
  app.launch(share=False)
461
 
462
+
463
  if __name__ == "__main__":
464
+ main()