From 07e30fe781c04f6e4b3f95cde51d36f8c55206fd Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Thu, 17 Apr 2025 02:03:34 +0800 Subject: [PATCH 01/31] Revert "Update face_swapper.py" This reverts commit 104d8cf4d6b2ff526198ef9c5af87a8120e990bd. --- modules/processors/frame/face_swapper.py | 682 +++++++++++++++++------ 1 file changed, 523 insertions(+), 159 deletions(-) diff --git a/modules/processors/frame/face_swapper.py b/modules/processors/frame/face_swapper.py index b09e600..36b83d6 100644 --- a/modules/processors/frame/face_swapper.py +++ b/modules/processors/frame/face_swapper.py @@ -1,44 +1,56 @@ -import os # <-- Added for os.path.exists from typing import Any, List import cv2 import insightface import threading - +import numpy as np import modules.globals +import logging import modules.processors.frame.core -# Ensure update_status is imported if not already globally accessible -# If it's part of modules.core, it might already be accessible via modules.core.update_status from modules.core import update_status from modules.face_analyser import get_one_face, get_many_faces, default_source_face from modules.typing import Face, Frame -from modules.utilities import conditional_download, resolve_relative_path, is_image, is_video +from modules.utilities import ( + conditional_download, + is_image, + is_video, +) from modules.cluster_analysis import find_closest_centroid +import os FACE_SWAPPER = None THREAD_LOCK = threading.Lock() -NAME = 'DLC.FACE-SWAPPER' +NAME = "DLC.FACE-SWAPPER" + +abs_dir = os.path.dirname(os.path.abspath(__file__)) +models_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models" +) def pre_check() -> bool: - download_directory_path = resolve_relative_path('../models') - # Ensure both models are mentioned or downloaded if necessary - # Conditional download might need adjustment if you want it to fetch FP32 too - conditional_download(download_directory_path, ['https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx']) - # Add a check or download for the FP32 model if you have a URL - # conditional_download(download_directory_path, ['URL_TO_FP32_MODEL_HERE']) + download_directory_path = abs_dir + conditional_download( + download_directory_path, + [ + "https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx" + ], + ) return True def pre_start() -> bool: - # --- No changes needed in pre_start --- if not modules.globals.map_faces and not is_image(modules.globals.source_path): - update_status('Select an image for source path.', NAME) + update_status("Select an image for source path.", NAME) return False - elif not modules.globals.map_faces and not get_one_face(cv2.imread(modules.globals.source_path)): - update_status('No face in source path detected.', NAME) + elif not modules.globals.map_faces and not get_one_face( + cv2.imread(modules.globals.source_path) + ): + update_status("No face in source path detected.", NAME) return False - if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path): - update_status('Select an image or video for target path.', NAME) + if not is_image(modules.globals.target_path) and not is_video( + modules.globals.target_path + ): + update_status("Select an image or video for target path.", NAME) return False return True @@ -48,112 +60,110 @@ def get_face_swapper() -> Any: with THREAD_LOCK: if FACE_SWAPPER is None: - # --- MODIFICATION START --- - # Define paths for both FP32 and FP16 models - model_dir = resolve_relative_path('../models') - model_path_fp32 = os.path.join(model_dir, 'inswapper_128.onnx') - model_path_fp16 = os.path.join(model_dir, 'inswapper_128_fp16.onnx') - chosen_model_path = None - - # Prioritize FP32 model - if os.path.exists(model_path_fp32): - chosen_model_path = model_path_fp32 - update_status(f"Loading FP32 model: {os.path.basename(chosen_model_path)}", NAME) - # Fallback to FP16 model - elif os.path.exists(model_path_fp16): - chosen_model_path = model_path_fp16 - update_status(f"FP32 model not found. Loading FP16 model: {os.path.basename(chosen_model_path)}", NAME) - # Error if neither model is found - else: - error_message = f"Face Swapper model not found. Please ensure 'inswapper_128.onnx' (recommended) or 'inswapper_128_fp16.onnx' exists in the '{model_dir}' directory." - update_status(error_message, NAME) - raise FileNotFoundError(error_message) - - # Load the chosen model - try: - FACE_SWAPPER = insightface.model_zoo.get_model(chosen_model_path, providers=modules.globals.execution_providers) - except Exception as e: - update_status(f"Error loading Face Swapper model {os.path.basename(chosen_model_path)}: {e}", NAME) - # Optionally, re-raise the exception or handle it more gracefully - raise e - # --- MODIFICATION END --- + model_path = os.path.join(models_dir, "inswapper_128_fp16.onnx") + FACE_SWAPPER = insightface.model_zoo.get_model( + model_path, providers=modules.globals.execution_providers + ) return FACE_SWAPPER def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame: - # --- No changes needed in swap_face --- - swapper = get_face_swapper() - if swapper is None: - # Handle case where model failed to load - update_status("Face swapper model not loaded, skipping swap.", NAME) - return temp_frame - return swapper.get(temp_frame, target_face, source_face, paste_back=True) + face_swapper = get_face_swapper() + + # Apply the face swap + swapped_frame = face_swapper.get( + temp_frame, target_face, source_face, paste_back=True + ) + + if modules.globals.mouth_mask: + # Create a mask for the target face + face_mask = create_face_mask(target_face, temp_frame) + + # Create the mouth mask + mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon = ( + create_lower_mouth_mask(target_face, temp_frame) + ) + + # Apply the mouth area + swapped_frame = apply_mouth_area( + swapped_frame, mouth_cutout, mouth_box, face_mask, lower_lip_polygon + ) + + if modules.globals.show_mouth_mask_box: + mouth_mask_data = (mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon) + swapped_frame = draw_mouth_mask_visualization( + swapped_frame, target_face, mouth_mask_data + ) + + return swapped_frame def process_frame(source_face: Face, temp_frame: Frame) -> Frame: - # --- No changes needed in process_frame --- - # Ensure the frame is in RGB format if color correction is enabled - # Note: InsightFace swapper often expects BGR by default. Double-check if color issues appear. - # If color correction is needed *before* swapping and insightface needs BGR: - # original_was_bgr = True # Assume input is BGR - # if modules.globals.color_correction: - # temp_frame = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB) - # original_was_bgr = False # Now it's RGB + if modules.globals.color_correction: + temp_frame = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB) if modules.globals.many_faces: many_faces = get_many_faces(temp_frame) if many_faces: for target_face in many_faces: - temp_frame = swap_face(source_face, target_face, temp_frame) + if source_face and target_face: + temp_frame = swap_face(source_face, target_face, temp_frame) + else: + print("Face detection failed for target/source.") else: target_face = get_one_face(temp_frame) - if target_face: + if target_face and source_face: temp_frame = swap_face(source_face, target_face, temp_frame) - - # Convert back if necessary (example, might not be needed depending on workflow) - # if modules.globals.color_correction and not original_was_bgr: - # temp_frame = cv2.cvtColor(temp_frame, cv2.COLOR_RGB2BGR) - + else: + logging.error("Face detection failed for target or source.") return temp_frame + def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame: - # --- No changes needed in process_frame_v2 --- - # (Assuming swap_face handles the potential None return from get_face_swapper) if is_image(modules.globals.target_path): if modules.globals.many_faces: source_face = default_source_face() - for map_entry in modules.globals.souce_target_map: # Renamed 'map' to 'map_entry' - target_face = map_entry['target']['face'] + for map in modules.globals.source_target_map: + target_face = map["target"]["face"] temp_frame = swap_face(source_face, target_face, temp_frame) elif not modules.globals.many_faces: - for map_entry in modules.globals.souce_target_map: # Renamed 'map' to 'map_entry' - if "source" in map_entry: - source_face = map_entry['source']['face'] - target_face = map_entry['target']['face'] + for map in modules.globals.source_target_map: + if "source" in map: + source_face = map["source"]["face"] + target_face = map["target"]["face"] temp_frame = swap_face(source_face, target_face, temp_frame) elif is_video(modules.globals.target_path): if modules.globals.many_faces: source_face = default_source_face() - for map_entry in modules.globals.souce_target_map: # Renamed 'map' to 'map_entry' - target_frame = [f for f in map_entry['target_faces_in_frame'] if f['location'] == temp_frame_path] + for map in modules.globals.source_target_map: + target_frame = [ + f + for f in map["target_faces_in_frame"] + if f["location"] == temp_frame_path + ] for frame in target_frame: - for target_face in frame['faces']: + for target_face in frame["faces"]: temp_frame = swap_face(source_face, target_face, temp_frame) elif not modules.globals.many_faces: - for map_entry in modules.globals.souce_target_map: # Renamed 'map' to 'map_entry' - if "source" in map_entry: - target_frame = [f for f in map_entry['target_faces_in_frame'] if f['location'] == temp_frame_path] - source_face = map_entry['source']['face'] + for map in modules.globals.source_target_map: + if "source" in map: + target_frame = [ + f + for f in map["target_faces_in_frame"] + if f["location"] == temp_frame_path + ] + source_face = map["source"]["face"] for frame in target_frame: - for target_face in frame['faces']: + for target_face in frame["faces"]: temp_frame = swap_face(source_face, target_face, temp_frame) - else: # Fallback for neither image nor video (e.g., live feed?) + + else: detected_faces = get_many_faces(temp_frame) if modules.globals.many_faces: if detected_faces: @@ -162,97 +172,451 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame: temp_frame = swap_face(source_face, target_face, temp_frame) elif not modules.globals.many_faces: - if detected_faces and hasattr(modules.globals, 'simple_map') and modules.globals.simple_map: # Check simple_map exists - if len(detected_faces) <= len(modules.globals.simple_map['target_embeddings']): + if detected_faces: + if len(detected_faces) <= len( + modules.globals.simple_map["target_embeddings"] + ): for detected_face in detected_faces: - closest_centroid_index, _ = find_closest_centroid(modules.globals.simple_map['target_embeddings'], detected_face.normed_embedding) - temp_frame = swap_face(modules.globals.simple_map['source_faces'][closest_centroid_index], detected_face, temp_frame) + closest_centroid_index, _ = find_closest_centroid( + modules.globals.simple_map["target_embeddings"], + detected_face.normed_embedding, + ) + + temp_frame = swap_face( + modules.globals.simple_map["source_faces"][ + closest_centroid_index + ], + detected_face, + temp_frame, + ) else: - detected_faces_centroids = [face.normed_embedding for face in detected_faces] + detected_faces_centroids = [] + for face in detected_faces: + detected_faces_centroids.append(face.normed_embedding) i = 0 - for target_embedding in modules.globals.simple_map['target_embeddings']: - closest_centroid_index, _ = find_closest_centroid(detected_faces_centroids, target_embedding) - # Ensure index is valid before accessing detected_faces - if closest_centroid_index < len(detected_faces): - temp_frame = swap_face(modules.globals.simple_map['source_faces'][i], detected_faces[closest_centroid_index], temp_frame) + for target_embedding in modules.globals.simple_map[ + "target_embeddings" + ]: + closest_centroid_index, _ = find_closest_centroid( + detected_faces_centroids, target_embedding + ) + + temp_frame = swap_face( + modules.globals.simple_map["source_faces"][i], + detected_faces[closest_centroid_index], + temp_frame, + ) i += 1 return temp_frame -def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None: - # --- No changes needed in process_frames --- - # Note: Ensure get_one_face is called only once if possible for efficiency if !map_faces - source_face = None +def process_frames( + source_path: str, temp_frame_paths: List[str], progress: Any = None +) -> None: if not modules.globals.map_faces: - source_img = cv2.imread(source_path) - if source_img is not None: - source_face = get_one_face(source_img) - if source_face is None: - update_status(f"Could not find face in source image: {source_path}, skipping swap.", NAME) - # If no source face, maybe skip processing? Or handle differently. - # For now, it will proceed but swap_face might fail later. - - for temp_frame_path in temp_frame_paths: - temp_frame = cv2.imread(temp_frame_path) - if temp_frame is None: - update_status(f"Warning: Could not read frame {temp_frame_path}", NAME) - if progress: progress.update(1) # Still update progress even if frame fails - continue # Skip to next frame - - try: - if not modules.globals.map_faces: - if source_face: # Only process if source face was found - result = process_frame(source_face, temp_frame) - else: - result = temp_frame # No source face, return original frame - else: - result = process_frame_v2(temp_frame, temp_frame_path) - - cv2.imwrite(temp_frame_path, result) - except Exception as exception: - update_status(f"Error processing frame {os.path.basename(temp_frame_path)}: {exception}", NAME) - # Decide whether to 'pass' (continue processing other frames) or raise - pass # Continue processing other frames - finally: + source_face = get_one_face(cv2.imread(source_path)) + for temp_frame_path in temp_frame_paths: + temp_frame = cv2.imread(temp_frame_path) + try: + result = process_frame(source_face, temp_frame) + cv2.imwrite(temp_frame_path, result) + except Exception as exception: + print(exception) + pass + if progress: + progress.update(1) + else: + for temp_frame_path in temp_frame_paths: + temp_frame = cv2.imread(temp_frame_path) + try: + result = process_frame_v2(temp_frame, temp_frame_path) + cv2.imwrite(temp_frame_path, result) + except Exception as exception: + print(exception) + pass if progress: progress.update(1) def process_image(source_path: str, target_path: str, output_path: str) -> None: - # --- No changes needed in process_image --- - # Note: Added checks for successful image reads and face detection - target_frame = cv2.imread(target_path) # Read original target for processing - if target_frame is None: - update_status(f"Error: Could not read target image: {target_path}", NAME) - return - if not modules.globals.map_faces: - source_img = cv2.imread(source_path) - if source_img is None: - update_status(f"Error: Could not read source image: {source_path}", NAME) - return - source_face = get_one_face(source_img) - if source_face is None: - update_status(f"Error: No face found in source image: {source_path}", NAME) - return - + source_face = get_one_face(cv2.imread(source_path)) + target_frame = cv2.imread(target_path) result = process_frame(source_face, target_frame) + cv2.imwrite(output_path, result) else: if modules.globals.many_faces: - update_status('Many faces enabled. Using first source image (if applicable in v2). Processing...', NAME) - # For process_frame_v2 on single image, it reads the 'output_path' which should be a copy - # Let's process the 'target_frame' we read instead. - result = process_frame_v2(target_frame) # Process the frame directly - - # Write the final result to the output path - success = cv2.imwrite(output_path, result) - if not success: - update_status(f"Error: Failed to write output image to: {output_path}", NAME) + update_status( + "Many faces enabled. Using first source image. Progressing...", NAME + ) + target_frame = cv2.imread(output_path) + result = process_frame_v2(target_frame) + cv2.imwrite(output_path, result) def process_video(source_path: str, temp_frame_paths: List[str]) -> None: - # --- No changes needed in process_video --- if modules.globals.map_faces and modules.globals.many_faces: - update_status('Many faces enabled. Using first source image (if applicable in v2). Processing...', NAME) - # The core processing logic is delegated, which is good. - modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames) \ No newline at end of file + update_status( + "Many faces enabled. Using first source image. Progressing...", NAME + ) + modules.processors.frame.core.process_video( + source_path, temp_frame_paths, process_frames + ) + + +def create_lower_mouth_mask( + face: Face, frame: Frame +) -> (np.ndarray, np.ndarray, tuple, np.ndarray): + mask = np.zeros(frame.shape[:2], dtype=np.uint8) + mouth_cutout = None + landmarks = face.landmark_2d_106 + if landmarks is not None: + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + lower_lip_order = [ + 65, + 66, + 62, + 70, + 69, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 0, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 65, + ] + lower_lip_landmarks = landmarks[lower_lip_order].astype( + np.float32 + ) # Use float for precise calculations + + # Calculate the center of the landmarks + center = np.mean(lower_lip_landmarks, axis=0) + + # Expand the landmarks outward + expansion_factor = ( + 1 + modules.globals.mask_down_size + ) # Adjust this for more or less expansion + expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center + + # Extend the top lip part + toplip_indices = [ + 20, + 0, + 1, + 2, + 3, + 4, + 5, + ] # Indices for landmarks 2, 65, 66, 62, 70, 69, 18 + toplip_extension = ( + modules.globals.mask_size * 0.5 + ) # Adjust this factor to control the extension + for idx in toplip_indices: + direction = expanded_landmarks[idx] - center + direction = direction / np.linalg.norm(direction) + expanded_landmarks[idx] += direction * toplip_extension + + # Extend the bottom part (chin area) + chin_indices = [ + 11, + 12, + 13, + 14, + 15, + 16, + ] # Indices for landmarks 21, 22, 23, 24, 0, 8 + chin_extension = 2 * 0.2 # Adjust this factor to control the extension + for idx in chin_indices: + expanded_landmarks[idx][1] += ( + expanded_landmarks[idx][1] - center[1] + ) * chin_extension + + # Convert back to integer coordinates + expanded_landmarks = expanded_landmarks.astype(np.int32) + + # Calculate bounding box for the expanded lower mouth + min_x, min_y = np.min(expanded_landmarks, axis=0) + max_x, max_y = np.max(expanded_landmarks, axis=0) + + # Add some padding to the bounding box + padding = int((max_x - min_x) * 0.1) # 10% padding + min_x = max(0, min_x - padding) + min_y = max(0, min_y - padding) + max_x = min(frame.shape[1], max_x + padding) + max_y = min(frame.shape[0], max_y + padding) + + # Ensure the bounding box dimensions are valid + if max_x <= min_x or max_y <= min_y: + if (max_x - min_x) <= 1: + max_x = min_x + 1 + if (max_y - min_y) <= 1: + max_y = min_y + 1 + + # Create the mask + mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8) + cv2.fillPoly(mask_roi, [expanded_landmarks - [min_x, min_y]], 255) + + # Apply Gaussian blur to soften the mask edges + mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5) + + # Place the mask ROI in the full-sized mask + mask[min_y:max_y, min_x:max_x] = mask_roi + + # Extract the masked area from the frame + mouth_cutout = frame[min_y:max_y, min_x:max_x].copy() + + # Return the expanded lower lip polygon in original frame coordinates + lower_lip_polygon = expanded_landmarks + + return mask, mouth_cutout, (min_x, min_y, max_x, max_y), lower_lip_polygon + + +def draw_mouth_mask_visualization( + frame: Frame, face: Face, mouth_mask_data: tuple +) -> Frame: + landmarks = face.landmark_2d_106 + if landmarks is not None and mouth_mask_data is not None: + mask, mouth_cutout, (min_x, min_y, max_x, max_y), lower_lip_polygon = ( + mouth_mask_data + ) + + vis_frame = frame.copy() + + # Ensure coordinates are within frame bounds + height, width = vis_frame.shape[:2] + min_x, min_y = max(0, min_x), max(0, min_y) + max_x, max_y = min(width, max_x), min(height, max_y) + + # Adjust mask to match the region size + mask_region = mask[0 : max_y - min_y, 0 : max_x - min_x] + + # Remove the color mask overlay + # color_mask = cv2.applyColorMap((mask_region * 255).astype(np.uint8), cv2.COLORMAP_JET) + + # Ensure shapes match before blending + vis_region = vis_frame[min_y:max_y, min_x:max_x] + # Remove blending with color_mask + # if vis_region.shape[:2] == color_mask.shape[:2]: + # blended = cv2.addWeighted(vis_region, 0.7, color_mask, 0.3, 0) + # vis_frame[min_y:max_y, min_x:max_x] = blended + + # Draw the lower lip polygon + cv2.polylines(vis_frame, [lower_lip_polygon], True, (0, 255, 0), 2) + + # Remove the red box + # cv2.rectangle(vis_frame, (min_x, min_y), (max_x, max_y), (0, 0, 255), 2) + + # Visualize the feathered mask + feather_amount = max( + 1, + min( + 30, + (max_x - min_x) // modules.globals.mask_feather_ratio, + (max_y - min_y) // modules.globals.mask_feather_ratio, + ), + ) + # Ensure kernel size is odd + kernel_size = 2 * feather_amount + 1 + feathered_mask = cv2.GaussianBlur( + mask_region.astype(float), (kernel_size, kernel_size), 0 + ) + feathered_mask = (feathered_mask / feathered_mask.max() * 255).astype(np.uint8) + # Remove the feathered mask color overlay + # color_feathered_mask = cv2.applyColorMap(feathered_mask, cv2.COLORMAP_VIRIDIS) + + # Ensure shapes match before blending feathered mask + # if vis_region.shape == color_feathered_mask.shape: + # blended_feathered = cv2.addWeighted(vis_region, 0.7, color_feathered_mask, 0.3, 0) + # vis_frame[min_y:max_y, min_x:max_x] = blended_feathered + + # Add labels + cv2.putText( + vis_frame, + "Lower Mouth Mask", + (min_x, min_y - 10), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (255, 255, 255), + 1, + ) + cv2.putText( + vis_frame, + "Feathered Mask", + (min_x, max_y + 20), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (255, 255, 255), + 1, + ) + + return vis_frame + return frame + + +def apply_mouth_area( + frame: np.ndarray, + mouth_cutout: np.ndarray, + mouth_box: tuple, + face_mask: np.ndarray, + mouth_polygon: np.ndarray, +) -> np.ndarray: + min_x, min_y, max_x, max_y = mouth_box + box_width = max_x - min_x + box_height = max_y - min_y + + if ( + mouth_cutout is None + or box_width is None + or box_height is None + or face_mask is None + or mouth_polygon is None + ): + return frame + + try: + resized_mouth_cutout = cv2.resize(mouth_cutout, (box_width, box_height)) + roi = frame[min_y:max_y, min_x:max_x] + + if roi.shape != resized_mouth_cutout.shape: + resized_mouth_cutout = cv2.resize( + resized_mouth_cutout, (roi.shape[1], roi.shape[0]) + ) + + color_corrected_mouth = apply_color_transfer(resized_mouth_cutout, roi) + + # Use the provided mouth polygon to create the mask + polygon_mask = np.zeros(roi.shape[:2], dtype=np.uint8) + adjusted_polygon = mouth_polygon - [min_x, min_y] + cv2.fillPoly(polygon_mask, [adjusted_polygon], 255) + + # Apply feathering to the polygon mask + feather_amount = min( + 30, + box_width // modules.globals.mask_feather_ratio, + box_height // modules.globals.mask_feather_ratio, + ) + feathered_mask = cv2.GaussianBlur( + polygon_mask.astype(float), (0, 0), feather_amount + ) + feathered_mask = feathered_mask / feathered_mask.max() + + face_mask_roi = face_mask[min_y:max_y, min_x:max_x] + combined_mask = feathered_mask * (face_mask_roi / 255.0) + + combined_mask = combined_mask[:, :, np.newaxis] + blended = ( + color_corrected_mouth * combined_mask + roi * (1 - combined_mask) + ).astype(np.uint8) + + # Apply face mask to blended result + face_mask_3channel = ( + np.repeat(face_mask_roi[:, :, np.newaxis], 3, axis=2) / 255.0 + ) + final_blend = blended * face_mask_3channel + roi * (1 - face_mask_3channel) + + frame[min_y:max_y, min_x:max_x] = final_blend.astype(np.uint8) + except Exception as e: + pass + + return frame + + +def create_face_mask(face: Face, frame: Frame) -> np.ndarray: + mask = np.zeros(frame.shape[:2], dtype=np.uint8) + landmarks = face.landmark_2d_106 + if landmarks is not None: + # Convert landmarks to int32 + landmarks = landmarks.astype(np.int32) + + # Extract facial features + right_side_face = landmarks[0:16] + left_side_face = landmarks[17:32] + right_eye = landmarks[33:42] + right_eye_brow = landmarks[43:51] + left_eye = landmarks[87:96] + left_eye_brow = landmarks[97:105] + + # Calculate forehead extension + right_eyebrow_top = np.min(right_eye_brow[:, 1]) + left_eyebrow_top = np.min(left_eye_brow[:, 1]) + eyebrow_top = min(right_eyebrow_top, left_eyebrow_top) + + face_top = np.min([right_side_face[0, 1], left_side_face[-1, 1]]) + forehead_height = face_top - eyebrow_top + extended_forehead_height = int(forehead_height * 5.0) # Extend by 50% + + # Create forehead points + forehead_left = right_side_face[0].copy() + forehead_right = left_side_face[-1].copy() + forehead_left[1] -= extended_forehead_height + forehead_right[1] -= extended_forehead_height + + # Combine all points to create the face outline + face_outline = np.vstack( + [ + [forehead_left], + right_side_face, + left_side_face[ + ::-1 + ], # Reverse left side to create a continuous outline + [forehead_right], + ] + ) + + # Calculate padding + padding = int( + np.linalg.norm(right_side_face[0] - left_side_face[-1]) * 0.05 + ) # 5% of face width + + # Create a slightly larger convex hull for padding + hull = cv2.convexHull(face_outline) + hull_padded = [] + for point in hull: + x, y = point[0] + center = np.mean(face_outline, axis=0) + direction = np.array([x, y]) - center + direction = direction / np.linalg.norm(direction) + padded_point = np.array([x, y]) + direction * padding + hull_padded.append(padded_point) + + hull_padded = np.array(hull_padded, dtype=np.int32) + + # Fill the padded convex hull + cv2.fillConvexPoly(mask, hull_padded, 255) + + # Smooth the mask edges + mask = cv2.GaussianBlur(mask, (5, 5), 3) + + return mask + + +def apply_color_transfer(source, target): + """ + Apply color transfer from target to source image + """ + source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype("float32") + target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype("float32") + + source_mean, source_std = cv2.meanStdDev(source) + target_mean, target_std = cv2.meanStdDev(target) + + # Reshape mean and std to be broadcastable + source_mean = source_mean.reshape(1, 1, 3) + source_std = source_std.reshape(1, 1, 3) + target_mean = target_mean.reshape(1, 1, 3) + target_std = target_std.reshape(1, 1, 3) + + # Perform the color transfer + source = (source - source_mean) * (target_std / source_std) + target_mean + + return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR) From 01900dcfb59f2e311a42a3ac34946b8032e1591a Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Thu, 17 Apr 2025 02:39:05 +0800 Subject: [PATCH 02/31] Revert "Update metadata.py" This reverts commit 90d5c2854258938025787c0b08870a714be1114e. --- modules/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/metadata.py b/modules/metadata.py index 122c778..6a507e3 100644 --- a/modules/metadata.py +++ b/modules/metadata.py @@ -1,3 +1,3 @@ name = 'Deep-Live-Cam' -version = '1.9' +version = '1.8' edition = 'GitHub Edition' From 18b259da70e419878d0b3ee8bee26ef56bdb3a09 Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Thu, 17 Apr 2025 02:44:24 +0800 Subject: [PATCH 03/31] Update requirements.txt improves speed by 10 to 40% --- requirements.txt | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index b99c191..f21bf0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,23 +1,21 @@ +--extra-index-url https://download.pytorch.org/whl/cu118 + numpy>=1.23.5,<2 typing-extensions>=4.8.0 -opencv-python==4.11.0.86 -onnx==1.17.0 -cv2_enumerate_cameras==1.1.18.3 +opencv-python==4.10.0.84 +cv2_enumerate_cameras==1.1.15 +onnx==1.16.0 insightface==0.7.3 psutil==5.9.8 tk==0.1.0 customtkinter==5.2.2 pillow==11.1.0 -torch; sys_platform != 'darwin' --index-url https://download.pytorch.org/whl/cu126 -torch; sys_platform == 'darwin' --index-url https://download.pytorch.org/whl/cu126 -torchvision; sys_platform != 'darwin' --index-url https://download.pytorch.org/whl/cu126 -torchvision; sys_platform == 'darwin' --index-url https://download.pytorch.org/whl/cu126 +torch==2.5.1+cu118; sys_platform != 'darwin' +torch==2.5.1; sys_platform == 'darwin' +torchvision==0.20.1; sys_platform != 'darwin' +torchvision==0.20.1; sys_platform == 'darwin' onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64' -onnxruntime-gpu==1.21; sys_platform != 'darwin' +onnxruntime-gpu==1.17; sys_platform != 'darwin' tensorflow; sys_platform != 'darwin' opennsfw2==0.10.2 -protobuf==4.23.2 -tqdm==4.66.4 -gfpgan==1.3.8 -tkinterdnd2==0.4.2 -pygrabber==0.2 +protobuf==4.23.2 \ No newline at end of file From 874abb4e5987074b6d29b5797420ab65ccf298e3 Mon Sep 17 00:00:00 2001 From: KRSHH <136873090+KRSHH@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:34:10 +0530 Subject: [PATCH 04/31] v2 prebuilt --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 2e163f0..d5ffecb 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,13 @@ By using this software, you agree to these terms and commit to using it in a man Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions. +## Exclusive v2.0 Quick Start - Pre-built (Windows / Nvidia) + + + +##### This is the fastest build you can get if you have a discrete NVIDIA GPU. + +###### These Pre-builts are perfect for non-technical users or those who don't have time to, or can't manually install all the requirements. Just a heads-up: this is an open-source project, so you can also install it manually. This will be 60 days ahead on the open source version. ## TLDR; Live Deepfake in just 3 Clicks ![easysteps](https://github.com/user-attachments/assets/af825228-852c-411b-b787-ffd9aac72fc6) From 40e47a469cbebe6482a62743335f037e2f82a86b Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Sat, 19 Apr 2025 03:41:00 +0800 Subject: [PATCH 05/31] Update requirements.txt --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index f21bf0c..ec85127 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,12 +10,12 @@ psutil==5.9.8 tk==0.1.0 customtkinter==5.2.2 pillow==11.1.0 -torch==2.5.1+cu118; sys_platform != 'darwin' -torch==2.5.1; sys_platform == 'darwin' +torch==2.6.0+cu118; sys_platform != 'darwin' +torch==2.6.0; sys_platform == 'darwin' torchvision==0.20.1; sys_platform != 'darwin' torchvision==0.20.1; sys_platform == 'darwin' onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64' onnxruntime-gpu==1.17; sys_platform != 'darwin' tensorflow; sys_platform != 'darwin' opennsfw2==0.10.2 -protobuf==4.23.2 \ No newline at end of file +protobuf==4.23.2 From 75b5b096d64663874e6e0b11afb5abad4f573900 Mon Sep 17 00:00:00 2001 From: NeuroDonu <112660822+NeuroDonu@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:03:24 +0300 Subject: [PATCH 06/31] fix --- modules/processors/frame/core.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/processors/frame/core.py b/modules/processors/frame/core.py index 7d76704..6d99fd1 100644 --- a/modules/processors/frame/core.py +++ b/modules/processors/frame/core.py @@ -42,18 +42,29 @@ def get_frame_processors_modules(frame_processors: List[str]) -> List[ModuleType def set_frame_processors_modules_from_ui(frame_processors: List[str]) -> None: global FRAME_PROCESSORS_MODULES + current_processor_names = [proc.__name__.split('.')[-1] for proc in FRAME_PROCESSORS_MODULES] + for frame_processor, state in modules.globals.fp_ui.items(): - if state == True and frame_processor not in frame_processors: - frame_processor_module = load_frame_processor_module(frame_processor) - FRAME_PROCESSORS_MODULES.append(frame_processor_module) - modules.globals.frame_processors.append(frame_processor) - if state == False: + if state == True and frame_processor not in current_processor_names: try: frame_processor_module = load_frame_processor_module(frame_processor) - FRAME_PROCESSORS_MODULES.remove(frame_processor_module) - modules.globals.frame_processors.remove(frame_processor) - except: - pass + FRAME_PROCESSORS_MODULES.append(frame_processor_module) + if frame_processor not in modules.globals.frame_processors: + modules.globals.frame_processors.append(frame_processor) + except SystemExit: + print(f"Warning: Failed to load frame processor {frame_processor} requested by UI state.") + except Exception as e: + print(f"Warning: Error loading frame processor {frame_processor} requested by UI state: {e}") + + elif state == False and frame_processor in current_processor_names: + try: + module_to_remove = next((mod for mod in FRAME_PROCESSORS_MODULES if mod.__name__.endswith(f'.{frame_processor}')), None) + if module_to_remove: + FRAME_PROCESSORS_MODULES.remove(module_to_remove) + if frame_processor in modules.globals.frame_processors: + modules.globals.frame_processors.remove(frame_processor) + except Exception as e: + print(f"Warning: Error removing frame processor {frame_processor}: {e}") def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], progress: Any = None) -> None: with ThreadPoolExecutor(max_workers=modules.globals.execution_threads) as executor: From 890beb0eae2f2668219ada2756d40606381fc936 Mon Sep 17 00:00:00 2001 From: NeuroDonu <112660822+NeuroDonu@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:03:49 +0300 Subject: [PATCH 07/31] fix & add trt support --- modules/processors/frame/face_enhancer.py | 39 +++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/modules/processors/frame/face_enhancer.py b/modules/processors/frame/face_enhancer.py index 4e1fdff..de192e6 100644 --- a/modules/processors/frame/face_enhancer.py +++ b/modules/processors/frame/face_enhancer.py @@ -48,6 +48,17 @@ def pre_start() -> bool: return True +TENSORRT_AVAILABLE = False +try: + import torch_tensorrt + TENSORRT_AVAILABLE = True +except ImportError as im: + print(f"TensorRT is not available: {im}") + pass +except Exception as e: + print(f"TensorRT is not available: {e}") + pass + def get_face_enhancer() -> Any: global FACE_ENHANCER @@ -55,16 +66,26 @@ def get_face_enhancer() -> Any: if FACE_ENHANCER is None: model_path = os.path.join(models_dir, "GFPGANv1.4.pth") - match platform.system(): - case "Darwin": # Mac OS - if torch.backends.mps.is_available(): - mps_device = torch.device("mps") - FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1, device=mps_device) # type: ignore[attr-defined] - else: - FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined] - case _: # Other OS - FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined] + selected_device = None + device_priority = [] + if TENSORRT_AVAILABLE and torch.cuda.is_available(): + selected_device = torch.device("cuda") + device_priority.append("TensorRT+CUDA") + elif torch.cuda.is_available(): + selected_device = torch.device("cuda") + device_priority.append("CUDA") + elif torch.backends.mps.is_available() and platform.system() == "Darwin": + selected_device = torch.device("mps") + device_priority.append("MPS") + elif not torch.cuda.is_available(): + selected_device = torch.device("cpu") + device_priority.append("CPU") + + FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1, device=selected_device) + + # for debug: + print(f"Selected device: {selected_device} and device priority: {device_priority}") return FACE_ENHANCER From 181144ce3396533d89eb919ceb443643ef2f5173 Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Sun, 20 Apr 2025 03:02:23 +0800 Subject: [PATCH 08/31] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index ec85127..6d9f8b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,8 +10,8 @@ psutil==5.9.8 tk==0.1.0 customtkinter==5.2.2 pillow==11.1.0 -torch==2.6.0+cu118; sys_platform != 'darwin' -torch==2.6.0; sys_platform == 'darwin' +torch==2.5.1+cu118; sys_platform != 'darwin' +torch==2.5.1; sys_platform == 'darwin' torchvision==0.20.1; sys_platform != 'darwin' torchvision==0.20.1; sys_platform == 'darwin' onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64' From 84836932e64a5b615f9c0c88b8eb0b23d07c45b7 Mon Sep 17 00:00:00 2001 From: killerlux <74460450+killerlux@users.noreply.github.com> Date: Wed, 30 Apr 2025 23:09:12 +0200 Subject: [PATCH 09/31] Added cmomands for linux --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d5ffecb..166a2d1 100644 --- a/README.md +++ b/README.md @@ -133,12 +133,20 @@ Place these files in the "**models**" folder. We highly recommend using a `venv` to avoid issues. + For Windows: ```bash python -m venv venv venv\Scripts\activate pip install -r requirements.txt ``` +For Linux: +```bash +# Ensure you use the installed Python 3.10 +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` **For macOS:** From 37bac273020990bffaad42135355e9ba29528392 Mon Sep 17 00:00:00 2001 From: KRSHH <136873090+KRSHH@users.noreply.github.com> Date: Thu, 1 May 2025 22:10:52 +0530 Subject: [PATCH 10/31] Add files via upload --- media/Download.png | Bin 0 -> 8901 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 media/Download.png diff --git a/media/Download.png b/media/Download.png new file mode 100644 index 0000000000000000000000000000000000000000..c79397d1e3f52a4e25fea3c4ab8f8dd229aa81d9 GIT binary patch literal 8901 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 TF~aG+00000NkvXXu0mjfQ=Abh literal 0 HcmV?d00001 From 938aa9eaf165370200b499cb42213ee47ef8158b Mon Sep 17 00:00:00 2001 From: KRSHH <136873090+KRSHH@users.noreply.github.com> Date: Thu, 1 May 2025 22:11:21 +0530 Subject: [PATCH 11/31] Delete media/download.png --- media/download.png | Bin 9263 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 media/download.png diff --git a/media/download.png b/media/download.png deleted file mode 100644 index 917d7d8074217d9580cd844521dd28dbf92e2aaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9263 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 Rp=baA002ovPDHLkV1h+SRh9q% From 267a273cb266ebc0e008896e11b86a0631251983 Mon Sep 17 00:00:00 2001 From: KRSHH <136873090+KRSHH@users.noreply.github.com> Date: Thu, 1 May 2025 22:12:55 +0530 Subject: [PATCH 12/31] Download for windows --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d5ffecb..ffb4035 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,11 @@ By using this software, you agree to these terms and commit to using it in a man Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions. -## Exclusive v2.0 Quick Start - Pre-built (Windows / Nvidia) +## Exclusive v2.0 Quick Start - Pre-built (Windows) - + -##### This is the fastest build you can get if you have a discrete NVIDIA GPU. +##### This is the fastest build you can get if you have a discrete NVIDIA or AMD GPU. ###### These Pre-builts are perfect for non-technical users or those who don't have time to, or can't manually install all the requirements. Just a heads-up: this is an open-source project, so you can also install it manually. This will be 60 days ahead on the open source version. From d86c36dc4747ce705988a41c5a3a5f298bbe01c0 Mon Sep 17 00:00:00 2001 From: KRSHH <136873090+KRSHH@users.noreply.github.com> Date: Sun, 4 May 2025 23:44:01 +0530 Subject: [PATCH 13/31] Change Download URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dfee6b..14083f9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Users are expected to use this software responsibly and legally. If using a real ## Exclusive v2.0 Quick Start - Pre-built (Windows) - + ##### This is the fastest build you can get if you have a discrete NVIDIA or AMD GPU. From b1f610d432c38e731abb72888d48cebd387bb6ff Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Mon, 5 May 2025 08:30:44 +0800 Subject: [PATCH 14/31] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14083f9..6dfee6b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Users are expected to use this software responsibly and legally. If using a real ## Exclusive v2.0 Quick Start - Pre-built (Windows) - + ##### This is the fastest build you can get if you have a discrete NVIDIA or AMD GPU. From c9f36eb350dfb66296c92b604feeed4b1acc6b5f Mon Sep 17 00:00:00 2001 From: KUNJ SHAH Date: Mon, 5 May 2025 18:29:44 +0530 Subject: [PATCH 15/31] Update __init__.py --- modules/__init__.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/__init__.py b/modules/__init__.py index e69de29..8b50aa7 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -0,0 +1,23 @@ +import cv2 +import numpy as np + +# Define a new function that supports unicode characters in file paths +def imread_unicode(path, flags=cv2.IMREAD_COLOR): + return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags) + +# Override the original `cv2.imread` +cv2.imread = imread_unicode + +# Define a function to support unicode characters in file paths when saving +def imwrite_unicode(path, img, params=None): + # Encode the image + ext = path.split(".")[-1] # Get file extension + result, encoded_img = cv2.imencode(f".{ext}", img, params if params else []) + + if result: + encoded_img.tofile(path) # Save image using numpy's `tofile()` + return True + return False + +# Override `cv2.imwrite` +cv2.imwrite = imwrite_unicode From 9ecd2dab8311286e47e205a25ccd526f75d7564b Mon Sep 17 00:00:00 2001 From: KUNJ SHAH Date: Mon, 5 May 2025 13:10:00 +0000 Subject: [PATCH 16/31] changes --- modules/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/__init__.py b/modules/__init__.py index 8b50aa7..c740bae 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,3 +1,4 @@ +import os import cv2 import numpy as np @@ -11,8 +12,11 @@ cv2.imread = imread_unicode # Define a function to support unicode characters in file paths when saving def imwrite_unicode(path, img, params=None): # Encode the image - ext = path.split(".")[-1] # Get file extension - result, encoded_img = cv2.imencode(f".{ext}", img, params if params else []) + root, ext = os.path.splitext(path) + # If no extension is found, you can choose a default extension (e.g., ".png") + if not ext: + ext = ".png" + result, encoded_img = cv2.imencode(ext, img, params if params else []) if result: encoded_img.tofile(path) # Save image using numpy's `tofile()` @@ -20,4 +24,4 @@ def imwrite_unicode(path, img, params=None): return False # Override `cv2.imwrite` -cv2.imwrite = imwrite_unicode +cv2.imwrite = imwrite_unicode \ No newline at end of file From fe4a87e8f23a9bbd9dd92517f5dac64668ef31f5 Mon Sep 17 00:00:00 2001 From: KUNJ SHAH Date: Mon, 5 May 2025 13:19:29 +0000 Subject: [PATCH 17/31] update --- modules/__init__.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/modules/__init__.py b/modules/__init__.py index c740bae..7968738 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -2,26 +2,17 @@ import os import cv2 import numpy as np -# Define a new function that supports unicode characters in file paths +# Utility function to support unicode characters in file paths for reading def imread_unicode(path, flags=cv2.IMREAD_COLOR): return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags) -# Override the original `cv2.imread` -cv2.imread = imread_unicode - -# Define a function to support unicode characters in file paths when saving +# Utility function to support unicode characters in file paths for writing def imwrite_unicode(path, img, params=None): - # Encode the image root, ext = os.path.splitext(path) - # If no extension is found, you can choose a default extension (e.g., ".png") if not ext: ext = ".png" result, encoded_img = cv2.imencode(ext, img, params if params else []) - if result: - encoded_img.tofile(path) # Save image using numpy's `tofile()` + encoded_img.tofile(path) return True - return False - -# Override `cv2.imwrite` -cv2.imwrite = imwrite_unicode \ No newline at end of file + return False \ No newline at end of file From a64940def77f1201fd23efea6bc4d9dadae33a2b Mon Sep 17 00:00:00 2001 From: KUNJ SHAH Date: Mon, 5 May 2025 13:19:46 +0000 Subject: [PATCH 18/31] update --- modules/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/__init__.py b/modules/__init__.py index 7968738..45a9cac 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -12,7 +12,7 @@ def imwrite_unicode(path, img, params=None): if not ext: ext = ".png" result, encoded_img = cv2.imencode(ext, img, params if params else []) - if result: + result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else []) encoded_img.tofile(path) return True return False \ No newline at end of file From bdbd7dcfbcf0e450be4cefc96ca62b22bc39ddf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20B=C3=B6er?= <1067159+gboeer@users.noreply.github.com> Date: Wed, 7 May 2025 13:23:31 +0200 Subject: [PATCH 19/31] fix typos in ui.py --- modules/ui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ui.py b/modules/ui.py index 94fa5cf..ce599d6 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -429,7 +429,7 @@ def create_source_target_popup( POPUP.destroy() select_output_path(start) else: - update_pop_status("Atleast 1 source with target is required!") + update_pop_status("At least 1 source with target is required!") scrollable_frame = ctk.CTkScrollableFrame( POPUP, width=POPUP_SCROLL_WIDTH, height=POPUP_SCROLL_HEIGHT @@ -489,7 +489,7 @@ def update_popup_source( global source_label_dict source_path = ctk.filedialog.askopenfilename( - title=_("select an source image"), + title=_("select a source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -584,7 +584,7 @@ def select_source_path() -> None: PREVIEW.withdraw() source_path = ctk.filedialog.askopenfilename( - title=_("select an source image"), + title=_("select a source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -627,7 +627,7 @@ def select_target_path() -> None: PREVIEW.withdraw() target_path = ctk.filedialog.askopenfilename( - title=_("select an target image or video"), + title=_("select a target image or video"), initialdir=RECENT_DIRECTORY_TARGET, filetypes=[img_ft, vid_ft], ) @@ -1108,7 +1108,7 @@ def update_webcam_source( global source_label_dict_live source_path = ctk.filedialog.askopenfilename( - title=_("select an source image"), + title=_("select a source image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) @@ -1160,7 +1160,7 @@ def update_webcam_target( global target_label_dict_live target_path = ctk.filedialog.askopenfilename( - title=_("select an target image"), + title=_("select a target image"), initialdir=RECENT_DIRECTORY_SOURCE, filetypes=[img_ft], ) From 7063bba4b3dc5fe177384a2eeee7379a2f3d3d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20B=C3=B6er?= <1067159+gboeer@users.noreply.github.com> Date: Wed, 7 May 2025 13:24:54 +0200 Subject: [PATCH 20/31] fix typos in zh.json --- locales/zh.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/zh.json b/locales/zh.json index c029898..445edc7 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -1,11 +1,11 @@ { "Source x Target Mapper": "Source x Target Mapper", - "select an source image": "选择一个源图像", + "select a source image": "选择一个源图像", "Preview": "预览", - "select an target image or video": "选择一个目标图像或视频", + "select a target image or video": "选择一个目标图像或视频", "save image output file": "保存图像输出文件", "save video output file": "保存视频输出文件", - "select an target image": "选择一个目标图像", + "select a target image": "选择一个目标图像", "source": "源", "Select a target": "选择一个目标", "Select a face": "选择一张脸", @@ -36,11 +36,11 @@ "Select source image": "请选取源图像", "Select target image": "请选取目标图像", "Please provide mapping!": "请提供映射", - "Atleast 1 source with target is required!": "至少需要一个来源图像与目标图像相关!", + "At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!", "At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!", "Face could not be detected in last upload!": "最近上传的图像中没有检测到人脸!", "Select Camera:": "选择摄像头", "All mappings cleared!": "所有映射均已清除!", "Mappings successfully submitted!": "成功提交映射!", "Source x Target Mapper is already open.": "源 x 目标映射器已打开。" -} \ No newline at end of file +} From 75122da3890a61fabcd67240a68318f1fbfdfe70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gordon=20B=C3=B6er?= <1067159+gboeer@users.noreply.github.com> Date: Wed, 7 May 2025 13:30:22 +0200 Subject: [PATCH 21/31] Create german localization --- locales/de.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 locales/de.json diff --git a/locales/de.json b/locales/de.json new file mode 100644 index 0000000..9e23ecd --- /dev/null +++ b/locales/de.json @@ -0,0 +1,46 @@ +{ + "Source x Target Mapper": "Quelle x Ziel Zuordnung", + "select a source image": "Wähle ein Quellbild", + "Preview": "Vorschau", + "select a target image or video": "Wähle ein Zielbild oder Video", + "save image output file": "Bildausgabedatei speichern", + "save video output file": "Videoausgabedatei speichern", + "select a target image": "Wähle ein Zielbild", + "source": "Quelle", + "Select a target": "Wähle ein Ziel", + "Select a face": "Wähle ein Gesicht", + "Keep audio": "Audio beibehalten", + "Face Enhancer": "Gesichtsverbesserung", + "Many faces": "Mehrere Gesichter", + "Show FPS": "FPS anzeigen", + "Keep fps": "FPS beibehalten", + "Keep frames": "Frames beibehalten", + "Fix Blueish Cam": "Bläuliche Kamera korrigieren", + "Mouth Mask": "Mundmaske", + "Show Mouth Mask Box": "Mundmaskenrahmen anzeigen", + "Start": "Starten", + "Live": "Live", + "Destroy": "Beenden", + "Map faces": "Gesichter zuordnen", + "Processing...": "Verarbeitung läuft...", + "Processing succeed!": "Verarbeitung erfolgreich!", + "Processing ignored!": "Verarbeitung ignoriert!", + "Failed to start camera": "Kamera konnte nicht gestartet werden", + "Please complete pop-up or close it.": "Bitte das Pop-up komplettieren oder schließen.", + "Getting unique faces": "Einzigartige Gesichter erfassen", + "Please select a source image first": "Bitte zuerst ein Quellbild auswählen", + "No faces found in target": "Keine Gesichter im Zielbild gefunden", + "Add": "Hinzufügen", + "Clear": "Löschen", + "Submit": "Absenden", + "Select source image": "Quellbild auswählen", + "Select target image": "Zielbild auswählen", + "Please provide mapping!": "Bitte eine Zuordnung angeben!", + "At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!", + "At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!", + "Face could not be detected in last upload!": "Im letzten Upload konnte kein Gesicht erkannt werden!", + "Select Camera:": "Kamera auswählen:", + "All mappings cleared!": "Alle Zuordnungen gelöscht!", + "Mappings successfully submitted!": "Zuordnungen erfolgreich übermittelt!", + "Source x Target Mapper is already open.": "Quell-zu-Ziel-Zuordnung ist bereits geöffnet." +} From 4a7874a96814f08cc9b1568787875da2f2540640 Mon Sep 17 00:00:00 2001 From: VilkkuKoo <116868441+VilkkuKoo@users.noreply.github.com> Date: Sun, 11 May 2025 01:28:53 +0300 Subject: [PATCH 22/31] Added a Finnish translation (#1255) * Added finnish translations * Fixed a typo --- locales/fi.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 locales/fi.json diff --git a/locales/fi.json b/locales/fi.json new file mode 100644 index 0000000..240775d --- /dev/null +++ b/locales/fi.json @@ -0,0 +1,46 @@ +{ + "Source x Target Mapper": "Source x Target Kartoitin", + "select an source image": "Valitse lähde kuva", + "Preview": "Esikatsele", + "select an target image or video": "Valitse kohde kuva tai video", + "save image output file": "tallenna kuva", + "save video output file": "tallenna video", + "select an target image": "Valitse kohde kuva", + "source": "lähde", + "Select a target": "Valitse kohde", + "Select a face": "Valitse kasvot", + "Keep audio": "Säilytä ääni", + "Face Enhancer": "Kasvojen Parantaja", + "Many faces": "Useampia kasvoja", + "Show FPS": "Näytä FPS", + "Keep fps": "Säilytä FPS", + "Keep frames": "Säilytä ruudut", + "Fix Blueish Cam": "Korjaa Sinertävä Kamera", + "Mouth Mask": "Suu Maski", + "Show Mouth Mask Box": "Näytä Suu Maski Laatiko", + "Start": "Aloita", + "Live": "Live", + "Destroy": "Tuhoa", + "Map faces": "Kartoita kasvot", + "Processing...": "Prosessoi...", + "Processing succeed!": "Prosessointi onnistui!", + "Processing ignored!": "Prosessointi lopetettu!", + "Failed to start camera": "Kameran käynnistäminen epäonnistui", + "Please complete pop-up or close it.": "Viimeistele tai sulje ponnahdusikkuna", + "Getting unique faces": "Hankitaan uniikkeja kasvoja", + "Please select a source image first": "Valitse ensin lähde kuva", + "No faces found in target": "Kasvoja ei löydetty kohteessa", + "Add": "Lisää", + "Clear": "Tyhjennä", + "Submit": "Lähetä", + "Select source image": "Valitse lähde kuva", + "Select target image": "Valitse kohde kuva", + "Please provide mapping!": "Tarjoa kartoitus!", + "Atleast 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!", + "At least 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!", + "Face could not be detected in last upload!": "Kasvoja ei voitu tunnistaa edellisessä latauksessa!", + "Select Camera:": "Valitse Kamera:", + "All mappings cleared!": "Kaikki kartoitukset tyhjennetty!", + "Mappings successfully submitted!": "Kartoitukset lähetety onnistuneesti!", + "Source x Target Mapper is already open.": "Lähde x Kohde Kartoittaja on jo auki." +} From 969007039993c3f161bbc7226ceb6de73f93b6d9 Mon Sep 17 00:00:00 2001 From: Teo Jia Cheng Date: Tue, 13 May 2025 00:14:49 +0800 Subject: [PATCH 23/31] Update __init__.py --- modules/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/__init__.py b/modules/__init__.py index 45a9cac..6eca4a3 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -11,8 +11,8 @@ def imwrite_unicode(path, img, params=None): root, ext = os.path.splitext(path) if not ext: ext = ".png" - result, encoded_img = cv2.imencode(ext, img, params if params else []) - result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else []) + result, encoded_img = cv2.imencode(ext, img, params if params else []) + result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else []) encoded_img.tofile(path) return True return False \ No newline at end of file From 994a63c54658f11dadcc7f480fb9d5b78fb16bc6 Mon Sep 17 00:00:00 2001 From: Giovanna Date: Wed, 14 May 2025 19:24:13 -0300 Subject: [PATCH 24/31] [Added] pt br translate --- locales/pt-br.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 locales/pt-br.json diff --git a/locales/pt-br.json b/locales/pt-br.json new file mode 100644 index 0000000..0ed7931 --- /dev/null +++ b/locales/pt-br.json @@ -0,0 +1,46 @@ +{ + "Source x Target Mapper": "Mapeador de Origem x Destino", + "select an source image": "Escolha uma imagem de origem", + "Preview": "Prévia", + "select an target image or video": "Escolha uma imagem ou vídeo de destino", + "save image output file": "Salvar imagem final", + "save video output file": "Salvar vídeo final", + "select an target image": "Escolha uma imagem de destino", + "source": "Origem", + "Select a target": "Escolha o destino", + "Select a face": "Escolha um rosto", + "Keep audio": "Manter o áudio original", + "Face Enhancer": "Melhorar rosto", + "Many faces": "Vários rostos", + "Show FPS": "Mostrar FPS", + "Keep fps": "Manter FPS", + "Keep frames": "Manter frames", + "Fix Blueish Cam": "Corrigir tom azulado da câmera", + "Mouth Mask": "Máscara da boca", + "Show Mouth Mask Box": "Mostrar área da máscara da boca", + "Start": "Começar", + "Live": "Ao vivo", + "Destroy": "Destruir", + "Map faces": "Mapear rostos", + "Processing...": "Processando...", + "Processing succeed!": "Tudo certo!", + "Processing ignored!": "Processamento ignorado!", + "Failed to start camera": "Não foi possível iniciar a câmera", + "Please complete pop-up or close it.": "Finalize ou feche o pop-up", + "Getting unique faces": "Buscando rostos diferentes", + "Please select a source image first": "Selecione primeiro uma imagem de origem", + "No faces found in target": "Nenhum rosto encontrado na imagem de destino", + "Add": "Adicionar", + "Clear": "Limpar", + "Submit": "Enviar", + "Select source image": "Escolha a imagem de origem", + "Select target image": "Escolha a imagem de destino", + "Please provide mapping!": "Você precisa realizar o mapeamento!", + "Atleast 1 source with target is required!": "É necessária pelo menos uma origem com um destino!", + "At least 1 source with target is required!": "É necessária pelo menos uma origem com um destino!", + "Face could not be detected in last upload!": "Não conseguimos detectar o rosto na última imagem!", + "Select Camera:": "Escolher câmera:", + "All mappings cleared!": "Todos os mapeamentos foram removidos!", + "Mappings successfully submitted!": "Mapeamentos enviados com sucesso!", + "Source x Target Mapper is already open.": "O Mapeador de Origem x Destino já está aberto." +} From 6cb5de01f87d7ae7dd5a5146806cc039c02e96dd Mon Sep 17 00:00:00 2001 From: inwchamp1337 <115950933+inwchamp1337@users.noreply.github.com> Date: Mon, 19 May 2025 00:33:19 +0700 Subject: [PATCH 25/31] Added a Thai translation (#1284) * Added a Thai translation * Update th.json --- locales/th.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 locales/th.json diff --git a/locales/th.json b/locales/th.json new file mode 100644 index 0000000..d37a9e6 --- /dev/null +++ b/locales/th.json @@ -0,0 +1,45 @@ +{ + "Source x Target Mapper": "ตัวจับคู่ต้นทาง x ปลายทาง", + "select a source image": "เลือกรูปภาพต้นฉบับ", + "Preview": "ตัวอย่าง", + "select a target image or video": "เลือกรูปภาพหรือวิดีโอเป้าหมาย", + "save image output file": "บันทึกไฟล์รูปภาพ", + "save video output file": "บันทึกไฟล์วิดีโอ", + "select a target image": "เลือกรูปภาพเป้าหมาย", + "source": "ต้นฉบับ", + "Select a target": "เลือกเป้าหมาย", + "Select a face": "เลือกใบหน้า", + "Keep audio": "เก็บเสียง", + "Face Enhancer": "ปรับปรุงใบหน้า", + "Many faces": "หลายใบหน้า", + "Show FPS": "แสดง FPS", + "Keep fps": "คงค่า FPS", + "Keep frames": "คงค่าเฟรม", + "Fix Blueish Cam": "แก้ไขภาพอมฟ้าจากกล้อง", + "Mouth Mask": "มาสก์ปาก", + "Show Mouth Mask Box": "แสดงกรอบมาสก์ปาก", + "Start": "เริ่ม", + "Live": "สด", + "Destroy": "หยุด", + "Map faces": "จับคู่ใบหน้า", + "Processing...": "กำลังประมวลผล...", + "Processing succeed!": "ประมวลผลสำเร็จแล้ว!", + "Processing ignored!": "การประมวลผลถูกละเว้น", + "Failed to start camera": "ไม่สามารถเริ่มกล้องได้", + "Please complete pop-up or close it.": "โปรดดำเนินการในป๊อปอัปให้เสร็จสิ้น หรือปิด", + "Getting unique faces": "กำลังค้นหาใบหน้าที่ไม่ซ้ำกัน", + "Please select a source image first": "โปรดเลือกภาพต้นฉบับก่อน", + "No faces found in target": "ไม่พบใบหน้าในภาพเป้าหมาย", + "Add": "เพิ่ม", + "Clear": "ล้าง", + "Submit": "ส่ง", + "Select source image": "เลือกภาพต้นฉบับ", + "Select target image": "เลือกภาพเป้าหมาย", + "Please provide mapping!": "โปรดระบุการจับคู่!", + "At least 1 source with target is required!": "ต้องมีการจับคู่ต้นฉบับกับเป้าหมายอย่างน้อย 1 คู่!", + "Face could not be detected in last upload!": "ไม่สามารถตรวจพบใบหน้าในไฟล์อัปโหลดล่าสุด!", + "Select Camera:": "เลือกกล้อง:", + "All mappings cleared!": "ล้างการจับคู่ทั้งหมดแล้ว!", + "Mappings successfully submitted!": "ส่งการจับคู่สำเร็จแล้ว!", + "Source x Target Mapper is already open.": "ตัวจับคู่ต้นทาง x ปลายทาง เปิดอยู่แล้ว" +} \ No newline at end of file From 72049f3e91ddd4e6cff0f383307334998594f896 Mon Sep 17 00:00:00 2001 From: Chou Chamnan Date: Mon, 19 May 2025 00:33:53 +0700 Subject: [PATCH 26/31] Add khmer translation (#1291) * Add khmer language * Fix khmer language --------- Co-authored-by: Chamnan dev --- locales/km.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 locales/km.json diff --git a/locales/km.json b/locales/km.json new file mode 100644 index 0000000..17f01b7 --- /dev/null +++ b/locales/km.json @@ -0,0 +1,45 @@ +{ + "Source x Target Mapper": "ប្រភប x បន្ថែម Mapper", + "select a source image": "ជ្រើសរើសប្រភពរូបភាព", + "Preview": "បង្ហាញ", + "select a target image or video": "ជ្រើសរើសគោលដៅរូបភាពឬវីដេអូ", + "save image output file": "រក្សាទុកលទ្ធផលឯកសាររូបភាព", + "save video output file": "រក្សាទុកលទ្ធផលឯកសារវីដេអូ", + "select a target image": "ជ្រើសរើសគោលដៅរូបភាព", + "source": "ប្រភព", + "Select a target": "ជ្រើសរើសគោលដៅ", + "Select a face": "ជ្រើសរើសមុខ", + "Keep audio": "រម្លងសម្លេង", + "Face Enhancer": "ឧបករណ៍ពង្រឹងមុខ", + "Many faces": "ទម្រង់មុខច្រើន", + "Show FPS": "បង្ហាញ FPS", + "Keep fps": "រម្លង fps", + "Keep frames": "រម្លងទម្រង់", + "Fix Blueish Cam": "ជួសជុល Cam Blueish", + "Mouth Mask": "របាំងមាត់", + "Show Mouth Mask Box": "បង្ហាញប្រអប់របាំងមាត់", + "Start": "ចាប់ផ្ដើម", + "Live": "ផ្សាយផ្ទាល់", + "Destroy": "លុប", + "Map faces": "ផែនទីមុខ", + "Processing...": "កំពុងដំណើរការ...", + "Processing succeed!": "ការដំណើរការទទួលបានជោគជ័យ!", + "Processing ignored!": "ការដំណើរការមិនទទួលបានជោគជ័យ!", + "Failed to start camera": "បរាជ័យដើម្បីចាប់ផ្ដើមបើកកាមេរ៉ា", + "Please complete pop-up or close it.": "សូមបញ្ចប់ផ្ទាំងផុស ឬបិទវា.", + "Getting unique faces": "ការចាប់ផ្ដើមទម្រង់មុខប្លែក", + "Please select a source image first": "សូមជ្រើសរើសប្រភពរូបភាពដំបូង", + "No faces found in target": "រកអត់ឃើញមុខនៅក្នុងគោលដៅ", + "Add": "បន្ថែម", + "Clear": "សម្អាត", + "Submit": "បញ្ចូន", + "Select source image": "ជ្រើសរើសប្រភពរូបភាព", + "Select target image": "ជ្រើសរើសគោលដៅរូបភាព", + "Please provide mapping!": "សូមផ្ដល់នៅផែនទី", + "At least 1 source with target is required!": "ត្រូវការប្រភពយ៉ាងហោចណាស់ ១ ដែលមានគោលដៅ!", + "Face could not be detected in last upload!": "មុខមិនអាចភ្ជាប់នៅក្នុងការបង្ហេាះចុងក្រោយ!", + "Select Camera:": "ជ្រើសរើសកាមេរ៉ា", + "All mappings cleared!": "ផែនទីទាំងអស់ត្រូវបានសម្អាត!", + "Mappings successfully submitted!": "ផែនទីត្រូវបានបញ្ជូនជោគជ័យ!", + "Source x Target Mapper is already open.": "ប្រភព x Target Mapper បានបើករួចហើយ។" +} From fc312516e376c3e4b9489fa742da07b04c1c7d37 Mon Sep 17 00:00:00 2001 From: Jonah Hewett <73886297+j-hewett@users.noreply.github.com> Date: Wed, 21 May 2025 16:35:37 +0100 Subject: [PATCH 27/31] Add Spanish translation --- locales/es.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 locales/es.json diff --git a/locales/es.json b/locales/es.json new file mode 100644 index 0000000..19e175b --- /dev/null +++ b/locales/es.json @@ -0,0 +1,46 @@ +{ + "Source x Target Mapper": "Mapeador de fuente x destino", + "select a source image": "Seleccionar imagen fuente", + "Preview": "Vista previa", + "select a target image or video": "elegir un video o una imagen fuente", + "save image output file": "guardar imagen final", + "save video output file": "guardar video final", + "select a target image": "elegir una imagen objetiva", + "source": "fuente", + "Select a target": "Elegir un destino", + "Select a face": "Elegir una cara", + "Keep audio": "Mantener audio original", + "Face Enhancer": "Potenciador de caras", + "Many faces": "Varias caras", + "Show FPS": "Mostrar fps", + "Keep fps": "Mantener fps", + "Keep frames": "Mantener frames", + "Fix Blueish Cam": "Corregir tono azul de video", + "Mouth Mask": "Máscara de boca", + "Show Mouth Mask Box": "Mostrar área de la máscara de boca", + "Start": "Iniciar", + "Live": "En vivo", + "Destroy": "Borrar", + "Map faces": "Mapear caras", + "Processing...": "Procesando...", + "Processing succeed!": "¡Proceso terminado con éxito!", + "Processing ignored!": "¡Procesamiento omitido!", + "Failed to start camera": "No se pudo iniciar la cámara", + "Please complete pop-up or close it.": "Complete o cierre el pop-up", + "Getting unique faces": "Buscando caras únicas", + "Please select a source image first": "Primero, seleccione una imagen fuente", + "No faces found in target": "No se encontró una cara en el destino", + "Add": "Agregar", + "Clear": "Limpiar", + "Submit": "Enviar", + "Select source image": "Seleccionar imagen fuente", + "Select target image": "Seleccionar imagen destino", + "Please provide mapping!": "Por favor, proporcione un mapeo", + "At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.", + "At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.", + "Face could not be detected in last upload!": "¡No se pudo encontrar una cara en el último video o imagen!", + "Select Camera:": "Elegir cámara:", + "All mappings cleared!": "¡Todos los mapeos fueron borrados!", + "Mappings successfully submitted!": "Mapeos enviados con éxito!", + "Source x Target Mapper is already open.": "El mapeador de fuente x destino ya está abierto." +} \ No newline at end of file From de27fb8a8194a9b262d9d4acfa7e69aa6e4d3b20 Mon Sep 17 00:00:00 2001 From: Neurofix Date: Sun, 25 May 2025 14:49:54 +0900 Subject: [PATCH 28/31] Create ko.json Add korean --- locales/ko.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 locales/ko.json diff --git a/locales/ko.json b/locales/ko.json new file mode 100644 index 0000000..8f87c89 --- /dev/null +++ b/locales/ko.json @@ -0,0 +1,45 @@ +{ + "Source x Target Mapper": "소스 x 타겟 매퍼", + "select a source image": "소스 이미지 선택", + "Preview": "미리보기", + "select a target image or video": "타겟 이미지 또는 영상 선택", + "save image output file": "이미지 출력 파일 저장", + "save video output file": "영상 출력 파일 저장", + "select a target image": "타겟 이미지 선택", + "source": "소스", + "Select a target": "타겟 선택", + "Select a face": "얼굴 선택", + "Keep audio": "오디오 유지", + "Face Enhancer": "얼굴 향상", + "Many faces": "여러 얼굴", + "Show FPS": "FPS 표시", + "Keep fps": "FPS 유지", + "Keep frames": "프레임 유지", + "Fix Blueish Cam": "푸른빛 카메라 보정", + "Mouth Mask": "입 마스크", + "Show Mouth Mask Box": "입 마스크 박스 표시", + "Start": "시작", + "Live": "라이브", + "Destroy": "종료", + "Map faces": "얼굴 매핑", + "Processing...": "처리 중...", + "Processing succeed!": "처리 성공!", + "Processing ignored!": "처리 무시됨!", + "Failed to start camera": "카메라 시작 실패", + "Please complete pop-up or close it.": "팝업을 완료하거나 닫아주세요.", + "Getting unique faces": "고유 얼굴 가져오는 중", + "Please select a source image first": "먼저 소스 이미지를 선택해주세요", + "No faces found in target": "타겟에서 얼굴을 찾을 수 없음", + "Add": "추가", + "Clear": "지우기", + "Submit": "제출", + "Select source image": "소스 이미지 선택", + "Select target image": "타겟 이미지 선택", + "Please provide mapping!": "매핑을 입력해주세요!", + "At least 1 source with target is required!": "최소 하나의 소스와 타겟이 필요합니다!", + "Face could not be detected in last upload!": "최근 업로드에서 얼굴을 감지할 수 없습니다!", + "Select Camera:": "카메라 선택:", + "All mappings cleared!": "모든 매핑이 삭제되었습니다!", + "Mappings successfully submitted!": "매핑이 성공적으로 제출되었습니다!", + "Source x Target Mapper is already open.": "소스 x 타겟 매퍼가 이미 열려 있습니다." +} From 989106e9141a2c29f5180aaf74ea43fbf03fe358 Mon Sep 17 00:00:00 2001 From: Jasurbek Odilov <55162993+Jocund96@users.noreply.github.com> Date: Sun, 25 May 2025 21:28:07 +0200 Subject: [PATCH 29/31] Add files via upload --- locales/ru.json | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 locales/ru.json diff --git a/locales/ru.json b/locales/ru.json new file mode 100644 index 0000000..ebaa168 --- /dev/null +++ b/locales/ru.json @@ -0,0 +1,45 @@ +{ + "Source x Target Mapper": "Сопоставитель Источник x Цель", + "select a source image": "выберите исходное изображение", + "Preview": "Предпросмотр", + "select a target image or video": "выберите целевое изображение или видео", + "save image output file": "сохранить выходной файл изображения", + "save video output file": "сохранить выходной файл видео", + "select a target image": "выберите целевое изображение", + "source": "источник", + "Select a target": "Выберите целевое изображение", + "Select a face": "Выберите лицо", + "Keep audio": "Сохранить аудио", + "Face Enhancer": "Улучшение лица", + "Many faces": "Несколько лиц", + "Show FPS": "Показать FPS", + "Keep fps": "Сохранить FPS", + "Keep frames": "Сохранить кадры", + "Fix Blueish Cam": "Исправить синеву камеры", + "Mouth Mask": "Маска рта", + "Show Mouth Mask Box": "Показать рамку маски рта", + "Start": "Старт", + "Live": "В реальном времени", + "Destroy": "Остановить", + "Map faces": "Сопоставить лица", + "Processing...": "Обработка...", + "Processing succeed!": "Обработка успешна!", + "Processing ignored!": "Обработка проигнорирована!", + "Failed to start camera": "Не удалось запустить камеру", + "Please complete pop-up or close it.": "Пожалуйста, заполните всплывающее окно или закройте его.", + "Getting unique faces": "Получение уникальных лиц", + "Please select a source image first": "Сначала выберите исходное изображение, пожалуйста", + "No faces found in target": "В целевом изображении не найдено лиц", + "Add": "Добавить", + "Clear": "Очистить", + "Submit": "Отправить", + "Select source image": "Выбрать исходное изображение", + "Select target image": "Выбрать целевое изображение", + "Please provide mapping!": "Пожалуйста, укажите сопоставление!", + "At least 1 source with target is required!": "Требуется хотя бы 1 источник с целью!", + "Face could not be detected in last upload!": "Лицо не обнаружено в последнем загруженном изображении!", + "Select Camera:": "Выберите камеру:", + "All mappings cleared!": "Все сопоставления очищены!", + "Mappings successfully submitted!": "Сопоставления успешно отправлены!", + "Source x Target Mapper is already open.": "Сопоставитель Источник-Цель уже открыт." +} \ No newline at end of file From e791f2f18aad061131e52b6351789610ae9f94c8 Mon Sep 17 00:00:00 2001 From: zjy-dev <1208264743@qq.com> Date: Sun, 1 Jun 2025 00:40:29 +0800 Subject: [PATCH 30/31] docs: add cuDNN installation guidance for CUDA --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dfee6b..1f1624a 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,10 @@ pip install -r requirements.txt **CUDA Execution Provider (Nvidia)** 1. Install [CUDA Toolkit 11.8.0](https://developer.nvidia.com/cuda-11-8-0-download-archive) -2. Install dependencies: +2. Install [cuDNN v8.9.7 for CUDA 11.x](https://developer.nvidia.com/rdp/cudnn-archive) (required for onnxruntime-gpu): + - Download cuDNN v8.9.7 for CUDA 11.x + - Make sure the cuDNN bin directory is in your system PATH +3. Install dependencies: ```bash pip uninstall onnxruntime onnxruntime-gpu From 745d449ca62fed0d089f7abc743a7be677c7c544 Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Mon, 9 Jun 2025 00:34:26 +0800 Subject: [PATCH 31/31] Update README.md support for RTX 50xx --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f1624a..18efaba 100644 --- a/README.md +++ b/README.md @@ -187,13 +187,14 @@ pip install -r requirements.txt **CUDA Execution Provider (Nvidia)** -1. Install [CUDA Toolkit 11.8.0](https://developer.nvidia.com/cuda-11-8-0-download-archive) -2. Install [cuDNN v8.9.7 for CUDA 11.x](https://developer.nvidia.com/rdp/cudnn-archive) (required for onnxruntime-gpu): - - Download cuDNN v8.9.7 for CUDA 11.x +1. Install [CUDA Toolkit 12.8.0](https://developer.nvidia.com/cuda-12-8-0-download-archive) +2. Install [cuDNN v8.9.7 for CUDA 12.x](https://developer.nvidia.com/rdp/cudnn-archive) (required for onnxruntime-gpu): + - Download cuDNN v8.9.7 for CUDA 12.x - Make sure the cuDNN bin directory is in your system PATH 3. Install dependencies: ```bash +pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128 pip uninstall onnxruntime onnxruntime-gpu pip install onnxruntime-gpu==1.16.3 ```