import os import webbrowser import customtkinter as ctk from typing import Callable, Tuple import cv2 from cv2_enumerate_cameras import enumerate_cameras from PIL import Image, ImageOps import tkinterdnd2 as tkdnd import time import json import modules.globals import modules.metadata from modules.face_analyser import ( get_one_face, get_unique_faces_from_target_image, get_unique_faces_from_target_video, add_blank_map, has_valid_map, simplify_maps, ) from modules.capturer import get_video_frame, get_video_frame_total from modules.processors.frame.core import get_frame_processors_modules from modules.utilities import ( is_image, is_video, resolve_relative_path, has_image_extension, ) os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" os.environ["QT_SCALE_FACTOR"] = "1" ROOT = None POPUP = None POPUP_LIVE = None ROOT_HEIGHT = 800 ROOT_WIDTH = 1000 PREVIEW = None PREVIEW_MAX_HEIGHT = 800 PREVIEW_MAX_WIDTH = 1400 PREVIEW_DEFAULT_WIDTH = 1280 PREVIEW_DEFAULT_HEIGHT = 720 POPUP_WIDTH = 700 POPUP_HEIGHT = 800 POPUP_SCROLL_WIDTH = 680 POPUP_SCROLL_HEIGHT = 600 POPUP_LIVE_WIDTH = 850 POPUP_LIVE_HEIGHT = 700 POPUP_LIVE_SCROLL_WIDTH = 830 POPUP_LIVE_SCROLL_HEIGHT = 600 MAPPER_PREVIEW_MAX_HEIGHT = 120 MAPPER_PREVIEW_MAX_WIDTH = 120 DEFAULT_BUTTON_WIDTH = 200 DEFAULT_BUTTON_HEIGHT = 40 RECENT_DIRECTORY_SOURCE = None RECENT_DIRECTORY_TARGET = None RECENT_DIRECTORY_OUTPUT = None preview_label = None preview_slider = None source_label = None target_label = None status_label = None popup_status_label = None popup_status_label_live = None source_label_dict = {} source_label_dict_live = {} target_label_dict_live = {} img_ft, vid_ft = modules.globals.file_types class ModernButton(ctk.CTkButton): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.configure( font=("Roboto", 16, "bold"), corner_radius=15, border_width=2, border_color="#3a7ebf", hover_color="#2b5d8b", fg_color="#3a7ebf", text_color="white", ) class DragDropButton(ModernButton): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.drop_target_register(tkdnd.DND_FILES) self.dnd_bind("<>", self.drop) def drop(self, event): file_path = event.data if file_path.startswith("{"): file_path = file_path[1:-1] self.handle_drop(file_path) def handle_drop(self, file_path): pass class SourceButton(DragDropButton): def handle_drop(self, file_path): if is_image(file_path): modules.globals.source_path = file_path global RECENT_DIRECTORY_SOURCE RECENT_DIRECTORY_SOURCE = os.path.dirname(modules.globals.source_path) image = render_image_preview(modules.globals.source_path, (250, 250)) source_label.configure(image=image) source_label.configure(text="") class SourceMapperButton(DragDropButton): def __init__(self, master, map, button_num, **kwargs): super().__init__(master, **kwargs) self.map = map self.button_num = button_num def handle_drop(self, file_path): if is_image(file_path): update_popup_source( self.master.master, self.map, self.button_num, file_path ) class TargetButton(DragDropButton): def handle_drop(self, file_path): global RECENT_DIRECTORY_TARGET if is_image(file_path) or is_video(file_path): modules.globals.target_path = file_path RECENT_DIRECTORY_TARGET = os.path.dirname(modules.globals.target_path) if is_image(file_path): image = render_image_preview(modules.globals.target_path, (250, 250)) target_label.configure(image=image) target_label.configure(text="") elif is_video(file_path): video_frame = render_video_preview(file_path, (250, 250)) target_label.configure(image=video_frame) target_label.configure(text="") class ModernLabel(ctk.CTkLabel): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.configure( font=("Roboto", 16), corner_radius=10, fg_color="#2a2d2e", text_color="white", ) class DragDropLabel(ModernLabel): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.drop_target_register(tkdnd.DND_FILES) self.dnd_bind("<>", self.drop) def drop(self, event): file_path = event.data if file_path.startswith("{"): file_path = file_path[1:-1] self.handle_drop(file_path) def handle_drop(self, file_path): pass class SourceLabel(DragDropLabel): def handle_drop(self, file_path): if is_image(file_path): modules.globals.source_path = file_path global RECENT_DIRECTORY_SOURCE RECENT_DIRECTORY_SOURCE = os.path.dirname(modules.globals.source_path) image = render_image_preview(modules.globals.source_path, (250, 250)) source_label.configure(image=image) source_label.configure(text="") class TargetLabel(DragDropLabel): def handle_drop(self, file_path): global RECENT_DIRECTORY_TARGET if is_image(file_path) or is_video(file_path): modules.globals.target_path = file_path RECENT_DIRECTORY_TARGET = os.path.dirname(modules.globals.target_path) if is_image(file_path): image = render_image_preview(modules.globals.target_path, (250, 250)) target_label.configure(image=image) target_label.configure(text="") elif is_video(file_path): video_frame = render_video_preview(file_path, (250, 250)) target_label.configure(image=video_frame) target_label.configure(text="") class ModernOptionMenu(ctk.CTkFrame): def __init__(self, master, values, command=None, **kwargs): super().__init__(master, fg_color="transparent") self.values = values self.command = command # Set initial value based on saved camera or first available self.current_value = ( modules.globals.selected_camera if modules.globals.selected_camera in values else (values[0] if values else "No cameras found") ) # Main button self.main_button = ctk.CTkButton( self, text=self.current_value, command=self.show_dropdown, width=300, height=40, corner_radius=8, fg_color="#1f538d", hover_color="#1a4572", text_color="white", font=("Roboto", 13, "bold"), border_width=2, border_color="#3d7ab8", ) self.main_button.pack(expand=True, fill="both") # Dropdown frame (initially hidden) self.dropdown_frame = None self.is_dropdown_visible = False self.click_binding = None def show_dropdown(self): if self.is_dropdown_visible: self.hide_dropdown() return # Calculate position and size button_width = self.main_button.winfo_width() dropdown_height = min(len(self.values) * 35, 200) # Limit max height # Create and show dropdown with fixed size self.dropdown_frame = ctk.CTkFrame( self.winfo_toplevel(), width=button_width, height=dropdown_height, fg_color="#1f538d", corner_radius=8, border_width=2, border_color="#3d7ab8", ) # Get the absolute coordinates of the button relative to the screen button_x = self.winfo_rootx() button_y = self.winfo_rooty() # Position the dropdown above the button, using relative coordinates relative_x = button_x - self.winfo_toplevel().winfo_rootx() relative_y = button_y - self.winfo_toplevel().winfo_rooty() - dropdown_height self.dropdown_frame.place(in_=self.winfo_toplevel(), x=relative_x, y=relative_y) # Prevent frame from resizing self.dropdown_frame.pack_propagate(False) # Create scrollable frame if needed if len(self.values) * 35 > 200: scrollable_frame = ctk.CTkScrollableFrame( self.dropdown_frame, width=button_width - 20, height=dropdown_height - 10, fg_color="#1f538d", scrollbar_button_color="#3d7ab8", scrollbar_button_hover_color="#2b5d8b", ) scrollable_frame.pack(expand=True, fill="both", padx=5, pady=5) container = scrollable_frame else: container = self.dropdown_frame # Add options for value in self.values: option_button = ctk.CTkButton( container, text=value, fg_color="transparent", hover_color="#233d54", text_color="white", height=35, corner_radius=4, font=("Roboto", 13), command=lambda v=value: self.select_value(v), ) option_button.pack(padx=2, pady=1, fill="x") self.is_dropdown_visible = True self.click_binding = self.winfo_toplevel().bind( "", self.on_click_outside, add="+" ) def on_click_outside(self, event): if self.is_dropdown_visible: widget_under_cursor = event.widget.winfo_containing( event.x_root, event.y_root ) if widget_under_cursor not in [self.main_button] + ( self.dropdown_frame.winfo_children() if self.dropdown_frame else [] ): self.hide_dropdown() def hide_dropdown(self): if self.dropdown_frame: if self.click_binding: self.winfo_toplevel().unbind("", self.click_binding) self.click_binding = None self.dropdown_frame.destroy() self.dropdown_frame = None self.is_dropdown_visible = False def select_value(self, value): self.current_value = value self.main_button.configure(text=value) self.hide_dropdown() if self.command: self.command(value) def get(self): return self.current_value def save_switch_states(): switch_states = { "keep_fps": modules.globals.keep_fps, "keep_audio": modules.globals.keep_audio, "keep_frames": modules.globals.keep_frames, "many_faces": modules.globals.many_faces, "map_faces": modules.globals.map_faces, "color_correction": modules.globals.color_correction, "nsfw_filter": modules.globals.nsfw_filter, "live_mirror": modules.globals.live_mirror, "live_resizable": modules.globals.live_resizable, "fp_ui": modules.globals.fp_ui, "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, "selected_camera": modules.globals.selected_camera, } with open("switch_states.json", "w") as f: json.dump(switch_states, f) def load_switch_states(): try: with open("switch_states.json", "r") as f: switch_states = json.load(f) modules.globals.keep_fps = switch_states.get("keep_fps", True) modules.globals.keep_audio = switch_states.get("keep_audio", True) modules.globals.keep_frames = switch_states.get("keep_frames", False) modules.globals.many_faces = switch_states.get("many_faces", False) modules.globals.map_faces = switch_states.get("map_faces", False) modules.globals.color_correction = switch_states.get("color_correction", False) modules.globals.nsfw_filter = switch_states.get("nsfw_filter", False) modules.globals.live_mirror = switch_states.get("live_mirror", 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.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) modules.globals.selected_camera = switch_states.get("selected_camera", None) except FileNotFoundError: # If the file doesn't exist, use default values pass def init(start: Callable[[], None], destroy: Callable[[], None]) -> tkdnd.TkinterDnD.Tk: global ROOT, PREVIEW, donate_frame ROOT = create_root(start, destroy) PREVIEW = create_preview(ROOT) return ROOT def create_root( start: Callable[[], None], destroy: Callable[[], None] ) -> tkdnd.TkinterDnD.Tk: global source_label, target_label, status_label, donate_frame, show_fps_switch load_switch_states() ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") root = tkdnd.TkinterDnD.Tk() root.title( f"{modules.metadata.name} {modules.metadata.version} {modules.metadata.edition}" ) root.configure(bg="#1a1a1a") root.protocol("WM_DELETE_WINDOW", lambda: destroy()) root.resizable(True, True) root.attributes("-alpha", 1.0) root.minsize(650, 870) main_frame = ctk.CTkFrame(root, fg_color="#1a1a1a") main_frame.pack(fill="both", expand=True, padx=20, pady=20) # Create two vertical frames for source and target source_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15) source_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew") target_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15) target_frame.grid(row=0, column=2, padx=10, pady=10, sticky="nsew") # Create a middle frame for swap button middle_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a") middle_frame.grid(row=0, column=1, padx=5, pady=10, sticky="ns") source_label = SourceLabel( source_frame, text="Drag & Drop\nSource Image Here", justify="center", width=250, height=250, ) source_label.pack(pady=(20, 10)) target_label = TargetLabel( target_frame, text="Drag & Drop\nTarget Image/Video Here", justify="center", width=250, height=250, ) target_label.pack(pady=(20, 10)) select_face_button = SourceButton( source_frame, text="Select a face", cursor="hand2", command=lambda: select_source_path(), ) select_face_button.pack(pady=10) select_target_button = TargetButton( target_frame, text="Select a target", cursor="hand2", command=lambda: select_target_path(), ) select_target_button.pack(pady=10) swap_faces_button = ModernButton( middle_frame, text="↔", cursor="hand2", command=lambda: swap_faces_paths(), width=50, height=50, ) swap_faces_button.pack(expand=True) 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") # Create two columns for options left_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e") left_column.pack(side="left", padx=20, expand=True) right_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e") right_column.pack(side="right", padx=20, expand=True) # Left column - Video/Processing Options keep_fps_value = ctk.BooleanVar(value=modules.globals.keep_fps) keep_fps_checkbox = ctk.CTkSwitch( left_column, text="Keep fps", variable=keep_fps_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "keep_fps", keep_fps_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) keep_fps_checkbox.pack(pady=5, anchor="w") # Move many faces switch to left column many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces) many_faces_switch = ctk.CTkSwitch( left_column, text="Many faces", variable=many_faces_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "many_faces", many_faces_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) many_faces_switch.pack(pady=5, anchor="w") keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio) keep_audio_switch = ctk.CTkSwitch( left_column, text="Keep audio", variable=keep_audio_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "keep_audio", keep_audio_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) keep_audio_switch.pack(pady=5, anchor="w") keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames) keep_frames_switch = ctk.CTkSwitch( left_column, text="Keep frames", variable=keep_frames_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "keep_frames", keep_frames_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) keep_frames_switch.pack(pady=5, anchor="w") # Face Processing Options enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"]) enhancer_switch = ctk.CTkSwitch( left_column, text="Face Enhancer", variable=enhancer_value, cursor="hand2", command=lambda: ( update_tumbler("face_enhancer", enhancer_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) enhancer_switch.pack(pady=5, anchor="w") color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction) color_correction_switch = ctk.CTkSwitch( left_column, text="Fix Blueish Cam", variable=color_correction_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "color_correction", color_correction_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) color_correction_switch.pack(pady=5, anchor="w") map_faces = ctk.BooleanVar(value=modules.globals.map_faces) map_faces_switch = ctk.CTkSwitch( left_column, text="Map faces", variable=map_faces, cursor="hand2", command=lambda: ( setattr(modules.globals, "map_faces", map_faces.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) map_faces_switch.pack(pady=5, anchor="w") # Right column - Face Detection & Masking Options show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps) show_fps_switch = ctk.CTkSwitch( right_column, text="Show FPS", variable=show_fps_value, cursor="hand2", command=lambda: ( setattr(modules.globals, "show_fps", show_fps_value.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) show_fps_switch.pack(pady=5, anchor="w") # Mouth Mask Controls 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: ( setattr(modules.globals, "mouth_mask", mouth_mask_var.get()), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) 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: ( setattr( modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get() ), save_switch_states(), ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) show_mouth_mask_box_switch.pack(pady=5, anchor="w") # Face Opacity Controls - Moved under Show Mouth Box opacity_frame = ctk.CTkFrame(right_column, fg_color="#2a2d2e") opacity_frame.pack(pady=5, anchor="w", fill="x") opacity_switch = ctk.CTkSwitch( opacity_frame, text="Face Opacity", variable=ctk.BooleanVar(value=modules.globals.opacity_switch), cursor="hand2", command=lambda: setattr( modules.globals, "opacity_switch", not modules.globals.opacity_switch ), progress_color="#3a7ebf", font=("Roboto", 14, "bold"), ) opacity_switch.pack(side="left", padx=(0, 10)) opacity_slider = ctk.CTkSlider( opacity_frame, from_=0, to=100, number_of_steps=100, command=update_opacity, fg_color=("gray75", "gray25"), progress_color="#3a7ebf", button_color="#3a7ebf", button_hover_color="#2b5d8b", ) opacity_slider.pack(side="left", fill="x", expand=True) opacity_slider.set(modules.globals.face_opacity) # Mask Size Controls mask_down_size_label = ctk.CTkLabel( right_column, text="Mask Size:", font=("Roboto", 12) ) mask_down_size_label.pack(pady=(5, 0), anchor="w") mask_down_size_slider = ctk.CTkSlider( right_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") # Mask Feather Controls 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(), ], ) 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.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") start_button = ModernButton( button_frame, text="Start", cursor="hand2", command=lambda: [ ( donate_frame.destroy() if "donate_frame" in globals() and donate_frame.winfo_exists() else None ), analyze_target(start, root), ], fg_color="#4CAF50", hover_color="#45a049", ) start_button.pack(side="left", padx=10, expand=True) preview_button = ModernButton( button_frame, text="Preview", cursor="hand2", command=lambda: toggle_preview() ) preview_button.pack(side="left", padx=10, expand=True) 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.grid(row=3, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="ew") camera_label = ctk.CTkLabel( camera_frame, text="Select Camera:", font=("Roboto", 14) ) camera_label.pack(side="left", padx=(20, 10), pady=10) available_cameras = get_available_cameras() available_camera_indices, available_camera_strings = available_cameras camera_variable = ctk.StringVar( value=( available_camera_strings[0] if available_camera_strings else "No cameras found" ) ) camera_optionmenu = ModernOptionMenu( camera_frame, values=available_camera_strings, command=lambda value: print(f"Selected: {value}"), # Add your command here ) camera_optionmenu.pack(side="left", padx=(10, 20), pady=10, fill="x", expand=True) live_button = ModernButton( camera_frame, text="Live", cursor="hand2", command=lambda: [ ( donate_frame.destroy() if "donate_frame" in globals() and donate_frame.winfo_exists() else None ), webcam_preview( root, available_camera_indices[ available_camera_strings.index(camera_optionmenu.get()) ], ), ], ) live_button.pack(side="left", padx=10, pady=10) status_label = ModernLabel( main_frame, text=None, justify="center", fg_color="#1a1a1a" ) status_label.grid(row=4, column=0, columnspan=3, pady=10, sticky="ew") donate_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a") donate_frame.grid(row=4, column=0, columnspan=3, pady=5, sticky="ew") donate_label = ModernLabel( donate_frame, text="Donate", justify="center", cursor="hand2", fg_color="#1870c4", text_color="#1870c4", ) donate_label.pack(side="left", expand=True) donate_label.bind( "