crash10155 commited on
Commit
e977387
·
verified ·
1 Parent(s): 104ba58

Update SwitcherAI/processors/frame/modules/face_swapper.py

Browse files
SwitcherAI/processors/frame/modules/face_swapper.py CHANGED
@@ -4,6 +4,7 @@ import insightface
4
  import threading
5
  import numpy as np
6
  from functools import lru_cache
 
7
 
8
  import SwitcherAI.globals
9
  import SwitcherAI.processors.frame.core as frame_processors
@@ -78,9 +79,44 @@ def get_frame_processor() -> Any:
78
 
79
  with THREAD_LOCK:
80
  if FRAME_PROCESSOR is None:
81
- config = get_current_model_config()
82
- model_path = resolve_relative_path(config['path'])
83
- FRAME_PROCESSOR = insightface.model_zoo.get_model(model_path, providers=SwitcherAI.globals.execution_providers)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  return FRAME_PROCESSOR
85
 
86
 
@@ -93,11 +129,32 @@ def get_embedding_converter() -> Any:
93
 
94
  with THREAD_LOCK:
95
  if EMBEDDING_CONVERTER is None:
96
- converter_path = resolve_relative_path(config['converter_path'])
97
  try:
98
- EMBEDDING_CONVERTER = insightface.model_zoo.get_model(converter_path, providers=SwitcherAI.globals.execution_providers)
99
- except Exception:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  EMBEDDING_CONVERTER = None
 
101
  return EMBEDDING_CONVERTER
102
 
103
 
@@ -109,31 +166,54 @@ def clear_frame_processor() -> None:
109
 
110
 
111
  def pre_check() -> bool:
112
- download_directory_path = resolve_relative_path('../.assets/models')
113
- config = get_current_model_config()
114
-
115
- # Download main model
116
- download_urls = [config['url']]
117
-
118
- # Download converter if needed
119
- if config.get('requires_converter', False):
120
- download_urls.append(config['converter_url'])
121
-
122
- conditional_download(download_directory_path, download_urls)
123
- return True
 
 
 
 
 
 
 
 
 
 
 
124
 
125
 
126
  def pre_process() -> bool:
127
- if not is_image(SwitcherAI.globals.source_path):
128
- update_status(wording.get('select_image_source') + wording.get('exclamation_mark'), NAME)
129
- return False
130
- elif not get_one_face(cv2.imread(SwitcherAI.globals.source_path)):
131
- update_status(wording.get('no_source_face_detected') + wording.get('exclamation_mark'), NAME)
132
- return False
133
- if not is_image(SwitcherAI.globals.target_path) and not is_video(SwitcherAI.globals.target_path):
134
- update_status(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  return False
136
- return True
137
 
138
 
139
  def post_process() -> None:
@@ -144,119 +224,165 @@ def post_process() -> None:
144
 
145
  def prepare_source_embedding(source_face: Face) -> np.ndarray:
146
  """Prepare source face embedding based on model type"""
147
- config = get_current_model_config()
148
- model_type = config['type']
149
-
150
- if model_type == 'inswapper':
151
- # Enhanced embedding preparation for inswapper
152
- model_path = resolve_relative_path(config['path'])
153
- model_initializer = get_static_model_initializer(model_path)
154
- source_embedding = source_face.embedding.reshape((1, -1))
155
- source_embedding = np.dot(source_embedding, model_initializer) / np.linalg.norm(source_embedding)
156
- return source_embedding
157
- elif model_type == 'simswap':
158
- # Use embedding converter for simswap
159
- converter = get_embedding_converter()
160
- if converter is not None:
161
- embedding = source_face.embedding.reshape(-1, 512)
162
- try:
163
- converted_embedding = converter.run(None, {'input': embedding})[0]
164
- converted_embedding = converted_embedding.ravel()
165
- normed_embedding = converted_embedding / np.linalg.norm(converted_embedding)
166
- return normed_embedding.reshape(1, -1)
167
- except Exception:
168
- pass
169
-
170
- # Fallback to original embedding
171
- return source_face.embedding.reshape(1, -1)
172
- else:
173
- # Default behavior
 
 
 
 
 
174
  return source_face.embedding.reshape(1, -1)
175
 
176
 
177
  def prepare_crop_frame(crop_frame: Frame) -> np.ndarray:
178
  """Prepare cropped frame for model input with normalization"""
179
- config = get_current_model_config()
180
- model_mean = config['mean']
181
- model_std = config['standard_deviation']
182
-
183
- # Convert to float and normalize
184
- crop_frame = crop_frame[:, :, ::-1] / 255.0
185
- crop_frame = (crop_frame - model_mean) / model_std
186
- crop_frame = crop_frame.transpose(2, 0, 1)
187
- crop_frame = np.expand_dims(crop_frame, axis=0).astype(np.float32)
188
- return crop_frame
 
 
 
 
 
189
 
190
 
191
  def normalize_crop_frame(crop_frame: np.ndarray) -> Frame:
192
  """Normalize cropped frame back to image format"""
193
- config = get_current_model_config()
194
- model_type = config['type']
195
- model_mean = config['mean']
196
- model_std = config['standard_deviation']
197
-
198
- crop_frame = crop_frame.transpose(1, 2, 0)
199
-
200
- # Apply reverse normalization for certain model types
201
- if model_type in ['simswap']:
202
- crop_frame = crop_frame * model_std + model_mean
203
-
204
- crop_frame = crop_frame.clip(0, 1)
205
- crop_frame = crop_frame[:, :, ::-1] * 255
206
- return crop_frame.astype(np.uint8)
 
 
 
 
 
207
 
208
 
209
  def enhanced_swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
210
  """Enhanced face swapping with improved preprocessing"""
211
- config = get_current_model_config()
212
- model_type = config['type']
213
-
214
- if model_type == 'inswapper':
215
- # Use original method for inswapper
216
- return get_frame_processor().get(temp_frame, target_face, source_face, paste_back=True)
217
- else:
218
- # Enhanced method for other models
219
- try:
220
- # Prepare source embedding
221
- source_embedding = prepare_source_embedding(source_face)
222
-
223
- # Get crop region (this would need proper implementation)
224
- # For now, fall back to original method
225
- return get_frame_processor().get(temp_frame, target_face, source_face, paste_back=True)
226
- except Exception:
227
- # Fallback to original method
228
- return get_frame_processor().get(temp_frame, target_face, source_face, paste_back=True)
 
 
 
 
 
 
 
 
 
 
 
229
 
230
 
231
  def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
232
  """Main face swapping function with model-specific handling"""
233
- config = get_current_model_config()
234
-
235
- # Use enhanced swapping for supported models
236
- if config['type'] in ['simswap', 'inswapper']:
237
- return enhanced_swap_face(source_face, target_face, temp_frame)
238
- else:
239
- # Original method
240
- return get_frame_processor().get(temp_frame, target_face, source_face, paste_back=True)
 
 
 
 
 
 
 
 
 
 
241
 
242
 
243
  def process_frame(source_face: Face, reference_face: Face, temp_frame: Frame) -> Frame:
244
  """Process frame with enhanced face selection logic"""
245
- if 'reference' in SwitcherAI.globals.face_recognition:
246
- similar_faces = find_similar_faces(temp_frame, reference_face, SwitcherAI.globals.reference_face_distance)
247
- if similar_faces:
248
- for similar_face in similar_faces:
249
- temp_frame = swap_face(source_face, similar_face, temp_frame)
250
-
251
- if 'many' in SwitcherAI.globals.face_recognition:
252
- many_faces = get_many_faces(temp_frame)
253
- if many_faces:
254
- # Sort faces by size (largest first) like the newer version
255
- many_faces = sorted(many_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
256
- for target_face in many_faces:
257
- temp_frame = swap_face(source_face, target_face, temp_frame)
258
-
259
- return temp_frame
 
 
 
 
 
 
 
 
 
 
260
 
261
 
262
  def get_average_face(faces: List[Face]) -> Face:
@@ -273,59 +399,122 @@ def get_average_face(faces: List[Face]) -> Face:
273
 
274
  def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None:
275
  """Enhanced frame processing with better source face handling"""
276
- source_frame = cv2.imread(source_path)
277
- source_faces = get_many_faces(source_frame)
278
-
279
- # Get best source face (largest)
280
- if source_faces:
281
- source_faces = sorted(source_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
282
- source_face = source_faces[0]
283
- else:
284
- source_face = get_one_face(source_frame)
285
-
286
- # Handle multiple source faces if available
287
- if len(source_faces) > 1:
288
- source_face = get_average_face(source_faces)
289
-
290
- reference_face = get_face_reference() if 'reference' in SwitcherAI.globals.face_recognition else None
291
-
292
- for temp_frame_path in temp_frame_paths:
293
- temp_frame = cv2.imread(temp_frame_path)
294
- result_frame = process_frame(source_face, reference_face, temp_frame)
295
- cv2.imwrite(temp_frame_path, result_frame)
296
- if update:
297
- update()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
 
300
  def process_image(source_path: str, target_path: str, output_path: str) -> None:
301
  """Enhanced image processing"""
302
- source_frame = cv2.imread(source_path)
303
- source_faces = get_many_faces(source_frame)
304
-
305
- # Get best source face
306
- if source_faces:
307
- source_faces = sorted(source_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
308
- source_face = source_faces[0]
309
 
310
- # Handle multiple source faces
311
- if len(source_faces) > 1:
312
- source_face = get_average_face(source_faces)
313
- else:
314
- source_face = get_one_face(source_frame)
315
-
316
- target_frame = cv2.imread(target_path)
317
- reference_face = get_one_face(target_frame, SwitcherAI.globals.reference_face_position) if 'reference' in SwitcherAI.globals.face_recognition else None
318
- result_frame = process_frame(source_face, reference_face, target_frame)
319
- cv2.imwrite(output_path, result_frame)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
 
322
  def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
323
- conditional_set_face_reference(temp_frame_paths)
324
- frame_processors.process_video(source_path, temp_frame_paths, process_frames)
 
 
 
325
 
326
 
327
  def conditional_set_face_reference(temp_frame_paths: List[str]) -> None:
328
- if 'reference' in SwitcherAI.globals.face_recognition and not get_face_reference():
329
- reference_frame = cv2.imread(temp_frame_paths[SwitcherAI.globals.reference_face_position])
330
- reference_face = get_one_face(reference_frame, SwitcherAI.globals.reference_face_position)
331
- set_face_reference(reference_face)
 
 
 
 
 
 
 
4
  import threading
5
  import numpy as np
6
  from functools import lru_cache
7
+ from pathlib import Path
8
 
9
  import SwitcherAI.globals
10
  import SwitcherAI.processors.frame.core as frame_processors
 
79
 
80
  with THREAD_LOCK:
81
  if FRAME_PROCESSOR is None:
82
+ try:
83
+ config = get_current_model_config()
84
+ model_path = resolve_relative_path(config['path'])
85
+
86
+ # Convert to Path object if it's a string for validation
87
+ if isinstance(model_path, str):
88
+ model_path_obj = Path(model_path)
89
+ else:
90
+ model_path_obj = model_path
91
+
92
+ # Check if model exists
93
+ if not model_path_obj.exists():
94
+ print(f"⚠️ Face swap model not found at: {model_path_obj}")
95
+ print("🔄 Attempting to download model...")
96
+ if not pre_check():
97
+ print("❌ Failed to download face swap model")
98
+ return None
99
+
100
+ # Verify model file size
101
+ if model_path_obj.stat().st_size < 1024: # Less than 1KB indicates corruption
102
+ print(f"⚠️ Face swap model appears corrupted: {model_path_obj}")
103
+ print("🔄 Attempting to re-download model...")
104
+ model_path_obj.unlink() # Delete corrupted file
105
+ if not pre_check():
106
+ print("❌ Failed to re-download face swap model")
107
+ return None
108
+
109
+ # Load model with string path (insightface expects string)
110
+ FRAME_PROCESSOR = insightface.model_zoo.get_model(
111
+ str(model_path_obj),
112
+ providers=SwitcherAI.globals.execution_providers
113
+ )
114
+ print("✅ Face swap processor initialized")
115
+
116
+ except Exception as e:
117
+ print(f"⚠️ Failed to initialize face swap processor: {e}")
118
+ FRAME_PROCESSOR = None
119
+
120
  return FRAME_PROCESSOR
121
 
122
 
 
129
 
130
  with THREAD_LOCK:
131
  if EMBEDDING_CONVERTER is None:
 
132
  try:
133
+ converter_path = resolve_relative_path(config['converter_path'])
134
+
135
+ # Convert to Path object if it's a string for validation
136
+ if isinstance(converter_path, str):
137
+ converter_path_obj = Path(converter_path)
138
+ else:
139
+ converter_path_obj = converter_path
140
+
141
+ # Check if converter exists
142
+ if not converter_path_obj.exists():
143
+ print(f"⚠️ Embedding converter not found at: {converter_path_obj}")
144
+ if not pre_check():
145
+ print("❌ Failed to download embedding converter")
146
+ return None
147
+
148
+ EMBEDDING_CONVERTER = insightface.model_zoo.get_model(
149
+ str(converter_path_obj),
150
+ providers=SwitcherAI.globals.execution_providers
151
+ )
152
+ print("✅ Embedding converter initialized")
153
+
154
+ except Exception as e:
155
+ print(f"⚠️ Failed to initialize embedding converter: {e}")
156
  EMBEDDING_CONVERTER = None
157
+
158
  return EMBEDDING_CONVERTER
159
 
160
 
 
166
 
167
 
168
  def pre_check() -> bool:
169
+ try:
170
+ download_directory_path = resolve_relative_path('../.assets/models')
171
+
172
+ # Ensure download directory exists
173
+ if isinstance(download_directory_path, str):
174
+ download_directory_path = Path(download_directory_path)
175
+ download_directory_path.mkdir(parents=True, exist_ok=True)
176
+
177
+ config = get_current_model_config()
178
+
179
+ # Download main model
180
+ download_urls = [config['url']]
181
+
182
+ # Download converter if needed
183
+ if config.get('requires_converter', False):
184
+ download_urls.append(config['converter_url'])
185
+
186
+ conditional_download(str(download_directory_path), download_urls)
187
+ return True
188
+
189
+ except Exception as e:
190
+ print(f"❌ Face swap pre-check failed: {e}")
191
+ return False
192
 
193
 
194
  def pre_process() -> bool:
195
+ try:
196
+ if not is_image(SwitcherAI.globals.source_path):
197
+ update_status(wording.get('select_image_source') + wording.get('exclamation_mark'), NAME)
198
+ return False
199
+ elif not get_one_face(cv2.imread(SwitcherAI.globals.source_path)):
200
+ update_status(wording.get('no_source_face_detected') + wording.get('exclamation_mark'), NAME)
201
+ return False
202
+ if not is_image(SwitcherAI.globals.target_path) and not is_video(SwitcherAI.globals.target_path):
203
+ update_status(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
204
+ return False
205
+
206
+ # Check if processor is available
207
+ processor = get_frame_processor()
208
+ if processor is None:
209
+ print("⚠️ Face swap processor not available")
210
+ return False
211
+
212
+ return True
213
+
214
+ except Exception as e:
215
+ print(f"⚠️ Face swap pre-process failed: {e}")
216
  return False
 
217
 
218
 
219
  def post_process() -> None:
 
224
 
225
  def prepare_source_embedding(source_face: Face) -> np.ndarray:
226
  """Prepare source face embedding based on model type"""
227
+ try:
228
+ config = get_current_model_config()
229
+ model_type = config['type']
230
+
231
+ if model_type == 'inswapper':
232
+ # Enhanced embedding preparation for inswapper
233
+ model_path = resolve_relative_path(config['path'])
234
+ model_initializer = get_static_model_initializer(str(model_path))
235
+ source_embedding = source_face.embedding.reshape((1, -1))
236
+ source_embedding = np.dot(source_embedding, model_initializer) / np.linalg.norm(source_embedding)
237
+ return source_embedding
238
+ elif model_type == 'simswap':
239
+ # Use embedding converter for simswap
240
+ converter = get_embedding_converter()
241
+ if converter is not None:
242
+ embedding = source_face.embedding.reshape(-1, 512)
243
+ try:
244
+ converted_embedding = converter.run(None, {'input': embedding})[0]
245
+ converted_embedding = converted_embedding.ravel()
246
+ normed_embedding = converted_embedding / np.linalg.norm(converted_embedding)
247
+ return normed_embedding.reshape(1, -1)
248
+ except Exception:
249
+ pass
250
+
251
+ # Fallback to original embedding
252
+ return source_face.embedding.reshape(1, -1)
253
+ else:
254
+ # Default behavior
255
+ return source_face.embedding.reshape(1, -1)
256
+
257
+ except Exception as e:
258
+ print(f"⚠️ Error preparing source embedding: {e}")
259
  return source_face.embedding.reshape(1, -1)
260
 
261
 
262
  def prepare_crop_frame(crop_frame: Frame) -> np.ndarray:
263
  """Prepare cropped frame for model input with normalization"""
264
+ try:
265
+ config = get_current_model_config()
266
+ model_mean = config['mean']
267
+ model_std = config['standard_deviation']
268
+
269
+ # Convert to float and normalize
270
+ crop_frame = crop_frame[:, :, ::-1] / 255.0
271
+ crop_frame = (crop_frame - model_mean) / model_std
272
+ crop_frame = crop_frame.transpose(2, 0, 1)
273
+ crop_frame = np.expand_dims(crop_frame, axis=0).astype(np.float32)
274
+ return crop_frame
275
+
276
+ except Exception as e:
277
+ print(f"⚠️ Error preparing crop frame: {e}")
278
+ return crop_frame
279
 
280
 
281
  def normalize_crop_frame(crop_frame: np.ndarray) -> Frame:
282
  """Normalize cropped frame back to image format"""
283
+ try:
284
+ config = get_current_model_config()
285
+ model_type = config['type']
286
+ model_mean = config['mean']
287
+ model_std = config['standard_deviation']
288
+
289
+ crop_frame = crop_frame.transpose(1, 2, 0)
290
+
291
+ # Apply reverse normalization for certain model types
292
+ if model_type in ['simswap']:
293
+ crop_frame = crop_frame * model_std + model_mean
294
+
295
+ crop_frame = crop_frame.clip(0, 1)
296
+ crop_frame = crop_frame[:, :, ::-1] * 255
297
+ return crop_frame.astype(np.uint8)
298
+
299
+ except Exception as e:
300
+ print(f"⚠️ Error normalizing crop frame: {e}")
301
+ return crop_frame.astype(np.uint8)
302
 
303
 
304
  def enhanced_swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
305
  """Enhanced face swapping with improved preprocessing"""
306
+ try:
307
+ processor = get_frame_processor()
308
+ if processor is None:
309
+ print("⚠️ Face swap processor not available")
310
+ return temp_frame
311
+
312
+ config = get_current_model_config()
313
+ model_type = config['type']
314
+
315
+ if model_type == 'inswapper':
316
+ # Use original method for inswapper
317
+ return processor.get(temp_frame, target_face, source_face, paste_back=True)
318
+ else:
319
+ # Enhanced method for other models
320
+ try:
321
+ # Prepare source embedding
322
+ source_embedding = prepare_source_embedding(source_face)
323
+
324
+ # Get crop region (this would need proper implementation)
325
+ # For now, fall back to original method
326
+ return processor.get(temp_frame, target_face, source_face, paste_back=True)
327
+ except Exception as e:
328
+ print(f"⚠️ Enhanced swap failed: {e}")
329
+ # Fallback to original method
330
+ return processor.get(temp_frame, target_face, source_face, paste_back=True)
331
+
332
+ except Exception as e:
333
+ print(f"⚠️ Face swap failed: {e}")
334
+ return temp_frame
335
 
336
 
337
  def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
338
  """Main face swapping function with model-specific handling"""
339
+ try:
340
+ processor = get_frame_processor()
341
+ if processor is None:
342
+ print("⚠️ Face swap processor not available, skipping swap")
343
+ return temp_frame
344
+
345
+ config = get_current_model_config()
346
+
347
+ # Use enhanced swapping for supported models
348
+ if config['type'] in ['simswap', 'inswapper']:
349
+ return enhanced_swap_face(source_face, target_face, temp_frame)
350
+ else:
351
+ # Original method
352
+ return processor.get(temp_frame, target_face, source_face, paste_back=True)
353
+
354
+ except Exception as e:
355
+ print(f"⚠️ Error in swap_face: {e}")
356
+ return temp_frame
357
 
358
 
359
  def process_frame(source_face: Face, reference_face: Face, temp_frame: Frame) -> Frame:
360
  """Process frame with enhanced face selection logic"""
361
+ try:
362
+ processor = get_frame_processor()
363
+ if processor is None:
364
+ print("⚠️ Face swap processor not available, skipping frame")
365
+ return temp_frame
366
+
367
+ if 'reference' in SwitcherAI.globals.face_recognition:
368
+ similar_faces = find_similar_faces(temp_frame, reference_face, SwitcherAI.globals.reference_face_distance)
369
+ if similar_faces:
370
+ for similar_face in similar_faces:
371
+ temp_frame = swap_face(source_face, similar_face, temp_frame)
372
+
373
+ if 'many' in SwitcherAI.globals.face_recognition:
374
+ many_faces = get_many_faces(temp_frame)
375
+ if many_faces:
376
+ # Sort faces by size (largest first) like the newer version
377
+ many_faces = sorted(many_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
378
+ for target_face in many_faces:
379
+ temp_frame = swap_face(source_face, target_face, temp_frame)
380
+
381
+ return temp_frame
382
+
383
+ except Exception as e:
384
+ print(f"⚠️ Error in process_frame: {e}")
385
+ return temp_frame
386
 
387
 
388
  def get_average_face(faces: List[Face]) -> Face:
 
399
 
400
  def process_frames(source_path: str, temp_frame_paths: List[str], update: Callable[[], None]) -> None:
401
  """Enhanced frame processing with better source face handling"""
402
+ try:
403
+ processor = get_frame_processor()
404
+ if processor is None:
405
+ print("⚠️ Face swap processor not available, skipping frame processing")
406
+ if update:
407
+ update()
408
+ return
409
+
410
+ source_frame = cv2.imread(source_path)
411
+ if source_frame is None:
412
+ print(f"⚠️ Failed to read source image: {source_path}")
413
+ if update:
414
+ update()
415
+ return
416
+
417
+ source_faces = get_many_faces(source_frame)
418
+
419
+ # Get best source face (largest)
420
+ if source_faces:
421
+ source_faces = sorted(source_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
422
+ source_face = source_faces[0]
423
+ else:
424
+ source_face = get_one_face(source_frame)
425
+
426
+ if source_face is None:
427
+ print("⚠️ No source face found")
428
+ if update:
429
+ update()
430
+ return
431
+
432
+ # Handle multiple source faces if available
433
+ if len(source_faces) > 1:
434
+ source_face = get_average_face(source_faces)
435
+
436
+ reference_face = get_face_reference() if 'reference' in SwitcherAI.globals.face_recognition else None
437
+
438
+ for temp_frame_path in temp_frame_paths:
439
+ try:
440
+ temp_frame = cv2.imread(temp_frame_path)
441
+ if temp_frame is not None:
442
+ result_frame = process_frame(source_face, reference_face, temp_frame)
443
+ cv2.imwrite(temp_frame_path, result_frame)
444
+ else:
445
+ print(f"⚠️ Failed to read frame: {temp_frame_path}")
446
+
447
+ except Exception as e:
448
+ print(f"⚠️ Error processing frame {temp_frame_path}: {e}")
449
+
450
+ if update:
451
+ update()
452
+
453
+ except Exception as e:
454
+ print(f"⚠️ Error in process_frames: {e}")
455
 
456
 
457
  def process_image(source_path: str, target_path: str, output_path: str) -> None:
458
  """Enhanced image processing"""
459
+ try:
460
+ processor = get_frame_processor()
461
+ if processor is None:
462
+ print("⚠️ Face swap processor not available, copying original image")
463
+ import shutil
464
+ shutil.copy2(target_path, output_path)
465
+ return
466
 
467
+ source_frame = cv2.imread(source_path)
468
+ if source_frame is None:
469
+ print(f"⚠️ Failed to read source image: {source_path}")
470
+ return
471
+
472
+ source_faces = get_many_faces(source_frame)
473
+
474
+ # Get best source face
475
+ if source_faces:
476
+ source_faces = sorted(source_faces, key=lambda x: (x.bbox[2] - x.bbox[0]) * (x.bbox[3] - x.bbox[1]), reverse=True)
477
+ source_face = source_faces[0]
478
+
479
+ # Handle multiple source faces
480
+ if len(source_faces) > 1:
481
+ source_face = get_average_face(source_faces)
482
+ else:
483
+ source_face = get_one_face(source_frame)
484
+
485
+ if source_face is None:
486
+ print("⚠️ No source face found")
487
+ return
488
+
489
+ target_frame = cv2.imread(target_path)
490
+ if target_frame is None:
491
+ print(f"⚠️ Failed to read target image: {target_path}")
492
+ return
493
+
494
+ reference_face = get_one_face(target_frame, SwitcherAI.globals.reference_face_position) if 'reference' in SwitcherAI.globals.face_recognition else None
495
+ result_frame = process_frame(source_face, reference_face, target_frame)
496
+ cv2.imwrite(output_path, result_frame)
497
+
498
+ except Exception as e:
499
+ print(f"⚠️ Error in process_image: {e}")
500
 
501
 
502
  def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
503
+ try:
504
+ conditional_set_face_reference(temp_frame_paths)
505
+ frame_processors.process_video(source_path, temp_frame_paths, process_frames)
506
+ except Exception as e:
507
+ print(f"⚠️ Error in process_video: {e}")
508
 
509
 
510
  def conditional_set_face_reference(temp_frame_paths: List[str]) -> None:
511
+ try:
512
+ if 'reference' in SwitcherAI.globals.face_recognition and not get_face_reference():
513
+ reference_frame = cv2.imread(temp_frame_paths[SwitcherAI.globals.reference_face_position])
514
+ if reference_frame is not None:
515
+ reference_face = get_one_face(reference_frame, SwitcherAI.globals.reference_face_position)
516
+ set_face_reference(reference_face)
517
+ else:
518
+ print(f"⚠️ Failed to read reference frame")
519
+ except Exception as e:
520
+ print(f"⚠️ Error setting face reference: {e}")