3333FRAME_CACHE = deque (maxlen = 3 ) # Cache for frame reuse
3434FACE_DETECTION_CACHE = {} # Cache face detections
3535LAST_DETECTION_TIME = 0
36- DETECTION_INTERVAL = 0.033 # ~30 FPS detection rate for live mode
3736FRAME_SKIP_COUNTER = 0
3837ADAPTIVE_QUALITY = True
3938# --- END: Mac M1-M5 Optimizations ---
@@ -273,7 +272,7 @@ def get_faces_optimized(frame: Frame, use_cache: bool = True) -> Optional[List[F
273272 time_since_last = current_time - LAST_DETECTION_TIME
274273
275274 # Skip detection if too soon (adaptive frame skipping)
276- if time_since_last < DETECTION_INTERVAL and FACE_DETECTION_CACHE :
275+ if time_since_last < modules . globals . DETECTION_INTERVAL and FACE_DETECTION_CACHE :
277276 return FACE_DETECTION_CACHE .get ('faces' )
278277
279278 # Perform detection
@@ -412,6 +411,90 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
412411 return final_frame
413412
414413
414+ def _build_pairs_from_file_map (temp_frame_path : str ) -> list :
415+ """Build (source_face, target_face) pairs from source_target_map for image/video files."""
416+ pairs = []
417+ with modules .globals .MAP_LOCK :
418+ source_target_map = list (getattr (modules .globals , "source_target_map" , []))
419+ if not source_target_map :
420+ return pairs
421+
422+ if modules .globals .many_faces :
423+ source_face = default_source_face ()
424+ if not source_face :
425+ return pairs
426+ for map_data in source_target_map :
427+ if is_image (modules .globals .target_path ):
428+ target_face = map_data .get ("target" , {}).get ("face" )
429+ if target_face :
430+ pairs .append ((source_face , target_face ))
431+ elif is_video (modules .globals .target_path ):
432+ for frame_data in map_data .get ("target_faces_in_frame" , []):
433+ if frame_data and frame_data .get ("location" ) == temp_frame_path :
434+ for target_face in frame_data .get ("faces" , []):
435+ pairs .append ((source_face , target_face ))
436+ else :
437+ for map_data in source_target_map :
438+ source_face = map_data .get ("source" , {}).get ("face" )
439+ if not source_face :
440+ continue
441+ if is_image (modules .globals .target_path ):
442+ target_face = map_data .get ("target" , {}).get ("face" )
443+ if target_face :
444+ pairs .append ((source_face , target_face ))
445+ elif is_video (modules .globals .target_path ):
446+ for frame_data in map_data .get ("target_faces_in_frame" , []):
447+ if frame_data and frame_data .get ("location" ) == temp_frame_path :
448+ for target_face in frame_data .get ("faces" , []):
449+ pairs .append ((source_face , target_face ))
450+ return pairs
451+
452+
453+ def _build_pairs_live (processed_frame : Frame ) -> list :
454+ """Build (source_face, target_face) pairs for live/webcam mode."""
455+ pairs = []
456+ detected_faces = get_many_faces (processed_frame )
457+ if not detected_faces :
458+ return pairs
459+
460+ with modules .globals .MAP_LOCK :
461+ simple_map = dict (getattr (modules .globals , "simple_map" , None ) or {})
462+
463+ if modules .globals .many_faces :
464+ source_face = default_source_face ()
465+ if source_face :
466+ for target_face in detected_faces :
467+ pairs .append ((source_face , target_face ))
468+ elif simple_map :
469+ source_faces = simple_map .get ("source_faces" , [])
470+ target_embeddings = simple_map .get ("target_embeddings" , [])
471+ if source_faces and target_embeddings and len (source_faces ) == len (target_embeddings ):
472+ if len (detected_faces ) <= len (target_embeddings ):
473+ for detected_face in detected_faces :
474+ if detected_face .normed_embedding is None :
475+ continue
476+ closest_idx , _ = find_closest_centroid (target_embeddings , detected_face .normed_embedding )
477+ if 0 <= closest_idx < len (source_faces ):
478+ pairs .append ((source_faces [closest_idx ], detected_face ))
479+ else :
480+ detected_embeddings = [f .normed_embedding for f in detected_faces if f .normed_embedding is not None ]
481+ detected_faces_with_embedding = [f for f in detected_faces if f .normed_embedding is not None ]
482+ if not detected_embeddings :
483+ return pairs
484+ for i , target_embedding in enumerate (target_embeddings ):
485+ if 0 <= i < len (source_faces ):
486+ closest_idx , _ = find_closest_centroid (detected_embeddings , target_embedding )
487+ if 0 <= closest_idx < len (detected_faces_with_embedding ):
488+ pairs .append ((source_faces [i ], detected_faces_with_embedding [closest_idx ]))
489+ else :
490+ source_face = default_source_face ()
491+ # Reuse already-detected faces instead of running detection again
492+ target_face = min (detected_faces , key = lambda x : x .bbox [0 ])
493+ if source_face and target_face :
494+ pairs .append ((source_face , target_face ))
495+ return pairs
496+
497+
415498def process_frame_v2 (temp_frame : Frame , temp_frame_path : str = "" ) -> Frame :
416499 """Handles complex mapping scenarios (map_faces=True) and live streams."""
417500 if getattr (modules .globals , "opacity" , 1.0 ) == 0 :
@@ -425,100 +508,14 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
425508 swapped_face_bboxes = [] # Keep track of where swaps happened
426509
427510 # Determine source/target pairs based on mode
428- source_target_pairs = []
429-
430- # Ensure maps exist before accessing them
431- source_target_map = getattr (modules .globals , "source_target_map" , None )
432- simple_map = getattr (modules .globals , "simple_map" , None )
433-
434- # Check if target is a file path (image or video) or live stream
435- is_file_target = modules .globals .target_path and (is_image (modules .globals .target_path ) or is_video (modules .globals .target_path ))
511+ is_file_target = modules .globals .target_path and (
512+ is_image (modules .globals .target_path ) or is_video (modules .globals .target_path )
513+ )
436514
437515 if is_file_target :
438- # Processing specific image or video file with pre-analyzed maps
439- if source_target_map :
440- if modules .globals .many_faces :
441- source_face = default_source_face () # Use default source for all targets
442- if source_face :
443- for map_data in source_target_map :
444- if is_image (modules .globals .target_path ):
445- target_info = map_data .get ("target" , {})
446- if target_info : # Check if target info exists
447- target_face = target_info .get ("face" )
448- if target_face :
449- source_target_pairs .append ((source_face , target_face ))
450- elif is_video (modules .globals .target_path ):
451- # Find faces for the current frame_path in video map
452- target_frames_data = map_data .get ("target_faces_in_frame" , [])
453- if target_frames_data : # Check if frame data exists
454- target_frames = [f for f in target_frames_data if f and f .get ("location" ) == temp_frame_path ]
455- for frame_data in target_frames :
456- faces_in_frame = frame_data .get ("faces" , [])
457- if faces_in_frame : # Check if faces exist
458- for target_face in faces_in_frame :
459- source_target_pairs .append ((source_face , target_face ))
460- else : # Single face or specific mapping
461- for map_data in source_target_map :
462- source_info = map_data .get ("source" , {})
463- if not source_info : continue # Skip if no source info
464- source_face = source_info .get ("face" )
465- if not source_face : continue # Skip if no source defined for this map entry
466-
467- if is_image (modules .globals .target_path ):
468- target_info = map_data .get ("target" , {})
469- if target_info :
470- target_face = target_info .get ("face" )
471- if target_face :
472- source_target_pairs .append ((source_face , target_face ))
473- elif is_video (modules .globals .target_path ):
474- target_frames_data = map_data .get ("target_faces_in_frame" , [])
475- if target_frames_data :
476- target_frames = [f for f in target_frames_data if f and f .get ("location" ) == temp_frame_path ]
477- for frame_data in target_frames :
478- faces_in_frame = frame_data .get ("faces" , [])
479- if faces_in_frame :
480- for target_face in faces_in_frame :
481- source_target_pairs .append ((source_face , target_face ))
482-
516+ source_target_pairs = _build_pairs_from_file_map (temp_frame_path )
483517 else :
484- # Live stream or webcam processing (analyze faces on the fly)
485- detected_faces = get_many_faces (processed_frame )
486- if detected_faces :
487- if modules .globals .many_faces :
488- source_face = default_source_face () # Use default source for all detected targets
489- if source_face :
490- for target_face in detected_faces :
491- source_target_pairs .append ((source_face , target_face ))
492- elif simple_map :
493- # Use simple_map (source_faces <-> target_embeddings)
494- source_faces = simple_map .get ("source_faces" , [])
495- target_embeddings = simple_map .get ("target_embeddings" , [])
496-
497- if source_faces and target_embeddings and len (source_faces ) == len (target_embeddings ):
498- # Match detected faces to the closest target embedding
499- if len (detected_faces ) <= len (target_embeddings ):
500- # More targets defined than detected - match each detected face
501- for detected_face in detected_faces :
502- if detected_face .normed_embedding is None : continue
503- closest_idx , _ = find_closest_centroid (target_embeddings , detected_face .normed_embedding )
504- if 0 <= closest_idx < len (source_faces ):
505- source_target_pairs .append ((source_faces [closest_idx ], detected_face ))
506- else :
507- # More faces detected than targets defined - match each target embedding to closest detected face
508- detected_embeddings = [f .normed_embedding for f in detected_faces if f .normed_embedding is not None ]
509- detected_faces_with_embedding = [f for f in detected_faces if f .normed_embedding is not None ]
510- if not detected_embeddings : return processed_frame # No embeddings to match
511-
512- for i , target_embedding in enumerate (target_embeddings ):
513- if 0 <= i < len (source_faces ): # Ensure source face exists for this embedding
514- closest_idx , _ = find_closest_centroid (detected_embeddings , target_embedding )
515- if 0 <= closest_idx < len (detected_faces_with_embedding ):
516- source_target_pairs .append ((source_faces [i ], detected_faces_with_embedding [closest_idx ]))
517- else : # Fallback: if no map, use default source for the single detected face (if any)
518- source_face = default_source_face ()
519- target_face = get_one_face (processed_frame , detected_faces ) # Use faces already detected
520- if source_face and target_face :
521- source_target_pairs .append ((source_face , target_face ))
518+ source_target_pairs = _build_pairs_live (processed_frame )
522519
523520
524521 # Perform swaps based on the collected pairs
0 commit comments