Privacy Mode
							parent
							
								
									3c7dd1a574
								
							
						
					
					
						commit
						8055d79daf
					
				|  | @ -20,6 +20,7 @@ import modules.metadata | ||||||
| import modules.ui as ui | import modules.ui as ui | ||||||
| from modules.processors.frame.core import get_frame_processors_modules | from modules.processors.frame.core import get_frame_processors_modules | ||||||
| from modules.utilities import has_image_extension, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clean_temp, normalize_output_path | from modules.utilities import has_image_extension, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clean_temp, normalize_output_path | ||||||
|  | from modules.fake_face_handler import cleanup_fake_face | ||||||
| 
 | 
 | ||||||
| if 'ROCMExecutionProvider' in modules.globals.execution_providers: | if 'ROCMExecutionProvider' in modules.globals.execution_providers: | ||||||
|     del torch |     del torch | ||||||
|  | @ -239,6 +240,7 @@ def start() -> None: | ||||||
| def destroy(to_quit=True) -> None: | def destroy(to_quit=True) -> None: | ||||||
|     if modules.globals.target_path: |     if modules.globals.target_path: | ||||||
|         clean_temp(modules.globals.target_path) |         clean_temp(modules.globals.target_path) | ||||||
|  |     cleanup_fake_face() | ||||||
|     if to_quit: quit() |     if to_quit: quit() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
											
												Binary file not shown.
											
										
									
								| After Width: | Height: | Size: 264 KiB | 
|  | @ -0,0 +1,120 @@ | ||||||
|  | import os | ||||||
|  | import requests | ||||||
|  | import tempfile | ||||||
|  | from pathlib import Path | ||||||
|  | import cv2 | ||||||
|  | import numpy as np | ||||||
|  | import modules.globals | ||||||
|  | 
 | ||||||
|  | def add_padding_to_face(image, padding_ratio=0.3): | ||||||
|  |     """Add padding around the face image | ||||||
|  |      | ||||||
|  |     Args: | ||||||
|  |         image: The input face image | ||||||
|  |         padding_ratio: Amount of padding to add as a ratio of image dimensions | ||||||
|  |          | ||||||
|  |     Returns: | ||||||
|  |         Padded image with background padding added | ||||||
|  |     """ | ||||||
|  |     if image is None: | ||||||
|  |         return None | ||||||
|  |          | ||||||
|  |     height, width = image.shape[:2] | ||||||
|  |     pad_x = int(width * padding_ratio) | ||||||
|  |     pad_y = int(height * padding_ratio) | ||||||
|  |      | ||||||
|  |     # Create larger image with padding | ||||||
|  |     padded_height = height + 2 * pad_y | ||||||
|  |     padded_width = width + 2 * pad_x | ||||||
|  |     padded_image = np.zeros((padded_height, padded_width, 3), dtype=np.uint8) | ||||||
|  |      | ||||||
|  |     # Fill padded area with blurred and darkened edge pixels | ||||||
|  |     edge_color = cv2.blur(image, (15, 15)) | ||||||
|  |     edge_color = (edge_color * 0.6).astype(np.uint8)  # Darken the padding | ||||||
|  |      | ||||||
|  |     # Fill the padded image with original face | ||||||
|  |     padded_image[pad_y:pad_y+height, pad_x:pad_x+width] = image | ||||||
|  |      | ||||||
|  |     # Fill padding areas with edge color | ||||||
|  |     # Top padding - repeat first row | ||||||
|  |     top_edge = edge_color[0, :, :] | ||||||
|  |     for i in range(pad_y): | ||||||
|  |         padded_image[i, pad_x:pad_x+width] = top_edge | ||||||
|  |          | ||||||
|  |     # Bottom padding - repeat last row | ||||||
|  |     bottom_edge = edge_color[-1, :, :] | ||||||
|  |     for i in range(pad_y): | ||||||
|  |         padded_image[pad_y+height+i, pad_x:pad_x+width] = bottom_edge | ||||||
|  |          | ||||||
|  |     # Left padding - repeat first column | ||||||
|  |     left_edge = edge_color[:, 0, :] | ||||||
|  |     for i in range(pad_x): | ||||||
|  |         padded_image[pad_y:pad_y+height, i] = left_edge | ||||||
|  |          | ||||||
|  |     # Right padding - repeat last column | ||||||
|  |     right_edge = edge_color[:, -1, :] | ||||||
|  |     for i in range(pad_x): | ||||||
|  |         padded_image[pad_y:pad_y+height, pad_x+width+i] = right_edge | ||||||
|  |          | ||||||
|  |     # Fill corners with nearest edge colors | ||||||
|  |     # Top-left corner | ||||||
|  |     padded_image[:pad_y, :pad_x] = edge_color[0, 0, :] | ||||||
|  |     # Top-right corner | ||||||
|  |     padded_image[:pad_y, pad_x+width:] = edge_color[0, -1, :] | ||||||
|  |     # Bottom-left corner | ||||||
|  |     padded_image[pad_y+height:, :pad_x] = edge_color[-1, 0, :] | ||||||
|  |     # Bottom-right corner | ||||||
|  |     padded_image[pad_y+height:, pad_x+width:] = edge_color[-1, -1, :] | ||||||
|  |      | ||||||
|  |     return padded_image | ||||||
|  | 
 | ||||||
|  | def get_fake_face() -> str: | ||||||
|  |     """Fetch a face from thispersondoesnotexist.com and save it temporarily""" | ||||||
|  |     try: | ||||||
|  |         # Create temp directory if it doesn't exist | ||||||
|  |         temp_dir = Path(tempfile.gettempdir()) / "deep-live-cam" | ||||||
|  |         temp_dir.mkdir(parents=True, exist_ok=True) | ||||||
|  |          | ||||||
|  |         # Generate temp file path | ||||||
|  |         temp_file = temp_dir / "fake_face.jpg" | ||||||
|  |          | ||||||
|  |         # Basic headers to mimic a browser request | ||||||
|  |         headers = { | ||||||
|  |             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         # Fetch the image | ||||||
|  |         response = requests.get('https://thispersondoesnotexist.com', headers=headers) | ||||||
|  |          | ||||||
|  |         if response.status_code == 200: | ||||||
|  |             # Read image from response | ||||||
|  |             image_array = np.asarray(bytearray(response.content), dtype=np.uint8) | ||||||
|  |             image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) | ||||||
|  |              | ||||||
|  |             # Add padding around the face | ||||||
|  |             padded_image = add_padding_to_face(image) | ||||||
|  |              | ||||||
|  |             # Save the padded image | ||||||
|  |             cv2.imwrite(str(temp_file), padded_image) | ||||||
|  |             return str(temp_file) | ||||||
|  |         else: | ||||||
|  |             print(f"Failed to fetch fake face: {response.status_code}") | ||||||
|  |             return None | ||||||
|  |     except Exception as e: | ||||||
|  |         print(f"Error fetching fake face: {str(e)}") | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  | def cleanup_fake_face(): | ||||||
|  |     """Clean up the temporary fake face image""" | ||||||
|  |     try: | ||||||
|  |         if modules.globals.fake_face_path and os.path.exists(modules.globals.fake_face_path): | ||||||
|  |             os.remove(modules.globals.fake_face_path) | ||||||
|  |             modules.globals.fake_face_path = None | ||||||
|  |     except Exception as e: | ||||||
|  |         print(f"Error cleaning up fake face: {str(e)}") | ||||||
|  | 
 | ||||||
|  | def refresh_fake_face(): | ||||||
|  |     """Refresh the fake face image""" | ||||||
|  |     cleanup_fake_face() | ||||||
|  |     modules.globals.fake_face_path = get_fake_face() | ||||||
|  |     return modules.globals.fake_face_path is not None  | ||||||
|  | @ -41,3 +41,5 @@ show_mouth_mask_box = False | ||||||
| mask_feather_ratio = 8 | mask_feather_ratio = 8 | ||||||
| mask_down_size = 0.50 | mask_down_size = 0.50 | ||||||
| mask_size = 1 | mask_size = 1 | ||||||
|  | use_fake_face = False | ||||||
|  | fake_face_path = None | ||||||
|  |  | ||||||
								
									
									
										
											150
										
									
									modules/ui.py
									
									
									
									
								
								
							
							
										
											150
										
									
									modules/ui.py
									
									
									
									
								|  | @ -28,6 +28,7 @@ from modules.utilities import ( | ||||||
| from modules.video_capture import VideoCapturer | from modules.video_capture import VideoCapturer | ||||||
| from modules.gettext import LanguageManager | from modules.gettext import LanguageManager | ||||||
| import platform | import platform | ||||||
|  | from modules.fake_face_handler import cleanup_fake_face, refresh_fake_face | ||||||
| 
 | 
 | ||||||
| if platform.system() == "Windows": | if platform.system() == "Windows": | ||||||
|     from pygrabber.dshow_graph import FilterGraph |     from pygrabber.dshow_graph import FilterGraph | ||||||
|  | @ -91,47 +92,52 @@ def init(start: Callable[[], None], destroy: Callable[[], None], lang: str) -> c | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def save_switch_states(): | def save_switch_states(): | ||||||
|     switch_states = { |     try: | ||||||
|         "keep_fps": modules.globals.keep_fps, |         states = { | ||||||
|         "keep_audio": modules.globals.keep_audio, |             "keep_fps": modules.globals.keep_fps, | ||||||
|         "keep_frames": modules.globals.keep_frames, |             "keep_audio": modules.globals.keep_audio, | ||||||
|         "many_faces": modules.globals.many_faces, |             "keep_frames": modules.globals.keep_frames, | ||||||
|         "map_faces": modules.globals.map_faces, |             "many_faces": modules.globals.many_faces, | ||||||
|         "color_correction": modules.globals.color_correction, |             "map_faces": modules.globals.map_faces, | ||||||
|         "nsfw_filter": modules.globals.nsfw_filter, |             "color_correction": modules.globals.color_correction, | ||||||
|         "live_mirror": modules.globals.live_mirror, |             "nsfw_filter": modules.globals.nsfw_filter, | ||||||
|         "live_resizable": modules.globals.live_resizable, |             "live_mirror": modules.globals.live_mirror, | ||||||
|         "fp_ui": modules.globals.fp_ui, |             "live_resizable": modules.globals.live_resizable, | ||||||
|         "show_fps": modules.globals.show_fps, |             "fp_ui": modules.globals.fp_ui, | ||||||
|         "mouth_mask": modules.globals.mouth_mask, |             "show_fps": modules.globals.show_fps, | ||||||
|         "show_mouth_mask_box": modules.globals.show_mouth_mask_box, |             "mouth_mask": modules.globals.mouth_mask, | ||||||
|     } |             "show_mouth_mask_box": modules.globals.show_mouth_mask_box, | ||||||
|     with open("switch_states.json", "w") as f: |             "use_fake_face": modules.globals.use_fake_face | ||||||
|         json.dump(switch_states, f) |         } | ||||||
|  |         with open(get_config_path(), 'w') as f: | ||||||
|  |             json.dump(states, f) | ||||||
|  |     except Exception as e: | ||||||
|  |         print(f"Error saving switch states: {str(e)}") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def load_switch_states(): | def load_switch_states(): | ||||||
|     try: |     try: | ||||||
|         with open("switch_states.json", "r") as f: |         if os.path.exists(get_config_path()): | ||||||
|             switch_states = json.load(f) |             with open(get_config_path(), 'r') as f: | ||||||
|         modules.globals.keep_fps = switch_states.get("keep_fps", True) |                 states = json.load(f) | ||||||
|         modules.globals.keep_audio = switch_states.get("keep_audio", True) |                 modules.globals.keep_fps = states.get("keep_fps", True) | ||||||
|         modules.globals.keep_frames = switch_states.get("keep_frames", False) |                 modules.globals.keep_audio = states.get("keep_audio", True) | ||||||
|         modules.globals.many_faces = switch_states.get("many_faces", False) |                 modules.globals.keep_frames = states.get("keep_frames", False) | ||||||
|         modules.globals.map_faces = switch_states.get("map_faces", False) |                 modules.globals.many_faces = states.get("many_faces", False) | ||||||
|         modules.globals.color_correction = switch_states.get("color_correction", False) |                 modules.globals.map_faces = states.get("map_faces", False) | ||||||
|         modules.globals.nsfw_filter = switch_states.get("nsfw_filter", False) |                 modules.globals.color_correction = states.get("color_correction", False) | ||||||
|         modules.globals.live_mirror = switch_states.get("live_mirror", False) |                 modules.globals.nsfw_filter = states.get("nsfw_filter", False) | ||||||
|         modules.globals.live_resizable = switch_states.get("live_resizable", False) |                 modules.globals.live_mirror = states.get("live_mirror", False) | ||||||
|         modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False}) |                 modules.globals.live_resizable = states.get("live_resizable", False) | ||||||
|         modules.globals.show_fps = switch_states.get("show_fps", False) |                 modules.globals.fp_ui = states.get("fp_ui", {"face_enhancer": False}) | ||||||
|         modules.globals.mouth_mask = switch_states.get("mouth_mask", False) |                 modules.globals.show_fps = states.get("show_fps", False) | ||||||
|         modules.globals.show_mouth_mask_box = switch_states.get( |                 modules.globals.mouth_mask = states.get("mouth_mask", False) | ||||||
|             "show_mouth_mask_box", False |                 modules.globals.show_mouth_mask_box = states.get( | ||||||
|         ) |                     "show_mouth_mask_box", False | ||||||
|     except FileNotFoundError: |                 ) | ||||||
|         # If the file doesn't exist, use default values |                 modules.globals.use_fake_face = states.get('use_fake_face', False) | ||||||
|         pass |     except Exception as e: | ||||||
|  |         print(f"Error loading switch states: {str(e)}") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.CTk: | def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.CTk: | ||||||
|  | @ -176,6 +182,27 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|     ) |     ) | ||||||
|     select_target_button.place(relx=0.6, rely=0.4, relwidth=0.3, relheight=0.1) |     select_target_button.place(relx=0.6, rely=0.4, relwidth=0.3, relheight=0.1) | ||||||
| 
 | 
 | ||||||
|  |     # AI Generated Face controls | ||||||
|  |     fake_face_value = ctk.BooleanVar(value=modules.globals.use_fake_face) | ||||||
|  |     fake_face_switch = ctk.CTkSwitch( | ||||||
|  |         root, | ||||||
|  |         text=_("Privacy Mode"), | ||||||
|  |         variable=fake_face_value, | ||||||
|  |         cursor="hand2", | ||||||
|  |         command=lambda: toggle_fake_face(fake_face_value) | ||||||
|  |     ) | ||||||
|  |     fake_face_switch.place(relx=0.1, rely=0.55) | ||||||
|  | 
 | ||||||
|  |     # Add refresh button next to the switch | ||||||
|  |     refresh_face_button = ctk.CTkButton( | ||||||
|  |         root, | ||||||
|  |         text="↻", | ||||||
|  |         width=30, | ||||||
|  |         cursor="hand2", | ||||||
|  |         command=lambda: refresh_fake_face_clicked() | ||||||
|  |     ) | ||||||
|  |     refresh_face_button.place(relx=0.35, rely=0.55) | ||||||
|  | 
 | ||||||
|     # Face Processing Options (Middle Left) |     # Face Processing Options (Middle Left) | ||||||
|     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( | ||||||
|  | @ -188,7 +215,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             save_switch_states(), |             save_switch_states(), | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     many_faces_switch.place(relx=0.1, rely=0.55) |     many_faces_switch.place(relx=0.1, rely=0.60) | ||||||
| 
 | 
 | ||||||
|     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( | ||||||
|  | @ -202,7 +229,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             close_mapper_window() if not map_faces.get() else None |             close_mapper_window() if not map_faces.get() else None | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     map_faces_switch.place(relx=0.1, rely=0.6) |     map_faces_switch.place(relx=0.1, rely=0.65) | ||||||
| 
 | 
 | ||||||
|     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( | ||||||
|  | @ -215,7 +242,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             save_switch_states(), |             save_switch_states(), | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     enhancer_switch.place(relx=0.1, rely=0.65) |     enhancer_switch.place(relx=0.1, rely=0.70) | ||||||
| 
 | 
 | ||||||
|     # Additional Options (Middle Right) |     # Additional Options (Middle Right) | ||||||
|     mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask) |     mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask) | ||||||
|  | @ -257,21 +284,21 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|     start_button = ctk.CTkButton( |     start_button = ctk.CTkButton( | ||||||
|         root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root) |         root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root) | ||||||
|     ) |     ) | ||||||
|     start_button.place(relx=0.15, rely=0.75, relwidth=0.2, relheight=0.05) |     start_button.place(relx=0.15, rely=0.80, relwidth=0.2, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     preview_button = ctk.CTkButton( |     preview_button = ctk.CTkButton( | ||||||
|         root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview() |         root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview() | ||||||
|     ) |     ) | ||||||
|     preview_button.place(relx=0.4, rely=0.75, relwidth=0.2, relheight=0.05) |     preview_button.place(relx=0.4, rely=0.80, relwidth=0.2, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     stop_button = ctk.CTkButton( |     stop_button = ctk.CTkButton( | ||||||
|         root, text=_("Destroy"), cursor="hand2", command=lambda: destroy() |         root, text=_("Destroy"), cursor="hand2", command=lambda: destroy() | ||||||
|     ) |     ) | ||||||
|     stop_button.place(relx=0.65, rely=0.75, relwidth=0.2, relheight=0.05) |     stop_button.place(relx=0.65, rely=0.80, relwidth=0.2, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     # Camera Section (Bottom) |     # Camera Section (Bottom) | ||||||
|     camera_label = ctk.CTkLabel(root, text=_("Select Camera:")) |     camera_label = ctk.CTkLabel(root, text=_("Select Camera:")) | ||||||
|     camera_label.place(relx=0.1, rely=0.85, relwidth=0.2, relheight=0.05) |     camera_label.place(relx=0.1, rely=0.87, relwidth=0.2, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     available_cameras = get_available_cameras() |     available_cameras = get_available_cameras() | ||||||
|     camera_indices, camera_names = available_cameras |     camera_indices, camera_names = available_cameras | ||||||
|  | @ -290,7 +317,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             root, variable=camera_variable, values=camera_names |             root, variable=camera_variable, values=camera_names | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     camera_optionmenu.place(relx=0.35, rely=0.85, relwidth=0.25, relheight=0.05) |     camera_optionmenu.place(relx=0.35, rely=0.87, relwidth=0.25, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     live_button = ctk.CTkButton( |     live_button = ctk.CTkButton( | ||||||
|         root, |         root, | ||||||
|  | @ -310,7 +337,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             else "disabled" |             else "disabled" | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     live_button.place(relx=0.65, rely=0.85, relwidth=0.2, relheight=0.05) |     live_button.place(relx=0.65, rely=0.87, relwidth=0.2, relheight=0.05) | ||||||
| 
 | 
 | ||||||
|     # Status and Links (Bottom) |     # Status and Links (Bottom) | ||||||
|     status_label = ctk.CTkLabel(root, text=None, justify="center") |     status_label = ctk.CTkLabel(root, text=None, justify="center") | ||||||
|  | @ -1147,4 +1174,33 @@ def update_webcam_target( | ||||||
|             target_label_dict_live[button_num] = target_image |             target_label_dict_live[button_num] = target_image | ||||||
|         else: |         else: | ||||||
|             update_pop_live_status("Face could not be detected in last upload!") |             update_pop_live_status("Face could not be detected in last upload!") | ||||||
|         return map |         return map | ||||||
|  | 
 | ||||||
|  | def toggle_fake_face(switch_var: ctk.BooleanVar) -> None: | ||||||
|  |     modules.globals.use_fake_face = switch_var.get() | ||||||
|  |     if modules.globals.use_fake_face: | ||||||
|  |         if not modules.globals.fake_face_path: | ||||||
|  |             if refresh_fake_face(): | ||||||
|  |                 modules.globals.source_path = modules.globals.fake_face_path | ||||||
|  |                 # Update the source image preview | ||||||
|  |                 image = render_image_preview(modules.globals.source_path, (200, 200)) | ||||||
|  |                 source_label.configure(image=image) | ||||||
|  |     else: | ||||||
|  |         cleanup_fake_face() | ||||||
|  |         # Clear the source image preview | ||||||
|  |         source_label.configure(image=None) | ||||||
|  |         modules.globals.source_path = None | ||||||
|  | 
 | ||||||
|  | def refresh_fake_face_clicked() -> None: | ||||||
|  |     if modules.globals.use_fake_face: | ||||||
|  |         if refresh_fake_face(): | ||||||
|  |             modules.globals.source_path = modules.globals.fake_face_path | ||||||
|  |             # Update the source image preview | ||||||
|  |             image = render_image_preview(modules.globals.source_path, (200, 200)) | ||||||
|  |             source_label.configure(image=image) | ||||||
|  | 
 | ||||||
|  | def get_config_path() -> str: | ||||||
|  |     """Get the path to the config file""" | ||||||
|  |     config_dir = os.path.join(os.path.expanduser("~"), ".deep-live-cam") | ||||||
|  |     os.makedirs(config_dir, exist_ok=True) | ||||||
|  |     return os.path.join(config_dir, "switch_states.json") | ||||||
		Loading…
	
		Reference in New Issue