mirror of
https://github.com/deepfakes/faceswap.git
synced 2026-10-29 14:09:34 +08:00
* FaceSwap 3 (#1515) * Update extract pipeline * Update requirements + setup for nvidia * Remove allow-growth option * tf.keras to keras updates * lib.model.losses - Port + fix all loss functions for Keras3 * lib.model - port initializers, layers. normalization to Keras3 * lib.model.autoclip to Keras 3 * Update mixed precision layer storage * model file to .keras format * Restructure nn_blocks to initialize layers in __init__ * Tensorboard - Trainer: Add Torch compatible Tensorboard callbacks - GUI event reader remove TF dependency * Loss logging - Flush TB logs on save - Replace TB live iterator for GUI * Backup models on total loss drop rather than per side * Update all models to Keras3 Compat * Remove lib.model.session * Update clip ViT to Keras 3 * plugins.extract.mask.unet-dfl - Fix for Keras3/Torch backend * Port AdaBelief to Keras 3 * setup.py: - Add --dev flag for dev tool install * Fix Keras 3 syntax * Fix LR Finder for Keras 3 * Fix mixed precision switching for Keras 3 * Add more optimizers + open up config setting * train: Remove updating FS1 weights to FS2 models * Alignments: Remove support for legacy .json files * tools.model: - Remove TF Saved Format saving - Fix Backup/Restore + Nan-Scan * Fix inference model creation for Keras 3 * Preview tool: Fix for Keras3 * setup.py: Configure keras backend * train: Migration of FS2 models to FS3 * Training: Default coverage to 100% * Remove DirectML backend * Update setup for MacOS * GUI: Force line reading to UTF-8 * Remove redundant Tensorflow references * Remove redundant code * Legacy model loading: Fix TFLamdaOp scalar ops and DepthwiseConv2D * Add vertical offset option for training * Github actions: Add more python versions * Add python version to workflow names * Github workflow: Exclude Python 3.12 for macOS * Implement custom training loop * Fs3 - Add RTX5xxx and ROCm 6.1-6.4 support (#1511) * setup.py: Add Cuda/ROCm version select options * bump minimum python version to 3.11 * Switch from setup.cgf to pyproject.toml * Documentation: Update all docs to use automodapi * Allow sysinfo to run with missing packages + correctly install tk under Linux * Bugfix: dot naming convention in clip models * lib.config: Centralise globally rather than passing as object - Add torch DataParallel for multi-gpu training - GUI: Group switches together when generating cli args - CLI: Remove deprecated multi-character argparse args - Refactor: - Centralise tensorboard reading/writing + unit tests - Create trainer plugin interfaces + add original + distributed * Update installers
300 lines
11 KiB
Python
300 lines
11 KiB
Python
#! /usr/env/bin/python3
|
|
"""
|
|
Holds information about the running system. Used in setup.py and lib.sysinfo
|
|
NOTE: Only packages from Python's Standard Library should be imported in this module
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import locale
|
|
import logging
|
|
import os
|
|
import platform
|
|
import re
|
|
import sys
|
|
import typing as T
|
|
|
|
from shutil import which
|
|
from subprocess import CalledProcessError, run
|
|
|
|
from lib.utils import get_module_objects
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
VALID_PYTHON = ((3, 11), (3, 13))
|
|
""" tuple[tuple[int, int], tuple[int, int]] : The minimum and maximum versions of Python that can
|
|
run Faceswap """
|
|
VALID_TORCH = ((2, 3), (2, 9))
|
|
""" tuple[tuple[int, int], tuple[int, int]] : The minimum and maximum versions of Torch that can
|
|
run Faceswap """
|
|
VALID_KERAS = ((3, 12), (3, 12))
|
|
""" tuple[tuple[int, int], tuple[int, int]] : The minimum and maximum versions of Keras that can
|
|
run Faceswap """
|
|
|
|
|
|
def _lines_from_command(command: list[str]) -> list[str]:
|
|
""" Output stdout lines from an executed command.
|
|
|
|
Parameters
|
|
----------
|
|
command : list[str]
|
|
The command to run
|
|
|
|
Returns
|
|
-------
|
|
list[str]
|
|
The output lines from the given command
|
|
"""
|
|
logger.debug("Running command %s", command)
|
|
try:
|
|
proc = run(command,
|
|
capture_output=True,
|
|
check=True,
|
|
encoding=locale.getpreferredencoding(),
|
|
errors="replace")
|
|
except (FileNotFoundError, CalledProcessError) as err:
|
|
logger.debug("Error from command: %s", str(err))
|
|
return []
|
|
return proc.stdout.splitlines()
|
|
|
|
|
|
class System: # pylint:disable=too-many-instance-attributes
|
|
""" Holds information about the currently running system and environment """
|
|
def __init__(self) -> None:
|
|
self.platform = platform.platform()
|
|
""" str : Human readable platform identifier """
|
|
self.system: T.Literal["darwin", "linux", "windows"] = T.cast(
|
|
T.Literal["darwin", "linux", "windows"], platform.system().lower())
|
|
""" str : The system (OS type) that this code is running on. Always lowercase """
|
|
self.machine = platform.machine()
|
|
""" str : The machine type (eg: "x86_64") """
|
|
self.release = platform.release()
|
|
""" str : The OS Release that this code is running on """
|
|
self.processor = platform.processor()
|
|
""" str : The processor in use, if detected """
|
|
self.cpu_count = os.cpu_count()
|
|
""" int : The number of CPU cores on the system """
|
|
self.python_implementation = platform.python_implementation()
|
|
""" str : The python implementation in use"""
|
|
self.python_version = platform.python_version()
|
|
""" str : The <major>.<minor>.<release> version of Python that is running """
|
|
self.python_architecture = platform.architecture()[0]
|
|
""" str : The Python architecture that is running (eg: 64bit/32bit)"""
|
|
self.encoding = locale.getpreferredencoding()
|
|
""" str : The system encoding """
|
|
self.is_conda = ("conda" in sys.version.lower() or
|
|
os.path.exists(os.path.join(sys.prefix, 'conda-meta')))
|
|
""" bool : ``True`` if running under Conda otherwise ``False`` """
|
|
self.is_admin = self._get_permissions()
|
|
""" bool : ``True`` if we are running with Admin privileges """
|
|
self.is_virtual_env = self._check_virtual_env()
|
|
""" bool : ``True`` if Python is being run inside a virtual environment """
|
|
|
|
@property
|
|
def is_linux(self) -> bool:
|
|
""" bool : `True` if running on a Linux system otherwise ``False``. """
|
|
return self.system == "linux"
|
|
|
|
@property
|
|
def is_macos(self) -> bool:
|
|
""" bool : `True` if running on a macOS system otherwise ``False``. """
|
|
return self.system == "darwin"
|
|
|
|
@property
|
|
def is_windows(self) -> bool:
|
|
""" bool : `True` if running on a Windows system otherwise ``False``. """
|
|
return self.system == "windows"
|
|
|
|
def __repr__(self) -> str:
|
|
""" Pretty print the system information for logging """
|
|
attrs = ", ".join(f"{k}={repr(v)}" for k, v in self.__dict__.items()
|
|
if not k.startswith("_"))
|
|
return f"{self.__class__.__name__}({attrs})"
|
|
|
|
def _get_permissions(self) -> bool:
|
|
""" Check whether user is admin
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
``True`` if we are running with Admin privileges
|
|
"""
|
|
if self.is_windows:
|
|
retval = ctypes.windll.shell32.IsUserAnAdmin() != 0 # type:ignore[attr-defined]
|
|
else:
|
|
retval = os.getuid() == 0 # type:ignore[attr-defined] # pylint:disable=no-member
|
|
return retval
|
|
|
|
def _check_virtual_env(self) -> bool:
|
|
""" Check whether we are in a virtual environment
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
``True`` if Python is being run inside a virtual environment
|
|
"""
|
|
if not self.is_conda:
|
|
retval = (hasattr(sys, "real_prefix") or
|
|
(hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix))
|
|
else:
|
|
prefix = os.path.dirname(sys.prefix)
|
|
retval = os.path.basename(prefix) == "envs"
|
|
return retval
|
|
|
|
def validate_python(self, max_version: tuple[int, int] | None = None) -> bool:
|
|
""" Check that the running Python version is valid
|
|
|
|
Parameters
|
|
----------
|
|
max_version: tuple[int, int] | None, Optional
|
|
The max version to validate Python against. ``None`` for the project Maximum.
|
|
Default: ``None`` (project maximum)
|
|
|
|
Returns
|
|
-------
|
|
bool
|
|
``True`` if the running Python version is valid, otherwise logs an error and exits
|
|
"""
|
|
max_python = VALID_PYTHON[1] if max_version is None else max_version
|
|
retval = (VALID_PYTHON[0] <= sys.version_info[:2] <= max_python
|
|
and self.python_architecture == "64bit")
|
|
logger.debug("Python version %s(%s) within %s - %s(64bit): %s",
|
|
self.python_version,
|
|
self.python_architecture,
|
|
VALID_PYTHON[0],
|
|
max_python,
|
|
retval)
|
|
if not retval:
|
|
print()
|
|
logger.error("Your Python version %s(%s) is unsupported. Please run with Python "
|
|
"version %s to %s 64bit.",
|
|
self.python_version,
|
|
self.python_architecture,
|
|
".".join(str(x) for x in VALID_PYTHON[0]),
|
|
".".join(str(x) for x in max_python))
|
|
print()
|
|
logger.error("If you have recently upgraded faceswap, then you will need to create a "
|
|
"new virtual environment.")
|
|
logger.error("The easiest way to do this is to run the latest version of the Faceswap "
|
|
"installer from:")
|
|
logger.error("https://github.com/deepfakes/faceswap/releases")
|
|
print()
|
|
input("Press <Enter> to close")
|
|
sys.exit(1)
|
|
|
|
return retval
|
|
|
|
def validate(self) -> None:
|
|
""" Perform validation that the running system can be used for faceswap. Log an error and
|
|
exit if it cannot """
|
|
if not any((self.is_linux, self.is_macos, self.is_windows)):
|
|
logger.error("Your system %s is not supported!", self.system.title())
|
|
sys.exit(1)
|
|
if self.is_macos and self.machine == "arm64" and not self.is_conda:
|
|
logger.error("Setting up Faceswap for Apple Silicon outside of a Conda "
|
|
"environment is unsupported")
|
|
sys.exit(1)
|
|
self.validate_python()
|
|
|
|
|
|
class Packages():
|
|
""" Holds information about installed python and conda packages.
|
|
|
|
Note: Packaging library is lazy loaded as it may not be available during setup.py
|
|
"""
|
|
def __init__(self) -> None:
|
|
self._conda_exe = which("conda")
|
|
self._installed_python = self._get_installed_python()
|
|
self._installed_conda: list[str] | None = None
|
|
self._get_installed_conda()
|
|
|
|
@property
|
|
def installed_python(self) -> dict[str, str]:
|
|
""" dict[str, str] : Installed Python package names to Python package versions """
|
|
return self._installed_python
|
|
|
|
@property
|
|
def installed_python_pretty(self) -> str:
|
|
""" str: A pretty printed representation of installed Python packages """
|
|
pkgs = self._installed_python
|
|
align = max(len(x) for x in pkgs) + 1
|
|
return "\n".join(f"{k.ljust(align)} {v}" for k, v in pkgs.items())
|
|
|
|
@property
|
|
def installed_conda(self) -> dict[str, tuple[str, str, str]]:
|
|
""" dict[str, tuple[str, str]] : Installed Conda package names to the version and
|
|
channel """
|
|
if not self._installed_conda:
|
|
return {}
|
|
|
|
installed = [re.sub(" +", " ", line.strip())
|
|
for line in self._installed_conda if not line.startswith("#")]
|
|
retval = {}
|
|
for pkg in installed:
|
|
item = pkg.split(" ")
|
|
assert len(item) == 4
|
|
retval[item[0]] = T.cast(tuple[str, str, str], tuple(item[1:]))
|
|
return retval
|
|
|
|
@property
|
|
def installed_conda_pretty(self) -> str:
|
|
""" str: A pretty printed representation of installed conda packages """
|
|
if not self._installed_conda:
|
|
return "Could not get Conda package list"
|
|
return "\n".join(self._installed_conda)
|
|
|
|
def __repr__(self) -> str:
|
|
""" Pretty print the installed packages for logging """
|
|
props = ", ".join(
|
|
f"{k}={repr(getattr(self, k))}"
|
|
for k, v in self.__class__.__dict__.items()
|
|
if isinstance(v, property) and not k.startswith("_") and "pretty" not in k)
|
|
return f"{self.__class__.__name__}({props})"
|
|
|
|
def _get_installed_python(self) -> dict[str, str]:
|
|
""" Parse the installed python modules
|
|
|
|
Returns
|
|
-------
|
|
dict[str, str]
|
|
Installed Python package names to Python package versions
|
|
"""
|
|
installed = _lines_from_command([sys.executable, "-m", "pip", "freeze", "--local"])
|
|
retval = {}
|
|
for pkg in installed:
|
|
if "==" not in pkg:
|
|
continue
|
|
item = pkg.split("==")
|
|
retval[item[0].lower()] = item[1]
|
|
logger.debug("Installed Python packages: %s", retval)
|
|
return retval
|
|
|
|
def _get_installed_conda(self) -> None:
|
|
""" Collect the output from 'conda list' for the installed Conda packages and
|
|
populate :attr:`_installed_conda`
|
|
|
|
Returns
|
|
-------
|
|
list[str]
|
|
Each line of output from the 'conda list' command
|
|
"""
|
|
if not self._conda_exe:
|
|
logger.debug("Conda not found. Not collecting packages")
|
|
return
|
|
|
|
lines = _lines_from_command([self._conda_exe, "list", "--show-channel-urls"])
|
|
if not lines:
|
|
self._installed_conda = ["Could not get Conda package list"]
|
|
return
|
|
self._installed_conda = lines
|
|
logger.debug("Installed Conda packages: %s", self.installed_conda)
|
|
|
|
|
|
__all__ = get_module_objects(__name__)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print(System())
|
|
print(Packages())
|