Mouth Mask And UI

pull/746/head
KRSHH 2024-10-28 10:56:01 +05:30
parent 4b6f60f59b
commit acc8e778b6
3 changed files with 478 additions and 124 deletions

View File

@ -36,3 +36,8 @@ fp_ui: Dict[str, bool] = {"face_enhancer": False} # Initialize with default val
camera_input_combobox = None camera_input_combobox = None
webcam_preview_running = False webcam_preview_running = False
show_fps = False # Initialize with default value show_fps = False # Initialize with default value
mouth_mask = False
show_mouth_mask_box = False
mask_down_size = 0.5
mask_size = 1.0
mask_feather_ratio = 8

View File

@ -2,35 +2,49 @@ from typing import Any, List
import cv2 import cv2
import insightface import insightface
import threading import threading
import numpy as np
import modules.globals import modules.globals
import modules.processors.frame.core import modules.processors.frame.core
from modules.core import update_status from modules.core import update_status
from modules.face_analyser import get_one_face, get_many_faces, default_source_face from modules.face_analyser import get_one_face, get_many_faces, default_source_face
from modules.typing import Face, Frame 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,
resolve_relative_path,
is_image,
is_video,
)
from modules.cluster_analysis import find_closest_centroid from modules.cluster_analysis import find_closest_centroid
FACE_SWAPPER = None FACE_SWAPPER = None
THREAD_LOCK = threading.Lock() THREAD_LOCK = threading.Lock()
NAME = 'DLC.FACE-SWAPPER' NAME = "DLC.FACE-SWAPPER"
def pre_check() -> bool: def pre_check() -> bool:
download_directory_path = resolve_relative_path('../models') download_directory_path = resolve_relative_path("../models")
conditional_download(download_directory_path, ['https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx']) conditional_download(
download_directory_path,
[
"https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx"
],
)
return True return True
def pre_start() -> bool: def pre_start() -> bool:
if not modules.globals.map_faces and not is_image(modules.globals.source_path): 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 return False
elif not modules.globals.map_faces and not get_one_face(cv2.imread(modules.globals.source_path)): elif not modules.globals.map_faces and not get_one_face(
update_status('No face in source path detected.', NAME) cv2.imread(modules.globals.source_path)
):
update_status("No face in source path detected.", NAME)
return False return False
if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path): if not is_image(modules.globals.target_path) and not is_video(
update_status('Select an image or video for target path.', NAME) modules.globals.target_path
):
update_status("Select an image or video for target path.", NAME)
return False return False
return True return True
@ -40,13 +54,35 @@ def get_face_swapper() -> Any:
with THREAD_LOCK: with THREAD_LOCK:
if FACE_SWAPPER is None: if FACE_SWAPPER is None:
model_path = resolve_relative_path('../models/inswapper_128_fp16.onnx') model_path = resolve_relative_path("../models/inswapper_128_fp16.onnx")
FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=modules.globals.execution_providers) FACE_SWAPPER = insightface.model_zoo.get_model(
model_path, providers=modules.globals.execution_providers
)
return FACE_SWAPPER return FACE_SWAPPER
def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame: def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True) face_swapper = get_face_swapper()
swapped_frame = face_swapper.get(
temp_frame, target_face, source_face, paste_back=True
)
if modules.globals.mouth_mask:
face_mask = create_face_mask(target_face, temp_frame)
mouth_mask_data = create_lower_mouth_mask(target_face, temp_frame)
mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon = mouth_mask_data
if mouth_box is not None:
swapped_frame = apply_mouth_area(
swapped_frame, mouth_cutout, mouth_box, face_mask, lower_lip_polygon
)
if modules.globals.show_mouth_mask_box:
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: def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
@ -71,34 +107,42 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
if modules.globals.many_faces: if modules.globals.many_faces:
source_face = default_source_face() source_face = default_source_face()
for map in modules.globals.souce_target_map: for map in modules.globals.souce_target_map:
target_face = map['target']['face'] target_face = map["target"]["face"]
temp_frame = swap_face(source_face, target_face, temp_frame) temp_frame = swap_face(source_face, target_face, temp_frame)
elif not modules.globals.many_faces: elif not modules.globals.many_faces:
for map in modules.globals.souce_target_map: for map in modules.globals.souce_target_map:
if "source" in map: if "source" in map:
source_face = map['source']['face'] source_face = map["source"]["face"]
target_face = map['target']['face'] target_face = map["target"]["face"]
temp_frame = swap_face(source_face, target_face, temp_frame) temp_frame = swap_face(source_face, target_face, temp_frame)
elif is_video(modules.globals.target_path): elif is_video(modules.globals.target_path):
if modules.globals.many_faces: if modules.globals.many_faces:
source_face = default_source_face() source_face = default_source_face()
for map in modules.globals.souce_target_map: for map in modules.globals.souce_target_map:
target_frame = [f for f in map['target_faces_in_frame'] if f['location'] == temp_frame_path] target_frame = [
f
for f in map["target_faces_in_frame"]
if f["location"] == temp_frame_path
]
for frame in target_frame: 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) temp_frame = swap_face(source_face, target_face, temp_frame)
elif not modules.globals.many_faces: elif not modules.globals.many_faces:
for map in modules.globals.souce_target_map: for map in modules.globals.souce_target_map:
if "source" in map: if "source" in map:
target_frame = [f for f in map['target_faces_in_frame'] if f['location'] == temp_frame_path] target_frame = [
source_face = map['source']['face'] 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 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) temp_frame = swap_face(source_face, target_face, temp_frame)
else: else:
detected_faces = get_many_faces(temp_frame) detected_faces = get_many_faces(temp_frame)
@ -110,25 +154,46 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
elif not modules.globals.many_faces: elif not modules.globals.many_faces:
if detected_faces: if detected_faces:
if len(detected_faces) <= len(modules.globals.simple_map['target_embeddings']): if len(detected_faces) <= len(
modules.globals.simple_map["target_embeddings"]
):
for detected_face in detected_faces: for detected_face in detected_faces:
closest_centroid_index, _ = find_closest_centroid(modules.globals.simple_map['target_embeddings'], detected_face.normed_embedding) 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) temp_frame = swap_face(
modules.globals.simple_map["source_faces"][
closest_centroid_index
],
detected_face,
temp_frame,
)
else: else:
detected_faces_centroids = [] detected_faces_centroids = []
for face in detected_faces: for face in detected_faces:
detected_faces_centroids.append(face.normed_embedding) detected_faces_centroids.append(face.normed_embedding)
i = 0 i = 0
for target_embedding in modules.globals.simple_map['target_embeddings']: for target_embedding in modules.globals.simple_map[
closest_centroid_index, _ = find_closest_centroid(detected_faces_centroids, target_embedding) "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) temp_frame = swap_face(
modules.globals.simple_map["source_faces"][i],
detected_faces[closest_centroid_index],
temp_frame,
)
i += 1 i += 1
return temp_frame return temp_frame
def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None: def process_frames(
source_path: str, temp_frame_paths: List[str], progress: Any = None
) -> None:
if not modules.globals.map_faces: if not modules.globals.map_faces:
source_face = get_one_face(cv2.imread(source_path)) source_face = get_one_face(cv2.imread(source_path))
for temp_frame_path in temp_frame_paths: for temp_frame_path in temp_frame_paths:
@ -162,7 +227,9 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
cv2.imwrite(output_path, result) cv2.imwrite(output_path, result)
else: else:
if modules.globals.many_faces: if modules.globals.many_faces:
update_status('Many faces enabled. Using first source image. Progressing...', NAME) update_status(
"Many faces enabled. Using first source image. Progressing...", NAME
)
target_frame = cv2.imread(output_path) target_frame = cv2.imread(output_path)
result = process_frame_v2(target_frame) result = process_frame_v2(target_frame)
cv2.imwrite(output_path, result) cv2.imwrite(output_path, result)
@ -170,5 +237,250 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
def process_video(source_path: str, temp_frame_paths: List[str]) -> None: def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
if modules.globals.map_faces and modules.globals.many_faces: if modules.globals.map_faces and modules.globals.many_faces:
update_status('Many faces enabled. Using first source image. Progressing...', NAME) update_status(
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames) "Many faces enabled. Using first source image. Progressing...", NAME
)
modules.processors.frame.core.process_video(
source_path, temp_frame_paths, process_frames
)
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:
landmarks = landmarks.astype(np.int32)
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]
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)
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
face_outline = np.vstack(
[
[forehead_left],
right_side_face,
left_side_face[::-1],
[forehead_right],
]
)
padding = int(np.linalg.norm(right_side_face[0] - left_side_face[-1]) * 0.05)
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)
cv2.fillConvexPoly(mask, hull_padded, 255)
mask = cv2.GaussianBlur(mask, (5, 5), 3)
return mask
def create_lower_mouth_mask(face: Face, frame: Frame) -> tuple:
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
mouth_cutout = None
landmarks = face.landmark_2d_106
if landmarks is not None:
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)
center = np.mean(lower_lip_landmarks, axis=0)
expansion_factor = 1 + modules.globals.mask_down_size
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
toplip_indices = [20, 0, 1, 2, 3, 4, 5]
toplip_extension = modules.globals.mask_size * 0.5
for idx in toplip_indices:
direction = expanded_landmarks[idx] - center
direction = direction / np.linalg.norm(direction)
expanded_landmarks[idx] += direction * toplip_extension
chin_indices = [11, 12, 13, 14, 15, 16]
chin_extension = 2 * 0.2
for idx in chin_indices:
expanded_landmarks[idx][1] += (
expanded_landmarks[idx][1] - center[1]
) * chin_extension
expanded_landmarks = expanded_landmarks.astype(np.int32)
min_x, min_y = np.min(expanded_landmarks, axis=0)
max_x, max_y = np.max(expanded_landmarks, axis=0)
padding = int((max_x - min_x) * 0.1)
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)
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
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)
mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5)
mask[min_y:max_y, min_x:max_x] = mask_roi
mouth_cutout = frame[min_y:max_y, min_x:max_x].copy()
return mask, mouth_cutout, (min_x, min_y, max_x, max_y), expanded_landmarks
return mask, mouth_cutout, None, None
def apply_mouth_area(
frame: Frame,
mouth_cutout: np.ndarray,
mouth_box: tuple,
face_mask: np.ndarray,
mouth_polygon: np.ndarray,
) -> Frame:
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)
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)
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)
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 apply_color_transfer(source: np.ndarray, target: np.ndarray) -> np.ndarray:
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)
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)
source = (source - source_mean) * (target_std / source_std) + target_mean
return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR)
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()
# Draw the lower lip polygon
cv2.polylines(vis_frame, [lower_lip_polygon], True, (0, 255, 0), 2)
# Add labels
cv2.putText(
vis_frame,
"Lower Mouth Mask",
(min_x, min_y - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(255, 255, 255),
1,
)
return vis_frame
return frame

View File

@ -215,6 +215,10 @@ def save_switch_states():
"live_resizable": modules.globals.live_resizable, "live_resizable": modules.globals.live_resizable,
"fp_ui": modules.globals.fp_ui, "fp_ui": modules.globals.fp_ui,
"show_fps": modules.globals.show_fps, "show_fps": modules.globals.show_fps,
"mouth_mask": modules.globals.mouth_mask,
"show_mouth_mask_box": modules.globals.show_mouth_mask_box,
"mask_down_size": modules.globals.mask_down_size,
"mask_feather_ratio": modules.globals.mask_feather_ratio,
} }
with open("switch_states.json", "w") as f: with open("switch_states.json", "w") as f:
json.dump(switch_states, f) json.dump(switch_states, f)
@ -230,13 +234,17 @@ def load_switch_states():
modules.globals.many_faces = switch_states.get("many_faces", False) modules.globals.many_faces = switch_states.get("many_faces", False)
modules.globals.map_faces = switch_states.get("map_faces", False) modules.globals.map_faces = switch_states.get("map_faces", False)
modules.globals.color_correction = switch_states.get("color_correction", False) modules.globals.color_correction = switch_states.get("color_correction", False)
modules.globals.nsfw_filter = switch_states.get( modules.globals.nsfw_filter = switch_states.get("nsfw_filter", False)
"nsfw_filter", False
) # Add this line
modules.globals.live_mirror = switch_states.get("live_mirror", False) modules.globals.live_mirror = switch_states.get("live_mirror", False)
modules.globals.live_resizable = switch_states.get("live_resizable", False) modules.globals.live_resizable = switch_states.get("live_resizable", False)
modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False}) modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False})
modules.globals.show_fps = switch_states.get("show_fps", False) modules.globals.show_fps = switch_states.get("show_fps", False)
modules.globals.mouth_mask = switch_states.get("mouth_mask", False)
modules.globals.show_mouth_mask_box = switch_states.get(
"show_mouth_mask_box", False
)
modules.globals.mask_down_size = switch_states.get("mask_down_size", 0.5)
modules.globals.mask_feather_ratio = switch_states.get("mask_feather_ratio", 8)
except FileNotFoundError: except FileNotFoundError:
# If the file doesn't exist, use default values # If the file doesn't exist, use default values
pass pass
@ -268,7 +276,7 @@ def create_root(
root.configure(bg="#1a1a1a") root.configure(bg="#1a1a1a")
root.protocol("WM_DELETE_WINDOW", lambda: destroy()) root.protocol("WM_DELETE_WINDOW", lambda: destroy())
root.resizable(True, True) root.resizable(True, True)
root.attributes("-alpha", 1.0) # Set window opacity to fully opaque root.attributes("-alpha", 1.0)
main_frame = ctk.CTkFrame(root, fg_color="#1a1a1a") main_frame = ctk.CTkFrame(root, fg_color="#1a1a1a")
main_frame.pack(fill="both", expand=True, padx=20, pady=20) main_frame.pack(fill="both", expand=True, padx=20, pady=20)
@ -331,19 +339,23 @@ def create_root(
options_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15) options_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15)
options_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") options_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10, sticky="nsew")
# Create a single column for options, centered # Create two columns for options
options_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e") left_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e")
options_column.pack(expand=True) left_column.pack(side="left", padx=20, expand=True)
# Switches right_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e")
right_column.pack(side="right", padx=20, expand=True)
# Left column controls
keep_fps_value = ctk.BooleanVar(value=modules.globals.keep_fps) keep_fps_value = ctk.BooleanVar(value=modules.globals.keep_fps)
keep_fps_checkbox = ctk.CTkSwitch( keep_fps_checkbox = ctk.CTkSwitch(
options_column, left_column,
text="Keep fps", text="Keep fps",
variable=keep_fps_value, variable=keep_fps_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr( command=lambda: (
modules.globals, "keep_fps", not modules.globals.keep_fps setattr(modules.globals, "keep_fps", keep_fps_value.get()),
save_switch_states(),
), ),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
@ -352,12 +364,13 @@ def create_root(
keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames) keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames)
keep_frames_switch = ctk.CTkSwitch( keep_frames_switch = ctk.CTkSwitch(
options_column, left_column,
text="Keep frames", text="Keep frames",
variable=keep_frames_value, variable=keep_frames_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr( command=lambda: (
modules.globals, "keep_frames", keep_frames_value.get() setattr(modules.globals, "keep_frames", keep_frames_value.get()),
save_switch_states(),
), ),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
@ -366,11 +379,14 @@ def create_root(
enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"]) enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"])
enhancer_switch = ctk.CTkSwitch( enhancer_switch = ctk.CTkSwitch(
options_column, left_column,
text="Face Enhancer", text="Face Enhancer",
variable=enhancer_value, variable=enhancer_value,
cursor="hand2", cursor="hand2",
command=lambda: update_tumbler("face_enhancer", enhancer_value.get()), command=lambda: (
update_tumbler("face_enhancer", enhancer_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
) )
@ -378,11 +394,14 @@ def create_root(
keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio) keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio)
keep_audio_switch = ctk.CTkSwitch( keep_audio_switch = ctk.CTkSwitch(
options_column, left_column,
text="Keep audio", text="Keep audio",
variable=keep_audio_value, variable=keep_audio_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr(modules.globals, "keep_audio", keep_audio_value.get()), command=lambda: (
setattr(modules.globals, "keep_audio", keep_audio_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
) )
@ -390,24 +409,47 @@ def create_root(
many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces) many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces)
many_faces_switch = ctk.CTkSwitch( many_faces_switch = ctk.CTkSwitch(
options_column, left_column,
text="Many faces", text="Many faces",
variable=many_faces_value, variable=many_faces_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr(modules.globals, "many_faces", many_faces_value.get()), command=lambda: (
setattr(modules.globals, "many_faces", many_faces_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
) )
many_faces_switch.pack(pady=5, anchor="w") many_faces_switch.pack(pady=5, anchor="w")
mask_down_size_label = ctk.CTkLabel(
left_column, text="Mask Size:", font=("Roboto", 12)
)
mask_down_size_label.pack(pady=(5, 0), anchor="w")
mask_down_size_slider = ctk.CTkSlider(
left_column,
from_=0.1,
to=1.0,
number_of_steps=9,
command=lambda value: [
setattr(modules.globals, "mask_down_size", value),
save_switch_states(),
],
)
mask_down_size_slider.set(modules.globals.mask_down_size)
mask_down_size_slider.pack(pady=(0, 5), fill="x")
# Right column controls
color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction) color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction)
color_correction_switch = ctk.CTkSwitch( color_correction_switch = ctk.CTkSwitch(
options_column, right_column,
text="Fix Blueish Cam", text="Fix Blueish Cam",
variable=color_correction_value, variable=color_correction_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr( command=lambda: (
modules.globals, "color_correction", color_correction_value.get() setattr(modules.globals, "color_correction", color_correction_value.get()),
save_switch_states(),
), ),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
@ -416,83 +458,83 @@ def create_root(
map_faces = ctk.BooleanVar(value=modules.globals.map_faces) map_faces = ctk.BooleanVar(value=modules.globals.map_faces)
map_faces_switch = ctk.CTkSwitch( map_faces_switch = ctk.CTkSwitch(
options_column, right_column,
text="Map faces", text="Map faces",
variable=map_faces, variable=map_faces,
cursor="hand2", cursor="hand2",
command=lambda: setattr(modules.globals, "map_faces", map_faces.get()), command=lambda: (
setattr(modules.globals, "map_faces", map_faces.get()),
save_switch_states(),
),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
) )
map_faces_switch.pack(pady=5, anchor="w") map_faces_switch.pack(pady=5, anchor="w")
# Add the Show FPS switch
show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps) show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps)
show_fps_switch = ctk.CTkSwitch( show_fps_switch = ctk.CTkSwitch(
options_column, right_column,
text="Show FPS", text="Show FPS",
variable=show_fps_value, variable=show_fps_value,
cursor="hand2", cursor="hand2",
command=lambda: setattr(modules.globals, "show_fps", show_fps_value.get()), command=lambda: (
setattr(modules.globals, "show_fps", show_fps_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf", progress_color="#3a7ebf",
font=("Roboto", 14, "bold"), font=("Roboto", 14, "bold"),
) )
show_fps_switch.pack(pady=5, anchor="w") show_fps_switch.pack(pady=5, anchor="w")
keep_fps_checkbox.configure( mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask)
mouth_mask_switch = ctk.CTkSwitch(
right_column,
text="Mouth Mask",
variable=mouth_mask_var,
cursor="hand2",
command=lambda: ( command=lambda: (
setattr(modules.globals, "keep_fps", keep_fps_value.get()), setattr(modules.globals, "mouth_mask", mouth_mask_var.get()),
save_switch_states(), save_switch_states(),
) ),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
) )
keep_frames_switch.configure( mouth_mask_switch.pack(pady=5, anchor="w")
show_mouth_mask_box_var = ctk.BooleanVar(value=modules.globals.show_mouth_mask_box)
show_mouth_mask_box_switch = ctk.CTkSwitch(
right_column,
text="Show Mouth Box",
variable=show_mouth_mask_box_var,
cursor="hand2",
command=lambda: ( command=lambda: (
setattr(modules.globals, "keep_frames", keep_frames_value.get()), setattr(
modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get()
),
save_switch_states(), save_switch_states(),
) ),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
) )
enhancer_switch.configure( show_mouth_mask_box_switch.pack(pady=5, anchor="w")
command=lambda: (
update_tumbler("face_enhancer", enhancer_value.get()), mask_feather_label = ctk.CTkLabel(
right_column, text="Mask Feather:", font=("Roboto", 12)
)
mask_feather_label.pack(pady=(5, 0), anchor="w")
mask_feather_slider = ctk.CTkSlider(
right_column,
from_=4,
to=16,
number_of_steps=12,
command=lambda value: [
setattr(modules.globals, "mask_feather_ratio", int(value)),
save_switch_states(), save_switch_states(),
) ],
)
keep_audio_switch.configure(
command=lambda: (
setattr(modules.globals, "keep_audio", keep_audio_value.get()),
save_switch_states(),
)
)
many_faces_switch.configure(
command=lambda: (
setattr(modules.globals, "many_faces", many_faces_value.get()),
save_switch_states(),
)
)
color_correction_switch.configure(
command=lambda: (
setattr(modules.globals, "color_correction", color_correction_value.get()),
save_switch_states(),
)
)
# nsfw_switch.configure( # Uncomment if you're using the NSFW filter
# command=lambda: (
# setattr(modules.globals, "nsfw_filter", nsfw_value.get()),
# save_switch_states(),
# )
# )
map_faces_switch.configure(
command=lambda: (
setattr(modules.globals, "map_faces", map_faces.get()),
save_switch_states(),
)
)
show_fps_switch.configure(
command=lambda: (
setattr(modules.globals, "show_fps", show_fps_value.get()),
save_switch_states(),
)
) )
mask_feather_slider.set(modules.globals.mask_feather_ratio)
mask_feather_slider.pack(pady=(0, 5), fill="x")
button_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a") button_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a")
button_frame.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") button_frame.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew")
@ -515,14 +557,21 @@ def create_root(
start_button.pack(side="left", padx=10, expand=True) start_button.pack(side="left", padx=10, expand=True)
preview_button = ModernButton( preview_button = ModernButton(
button_frame, button_frame, text="Preview", cursor="hand2", command=lambda: toggle_preview()
text="Preview",
cursor="hand2",
command=lambda: toggle_preview(),
) )
preview_button.pack(side="left", padx=10, expand=True) preview_button.pack(side="left", padx=10, expand=True)
# --- Camera Selection --- stop_button = ModernButton(
button_frame,
text="Destroy",
cursor="hand2",
command=lambda: destroy(),
fg_color="#f44336",
hover_color="#d32f2f",
)
stop_button.pack(side="left", padx=10, expand=True)
# Camera Selection
camera_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15) camera_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15)
camera_frame.grid(row=3, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="ew") camera_frame.grid(row=3, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="ew")
@ -568,17 +617,6 @@ def create_root(
], ],
) )
live_button.pack(side="left", padx=10, pady=10) live_button.pack(side="left", padx=10, pady=10)
# --- End Camera Selection ---
stop_button = ModernButton(
button_frame,
text="Destroy",
cursor="hand2",
command=lambda: destroy(),
fg_color="#f44336",
hover_color="#d32f2f",
)
stop_button.pack(side="left", padx=10, expand=True)
status_label = ModernLabel( status_label = ModernLabel(
main_frame, text=None, justify="center", fg_color="#1a1a1a" main_frame, text=None, justify="center", fg_color="#1a1a1a"
@ -597,7 +635,6 @@ def create_root(
text_color="#1870c4", text_color="#1870c4",
) )
donate_label.pack(side="left", expand=True) donate_label.pack(side="left", expand=True)
donate_label.bind( donate_label.bind(
"<Button>", lambda event: webbrowser.open("https://paypal.me/hacksider") "<Button>", lambda event: webbrowser.open("https://paypal.me/hacksider")
) )