Migrate to theme based UI using customtkinter

This commit is contained in:
henryruhs 2023-06-11 20:22:56 +02:00
parent 7ccc36dbf4
commit 315c980be8
7 changed files with 208 additions and 91 deletions

View File

@ -6,6 +6,7 @@ onnx==1.14.0
insightface==0.7.3
psutil==5.9.5
tk==0.1.0
customtkinter==5.1.3
pillow==9.5.0
torch==2.0.1+cu118; sys_platform != 'darwin'
torch==2.0.1; sys_platform == 'darwin'

View File

@ -7,6 +7,7 @@ FACE_ANALYSER = None
def get_face_analyser() -> Any:
global FACE_ANALYSER
if FACE_ANALYSER is None:
FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=roop.globals.providers)
FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))

View File

@ -48,6 +48,7 @@ else:
def get_code_former():
global CODE_FORMER
with THREAD_LOCK:
if CODE_FORMER is None:
CODE_FORMER = ARCH_REGISTRY.get("CodeFormer")(

View File

@ -7,19 +7,20 @@ import insightface
import threading
import roop.globals
from roop.analyser import get_one_face, get_many_faces
from roop.utilities import conditional_download
from roop.utilities import conditional_download, resolve_relative_path
FACE_SWAPPER = None
THREAD_LOCK = threading.Lock()
def pre_check() -> None:
download_directory_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models')
download_directory_path = resolve_relative_path('../models')
conditional_download(download_directory_path, ['https://huggingface.co/deepinsight/inswapper/resolve/main/inswapper_128.onnx'])
def get_face_swapper() -> None:
global FACE_SWAPPER
with THREAD_LOCK:
if FACE_SWAPPER is None:
model_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../models/inswapper_128.onnx')

155
roop/ui.json Normal file
View File

@ -0,0 +1,155 @@
{
"CTk": {
"fg_color": ["gray95", "gray10"]
},
"CTkToplevel": {
"fg_color": ["gray95", "gray10"]
},
"CTkFrame": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray90", "gray13"],
"top_fg_color": ["gray85", "gray16"],
"border_color": ["gray65", "gray28"]
},
"CTkButton": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["#3a7ebf", "#1f538d"],
"hover_color": ["#325882", "#14375e"],
"border_color": ["#3E454A", "#949A9F"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkLabel": {
"corner_radius": 0,
"fg_color": "transparent",
"text_color": ["gray14", "gray84"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckbox": {
"corner_radius": 6,
"border_width": 3,
"fg_color": ["#3a7ebf", "#1f538d"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkSwitch": {
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkRadiobutton": {
"corner_radius": 1000,
"border_width_checked": 6,
"border_width_unchecked": 3,
"fg_color": ["#3a7ebf", "#1f538d"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkProgressBar": {
"corner_radius": 1000,
"border_width": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"],
"border_color": ["gray", "gray"]
},
"CTkSlider": {
"corner_radius": 1000,
"button_corner_radius": 1000,
"border_width": 6,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["gray40", "#AAB0B5"],
"button_color": ["#3a7ebf", "#1f538d"],
"button_hover_color": ["#325882", "#14375e"]
},
"CTkOptionMenu": {
"corner_radius": 6,
"fg_color": ["#3a7ebf", "#1f538d"],
"button_color": ["#325882", "#14375e"],
"button_hover_color": ["#234567", "#1e2c40"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkComboBox": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"button_color": ["#979DA2", "#565B5E"],
"button_hover_color": ["#6E7174", "#7A848D"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray50", "gray45"]
},
"CTkScrollbar": {
"corner_radius": 1000,
"border_spacing": 4,
"fg_color": "transparent",
"button_color": ["gray55", "gray41"],
"button_hover_color": ["gray40", "gray53"]
},
"CTkSegmentedButton": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#979DA2", "gray29"],
"selected_color": ["#3a7ebf", "#1f538d"],
"selected_hover_color": ["#325882", "#14375e"],
"unselected_color": ["#979DA2", "gray29"],
"unselected_hover_color": ["gray70", "gray41"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray100", "gray20"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],
"text_color": ["gray14", "gray84"]
},
"CTkFont": {
"macOS": {
"family": "Avenir",
"size": 12,
"weight": "normal"
},
"Windows": {
"family": "Corbel",
"size": 12,
"weight": "normal"
},
"Linux": {
"family": "Montserrat",
"size": 12,
"weight": "normal"
}
}
}

View File

@ -1,7 +1,6 @@
import os
import tkinter as tk
from tkinter import filedialog
from typing import Callable, Any, Tuple
import customtkinter as ctk
from typing import Callable, Tuple
import cv2
from PIL import Image, ImageTk, ImageOps
@ -10,12 +9,8 @@ import roop.globals
from roop.analyser import get_one_face
from roop.capturer import get_video_frame
from roop.swapper import process_faces
from roop.utilities import is_image, is_video
from roop.utilities import is_image, is_video, resolve_relative_path
PRIMARY_COLOR = '#2d3436'
SECONDARY_COLOR = '#74b9ff'
TERTIARY_COLOR = '#f1c40f'
ACCENT_COLOR = '#2ecc71'
WINDOW_HEIGHT = 700
WINDOW_WIDTH = 600
PREVIEW_MAX_HEIGHT = 700
@ -25,7 +20,7 @@ RECENT_DIRECTORY_TARGET = None
RECENT_DIRECTORY_OUTPUT = None
def init(start: Callable, destroy: Callable) -> tk.Tk:
def init(start: Callable, destroy: Callable) -> ctk.CTk:
global ROOT, PREVIEW
ROOT = create_root(start, destroy)
@ -34,131 +29,88 @@ def init(start: Callable, destroy: Callable) -> tk.Tk:
return ROOT
def create_root(start: Callable, destroy: Callable) -> tk.Tk:
def create_root(start: Callable, destroy: Callable) -> ctk.CTk:
global source_label, target_label, status_label
root = tk.Tk()
ctk.set_appearance_mode('system')
ctk.set_default_color_theme(resolve_relative_path('ui.json'))
root = ctk.CTk()
root.minsize(WINDOW_WIDTH, WINDOW_HEIGHT)
root.title('roop')
root.configure(bg=PRIMARY_COLOR)
root.option_add('*Font', ('Arial', 11))
root.configure()
root.protocol('WM_DELETE_WINDOW', lambda: destroy())
source_label = tk.Label(root, bg=PRIMARY_COLOR)
source_label = ctk.CTkLabel(root, text=None)
source_label.place(relx=0.1, rely=0.1, relwidth=0.3, relheight=0.25)
target_label = tk.Label(root, bg=PRIMARY_COLOR)
target_label = ctk.CTkLabel(root, text=None)
target_label.place(relx=0.6, rely=0.1, relwidth=0.3, relheight=0.25)
source_button = create_primary_button(root, 'Select a face', lambda: select_source_path())
source_button = ctk.CTkButton(root, text='Select a face', command=lambda: select_source_path())
source_button.place(relx=0.1, rely=0.4, relwidth=0.3, relheight=0.1)
target_button = create_primary_button(root, 'Select a target', lambda: select_target_path())
target_button = ctk.CTkButton(root, text='Select a target', command=lambda: select_target_path())
target_button.place(relx=0.6, rely=0.4, relwidth=0.3, relheight=0.1)
keep_fps_value = tk.BooleanVar(value=roop.globals.keep_fps)
keep_fps_checkbox = create_checkbox(root, 'Limit to 30 fps', keep_fps_value, lambda: setattr(roop.globals, 'keep_fps', not roop.globals.keep_fps))
keep_fps_value = ctk.BooleanVar(value=roop.globals.keep_fps)
keep_fps_checkbox = ctk.CTkSwitch(root, text='Keep fps', variable=keep_fps_value, command=lambda: setattr(roop.globals, 'keep_fps', not roop.globals.keep_fps))
keep_fps_checkbox.place(relx=0.1, rely=0.6)
keep_frames_value = tk.BooleanVar(value=roop.globals.keep_frames)
keep_frames_checkbox = create_checkbox(root, 'Keep frames dir', keep_frames_value, lambda: setattr(roop.globals, 'keep_frames', keep_frames_value.get()))
keep_frames_checkbox.place(relx=0.1, rely=0.65)
keep_frames_value = ctk.BooleanVar(value=roop.globals.keep_frames)
keep_frames_switch = ctk.CTkSwitch(root, text='Keep frames', variable=keep_frames_value, command=lambda: setattr(roop.globals, 'keep_frames', keep_frames_value.get()))
keep_frames_switch.place(relx=0.1, rely=0.65)
keep_audio_value = tk.BooleanVar(value=roop.globals.keep_audio)
keep_audio_checkbox = create_checkbox(root, 'Keep original audio', keep_audio_value, lambda: setattr(roop.globals, 'keep_audio', keep_audio_value.get()))
keep_audio_checkbox.place(relx=0.6, rely=0.6)
keep_audio_value = ctk.BooleanVar(value=roop.globals.keep_audio)
keep_audio_switch = ctk.CTkSwitch(root, text='Keep audio', variable=keep_audio_value, command=lambda: setattr(roop.globals, 'keep_audio', keep_audio_value.get()))
keep_audio_switch.place(relx=0.6, rely=0.6)
many_faces_value = tk.BooleanVar(value=roop.globals.many_faces)
many_faces_checkbox = create_checkbox(root, 'Replace all faces', many_faces_value, lambda: setattr(roop.globals, 'many_faces', many_faces_value.get()))
many_faces_checkbox.place(relx=0.6, rely=0.65)
many_faces_value = ctk.BooleanVar(value=roop.globals.many_faces)
many_faces_switch = ctk.CTkSwitch(root, text='Many faces', variable=many_faces_value, command=lambda: setattr(roop.globals, 'many_faces', many_faces_value.get()))
many_faces_switch.place(relx=0.6, rely=0.65)
start_button = create_secondary_button(root, 'Start', lambda: select_output_path(start))
start_button = ctk.CTkButton(root, text='Start', command=lambda: select_output_path(start))
start_button.place(relx=0.15, rely=0.75, relwidth=0.2, relheight=0.05)
stop_button = create_secondary_button(root, 'Destroy', lambda: destroy())
stop_button = ctk.CTkButton(root, text='Destroy', command=lambda: destroy())
stop_button.place(relx=0.4, rely=0.75, relwidth=0.2, relheight=0.05)
preview_button = create_secondary_button(root, 'Preview', lambda: toggle_preview())
preview_button = ctk.CTkButton(root, text='Preview', command=lambda: toggle_preview())
preview_button.place(relx=0.65, rely=0.75, relwidth=0.2, relheight=0.05)
status_label = tk.Label(root, justify='center', text='Status: None', fg=ACCENT_COLOR, bg=PRIMARY_COLOR)
status_label = ctk.CTkLabel(root, text='Status: None', justify='center')
status_label.place(relx=0.1, rely=0.9)
return root
def create_preview(parent) -> tk.Toplevel:
global preview_label, preview_scale
def create_preview(parent) -> ctk.CTkToplevel:
global preview_label, preview_slider
preview = tk.Toplevel(parent)
preview = ctk.CTkToplevel(parent)
preview.withdraw()
preview.title('Preview')
preview.configure(bg=PRIMARY_COLOR)
preview.option_add('*Font', ('Arial', 11))
preview.configure()
preview.protocol('WM_DELETE_WINDOW', lambda: toggle_preview())
preview.resizable(width=False, height=False)
preview_label = tk.Label(preview, bg=PRIMARY_COLOR)
preview_label = ctk.CTkLabel(preview, text=None)
preview_label.pack(fill='both', expand=True)
preview_scale = tk.Scale(preview, orient='horizontal', command=lambda frame_value: update_preview(int(frame_value)))
preview_scale.pack(fill='x')
preview_slider = ctk.CTkSlider(preview, from_=0, to=100, border_width=10, command=lambda frame_value: update_preview(frame_value))
preview_slider.pack(fill='x')
return preview
def create_primary_button(parent: Any, text: str, command: Callable) -> tk.Button:
return tk.Button(
parent,
text=text,
command=command,
bg=PRIMARY_COLOR,
fg=SECONDARY_COLOR,
relief='flat',
highlightthickness=4,
highlightbackground=SECONDARY_COLOR,
activebackground=SECONDARY_COLOR,
borderwidth=4
)
def create_secondary_button(parent: Any, text: str, command: Callable) -> tk.Button:
return tk.Button(
parent,
text=text,
command=command,
bg=TERTIARY_COLOR,
relief='flat',
borderwidth=0,
highlightthickness=0
)
def create_checkbox(parent: Any, text: str, variable: tk.BooleanVar, command: Callable) -> tk.Checkbutton:
return tk.Checkbutton(
parent,
text=text,
variable=variable,
command=command,
relief='flat',
bg=PRIMARY_COLOR,
activebackground=PRIMARY_COLOR,
activeforeground=SECONDARY_COLOR,
selectcolor=PRIMARY_COLOR,
fg=SECONDARY_COLOR,
borderwidth=0,
highlightthickness=0
)
def update_status(text: str) -> None:
status_label['text'] = text
status_label.configure(text=text)
ROOT.update()
def select_source_path() -> None:
global RECENT_DIRECTORY_SOURCE
source_path = filedialog.askopenfilename(title='Select an face image', initialdir=RECENT_DIRECTORY_SOURCE)
source_path = ctk.filedialog.askopenfilename(title='Select an face image', initialdir=RECENT_DIRECTORY_SOURCE)
if is_image(source_path):
roop.globals.source_path = source_path
RECENT_DIRECTORY_SOURCE = os.path.dirname(roop.globals.source_path)
@ -173,7 +125,8 @@ def select_source_path() -> None:
def select_target_path() -> None:
global RECENT_DIRECTORY_TARGET
target_path = filedialog.askopenfilename(title='Select an image or video target', initialdir=RECENT_DIRECTORY_TARGET)
target_path = ctk.filedialog.askopenfilename(title='Select an image or video target', initialdir=RECENT_DIRECTORY_TARGET)
if is_image(target_path):
roop.globals.target_path = target_path
RECENT_DIRECTORY_TARGET = os.path.dirname(roop.globals.target_path)
@ -194,10 +147,11 @@ def select_target_path() -> None:
def select_output_path(start):
global RECENT_DIRECTORY_OUTPUT
if is_image(roop.globals.target_path):
output_path = filedialog.asksaveasfilename(title='Save image output', initialfile='output.png', initialdir=RECENT_DIRECTORY_OUTPUT)
output_path = ctk.filedialog.asksaveasfilename(title='Save image output', initialfile='output.png', initialdir=RECENT_DIRECTORY_OUTPUT)
elif is_video(roop.globals.target_path):
output_path = filedialog.asksaveasfilename(title='Save video output', initialfile='output.mp4', initialdir=RECENT_DIRECTORY_OUTPUT)
output_path = ctk.filedialog.asksaveasfilename(title='Save video output', initialfile='output.mp4', initialdir=RECENT_DIRECTORY_OUTPUT)
if output_path:
roop.globals.output_path = output_path
RECENT_DIRECTORY_OUTPUT = os.path.dirname(roop.globals.output_path)

View File

@ -126,3 +126,7 @@ def conditional_download(download_directory_path: str, urls: List[str]):
total = int(request.headers.get('Content-Length', 0))
with tqdm(total=total, desc='Downloading', unit='B', unit_scale=True, unit_divisor=1024) as progress:
urllib.request.urlretrieve(url, download_file_path, reporthook=lambda count, block_size, total_size: progress.update(block_size))
def resolve_relative_path(path: str) -> str:
return os.path.abspath(os.path.join(os.path.dirname(__file__), path))