diff --git a/.github/workflows/build_dot.yaml b/.github/workflows/build_dot.yaml index 1f2a1dd..390c229 100644 --- a/.github/workflows/build_dot.yaml +++ b/.github/workflows/build_dot.yaml @@ -14,7 +14,7 @@ on: jobs: build-and-test: if: github.event.pull_request.draft == false - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Code Checkout uses: actions/checkout@v3 diff --git a/.github/workflows/code_check.yaml b/.github/workflows/code_check.yaml index 189af44..1e5ca86 100644 --- a/.github/workflows/code_check.yaml +++ b/.github/workflows/code_check.yaml @@ -10,7 +10,7 @@ on: jobs: code-check: if: github.event.pull_request.draft == false - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Code Checkout uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df72649..ff5bdb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: args: [--max-line-length=150, --extend-ignore=E203] - repo: https://github.com/PyCQA/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b0141..f53517a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,26 +17,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update readme by @giorgiop in https://github.com/sensity-ai/dot/pull/6 * Add more press on README.md by @giorgiop in https://github.com/sensity-ai/dot/pull/7 * [ImgBot] Optimize images by @imgbot in https://github.com/sensity-ai/dot/pull/8 -* Update README to Download Models from Github Release Binaries by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/19 -* Update README + Add Github Templates by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/16 -* Verify camera ID when running dot in camera mode by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/18 -* Add Feature to Use Config Files by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/17 +* Update README to Download Models from Github Release Binaries by @ajndkr in https://github.com/sensity-ai/dot/pull/19 +* Update README + Add Github Templates by @ajndkr in https://github.com/sensity-ai/dot/pull/16 +* Verify camera ID when running dot in camera mode by @ajndkr in https://github.com/sensity-ai/dot/pull/18 +* Add Feature to Use Config Files by @ajndkr in https://github.com/sensity-ai/dot/pull/17 * ⬆️ Bump numpy from 1.21.1 to 1.22.0 by @dependabot in https://github.com/sensity-ai/dot/pull/25 * Update python version to 3.8 by @vassilispapadop in https://github.com/sensity-ai/dot/pull/28 * Requirements changes now trigger CI by @giorgiop in https://github.com/sensity-ai/dot/pull/27 -* Fix python3.8 pip cache location in CI by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/29 -* Fix `--save_folder` CLI Option by @vassilispapadop and @ghassen1302 in https://github.com/sensity-ai/dot/pull/26 -* Add contributors list by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/31 -* Add Google Colab demo notebook by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/33 -* Speed up SimSwap's `reverse2original` by @AjinkyaIndulkar and @ghassen1302 in https://github.com/sensity-ai/dot/pull/20 -* Add `bumpversion` for semantic versioning by @AjinkyaIndulkar in https://github.com/sensity-ai/dot/pull/34 +* Fix python3.8 pip cache location in CI by @ajndkr in https://github.com/sensity-ai/dot/pull/29 +* Fix `--save_folder` CLI Option by @vassilispapadop and @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/26 +* Add contributors list by @ajndkr in https://github.com/sensity-ai/dot/pull/31 +* Add Google Colab demo notebook by @ajndkr in https://github.com/sensity-ai/dot/pull/33 +* Speed up SimSwap's `reverse2original` by @ajndkr and @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/20 +* Add `bumpversion` for semantic versioning by @ajndkr in https://github.com/sensity-ai/dot/pull/34 * Update README with speed metrics by @giorgiop in https://github.com/sensity-ai/dot/pull/37 +* Add a Graphical interface for dot by @Ghassen-Chaabouni in https://github.com/sensity-ai/dot/pull/85 ## New Contributors * @giorgiop made their first contribution in https://github.com/sensity-ai/dot/pull/6 * @ghassen1302 made their first contribution in https://github.com/sensity-ai/dot/pull/6 * @imgbot made their first contribution in https://github.com/sensity-ai/dot/pull/8 -* @AjinkyaIndulkar made their first contribution in https://github.com/sensity-ai/dot/pull/19 +* @ajndkr made their first contribution in https://github.com/sensity-ai/dot/pull/19 * @dependabot made their first contribution in https://github.com/sensity-ai/dot/pull/25 * @vassilispapadop made their first contribution in https://github.com/sensity-ai/dot/pull/28 diff --git a/README.md b/README.md index e955a7b..e341031 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,44 @@ Supported methods: - lower quality face swap (via OpenCV) - [FOMM](https://github.com/AliaksandrSiarohin/first-order-model), First Order Motion Model for image animation -## Installation +## Running dot -### Install Pre-requisites +### Graphical interface + +#### GUI Installation + +Download and run the dot executable for your OS: + +- Windows: + - ToDo +- Ubuntu: + - ToDo +- Mac: + - ToDo + +#### GUI Usage + +Usage example: + +1. Specify the source image in the field `source`. +2. Specify the camera id number in the field `target`. In most cases, `0` is the correct camera id. +3. Specify the config file in the field `config_file`. Select a default configuration from the dropdown list or use a custom file. +4. (Optional) Check the field `use_gpu` to use the GPU. +5. Click on the `RUN` button to start the deepfake. + +For more information about each field, click on the menu `Help/Usage`. + +Watch the following demo video for better understanding of the interface + +

+ +

+ +### Command Line + +#### CLI Installation + +##### Install Pre-requisites - Linux @@ -61,11 +96,11 @@ Supported methods: no pre-requisites to be installed, skip this step -### Create Conda Environment +##### Create Conda Environment > The instructions assumes that you have Miniconda installed on your machine. If you don't, you can refer to this [link](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html) for installation instructions. -#### With GPU Support +###### With GPU Support ```bash conda env create -f envs/environment-gpu.yaml @@ -79,20 +114,20 @@ Install the `torch` and `torchvision` dependencies based on the CUDA version ins To check that `torch` and `torchvision` are installed correctly, run the following command: `python -c "import torch; print(torch.cuda.is_available())"`. If the output is `True`, the dependencies are installed with CUDA support. -#### With CPU Support (slow, not recommended) +###### With CPU Support (slow, not recommended) ```bash conda env create -f envs/environment-cpu.yaml conda activate dot ``` -### Install dot +##### Install dot ```bash pip install -e . ``` -### Download Models +##### Download Models - Download GitHub Release binaries from [here](https://github.com/sensity-ai/dot/releases/tag/1.0.0) or use the following `wget` commands: @@ -115,9 +150,7 @@ pip install -e . rm -rf *.z* ``` -## Usage - -### Running dot +#### CLI Usage Run `dot --help` to get a full list of available options. @@ -148,7 +181,7 @@ Run `dot --help` to get a full list of available options. **Note**: To enable face superresolution, use the flag `--gpen_type gpen_256` or `--gpen_type gpen_512`. To use *dot* on CPU (not recommended), do not pass the `--use_gpu` flag. -### Controlling dot +#### Controlling dot with CLI > **Disclaimer**: We use the `SimSwap` technique for the following demonstration diff --git a/assets/gui_dot_demo.gif b/assets/gui_dot_demo.gif new file mode 100644 index 0000000..02ebd0a Binary files /dev/null and b/assets/gui_dot_demo.gif differ diff --git a/requirements-dev.txt b/requirements-dev.txt index e2d801a..c5e2f1b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # # pip-compile --extra=dev --output-file=requirements-dev.txt --strip-extras setup.cfg # @@ -8,6 +8,8 @@ absl-py==1.1.0 # via mediapipe asttokens==2.0.5 # via stack-data +atomicwrites==1.4.1 + # via pytest attrs==21.4.0 # via # mediapipe @@ -30,10 +32,20 @@ click==8.0.2 # via # black # dot (setup.cfg) +colorama==0.4.6 + # via + # click + # ipython + # pytest + # tqdm coverage==6.4.2 # via pytest-cov +customtkinter==5.2.0 + # via dot (setup.cfg) cycler==0.11.0 # via matplotlib +darkdetect==0.8.0 + # via customtkinter decorator==5.1.1 # via # ipdb @@ -68,7 +80,7 @@ ipython==8.10.0 # via # dot (setup.cfg) # ipdb -isort==5.9.3 +isort==5.12.0 # via dot (setup.cfg) jedi==0.18.1 # via ipython @@ -130,8 +142,6 @@ parso==0.8.3 # via jedi pathspec==0.9.0 # via black -pexpect==4.8.0 - # via ipython pickleshare==0.7.5 # via ipython pillow==9.3.0 @@ -156,8 +166,6 @@ protobuf==3.20.2 # dot (setup.cfg) # mediapipe # onnxruntime -ptyprocess==0.7.0 - # via pexpect pure-eval==0.2.2 # via stack-data py==1.11.0 diff --git a/requirements.txt b/requirements.txt index 33bc179..fa9f811 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: # # pip-compile setup.cfg # @@ -14,10 +14,21 @@ chardet==4.0.0 # via requests click==8.0.2 # via dot (setup.cfg) +colorama==0.4.6 + # via + # click + # pytest + # tqdm +customtkinter==5.2.0 + # via dot (setup.cfg) cycler==0.11.0 # via matplotlib +darkdetect==0.8.0 + # via customtkinter dlib==19.19.0 # via dot (setup.cfg) +exceptiongroup==1.1.2 + # via pytest face-alignment==1.3.3 # via dot (setup.cfg) flatbuffers==2.0 @@ -28,6 +39,8 @@ idna==2.10 # via requests imageio==2.19.3 # via scikit-image +iniconfig==2.0.0 + # via pytest kiwisolver==1.4.3 # via matplotlib kornia==0.6.5 @@ -72,6 +85,7 @@ packaging==21.3 # via # kornia # matplotlib + # pytest # scikit-image pillow==9.3.0 # via @@ -80,6 +94,8 @@ pillow==9.3.0 # matplotlib # scikit-image # torchvision +pluggy==1.2.0 + # via pytest protobuf==3.20.2 # via # dot (setup.cfg) @@ -89,6 +105,8 @@ pyparsing==3.0.9 # via # matplotlib # packaging +pytest==7.4.0 + # via dot (setup.cfg) python-dateutil==2.8.2 # via matplotlib pywavelets==1.3.0 @@ -112,6 +130,8 @@ six==1.16.0 # python-dateutil tifffile==2022.5.4 # via scikit-image +tomli==2.0.1 + # via pytest torch==1.9.0 # via # dot (setup.cfg) diff --git a/setup.cfg b/setup.cfg index 28a3b77..e5286c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,8 @@ install_requires = scipy torch torchvision + customtkinter + pytest [options.extras_require] dev = @@ -53,7 +55,7 @@ dev = flake8 ipdb ipython - isort + isort==5.12.0 pre-commit pytest pytest-cov @@ -65,3 +67,4 @@ where = src [options.entry_points] console_scripts = dot = dot.__main__:main + dot-ui = dot.ui.ui:main diff --git a/src/dot/faceswap_cv2/swap.py b/src/dot/faceswap_cv2/swap.py index dd15749..342c1dd 100644 --- a/src/dot/faceswap_cv2/swap.py +++ b/src/dot/faceswap_cv2/swap.py @@ -17,7 +17,9 @@ ) # define globals -CACHED_PREDICTOR_PATH = "./assets/models/shape_predictor_68_face_landmarks.dat" +CACHED_PREDICTOR_PATH = ( + "./saved_models/faceswap_cv/shape_predictor_68_face_landmarks.dat" +) class Swap: diff --git a/src/dot/ui/ui.py b/src/dot/ui/ui.py new file mode 100644 index 0000000..31537e7 --- /dev/null +++ b/src/dot/ui/ui.py @@ -0,0 +1,744 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2022, Sensity B.V. All rights reserved. +licensed under the BSD 3-Clause "New" or "Revised" License. +""" + +import os +import tkinter + +import click +import customtkinter +import yaml + +from dot.__main__ import run + +customtkinter.set_appearance_mode("Dark") +customtkinter.set_default_color_theme("blue") + + +class ToplevelUsageWindow(customtkinter.CTkToplevel): + """ + The class of the usage window + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.title("Usage") + self.geometry(f"{700}x{550}") + self.resizable(False, False) + self.attributes("-topmost", True) + + self.textbox = customtkinter.CTkTextbox( + master=self, width=700, height=550, corner_radius=0 + ) + self.textbox.grid(row=0, column=0, sticky="nsew") + self.textbox.insert( + "0.0", + """ + swap_type (str): The type of swap to run.\n + source (str): The source image or video.\n + target (Union[int, str]): The target image, video or camera id.\n + model_path (str, optional): The path to the model's weights. Defaults to None.\n + parsing_model_path (str, optional): The path to the parsing model. Defaults to None.\n + arcface_model_path (str, optional): The path to the arcface model. Defaults to None.\n + checkpoints_dir (str, optional): The path to the checkpoints directory. Defaults to None.\n + gpen_type (str, optional): The type of gpen model to use. Defaults to None.\n + gpen_path (str, optional): The path to the gpen models. Defaults to "./saved_models/gpen".\n + crop_size (int, optional): The size to crop the images to. Defaults to 224.\n + save_folder (str, optional): The path to the save folder. Defaults to None.\n + show_fps (bool, optional): Pass flag to show fps value. Defaults to False.\n + use_gpu (bool, optional): Pass flag to use GPU else use CPU. Defaults to False.\n + use_video (bool, optional): Pass flag to use video-swap pipeline. Defaults to False.\n + use_image (bool, optional): Pass flag to use image-swap pipeline. Defaults to False.\n + limit (int, optional): The number of frames to process. Defaults to None. + """, + ) + self.textbox.configure(state=tkinter.DISABLED) + + +class ToplevelAboutWindow(customtkinter.CTkToplevel): + """ + The class of the about window + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.title("About DOT") + self.geometry(f"{700}x{300}") + self.resizable(False, False) + self.attributes("-topmost", True) + + self.textbox = customtkinter.CTkTextbox( + master=self, width=700, height=300, corner_radius=0 + ) + self.textbox.grid(row=0, column=0, sticky="nsew") + + self.textbox.insert( + "0.0", + """ + dot (aka Deepfake Offensive Toolkit) makes real-time, controllable deepfakes ready for virtual cameras injection. \n + dot is created for performing penetration testing against e.g. identity verification and video conferencing systems, \n + for the use by security analysts, Red Team members, and biometrics researchers. \n + dot is developed for research and demonstration purposes. \n + As an end user, you have the responsibility to obey all applicable laws when using this program. \n + Authors and contributing developers assume no liability and are not responsible for any misuse \n + or damage caused by the use of this program. + """, + ) + self.textbox.configure(state=tkinter.DISABLED) + + +class App(customtkinter.CTk): + """ + The main class of the ui interface + """ + + def __init__(self): + super().__init__() + + # configure window + self.title("Deepfake Offensive Toolkit") + self.geometry(f"{835}x{635}") + self.resizable(False, False) + + self.grid_columnconfigure((0, 1), weight=1) + self.grid_rowconfigure((0, 1, 2, 3), weight=1) + + # create menubar + menubar = tkinter.Menu(self) + + filemenu = tkinter.Menu(menubar, tearoff=0) + filemenu.add_command(label="Exit", command=self.quit) + menubar.add_cascade(label="File", menu=filemenu) + + helpmenu = tkinter.Menu(menubar, tearoff=0) + helpmenu.add_command(label="Usage", command=self.usage_window) + helpmenu.add_separator() + helpmenu.add_command(label="About DOT", command=self.about_window) + menubar.add_cascade(label="Help", menu=helpmenu) + + self.config(menu=menubar) + + self.toplevel_usage_window = None + self.toplevel_about_window = None + + # create entry text for source, target and config + self.source_target_config_frame = customtkinter.CTkFrame(self) + self.source_target_config_frame.grid( + row=0, column=0, padx=(20, 20), pady=(20, 0), sticky="nsew" + ) + + self.source_label = customtkinter.CTkLabel( + master=self.source_target_config_frame, text="source" + ) + + self.source = customtkinter.CTkEntry( + master=self.source_target_config_frame, + placeholder_text="source", + width=85, + ) + self.source_button = customtkinter.CTkButton( + master=self.source_target_config_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.source), + width=10, + ) + + self.target = customtkinter.CTkEntry( + master=self.source_target_config_frame, placeholder_text="target", width=85 + ) + self.target_label = customtkinter.CTkLabel( + master=self.source_target_config_frame, text="target" + ) + + self.config_file_var = customtkinter.StringVar( + value="Select" + ) # set initial value + + self.config_file_combobox = customtkinter.CTkOptionMenu( + master=self.source_target_config_frame, + values=["fomm", "faceswap_cv2", "simswap", "simswaphq"], + command=self.optionmenu_callback, + variable=self.config_file_var, + width=85, + button_color="#3C3C3C", + fg_color="#343638", + dynamic_resizing=False, + ) + + self.config_file = customtkinter.CTkEntry( + master=self.source_target_config_frame, placeholder_text="config", width=85 + ) + self.config_file_label = customtkinter.CTkLabel( + master=self.source_target_config_frame, text="config_file" + ) + self.config_file_button = customtkinter.CTkButton( + master=self.source_target_config_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.upload_action_config_file( + self.config_file_combobox, self.config_file_var + ), + width=10, + ) + + self.source_label.grid(row=1, column=0, pady=(50, 10), padx=30, sticky="w") + self.source.grid(row=1, column=0, pady=(50, 10), padx=(80, 20), sticky="w") + self.source_button.grid( + row=1, + column=0, + pady=(50, 10), + padx=(175, 20), + sticky="w", + ) + + self.target.grid(row=2, column=0, pady=10, padx=(80, 20), sticky="w") + self.target_label.grid(row=2, column=0, pady=10, padx=(35, 20), sticky="w") + + self.config_file_combobox.grid( + row=3, column=0, pady=10, padx=(80, 20), sticky="w" + ) + self.config_file_label.grid(row=3, column=0, pady=10, padx=10, sticky="w") + + self.config_file_button.grid( + row=3, + column=0, + pady=10, + padx=(175, 20), + sticky="w", + ) + + # create entry text for dot options + self.option_entry_frame = customtkinter.CTkFrame(self) + self.option_entry_frame.grid( + row=1, column=0, columnspan=4, padx=(20, 20), pady=(20, 0), sticky="nsew" + ) + + self.advanced_options = customtkinter.CTkLabel( + master=self.option_entry_frame, text="Advanced" + ) + + self.model_path_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="model_path" + ) + self.model_path = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="model_path", width=85 + ) + + self.parsing_model_path_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="parsing_model" + ) + self.parsing_model_path = customtkinter.CTkEntry( + master=self.option_entry_frame, + placeholder_text="parsing_model_path", + width=85, + ) + + self.arcface_model_path_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="arcface_model" + ) + self.arcface_model_path = customtkinter.CTkEntry( + master=self.option_entry_frame, + placeholder_text="arcface_model_path", + width=85, + ) + + self.checkpoints_dir_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="checkpoints_dir" + ) + self.checkpoints_dir = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="checkpoints_dir", width=85 + ) + + self.gpen_path_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="gpen_path" + ) + self.gpen_path = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="gpen_path", width=85 + ) + + self.save_folder_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="save_folder" + ) + self.save_folder = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="save_folder", width=85 + ) + + self.crop_size_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="crop_size" + ) + self.crop_size = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="crop_size" + ) + + self.limit_label = customtkinter.CTkLabel( + master=self.option_entry_frame, text="limit" + ) + self.limit = customtkinter.CTkEntry( + master=self.option_entry_frame, placeholder_text="limit" + ) + + self.model_path_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.model_path), + width=10, + ) + self.parsing_model_path_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.parsing_model_path), + width=10, + ) + self.arcface_model_path_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.arcface_model_path), + width=10, + ) + self.checkpoints_dir_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.checkpoints_dir), + width=10, + ) + self.gpen_path_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.gpen_path), + width=10, + ) + self.save_folder_button = customtkinter.CTkButton( + master=self.option_entry_frame, + fg_color="gray", + text_color="white", + text="Open", + command=lambda: self.UploadAction(self.save_folder), + width=10, + ) + + self.advanced_options.grid(row=0, column=0, pady=10, padx=(20, 20), sticky="w") + + self.model_path_label.grid(row=1, column=2, pady=10, padx=(40, 20), sticky="w") + self.model_path.grid(row=1, column=2, pady=10, padx=(115, 20), sticky="w") + self.model_path_button.grid( + row=1, + column=2, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.parsing_model_path_label.grid( + row=2, column=2, pady=10, padx=(23, 20), sticky="w" + ) + self.parsing_model_path.grid( + row=2, column=2, pady=10, padx=(115, 20), sticky="w" + ) + self.parsing_model_path_button.grid( + row=2, + column=2, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.arcface_model_path_label.grid( + row=3, column=2, pady=10, padx=(21, 20), sticky="w" + ) + self.arcface_model_path.grid( + row=3, column=2, pady=10, padx=(115, 20), sticky="w" + ) + self.arcface_model_path_button.grid( + row=3, + column=2, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.checkpoints_dir_label.grid( + row=4, column=2, pady=10, padx=(16, 20), sticky="w" + ) + self.checkpoints_dir.grid(row=4, column=2, pady=10, padx=(115, 20), sticky="w") + self.checkpoints_dir_button.grid( + row=4, + column=2, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.gpen_path_label.grid(row=1, column=3, pady=10, padx=(48, 20), sticky="w") + self.gpen_path.grid(row=1, column=3, pady=10, padx=(115, 20), sticky="w") + self.gpen_path_button.grid( + row=1, + column=3, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.save_folder_label.grid(row=2, column=3, pady=10, padx=(40, 20), sticky="w") + self.save_folder.grid(row=2, column=3, pady=10, padx=(115, 20), sticky="w") + self.save_folder_button.grid( + row=2, + column=3, + pady=10, + padx=(210, 20), + sticky="w", + ) + + self.crop_size_label.grid(row=3, column=3, pady=10, padx=(50, 20), sticky="w") + self.crop_size.grid(row=3, column=3, pady=10, padx=(115, 20), sticky="w") + + self.limit_label.grid(row=4, column=3, pady=10, padx=(80, 20), sticky="w") + self.limit.grid(row=4, column=3, pady=10, padx=(115, 20), sticky="w") + + # create radiobutton frame for swap_type + self.swap_type_frame = customtkinter.CTkFrame(self) + self.swap_type_frame.grid( + row=0, column=1, padx=(20, 20), pady=(20, 0), sticky="nsew" + ) + self.swap_type_radio_var = tkinter.StringVar(value=None) + self.swap_type_label_radio_group = customtkinter.CTkLabel( + master=self.swap_type_frame, text="swap_type" + ) + self.swap_type_label_radio_group.grid( + row=0, column=2, columnspan=1, padx=10, pady=10, sticky="" + ) + self.fomm_radio_button = customtkinter.CTkRadioButton( + master=self.swap_type_frame, + variable=self.swap_type_radio_var, + value="fomm", + text="fomm", + ) + + self.fomm_radio_button.grid(row=1, column=2, pady=10, padx=20, sticky="w") + self.faceswap_cv2_radio_button = customtkinter.CTkRadioButton( + master=self.swap_type_frame, + variable=self.swap_type_radio_var, + value="faceswap_cv2", + text="faceswap_cv2", + ) + self.faceswap_cv2_radio_button.grid( + row=2, column=2, pady=10, padx=20, sticky="w" + ) + self.simswap_radio_button = customtkinter.CTkRadioButton( + master=self.swap_type_frame, + variable=self.swap_type_radio_var, + value="simswap", + text="simswap", + ) + self.simswap_radio_button.grid(row=3, column=2, pady=10, padx=20, sticky="w") + + # create radiobutton frame for gpen_type + self.gpen_type_frame = customtkinter.CTkFrame(self) + self.gpen_type_frame.grid( + row=0, column=2, padx=(20, 20), pady=(20, 0), sticky="nsew" + ) + self.gpen_type_radio_var = tkinter.StringVar(value="") + self.gpen_type_label_radio_group = customtkinter.CTkLabel( + master=self.gpen_type_frame, text="gpen_type" + ) + self.gpen_type_label_radio_group.grid( + row=0, column=2, columnspan=1, padx=10, pady=10, sticky="" + ) + self.gpen_type_radio_button_1 = customtkinter.CTkRadioButton( + master=self.gpen_type_frame, + variable=self.gpen_type_radio_var, + value="gpen_256", + text="gpen_256", + ) + + self.gpen_type_radio_button_1.grid( + row=1, column=2, pady=10, padx=20, sticky="w" + ) + self.gpen_type_radio_button_2 = customtkinter.CTkRadioButton( + master=self.gpen_type_frame, + variable=self.gpen_type_radio_var, + value="gpen_512", + text="gpen_512", + ) + self.gpen_type_radio_button_2.grid( + row=2, column=2, pady=10, padx=20, sticky="w" + ) + + # create checkbox and switch frame + self.checkbox_slider_frame = customtkinter.CTkFrame(self) + self.checkbox_slider_frame.grid( + row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew" + ) + + self.show_fps_checkbox_var = tkinter.IntVar() + self.show_fps_checkbox = customtkinter.CTkCheckBox( + master=self.checkbox_slider_frame, + text="show_fps", + variable=self.show_fps_checkbox_var, + ) + + self.use_gpu_checkbox_var = tkinter.IntVar() + self.use_gpu_checkbox = customtkinter.CTkCheckBox( + master=self.checkbox_slider_frame, + text="use_gpu", + variable=self.use_gpu_checkbox_var, + ) + + self.use_video_checkbox_var = tkinter.IntVar() + self.use_video_checkbox = customtkinter.CTkCheckBox( + master=self.checkbox_slider_frame, + text="use_video", + variable=self.use_video_checkbox_var, + ) + + self.use_image_checkbox_var = tkinter.IntVar() + self.use_image_checkbox = customtkinter.CTkCheckBox( + master=self.checkbox_slider_frame, + text="use_image", + variable=self.use_image_checkbox_var, + ) + + self.head_pose_checkbox_var = tkinter.IntVar() + self.head_pose_checkbox = customtkinter.CTkCheckBox( + master=self.checkbox_slider_frame, + text="head_pose", + variable=self.head_pose_checkbox_var, + ) + + self.show_fps_checkbox.grid(row=1, column=3, pady=(20, 0), padx=20, sticky="w") + self.use_gpu_checkbox.grid(row=2, column=3, pady=(20, 0), padx=20, sticky="w") + self.use_video_checkbox.grid(row=3, column=3, pady=(20, 0), padx=20, sticky="w") + self.use_image_checkbox.grid(row=4, column=3, pady=(20, 0), padx=20, sticky="w") + self.head_pose_checkbox.grid(row=5, column=3, pady=(20, 0), padx=20, sticky="w") + + # create run button + self.run_button = customtkinter.CTkButton( + master=self, + fg_color="white", + border_width=2, + text_color="black", + text="RUN", + command=self.start_button_event, + ) + self.run_button.grid( + row=2, column=1, columnspan=2, padx=(0, 100), pady=(20, 20), sticky="nsew" + ) + + def usage_window(self): + """ + Open the usage window + """ + + if ( + self.toplevel_usage_window is None + or not self.toplevel_usage_window.winfo_exists() + ): + self.toplevel_usage_window = ToplevelUsageWindow( + self + ) # create window if its None or destroyed + self.toplevel_usage_window.focus() + + def about_window(self): + """ + Open the about window + """ + + if ( + self.toplevel_about_window is None + or not self.toplevel_about_window.winfo_exists() + ): + self.toplevel_about_window = ToplevelAboutWindow( + self + ) # create window if its None or destroyed + self.toplevel_about_window.focus() + + def UploadAction(self, entry_element: customtkinter.CTkOptionMenu): + """ + Action for the upload buttons to update the value of a CTkEntry + + Args: + entry_element (customtkinter.CTkOptionMenu): The CTkEntry element. + """ + + filename = tkinter.filedialog.askopenfilename() + self.modify_entry(entry_element, filename) + + def modify_entry(self, entry_element: customtkinter.CTkEntry, text: str): + """ + Modify the value of the CTkEntry + + Args: + entry_element (customtkinter.CTkOptionMenu): The CTkEntry element. + text (str): The new text that will be inserted into the CTkEntry + """ + + entry_element.delete(0, tkinter.END) + entry_element.insert(0, text) + + def upload_action_config_file( + self, + element: customtkinter.CTkOptionMenu, + config_file_var: customtkinter.StringVar, + ): + """ + Set the configurations for the swap_type using the upload button + + Args: + element (customtkinter.CTkOptionMenu): The OptionMenu element. + config_file_var (customtkinter.StringVar): OptionMenu variable. + """ + + entry_list = [ + "source", + "target", + "model_path", + "parsing_model_path", + "arcface_model_path", + "checkpoints_dir", + "gpen_path", + "save_folder", + "crop_size", + "limit", + ] + radio_list = ["swap_type"] + + filename = tkinter.filedialog.askopenfilename() + + config = {} + if len(filename) > 0: + with open(filename) as f: + config = yaml.safe_load(f) + + if config["swap_type"] == "simswap": + if config.get("swap_type", "0") == "512": + config_file_var = "simswaphq" + else: + config_file_var = "simswap" + else: + config_file_var = config["swap_type"] + + element.set(config_file_var) + + for key in config.keys(): + if key in entry_list: + self.modify_entry(eval(f"self.{key}"), config[key]) + elif key in radio_list: + self.swap_type_radio_var = tkinter.StringVar(value=config[key]) + eval(f"self.{config[key]}_radio_button").invoke() + + for entry in entry_list: + if entry not in ["source", "target"]: + if entry not in config.keys(): + self.modify_entry(eval(f"self.{entry}"), "") + + def optionmenu_callback(self, choice: str): + """ + Set the configurations for the swap_type using the optionmenu + + Args: + choice (str): The type of swap to run. + """ + + entry_list = [ + "source", + "target", + "model_path", + "parsing_model_path", + "arcface_model_path", + "checkpoints_dir", + "gpen_path", + "save_folder", + "crop_size", + "limit", + ] + radio_list = ["swap_type"] + + config_file = f"./configs/{choice}.yaml" + if os.path.isfile(config_file): + config = {} + with open(config_file) as f: + config = yaml.safe_load(f) + + for key in config.keys(): + if key in entry_list: + self.modify_entry(eval(f"self.{key}"), config[key]) + elif key in radio_list: + self.swap_type_radio_var = tkinter.StringVar(value=config[key]) + eval(f"self.{config[key]}_radio_button").invoke() + + for entry in entry_list: + if entry not in ["source", "target"]: + if entry not in config.keys(): + self.modify_entry(eval(f"self.{entry}"), "") + + def start_button_event(self): + """ + Start running the deepfake + """ + + # load config, if provided + config = {} + if len(self.config_file.get()) > 0: + with open(self.config_file.get()) as f: + config = yaml.safe_load(f) + + # run dot + run( + swap_type=config.get("swap_type", self.swap_type_radio_var.get() or None), + source=config.get("source", self.source.get() or None), + target=config.get("target", self.target.get() or None), + model_path=config.get("model_path", self.model_path.get() or None), + parsing_model_path=config.get( + "parsing_model_path", self.parsing_model_path.get() or None + ), + arcface_model_path=config.get( + "arcface_model_path", self.arcface_model_path.get() or None + ), + checkpoints_dir=config.get( + "checkpoints_dir", self.checkpoints_dir.get() or None + ), + gpen_type=config.get("gpen_type", self.gpen_type_radio_var.get()), + gpen_path=config.get( + "gpen_path", self.gpen_path.get() or "./saved_models/gpen" + ), + crop_size=config.get( + "crop_size", + (int(self.crop_size.get()) if len(self.crop_size.get()) > 0 else None) + or 224, + ), + head_pose=config.get("head_pose", int(self.head_pose_checkbox.get())), + save_folder=config.get("save_folder", self.save_folder.get() or None), + show_fps=config.get("show_fps", int(self.show_fps_checkbox.get())), + use_gpu=config.get("use_gpu", int(self.use_gpu_checkbox.get())), + use_video=config.get("use_video", int(self.use_video_checkbox.get())), + use_image=config.get("use_image", int(self.use_image_checkbox.get())), + limit=config.get("limit", self.limit.get() or None), + ) + + +@click.command() +def main(): + """Run the dot UI.""" + + app = App() + app.mainloop() + + +if __name__ == "__main__": + main()