* Improve typing for our callbacks

* Return 0 for get_download_size

* Introduce ONNX powered face enhancer

* Introduce ONNX powered face enhancer

* Introduce ONNX powered face enhancer

* Remove tile processing from frame enhancer

* Fix video compress translation for libvpx-vp9

* Allow zero values for video compression

* Develop (#134)

* Introduce model options to the frame processors

* Finish UI to select frame processors models

* Simplify frame processors options

* Fix lint in CI

* Rename all kind of settings to options

* Add blend to enhancers

* Simplify webcam mode naming

* Bypass SSL issues under Windows

* Fix blend of frame enhancer

* Massive CLI refactoring, Register and apply ARGS via the frame processors

* Refine UI theme and introduce donate button

* Update dependencies and fix cpu only torch

* Update dependencies and fix cpu only torch

* Fix theme, Fix frame_processors in headless mode

* Remove useless astype

* Disable CoreML for the ONNX face enhancer

* Disable CoreML for the ONNX face enhancer

* Predict webcam too

* Improve resize of preview

* Change output quality defaults, Move options to the right

* Support for codeformer model

* Update the typo

* Add GPEN and GFPGAN 1.2

* Extract blend_frame methods

* Extend the installer

* Revert broken Gradio

* Rework on ui components

* Move output path selector to the output options

* Remove tons of pointless component updates

* Reset more base theme styling

* Use latest Gradio

* Fix the sliders

* More styles

* Update torch to 2.1.0

* Add RealESRNet_x4plus

* Fix that button

* Use latest onnxruntime-silicon

* Looks stable to me

* Lowercase model keys, Update preview and readme
This commit is contained in:
Henry Ruhs 2023-10-09 10:16:13 +02:00 committed by GitHub
parent 3e361e7701
commit a6809c3ccb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1105 additions and 563 deletions

BIN
.github/preview.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -30,6 +30,6 @@ jobs:
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: '3.10' python-version: '3.10'
- run: pip install -r requirements.txt - run: python install.py --torch cpu --onnxruntime cpu
- run: pip install pytest - run: pip install pytest
- run: pytest - run: pytest

View File

@ -29,36 +29,56 @@ Run the command:
``` ```
python run.py [options] python run.py [options]
-h, --help show this help message and exit options:
-s SOURCE_PATH, --source SOURCE_PATH select a source image -h, --help show this help message and exit
-t TARGET_PATH, --target TARGET_PATH select a target image or video -s SOURCE_PATH, --source SOURCE_PATH select a source image
-o OUTPUT_PATH, --output OUTPUT_PATH specify the output file or directory -t TARGET_PATH, --target TARGET_PATH select a target image or video
--frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] choose from the available frame processors (choices: face_enhancer, face_swapper, frame_enhancer, ...) -o OUTPUT_PATH, --output OUTPUT_PATH specify the output file or directory
--ui-layouts UI_LAYOUTS [UI_LAYOUTS ...] choose from the available ui layouts (choices: benchmark, webcam, default, ...) -v, --version show program's version number and exit
--keep-fps preserve the frames per second (fps) of the target
--keep-temp retain temporary frames after processing misc:
--skip-audio omit audio from the target --skip-download omit automate downloads and lookups
--face-recognition {reference,many} specify the method for face recognition --headless run the program in headless mode
--face-analyser-direction {left-right,right-left,top-bottom,bottom-top,small-large,large-small} specify the direction used for face analysis
--face-analyser-age {child,teen,adult,senior} specify the age used for face analysis execution:
--face-analyser-gender {male,female} specify the gender used for face analysis --execution-providers {cpu} [{cpu} ...] choose from the available execution providers (choices: cpu, ...)
--reference-face-position REFERENCE_FACE_POSITION specify the position of the reference face --execution-thread-count EXECUTION_THREAD_COUNT specify the number of execution threads
--reference-face-distance REFERENCE_FACE_DISTANCE specify the distance between the reference face and the target face --execution-queue-count EXECUTION_QUEUE_COUNT specify the number of execution queries
--reference-frame-number REFERENCE_FRAME_NUMBER specify the number of the reference frame --max-memory MAX_MEMORY specify the maximum amount of ram to be used (in gb)
--trim-frame-start TRIM_FRAME_START specify the start frame for extraction
--trim-frame-end TRIM_FRAME_END specify the end frame for extraction face recognition:
--temp-frame-format {jpg,png} specify the image format used for frame extraction --face-recognition {reference,many} specify the method for face recognition
--temp-frame-quality [0-100] specify the image quality used for frame extraction --face-analyser-direction {left-right,right-left,top-bottom,bottom-top,small-large,large-small} specify the direction used for face analysis
--output-image-quality [0-100] specify the quality used for the output image --face-analyser-age {child,teen,adult,senior} specify the age used for face analysis
--output-video-encoder {libx264,libx265,libvpx-vp9,h264_nvenc,hevc_nvenc} specify the encoder used for the output video --face-analyser-gender {male,female} specify the gender used for face analysis
--output-video-quality [0-100] specify the quality used for the output video --reference-face-position REFERENCE_FACE_POSITION specify the position of the reference face
--max-memory MAX_MEMORY specify the maximum amount of ram to be used (in gb) --reference-face-distance REFERENCE_FACE_DISTANCE specify the distance between the reference face and the target face
--execution-providers {cpu} [{cpu} ...] choose from the available execution providers (choices: cpu, ...) --reference-frame-number REFERENCE_FRAME_NUMBER specify the number of the reference frame
--execution-thread-count EXECUTION_THREAD_COUNT specify the number of execution threads
--execution-queue-count EXECUTION_QUEUE_COUNT specify the number of execution queries frame extraction:
--skip-download omit automate downloads and lookups --trim-frame-start TRIM_FRAME_START specify the start frame for extraction
--headless run the program in headless mode --trim-frame-end TRIM_FRAME_END specify the end frame for extraction
-v, --version show program's version number and exit --temp-frame-format {jpg,png} specify the image format used for frame extraction
--temp-frame-quality [0-100] specify the image quality used for frame extraction
--keep-temp retain temporary frames after processing
output creation:
--output-image-quality [0-100] specify the quality used for the output image
--output-video-encoder {libx264,libx265,libvpx-vp9,h264_nvenc,hevc_nvenc} specify the encoder used for the output video
--output-video-quality [0-100] specify the quality used for the output video
--keep-fps preserve the frames per second (fps) of the target
--skip-audio omit audio from the target
frame processors:
--frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] choose from the available frame processors (choices: face_enhancer, face_swapper, frame_enhancer, ...)
--face-enhancer-model {codeformer,gfpgan_1.2,gfpgan_1.3,gfpgan_1.4,gpen_bfr_512} choose from the mode for the frame processor
--face-enhancer-blend [0-100] specify the blend factor for the frame processor
--face-swapper-model {inswapper_128,inswapper_128_fp16} choose from the mode for the frame processor
--frame-enhancer-model {realesrgan_x2plus,realesrgan_x4plus,realesrnet_x4plus} choose from the mode for the frame processor
--frame-enhancer-blend [0-100] specify the blend factor for the frame processor
uis:
--ui-layouts UI_LAYOUTS [UI_LAYOUTS ...] choose from the available ui layouts (choices: benchmark, webcam, default, ...)
``` ```

View File

@ -2,10 +2,9 @@ from typing import List
from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder
face_recognition : List[FaceRecognition] = [ 'reference', 'many' ] face_recognitions : List[FaceRecognition] = [ 'reference', 'many' ]
face_analyser_direction : List[FaceAnalyserDirection] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small' ] face_analyser_directions : List[FaceAnalyserDirection] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small' ]
face_analyser_age : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ] face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ]
face_analyser_gender : List[FaceAnalyserGender] = [ 'male', 'female' ] face_analyser_genders : List[FaceAnalyserGender] = [ 'male', 'female' ]
temp_frame_format : List[TempFrameFormat] = [ 'jpg', 'png' ] temp_frame_formats : List[TempFrameFormat] = [ 'jpg', 'png' ]
output_video_encoder : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ] output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ]

View File

@ -1,111 +1,158 @@
#!/usr/bin/env python3
import os import os
# single thread doubles cuda performance
os.environ['OMP_NUM_THREADS'] = '1' os.environ['OMP_NUM_THREADS'] = '1'
# reduce tensorflow log level
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import signal
import sys import sys
import warnings import warnings
from typing import List
import platform import platform
import signal
import shutil import shutil
import argparse
import onnxruntime import onnxruntime
import tensorflow import tensorflow
from argparse import ArgumentParser, HelpFormatter
import facefusion.choices import facefusion.choices
import facefusion.globals import facefusion.globals
from facefusion import wording, metadata from facefusion import metadata, wording
from facefusion.predictor import predict_image, predict_video from facefusion.predictor import predict_image, predict_video
from facefusion.processors.frame.core import get_frame_processors_modules from facefusion.processors.frame.core import get_frame_processors_modules, load_frame_processor_module
from facefusion.utilities import is_image, is_video, detect_fps, compress_image, merge_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clear_temp, normalize_output_path, list_module_names, decode_execution_providers, encode_execution_providers from facefusion.utilities import is_image, is_video, detect_fps, compress_image, merge_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clear_temp, list_module_names, encode_execution_providers, decode_execution_providers, normalize_output_path
warnings.filterwarnings('ignore', category = FutureWarning, module = 'insightface') warnings.filterwarnings('ignore', category = FutureWarning, module = 'insightface')
warnings.filterwarnings('ignore', category = UserWarning, module = 'torchvision') warnings.filterwarnings('ignore', category = UserWarning, module = 'torchvision')
def parse_args() -> None: def cli() -> None:
signal.signal(signal.SIGINT, lambda signal_number, frame: destroy()) signal.signal(signal.SIGINT, lambda signal_number, frame: destroy())
program = argparse.ArgumentParser(formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position = 120)) program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 120), add_help = False)
# general
program.add_argument('-s', '--source', help = wording.get('source_help'), dest = 'source_path') program.add_argument('-s', '--source', help = wording.get('source_help'), dest = 'source_path')
program.add_argument('-t', '--target', help = wording.get('target_help'), dest = 'target_path') program.add_argument('-t', '--target', help = wording.get('target_help'), dest = 'target_path')
program.add_argument('-o', '--output', help = wording.get('output_help'), dest = 'output_path') program.add_argument('-o', '--output', help = wording.get('output_help'), dest = 'output_path')
program.add_argument('--frame-processors', help = wording.get('frame_processors_help').format(choices = ', '.join(list_module_names('facefusion/processors/frame/modules'))), dest = 'frame_processors', default = ['face_swapper'], nargs = '+')
program.add_argument('--ui-layouts', help = wording.get('ui_layouts_help').format(choices = ', '.join(list_module_names('facefusion/uis/layouts'))), dest = 'ui_layouts', default = ['default'], nargs = '+')
program.add_argument('--keep-fps', help = wording.get('keep_fps_help'), dest = 'keep_fps', action = 'store_true')
program.add_argument('--keep-temp', help = wording.get('keep_temp_help'), dest = 'keep_temp', action = 'store_true')
program.add_argument('--skip-audio', help = wording.get('skip_audio_help'), dest = 'skip_audio', action = 'store_true')
program.add_argument('--face-recognition', help = wording.get('face_recognition_help'), dest = 'face_recognition', default = 'reference', choices = facefusion.choices.face_recognition)
program.add_argument('--face-analyser-direction', help = wording.get('face_analyser_direction_help'), dest = 'face_analyser_direction', default = 'left-right', choices = facefusion.choices.face_analyser_direction)
program.add_argument('--face-analyser-age', help = wording.get('face_analyser_age_help'), dest = 'face_analyser_age', choices = facefusion.choices.face_analyser_age)
program.add_argument('--face-analyser-gender', help = wording.get('face_analyser_gender_help'), dest = 'face_analyser_gender', choices = facefusion.choices.face_analyser_gender)
program.add_argument('--reference-face-position', help = wording.get('reference_face_position_help'), dest = 'reference_face_position', type = int, default = 0)
program.add_argument('--reference-face-distance', help = wording.get('reference_face_distance_help'), dest = 'reference_face_distance', type = float, default = 1.5)
program.add_argument('--reference-frame-number', help = wording.get('reference_frame_number_help'), dest = 'reference_frame_number', type = int, default = 0)
program.add_argument('--trim-frame-start', help = wording.get('trim_frame_start_help'), dest = 'trim_frame_start', type = int)
program.add_argument('--trim-frame-end', help = wording.get('trim_frame_end_help'), dest = 'trim_frame_end', type = int)
program.add_argument('--temp-frame-format', help = wording.get('temp_frame_format_help'), dest = 'temp_frame_format', default = 'jpg', choices = facefusion.choices.temp_frame_format)
program.add_argument('--temp-frame-quality', help = wording.get('temp_frame_quality_help'), dest = 'temp_frame_quality', type = int, default = 100, choices = range(101), metavar = '[0-100]')
program.add_argument('--output-image-quality', help=wording.get('output_image_quality_help'), dest = 'output_image_quality', type = int, default = 90, choices = range(101), metavar = '[0-100]')
program.add_argument('--output-video-encoder', help = wording.get('output_video_encoder_help'), dest = 'output_video_encoder', default = 'libx264', choices = facefusion.choices.output_video_encoder)
program.add_argument('--output-video-quality', help = wording.get('output_video_quality_help'), dest = 'output_video_quality', type = int, default = 90, choices = range(101), metavar = '[0-100]')
program.add_argument('--max-memory', help = wording.get('max_memory_help'), dest = 'max_memory', type = int)
program.add_argument('--execution-providers', help = wording.get('execution_providers_help').format(choices = 'cpu'), dest = 'execution_providers', default = ['cpu'], choices = suggest_execution_providers_choices(), nargs = '+')
program.add_argument('--execution-thread-count', help = wording.get('execution_thread_count_help'), dest = 'execution_thread_count', type = int, default = suggest_execution_thread_count_default())
program.add_argument('--execution-queue-count', help = wording.get('execution_queue_count_help'), dest = 'execution_queue_count', type = int, default = 1)
program.add_argument('--skip-download', help = wording.get('skip_download_help'), dest = 'skip_download', action = 'store_true')
program.add_argument('--headless', help = wording.get('headless_help'), dest = 'headless', action = 'store_true')
program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version') program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
# misc
group_misc = program.add_argument_group('misc')
group_misc.add_argument('--skip-download', help = wording.get('skip_download_help'), dest = 'skip_download', action = 'store_true')
group_misc.add_argument('--headless', help = wording.get('headless_help'), dest = 'headless', action = 'store_true')
# execution
group_execution = program.add_argument_group('execution')
group_execution.add_argument('--execution-providers', help = wording.get('execution_providers_help').format(choices = 'cpu'), dest = 'execution_providers', default = [ 'cpu' ], choices = encode_execution_providers(onnxruntime.get_available_providers()), nargs = '+')
group_execution.add_argument('--execution-thread-count', help = wording.get('execution_thread_count_help'), dest = 'execution_thread_count', type = int, default = 1)
group_execution.add_argument('--execution-queue-count', help = wording.get('execution_queue_count_help'), dest = 'execution_queue_count', type = int, default = 1)
group_execution.add_argument('--max-memory', help=wording.get('max_memory_help'), dest='max_memory', type = int)
# face recognition
group_face_recognition = program.add_argument_group('face recognition')
group_face_recognition.add_argument('--face-recognition', help = wording.get('face_recognition_help'), dest = 'face_recognition', default = 'reference', choices = facefusion.choices.face_recognitions)
group_face_recognition.add_argument('--face-analyser-direction', help = wording.get('face_analyser_direction_help'), dest = 'face_analyser_direction', default = 'left-right', choices = facefusion.choices.face_analyser_directions)
group_face_recognition.add_argument('--face-analyser-age', help = wording.get('face_analyser_age_help'), dest = 'face_analyser_age', choices = facefusion.choices.face_analyser_ages)
group_face_recognition.add_argument('--face-analyser-gender', help = wording.get('face_analyser_gender_help'), dest = 'face_analyser_gender', choices = facefusion.choices.face_analyser_genders)
group_face_recognition.add_argument('--reference-face-position', help = wording.get('reference_face_position_help'), dest = 'reference_face_position', type = int, default = 0)
group_face_recognition.add_argument('--reference-face-distance', help = wording.get('reference_face_distance_help'), dest = 'reference_face_distance', type = float, default = 1.5)
group_face_recognition.add_argument('--reference-frame-number', help = wording.get('reference_frame_number_help'), dest = 'reference_frame_number', type = int, default = 0)
# frame extraction
group_processing = program.add_argument_group('frame extraction')
group_processing.add_argument('--trim-frame-start', help = wording.get('trim_frame_start_help'), dest = 'trim_frame_start', type = int)
group_processing.add_argument('--trim-frame-end', help = wording.get('trim_frame_end_help'), dest = 'trim_frame_end', type = int)
group_processing.add_argument('--temp-frame-format', help = wording.get('temp_frame_format_help'), dest = 'temp_frame_format', default = 'jpg', choices = facefusion.choices.temp_frame_formats)
group_processing.add_argument('--temp-frame-quality', help = wording.get('temp_frame_quality_help'), dest = 'temp_frame_quality', type = int, default = 100, choices = range(101), metavar = '[0-100]')
group_processing.add_argument('--keep-temp', help = wording.get('keep_temp_help'), dest = 'keep_temp', action = 'store_true')
# output creation
group_output = program.add_argument_group('output creation')
group_output.add_argument('--output-image-quality', help=wording.get('output_image_quality_help'), dest = 'output_image_quality', type = int, default = 80, choices = range(101), metavar = '[0-100]')
group_output.add_argument('--output-video-encoder', help = wording.get('output_video_encoder_help'), dest = 'output_video_encoder', default = 'libx264', choices = facefusion.choices.output_video_encoders)
group_output.add_argument('--output-video-quality', help = wording.get('output_video_quality_help'), dest = 'output_video_quality', type = int, default = 80, choices = range(101), metavar = '[0-100]')
group_output.add_argument('--keep-fps', help = wording.get('keep_fps_help'), dest = 'keep_fps', action = 'store_true')
group_output.add_argument('--skip-audio', help = wording.get('skip_audio_help'), dest = 'skip_audio', action = 'store_true')
# frame processors
available_frame_processors = list_module_names('facefusion/processors/frame/modules')
program = ArgumentParser(parents = [ program ], formatter_class = program.formatter_class, add_help = True)
group_frame_processors = program.add_argument_group('frame processors')
group_frame_processors.add_argument('--frame-processors', help = wording.get('frame_processors_help').format(choices = ', '.join(available_frame_processors)), dest = 'frame_processors', default = [ 'face_swapper' ], nargs = '+')
for frame_processor in available_frame_processors:
frame_processor_module = load_frame_processor_module(frame_processor)
frame_processor_module.register_args(group_frame_processors)
# uis
group_uis = program.add_argument_group('uis')
group_uis.add_argument('--ui-layouts', help = wording.get('ui_layouts_help').format(choices = ', '.join(list_module_names('facefusion/uis/layouts'))), dest = 'ui_layouts', default = [ 'default' ], nargs = '+')
run(program)
def apply_args(program : ArgumentParser) -> None:
args = program.parse_args() args = program.parse_args()
# general
facefusion.globals.source_path = args.source_path facefusion.globals.source_path = args.source_path
facefusion.globals.target_path = args.target_path facefusion.globals.target_path = args.target_path
facefusion.globals.output_path = normalize_output_path(facefusion.globals.source_path, facefusion.globals.target_path, args.output_path) facefusion.globals.output_path = normalize_output_path(facefusion.globals.source_path, facefusion.globals.target_path, args.output_path)
facefusion.globals.frame_processors = args.frame_processors # misc
facefusion.globals.ui_layouts = args.ui_layouts facefusion.globals.skip_download = args.skip_download
facefusion.globals.keep_fps = args.keep_fps facefusion.globals.headless = args.headless
facefusion.globals.keep_temp = args.keep_temp # execution
facefusion.globals.skip_audio = args.skip_audio facefusion.globals.execution_providers = decode_execution_providers(args.execution_providers)
facefusion.globals.execution_thread_count = args.execution_thread_count
facefusion.globals.execution_queue_count = args.execution_queue_count
facefusion.globals.max_memory = args.max_memory
# face recognition
facefusion.globals.face_recognition = args.face_recognition facefusion.globals.face_recognition = args.face_recognition
facefusion.globals.face_analyser_direction = args.face_analyser_direction facefusion.globals.face_analyser_direction = args.face_analyser_direction
facefusion.globals.face_analyser_age = args.face_analyser_age facefusion.globals.face_analyser_age = args.face_analyser_age
facefusion.globals.face_analyser_gender = args.face_analyser_gender facefusion.globals.face_analyser_gender = args.face_analyser_gender
facefusion.globals.reference_face_position = args.reference_face_position facefusion.globals.reference_face_position = args.reference_face_position
facefusion.globals.reference_frame_number = args.reference_frame_number
facefusion.globals.reference_face_distance = args.reference_face_distance facefusion.globals.reference_face_distance = args.reference_face_distance
facefusion.globals.reference_frame_number = args.reference_frame_number
# frame extraction
facefusion.globals.trim_frame_start = args.trim_frame_start facefusion.globals.trim_frame_start = args.trim_frame_start
facefusion.globals.trim_frame_end = args.trim_frame_end facefusion.globals.trim_frame_end = args.trim_frame_end
facefusion.globals.temp_frame_format = args.temp_frame_format facefusion.globals.temp_frame_format = args.temp_frame_format
facefusion.globals.temp_frame_quality = args.temp_frame_quality facefusion.globals.temp_frame_quality = args.temp_frame_quality
facefusion.globals.keep_temp = args.keep_temp
# output creation
facefusion.globals.output_image_quality = args.output_image_quality facefusion.globals.output_image_quality = args.output_image_quality
facefusion.globals.output_video_encoder = args.output_video_encoder facefusion.globals.output_video_encoder = args.output_video_encoder
facefusion.globals.output_video_quality = args.output_video_quality facefusion.globals.output_video_quality = args.output_video_quality
facefusion.globals.max_memory = args.max_memory facefusion.globals.keep_fps = args.keep_fps
facefusion.globals.execution_providers = decode_execution_providers(args.execution_providers) facefusion.globals.skip_audio = args.skip_audio
facefusion.globals.execution_thread_count = args.execution_thread_count # frame processors
facefusion.globals.execution_queue_count = args.execution_queue_count available_frame_processors = list_module_names('facefusion/processors/frame/modules')
facefusion.globals.skip_download = args.skip_download facefusion.globals.frame_processors = args.frame_processors
facefusion.globals.headless = args.headless for frame_processor in available_frame_processors:
frame_processor_module = load_frame_processor_module(frame_processor)
frame_processor_module.apply_args(program)
# uis
facefusion.globals.ui_layouts = args.ui_layouts
def suggest_execution_providers_choices() -> List[str]: def run(program : ArgumentParser) -> None:
return encode_execution_providers(onnxruntime.get_available_providers()) apply_args(program)
limit_resources()
if not pre_check():
return
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
if not frame_processor_module.pre_check():
return
if facefusion.globals.headless:
conditional_process()
else:
import facefusion.uis.core as ui
for ui_layout in ui.get_ui_layouts_modules(facefusion.globals.ui_layouts):
if not ui_layout.pre_check():
return
ui.launch()
def suggest_execution_thread_count_default() -> int: def destroy() -> None:
if 'CUDAExecutionProvider' in onnxruntime.get_available_providers(): if facefusion.globals.target_path:
return 8 clear_temp(facefusion.globals.target_path)
return 1 sys.exit()
def limit_resources() -> None: def limit_resources() -> None:
# prevent tensorflow memory leak # prevent tensorflow memory leak
gpus = tensorflow.config.experimental.list_physical_devices('GPU') gpus = tensorflow.config.experimental.list_physical_devices('GPU')
for gpu in gpus: for gpu in gpus:
tensorflow.config.experimental.set_virtual_device_configuration(gpu, [ tensorflow.config.experimental.set_virtual_device_configuration(gpu,
[
tensorflow.config.experimental.VirtualDeviceConfiguration(memory_limit = 512) tensorflow.config.experimental.VirtualDeviceConfiguration(memory_limit = 512)
]) ])
# limit memory usage # limit memory usage
@ -122,10 +169,6 @@ def limit_resources() -> None:
resource.setrlimit(resource.RLIMIT_DATA, (memory, memory)) resource.setrlimit(resource.RLIMIT_DATA, (memory, memory))
def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None:
print('[' + scope + '] ' + message)
def pre_check() -> bool: def pre_check() -> bool:
if sys.version_info < (3, 9): if sys.version_info < (3, 9):
update_status(wording.get('python_not_supported').format(version = '3.9')) update_status(wording.get('python_not_supported').format(version = '3.9'))
@ -136,6 +179,16 @@ def pre_check() -> bool:
return True return True
def conditional_process() -> None:
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
if not frame_processor_module.pre_process('output'):
return
if is_image(facefusion.globals.target_path):
process_image()
if is_video(facefusion.globals.target_path):
process_video()
def process_image() -> None: def process_image() -> None:
if predict_image(facefusion.globals.target_path): if predict_image(facefusion.globals.target_path):
return return
@ -160,6 +213,7 @@ def process_video() -> None:
if predict_video(facefusion.globals.target_path): if predict_video(facefusion.globals.target_path):
return return
fps = detect_fps(facefusion.globals.target_path) if facefusion.globals.keep_fps else 25.0 fps = detect_fps(facefusion.globals.target_path) if facefusion.globals.keep_fps else 25.0
# create temp
update_status(wording.get('creating_temp')) update_status(wording.get('creating_temp'))
create_temp(facefusion.globals.target_path) create_temp(facefusion.globals.target_path)
# extract frames # extract frames
@ -199,39 +253,5 @@ def process_video() -> None:
update_status(wording.get('processing_video_failed')) update_status(wording.get('processing_video_failed'))
def conditional_process() -> None: def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None:
for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors): print('[' + scope + '] ' + message)
if not frame_processor_module.pre_process('output'):
return
if is_image(facefusion.globals.target_path):
process_image()
if is_video(facefusion.globals.target_path):
process_video()
def run() -> None:
parse_args()
limit_resources()
# pre check
if not pre_check():
return
for frame_processor in get_frame_processors_modules(facefusion.globals.frame_processors):
if not frame_processor.pre_check():
return
# headless or ui
if facefusion.globals.headless:
conditional_process()
else:
import facefusion.uis.core as ui
# pre check
for ui_layout in ui.get_ui_layouts_modules(facefusion.globals.ui_layouts):
if not ui_layout.pre_check():
return
ui.launch()
def destroy() -> None:
if facefusion.globals.target_path:
clear_temp(facefusion.globals.target_path)
sys.exit()

View File

@ -2,31 +2,39 @@ from typing import List, Optional
from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder
# general
source_path : Optional[str] = None source_path : Optional[str] = None
target_path : Optional[str] = None target_path : Optional[str] = None
output_path : Optional[str] = None output_path : Optional[str] = None
frame_processors : List[str] = [] # misc
ui_layouts : List[str] = [] skip_download : Optional[bool] = None
keep_fps : Optional[bool] = None headless : Optional[bool] = None
keep_temp : Optional[bool] = None # execution
skip_audio : Optional[bool] = None execution_providers : List[str] = []
execution_thread_count : Optional[int] = None
execution_queue_count : Optional[int] = None
max_memory : Optional[int] = None
# face recognition
face_recognition : Optional[FaceRecognition] = None face_recognition : Optional[FaceRecognition] = None
face_analyser_direction : Optional[FaceAnalyserDirection] = None face_analyser_direction : Optional[FaceAnalyserDirection] = None
face_analyser_age : Optional[FaceAnalyserAge] = None face_analyser_age : Optional[FaceAnalyserAge] = None
face_analyser_gender : Optional[FaceAnalyserGender] = None face_analyser_gender : Optional[FaceAnalyserGender] = None
reference_face_position : Optional[int] = None reference_face_position : Optional[int] = None
reference_frame_number : Optional[int] = None
reference_face_distance : Optional[float] = None reference_face_distance : Optional[float] = None
reference_frame_number : Optional[int] = None
# frame extraction
trim_frame_start : Optional[int] = None trim_frame_start : Optional[int] = None
trim_frame_end : Optional[int] = None trim_frame_end : Optional[int] = None
temp_frame_format : Optional[TempFrameFormat] = None temp_frame_format : Optional[TempFrameFormat] = None
temp_frame_quality : Optional[int] = None temp_frame_quality : Optional[int] = None
keep_temp : Optional[bool] = None
# output creation
output_image_quality : Optional[int] = None output_image_quality : Optional[int] = None
output_video_encoder : Optional[OutputVideoEncoder] = None output_video_encoder : Optional[OutputVideoEncoder] = None
output_video_quality : Optional[int] = None output_video_quality : Optional[int] = None
max_memory : Optional[int] = None keep_fps : Optional[bool] = None
execution_providers : List[str] = [] skip_audio : Optional[bool] = None
execution_thread_count : Optional[int] = None # frame processors
execution_queue_count : Optional[int] = None frame_processors : List[str] = []
skip_download : Optional[bool] = None # uis
headless : Optional[bool] = None ui_layouts : List[str] = []

View File

@ -1,9 +1,6 @@
from typing import Dict, Tuple from typing import Dict, Tuple
import argparse
import os
import sys
import subprocess import subprocess
import tempfile from argparse import ArgumentParser, HelpFormatter
subprocess.call([ 'pip', 'install' , 'inquirer', '-q' ]) subprocess.call([ 'pip', 'install' , 'inquirer', '-q' ])
@ -11,55 +8,61 @@ import inquirer
from facefusion import metadata, wording from facefusion import metadata, wording
TORCH : Dict[str, str] =\
{
'cpu': 'https://download.pytorch.org/whl/cpu',
'cuda': 'https://download.pytorch.org/whl/cu118',
'rocm': 'https://download.pytorch.org/whl/rocm5.6'
}
ONNXRUNTIMES : Dict[str, Tuple[str, str]] =\ ONNXRUNTIMES : Dict[str, Tuple[str, str]] =\
{ {
'cpu': ('onnxruntime', '1.16.0 '), 'cpu': ('onnxruntime', '1.16.0 '),
'cuda': ('onnxruntime-gpu', '1.16.0'), 'cuda': ('onnxruntime-gpu', '1.16.0'),
'coreml-legacy': ('onnxruntime-coreml', '1.13.1'), 'coreml-legacy': ('onnxruntime-coreml', '1.13.1'),
'coreml-silicon': ('onnxruntime-silicon', '1.14.2'), 'coreml-silicon': ('onnxruntime-silicon', '1.16.0'),
'directml': ('onnxruntime-directml', '1.16.0'), 'directml': ('onnxruntime-directml', '1.16.0'),
'openvino': ('onnxruntime-openvino', '1.15.0') 'openvino': ('onnxruntime-openvino', '1.15.0')
} }
def run() -> None: def cli() -> None:
program = argparse.ArgumentParser(formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position = 120)) program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 120))
program.add_argument('--onnxruntime', help = wording.get('onnxruntime_help'), dest = 'onnxruntime', choices = ONNXRUNTIMES.keys()) program.add_argument('--torch', help = wording.get('install_dependency_help').format(dependency = 'torch'), dest = 'torch', choices = TORCH.keys())
program.add_argument('--onnxruntime', help = wording.get('install_dependency_help').format(dependency = 'onnxruntime'), dest = 'onnxruntime', choices = ONNXRUNTIMES.keys())
program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version') program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
run(program)
def run(program : ArgumentParser) -> None:
args = program.parse_args() args = program.parse_args()
if args.onnxruntime: if args.onnxruntime:
answers =\ answers =\
{ {
'torch': args.torch,
'onnxruntime': args.onnxruntime 'onnxruntime': args.onnxruntime
} }
else: else:
answers = inquirer.prompt( answers = inquirer.prompt(
[ [
inquirer.List(
'torch',
message = wording.get('install_dependency_help').format(dependency = 'torch'),
choices = list(TORCH.keys())
),
inquirer.List( inquirer.List(
'onnxruntime', 'onnxruntime',
message = wording.get('onnxruntime_help'), message = wording.get('install_dependency_help').format(dependency = 'onnxruntime'),
choices = list(ONNXRUNTIMES.keys()) choices = list(ONNXRUNTIMES.keys())
) )
]) ])
if answers is not None: if answers is not None:
torch = answers['torch']
torch_url = TORCH[torch]
onnxruntime = answers['onnxruntime'] onnxruntime = answers['onnxruntime']
onnxruntime_name, onnxruntime_version = ONNXRUNTIMES[onnxruntime] onnxruntime_name, onnxruntime_version = ONNXRUNTIMES[onnxruntime]
python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor)
subprocess.call([ 'pip', 'uninstall', 'torch', '-y' ]) subprocess.call([ 'pip', 'uninstall', 'torch', '-y' ])
if onnxruntime == 'cuda': subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--extra-index-url', torch_url ])
subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--extra-index-url', 'https://download.pytorch.org/whl/cu118' ])
else:
subprocess.call([ 'pip', 'install', '-r', 'requirements.txt' ])
if onnxruntime != 'cpu': if onnxruntime != 'cpu':
subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y' ]) subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y' ])
if onnxruntime != 'coreml-silicon': subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version ])
subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version ])
elif python_id in [ 'cp39', 'cp310', 'cp311' ]:
wheel_name = '-'.join([ 'onnxruntime_silicon', onnxruntime_version, python_id, python_id, 'macosx_12_0_arm64.whl' ])
wheel_path = os.path.join(tempfile.gettempdir(), wheel_name)
wheel_url = 'https://github.com/cansik/onnxruntime-silicon/releases/download/v' + onnxruntime_version + '/' + wheel_name
subprocess.call([ 'curl', '--silent', '--location', '--continue-at', '-', '--output', wheel_path, wheel_url ])
subprocess.call([ 'pip', 'install', wheel_path ])
os.remove(wheel_path)

View File

@ -2,7 +2,7 @@ METADATA =\
{ {
'name': 'FaceFusion', 'name': 'FaceFusion',
'description': 'Next generation face swapper and enhancer', 'description': 'Next generation face swapper and enhancer',
'version': '1.2.1', 'version': '1.3.0',
'license': 'MIT', 'license': 'MIT',
'author': 'Henry Ruhs', 'author': 'Henry Ruhs',
'url': 'https://facefusion.io' 'url': 'https://facefusion.io'

View File

@ -1,5 +1,6 @@
import threading import threading
from functools import lru_cache from functools import lru_cache
import numpy import numpy
import opennsfw2 import opennsfw2
from PIL import Image from PIL import Image
@ -10,6 +11,8 @@ from facefusion.typing import Frame
PREDICTOR = None PREDICTOR = None
THREAD_LOCK : threading.Lock = threading.Lock() THREAD_LOCK : threading.Lock = threading.Lock()
MAX_PROBABILITY = 0.75 MAX_PROBABILITY = 0.75
FRAME_INTERVAL = 25
STREAM_COUNTER = 0
def get_predictor() -> Model: def get_predictor() -> Model:
@ -27,8 +30,17 @@ def clear_predictor() -> None:
PREDICTOR = None PREDICTOR = None
def predict_frame(target_frame : Frame) -> bool: def predict_stream(frame : Frame) -> bool:
image = Image.fromarray(target_frame) global STREAM_COUNTER
STREAM_COUNTER = STREAM_COUNTER + 1
if STREAM_COUNTER % FRAME_INTERVAL == 0:
return predict_frame(frame)
return False
def predict_frame(frame : Frame) -> bool:
image = Image.fromarray(frame)
image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO) image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO)
views = numpy.expand_dims(image, axis = 0) views = numpy.expand_dims(image, axis = 0)
_, probability = get_predictor().predict(views)[0] _, probability = get_predictor().predict(views)[0]
@ -42,5 +54,5 @@ def predict_image(image_path : str) -> bool:
@lru_cache(maxsize = None) @lru_cache(maxsize = None)
def predict_video(video_path : str) -> bool: def predict_video(video_path : str) -> bool:
_, probabilities = opennsfw2.predict_video_frames(video_path = video_path, frame_interval = 25) _, probabilities = opennsfw2.predict_video_frames(video_path = video_path, frame_interval = FRAME_INTERVAL)
return any(probability > MAX_PROBABILITY for probability in probabilities) return any(probability > MAX_PROBABILITY for probability in probabilities)

View File

@ -0,0 +1,5 @@
from typing import List
face_swapper_models : List[str] = [ 'inswapper_128', 'inswapper_128_fp16' ]
face_enhancer_models : List[str] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_512' ]
frame_enhancer_models : List[str] = [ 'realesrgan_x2plus', 'realesrgan_x4plus', 'realesrnet_x4plus' ]

View File

@ -5,17 +5,22 @@ import psutil
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from queue import Queue from queue import Queue
from types import ModuleType from types import ModuleType
from typing import Any, List, Callable from typing import Any, List
from tqdm import tqdm from tqdm import tqdm
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.typing import Process_Frames
FRAME_PROCESSORS_MODULES : List[ModuleType] = [] FRAME_PROCESSORS_MODULES : List[ModuleType] = []
FRAME_PROCESSORS_METHODS =\ FRAME_PROCESSORS_METHODS =\
[ [
'get_frame_processor', 'get_frame_processor',
'clear_frame_processor', 'clear_frame_processor',
'get_options',
'set_options',
'register_args',
'apply_args',
'pre_check', 'pre_check',
'pre_process', 'pre_process',
'process_frame', 'process_frame',
@ -57,7 +62,7 @@ def clear_frame_processors_modules() -> None:
FRAME_PROCESSORS_MODULES = [] FRAME_PROCESSORS_MODULES = []
def multi_process_frames(source_path : str, temp_frame_paths : List[str], process_frames : Callable[[str, List[str], Callable[[], None]], None]) -> None: def multi_process_frames(source_path : str, temp_frame_paths : List[str], process_frames : Process_Frames) -> None:
progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]' progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]'
with tqdm(total = len(temp_frame_paths), desc = wording.get('processing'), unit = 'frame', dynamic_ncols = True, bar_format = progress_bar_format) as progress: with tqdm(total = len(temp_frame_paths), desc = wording.get('processing'), unit = 'frame', dynamic_ncols = True, bar_format = progress_bar_format) as progress:
with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor: with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:

View File

@ -0,0 +1,7 @@
from typing import Optional
face_swapper_model : Optional[str] = None
face_enhancer_model : Optional[str] = None
face_enhancer_blend : Optional[int] = None
frame_enhancer_model : Optional[str] = None
frame_enhancer_blend : Optional[int] = None

View File

@ -1,21 +1,53 @@
from typing import Any, List, Callable from typing import Any, List, Tuple, Dict, Literal, Optional
from argparse import ArgumentParser
import cv2
import threading import threading
from gfpgan.utils import GFPGANer import numpy
import onnxruntime
import facefusion.globals import facefusion.globals
from facefusion import wording, utilities from facefusion import wording
from facefusion.core import update_status from facefusion.core import update_status
from facefusion.face_analyser import get_many_faces, clear_face_analyser from facefusion.face_analyser import get_many_faces, clear_face_analyser
from facefusion.typing import Frame, Face, ProcessMode from facefusion.typing import Face, Frame, Matrix, Update_Process, ProcessMode, ModelValue, OptionsWithModel
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
from facefusion.vision import read_image, read_static_image, write_image from facefusion.vision import read_image, read_static_image, write_image
from facefusion.processors.frame import globals as frame_processors_globals
from facefusion.processors.frame import choices as frame_processors_choices
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore() THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
THREAD_LOCK : threading.Lock = threading.Lock() THREAD_LOCK : threading.Lock = threading.Lock()
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_ENHANCER' NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_ENHANCER'
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.pth' MODELS : Dict[str, ModelValue] =\
MODEL_PATH = resolve_relative_path('../.assets/models/GFPGANv1.4.pth') {
'codeformer':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx',
'path': resolve_relative_path('../.assets/models/codeformer.onnx')
},
'gfpgan_1.2':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.2.onnx',
'path': resolve_relative_path('../.assets/models/GFPGANv1.2.onnx')
},
'gfpgan_1.3':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.3.onnx',
'path': resolve_relative_path('../.assets/models/GFPGANv1.3.onnx')
},
'gfpgan_1.4':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.onnx',
'path': resolve_relative_path('../.assets/models/GFPGANv1.4.onnx')
},
'gpen_bfr_512':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/GPEN-BFR-512.onnx',
'path': resolve_relative_path('../.assets/models/GPEN-BFR-512.onnx')
}
}
OPTIONS : Optional[OptionsWithModel] = None
def get_frame_processor() -> Any: def get_frame_processor() -> Any:
@ -23,11 +55,8 @@ def get_frame_processor() -> Any:
with THREAD_LOCK: with THREAD_LOCK:
if FRAME_PROCESSOR is None: if FRAME_PROCESSOR is None:
FRAME_PROCESSOR = GFPGANer( model_path = get_options('model').get('path')
model_path = MODEL_PATH, FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = facefusion.globals.execution_providers)
upscale = 1,
device = utilities.get_device(facefusion.globals.execution_providers)
)
return FRAME_PROCESSOR return FRAME_PROCESSOR
@ -37,18 +66,49 @@ def clear_frame_processor() -> None:
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
def get_options(key : Literal[ 'model' ]) -> Any:
global OPTIONS
if OPTIONS is None:
OPTIONS =\
{
'model': MODELS[frame_processors_globals.face_enhancer_model]
}
return OPTIONS.get(key)
def set_options(key : Literal[ 'model' ], value : Any) -> None:
global OPTIONS
OPTIONS[key] = value
def register_args(program : ArgumentParser) -> None:
program.add_argument('--face-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'face_enhancer_model', default = 'gfpgan_1.4', choices = frame_processors_choices.face_enhancer_models)
program.add_argument('--face-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest= 'face_enhancer_blend', type = int, default= 100, choices = range(101), metavar = '[0-100]')
def apply_args(program : ArgumentParser) -> None:
args = program.parse_args()
frame_processors_globals.face_enhancer_model = args.face_enhancer_model
frame_processors_globals.face_enhancer_blend = args.face_enhancer_blend
def pre_check() -> bool: def pre_check() -> bool:
if not facefusion.globals.skip_download: if not facefusion.globals.skip_download:
download_directory_path = resolve_relative_path('../.assets/models') download_directory_path = resolve_relative_path('../.assets/models')
conditional_download(download_directory_path, [ MODEL_URL ]) model_url = get_options('model').get('url')
conditional_download(download_directory_path, [ model_url ])
return True return True
def pre_process(mode : ProcessMode) -> bool: def pre_process(mode : ProcessMode) -> bool:
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH): model_url = get_options('model').get('url')
model_path = get_options('model').get('path')
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
return False return False
elif not is_file(MODEL_PATH): elif not is_file(model_path):
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
return False return False
if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path): if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
@ -66,22 +126,77 @@ def post_process() -> None:
read_static_image.cache_clear() read_static_image.cache_clear()
def enhance_face(target_face : Face, temp_frame : Frame) -> Frame: def enhance_face(target_face: Face, temp_frame: Frame) -> Frame:
start_x, start_y, end_x, end_y = map(int, target_face['bbox']) frame_processor = get_frame_processor()
padding_x = int((end_x - start_x) * 0.5) crop_frame, affine_matrix = warp_face(target_face, temp_frame)
padding_y = int((end_y - start_y) * 0.5) crop_frame = prepare_crop_frame(crop_frame)
start_x = max(0, start_x - padding_x) frame_processor_inputs = {}
start_y = max(0, start_y - padding_y) for frame_processor_input in frame_processor.get_inputs():
end_x = max(0, end_x + padding_x) if frame_processor_input.name == 'input':
end_y = max(0, end_y + padding_y) frame_processor_inputs[frame_processor_input.name] = crop_frame
crop_frame = temp_frame[start_y:end_y, start_x:end_x] if frame_processor_input.name == 'weight':
if crop_frame.size: frame_processor_inputs[frame_processor_input.name] = numpy.array([ 1 ], dtype = numpy.double)
with THREAD_SEMAPHORE: with THREAD_SEMAPHORE:
_, _, crop_frame = get_frame_processor().enhance( crop_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
crop_frame, crop_frame = normalize_crop_frame(crop_frame)
paste_back = True paste_frame = paste_back(temp_frame, crop_frame, affine_matrix)
) temp_frame = blend_frame(temp_frame, paste_frame)
temp_frame[start_y:end_y, start_x:end_x] = crop_frame return temp_frame
def warp_face(target_face : Face, temp_frame : Frame) -> Tuple[Frame, Matrix]:
template = numpy.array(
[
[ 192.98138, 239.94708 ],
[ 318.90277, 240.1936 ],
[ 256.63416, 314.01935 ],
[ 201.26117, 371.41043 ],
[ 313.08905, 371.15118 ]
])
affine_matrix = cv2.estimateAffinePartial2D(target_face['kps'], template, method = cv2.LMEDS)[0]
crop_frame = cv2.warpAffine(temp_frame, affine_matrix, (512, 512))
return crop_frame, affine_matrix
def prepare_crop_frame(crop_frame : Frame) -> Frame:
crop_frame = crop_frame[:, :, ::-1] / 255.0
crop_frame = (crop_frame - 0.5) / 0.5
crop_frame = numpy.expand_dims(crop_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
return crop_frame
def normalize_crop_frame(crop_frame : Frame) -> Frame:
crop_frame = numpy.clip(crop_frame, -1, 1)
crop_frame = (crop_frame + 1) / 2
crop_frame = crop_frame.transpose(1, 2, 0)
crop_frame = (crop_frame * 255.0).round()
crop_frame = crop_frame.astype(numpy.uint8)[:, :, ::-1]
return crop_frame
def paste_back(temp_frame : Frame, crop_frame : Frame, affine_matrix : Matrix) -> Frame:
inverse_affine_matrix = cv2.invertAffineTransform(affine_matrix)
temp_frame_height, temp_frame_width = temp_frame.shape[0:2]
crop_frame_height, crop_frame_width = crop_frame.shape[0:2]
inverse_crop_frame = cv2.warpAffine(crop_frame, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
inverse_mask = numpy.ones((crop_frame_height, crop_frame_width, 3), dtype = numpy.float32)
inverse_mask_frame = cv2.warpAffine(inverse_mask, inverse_affine_matrix, (temp_frame_width, temp_frame_height))
inverse_mask_frame = cv2.erode(inverse_mask_frame, numpy.ones((2, 2)))
inverse_mask_border = inverse_mask_frame * inverse_crop_frame
inverse_mask_area = numpy.sum(inverse_mask_frame) // 3
inverse_mask_edge = int(inverse_mask_area ** 0.5) // 20
inverse_mask_radius = inverse_mask_edge * 2
inverse_mask_center = cv2.erode(inverse_mask_frame, numpy.ones((inverse_mask_radius, inverse_mask_radius)))
inverse_mask_blur_size = inverse_mask_edge * 2 + 1
inverse_mask_blur_area = cv2.GaussianBlur(inverse_mask_center, (inverse_mask_blur_size, inverse_mask_blur_size), 0)
temp_frame = inverse_mask_blur_area * inverse_mask_border + (1 - inverse_mask_blur_area) * temp_frame
temp_frame = temp_frame.clip(0, 255).astype(numpy.uint8)
return temp_frame
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100)
temp_frame = cv2.addWeighted(temp_frame, face_enhancer_blend, paste_frame, 1 - face_enhancer_blend, 0)
return temp_frame return temp_frame
@ -93,7 +208,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
return temp_frame return temp_frame
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None: def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
for temp_frame_path in temp_frame_paths: for temp_frame_path in temp_frame_paths:
temp_frame = read_image(temp_frame_path) temp_frame = read_image(temp_frame_path)
result_frame = process_frame(None, None, temp_frame) result_frame = process_frame(None, None, temp_frame)

View File

@ -1,4 +1,5 @@
from typing import Any, List, Callable from typing import Any, List, Dict, Literal, Optional
from argparse import ArgumentParser
import insightface import insightface
import threading import threading
@ -8,15 +9,29 @@ from facefusion import wording
from facefusion.core import update_status from facefusion.core import update_status
from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
from facefusion.face_reference import get_face_reference, set_face_reference from facefusion.face_reference import get_face_reference, set_face_reference
from facefusion.typing import Face, Frame, ProcessMode from facefusion.typing import Face, Frame, Update_Process, ProcessMode, ModelValue, OptionsWithModel
from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done from facefusion.utilities import conditional_download, resolve_relative_path, is_image, is_video, is_file, is_download_done
from facefusion.vision import read_image, read_static_image, write_image from facefusion.vision import read_image, read_static_image, write_image
from facefusion.processors.frame import globals as frame_processors_globals
from facefusion.processors.frame import choices as frame_processors_choices
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
THREAD_LOCK : threading.Lock = threading.Lock() THREAD_LOCK : threading.Lock = threading.Lock()
NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_SWAPPER' NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_SWAPPER'
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx' MODELS : Dict[str, ModelValue] =\
MODEL_PATH = resolve_relative_path('../.assets/models/inswapper_128.onnx') {
'inswapper_128':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx',
'path': resolve_relative_path('../.assets/models/inswapper_128.onnx')
},
'inswapper_128_fp16':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx',
'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx')
}
}
OPTIONS : Optional[OptionsWithModel] = None
def get_frame_processor() -> Any: def get_frame_processor() -> Any:
@ -24,7 +39,8 @@ def get_frame_processor() -> Any:
with THREAD_LOCK: with THREAD_LOCK:
if FRAME_PROCESSOR is None: if FRAME_PROCESSOR is None:
FRAME_PROCESSOR = insightface.model_zoo.get_model(MODEL_PATH, providers = facefusion.globals.execution_providers) model_path = get_options('model').get('path')
FRAME_PROCESSOR = insightface.model_zoo.get_model(model_path, providers = facefusion.globals.execution_providers)
return FRAME_PROCESSOR return FRAME_PROCESSOR
@ -34,18 +50,47 @@ def clear_frame_processor() -> None:
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
def get_options(key : Literal[ 'model' ]) -> Any:
global OPTIONS
if OPTIONS is None:
OPTIONS = \
{
'model': MODELS[frame_processors_globals.face_swapper_model]
}
return OPTIONS.get(key)
def set_options(key : Literal[ 'model' ], value : Any) -> None:
global OPTIONS
OPTIONS[key] = value
def register_args(program : ArgumentParser) -> None:
program.add_argument('--face-swapper-model', help = wording.get('frame_processor_model_help'), dest = 'face_swapper_model', default = 'inswapper_128', choices = frame_processors_choices.face_swapper_models)
def apply_args(program : ArgumentParser) -> None:
args = program.parse_args()
frame_processors_globals.face_swapper_model = args.face_swapper_model
def pre_check() -> bool: def pre_check() -> bool:
if not facefusion.globals.skip_download: if not facefusion.globals.skip_download:
download_directory_path = resolve_relative_path('../.assets/models') download_directory_path = resolve_relative_path('../.assets/models')
conditional_download(download_directory_path, [ MODEL_URL ]) model_url = get_options('model').get('url')
conditional_download(download_directory_path, [ model_url ])
return True return True
def pre_process(mode : ProcessMode) -> bool: def pre_process(mode : ProcessMode) -> bool:
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH): model_url = get_options('model').get('url')
model_path = get_options('model').get('path')
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
return False return False
elif not is_file(MODEL_PATH): elif not is_file(model_path):
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
return False return False
if not is_image(facefusion.globals.source_path): if not is_image(facefusion.globals.source_path):
@ -87,7 +132,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
return temp_frame return temp_frame
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None: def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
source_face = get_one_face(read_static_image(source_path)) source_face = get_one_face(read_static_image(source_path))
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
for temp_frame_path in temp_frame_paths: for temp_frame_path in temp_frame_paths:

View File

@ -1,23 +1,47 @@
from typing import Any, List, Callable from typing import Any, List, Dict, Literal, Optional
from argparse import ArgumentParser
import threading import threading
import cv2
from basicsr.archs.rrdbnet_arch import RRDBNet from basicsr.archs.rrdbnet_arch import RRDBNet
from realesrgan import RealESRGANer from realesrgan import RealESRGANer
import facefusion.globals import facefusion.globals
import facefusion.processors.frame.core as frame_processors import facefusion.processors.frame.core as frame_processors
from facefusion import wording, utilities from facefusion import wording
from facefusion.core import update_status from facefusion.core import update_status
from facefusion.face_analyser import clear_face_analyser from facefusion.face_analyser import clear_face_analyser
from facefusion.typing import Frame, Face, ProcessMode from facefusion.typing import Frame, Face, Update_Process, ProcessMode, ModelValue, OptionsWithModel
from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done, get_device
from facefusion.vision import read_image, read_static_image, write_image from facefusion.vision import read_image, read_static_image, write_image
from facefusion.processors.frame import globals as frame_processors_globals
from facefusion.processors.frame import choices as frame_processors_choices
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore() THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
THREAD_LOCK : threading.Lock = threading.Lock() THREAD_LOCK : threading.Lock = threading.Lock()
NAME = 'FACEFUSION.FRAME_PROCESSOR.FRAME_ENHANCER' NAME = 'FACEFUSION.FRAME_PROCESSOR.FRAME_ENHANCER'
MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth' MODELS: Dict[str, ModelValue] =\
MODEL_PATH = resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth') {
'realesrgan_x2plus':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x2plus.pth',
'path': resolve_relative_path('../.assets/models/RealESRGAN_x2plus.pth'),
'scale': 2
},
'realesrgan_x4plus':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth',
'path': resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth'),
'scale': 4
},
'realesrnet_x4plus':
{
'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRNet_x4plus.pth',
'path': resolve_relative_path('../.assets/models/RealESRNet_x4plus.pth'),
'scale': 4
}
}
OPTIONS : Optional[OptionsWithModel] = None
def get_frame_processor() -> Any: def get_frame_processor() -> Any:
@ -25,21 +49,17 @@ def get_frame_processor() -> Any:
with THREAD_LOCK: with THREAD_LOCK:
if FRAME_PROCESSOR is None: if FRAME_PROCESSOR is None:
model_path = get_options('model').get('path')
model_scale = get_options('model').get('scale')
FRAME_PROCESSOR = RealESRGANer( FRAME_PROCESSOR = RealESRGANer(
model_path = MODEL_PATH, model_path = model_path,
model = RRDBNet( model = RRDBNet(
num_in_ch = 3, num_in_ch = 3,
num_out_ch = 3, num_out_ch = 3,
num_feat = 64, scale = model_scale
num_block = 23,
num_grow_ch = 32,
scale = 4
), ),
device = utilities.get_device(facefusion.globals.execution_providers), device = get_device(facefusion.globals.execution_providers),
tile = 512, scale = model_scale
tile_pad = 32,
pre_pad = 0,
scale = 4
) )
return FRAME_PROCESSOR return FRAME_PROCESSOR
@ -50,18 +70,49 @@ def clear_frame_processor() -> None:
FRAME_PROCESSOR = None FRAME_PROCESSOR = None
def get_options(key : Literal[ 'model' ]) -> Any:
global OPTIONS
if OPTIONS is None:
OPTIONS = \
{
'model': MODELS[frame_processors_globals.frame_enhancer_model]
}
return OPTIONS.get(key)
def set_options(key : Literal[ 'model' ], value : Any) -> None:
global OPTIONS
OPTIONS[key] = value
def register_args(program : ArgumentParser) -> None:
program.add_argument('--frame-enhancer-model', help = wording.get('frame_processor_model_help'), dest = 'frame_enhancer_model', default = 'realesrgan_x2plus', choices = frame_processors_choices.frame_enhancer_models)
program.add_argument('--frame-enhancer-blend', help = wording.get('frame_processor_blend_help'), dest = 'frame_enhancer_blend', type = int, default = 100, choices = range(101), metavar = '[0-100]')
def apply_args(program : ArgumentParser) -> None:
args = program.parse_args()
frame_processors_globals.frame_enhancer_model = args.frame_enhancer_model
frame_processors_globals.frame_enhancer_blend = args.frame_enhancer_blend
def pre_check() -> bool: def pre_check() -> bool:
if not facefusion.globals.skip_download: if not facefusion.globals.skip_download:
download_directory_path = resolve_relative_path('../.assets/models') download_directory_path = resolve_relative_path('../.assets/models')
conditional_download(download_directory_path, [ MODEL_URL ]) model_url = get_options('model').get('url')
conditional_download(download_directory_path, [ model_url ])
return True return True
def pre_process(mode : ProcessMode) -> bool: def pre_process(mode : ProcessMode) -> bool:
if not facefusion.globals.skip_download and not is_download_done(MODEL_URL, MODEL_PATH): model_url = get_options('model').get('url')
model_path = get_options('model').get('path')
if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
return False return False
elif not is_file(MODEL_PATH): elif not is_file(model_path):
update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) update_status(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
return False return False
if mode == 'output' and not facefusion.globals.output_path: if mode == 'output' and not facefusion.globals.output_path:
@ -78,7 +129,15 @@ def post_process() -> None:
def enhance_frame(temp_frame : Frame) -> Frame: def enhance_frame(temp_frame : Frame) -> Frame:
with THREAD_SEMAPHORE: with THREAD_SEMAPHORE:
temp_frame, _ = get_frame_processor().enhance(temp_frame, outscale = 1) paste_frame, _ = get_frame_processor().enhance(temp_frame)
temp_frame = blend_frame(temp_frame, paste_frame)
return temp_frame
def blend_frame(temp_frame : Frame, paste_frame : Frame) -> Frame:
frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100)
temp_frame = cv2.resize(temp_frame, (paste_frame.shape[1], paste_frame.shape[0]))
temp_frame = cv2.addWeighted(temp_frame, frame_enhancer_blend, paste_frame, 1 - frame_enhancer_blend, 0)
return temp_frame return temp_frame
@ -86,7 +145,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : Frame)
return enhance_frame(temp_frame) return enhance_frame(temp_frame)
def process_frames(source_path : str, temp_frame_paths : List[str], update_progress: Callable[[], None]) -> None: def process_frames(source_path : str, temp_frame_paths : List[str], update_progress : Update_Process) -> None:
for temp_frame_path in temp_frame_paths: for temp_frame_path in temp_frame_paths:
temp_frame = read_image(temp_frame_path) temp_frame = read_image(temp_frame_path)
result_frame = process_frame(None, None, temp_frame) result_frame = process_frame(None, None, temp_frame)

View File

@ -1,9 +1,13 @@
from typing import Any, Literal from typing import Any, Literal, Callable, List, TypedDict, Dict
from insightface.app.common import Face from insightface.app.common import Face
import numpy import numpy
Face = Face Face = Face
Frame = numpy.ndarray[Any, Any] Frame = numpy.ndarray[Any, Any]
Matrix = numpy.ndarray[Any, Any]
Update_Process = Callable[[], None]
Process_Frames = Callable[[str, List[str], Update_Process], None]
ProcessMode = Literal[ 'output', 'preview', 'stream' ] ProcessMode = Literal[ 'output', 'preview', 'stream' ]
FaceRecognition = Literal[ 'reference', 'many' ] FaceRecognition = Literal[ 'reference', 'many' ]
@ -12,3 +16,9 @@ FaceAnalyserAge = Literal[ 'child', 'teen', 'adult', 'senior' ]
FaceAnalyserGender = Literal[ 'male', 'female' ] FaceAnalyserGender = Literal[ 'male', 'female' ]
TempFrameFormat = Literal[ 'jpg', 'png' ] TempFrameFormat = Literal[ 'jpg', 'png' ]
OutputVideoEncoder = Literal[ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ] OutputVideoEncoder = Literal[ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ]
ModelValue = Dict['str', Any]
OptionsWithModel = TypedDict('OptionsWithModel',
{
'model' : ModelValue
})

View File

@ -0,0 +1,7 @@
:root:root:root button:not([class])
{
border-radius: 0.375rem;
float: left;
overflow: hidden;
width: 100%;
}

View File

@ -0,0 +1,32 @@
:root:root:root input[type="number"]
{
max-width: 6rem;
}
:root:root:root [type="checkbox"],
:root:root:root [type="radio"]
{
border-radius: 50%;
height: 1.125rem;
width: 1.125rem;
}
:root:root:root input[type="range"]
{
height: 0.5rem;
}
:root:root:root input[type="range"]::-moz-range-thumb,
:root:root:root input[type="range"]::-webkit-slider-thumb
{
background: var(--neutral-300);
border: unset;
border-radius: 50%;
height: 1.125rem;
width: 1.125rem;
}
:root:root:root input[type="range"]::-webkit-slider-thumb
{
margin-top: 0.375rem;
}

View File

@ -2,6 +2,6 @@ from typing import List
from facefusion.uis.typing import WebcamMode from facefusion.uis.typing import WebcamMode
settings : List[str] = [ 'keep-fps', 'keep-temp', 'skip-audio', 'skip-download' ] common_options : List[str] = [ 'keep-fps', 'keep-temp', 'skip-audio', 'skip-download' ]
webcam_mode : List[WebcamMode] = [ 'inline', 'stream_udp', 'stream_v4l2' ] webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ]
webcam_resolution : List[str] = [ '320x240', '640x480', '1280x720', '1920x1080', '2560x1440', '3840x2160' ] webcam_resolutions : List[str] = [ '320x240', '640x480', '1280x720', '1920x1080', '2560x1440', '3840x2160' ]

View File

@ -1,12 +1,23 @@
from typing import Optional from typing import Optional
import gradio import gradio
from facefusion import metadata from facefusion import metadata, wording
ABOUT_HTML : Optional[gradio.HTML] = None ABOUT_BUTTON : Optional[gradio.HTML] = None
DONATE_BUTTON : Optional[gradio.HTML] = None
def render() -> None: def render() -> None:
global ABOUT_HTML global ABOUT_BUTTON
global DONATE_BUTTON
ABOUT_HTML = gradio.HTML('<center><a href="' + metadata.get('url') + '">' + metadata.get('name') + ' ' + metadata.get('version') + '</a></center>') ABOUT_BUTTON = gradio.Button(
value = metadata.get('name') + ' ' + metadata.get('version'),
variant = 'primary',
link = metadata.get('url')
)
DONATE_BUTTON = gradio.Button(
value = wording.get('donate_button_label'),
link = 'https://donate.facefusion.io',
size = 'sm'
)

View File

@ -11,9 +11,8 @@ from facefusion.face_cache import clear_faces_cache
from facefusion.processors.frame.core import get_frame_processors_modules from facefusion.processors.frame.core import get_frame_processors_modules
from facefusion.vision import count_video_frame_total from facefusion.vision import count_video_frame_total
from facefusion.core import limit_resources, conditional_process from facefusion.core import limit_resources, conditional_process
from facefusion.uis.typing import Update
from facefusion.uis import core as ui
from facefusion.utilities import normalize_output_path, clear_temp from facefusion.utilities import normalize_output_path, clear_temp
from facefusion.uis.core import get_ui_component
BENCHMARK_RESULTS_DATAFRAME : Optional[gradio.Dataframe] = None BENCHMARK_RESULTS_DATAFRAME : Optional[gradio.Dataframe] = None
BENCHMARK_START_BUTTON : Optional[gradio.Button] = None BENCHMARK_START_BUTTON : Optional[gradio.Button] = None
@ -58,16 +57,18 @@ def render() -> None:
) )
BENCHMARK_START_BUTTON = gradio.Button( BENCHMARK_START_BUTTON = gradio.Button(
value = wording.get('start_button_label'), value = wording.get('start_button_label'),
variant = 'primary' variant = 'primary',
size = 'sm'
) )
BENCHMARK_CLEAR_BUTTON = gradio.Button( BENCHMARK_CLEAR_BUTTON = gradio.Button(
value = wording.get('clear_button_label') value = wording.get('clear_button_label'),
size = 'sm'
) )
def listen() -> None: def listen() -> None:
benchmark_runs_checkbox_group = ui.get_component('benchmark_runs_checkbox_group') benchmark_runs_checkbox_group = get_ui_component('benchmark_runs_checkbox_group')
benchmark_cycles_slider = ui.get_component('benchmark_cycles_slider') benchmark_cycles_slider = get_ui_component('benchmark_cycles_slider')
if benchmark_runs_checkbox_group and benchmark_cycles_slider: if benchmark_runs_checkbox_group and benchmark_cycles_slider:
BENCHMARK_START_BUTTON.click(start, inputs = [ benchmark_runs_checkbox_group, benchmark_cycles_slider ], outputs = BENCHMARK_RESULTS_DATAFRAME) BENCHMARK_START_BUTTON.click(start, inputs = [ benchmark_runs_checkbox_group, benchmark_cycles_slider ], outputs = BENCHMARK_RESULTS_DATAFRAME)
BENCHMARK_CLEAR_BUTTON.click(clear, outputs = BENCHMARK_RESULTS_DATAFRAME) BENCHMARK_CLEAR_BUTTON.click(clear, outputs = BENCHMARK_RESULTS_DATAFRAME)
@ -124,7 +125,7 @@ def benchmark(target_path : str, benchmark_cycles : int) -> List[Any]:
] ]
def clear() -> Update: def clear() -> gradio.Dataframe:
if facefusion.globals.target_path: if facefusion.globals.target_path:
clear_temp(facefusion.globals.target_path) clear_temp(facefusion.globals.target_path)
return gradio.update(value = None) return gradio.Dataframe(value = None)

View File

@ -1,9 +1,8 @@
from typing import Optional, List from typing import Optional
import gradio import gradio
from facefusion import wording from facefusion import wording
from facefusion.uis.typing import Update from facefusion.uis.core import register_ui_component
from facefusion.uis import core as ui
from facefusion.uis.components.benchmark import BENCHMARKS from facefusion.uis.components.benchmark import BENCHMARKS
BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
@ -21,18 +20,10 @@ def render() -> None:
) )
BENCHMARK_CYCLES_SLIDER = gradio.Slider( BENCHMARK_CYCLES_SLIDER = gradio.Slider(
label = wording.get('benchmark_cycles_slider_label'), label = wording.get('benchmark_cycles_slider_label'),
minimum = 1,
step = 1,
value = 3, value = 3,
step = 1,
minimum = 1,
maximum = 10 maximum = 10
) )
ui.register_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP) register_ui_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP)
ui.register_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER) register_ui_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER)
def listen() -> None:
BENCHMARK_RUNS_CHECKBOX_GROUP.change(update_benchmark_runs, inputs = BENCHMARK_RUNS_CHECKBOX_GROUP, outputs = BENCHMARK_RUNS_CHECKBOX_GROUP)
def update_benchmark_runs(benchmark_runs : List[str]) -> Update:
return gradio.update(value = benchmark_runs)

View File

@ -0,0 +1,38 @@
from typing import Optional, List
import gradio
import facefusion.globals
from facefusion import wording
from facefusion.uis import choices
COMMON_OPTIONS_CHECKBOX_GROUP : Optional[gradio.Checkboxgroup] = None
def render() -> None:
global COMMON_OPTIONS_CHECKBOX_GROUP
value = []
if facefusion.globals.keep_fps:
value.append('keep-fps')
if facefusion.globals.keep_temp:
value.append('keep-temp')
if facefusion.globals.skip_audio:
value.append('skip-audio')
if facefusion.globals.skip_download:
value.append('skip-download')
COMMON_OPTIONS_CHECKBOX_GROUP = gradio.Checkboxgroup(
label = wording.get('common_options_checkbox_group_label'),
choices = choices.common_options,
value = value
)
def listen() -> None:
COMMON_OPTIONS_CHECKBOX_GROUP.change(update, inputs = COMMON_OPTIONS_CHECKBOX_GROUP)
def update(common_options : List[str]) -> None:
facefusion.globals.keep_fps = 'keep-fps' in common_options
facefusion.globals.keep_temp = 'keep-temp' in common_options
facefusion.globals.skip_audio = 'skip-audio' in common_options
facefusion.globals.skip_download = 'skip-download' in common_options

View File

@ -6,7 +6,6 @@ import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.face_analyser import clear_face_analyser from facefusion.face_analyser import clear_face_analyser
from facefusion.processors.frame.core import clear_frame_processors_modules from facefusion.processors.frame.core import clear_frame_processors_modules
from facefusion.uis.typing import Update
from facefusion.utilities import encode_execution_providers, decode_execution_providers from facefusion.utilities import encode_execution_providers, decode_execution_providers
EXECUTION_PROVIDERS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None EXECUTION_PROVIDERS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
@ -26,10 +25,10 @@ def listen() -> None:
EXECUTION_PROVIDERS_CHECKBOX_GROUP.change(update_execution_providers, inputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP, outputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP) EXECUTION_PROVIDERS_CHECKBOX_GROUP.change(update_execution_providers, inputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP, outputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP)
def update_execution_providers(execution_providers : List[str]) -> Update: def update_execution_providers(execution_providers : List[str]) -> gradio.CheckboxGroup:
clear_face_analyser() clear_face_analyser()
clear_frame_processors_modules() clear_frame_processors_modules()
if not execution_providers: if not execution_providers:
execution_providers = encode_execution_providers(onnxruntime.get_available_providers()) execution_providers = encode_execution_providers(onnxruntime.get_available_providers())
facefusion.globals.execution_providers = decode_execution_providers(execution_providers) facefusion.globals.execution_providers = decode_execution_providers(execution_providers)
return gradio.update(value = execution_providers) return gradio.CheckboxGroup(value = execution_providers)

View File

@ -3,7 +3,6 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.uis.typing import Update
EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None
@ -21,9 +20,9 @@ def render() -> None:
def listen() -> None: def listen() -> None:
EXECUTION_QUEUE_COUNT_SLIDER.change(update_execution_queue_count, inputs = EXECUTION_QUEUE_COUNT_SLIDER, outputs = EXECUTION_QUEUE_COUNT_SLIDER) EXECUTION_QUEUE_COUNT_SLIDER.change(update_execution_queue_count, inputs = EXECUTION_QUEUE_COUNT_SLIDER)
def update_execution_queue_count(execution_queue_count : int = 1) -> Update: def update_execution_queue_count(execution_queue_count : int = 1) -> None:
facefusion.globals.execution_queue_count = execution_queue_count facefusion.globals.execution_queue_count = execution_queue_count
return gradio.update(value = execution_queue_count)

View File

@ -3,7 +3,6 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.uis.typing import Update
EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None
@ -21,9 +20,9 @@ def render() -> None:
def listen() -> None: def listen() -> None:
EXECUTION_THREAD_COUNT_SLIDER.change(update_execution_thread_count, inputs = EXECUTION_THREAD_COUNT_SLIDER, outputs = EXECUTION_THREAD_COUNT_SLIDER) EXECUTION_THREAD_COUNT_SLIDER.change(update_execution_thread_count, inputs = EXECUTION_THREAD_COUNT_SLIDER)
def update_execution_thread_count(execution_thread_count : int = 1) -> Update: def update_execution_thread_count(execution_thread_count : int = 1) -> None:
facefusion.globals.execution_thread_count = execution_thread_count facefusion.globals.execution_thread_count = execution_thread_count
return gradio.update(value = execution_thread_count)

View File

@ -5,8 +5,7 @@ import gradio
import facefusion.choices import facefusion.choices
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.uis import core as ui from facefusion.uis.core import register_ui_component
from facefusion.uis.typing import Update
FACE_ANALYSER_DIRECTION_DROPDOWN : Optional[gradio.Dropdown] = None FACE_ANALYSER_DIRECTION_DROPDOWN : Optional[gradio.Dropdown] = None
FACE_ANALYSER_AGE_DROPDOWN : Optional[gradio.Dropdown] = None FACE_ANALYSER_AGE_DROPDOWN : Optional[gradio.Dropdown] = None
@ -20,33 +19,32 @@ def render() -> None:
FACE_ANALYSER_DIRECTION_DROPDOWN = gradio.Dropdown( FACE_ANALYSER_DIRECTION_DROPDOWN = gradio.Dropdown(
label = wording.get('face_analyser_direction_dropdown_label'), label = wording.get('face_analyser_direction_dropdown_label'),
choices = facefusion.choices.face_analyser_direction, choices = facefusion.choices.face_analyser_directions,
value = facefusion.globals.face_analyser_direction value = facefusion.globals.face_analyser_direction
) )
FACE_ANALYSER_AGE_DROPDOWN = gradio.Dropdown( FACE_ANALYSER_AGE_DROPDOWN = gradio.Dropdown(
label = wording.get('face_analyser_age_dropdown_label'), label = wording.get('face_analyser_age_dropdown_label'),
choices = ['none'] + facefusion.choices.face_analyser_age, choices = ['none'] + facefusion.choices.face_analyser_ages,
value = facefusion.globals.face_analyser_age or 'none' value = facefusion.globals.face_analyser_age or 'none'
) )
FACE_ANALYSER_GENDER_DROPDOWN = gradio.Dropdown( FACE_ANALYSER_GENDER_DROPDOWN = gradio.Dropdown(
label = wording.get('face_analyser_gender_dropdown_label'), label = wording.get('face_analyser_gender_dropdown_label'),
choices = ['none'] + facefusion.choices.face_analyser_gender, choices = ['none'] + facefusion.choices.face_analyser_genders,
value = facefusion.globals.face_analyser_gender or 'none' value = facefusion.globals.face_analyser_gender or 'none'
) )
ui.register_component('face_analyser_direction_dropdown', FACE_ANALYSER_DIRECTION_DROPDOWN) register_ui_component('face_analyser_direction_dropdown', FACE_ANALYSER_DIRECTION_DROPDOWN)
ui.register_component('face_analyser_age_dropdown', FACE_ANALYSER_AGE_DROPDOWN) register_ui_component('face_analyser_age_dropdown', FACE_ANALYSER_AGE_DROPDOWN)
ui.register_component('face_analyser_gender_dropdown', FACE_ANALYSER_GENDER_DROPDOWN) register_ui_component('face_analyser_gender_dropdown', FACE_ANALYSER_GENDER_DROPDOWN)
def listen() -> None: def listen() -> None:
FACE_ANALYSER_DIRECTION_DROPDOWN.select(lambda value: update_dropdown('face_analyser_direction', value), inputs = FACE_ANALYSER_DIRECTION_DROPDOWN, outputs = FACE_ANALYSER_DIRECTION_DROPDOWN) FACE_ANALYSER_DIRECTION_DROPDOWN.select(lambda value: update_dropdown('face_analyser_direction', value), inputs = FACE_ANALYSER_DIRECTION_DROPDOWN)
FACE_ANALYSER_AGE_DROPDOWN.select(lambda value: update_dropdown('face_analyser_age', value), inputs = FACE_ANALYSER_AGE_DROPDOWN, outputs = FACE_ANALYSER_AGE_DROPDOWN) FACE_ANALYSER_AGE_DROPDOWN.select(lambda value: update_dropdown('face_analyser_age', value), inputs = FACE_ANALYSER_AGE_DROPDOWN)
FACE_ANALYSER_GENDER_DROPDOWN.select(lambda value: update_dropdown('face_analyser_gender', value), inputs = FACE_ANALYSER_GENDER_DROPDOWN, outputs = FACE_ANALYSER_GENDER_DROPDOWN) FACE_ANALYSER_GENDER_DROPDOWN.select(lambda value: update_dropdown('face_analyser_gender', value), inputs = FACE_ANALYSER_GENDER_DROPDOWN)
def update_dropdown(name : str, value : str) -> Update: def update_dropdown(name : str, value : str) -> None:
if value == 'none': if value == 'none':
setattr(facefusion.globals, name, None) setattr(facefusion.globals, name, None)
else: else:
setattr(facefusion.globals, name, value) setattr(facefusion.globals, name, value)
return gradio.update(value = value)

View File

@ -9,9 +9,9 @@ from facefusion.vision import get_video_frame, normalize_frame_color, read_stati
from facefusion.face_analyser import get_many_faces from facefusion.face_analyser import get_many_faces
from facefusion.face_reference import clear_face_reference from facefusion.face_reference import clear_face_reference
from facefusion.typing import Frame, FaceRecognition from facefusion.typing import Frame, FaceRecognition
from facefusion.uis import core as ui
from facefusion.uis.typing import ComponentName, Update
from facefusion.utilities import is_image, is_video from facefusion.utilities import is_image, is_video
from facefusion.uis.core import get_ui_component, register_ui_component
from facefusion.uis.typing import ComponentName
FACE_RECOGNITION_DROPDOWN : Optional[gradio.Dropdown] = None FACE_RECOGNITION_DROPDOWN : Optional[gradio.Dropdown] = None
REFERENCE_FACE_POSITION_GALLERY : Optional[gradio.Gallery] = None REFERENCE_FACE_POSITION_GALLERY : Optional[gradio.Gallery] = None
@ -40,20 +40,21 @@ def render() -> None:
reference_face_gallery_args['value'] = extract_gallery_frames(reference_frame) reference_face_gallery_args['value'] = extract_gallery_frames(reference_frame)
FACE_RECOGNITION_DROPDOWN = gradio.Dropdown( FACE_RECOGNITION_DROPDOWN = gradio.Dropdown(
label = wording.get('face_recognition_dropdown_label'), label = wording.get('face_recognition_dropdown_label'),
choices = facefusion.choices.face_recognition, choices = facefusion.choices.face_recognitions,
value = facefusion.globals.face_recognition value = facefusion.globals.face_recognition
) )
REFERENCE_FACE_POSITION_GALLERY = gradio.Gallery(**reference_face_gallery_args) REFERENCE_FACE_POSITION_GALLERY = gradio.Gallery(**reference_face_gallery_args)
REFERENCE_FACE_DISTANCE_SLIDER = gradio.Slider( REFERENCE_FACE_DISTANCE_SLIDER = gradio.Slider(
label = wording.get('reference_face_distance_slider_label'), label = wording.get('reference_face_distance_slider_label'),
value = facefusion.globals.reference_face_distance, value = facefusion.globals.reference_face_distance,
maximum = 3,
step = 0.05, step = 0.05,
minimum = 0,
maximum = 3,
visible = 'reference' in facefusion.globals.face_recognition visible = 'reference' in facefusion.globals.face_recognition
) )
ui.register_component('face_recognition_dropdown', FACE_RECOGNITION_DROPDOWN) register_ui_component('face_recognition_dropdown', FACE_RECOGNITION_DROPDOWN)
ui.register_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY) register_ui_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY)
ui.register_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER) register_ui_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER)
def listen() -> None: def listen() -> None:
@ -67,7 +68,7 @@ def listen() -> None:
'target_video' 'target_video'
] ]
for component_name in multi_component_names: for component_name in multi_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(component, method)(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY) getattr(component, method)(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
@ -78,29 +79,29 @@ def listen() -> None:
'face_analyser_gender_dropdown' 'face_analyser_gender_dropdown'
] ]
for component_name in select_component_names: for component_name in select_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
component.select(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY) component.select(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
preview_frame_slider = ui.get_component('preview_frame_slider') preview_frame_slider = get_ui_component('preview_frame_slider')
if preview_frame_slider: if preview_frame_slider:
preview_frame_slider.release(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY) preview_frame_slider.release(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY)
def update_face_recognition(face_recognition : FaceRecognition) -> Tuple[Update, Update]: def update_face_recognition(face_recognition : FaceRecognition) -> Tuple[gradio.Gallery, gradio.Slider]:
if face_recognition == 'reference': if face_recognition == 'reference':
facefusion.globals.face_recognition = face_recognition facefusion.globals.face_recognition = face_recognition
return gradio.update(visible = True), gradio.update(visible = True) return gradio.Gallery(visible = True), gradio.Slider(visible = True)
if face_recognition == 'many': if face_recognition == 'many':
facefusion.globals.face_recognition = face_recognition facefusion.globals.face_recognition = face_recognition
return gradio.update(visible = False), gradio.update(visible = False) return gradio.Gallery(visible = False), gradio.Slider(visible = False)
def clear_and_update_face_reference_position(event: gradio.SelectData) -> Update: def clear_and_update_face_reference_position(event: gradio.SelectData) -> gradio.Gallery:
clear_face_reference() clear_face_reference()
return update_face_reference_position(event.index) return update_face_reference_position(event.index)
def update_face_reference_position(reference_face_position : int = 0) -> Update: def update_face_reference_position(reference_face_position : int = 0) -> gradio.Gallery:
gallery_frames = [] gallery_frames = []
facefusion.globals.reference_face_position = reference_face_position facefusion.globals.reference_face_position = reference_face_position
if is_image(facefusion.globals.target_path): if is_image(facefusion.globals.target_path):
@ -110,13 +111,12 @@ def update_face_reference_position(reference_face_position : int = 0) -> Update:
reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number) reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
gallery_frames = extract_gallery_frames(reference_frame) gallery_frames = extract_gallery_frames(reference_frame)
if gallery_frames: if gallery_frames:
return gradio.update(value = gallery_frames) return gradio.Gallery(value = gallery_frames)
return gradio.update(value = None) return gradio.Gallery(value = None)
def update_reference_face_distance(reference_face_distance : float) -> Update: def update_reference_face_distance(reference_face_distance : float) -> None:
facefusion.globals.reference_face_distance = reference_face_distance facefusion.globals.reference_face_distance = reference_face_distance
return gradio.update(value = reference_face_distance)
def extract_gallery_frames(reference_frame : Frame) -> List[Frame]: def extract_gallery_frames(reference_frame : Frame) -> List[Frame]:

View File

@ -4,9 +4,8 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.processors.frame.core import load_frame_processor_module, clear_frame_processors_modules from facefusion.processors.frame.core import load_frame_processor_module, clear_frame_processors_modules
from facefusion.uis import core as ui
from facefusion.uis.typing import Update
from facefusion.utilities import list_module_names from facefusion.utilities import list_module_names
from facefusion.uis.core import register_ui_component
FRAME_PROCESSORS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None FRAME_PROCESSORS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
@ -19,23 +18,23 @@ def render() -> None:
choices = sort_frame_processors(facefusion.globals.frame_processors), choices = sort_frame_processors(facefusion.globals.frame_processors),
value = facefusion.globals.frame_processors value = facefusion.globals.frame_processors
) )
ui.register_component('frame_processors_checkbox_group', FRAME_PROCESSORS_CHECKBOX_GROUP) register_ui_component('frame_processors_checkbox_group', FRAME_PROCESSORS_CHECKBOX_GROUP)
def listen() -> None: def listen() -> None:
FRAME_PROCESSORS_CHECKBOX_GROUP.change(update_frame_processors, inputs = FRAME_PROCESSORS_CHECKBOX_GROUP, outputs = FRAME_PROCESSORS_CHECKBOX_GROUP) FRAME_PROCESSORS_CHECKBOX_GROUP.change(update_frame_processors, inputs = FRAME_PROCESSORS_CHECKBOX_GROUP, outputs = FRAME_PROCESSORS_CHECKBOX_GROUP)
def update_frame_processors(frame_processors : List[str]) -> Update: def update_frame_processors(frame_processors : List[str]) -> gradio.CheckboxGroup:
clear_frame_processors_modules()
facefusion.globals.frame_processors = frame_processors facefusion.globals.frame_processors = frame_processors
clear_frame_processors_modules()
for frame_processor in frame_processors: for frame_processor in frame_processors:
frame_processor_module = load_frame_processor_module(frame_processor) frame_processor_module = load_frame_processor_module(frame_processor)
if not frame_processor_module.pre_check(): if not frame_processor_module.pre_check():
return gradio.update() return gradio.CheckboxGroup()
return gradio.update(value = frame_processors, choices = sort_frame_processors(frame_processors)) return gradio.CheckboxGroup(value = frame_processors, choices = sort_frame_processors(frame_processors))
def sort_frame_processors(frame_processors : List[str]) -> list[str]: def sort_frame_processors(frame_processors : List[str]) -> list[str]:
frame_processors_names = list_module_names('facefusion/processors/frame/modules') available_frame_processors = list_module_names('facefusion/processors/frame/modules')
return sorted(frame_processors_names, key = lambda frame_processor : frame_processors.index(frame_processor) if frame_processor in frame_processors else len(frame_processors)) return sorted(available_frame_processors, key = lambda frame_processor : frame_processors.index(frame_processor) if frame_processor in frame_processors else len(frame_processors))

View File

@ -0,0 +1,118 @@
from typing import List, Optional, Tuple
import gradio
import facefusion.globals
from facefusion import wording
from facefusion.processors.frame.core import load_frame_processor_module
from facefusion.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices
from facefusion.uis.core import get_ui_component, register_ui_component
FACE_SWAPPER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
FACE_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
FACE_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None
FRAME_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
FRAME_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None
def render() -> None:
global FACE_SWAPPER_MODEL_DROPDOWN
global FACE_ENHANCER_MODEL_DROPDOWN
global FACE_ENHANCER_BLEND_SLIDER
global FRAME_ENHANCER_MODEL_DROPDOWN
global FRAME_ENHANCER_BLEND_SLIDER
FACE_SWAPPER_MODEL_DROPDOWN = gradio.Dropdown(
label = wording.get('face_swapper_model_dropdown_label'),
choices = frame_processors_choices.face_swapper_models,
value = frame_processors_globals.face_swapper_model,
visible = 'face_swapper' in facefusion.globals.frame_processors
)
FACE_ENHANCER_MODEL_DROPDOWN = gradio.Dropdown(
label = wording.get('face_enhancer_model_dropdown_label'),
choices = frame_processors_choices.face_enhancer_models,
value = frame_processors_globals.face_enhancer_model,
visible = 'face_enhancer' in facefusion.globals.frame_processors
)
FACE_ENHANCER_BLEND_SLIDER = gradio.Slider(
label = wording.get('face_enhancer_blend_slider_label'),
value = frame_processors_globals.face_enhancer_blend,
step = 1,
minimum = 0,
maximum = 100,
visible = 'face_enhancer' in facefusion.globals.frame_processors
)
FRAME_ENHANCER_MODEL_DROPDOWN = gradio.Dropdown(
label = wording.get('frame_enhancer_model_dropdown_label'),
choices = frame_processors_choices.frame_enhancer_models,
value = frame_processors_globals.frame_enhancer_model,
visible = 'frame_enhancer' in facefusion.globals.frame_processors
)
FRAME_ENHANCER_BLEND_SLIDER = gradio.Slider(
label = wording.get('frame_enhancer_blend_slider_label'),
value = frame_processors_globals.frame_enhancer_blend,
step = 1,
minimum = 0,
maximum = 100,
visible = 'face_enhancer' in facefusion.globals.frame_processors
)
register_ui_component('face_swapper_model_dropdown', FACE_SWAPPER_MODEL_DROPDOWN)
register_ui_component('face_enhancer_model_dropdown', FACE_ENHANCER_MODEL_DROPDOWN)
register_ui_component('face_enhancer_blend_slider', FACE_ENHANCER_BLEND_SLIDER)
register_ui_component('frame_enhancer_model_dropdown', FRAME_ENHANCER_MODEL_DROPDOWN)
register_ui_component('frame_enhancer_blend_slider', FRAME_ENHANCER_BLEND_SLIDER)
def listen() -> None:
FACE_SWAPPER_MODEL_DROPDOWN.change(update_face_swapper_model, inputs = FACE_SWAPPER_MODEL_DROPDOWN, outputs = FACE_SWAPPER_MODEL_DROPDOWN)
FACE_ENHANCER_MODEL_DROPDOWN.change(update_face_enhancer_model, inputs = FACE_ENHANCER_MODEL_DROPDOWN, outputs = FACE_ENHANCER_MODEL_DROPDOWN)
FACE_ENHANCER_BLEND_SLIDER.change(update_face_enhancer_blend, inputs = FACE_ENHANCER_BLEND_SLIDER)
FRAME_ENHANCER_MODEL_DROPDOWN.change(update_frame_enhancer_model, inputs = FRAME_ENHANCER_MODEL_DROPDOWN, outputs = FRAME_ENHANCER_MODEL_DROPDOWN)
FRAME_ENHANCER_BLEND_SLIDER.change(update_frame_enhancer_blend, inputs = FRAME_ENHANCER_BLEND_SLIDER)
frame_processors_checkbox_group = get_ui_component('frame_processors_checkbox_group')
if frame_processors_checkbox_group:
frame_processors_checkbox_group.change(toggle_face_swapper_model, inputs = frame_processors_checkbox_group, outputs = [ FACE_SWAPPER_MODEL_DROPDOWN, FACE_ENHANCER_MODEL_DROPDOWN, FACE_ENHANCER_BLEND_SLIDER, FRAME_ENHANCER_MODEL_DROPDOWN, FRAME_ENHANCER_BLEND_SLIDER ])
def update_face_swapper_model(face_swapper_model : str) -> gradio.Dropdown:
frame_processors_globals.face_swapper_model = face_swapper_model
face_swapper_module = load_frame_processor_module('face_swapper')
face_swapper_module.clear_frame_processor()
face_swapper_module.set_options('model', face_swapper_module.MODELS[face_swapper_model])
if not face_swapper_module.pre_check():
return gradio.Dropdown()
return gradio.Dropdown(value = face_swapper_model)
def update_face_enhancer_model(face_enhancer_model : str) -> gradio.Dropdown:
frame_processors_globals.face_enhancer_model = face_enhancer_model
face_enhancer_module = load_frame_processor_module('face_enhancer')
face_enhancer_module.clear_frame_processor()
face_enhancer_module.set_options('model', face_enhancer_module.MODELS[face_enhancer_model])
if not face_enhancer_module.pre_check():
return gradio.Dropdown()
return gradio.Dropdown(value = face_enhancer_model)
def update_face_enhancer_blend(face_enhancer_blend : int) -> None:
frame_processors_globals.face_enhancer_blend = face_enhancer_blend
def update_frame_enhancer_model(frame_enhancer_model : str) -> gradio.Dropdown:
frame_processors_globals.frame_enhancer_model = frame_enhancer_model
frame_enhancer_module = load_frame_processor_module('frame_enhancer')
frame_enhancer_module.clear_frame_processor()
frame_enhancer_module.set_options('model', frame_enhancer_module.MODELS[frame_enhancer_model])
if not frame_enhancer_module.pre_check():
return gradio.Dropdown()
return gradio.Dropdown(value = frame_enhancer_model)
def update_frame_enhancer_blend(frame_enhancer_blend : int) -> None:
frame_processors_globals.frame_enhancer_blend = frame_enhancer_blend
def toggle_face_swapper_model(frame_processors : List[str]) -> Tuple[gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Slider]:
has_face_swapper = 'face_swapper' in frame_processors
has_face_enhancer = 'face_enhancer' in frame_processors
has_frame_enhancer = 'frame_enhancer' in frame_processors
return gradio.Dropdown(visible = has_face_swapper), gradio.Dropdown(visible = has_face_enhancer), gradio.Slider(visible = has_face_enhancer), gradio.Dropdown(visible = has_frame_enhancer), gradio.Slider(visible = has_frame_enhancer)

View File

@ -3,7 +3,6 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.uis.typing import Update
MAX_MEMORY_SLIDER : Optional[gradio.Slider] = None MAX_MEMORY_SLIDER : Optional[gradio.Slider] = None
@ -13,16 +12,15 @@ def render() -> None:
MAX_MEMORY_SLIDER = gradio.Slider( MAX_MEMORY_SLIDER = gradio.Slider(
label = wording.get('max_memory_slider_label'), label = wording.get('max_memory_slider_label'),
step = 1,
minimum = 0, minimum = 0,
maximum = 128, maximum = 128
step = 1
) )
def listen() -> None: def listen() -> None:
MAX_MEMORY_SLIDER.change(update_max_memory, inputs = MAX_MEMORY_SLIDER, outputs = MAX_MEMORY_SLIDER) MAX_MEMORY_SLIDER.change(update_max_memory, inputs = MAX_MEMORY_SLIDER)
def update_max_memory(max_memory : int) -> Update: def update_max_memory(max_memory : int) -> None:
facefusion.globals.max_memory = max_memory if max_memory > 0 else None facefusion.globals.max_memory = max_memory if max_memory > 0 else None
return gradio.update(value = max_memory)

View File

@ -1,16 +1,14 @@
import tempfile
from typing import Tuple, Optional from typing import Tuple, Optional
import gradio import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.core import limit_resources, conditional_process from facefusion.core import limit_resources, conditional_process
from facefusion.uis.typing import Update from facefusion.uis.core import get_ui_component
from facefusion.utilities import is_image, is_video, normalize_output_path, clear_temp from facefusion.utilities import is_image, is_video, normalize_output_path, clear_temp
OUTPUT_IMAGE : Optional[gradio.Image] = None OUTPUT_IMAGE : Optional[gradio.Image] = None
OUTPUT_VIDEO : Optional[gradio.Video] = None OUTPUT_VIDEO : Optional[gradio.Video] = None
OUTPUT_PATH_TEXTBOX : Optional[gradio.Textbox] = None
OUTPUT_START_BUTTON : Optional[gradio.Button] = None OUTPUT_START_BUTTON : Optional[gradio.Button] = None
OUTPUT_CLEAR_BUTTON : Optional[gradio.Button] = None OUTPUT_CLEAR_BUTTON : Optional[gradio.Button] = None
@ -18,7 +16,6 @@ OUTPUT_CLEAR_BUTTON : Optional[gradio.Button] = None
def render() -> None: def render() -> None:
global OUTPUT_IMAGE global OUTPUT_IMAGE
global OUTPUT_VIDEO global OUTPUT_VIDEO
global OUTPUT_PATH_TEXTBOX
global OUTPUT_START_BUTTON global OUTPUT_START_BUTTON
global OUTPUT_CLEAR_BUTTON global OUTPUT_CLEAR_BUTTON
@ -29,43 +26,36 @@ def render() -> None:
OUTPUT_VIDEO = gradio.Video( OUTPUT_VIDEO = gradio.Video(
label = wording.get('output_image_or_video_label') label = wording.get('output_image_or_video_label')
) )
OUTPUT_PATH_TEXTBOX = gradio.Textbox(
label = wording.get('output_path_textbox_label'),
value = facefusion.globals.output_path or tempfile.gettempdir(),
max_lines = 1
)
OUTPUT_START_BUTTON = gradio.Button( OUTPUT_START_BUTTON = gradio.Button(
value = wording.get('start_button_label'), value = wording.get('start_button_label'),
variant = 'primary' variant = 'primary',
size = 'sm'
) )
OUTPUT_CLEAR_BUTTON = gradio.Button( OUTPUT_CLEAR_BUTTON = gradio.Button(
value = wording.get('clear_button_label'), value = wording.get('clear_button_label'),
size = 'sm'
) )
def listen() -> None: def listen() -> None:
OUTPUT_PATH_TEXTBOX.change(update_output_path, inputs = OUTPUT_PATH_TEXTBOX, outputs = OUTPUT_PATH_TEXTBOX) output_path_textbox = get_ui_component('output_path_textbox')
OUTPUT_START_BUTTON.click(start, inputs = OUTPUT_PATH_TEXTBOX, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ]) if output_path_textbox:
OUTPUT_START_BUTTON.click(start, inputs = output_path_textbox, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ])
OUTPUT_CLEAR_BUTTON.click(clear, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ]) OUTPUT_CLEAR_BUTTON.click(clear, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ])
def start(output_path : str) -> Tuple[Update, Update]: def start(output_path : str) -> Tuple[gradio.Image, gradio.Video]:
facefusion.globals.output_path = normalize_output_path(facefusion.globals.source_path, facefusion.globals.target_path, output_path) facefusion.globals.output_path = normalize_output_path(facefusion.globals.source_path, facefusion.globals.target_path, output_path)
limit_resources() limit_resources()
conditional_process() conditional_process()
if is_image(facefusion.globals.output_path): if is_image(facefusion.globals.output_path):
return gradio.update(value = facefusion.globals.output_path, visible = True), gradio.update(value = None, visible = False) return gradio.Image(value = facefusion.globals.output_path, visible = True), gradio.Video(value = None, visible = False)
if is_video(facefusion.globals.output_path): if is_video(facefusion.globals.output_path):
return gradio.update(value = None, visible = False), gradio.update(value = facefusion.globals.output_path, visible = True) return gradio.Image(value = None, visible = False), gradio.Video(value = facefusion.globals.output_path, visible = True)
return gradio.update(), gradio.update() return gradio.Image(), gradio.Video()
def update_output_path(output_path : str) -> Update: def clear() -> Tuple[gradio.Image, gradio.Video]:
facefusion.globals.output_path = output_path
return gradio.update(value = output_path)
def clear() -> Tuple[Update, Update]:
if facefusion.globals.target_path: if facefusion.globals.target_path:
clear_temp(facefusion.globals.target_path) clear_temp(facefusion.globals.target_path)
return gradio.update(value = None), gradio.update(value = None) return gradio.Image(value = None), gradio.Video(value = None)

View File

@ -1,33 +1,43 @@
from typing import Optional, Tuple, List from typing import Optional, Tuple, List
import tempfile
import gradio import gradio
import facefusion.choices import facefusion.choices
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.typing import OutputVideoEncoder from facefusion.typing import OutputVideoEncoder
from facefusion.uis import core as ui
from facefusion.uis.typing import Update, ComponentName
from facefusion.utilities import is_image, is_video from facefusion.utilities import is_image, is_video
from facefusion.uis.typing import ComponentName
from facefusion.uis.core import get_ui_component, register_ui_component
OUTPUT_PATH_TEXTBOX : Optional[gradio.Textbox] = None
OUTPUT_IMAGE_QUALITY_SLIDER : Optional[gradio.Slider] = None OUTPUT_IMAGE_QUALITY_SLIDER : Optional[gradio.Slider] = None
OUTPUT_VIDEO_ENCODER_DROPDOWN : Optional[gradio.Dropdown] = None OUTPUT_VIDEO_ENCODER_DROPDOWN : Optional[gradio.Dropdown] = None
OUTPUT_VIDEO_QUALITY_SLIDER : Optional[gradio.Slider] = None OUTPUT_VIDEO_QUALITY_SLIDER : Optional[gradio.Slider] = None
def render() -> None: def render() -> None:
global OUTPUT_PATH_TEXTBOX
global OUTPUT_IMAGE_QUALITY_SLIDER global OUTPUT_IMAGE_QUALITY_SLIDER
global OUTPUT_VIDEO_ENCODER_DROPDOWN global OUTPUT_VIDEO_ENCODER_DROPDOWN
global OUTPUT_VIDEO_QUALITY_SLIDER global OUTPUT_VIDEO_QUALITY_SLIDER
OUTPUT_PATH_TEXTBOX = gradio.Textbox(
label = wording.get('output_path_textbox_label'),
value = facefusion.globals.output_path or tempfile.gettempdir(),
max_lines = 1
)
OUTPUT_IMAGE_QUALITY_SLIDER = gradio.Slider( OUTPUT_IMAGE_QUALITY_SLIDER = gradio.Slider(
label = wording.get('output_image_quality_slider_label'), label = wording.get('output_image_quality_slider_label'),
value = facefusion.globals.output_image_quality, value = facefusion.globals.output_image_quality,
step = 1, step = 1,
minimum = 0,
maximum = 100,
visible = is_image(facefusion.globals.target_path) visible = is_image(facefusion.globals.target_path)
) )
OUTPUT_VIDEO_ENCODER_DROPDOWN = gradio.Dropdown( OUTPUT_VIDEO_ENCODER_DROPDOWN = gradio.Dropdown(
label = wording.get('output_video_encoder_dropdown_label'), label = wording.get('output_video_encoder_dropdown_label'),
choices = facefusion.choices.output_video_encoder, choices = facefusion.choices.output_video_encoders,
value = facefusion.globals.output_video_encoder, value = facefusion.globals.output_video_encoder,
visible = is_video(facefusion.globals.target_path) visible = is_video(facefusion.globals.target_path)
) )
@ -35,14 +45,18 @@ def render() -> None:
label = wording.get('output_video_quality_slider_label'), label = wording.get('output_video_quality_slider_label'),
value = facefusion.globals.output_video_quality, value = facefusion.globals.output_video_quality,
step = 1, step = 1,
minimum = 0,
maximum = 100,
visible = is_video(facefusion.globals.target_path) visible = is_video(facefusion.globals.target_path)
) )
register_ui_component('output_path_textbox', OUTPUT_PATH_TEXTBOX)
def listen() -> None: def listen() -> None:
OUTPUT_IMAGE_QUALITY_SLIDER.change(update_output_image_quality, inputs = OUTPUT_IMAGE_QUALITY_SLIDER, outputs = OUTPUT_IMAGE_QUALITY_SLIDER) OUTPUT_PATH_TEXTBOX.change(update_output_path, inputs = OUTPUT_PATH_TEXTBOX)
OUTPUT_VIDEO_ENCODER_DROPDOWN.select(update_output_video_encoder, inputs = OUTPUT_VIDEO_ENCODER_DROPDOWN, outputs = OUTPUT_VIDEO_ENCODER_DROPDOWN) OUTPUT_IMAGE_QUALITY_SLIDER.change(update_output_image_quality, inputs = OUTPUT_IMAGE_QUALITY_SLIDER)
OUTPUT_VIDEO_QUALITY_SLIDER.change(update_output_video_quality, inputs = OUTPUT_VIDEO_QUALITY_SLIDER, outputs = OUTPUT_VIDEO_QUALITY_SLIDER) OUTPUT_VIDEO_ENCODER_DROPDOWN.select(update_output_video_encoder, inputs = OUTPUT_VIDEO_ENCODER_DROPDOWN)
OUTPUT_VIDEO_QUALITY_SLIDER.change(update_output_video_quality, inputs = OUTPUT_VIDEO_QUALITY_SLIDER)
multi_component_names : List[ComponentName] =\ multi_component_names : List[ComponentName] =\
[ [
'source_image', 'source_image',
@ -50,30 +64,31 @@ def listen() -> None:
'target_video' 'target_video'
] ]
for component_name in multi_component_names: for component_name in multi_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(component, method)(remote_update, outputs = [ OUTPUT_IMAGE_QUALITY_SLIDER, OUTPUT_VIDEO_ENCODER_DROPDOWN, OUTPUT_VIDEO_QUALITY_SLIDER ]) getattr(component, method)(remote_update, outputs = [ OUTPUT_IMAGE_QUALITY_SLIDER, OUTPUT_VIDEO_ENCODER_DROPDOWN, OUTPUT_VIDEO_QUALITY_SLIDER ])
def remote_update() -> Tuple[Update, Update, Update]: def remote_update() -> Tuple[gradio.Slider, gradio.Dropdown, gradio.Slider]:
if is_image(facefusion.globals.target_path): if is_image(facefusion.globals.target_path):
return gradio.update(visible = True), gradio.update(visible = False), gradio.update(visible = False) return gradio.Slider(visible = True), gradio.Dropdown(visible = False), gradio.Slider(visible = False)
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
return gradio.update(visible = False), gradio.update(visible = True), gradio.update(visible = True) return gradio.Slider(visible = False), gradio.Dropdown(visible = True), gradio.Slider(visible = True)
return gradio.update(visible = False), gradio.update(visible = False), gradio.update(visible = False) return gradio.Slider(visible = False), gradio.Dropdown(visible = False), gradio.Slider(visible = False)
def update_output_image_quality(output_image_quality : int) -> Update: def update_output_path(output_path : str) -> None:
facefusion.globals.output_path = output_path
def update_output_image_quality(output_image_quality : int) -> None:
facefusion.globals.output_image_quality = output_image_quality facefusion.globals.output_image_quality = output_image_quality
return gradio.update(value = output_image_quality)
def update_output_video_encoder(output_video_encoder: OutputVideoEncoder) -> Update: def update_output_video_encoder(output_video_encoder: OutputVideoEncoder) -> None:
facefusion.globals.output_video_encoder = output_video_encoder facefusion.globals.output_video_encoder = output_video_encoder
return gradio.update(value = output_video_encoder)
def update_output_video_quality(output_video_quality : int) -> Update: def update_output_video_quality(output_video_quality : int) -> None:
facefusion.globals.output_video_quality = output_video_quality facefusion.globals.output_video_quality = output_video_quality
return gradio.update(value = output_video_quality)

View File

@ -4,15 +4,15 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.typing import Frame, Face
from facefusion.vision import get_video_frame, count_video_frame_total, normalize_frame_color, resize_frame_dimension, read_static_image from facefusion.vision import get_video_frame, count_video_frame_total, normalize_frame_color, resize_frame_dimension, read_static_image
from facefusion.face_analyser import get_one_face from facefusion.face_analyser import get_one_face
from facefusion.face_reference import get_face_reference, set_face_reference from facefusion.face_reference import get_face_reference, set_face_reference
from facefusion.predictor import predict_frame from facefusion.predictor import predict_frame
from facefusion.processors.frame.core import load_frame_processor_module from facefusion.processors.frame.core import load_frame_processor_module
from facefusion.typing import Frame, Face
from facefusion.uis import core as ui
from facefusion.uis.typing import ComponentName, Update
from facefusion.utilities import is_video, is_image from facefusion.utilities import is_video, is_image
from facefusion.uis.typing import ComponentName
from facefusion.uis.core import get_ui_component, register_ui_component
PREVIEW_IMAGE : Optional[gradio.Image] = None PREVIEW_IMAGE : Optional[gradio.Image] = None
PREVIEW_FRAME_SLIDER : Optional[gradio.Slider] = None PREVIEW_FRAME_SLIDER : Optional[gradio.Slider] = None
@ -24,12 +24,15 @@ def render() -> None:
preview_image_args: Dict[str, Any] =\ preview_image_args: Dict[str, Any] =\
{ {
'label': wording.get('preview_image_label') 'label': wording.get('preview_image_label'),
'interactive': False
} }
preview_frame_slider_args: Dict[str, Any] =\ preview_frame_slider_args: Dict[str, Any] =\
{ {
'label': wording.get('preview_frame_slider_label'), 'label': wording.get('preview_frame_slider_label'),
'step': 1, 'step': 1,
'minimum': 0,
'maximum': 100,
'visible': False 'visible': False
} }
conditional_set_face_reference() conditional_set_face_reference()
@ -49,7 +52,7 @@ def render() -> None:
preview_frame_slider_args['visible'] = True preview_frame_slider_args['visible'] = True
PREVIEW_IMAGE = gradio.Image(**preview_image_args) PREVIEW_IMAGE = gradio.Image(**preview_image_args)
PREVIEW_FRAME_SLIDER = gradio.Slider(**preview_frame_slider_args) PREVIEW_FRAME_SLIDER = gradio.Slider(**preview_frame_slider_args)
ui.register_component('preview_frame_slider', PREVIEW_FRAME_SLIDER) register_ui_component('preview_frame_slider', PREVIEW_FRAME_SLIDER)
def listen() -> None: def listen() -> None:
@ -61,7 +64,7 @@ def listen() -> None:
'target_video' 'target_video'
] ]
for component_name in multi_component_names: for component_name in multi_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(component, method)(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) getattr(component, method)(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
@ -69,10 +72,13 @@ def listen() -> None:
update_component_names : List[ComponentName] =\ update_component_names : List[ComponentName] =\
[ [
'face_recognition_dropdown', 'face_recognition_dropdown',
'frame_processors_checkbox_group' 'frame_processors_checkbox_group',
'face_swapper_model_dropdown',
'face_enhancer_model_dropdown',
'frame_enhancer_model_dropdown'
] ]
for component_name in update_component_names: for component_name in update_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
select_component_names : List[ComponentName] =\ select_component_names : List[ComponentName] =\
@ -83,15 +89,22 @@ def listen() -> None:
'face_analyser_gender_dropdown' 'face_analyser_gender_dropdown'
] ]
for component_name in select_component_names: for component_name in select_component_names:
component = ui.get_component(component_name) component = get_ui_component(component_name)
if component: if component:
component.select(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) component.select(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
reference_face_distance_slider = ui.get_component('reference_face_distance_slider') change_component_names : List[ComponentName] =\
if reference_face_distance_slider: [
reference_face_distance_slider.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) 'reference_face_distance_slider',
'face_enhancer_blend_slider',
'frame_enhancer_blend_slider'
]
for component_name in change_component_names:
component = get_ui_component(component_name)
if component:
component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
def update_preview_image(frame_number : int = 0) -> Update: def update_preview_image(frame_number : int = 0) -> gradio.Image:
conditional_set_face_reference() conditional_set_face_reference()
source_face = get_one_face(read_static_image(facefusion.globals.source_path)) source_face = get_one_face(read_static_image(facefusion.globals.source_path))
reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None
@ -99,30 +112,30 @@ def update_preview_image(frame_number : int = 0) -> Update:
target_frame = read_static_image(facefusion.globals.target_path) target_frame = read_static_image(facefusion.globals.target_path)
preview_frame = process_preview_frame(source_face, reference_face, target_frame) preview_frame = process_preview_frame(source_face, reference_face, target_frame)
preview_frame = normalize_frame_color(preview_frame) preview_frame = normalize_frame_color(preview_frame)
return gradio.update(value = preview_frame) return gradio.Image(value = preview_frame)
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
facefusion.globals.reference_frame_number = frame_number facefusion.globals.reference_frame_number = frame_number
temp_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number) temp_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
preview_frame = process_preview_frame(source_face, reference_face, temp_frame) preview_frame = process_preview_frame(source_face, reference_face, temp_frame)
preview_frame = normalize_frame_color(preview_frame) preview_frame = normalize_frame_color(preview_frame)
return gradio.update(value = preview_frame) return gradio.Image(value = preview_frame)
return gradio.update(value = None) return gradio.Image(value = None)
def update_preview_frame_slider(frame_number : int = 0) -> Update: def update_preview_frame_slider(frame_number : int = 0) -> gradio.Slider:
if is_image(facefusion.globals.target_path): if is_image(facefusion.globals.target_path):
return gradio.update(value = None, maximum = None, visible = False) return gradio.Slider(value = None, maximum = None, visible = False)
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
facefusion.globals.reference_frame_number = frame_number facefusion.globals.reference_frame_number = frame_number
video_frame_total = count_video_frame_total(facefusion.globals.target_path) video_frame_total = count_video_frame_total(facefusion.globals.target_path)
return gradio.update(maximum = video_frame_total, visible = True) return gradio.Slider(maximum = video_frame_total, visible = True)
return gradio.update(value = None, maximum = None, visible = False) return gradio.Slider()
def process_preview_frame(source_face : Face, reference_face : Face, temp_frame : Frame) -> Frame: def process_preview_frame(source_face : Face, reference_face : Face, temp_frame : Frame) -> Frame:
temp_frame = resize_frame_dimension(temp_frame, 640, 640)
if predict_frame(temp_frame): if predict_frame(temp_frame):
return cv2.GaussianBlur(temp_frame, (99, 99), 0) return cv2.GaussianBlur(temp_frame, (99, 99), 0)
temp_frame = resize_frame_dimension(temp_frame, 480)
for frame_processor in facefusion.globals.frame_processors: for frame_processor in facefusion.globals.frame_processors:
frame_processor_module = load_frame_processor_module(frame_processor) frame_processor_module = load_frame_processor_module(frame_processor)
if frame_processor_module.pre_process('preview'): if frame_processor_module.pre_process('preview'):

View File

@ -1,40 +0,0 @@
from typing import Optional, List
import gradio
import facefusion.globals
from facefusion import wording
from facefusion.uis import choices
from facefusion.uis.typing import Update
SETTINGS_CHECKBOX_GROUP : Optional[gradio.Checkboxgroup] = None
def render() -> None:
global SETTINGS_CHECKBOX_GROUP
value = []
if facefusion.globals.keep_fps:
value.append('keep-fps')
if facefusion.globals.keep_temp:
value.append('keep-temp')
if facefusion.globals.skip_audio:
value.append('skip-audio')
if facefusion.globals.skip_download:
value.append('skip-download')
SETTINGS_CHECKBOX_GROUP = gradio.Checkboxgroup(
label = wording.get('settings_checkbox_group_label'),
choices = choices.settings,
value = value
)
def listen() -> None:
SETTINGS_CHECKBOX_GROUP.change(update, inputs = SETTINGS_CHECKBOX_GROUP, outputs = SETTINGS_CHECKBOX_GROUP)
def update(settings : List[str]) -> Update:
facefusion.globals.keep_fps = 'keep-fps' in settings
facefusion.globals.keep_temp = 'keep-temp' in settings
facefusion.globals.skip_audio = 'skip-audio' in settings
facefusion.globals.skip_download = 'skip-download' in settings
return gradio.update(value = settings)

View File

@ -3,9 +3,8 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.uis import core as ui
from facefusion.uis.typing import Update
from facefusion.utilities import is_image from facefusion.utilities import is_image
from facefusion.uis.core import register_ui_component
SOURCE_FILE : Optional[gradio.File] = None SOURCE_FILE : Optional[gradio.File] = None
SOURCE_IMAGE : Optional[gradio.Image] = None SOURCE_IMAGE : Optional[gradio.Image] = None
@ -32,16 +31,16 @@ def render() -> None:
visible = is_source_image, visible = is_source_image,
show_label = False show_label = False
) )
ui.register_component('source_image', SOURCE_IMAGE) register_ui_component('source_image', SOURCE_IMAGE)
def listen() -> None: def listen() -> None:
SOURCE_FILE.change(update, inputs = SOURCE_FILE, outputs = SOURCE_IMAGE) SOURCE_FILE.change(update, inputs = SOURCE_FILE, outputs = SOURCE_IMAGE)
def update(file: IO[Any]) -> Update: def update(file: IO[Any]) -> gradio.Image:
if file and is_image(file.name): if file and is_image(file.name):
facefusion.globals.source_path = file.name facefusion.globals.source_path = file.name
return gradio.update(value = file.name, visible = True) return gradio.Image(value = file.name, visible = True)
facefusion.globals.source_path = None facefusion.globals.source_path = None
return gradio.update(value = None, visible = False) return gradio.Image(value = None, visible = False)

View File

@ -4,9 +4,8 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.face_reference import clear_face_reference from facefusion.face_reference import clear_face_reference
from facefusion.uis import core as ui
from facefusion.uis.typing import Update
from facefusion.utilities import is_image, is_video from facefusion.utilities import is_image, is_video
from facefusion.uis.core import register_ui_component
TARGET_FILE : Optional[gradio.File] = None TARGET_FILE : Optional[gradio.File] = None
TARGET_IMAGE : Optional[gradio.Image] = None TARGET_IMAGE : Optional[gradio.Image] = None
@ -42,21 +41,21 @@ def render() -> None:
visible = is_target_video, visible = is_target_video,
show_label = False show_label = False
) )
ui.register_component('target_image', TARGET_IMAGE) register_ui_component('target_image', TARGET_IMAGE)
ui.register_component('target_video', TARGET_VIDEO) register_ui_component('target_video', TARGET_VIDEO)
def listen() -> None: def listen() -> None:
TARGET_FILE.change(update, inputs = TARGET_FILE, outputs = [ TARGET_IMAGE, TARGET_VIDEO ]) TARGET_FILE.change(update, inputs = TARGET_FILE, outputs = [ TARGET_IMAGE, TARGET_VIDEO ])
def update(file : IO[Any]) -> Tuple[Update, Update]: def update(file : IO[Any]) -> Tuple[gradio.Image, gradio.Video]:
clear_face_reference() clear_face_reference()
if file and is_image(file.name): if file and is_image(file.name):
facefusion.globals.target_path = file.name facefusion.globals.target_path = file.name
return gradio.update(value = file.name, visible = True), gradio.update(value = None, visible = False) return gradio.Image(value = file.name, visible = True), gradio.Video(value = None, visible = False)
if file and is_video(file.name): if file and is_video(file.name):
facefusion.globals.target_path = file.name facefusion.globals.target_path = file.name
return gradio.update(value = None, visible = False), gradio.update(value = file.name, visible = True) return gradio.Image(value = None, visible = False), gradio.Video(value = file.name, visible = True)
facefusion.globals.target_path = None facefusion.globals.target_path = None
return gradio.update(value = None, visible = False), gradio.update(value = None, visible = False) return gradio.Image(value = None, visible = False), gradio.Video(value = None, visible = False)

View File

@ -5,9 +5,8 @@ import facefusion.choices
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.typing import TempFrameFormat from facefusion.typing import TempFrameFormat
from facefusion.uis import core as ui
from facefusion.uis.typing import Update
from facefusion.utilities import is_video from facefusion.utilities import is_video
from facefusion.uis.core import get_ui_component
TEMP_FRAME_FORMAT_DROPDOWN : Optional[gradio.Dropdown] = None TEMP_FRAME_FORMAT_DROPDOWN : Optional[gradio.Dropdown] = None
TEMP_FRAME_QUALITY_SLIDER : Optional[gradio.Slider] = None TEMP_FRAME_QUALITY_SLIDER : Optional[gradio.Slider] = None
@ -19,7 +18,7 @@ def render() -> None:
TEMP_FRAME_FORMAT_DROPDOWN = gradio.Dropdown( TEMP_FRAME_FORMAT_DROPDOWN = gradio.Dropdown(
label = wording.get('temp_frame_format_dropdown_label'), label = wording.get('temp_frame_format_dropdown_label'),
choices = facefusion.choices.temp_frame_format, choices = facefusion.choices.temp_frame_formats,
value = facefusion.globals.temp_frame_format, value = facefusion.globals.temp_frame_format,
visible = is_video(facefusion.globals.target_path) visible = is_video(facefusion.globals.target_path)
) )
@ -27,30 +26,30 @@ def render() -> None:
label = wording.get('temp_frame_quality_slider_label'), label = wording.get('temp_frame_quality_slider_label'),
value = facefusion.globals.temp_frame_quality, value = facefusion.globals.temp_frame_quality,
step = 1, step = 1,
minimum = 0,
maximum = 100,
visible = is_video(facefusion.globals.target_path) visible = is_video(facefusion.globals.target_path)
) )
def listen() -> None: def listen() -> None:
TEMP_FRAME_FORMAT_DROPDOWN.select(update_temp_frame_format, inputs = TEMP_FRAME_FORMAT_DROPDOWN, outputs = TEMP_FRAME_FORMAT_DROPDOWN) TEMP_FRAME_FORMAT_DROPDOWN.select(update_temp_frame_format, inputs = TEMP_FRAME_FORMAT_DROPDOWN)
TEMP_FRAME_QUALITY_SLIDER.change(update_temp_frame_quality, inputs = TEMP_FRAME_QUALITY_SLIDER, outputs = TEMP_FRAME_QUALITY_SLIDER) TEMP_FRAME_QUALITY_SLIDER.change(update_temp_frame_quality, inputs = TEMP_FRAME_QUALITY_SLIDER)
target_video = ui.get_component('target_video') target_video = get_ui_component('target_video')
if target_video: if target_video:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(target_video, method)(remote_update, outputs = [ TEMP_FRAME_FORMAT_DROPDOWN, TEMP_FRAME_QUALITY_SLIDER ]) getattr(target_video, method)(remote_update, outputs = [ TEMP_FRAME_FORMAT_DROPDOWN, TEMP_FRAME_QUALITY_SLIDER ])
def remote_update() -> Tuple[Update, Update]: def remote_update() -> Tuple[gradio.Dropdown, gradio.Slider]:
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
return gradio.update(visible = True), gradio.update(visible = True) return gradio.Dropdown(visible = True), gradio.Slider(visible = True)
return gradio.update(visible = False), gradio.update(visible = False) return gradio.Dropdown(visible = False), gradio.Slider(visible = False)
def update_temp_frame_format(temp_frame_format : TempFrameFormat) -> Update: def update_temp_frame_format(temp_frame_format : TempFrameFormat) -> None:
facefusion.globals.temp_frame_format = temp_frame_format facefusion.globals.temp_frame_format = temp_frame_format
return gradio.update(value = temp_frame_format)
def update_temp_frame_quality(temp_frame_quality : int) -> Update: def update_temp_frame_quality(temp_frame_quality : int) -> None:
facefusion.globals.temp_frame_quality = temp_frame_quality facefusion.globals.temp_frame_quality = temp_frame_quality
return gradio.update(value = temp_frame_quality)

View File

@ -4,9 +4,8 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.vision import count_video_frame_total from facefusion.vision import count_video_frame_total
from facefusion.uis import core as ui
from facefusion.uis.typing import Update
from facefusion.utilities import is_video from facefusion.utilities import is_video
from facefusion.uis.core import get_ui_component
TRIM_FRAME_START_SLIDER : Optional[gradio.Slider] = None TRIM_FRAME_START_SLIDER : Optional[gradio.Slider] = None
TRIM_FRAME_END_SLIDER : Optional[gradio.Slider] = None TRIM_FRAME_END_SLIDER : Optional[gradio.Slider] = None
@ -20,12 +19,16 @@ def render() -> None:
{ {
'label': wording.get('trim_frame_start_slider_label'), 'label': wording.get('trim_frame_start_slider_label'),
'step': 1, 'step': 1,
'minimum': 0,
'maximum': 100,
'visible': False 'visible': False
} }
trim_frame_end_slider_args : Dict[str, Any] =\ trim_frame_end_slider_args : Dict[str, Any] =\
{ {
'label': wording.get('trim_frame_end_slider_label'), 'label': wording.get('trim_frame_end_slider_label'),
'step': 1, 'step': 1,
'minimum': 0,
'maximum': 100,
'visible': False 'visible': False
} }
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
@ -41,29 +44,27 @@ def render() -> None:
def listen() -> None: def listen() -> None:
TRIM_FRAME_START_SLIDER.change(update_trim_frame_start, inputs = TRIM_FRAME_START_SLIDER, outputs = TRIM_FRAME_START_SLIDER) TRIM_FRAME_START_SLIDER.change(update_trim_frame_start, inputs = TRIM_FRAME_START_SLIDER)
TRIM_FRAME_END_SLIDER.change(update_trim_frame_end, inputs = TRIM_FRAME_END_SLIDER, outputs = TRIM_FRAME_END_SLIDER) TRIM_FRAME_END_SLIDER.change(update_trim_frame_end, inputs = TRIM_FRAME_END_SLIDER)
target_video = ui.get_component('target_video') target_video = get_ui_component('target_video')
if target_video: if target_video:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(target_video, method)(remote_update, outputs = [ TRIM_FRAME_START_SLIDER, TRIM_FRAME_END_SLIDER ]) getattr(target_video, method)(remote_update, outputs = [ TRIM_FRAME_START_SLIDER, TRIM_FRAME_END_SLIDER ])
def remote_update() -> Tuple[Update, Update]: def remote_update() -> Tuple[gradio.Slider, gradio.Slider]:
if is_video(facefusion.globals.target_path): if is_video(facefusion.globals.target_path):
video_frame_total = count_video_frame_total(facefusion.globals.target_path) video_frame_total = count_video_frame_total(facefusion.globals.target_path)
facefusion.globals.trim_frame_start = None facefusion.globals.trim_frame_start = None
facefusion.globals.trim_frame_end = None facefusion.globals.trim_frame_end = None
return gradio.update(value = 0, maximum = video_frame_total, visible = True), gradio.update(value = video_frame_total, maximum = video_frame_total, visible = True) return gradio.Slider(value = 0, maximum = video_frame_total, visible = True), gradio.Slider(value = video_frame_total, maximum = video_frame_total, visible = True)
return gradio.update(value = None, maximum = None, visible = False), gradio.update(value = None, maximum = None, visible = False) return gradio.Slider(value = None, maximum = None, visible = False), gradio.Slider(value = None, maximum = None, visible = False)
def update_trim_frame_start(trim_frame_start : int) -> Update: def update_trim_frame_start(trim_frame_start : int) -> None:
facefusion.globals.trim_frame_start = trim_frame_start if trim_frame_start > 0 else None facefusion.globals.trim_frame_start = trim_frame_start if trim_frame_start > 0 else None
return gradio.update(value = trim_frame_start)
def update_trim_frame_end(trim_frame_end : int) -> Update: def update_trim_frame_end(trim_frame_end : int) -> None:
video_frame_total = count_video_frame_total(facefusion.globals.target_path) video_frame_total = count_video_frame_total(facefusion.globals.target_path)
facefusion.globals.trim_frame_end = trim_frame_end if trim_frame_end < video_frame_total else None facefusion.globals.trim_frame_end = trim_frame_end if trim_frame_end < video_frame_total else None
return gradio.update(value = trim_frame_end)

View File

@ -10,13 +10,14 @@ from tqdm import tqdm
import facefusion.globals import facefusion.globals
from facefusion import wording from facefusion import wording
from facefusion.predictor import predict_stream
from facefusion.typing import Frame, Face from facefusion.typing import Frame, Face
from facefusion.face_analyser import get_one_face from facefusion.face_analyser import get_one_face
from facefusion.processors.frame.core import load_frame_processor_module from facefusion.processors.frame.core import get_frame_processors_modules
from facefusion.uis import core as ui
from facefusion.uis.typing import StreamMode, WebcamMode, Update
from facefusion.utilities import open_ffmpeg from facefusion.utilities import open_ffmpeg
from facefusion.vision import normalize_frame_color, read_static_image from facefusion.vision import normalize_frame_color, read_static_image
from facefusion.uis.typing import StreamMode, WebcamMode
from facefusion.uis.core import get_ui_component
WEBCAM_IMAGE : Optional[gradio.Image] = None WEBCAM_IMAGE : Optional[gradio.Image] = None
WEBCAM_START_BUTTON : Optional[gradio.Button] = None WEBCAM_START_BUTTON : Optional[gradio.Button] = None
@ -33,25 +34,27 @@ def render() -> None:
) )
WEBCAM_START_BUTTON = gradio.Button( WEBCAM_START_BUTTON = gradio.Button(
value = wording.get('start_button_label'), value = wording.get('start_button_label'),
variant = 'primary' variant = 'primary',
size = 'sm'
) )
WEBCAM_STOP_BUTTON = gradio.Button( WEBCAM_STOP_BUTTON = gradio.Button(
value = wording.get('stop_button_label') value = wording.get('stop_button_label'),
size = 'sm'
) )
def listen() -> None: def listen() -> None:
start_event = None start_event = None
webcam_mode_radio = ui.get_component('webcam_mode_radio') webcam_mode_radio = get_ui_component('webcam_mode_radio')
webcam_resolution_dropdown = ui.get_component('webcam_resolution_dropdown') webcam_resolution_dropdown = get_ui_component('webcam_resolution_dropdown')
webcam_fps_slider = ui.get_component('webcam_fps_slider') webcam_fps_slider = get_ui_component('webcam_fps_slider')
if webcam_mode_radio and webcam_resolution_dropdown and webcam_fps_slider: if webcam_mode_radio and webcam_resolution_dropdown and webcam_fps_slider:
start_event = WEBCAM_START_BUTTON.click(start, inputs = [ webcam_mode_radio, webcam_resolution_dropdown, webcam_fps_slider ], outputs = WEBCAM_IMAGE) start_event = WEBCAM_START_BUTTON.click(start, inputs = [ webcam_mode_radio, webcam_resolution_dropdown, webcam_fps_slider ], outputs = WEBCAM_IMAGE)
webcam_mode_radio.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event) webcam_mode_radio.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
webcam_resolution_dropdown.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event) webcam_resolution_dropdown.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
webcam_fps_slider.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event) webcam_fps_slider.change(stop, outputs = WEBCAM_IMAGE, cancels = start_event)
WEBCAM_STOP_BUTTON.click(stop, cancels = start_event) WEBCAM_STOP_BUTTON.click(stop, cancels = start_event)
source_image = ui.get_component('source_image') source_image = get_ui_component('source_image')
if source_image: if source_image:
for method in [ 'upload', 'change', 'clear' ]: for method in [ 'upload', 'change', 'clear' ]:
getattr(source_image, method)(stop, cancels = start_event) getattr(source_image, method)(stop, cancels = start_event)
@ -61,10 +64,8 @@ def start(mode: WebcamMode, resolution: str, fps: float) -> Generator[Frame, Non
facefusion.globals.face_recognition = 'many' facefusion.globals.face_recognition = 'many'
source_face = get_one_face(read_static_image(facefusion.globals.source_path)) source_face = get_one_face(read_static_image(facefusion.globals.source_path))
stream = None stream = None
if mode == 'stream_udp': if mode in [ 'udp', 'v4l2' ]:
stream = open_stream('udp', resolution, fps) stream = open_stream(mode, resolution, fps) # type: ignore[arg-type]
if mode == 'stream_v4l2':
stream = open_stream('v4l2', resolution, fps)
capture = capture_webcam(resolution, fps) capture = capture_webcam(resolution, fps)
if capture.isOpened(): if capture.isOpened():
for capture_frame in multi_process_capture(source_face, capture): for capture_frame in multi_process_capture(source_face, capture):
@ -80,6 +81,8 @@ def multi_process_capture(source_face: Face, capture : cv2.VideoCapture) -> Gene
deque_capture_frames : Deque[Frame] = deque() deque_capture_frames : Deque[Frame] = deque()
while True: while True:
_, capture_frame = capture.read() _, capture_frame = capture.read()
if predict_stream(capture_frame):
return
future = executor.submit(process_stream_frame, source_face, capture_frame) future = executor.submit(process_stream_frame, source_face, capture_frame)
futures.append(future) futures.append(future)
for future_done in [ future for future in futures if future.done() ]: for future_done in [ future for future in futures if future.done() ]:
@ -91,8 +94,8 @@ def multi_process_capture(source_face: Face, capture : cv2.VideoCapture) -> Gene
progress.update() progress.update()
def stop() -> Update: def stop() -> gradio.Image:
return gradio.update(value = None) return gradio.Image(value = None)
def capture_webcam(resolution : str, fps : float) -> cv2.VideoCapture: def capture_webcam(resolution : str, fps : float) -> cv2.VideoCapture:
@ -109,8 +112,7 @@ def capture_webcam(resolution : str, fps : float) -> cv2.VideoCapture:
def process_stream_frame(source_face : Face, temp_frame : Frame) -> Frame: def process_stream_frame(source_face : Face, temp_frame : Frame) -> Frame:
for frame_processor in facefusion.globals.frame_processors: for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
frame_processor_module = load_frame_processor_module(frame_processor)
if frame_processor_module.pre_process('stream'): if frame_processor_module.pre_process('stream'):
temp_frame = frame_processor_module.process_frame( temp_frame = frame_processor_module.process_frame(
source_face, source_face,

View File

@ -3,8 +3,7 @@ import gradio
from facefusion import wording from facefusion import wording
from facefusion.uis import choices from facefusion.uis import choices
from facefusion.uis import core as ui from facefusion.uis.core import register_ui_component
from facefusion.uis.typing import Update
WEBCAM_MODE_RADIO : Optional[gradio.Radio] = None WEBCAM_MODE_RADIO : Optional[gradio.Radio] = None
WEBCAM_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None WEBCAM_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None
@ -18,25 +17,21 @@ def render() -> None:
WEBCAM_MODE_RADIO = gradio.Radio( WEBCAM_MODE_RADIO = gradio.Radio(
label = wording.get('webcam_mode_radio_label'), label = wording.get('webcam_mode_radio_label'),
choices = choices.webcam_mode, choices = choices.webcam_modes,
value = 'inline' value = 'inline'
) )
WEBCAM_RESOLUTION_DROPDOWN = gradio.Dropdown( WEBCAM_RESOLUTION_DROPDOWN = gradio.Dropdown(
label = wording.get('webcam_resolution_dropdown'), label = wording.get('webcam_resolution_dropdown'),
choices = choices.webcam_resolution, choices = choices.webcam_resolutions,
value = choices.webcam_resolution[0] value = choices.webcam_resolutions[0]
) )
WEBCAM_FPS_SLIDER = gradio.Slider( WEBCAM_FPS_SLIDER = gradio.Slider(
label = wording.get('webcam_fps_slider'), label = wording.get('webcam_fps_slider'),
minimum = 1, value = 25,
maximum = 60,
step = 1, step = 1,
value = 25 minimum = 1,
maximum = 60
) )
ui.register_component('webcam_mode_radio', WEBCAM_MODE_RADIO) register_ui_component('webcam_mode_radio', WEBCAM_MODE_RADIO)
ui.register_component('webcam_resolution_dropdown', WEBCAM_RESOLUTION_DROPDOWN) register_ui_component('webcam_resolution_dropdown', WEBCAM_RESOLUTION_DROPDOWN)
ui.register_component('webcam_fps_slider', WEBCAM_FPS_SLIDER) register_ui_component('webcam_fps_slider', WEBCAM_FPS_SLIDER)
def update() -> Update:
return gradio.update(value = None)

View File

@ -1,5 +1,5 @@
from types import ModuleType
from typing import Dict, Optional, Any, List from typing import Dict, Optional, Any, List
from types import ModuleType
import importlib import importlib
import sys import sys
import gradio import gradio
@ -7,8 +7,9 @@ import gradio
import facefusion.globals import facefusion.globals
from facefusion import metadata, wording from facefusion import metadata, wording
from facefusion.uis.typing import Component, ComponentName from facefusion.uis.typing import Component, ComponentName
from facefusion.utilities import resolve_relative_path
COMPONENTS: Dict[ComponentName, Component] = {} UI_COMPONENTS: Dict[ComponentName, Component] = {}
UI_LAYOUT_MODULES : List[ModuleType] = [] UI_LAYOUT_MODULES : List[ModuleType] = []
UI_LAYOUT_METHODS =\ UI_LAYOUT_METHODS =\
[ [
@ -43,8 +44,18 @@ def get_ui_layouts_modules(ui_layouts : List[str]) -> List[ModuleType]:
return UI_LAYOUT_MODULES return UI_LAYOUT_MODULES
def get_ui_component(name: ComponentName) -> Optional[Component]:
if name in UI_COMPONENTS:
return UI_COMPONENTS[name]
return None
def register_ui_component(name: ComponentName, component: Component) -> None:
UI_COMPONENTS[name] = component
def launch() -> None: def launch() -> None:
with gradio.Blocks(theme = get_theme(), title = metadata.get('name') + ' ' + metadata.get('version')) as ui: with gradio.Blocks(theme = get_theme(), css = get_css(), title = metadata.get('name') + ' ' + metadata.get('version')) as ui:
for ui_layout in facefusion.globals.ui_layouts: for ui_layout in facefusion.globals.ui_layouts:
ui_layout_module = load_ui_layout_module(ui_layout) ui_layout_module = load_ui_layout_module(ui_layout)
if ui_layout_module.pre_render(): if ui_layout_module.pre_render():
@ -57,22 +68,63 @@ def launch() -> None:
def get_theme() -> gradio.Theme: def get_theme() -> gradio.Theme:
return gradio.themes.Soft( return gradio.themes.Base(
primary_hue = gradio.themes.colors.red, primary_hue = gradio.themes.colors.red,
secondary_hue = gradio.themes.colors.gray, secondary_hue = gradio.themes.colors.neutral,
font = gradio.themes.GoogleFont('Inter') font = gradio.themes.GoogleFont('Open Sans')
).set( ).set(
background_fill_primary = '*neutral_50', background_fill_primary = '*neutral_100',
block_label_text_size = '*text_sm', block_background_fill = 'white',
block_title_text_size = '*text_sm' block_border_width = '0',
block_label_background_fill = '*primary_100',
block_label_background_fill_dark = '*primary_600',
block_label_border_width = 'none',
block_label_margin = '0.5rem',
block_label_radius = '*radius_md',
block_label_text_color = '*primary_500',
block_label_text_color_dark = 'white',
block_label_text_weight = '600',
block_title_background_fill = '*primary_100',
block_title_background_fill_dark = '*primary_600',
block_title_padding = '*block_label_padding',
block_title_radius = '*block_label_radius',
block_title_text_color = '*primary_500',
block_title_text_size = '*text_sm',
block_title_text_weight = '600',
block_padding = '0.5rem',
border_color_primary = 'transparent',
border_color_primary_dark = 'transparent',
button_large_padding = '2rem 0.5rem',
button_large_text_weight = 'normal',
button_primary_background_fill = '*primary_500',
button_primary_text_color = 'white',
button_secondary_background_fill = 'white',
button_secondary_border_color = 'transparent',
button_secondary_border_color_dark = 'transparent',
button_secondary_border_color_hover = 'transparent',
button_secondary_border_color_hover_dark = 'transparent',
button_secondary_text_color = '*neutral_800',
button_small_padding = '0.75rem',
checkbox_background_color = '*neutral_200',
checkbox_background_color_selected = '*primary_600',
checkbox_background_color_selected_dark = '*primary_700',
checkbox_border_color_focus = '*primary_500',
checkbox_border_color_focus_dark = '*primary_600',
checkbox_border_color_selected = '*primary_600',
checkbox_border_color_selected_dark = '*primary_700',
checkbox_label_background_fill = '*neutral_50',
checkbox_label_background_fill_hover = '*neutral_50',
checkbox_label_background_fill_selected = '*primary_500',
checkbox_label_background_fill_selected_dark = '*primary_600',
checkbox_label_text_color_selected = 'white',
input_background_fill = '*neutral_50',
shadow_drop = 'none',
slider_color = '*primary_500',
slider_color_dark = '*primary_600'
) )
def get_component(name: ComponentName) -> Optional[Component]: def get_css() -> str:
if name in COMPONENTS: fixes_css_path = resolve_relative_path('uis/assets/fixes.css')
return COMPONENTS[name] overrides_css_path = resolve_relative_path('uis/assets/overrides.css')
return None return open(fixes_css_path, 'r').read() + open(overrides_css_path, 'r').read()
def register_component(name: ComponentName, component: Component) -> None:
COMPONENTS[name] = component

View File

@ -1,8 +1,8 @@
import gradio import gradio
import facefusion.globals import facefusion.globals
from facefusion.uis.components import about, processors, execution, execution_thread_count, execution_queue_count, limit_resources, benchmark_settings, benchmark
from facefusion.utilities import conditional_download from facefusion.utilities import conditional_download
from facefusion.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, limit_resources, benchmark_options, benchmark
def pre_check() -> bool: def pre_check() -> bool:
@ -30,10 +30,11 @@ def render() -> gradio.Blocks:
with gradio.Blocks() as layout: with gradio.Blocks() as layout:
with gradio.Row(): with gradio.Row():
with gradio.Column(scale = 2): with gradio.Column(scale = 2):
with gradio.Box(): with gradio.Blocks():
about.render() about.render()
with gradio.Blocks(): with gradio.Blocks():
processors.render() frame_processors.render()
frame_processors_options.render()
with gradio.Blocks(): with gradio.Blocks():
execution.render() execution.render()
execution_thread_count.render() execution_thread_count.render()
@ -41,7 +42,7 @@ def render() -> gradio.Blocks:
with gradio.Blocks(): with gradio.Blocks():
limit_resources.render() limit_resources.render()
with gradio.Blocks(): with gradio.Blocks():
benchmark_settings.render() benchmark_options.render()
with gradio.Column(scale= 5): with gradio.Column(scale= 5):
with gradio.Blocks(): with gradio.Blocks():
benchmark.render() benchmark.render()
@ -49,15 +50,14 @@ def render() -> gradio.Blocks:
def listen() -> None: def listen() -> None:
processors.listen() frame_processors.listen()
frame_processors_options.listen()
execution.listen() execution.listen()
execution_thread_count.listen() execution_thread_count.listen()
execution_queue_count.listen() execution_queue_count.listen()
limit_resources.listen() limit_resources.listen()
benchmark_settings.listen()
benchmark.listen() benchmark.listen()
def run(ui : gradio.Blocks) -> None: def run(ui : gradio.Blocks) -> None:
ui.queue(concurrency_count = 2, api_open = False) ui.queue(concurrency_count = 2, api_open = False).launch(show_api = False)
ui.launch(show_api = False)

View File

@ -1,6 +1,6 @@
import gradio import gradio
from facefusion.uis.components import about, processors, execution, execution_thread_count, execution_queue_count, limit_resources, temp_frame, output_settings, settings, source, target, preview, trim_frame, face_analyser, face_selector, output from facefusion.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, limit_resources, temp_frame, output_options, common_options, source, target, preview, trim_frame, face_analyser, face_selector, output
def pre_check() -> bool: def pre_check() -> bool:
@ -15,10 +15,11 @@ def render() -> gradio.Blocks:
with gradio.Blocks() as layout: with gradio.Blocks() as layout:
with gradio.Row(): with gradio.Row():
with gradio.Column(scale = 2): with gradio.Column(scale = 2):
with gradio.Box(): with gradio.Blocks():
about.render() about.render()
with gradio.Blocks(): with gradio.Blocks():
processors.render() frame_processors.render()
frame_processors_options.render()
with gradio.Blocks(): with gradio.Blocks():
execution.render() execution.render()
execution_thread_count.render() execution_thread_count.render()
@ -28,9 +29,7 @@ def render() -> gradio.Blocks:
with gradio.Blocks(): with gradio.Blocks():
temp_frame.render() temp_frame.render()
with gradio.Blocks(): with gradio.Blocks():
output_settings.render() output_options.render()
with gradio.Blocks():
settings.render()
with gradio.Column(scale = 2): with gradio.Column(scale = 2):
with gradio.Blocks(): with gradio.Blocks():
source.render() source.render()
@ -47,18 +46,21 @@ def render() -> gradio.Blocks:
face_selector.render() face_selector.render()
with gradio.Row(): with gradio.Row():
face_analyser.render() face_analyser.render()
with gradio.Blocks():
common_options.render()
return layout return layout
def listen() -> None: def listen() -> None:
processors.listen() frame_processors.listen()
frame_processors_options.listen()
execution.listen() execution.listen()
execution_thread_count.listen() execution_thread_count.listen()
execution_queue_count.listen() execution_queue_count.listen()
limit_resources.listen() limit_resources.listen()
temp_frame.listen() temp_frame.listen()
output_settings.listen() output_options.listen()
settings.listen() common_options.listen()
source.listen() source.listen()
target.listen() target.listen()
preview.listen() preview.listen()

View File

@ -1,6 +1,6 @@
import gradio import gradio
from facefusion.uis.components import about, processors, execution, execution_thread_count, webcam_settings, source, webcam from facefusion.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, webcam_options, source, webcam
def pre_check() -> bool: def pre_check() -> bool:
@ -15,15 +15,16 @@ def render() -> gradio.Blocks:
with gradio.Blocks() as layout: with gradio.Blocks() as layout:
with gradio.Row(): with gradio.Row():
with gradio.Column(scale = 2): with gradio.Column(scale = 2):
with gradio.Box(): with gradio.Blocks():
about.render() about.render()
with gradio.Blocks(): with gradio.Blocks():
processors.render() frame_processors.render()
frame_processors_options.render()
with gradio.Blocks(): with gradio.Blocks():
execution.render() execution.render()
execution_thread_count.render() execution_thread_count.render()
with gradio.Blocks(): with gradio.Blocks():
webcam_settings.render() webcam_options.render()
with gradio.Blocks(): with gradio.Blocks():
source.render() source.render()
with gradio.Column(scale = 5): with gradio.Column(scale = 5):
@ -33,7 +34,8 @@ def render() -> gradio.Blocks:
def listen() -> None: def listen() -> None:
processors.listen() frame_processors.listen()
frame_processors_options.listen()
execution.listen() execution.listen()
execution_thread_count.listen() execution_thread_count.listen()
source.listen() source.listen()
@ -41,5 +43,4 @@ def listen() -> None:
def run(ui : gradio.Blocks) -> None: def run(ui : gradio.Blocks) -> None:
ui.queue(concurrency_count = 2, api_open = False) ui.queue(concurrency_count = 2, api_open = False).launch(show_api = False)
ui.launch(show_api = False)

View File

@ -1,4 +1,4 @@
from typing import Literal, Dict, Any from typing import Literal
import gradio import gradio
Component = gradio.File or gradio.Image or gradio.Video or gradio.Slider Component = gradio.File or gradio.Image or gradio.Video or gradio.Slider
@ -15,12 +15,18 @@ ComponentName = Literal\
'face_analyser_age_dropdown', 'face_analyser_age_dropdown',
'face_analyser_gender_dropdown', 'face_analyser_gender_dropdown',
'frame_processors_checkbox_group', 'frame_processors_checkbox_group',
'face_swapper_model_dropdown',
'face_enhancer_model_dropdown',
'face_enhancer_blend_slider',
'frame_enhancer_model_dropdown',
'frame_enhancer_blend_slider',
'output_path_textbox',
'benchmark_runs_checkbox_group', 'benchmark_runs_checkbox_group',
'benchmark_cycles_slider', 'benchmark_cycles_slider',
'player_url_textbox_label',
'webcam_mode_radio', 'webcam_mode_radio',
'webcam_resolution_dropdown', 'webcam_resolution_dropdown',
'webcam_fps_slider' 'webcam_fps_slider'
] ]
WebcamMode = Literal[ 'inline', 'stream_udp', 'stream_v4l2' ] WebcamMode = Literal[ 'inline', 'udp', 'v4l2' ]
StreamMode = Literal[ 'udp', 'v4l2' ] StreamMode = Literal[ 'udp', 'v4l2' ]
Update = Dict[Any, Any]

View File

@ -70,13 +70,13 @@ def merge_video(target_path : str, fps : float) -> bool:
temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d') temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d')
commands = [ '-hwaccel', 'auto', '-r', str(fps), '-i', temp_frames_pattern, '-c:v', facefusion.globals.output_video_encoder ] commands = [ '-hwaccel', 'auto', '-r', str(fps), '-i', temp_frames_pattern, '-c:v', facefusion.globals.output_video_encoder ]
if facefusion.globals.output_video_encoder in [ 'libx264', 'libx265' ]: if facefusion.globals.output_video_encoder in [ 'libx264', 'libx265' ]:
output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.5)) output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
commands.extend([ '-crf', str(output_video_compression) ]) commands.extend([ '-crf', str(output_video_compression) ])
if facefusion.globals.output_video_encoder in [ 'libvpx-vp9' ]: if facefusion.globals.output_video_encoder in [ 'libvpx-vp9' ]:
output_video_compression = round(63 - (facefusion.globals.output_video_quality * 0.5)) output_video_compression = round(63 - (facefusion.globals.output_video_quality * 0.63))
commands.extend([ '-crf', str(output_video_compression) ]) commands.extend([ '-crf', str(output_video_compression) ])
if facefusion.globals.output_video_encoder in [ 'h264_nvenc', 'hevc_nvenc' ]: if facefusion.globals.output_video_encoder in [ 'h264_nvenc', 'hevc_nvenc' ]:
output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.5)) output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
commands.extend([ '-cq', str(output_video_compression) ]) commands.extend([ '-cq', str(output_video_compression) ])
commands.extend([ '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_output_video_path ]) commands.extend([ '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_output_video_path ])
return run_ffmpeg(commands) return run_ffmpeg(commands)
@ -187,7 +187,7 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
initial = 0 initial = 0
if initial < total: if initial < total:
with tqdm(total = total, initial = initial, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024) as progress: with tqdm(total = total, initial = initial, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024) as progress:
subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--location', '--continue-at', '-', '--output', download_file_path, url ]) subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ])
current = initial current = initial
while current < total: while current < total:
if is_file(download_file_path): if is_file(download_file_path):
@ -196,12 +196,12 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
@lru_cache(maxsize = None) @lru_cache(maxsize = None)
def get_download_size(url : str) -> Optional[int]: def get_download_size(url : str) -> int:
try: try:
response = urllib.request.urlopen(url) # type: ignore[attr-defined] response = urllib.request.urlopen(url) # type: ignore[attr-defined]
return int(response.getheader('Content-Length')) return int(response.getheader('Content-Length'))
except (OSError, ValueError): except (OSError, ValueError):
return None return 0
def is_download_done(url : str, file_path : str) -> bool: def is_download_done(url : str, file_path : str) -> bool:

View File

@ -40,12 +40,13 @@ def normalize_frame_color(frame : Frame) -> Frame:
return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
def resize_frame_dimension(frame : Frame, max_height : int) -> Frame: def resize_frame_dimension(frame : Frame, max_width : int, max_height : int) -> Frame:
height, width = frame.shape[:2] height, width = frame.shape[:2]
if height > max_height: if height > max_height or width > max_width:
scale = max_height / height scale = min(max_height / height, max_width / width)
max_width = int(width * scale) new_width = int(width * scale)
frame = cv2.resize(frame, (max_width, max_height)) new_height = int(height * scale)
return cv2.resize(frame, (new_width, new_height))
return frame return frame

View File

@ -2,11 +2,13 @@ WORDING =\
{ {
'python_not_supported': 'Python version is not supported, upgrade to {version} or higher', 'python_not_supported': 'Python version is not supported, upgrade to {version} or higher',
'ffmpeg_not_installed': 'FFMpeg is not installed', 'ffmpeg_not_installed': 'FFMpeg is not installed',
'onnxruntime_help': 'select the onnxruntime to be installed', 'install_dependency_help': 'select the variant of {dependency} to install',
'source_help': 'select a source image', 'source_help': 'select a source image',
'target_help': 'select a target image or video', 'target_help': 'select a target image or video',
'output_help': 'specify the output file or directory', 'output_help': 'specify the output file or directory',
'frame_processors_help': 'choose from the available frame processors (choices: {choices}, ...)', 'frame_processors_help': 'choose from the available frame processors (choices: {choices}, ...)',
'frame_processor_model_help': 'choose from the mode for the frame processor',
'frame_processor_blend_help': 'specify the blend factor for the frame processor',
'ui_layouts_help': 'choose from the available ui layouts (choices: {choices}, ...)', 'ui_layouts_help': 'choose from the available ui layouts (choices: {choices}, ...)',
'keep_fps_help': 'preserve the frames per second (fps) of the target', 'keep_fps_help': 'preserve the frames per second (fps) of the target',
'keep_temp_help': 'retain temporary frames after processing', 'keep_temp_help': 'retain temporary frames after processing',
@ -58,6 +60,7 @@ WORDING =\
'frame_processor_not_implemented': 'Frame processor {frame_processor} not implemented correctly', 'frame_processor_not_implemented': 'Frame processor {frame_processor} not implemented correctly',
'ui_layout_not_loaded': 'UI layout {ui_layout} could not be loaded', 'ui_layout_not_loaded': 'UI layout {ui_layout} could not be loaded',
'ui_layout_not_implemented': 'UI layout {ui_layout} not implemented correctly', 'ui_layout_not_implemented': 'UI layout {ui_layout} not implemented correctly',
'donate_button_label': 'DONATE',
'start_button_label': 'START', 'start_button_label': 'START',
'stop_button_label': 'STOP', 'stop_button_label': 'STOP',
'clear_button_label': 'CLEAR', 'clear_button_label': 'CLEAR',
@ -82,7 +85,12 @@ WORDING =\
'preview_image_label': 'PREVIEW', 'preview_image_label': 'PREVIEW',
'preview_frame_slider_label': 'PREVIEW FRAME', 'preview_frame_slider_label': 'PREVIEW FRAME',
'frame_processors_checkbox_group_label': 'FRAME PROCESSORS', 'frame_processors_checkbox_group_label': 'FRAME PROCESSORS',
'settings_checkbox_group_label': 'SETTINGS', 'face_swapper_model_dropdown_label': 'FACE SWAPPER MODEL',
'face_enhancer_model_dropdown_label': 'FACE ENHANCER MODEL',
'face_enhancer_blend_slider_label': 'FACE ENHANCER BLEND',
'frame_enhancer_model_dropdown_label': 'FRAME ENHANCER MODEL',
'frame_enhancer_blend_slider_label': 'FRAME ENHANCER BLEND',
'common_options_checkbox_group_label': 'OPTIONS',
'temp_frame_format_dropdown_label': 'TEMP FRAME FORMAT', 'temp_frame_format_dropdown_label': 'TEMP FRAME FORMAT',
'temp_frame_quality_slider_label': 'TEMP FRAME QUALITY', 'temp_frame_quality_slider_label': 'TEMP FRAME QUALITY',
'trim_frame_start_slider_label': 'TRIM FRAME START', 'trim_frame_start_slider_label': 'TRIM FRAME START',

View File

@ -3,4 +3,4 @@
from facefusion import installer from facefusion import installer
if __name__ == '__main__': if __name__ == '__main__':
installer.run() installer.cli()

View File

@ -1,14 +1,15 @@
gfpgan==1.3.8 basicsr==1.4.2
gradio==3.44.3 gradio==3.47.1
insightface==0.7.3 insightface==0.7.3
numpy==1.24.3 numpy==1.24.3
onnx==1.14.1 onnx==1.14.1
onnxruntime==1.15.1 onnxruntime==1.16.0
opencv-python==4.8.0.76 opencv-python==4.8.1.78
opennsfw2==0.10.2 opennsfw2==0.10.2
pillow==10.0.1 pillow==10.0.1
protobuf==4.24.2 protobuf==4.24.2
psutil==5.9.5 psutil==5.9.5
realesrgan==0.3.0 realesrgan==0.3.0
tensorflow==2.13.0 tensorflow==2.13.0
torch==2.1.0
tqdm==4.66.1 tqdm==4.66.1

2
run.py
View File

@ -3,4 +3,4 @@
from facefusion import core from facefusion import core
if __name__ == '__main__': if __name__ == '__main__':
core.run() core.cli()

View File

@ -143,7 +143,7 @@ def test_is_video() -> None:
def test_get_download_size() -> None: def test_get_download_size() -> None:
assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4') == 191675 assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4') == 191675
assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-360p.mp4') == 370732 assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-360p.mp4') == 370732
assert get_download_size('invalid') is None assert get_download_size('invalid') == 0
def test_is_download_done() -> None: def test_is_download_done() -> None: