1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-07 10:43:27 -04:00
faceswap/lib/cli/actions.py
torzdf 6a3b674bef
Rebase code (#1326)
* Remove tensorflow_probability requirement

* setup.py - fix progress bars

* requirements.txt: Remove pre python 3.9 packages

* update apple requirements.txt

* update INSTALL.md

* Remove python<3.9 code

* setup.py - fix Windows Installer

* typing: python3.9 compliant

* Update pytest and readthedocs python versions

* typing fixes

* Python Version updates
  - Reduce max version to 3.10
  - Default to 3.10 in installers
  - Remove incompatible 3.11 tests

* Update dependencies

* Downgrade imageio dep for Windows

* typing: merge optional unions and fixes

* Updates
  - min python version 3.10
  - typing to python 3.10 spec
  - remove pre-tf2.10 code
  - Add conda tests

* train: re-enable optimizer saving

* Update dockerfiles

* Update setup.py
  - Apple Conda deps to setup.py
  - Better Cuda + dependency handling

* bugfix: Patch logging to prevent Autograph errors

* Update dockerfiles

* Setup.py - Setup.py - stdout to utf-8

* Add more OSes to github Actions

* suppress mac-os end to end test
2023-06-27 11:27:47 +01:00

419 lines
16 KiB
Python

#!/usr/bin/env python3
""" Custom :class:`argparse.Action` objects for Faceswap's Command Line Interface.
The custom actions within this module allow for custom manipulation of Command Line Arguments
as well as adding a mechanism for indicating to the GUI how specific options should be rendered.
"""
import argparse
import os
import typing as T
# << FILE HANDLING >>
class _FullPaths(argparse.Action): # pylint: disable=too-few-public-methods
""" Parent class for various file type and file path handling classes.
Expands out given paths to their full absolute paths. This class should not be
called directly. It is the base class for the various different file handling
methods.
"""
def __call__(self, parser, namespace, values, option_string=None) -> None:
if isinstance(values, (list, tuple)):
vals = [os.path.abspath(os.path.expanduser(val)) for val in values]
else:
vals = os.path.abspath(os.path.expanduser(values))
setattr(namespace, self.dest, vals)
class DirFullPaths(_FullPaths):
""" Adds support for a Directory browser in the GUI.
This is a standard :class:`argparse.Action` (with stock parameters) which indicates to the GUI
that a dialog box should be opened in order to browse for a folder.
No additional parameters are required.
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--folder_location"),
>>> action=DirFullPaths)),
"""
# pylint: disable=too-few-public-methods,unnecessary-pass
pass
class FileFullPaths(_FullPaths):
""" Adds support for a File browser to select a single file in the GUI.
This extends the standard :class:`argparse.Action` and adds an additional parameter
:attr:`filetypes`, indicating to the GUI that it should pop a file browser for opening a file
and limit the results to the file types listed. As well as the standard parameters, the
following parameter is required:
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--video_location"),
>>> action=FileFullPaths,
>>> filetypes="video))"
"""
# pylint: disable=too-few-public-methods
def __init__(self, *args, filetypes: str | None = None, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.filetypes = filetypes
def _get_kwargs(self):
names = ["option_strings",
"dest",
"nargs",
"const",
"default",
"type",
"choices",
"help",
"metavar",
"filetypes"]
return [(name, getattr(self, name)) for name in names]
class FilesFullPaths(FileFullPaths): # pylint: disable=too-few-public-methods
""" Adds support for a File browser to select multiple files in the GUI.
This extends the standard :class:`argparse.Action` and adds an additional parameter
:attr:`filetypes`, indicating to the GUI that it should pop a file browser, and limit
the results to the file types listed. Multiple files can be selected for opening, so the
:attr:`nargs` parameter must be set. As well as the standard parameters, the following
parameter is required:
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--images"),
>>> action=FilesFullPaths,
>>> filetypes="image",
>>> nargs="+"))
"""
def __init__(self, *args, filetypes: str | None = None, **kwargs) -> None:
if kwargs.get("nargs", None) is None:
opt = kwargs["option_strings"]
raise ValueError(f"nargs must be provided for FilesFullPaths: {opt}")
super().__init__(*args, **kwargs)
class DirOrFileFullPaths(FileFullPaths): # pylint: disable=too-few-public-methods
""" Adds support to the GUI to launch either a file browser or a folder browser.
Some inputs (for example source frames) can come from a folder of images or from a
video file. This indicates to the GUI that it should place 2 buttons (one for a folder
browser, one for a file browser) for file/folder browsing.
The standard :class:`argparse.Action` is extended with the additional parameter
:attr:`filetypes`, indicating to the GUI that it should pop a file browser, and limit
the results to the file types listed. As well as the standard parameters, the following
parameter is required:
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`. NB: This parameter is only used for
the file browser and not the folder browser
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--input_frames"),
>>> action=DirOrFileFullPaths,
>>> filetypes="video))"
"""
class DirOrFilesFullPaths(FileFullPaths): # pylint: disable=too-few-public-methods
""" Adds support to the GUI to launch either a file browser for selecting multiple files
or a folder browser.
Some inputs (for example face filter) can come from a folder of images or from multiple
image file. This indicates to the GUI that it should place 2 buttons (one for a folder
browser, one for a multi-file browser) for file/folder browsing.
The standard :class:`argparse.Action` is extended with the additional parameter
:attr:`filetypes`, indicating to the GUI that it should pop a file browser, and limit
the results to the file types listed. As well as the standard parameters, the following
parameter is required:
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`. NB: This parameter is only used for
the file browser and not the folder browser
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--input_frames"),
>>> action=DirOrFileFullPaths,
>>> filetypes="video))"
"""
def __call__(self, parser, namespace, values, option_string=None) -> None:
""" Override :class:`_FullPaths` __call__ function.
The input for this option can be a space separated list of files or a single folder.
Folders can have spaces in them, so we don't want to blindly expand the paths.
We check whether the input can be resolved to a folder first before expanding.
"""
assert isinstance(values, (list, tuple))
folder = os.path.abspath(os.path.expanduser(" ".join(values)))
if os.path.isdir(folder):
setattr(namespace, self.dest, [folder])
else: # file list so call parent method
super().__call__(parser, namespace, values, option_string)
class SaveFileFullPaths(FileFullPaths):
""" Adds support for a Save File dialog in the GUI.
This extends the standard :class:`argparse.Action` and adds an additional parameter
:attr:`filetypes`, indicating to the GUI that it should pop a save file browser, and limit
the results to the file types listed. As well as the standard parameters, the following
parameter is required:
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--video_out"),
>>> action=SaveFileFullPaths,
>>> filetypes="video"))
"""
# pylint: disable=too-few-public-methods,unnecessary-pass
pass
class ContextFullPaths(FileFullPaths):
""" Adds support for context sensitive browser dialog opening in the GUI.
For some tasks, the type of action (file load, folder open, file save etc.) can vary
depending on the task to be performed (a good example of this is the effmpeg tool).
Using this action indicates to the GUI that the type of dialog to be launched can change
depending on another option. As well as the standard parameters, the below parameters are
required. NB: :attr:`nargs` are explicitly disallowed.
Parameters
----------
filetypes: str
The accepted file types for this option. This is the key for the GUIs lookup table which
can be found in :class:`lib.gui.utils.FileHandler`
action_option: str
The command line option that dictates the context of the file dialog to be opened.
Bespoke actions are set in :class:`lib.gui.utils.FileHandler`
Example
-------
Assuming an argument has already been set with option string `-a` indicating the action to be
performed, the following will pop a different type of dialog depending on the action selected:
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--input_video"),
>>> action=ContextFullPaths,
>>> filetypes="video",
>>> action_option="-a"))
"""
# pylint: disable=too-few-public-methods, too-many-arguments
def __init__(self,
*args,
filetypes: str | None = None,
action_option: str | None = None,
**kwargs) -> None:
opt = kwargs["option_strings"]
if kwargs.get("nargs", None) is not None:
raise ValueError(f"nargs not allowed for ContextFullPaths: {opt}")
if filetypes is None:
raise ValueError(f"filetypes is required for ContextFullPaths: {opt}")
if action_option is None:
raise ValueError(f"action_option is required for ContextFullPaths: {opt}")
super().__init__(*args, filetypes=filetypes, **kwargs)
self.action_option = action_option
def _get_kwargs(self) -> list[tuple[str, T.Any]]:
names = ["option_strings",
"dest",
"nargs",
"const",
"default",
"type",
"choices",
"help",
"metavar",
"filetypes",
"action_option"]
return [(name, getattr(self, name)) for name in names]
# << GUI DISPLAY OBJECTS >>
class Radio(argparse.Action): # pylint: disable=too-few-public-methods
""" Adds support for a GUI Radio options box.
This is a standard :class:`argparse.Action` (with stock parameters) which indicates to the GUI
that the options passed should be rendered as a group of Radio Buttons rather than a combo box.
No additional parameters are required, but the :attr:`choices` parameter must be provided as
these will be the Radio Box options. :attr:`nargs` are explicitly disallowed.
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--foobar"),
>>> action=Radio,
>>> choices=["foo", "bar"))
"""
def __init__(self, *args, **kwargs) -> None:
opt = kwargs["option_strings"]
if kwargs.get("nargs", None) is not None:
raise ValueError(f"nargs not allowed for Radio buttons: {opt}")
if not kwargs.get("choices", []):
raise ValueError(f"Choices must be provided for Radio buttons: {opt}")
super().__init__(*args, **kwargs)
def __call__(self, parser, namespace, values, option_string=None) -> None:
setattr(namespace, self.dest, values)
class MultiOption(argparse.Action): # pylint: disable=too-few-public-methods
""" Adds support for multiple option checkboxes in the GUI.
This is a standard :class:`argparse.Action` (with stock parameters) which indicates to the GUI
that the options passed should be rendered as a group of Radio Buttons rather than a combo box.
The :attr:`choices` parameter must be provided as this provides the valid option choices.
Example
-------
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--foobar"),
>>> action=MultiOption,
>>> choices=["foo", "bar"))
"""
def __init__(self, *args, **kwargs) -> None:
opt = kwargs["option_strings"]
if not kwargs.get("nargs", []):
raise ValueError(f"nargs must be provided for MultiOption: {opt}")
if not kwargs.get("choices", []):
raise ValueError(f"Choices must be provided for MultiOption: {opt}")
super().__init__(*args, **kwargs)
def __call__(self, parser, namespace, values, option_string=None) -> None:
setattr(namespace, self.dest, values)
class Slider(argparse.Action): # pylint: disable=too-few-public-methods
""" Adds support for a slider in the GUI.
The standard :class:`argparse.Action` is extended with the additional parameters listed below.
The :attr:`default` value must be supplied and the :attr:`type` must be either :class:`int` or
:class:`float`. :attr:`nargs` are explicitly disallowed.
Parameters
----------
min_max: tuple
The (`min`, `max`) values that the slider's range should be set to. The values should be a
pair of `float` or `int` data types, depending on the data type of the slider. NB: These
min/max values are not enforced, they are purely for setting the slider range. Values
outside of this range can still be explicitly passed in from the cli.
rounding: int
If the underlying data type for the option is a `float` then this value is the number of
decimal places to round the slider values to. If the underlying data type for the option is
an `int` then this is the step interval between each value for the slider.
Examples
--------
For integer values:
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--foobar"),
>>> action=Slider,
>>> min_max=(0, 10)
>>> rounding=1
>>> type=int,
>>> default=5))
For floating point values:
>>> argument_list = []
>>> argument_list.append(dict(
>>> opts=("-f", "--foobar"),
>>> action=Slider,
>>> min_max=(0.00, 1.00)
>>> rounding=2
>>> type=float,
>>> default=5.00))
"""
def __init__(self,
*args,
min_max: tuple[int, int] | tuple[float, float] | None = None,
rounding: int | None = None,
**kwargs) -> None:
opt = kwargs["option_strings"]
if kwargs.get("nargs", None) is not None:
raise ValueError(f"nargs not allowed for Slider: {opt}")
if kwargs.get("default", None) is None:
raise ValueError(f"A default value must be supplied for Slider: {opt}")
if kwargs.get("type", None) not in (int, float):
raise ValueError(f"Sliders only accept int and float data types: {opt}")
if min_max is None:
raise ValueError(f"min_max must be provided for Sliders: {opt}")
if rounding is None:
raise ValueError(f"rounding must be provided for Sliders: {opt}")
super().__init__(*args, **kwargs)
self.min_max = min_max
self.rounding = rounding
def _get_kwargs(self) -> list[tuple[str, T.Any]]:
names = ["option_strings",
"dest",
"nargs",
"const",
"default",
"type",
"choices",
"help",
"metavar",
"min_max", # Tuple containing min and max values of scale
"rounding"] # Decimal places to round floats to or step interval for ints
return [(name, getattr(self, name)) for name in names]
def __call__(self, parser, namespace, values, option_string=None) -> None:
setattr(namespace, self.dest, values)