Athagi commited on
Commit
229b864
Β·
verified Β·
1 Parent(s): 8df0d6a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -193
app.py CHANGED
@@ -4,12 +4,11 @@ import numpy as np
4
  import gradio as gr
5
  from insightface.app import FaceAnalysis
6
  from insightface.model_zoo import get_model # For swapper
7
- from PIL import Image # Import PIL directly
8
  import tempfile
9
  import logging
10
  import onnxruntime
11
- from typing import List, Optional, Tuple, Any # For improved type hinting
12
- import copy # Import for deepcopy
13
 
14
  # --- Configuration & Setup ---
15
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -44,24 +43,6 @@ face_analyzer: Optional[FaceAnalysis] = None
44
  swapper = None
45
  face_restorer: Optional[onnxruntime.InferenceSession] = None
46
 
47
- # --- Create Directories and Dummy Example Files (Moved Earlier) ---
48
- os.makedirs("models", exist_ok=True)
49
- os.makedirs("examples", exist_ok=True)
50
- example_files_to_create = {
51
- "examples/source_example_1.jpg": ('blue', (100,100)),
52
- "examples/source_example_2.png": ('green', (100,100)),
53
- "examples/target_example_1.jpg": ('yellow', (200,200)),
54
- "examples/target_example_2.png": ('magenta', (200,200)),
55
- }
56
- for f_path, (color, size) in example_files_to_create.items():
57
- if not os.path.exists(f_path):
58
- try:
59
- Image.new('RGB', size, color=color).save(f_path)
60
- logging.info(f"Created dummy example file: {f_path}")
61
- except Exception as e:
62
- logging.warning(f"Could not create dummy example file {f_path}: {e}")
63
-
64
-
65
  def initialize_models():
66
  global face_analyzer, swapper, face_restorer
67
  try:
@@ -117,7 +98,7 @@ def convert_cv2_to_pil(cv2_image: Optional[np.ndarray]) -> Optional[Image.Image]
117
  logging.error(f"Error converting CV2 to PIL: {e}")
118
  return None
119
 
120
- def get_faces_from_image(img_np: np.ndarray) -> List[Any]:
121
  if not core_models_loaded_successfully or face_analyzer is None:
122
  logging.error("Face analyzer not available for get_faces_from_image. Core models might have failed to load.")
123
  return []
@@ -131,11 +112,10 @@ def get_faces_from_image(img_np: np.ndarray) -> List[Any]:
131
  logging.error(f"Error during face detection: {e}", exc_info=True)
132
  return []
133
 
134
- def draw_detected_faces(img_np: np.ndarray, faces: List[Any]) -> np.ndarray:
135
  img_with_boxes = img_np.copy()
136
  for i, face in enumerate(faces):
137
- # Face object from insightface usually supports attribute access for bbox
138
- box = face.bbox.astype(int)
139
  x1, y1, x2, y2 = box[0], box[1], box[2], box[3]
140
  cv2.rectangle(img_with_boxes, (x1, y1), (x2, y2), (0, 255, 0), 2)
141
  label_position = (x1, max(0, y1 - 10))
@@ -225,124 +205,29 @@ def histogram_match_color(source_img_bgr: np.ndarray, target_img_bgr: np.ndarray
225
  logging.error(f"Error during color histogram matching: {e}", exc_info=True)
226
  return source_img_bgr
227
 
228
- def process_face_swap(source_file_objects: Optional[List[Any]],
229
- target_pil_img: Optional[Image.Image],
230
  target_face_index: int, apply_enhancement: bool, apply_color_correction: bool,
231
  progress=gr.Progress(track_tqdm=True)):
232
  progress(0, desc="Initializing process...")
233
  if not core_models_loaded_successfully:
234
  gr.Error("CRITICAL: Core models (Face Analyzer or Swapper) not loaded. Cannot proceed.")
235
  return Image.new('RGB', (100, 100), color='lightgrey'), None, "Core models failed to load."
 
 
236
 
237
- if not source_file_objects:
238
- raise gr.Error("Source image(s) not provided. Please upload one or more source face images.")
239
- if target_pil_img is None:
240
- raise gr.Error("Target image not provided. Please upload a target scene image.")
241
-
242
- progress(0.05, desc="Reading source images...")
243
- source_pil_list: List[Image.Image] = []
244
- for file_obj in source_file_objects:
245
- try:
246
- img = Image.open(file_obj.name).convert('RGB')
247
- source_pil_list.append(img)
248
- except Exception as e:
249
- logging.warning(f"Could not read or convert one of the source images ({file_obj.name}): {e}")
250
- gr.Info(f"Skipping one source image due to read error: {os.path.basename(file_obj.name)}")
251
-
252
- if not source_pil_list:
253
- raise gr.Error("No valid source images could be read. Please check the uploaded files.")
254
-
255
- progress(0.10, desc=f"Extracting features from {len(source_pil_list)} source image(s)...")
256
- valid_source_face_objects: List[Any] = []
257
-
258
- for idx, pil_img in enumerate(source_pil_list):
259
- source_np_item = convert_pil_to_cv2(pil_img)
260
- if source_np_item is None:
261
- logging.warning(f"Could not convert source image {idx+1} to CV2 format.")
262
- gr.Info(f"Skipping source image {idx+1} due to conversion error.")
263
- continue
264
-
265
- current_source_faces = get_faces_from_image(source_np_item)
266
- if not current_source_faces:
267
- logging.warning(f"No face found in source image {idx+1}. It will be skipped.")
268
- gr.Info(f"No face found in source image {idx+1}, skipping it.")
269
- continue
270
-
271
- detected_face = current_source_faces[0]
272
- # Check for embedding attribute first, then key
273
- current_embedding = None
274
- if hasattr(detected_face, 'embedding') and detected_face.embedding is not None:
275
- current_embedding = detected_face.embedding
276
- elif 'embedding' in detected_face and detected_face['embedding'] is not None:
277
- current_embedding = detected_face['embedding']
278
-
279
- if current_embedding is not None:
280
- valid_source_face_objects.append(detected_face)
281
- else:
282
- logging.warning(f"Valid embedding not found for face in source image {idx+1}.")
283
- gr.Info(f"Valid embedding not found in source image {idx+1}, skipping.")
284
-
285
- if not valid_source_face_objects:
286
- raise gr.Error("No usable faces (with embeddings) found in any of the uploaded source images.")
287
-
288
- source_face_for_swap: Optional[Any] = None
289
- if len(valid_source_face_objects) == 1:
290
- source_face_for_swap = valid_source_face_objects[0]
291
- logging.info("Using single source face for swap.")
292
- gr.Info("Using features from 1 source image.")
293
- else:
294
- logging.info(f"Averaging embeddings from {len(valid_source_face_objects)} source faces.")
295
- gr.Info(f"Averaging features from {len(valid_source_face_objects)} source images for potentially better results.")
296
-
297
- embeddings_to_average = []
298
- for face_obj in valid_source_face_objects:
299
- emb_val = None
300
- if hasattr(face_obj, 'embedding') and face_obj.embedding is not None:
301
- emb_val = face_obj.embedding
302
- elif 'embedding' in face_obj and face_obj['embedding'] is not None:
303
- emb_val = face_obj['embedding']
304
-
305
- if emb_val is None: # Should have been caught, but defensive
306
- logging.error("A face object in valid_source_face_objects lacked an embedding unexpectedly.")
307
- raise gr.Error("Internal error: Missing embedding in a validated source face.")
308
- embeddings_to_average.append(emb_val)
309
-
310
- first_emb_shape = embeddings_to_average[0].shape
311
- for i, emb in enumerate(embeddings_to_average):
312
- if emb.shape != first_emb_shape:
313
- logging.error(f"Embeddings have inconsistent shapes. Cannot average. Emb {i} shape: {emb.shape}, Expected: {first_emb_shape}")
314
- raise gr.Error("Source face embeddings have inconsistent structures. Cannot average.")
315
-
316
- avg_embedding = np.mean(np.array(embeddings_to_average), axis=0)
317
- avg_embedding = avg_embedding / np.linalg.norm(avg_embedding)
318
-
319
- source_face_for_swap = copy.deepcopy(valid_source_face_objects[0]) # Deepcopy to preserve class and attributes
320
-
321
- # Set 'embedding' (primary)
322
- if hasattr(source_face_for_swap, 'embedding'):
323
- source_face_for_swap.embedding = avg_embedding
324
- elif 'embedding' in source_face_for_swap:
325
- source_face_for_swap['embedding'] = avg_embedding
326
- else: # If somehow neither exists, create it (less likely for Face objects)
327
- source_face_for_swap['embedding'] = avg_embedding
328
-
329
- # Set 'normed_embedding' if it exists, as this was the original error trigger
330
- if hasattr(source_face_for_swap, 'normed_embedding'):
331
- source_face_for_swap.normed_embedding = avg_embedding
332
- elif 'normed_embedding' in source_face_for_swap:
333
- source_face_for_swap['normed_embedding'] = avg_embedding
334
- # If 'normed_embedding' does not exist, we assume the swapper might fall back to 'embedding'
335
- # or that the original Face object structure usually has both as the same if already normalized.
336
-
337
- if source_face_for_swap is None:
338
- raise gr.Error("Failed to prepare a source face for swapping.")
339
-
340
- progress(0.20, desc="Converting target image...")
341
  target_np = convert_pil_to_cv2(target_pil_img)
342
- if target_np is None:
343
- raise gr.Error("Target image conversion failed. Ensure the image is valid and not corrupted.")
344
  target_h, target_w = target_np.shape[:2]
345
 
 
 
 
 
 
 
346
  progress(0.25, desc="Detecting faces in target image...")
347
  target_faces = get_faces_from_image(target_np)
348
  if not target_faces:
@@ -351,15 +236,12 @@ def process_face_swap(source_file_objects: Optional[List[Any]],
351
  err_msg = (f"Selected target face index ({target_face_index}) is out of range. "
352
  f"Detected {len(target_faces)} faces (indices 0 to {len(target_faces)-1}). Please re-preview and select a valid face.")
353
  raise gr.Error(err_msg)
354
- target_face_to_swap_info = target_faces[int(target_face_index)]
355
-
356
  swapped_bgr_img = target_np.copy()
357
  try:
358
  progress(0.4, desc="Performing face swap...")
359
- swapped_bgr_img = swapper.get(swapped_bgr_img, target_face_to_swap_info, source_face_for_swap, paste_back=True)
360
-
361
- # target_face_to_swap_info is directly from insightface, .bbox should be safe
362
- bbox_coords = target_face_to_swap_info.bbox.astype(int)
363
 
364
  if apply_enhancement:
365
  if restoration_model_loaded_successfully and face_restorer is not None:
@@ -403,6 +285,9 @@ def process_face_swap(source_file_objects: Optional[List[Any]],
403
  logging.warning("Skipping color correction due to invalid bounding box for region extraction.")
404
  except Exception as e:
405
  logging.error(f"Error during face swapping or post-processing: {e}", exc_info=True)
 
 
 
406
  raise gr.Error(f"An error occurred during processing: {str(e)}. Check logs for details.")
407
 
408
  progress(0.9, desc="Finalizing image conversion...")
@@ -436,7 +321,7 @@ def preview_target_faces(target_pil_img: Optional[Image.Image]):
436
  if target_np is None:
437
  gr.Warning("Could not process target image for preview. Please try a different image.")
438
  return blank_preview_placeholder, reset_slider
439
- faces = get_faces_from_image(target_np)
440
  if not faces:
441
  max_h, max_w = DETECTION_SIZE[1], DETECTION_SIZE[0]
442
  h, w = target_np.shape[:2]
@@ -449,8 +334,7 @@ def preview_target_faces(target_pil_img: Optional[Image.Image]):
449
  if preview_pil_img is None: preview_pil_img = blank_preview_placeholder
450
  gr.Info("No faces were detected in the target image.")
451
  return preview_pil_img, reset_slider
452
-
453
- preview_np_with_boxes = draw_detected_faces(target_np, faces)
454
  preview_pil_img_with_boxes = convert_cv2_to_pil(preview_np_with_boxes)
455
  if preview_pil_img_with_boxes is None:
456
  preview_pil_img_with_boxes = blank_preview_placeholder
@@ -461,13 +345,13 @@ def preview_target_faces(target_pil_img: Optional[Image.Image]):
461
  return preview_pil_img_with_boxes, slider_update
462
 
463
  # --- Gradio UI Definition ---
464
- with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.5 Multi-Source", theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky)) as demo: #Version bump
465
  gr.Markdown(
466
  """
467
  <div style="text-align: center; border-bottom: 1px solid #eee; padding-bottom:10px;">
468
- <h1>πŸ’Ž Ultimate Face Swap AI <span style="font-size:0.8em; color: #555;">v3.5 Multi-Source</span> ✨</h1>
469
- <p>Upload <strong>one or more source face images</strong>, a <strong>target image</strong>, and let the AI work its magic!</p>
470
- <p style="font-size:0.9em;">If multiple source images are provided, their facial features will be averaged for the swap.</p>
471
  </div>
472
  """
473
  )
@@ -476,15 +360,13 @@ with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.5 Multi-Source", theme=gr.th
476
  gr.Error(
477
  "πŸ”΄ CRITICAL ERROR: Core models (Face Analyzer or Swapper) failed to load. "
478
  "The application UI will load but WILL NOT FUNCTION correctly. "
479
- "Please check the console logs for details and restart after fixing."
 
480
  )
481
 
482
  with gr.Row():
483
  with gr.Column(scale=1, min_width=300):
484
- source_image_input = gr.Files(
485
- label="πŸ‘€ Source Face Images (Upload one or more clear face images)",
486
- file_types=["image"],
487
- )
488
  with gr.Column(scale=1, min_width=300):
489
  target_image_input = gr.Image(label="πŸ–ΌοΈ Target Scene Image (Upload image to swap a face into)", type="pil", sources=["upload", "clipboard"], height=380)
490
 
@@ -501,6 +383,7 @@ with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.5 Multi-Source", theme=gr.th
501
  )
502
  gr.Markdown("<p style='font-size:0.8em; color:#555;'>After uploading target, click 'Preview'. Then use slider to select face.</p>")
503
 
 
504
  gr.HTML("<hr style='margin-top: 5px; margin-bottom: 5px; border-style: solid; border-width: thin; border-color: #ddd;'>")
505
 
506
  enhance_checkbox_label = "✨ Apply Selective Face Restoration"
@@ -512,9 +395,9 @@ with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.5 Multi-Source", theme=gr.th
512
  interactive=restoration_model_loaded_successfully
513
  )
514
  if not restoration_model_loaded_successfully:
515
- gr.Markdown("<p style='color: orange; font-size:0.8em;'>⚠️ Face restoration model not loaded. This feature is disabled.</p>")
516
  else:
517
- gr.Markdown("<p style='font-size:0.8em; color:#555;'>Improves quality of the swapped face region.</p>")
518
 
519
  color_correction_checkbox = gr.Checkbox(
520
  label="🎨 Apply Color Correction",
@@ -585,57 +468,29 @@ with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.5 Multi-Source", theme=gr.th
585
  gr.HTML("<hr style='margin-top: 20px; margin-bottom: 10px;'>")
586
  gr.Markdown("### πŸ“‹ Example Usage")
587
 
588
- def process_face_swap_for_examples(source_file_paths_list: List[str],
589
- target_img_path: str, idx: int, enhance: bool, color_correct: bool,
590
- progress=gr.Progress(track_tqdm=True)):
591
- mock_source_file_objects = []
592
- if source_file_paths_list:
593
- for path in source_file_paths_list:
594
- if not os.path.exists(path):
595
- logging.error(f"Example source file not found: {path}. Skipping this example or part of it.")
596
- continue
597
- mock_file_obj = type('MockFile', (object,), {'name': path})()
598
- mock_source_file_objects.append(mock_file_obj)
599
-
600
- target_pil = None
601
- if target_img_path and os.path.exists(target_img_path):
602
- try:
603
- target_pil = Image.open(target_img_path)
604
- except Exception as e:
605
- logging.error(f"Failed to load example target image {target_img_path}: {e}")
606
- return Image.new('RGB', (100,100), 'red'), None, f"Error loading example target: {os.path.basename(target_img_path)}"
607
- elif target_img_path and not os.path.exists(target_img_path):
608
- logging.error(f"Example target file not found: {target_img_path}")
609
- return Image.new('RGB', (100,100), 'red'), None, f"Example target missing: {os.path.basename(target_img_path)}"
610
-
611
- if not mock_source_file_objects and not target_pil: # Both sources and target failed or were not provided
612
- return Image.new('RGB', (100,100), 'red'), None, "Error loading example images."
613
- if not mock_source_file_objects and target_pil : # Sources failed/missing but target is ok
614
- # This case means process_face_swap will raise an error about no source images.
615
- # Let's try to call it anyway and let its internal error handling manage the gr.Error.
616
- # Or, we can return a specific message here.
617
- # For consistency, let process_face_swap handle the "no source" error.
618
- pass
619
-
620
-
621
- return process_face_swap(mock_source_file_objects, target_pil, idx, enhance, color_correct, progress)
622
 
623
  gr.Examples(
624
  examples=[
625
- [["examples/source_example_1.jpg"], "examples/target_example_1.jpg", 0, True, True],
626
- [["examples/source_example_1.jpg", "examples/source_example_2.png"], "examples/target_example_2.png", 1, True, False],
627
- [["examples/source_example_1.jpg"], "examples/target_example_1.jpg", 0, False, True],
628
  ],
629
  inputs=[source_image_input, target_image_input, face_index_slider, enhance_checkbox, color_correction_checkbox],
630
  outputs=[swapped_image_output, download_output_file, status_message_output],
631
  fn=process_face_swap_for_examples,
632
  cache_examples=False,
633
- label="Example Face Swaps (Ensure example images exist in 'examples' folder)"
634
  )
635
 
636
  if __name__ == "__main__":
 
 
 
637
  print("\n" + "="*70)
638
- print("πŸš€ ULTIMATE FACE SWAP AI - v3.5 Multi-Source STARTUP STATUS πŸš€")
639
  print("="*70)
640
  print(f"Execution Providers Selected: {EXECUTION_PROVIDERS}")
641
 
@@ -656,5 +511,5 @@ if __name__ == "__main__":
656
  print("\nπŸ‘‰ The Gradio interface will launch, but swapping functionality will be broken. Please address errors.")
657
  else:
658
  print("\nπŸ‘‰ Launching Gradio Interface... Access it in your browser (usually at http://127.0.0.1:7860).")
659
-
660
  demo.launch()
 
4
  import gradio as gr
5
  from insightface.app import FaceAnalysis
6
  from insightface.model_zoo import get_model # For swapper
7
+ from PIL import Image
8
  import tempfile
9
  import logging
10
  import onnxruntime
11
+ from typing import List, Optional, Tuple # For improved type hinting
 
12
 
13
  # --- Configuration & Setup ---
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
43
  swapper = None
44
  face_restorer: Optional[onnxruntime.InferenceSession] = None
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def initialize_models():
47
  global face_analyzer, swapper, face_restorer
48
  try:
 
98
  logging.error(f"Error converting CV2 to PIL: {e}")
99
  return None
100
 
101
+ def get_faces_from_image(img_np: np.ndarray) -> List:
102
  if not core_models_loaded_successfully or face_analyzer is None:
103
  logging.error("Face analyzer not available for get_faces_from_image. Core models might have failed to load.")
104
  return []
 
112
  logging.error(f"Error during face detection: {e}", exc_info=True)
113
  return []
114
 
115
+ def draw_detected_faces(img_np: np.ndarray, faces: List) -> np.ndarray:
116
  img_with_boxes = img_np.copy()
117
  for i, face in enumerate(faces):
118
+ box = face.bbox.astype(int)
 
119
  x1, y1, x2, y2 = box[0], box[1], box[2], box[3]
120
  cv2.rectangle(img_with_boxes, (x1, y1), (x2, y2), (0, 255, 0), 2)
121
  label_position = (x1, max(0, y1 - 10))
 
205
  logging.error(f"Error during color histogram matching: {e}", exc_info=True)
206
  return source_img_bgr
207
 
208
+ def process_face_swap(source_pil_img: Optional[Image.Image], target_pil_img: Optional[Image.Image],
 
209
  target_face_index: int, apply_enhancement: bool, apply_color_correction: bool,
210
  progress=gr.Progress(track_tqdm=True)):
211
  progress(0, desc="Initializing process...")
212
  if not core_models_loaded_successfully:
213
  gr.Error("CRITICAL: Core models (Face Analyzer or Swapper) not loaded. Cannot proceed.")
214
  return Image.new('RGB', (100, 100), color='lightgrey'), None, "Core models failed to load."
215
+ if source_pil_img is None: raise gr.Error("Source image not provided. Please upload a source face image.")
216
+ if target_pil_img is None: raise gr.Error("Target image not provided. Please upload a target scene image.")
217
 
218
+ progress(0.05, desc="Converting images to OpenCV format...")
219
+ source_np = convert_pil_to_cv2(source_pil_img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  target_np = convert_pil_to_cv2(target_pil_img)
221
+ if source_np is None or target_np is None:
222
+ raise gr.Error("Image conversion failed. Ensure images are valid (e.g., JPG, PNG) and not corrupted.")
223
  target_h, target_w = target_np.shape[:2]
224
 
225
+ progress(0.15, desc="Detecting face in source image...")
226
+ source_faces = get_faces_from_image(source_np)
227
+ if not source_faces:
228
+ raise gr.Error("No face found in the source image. Please use a clear, well-lit image of a single, front-facing face.")
229
+ source_face = source_faces[0]
230
+
231
  progress(0.25, desc="Detecting faces in target image...")
232
  target_faces = get_faces_from_image(target_np)
233
  if not target_faces:
 
236
  err_msg = (f"Selected target face index ({target_face_index}) is out of range. "
237
  f"Detected {len(target_faces)} faces (indices 0 to {len(target_faces)-1}). Please re-preview and select a valid face.")
238
  raise gr.Error(err_msg)
239
+ target_face_to_swap_info = target_faces[int(target_face_index)]
 
240
  swapped_bgr_img = target_np.copy()
241
  try:
242
  progress(0.4, desc="Performing face swap...")
243
+ swapped_bgr_img = swapper.get(swapped_bgr_img, target_face_to_swap_info, source_face, paste_back=True)
244
+ bbox_coords = target_face_to_swap_info.bbox.astype(int)
 
 
245
 
246
  if apply_enhancement:
247
  if restoration_model_loaded_successfully and face_restorer is not None:
 
285
  logging.warning("Skipping color correction due to invalid bounding box for region extraction.")
286
  except Exception as e:
287
  logging.error(f"Error during face swapping or post-processing: {e}", exc_info=True)
288
+ swapped_pil_img_on_error = convert_cv2_to_pil(swapped_bgr_img)
289
+ if swapped_pil_img_on_error is None:
290
+ swapped_pil_img_on_error = Image.new('RGB', (target_w, target_h), color='lightgrey')
291
  raise gr.Error(f"An error occurred during processing: {str(e)}. Check logs for details.")
292
 
293
  progress(0.9, desc="Finalizing image conversion...")
 
321
  if target_np is None:
322
  gr.Warning("Could not process target image for preview. Please try a different image.")
323
  return blank_preview_placeholder, reset_slider
324
+ faces = get_faces_from_image(target_np)
325
  if not faces:
326
  max_h, max_w = DETECTION_SIZE[1], DETECTION_SIZE[0]
327
  h, w = target_np.shape[:2]
 
334
  if preview_pil_img is None: preview_pil_img = blank_preview_placeholder
335
  gr.Info("No faces were detected in the target image.")
336
  return preview_pil_img, reset_slider
337
+ preview_np_with_boxes = draw_detected_faces(target_np, faces)
 
338
  preview_pil_img_with_boxes = convert_cv2_to_pil(preview_np_with_boxes)
339
  if preview_pil_img_with_boxes is None:
340
  preview_pil_img_with_boxes = blank_preview_placeholder
 
345
  return preview_pil_img_with_boxes, slider_update
346
 
347
  # --- Gradio UI Definition ---
348
+ with gr.Blocks(title="Ultimate Face Swap AI πŸ’Ž v3.2 Lite", theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.sky)) as demo:
349
  gr.Markdown(
350
  """
351
  <div style="text-align: center; border-bottom: 1px solid #eee; padding-bottom:10px;">
352
+ <h1>πŸ’Ž Ultimate Face Swap AI <span style="font-size:0.8em; color: #555;">v3.2 Lite</span> ✨</h1>
353
+ <p>Upload a <strong>source face</strong>, a <strong>target image</strong>, and let the AI work its magic!</p>
354
+ <p style="font-size:0.9em;">Optionally enhance with <strong>face restoration</strong> and <strong>color correction</strong> for improved realism.</p>
355
  </div>
356
  """
357
  )
 
360
  gr.Error(
361
  "πŸ”΄ CRITICAL ERROR: Core models (Face Analyzer or Swapper) failed to load. "
362
  "The application UI will load but WILL NOT FUNCTION correctly. "
363
+ "Please check the console logs for details (e.g., model file paths, ONNX Runtime issues) "
364
+ "and restart the application after fixing the underlying problem."
365
  )
366
 
367
  with gr.Row():
368
  with gr.Column(scale=1, min_width=300):
369
+ source_image_input = gr.Image(label="πŸ‘€ Source Face Image (Clear, single face recommended)", type="pil", sources=["upload", "clipboard"], height=380)
 
 
 
370
  with gr.Column(scale=1, min_width=300):
371
  target_image_input = gr.Image(label="πŸ–ΌοΈ Target Scene Image (Upload image to swap a face into)", type="pil", sources=["upload", "clipboard"], height=380)
372
 
 
383
  )
384
  gr.Markdown("<p style='font-size:0.8em; color:#555;'>After uploading target, click 'Preview'. Then use slider to select face.</p>")
385
 
386
+ # Changed from gr.Markdown("---", style="...") to gr.HTML for wider compatibility
387
  gr.HTML("<hr style='margin-top: 5px; margin-bottom: 5px; border-style: solid; border-width: thin; border-color: #ddd;'>")
388
 
389
  enhance_checkbox_label = "✨ Apply Selective Face Restoration"
 
395
  interactive=restoration_model_loaded_successfully
396
  )
397
  if not restoration_model_loaded_successfully:
398
+ gr.Markdown("<p style='color: orange; font-size:0.8em;'>⚠️ Face restoration model not loaded. This feature is disabled. Check console logs for details on `RESTORATION_MODEL_PATH`.</p>")
399
  else:
400
+ gr.Markdown("<p style='font-size:0.8em; color:#555;'>Improves quality of the swapped face region. Requires restoration model.</p>")
401
 
402
  color_correction_checkbox = gr.Checkbox(
403
  label="🎨 Apply Color Correction",
 
468
  gr.HTML("<hr style='margin-top: 20px; margin-bottom: 10px;'>")
469
  gr.Markdown("### πŸ“‹ Example Usage")
470
 
471
+ def process_face_swap_for_examples(src_img, tgt_img, idx, enhance, color_correct, progress=gr.Progress(track_tqdm=True)):
472
+ img, file_path, status = process_face_swap(src_img, tgt_img, idx, enhance, color_correct, progress)
473
+ return img, file_path, status
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
  gr.Examples(
476
  examples=[
477
+ ["examples/source_example_1.jpg", "examples/target_example_1.jpg", 0, True, True],
478
+ ["examples/source_example_2.png", "examples/target_example_2.png", 1, True, False],
479
+ ["examples/source_example_1.jpg", "examples/target_example_1.jpg", 0, False, True],
480
  ],
481
  inputs=[source_image_input, target_image_input, face_index_slider, enhance_checkbox, color_correction_checkbox],
482
  outputs=[swapped_image_output, download_output_file, status_message_output],
483
  fn=process_face_swap_for_examples,
484
  cache_examples=False,
485
+ label="Example Face Swaps (Click to run - ensure example images exist in 'examples' folder)"
486
  )
487
 
488
  if __name__ == "__main__":
489
+ os.makedirs("models", exist_ok=True)
490
+ os.makedirs("examples", exist_ok=True)
491
+
492
  print("\n" + "="*70)
493
+ print("πŸš€ ULTIMATE FACE SWAP AI - v3.2 Lite STARTUP STATUS πŸš€") # Version bump
494
  print("="*70)
495
  print(f"Execution Providers Selected: {EXECUTION_PROVIDERS}")
496
 
 
511
  print("\nπŸ‘‰ The Gradio interface will launch, but swapping functionality will be broken. Please address errors.")
512
  else:
513
  print("\nπŸ‘‰ Launching Gradio Interface... Access it in your browser (usually at http://127.0.0.1:7860).")
514
+
515
  demo.launch()