1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-08 11:53:26 -04:00
faceswap/scripts/gui.py
torzdf 2c11eb1649 Built in GUI [ALPHA] (#325)
* Alpha Build - Pre Testing

* Minor code amends

* Add in the actual script execution
2018-04-04 14:39:23 -07:00

402 lines
16 KiB
Python

import sys
from threading import Thread
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from lib.cli import FullPaths
from lib.Serializer import JSONSerializer
class TKGui(object):
''' The Graphical User Interface '''
def __init__(self, subparser, subparsers, parser, command, description='default'):
self.root = tk.Tk()
self.parser = parser
self.opts = self.extract_options(subparsers)
self.icofolder = tk.PhotoImage(file='icons/open_folder.png')
self.icofile = tk.PhotoImage(file='icons/open_file.png')
self.icosave = tk.PhotoImage(file='icons/save.png')
self.icoreset = tk.PhotoImage(file='icons/reset.png')
self.icoclear = tk.PhotoImage(file='icons/clear.png')
self.helptext = tk.StringVar()
self.statustext = tk.StringVar()
self.serializer = JSONSerializer
self.filetypes=(('Faceswap files', '*.fsw'), ('All files', '*.*'))
self.parse_arguments(description, subparser, command)
def bad_args(self, args):
self.parser.print_help()
exit(0)
def extract_options(self, subparsers):
''' Extract the existing ArgParse Options '''
options = {command: subparsers[command].argument_list + subparsers[command].optional_arguments for command in subparsers.keys()}
for command in options.values():
for option in command:
option['control_title'] = self.set_control_title(option.get('opts',''))
option['control_type'] = self.set_control_type(option)
return options
@staticmethod
def set_control_title(opts):
''' Take the option switch and format it nicely '''
ctl_title = opts[0]
if len(opts) == 2:
ctl_title = opts[1]
ctl_title = ctl_title.replace('-',' ').replace('_',' ')
ctl_title = ctl_title.title().strip()
return ctl_title
@staticmethod
def set_control_type(option):
''' Set what control type we should use for an option based on existence of various variables '''
if option.get('dest', '') == 'alignments_path':
ctl_type = 'filechooser'
elif option.get('action', '') == FullPaths:
ctl_type = 'folderchooser'
elif option.get('choices', '') != '':
ctl_type = 'combobox'
elif option.get('type', '') in (str, float, int):
ctl_type = 'entrybox'
elif option.get('action', '') == 'store_true':
ctl_type = 'checkbox'
return ctl_type
def parse_arguments(self, description, subparser, command):
parser = subparser.add_parser(
command,
help="This Launches a GUI for Faceswap.",
description=description,
epilog="Questions and feedback: \
https://github.com/deepfakes/faceswap-playground"
)
parser.set_defaults(func=self.process)
def process(self, arguments):
''' Builds the GUI '''
self.arguments = arguments
self.root.title('faceswap.py')
self.menu()
notebook = ttk.Notebook(self.root)
notebook.pack(fill=tk.BOTH, expand=True)
# extract, train and convert are explicitly stated to ensure they are always displayed in the same order
for command in ('extract', 'train', 'convert'):
title = command.title()
page = ttk.Frame(notebook)
self.add_left_frame(page, command)
self.add_frame_seperator(page)
opt_frame = self.add_right_frame(page)
for option in self.opts[command]:
self.build_tabs(option, opt_frame)
notebook.add(page, text=title)
self.root.mainloop()
# All pages stuff
def menu(self):
''' Menu bar for loading and saving configs '''
menubar = tk.Menu(self.root)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label='Load full config...', command=self.load_config)
filemenu.add_command(label='Save full config...', command=self.save_config)
filemenu.add_separator()
filemenu.add_command(label='Reset all to default', command=self.reset_config)
filemenu.add_command(label='Clear all', command=self.clear_config)
filemenu.add_separator()
filemenu.add_command(label='Quit', command=self.root.quit)
menubar.add_cascade(label="File", menu=filemenu)
self.root.config(menu=menubar)
def load_config(self, command=None):
''' Load a saved config file '''
config_file = filedialog.askopenfile(mode='r', filetypes=self.filetypes)
if not config_file:
return
config = self.serializer.unmarshal(config_file.read())
if command is None:
for command, options in config.items():
self.set_command_args(command, options)
else:
options = config[command]
self.set_command_args(command, options)
def set_command_args(self, command, options):
''' Pass the saved config items back to the GUI '''
for src_option, src_value in options.items():
for dst_options in self.opts[command]:
if dst_options['control_title'] == src_option:
dst_options['value'].set(src_value)
break
def save_config(self, command=None):
''' Save the current GUI state to a config file in json format '''
config_file = filedialog.asksaveasfile(mode='w', filetypes=self.filetypes, defaultextension='.fsw')
if not config_file:
return
if command is None:
config = {command: {option['control_title']: option['value'].get() for option in options} for command, options in self.opts.items()}
else:
config = {command: {option['control_title']: option['value'].get() for option in self.opts[command]}}
config_file.write(self.serializer.marshal(config))
config_file.close
def reset_config(self, command=None):
''' Reset the GUI to the default values '''
if command is None:
options = [option for options in self.opts.values() for option in options]
else:
options = [option for option in self.opts[command]]
for option in options:
default = option.get('default', '')
default = '' if default is None else default
option['value'].set(default)
def clear_config(self, command=None):
''' Clear all values from the GUI '''
if command is None:
options = [option for options in self.opts.values() for option in options]
else:
options = [option for option in self.opts[command]]
for option in options:
if isinstance(option['value'].get(), bool):
option['value'].set(False)
elif isinstance(option['value'].get(), int):
option['value'].set(0)
else:
option['value'].set('')
@staticmethod
def add_frame_seperator(page):
''' Add a seperator between left and right frames '''
separator = tk.Frame(page, width=2, bd=1, relief=tk.SUNKEN)
separator.pack(fill=tk.Y, padx=5, side=tk.LEFT)
# Left Frame stuff
def add_left_frame(self, page, title):
''' Add help display and execute button to the left frame of each page '''
frame = tk.Frame(page)
frame.pack(fill=tk.X, padx=(10,5), side=tk.LEFT, anchor=tk.N)
topframe = tk.Frame(frame)
topframe.pack(fill=tk.X, side=tk.TOP)
bottomframe = tk.Frame(frame)
bottomframe.pack(fill=tk.X, side=tk.BOTTOM)
self.add_info_section(topframe)
self.add_action_buttons(topframe, title)
self.add_util_buttons(bottomframe, title)
self.add_status_section(bottomframe)
def add_info_section(self, frame):
''' Build the info text section page '''
helpframe=tk.Frame(frame)
helpframe.pack(fill=tk.X, side=tk.TOP, pady=5)
lbl_title = tk.Label(helpframe, text='Info', width=15, anchor=tk.SW)
lbl_title.pack(side=tk.TOP)
self.helptext.set('')
lbl_help = tk.Label(helpframe, height=20, width=15, textvariable=self.helptext, wraplength=120, justify=tk.LEFT, anchor=tk.NW, bg="gray90")
lbl_help.pack(side=tk.BOTTOM, anchor=tk.N)
def bind_help(self, control, helptext):
''' Controls the help text displayed on mouse hover '''
control.bind('<Enter>', lambda event: self.helptext.set(helptext))
control.bind('<FocusIn>', lambda event: self.helptext.set(helptext))
control.bind('<Leave>', lambda event: self.helptext.set(''))
control.bind('<FocusOut>', lambda event: self.helptext.set(''))
def add_action_buttons(self, frame, title):
''' Add the action buttons for page '''
command = title.lower()
title = title.capitalize()
actframe = tk.Frame(frame)
actframe.pack(fill=tk.X, side=tk.BOTTOM, pady=(15, 0))
btnexecute = tk.Button(actframe, text=title, height=2, width=12, command=lambda: self.execute_script(command))
btnexecute.pack()
self.bind_help(btnexecute, 'Run the {} script'.format(title))
def add_util_buttons(self, frame, title):
''' Add the section utility buttons '''
command = title.lower()
title = title.capitalize()
utilframe = tk.Frame(frame)
utilframe.pack(side=tk.TOP, pady=(5,0))
btnload = tk.Button(utilframe, image=self.icofile, height=16, width=16, command=lambda: self.load_config(command))
btnload.pack(padx=2, pady=2, side=tk.LEFT)
self.bind_help(btnload, 'Load existing {} config'.format(title))
btnsave = tk.Button(utilframe, image=self.icosave, height=16, width=16, command=lambda: self.save_config(command))
btnsave.pack(padx=2, pady=2, side=tk.LEFT)
self.bind_help(btnsave, 'Save {} config'.format(title))
btnreset = tk.Button(utilframe, image=self.icoreset, height=16, width=16, command=lambda: self.reset_config(command))
btnreset.pack(padx=2, pady=2, side=tk.RIGHT)
self.bind_help(btnreset, 'Reset {} config to default'.format(title))
btnclear = tk.Button(utilframe, image=self.icoclear, height=16, width=16, command=lambda: self.clear_config(command))
btnclear.pack(padx=2, pady=2, side=tk.RIGHT)
self.bind_help(btnclear, 'Clear all entries for {} config'.format(title))
def add_status_section(self, frame):
''' Build the info text section page '''
statusframe = tk.Frame(frame)
statusframe.pack(side=tk.BOTTOM, pady=(5,0))
lbl_title = tk.Label(statusframe, text='Status', width=15, anchor=tk.SW)
lbl_title.pack(side=tk.TOP)
self.statustext.set('Idle')
lbl_status = tk.Label(statusframe, height=1, width=15, textvariable=self.statustext, wraplength=120, justify=tk.LEFT, anchor=tk.NW, bg="gray90")
lbl_status.pack(side=tk.BOTTOM, anchor=tk.N)
def execute_script(self, command):
optlist = ['faceswap.py', command]
for item in self.opts[command]:
opt_value = str(item.get('value','').get())
opt = item['opts'][0]
if opt_value == 'False' or opt_value == '':
continue
elif opt_value == 'True':
optlist.append(opt)
else:
optlist.extend((opt, opt_value))
sys.argv = optlist
process = Thread(target=self.launch_thread, args=(command,))
process.start()
def launch_thread(self, command):
''' Launch the script inside a subprocess to keep the GUI active '''
title = command.capitalize()
self.statustext.set('Running - {}'.format(title))
self.parser.set_defaults(func=self.bad_args)
arguments = self.parser.parse_args()
arguments.func(arguments)
self.statustext.set('Idle')
# Right Frame setup
def add_right_frame(self, page):
''' Add the options panel to the right frame of each page '''
frame = tk.Frame(page)
frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(0,5))
canvas = tk.Canvas(frame, width=490, height=450, bd=0, highlightthickness=0)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.add_scrollbar(frame, canvas)
opts_frame = tk.Frame(canvas)
canvas.create_window((0,0), window=opts_frame, anchor=tk.NW)
return opts_frame
def add_scrollbar(self, frame, canvas):
''' Add a scrollbar to the options frame '''
scrollbar = tk.Scrollbar(frame, command=canvas.yview)
scrollbar.pack(side=tk.LEFT, fill='y')
canvas.configure(yscrollcommand = scrollbar.set)
canvas.bind('<Configure>', lambda event, opt_canvas=canvas: self.update_scrollbar(event, opt_canvas))
@staticmethod
def update_scrollbar(event, canvas):
canvas.configure(scrollregion=canvas.bbox('all'))
# Build the Right Frame Options
def build_tabs(self, option, page):
''' Build the correct control type for the option passed through '''
ctl_type = option['control_type']
ctl_title = option['control_title']
ctl_help = ' '.join(option.get('help', '').split())
ctl_help = '. '.join(i.capitalize() for i in ctl_help.split('. '))
ctl_help = ctl_title + ' - ' + ctl_help
ctl_frame = self.build_control_frame(page)
self.build_control_label(ctl_frame, ctl_title)
if ctl_type == 'combobox':
option['value'] = self.build_combobox(ctl_frame, ctl_help, option.get('default',''), option['choices'])
if ctl_type == 'checkbox':
option['value'] = self.build_checkbox(ctl_frame, ctl_help, option.get('default', False), ctl_title)
if ctl_type == 'entrybox':
option['value'] = self.build_entrybox(ctl_frame, ctl_help, option.get('default',''), None)
if ctl_type in ('filechooser', 'folderchooser'):
option['value'] = self.build_entrybox(ctl_frame, ctl_help, option.get('default',''), ctl_type)
@staticmethod
def build_control_frame(page):
''' Build the frame to hold the control '''
frame = tk.Frame(page)
frame.pack(fill=tk.X)
return frame
@staticmethod
def build_control_label(frame, title):
''' Build and place the control label '''
label = tk.Label(frame, text=title, width=15, anchor=tk.W)
label.pack(padx=5, pady=5, side=tk.LEFT, anchor=tk.N)
def build_combobox(self, frame, helptext, default, items):
''' Build and place combobox controls '''
default = default if default in items else ''
cmbvar = tk.StringVar(frame)
cmbvar.set(default)
cmbbox = ttk.Combobox(frame, textvariable=cmbvar, width=40)
cmbbox['values'] = [item for item in items]
cmbbox.pack(fill=tk.X, padx=5, pady=5)
self.bind_help(cmbbox, helptext)
return cmbvar
def build_checkbox(self, frame, helptext, default, title):
''' Build and place checkbox controls '''
chkvar = tk.BooleanVar(frame)
chkvar.set(default)
chkbox = tk.Checkbutton(frame, variable=chkvar)
chkbox.pack(padx=5, pady=5, anchor=tk.W)
self.bind_help(chkbox, helptext)
return chkvar
def build_entrybox(self, frame, helptext, default, opentype = None):
''' Build and place entry controls '''
default = default if default is not None else ''
etyvar = tk.StringVar(frame)
etyvar.set(default)
if opentype is not None:
self.add_browser_buttons(frame, opentype, etyvar)
etybox = tk.Entry(frame, textvariable=etyvar)
etybox.pack(fill=tk.X, padx=5, pady=5)
self.bind_help(etybox, helptext)
return etyvar
def add_browser_buttons(self, frame, opentype, filepath):
''' Add correct file browser button for control '''
if opentype == 'filechooser':
fileopn = tk.Button(frame, image=self.icofile, command=lambda: self.askfile(filepath), width=16, height=16)
else:
fileopn = tk.Button(frame, image=self.icofolder, command=lambda: self.askdirectory(filepath))
fileopn.pack(side=tk.RIGHT)
@staticmethod
def askdirectory(filepath):
''' Pop-up to get path to a directory '''
dirname = filedialog.askdirectory()
if dirname:
filepath.set(dirname)
@staticmethod
def askfile(filepath):
''' Pop-up to get path to a file '''
filename = filedialog.askopenfilename()
if filename:
filepath.set(filename)