diff --git a/.github/preview.png b/.github/preview.png index eaa24af4..fc01d8c7 100644 Binary files a/.github/preview.png and b/.github/preview.png differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8248d5df..5bd2728d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,6 @@ jobs: uses: actions/setup-python@v2 with: python-version: '3.10' - - run: pip install -r requirements.txt + - run: python install.py --torch cpu --onnxruntime cpu - run: pip install pytest - run: pytest diff --git a/README.md b/README.md index 543e2f79..c10322d8 100644 --- a/README.md +++ b/README.md @@ -29,36 +29,56 @@ Run the command: ``` python run.py [options] --h, --help show this help message and exit --s SOURCE_PATH, --source SOURCE_PATH select a source image --t TARGET_PATH, --target TARGET_PATH select a target image or video --o OUTPUT_PATH, --output OUTPUT_PATH specify the output file or directory ---frame-processors FRAME_PROCESSORS [FRAME_PROCESSORS ...] choose from the available frame processors (choices: face_enhancer, face_swapper, frame_enhancer, ...) ---ui-layouts UI_LAYOUTS [UI_LAYOUTS ...] choose from the available ui layouts (choices: benchmark, webcam, default, ...) ---keep-fps preserve the frames per second (fps) of the target ---keep-temp retain temporary frames after processing ---skip-audio omit audio from the target ---face-recognition {reference,many} specify the method for face recognition ---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 ---face-analyser-gender {male,female} specify the gender used for face analysis ---reference-face-position REFERENCE_FACE_POSITION specify the position of the reference face ---reference-face-distance REFERENCE_FACE_DISTANCE specify the distance between the reference face and the target face ---reference-frame-number REFERENCE_FRAME_NUMBER specify the number of the reference frame ---trim-frame-start TRIM_FRAME_START specify the start frame for extraction ---trim-frame-end TRIM_FRAME_END specify the end frame for extraction ---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 ---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 ---max-memory MAX_MEMORY specify the maximum amount of ram to be used (in gb) ---execution-providers {cpu} [{cpu} ...] choose from the available execution providers (choices: cpu, ...) ---execution-thread-count EXECUTION_THREAD_COUNT specify the number of execution threads ---execution-queue-count EXECUTION_QUEUE_COUNT specify the number of execution queries ---skip-download omit automate downloads and lookups ---headless run the program in headless mode --v, --version show program's version number and exit +options: + -h, --help show this help message and exit + -s SOURCE_PATH, --source SOURCE_PATH select a source image + -t TARGET_PATH, --target TARGET_PATH select a target image or video + -o OUTPUT_PATH, --output OUTPUT_PATH specify the output file or directory + -v, --version show program's version number and exit + +misc: + --skip-download omit automate downloads and lookups + --headless run the program in headless mode + +execution: + --execution-providers {cpu} [{cpu} ...] choose from the available execution providers (choices: cpu, ...) + --execution-thread-count EXECUTION_THREAD_COUNT specify the number of execution threads + --execution-queue-count EXECUTION_QUEUE_COUNT specify the number of execution queries + --max-memory MAX_MEMORY specify the maximum amount of ram to be used (in gb) + +face recognition: + --face-recognition {reference,many} specify the method for face recognition + --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 + --face-analyser-gender {male,female} specify the gender used for face analysis + --reference-face-position REFERENCE_FACE_POSITION specify the position of the reference face + --reference-face-distance REFERENCE_FACE_DISTANCE specify the distance between the reference face and the target face + --reference-frame-number REFERENCE_FRAME_NUMBER specify the number of the reference frame + +frame extraction: + --trim-frame-start TRIM_FRAME_START specify the start frame for extraction + --trim-frame-end TRIM_FRAME_END specify the end frame for extraction + --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, ...) ``` diff --git a/facefusion/choices.py b/facefusion/choices.py index c61e5644..f758e7d1 100644 --- a/facefusion/choices.py +++ b/facefusion/choices.py @@ -2,10 +2,9 @@ from typing import List from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder -face_recognition : List[FaceRecognition] = [ 'reference', 'many' ] -face_analyser_direction : 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_gender : List[FaceAnalyserGender] = [ 'male', 'female' ] -temp_frame_format : List[TempFrameFormat] = [ 'jpg', 'png' ] -output_video_encoder : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ] - +face_recognitions : List[FaceRecognition] = [ 'reference', 'many' ] +face_analyser_directions : List[FaceAnalyserDirection] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small' ] +face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ] +face_analyser_genders : List[FaceAnalyserGender] = [ 'male', 'female' ] +temp_frame_formats : List[TempFrameFormat] = [ 'jpg', 'png' ] +output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ] diff --git a/facefusion/core.py b/facefusion/core.py index b84dee7f..2e2e48fb 100755 --- a/facefusion/core.py +++ b/facefusion/core.py @@ -1,111 +1,158 @@ -#!/usr/bin/env python3 - import os -# single thread doubles cuda performance + os.environ['OMP_NUM_THREADS'] = '1' -# reduce tensorflow log level os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' + +import signal import sys import warnings -from typing import List import platform -import signal import shutil -import argparse import onnxruntime import tensorflow +from argparse import ArgumentParser, HelpFormatter import facefusion.choices import facefusion.globals -from facefusion import wording, metadata +from facefusion import metadata, wording from facefusion.predictor import predict_image, predict_video -from facefusion.processors.frame.core import get_frame_processors_modules -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.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, list_module_names, encode_execution_providers, decode_execution_providers, normalize_output_path warnings.filterwarnings('ignore', category = FutureWarning, module = 'insightface') warnings.filterwarnings('ignore', category = UserWarning, module = 'torchvision') -def parse_args() -> None: +def cli() -> None: 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('-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('--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') + # 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() - + # general facefusion.globals.source_path = args.source_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.frame_processors = args.frame_processors - facefusion.globals.ui_layouts = args.ui_layouts - facefusion.globals.keep_fps = args.keep_fps - facefusion.globals.keep_temp = args.keep_temp - facefusion.globals.skip_audio = args.skip_audio + # misc + facefusion.globals.skip_download = args.skip_download + facefusion.globals.headless = args.headless + # execution + 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_analyser_direction = args.face_analyser_direction facefusion.globals.face_analyser_age = args.face_analyser_age facefusion.globals.face_analyser_gender = args.face_analyser_gender 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_frame_number = args.reference_frame_number + # frame extraction facefusion.globals.trim_frame_start = args.trim_frame_start facefusion.globals.trim_frame_end = args.trim_frame_end facefusion.globals.temp_frame_format = args.temp_frame_format 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_video_encoder = args.output_video_encoder facefusion.globals.output_video_quality = args.output_video_quality - facefusion.globals.max_memory = args.max_memory - 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.skip_download = args.skip_download - facefusion.globals.headless = args.headless + facefusion.globals.keep_fps = args.keep_fps + facefusion.globals.skip_audio = args.skip_audio + # frame processors + available_frame_processors = list_module_names('facefusion/processors/frame/modules') + facefusion.globals.frame_processors = args.frame_processors + 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]: - return encode_execution_providers(onnxruntime.get_available_providers()) +def run(program : ArgumentParser) -> None: + 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: - if 'CUDAExecutionProvider' in onnxruntime.get_available_providers(): - return 8 - return 1 +def destroy() -> None: + if facefusion.globals.target_path: + clear_temp(facefusion.globals.target_path) + sys.exit() def limit_resources() -> None: # prevent tensorflow memory leak gpus = tensorflow.config.experimental.list_physical_devices('GPU') 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) ]) # limit memory usage @@ -122,10 +169,6 @@ def limit_resources() -> None: resource.setrlimit(resource.RLIMIT_DATA, (memory, memory)) -def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None: - print('[' + scope + '] ' + message) - - def pre_check() -> bool: if sys.version_info < (3, 9): update_status(wording.get('python_not_supported').format(version = '3.9')) @@ -136,6 +179,16 @@ def pre_check() -> bool: 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: if predict_image(facefusion.globals.target_path): return @@ -160,6 +213,7 @@ def process_video() -> None: if predict_video(facefusion.globals.target_path): return fps = detect_fps(facefusion.globals.target_path) if facefusion.globals.keep_fps else 25.0 + # create temp update_status(wording.get('creating_temp')) create_temp(facefusion.globals.target_path) # extract frames @@ -199,39 +253,5 @@ def process_video() -> None: update_status(wording.get('processing_video_failed')) -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 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() +def update_status(message : str, scope : str = 'FACEFUSION.CORE') -> None: + print('[' + scope + '] ' + message) diff --git a/facefusion/globals.py b/facefusion/globals.py index 262e9e03..c77dc5b4 100644 --- a/facefusion/globals.py +++ b/facefusion/globals.py @@ -2,31 +2,39 @@ from typing import List, Optional from facefusion.typing import FaceRecognition, FaceAnalyserDirection, FaceAnalyserAge, FaceAnalyserGender, TempFrameFormat, OutputVideoEncoder +# general source_path : Optional[str] = None target_path : Optional[str] = None output_path : Optional[str] = None -frame_processors : List[str] = [] -ui_layouts : List[str] = [] -keep_fps : Optional[bool] = None -keep_temp : Optional[bool] = None -skip_audio : Optional[bool] = None +# misc +skip_download : Optional[bool] = None +headless : Optional[bool] = None +# execution +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_analyser_direction : Optional[FaceAnalyserDirection] = None face_analyser_age : Optional[FaceAnalyserAge] = None face_analyser_gender : Optional[FaceAnalyserGender] = None reference_face_position : Optional[int] = None -reference_frame_number : Optional[int] = None reference_face_distance : Optional[float] = None +reference_frame_number : Optional[int] = None +# frame extraction trim_frame_start : Optional[int] = None trim_frame_end : Optional[int] = None temp_frame_format : Optional[TempFrameFormat] = None temp_frame_quality : Optional[int] = None +keep_temp : Optional[bool] = None +# output creation output_image_quality : Optional[int] = None output_video_encoder : Optional[OutputVideoEncoder] = None output_video_quality : Optional[int] = None -max_memory : Optional[int] = None -execution_providers : List[str] = [] -execution_thread_count : Optional[int] = None -execution_queue_count : Optional[int] = None -skip_download : Optional[bool] = None -headless : Optional[bool] = None +keep_fps : Optional[bool] = None +skip_audio : Optional[bool] = None +# frame processors +frame_processors : List[str] = [] +# uis +ui_layouts : List[str] = [] diff --git a/facefusion/installer.py b/facefusion/installer.py index d9999f53..72b87bd0 100644 --- a/facefusion/installer.py +++ b/facefusion/installer.py @@ -1,9 +1,6 @@ from typing import Dict, Tuple -import argparse -import os -import sys import subprocess -import tempfile +from argparse import ArgumentParser, HelpFormatter subprocess.call([ 'pip', 'install' , 'inquirer', '-q' ]) @@ -11,55 +8,61 @@ import inquirer 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]] =\ { 'cpu': ('onnxruntime', '1.16.0 '), 'cuda': ('onnxruntime-gpu', '1.16.0'), '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'), 'openvino': ('onnxruntime-openvino', '1.15.0') } -def run() -> None: - program = argparse.ArgumentParser(formatter_class = lambda prog: argparse.HelpFormatter(prog, max_help_position = 120)) - program.add_argument('--onnxruntime', help = wording.get('onnxruntime_help'), dest = 'onnxruntime', choices = ONNXRUNTIMES.keys()) +def cli() -> None: + program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 120)) + 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') + run(program) + + +def run(program : ArgumentParser) -> None: args = program.parse_args() if args.onnxruntime: answers =\ { + 'torch': args.torch, 'onnxruntime': args.onnxruntime } else: answers = inquirer.prompt( [ + inquirer.List( + 'torch', + message = wording.get('install_dependency_help').format(dependency = 'torch'), + choices = list(TORCH.keys()) + ), inquirer.List( 'onnxruntime', - message = wording.get('onnxruntime_help'), + message = wording.get('install_dependency_help').format(dependency = 'onnxruntime'), choices = list(ONNXRUNTIMES.keys()) ) ]) - if answers is not None: + torch = answers['torch'] + torch_url = TORCH[torch] onnxruntime = answers['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' ]) - if onnxruntime == 'cuda': - subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--extra-index-url', 'https://download.pytorch.org/whl/cu118' ]) - else: - subprocess.call([ 'pip', 'install', '-r', 'requirements.txt' ]) + subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--extra-index-url', torch_url ]) if onnxruntime != 'cpu': subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y' ]) - if onnxruntime != 'coreml-silicon': - 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) + subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version ]) diff --git a/facefusion/metadata.py b/facefusion/metadata.py index cce1a0f4..807f8513 100644 --- a/facefusion/metadata.py +++ b/facefusion/metadata.py @@ -2,7 +2,7 @@ METADATA =\ { 'name': 'FaceFusion', 'description': 'Next generation face swapper and enhancer', - 'version': '1.2.1', + 'version': '1.3.0', 'license': 'MIT', 'author': 'Henry Ruhs', 'url': 'https://facefusion.io' diff --git a/facefusion/predictor.py b/facefusion/predictor.py index 18a75a1f..b173ba12 100644 --- a/facefusion/predictor.py +++ b/facefusion/predictor.py @@ -1,5 +1,6 @@ import threading from functools import lru_cache + import numpy import opennsfw2 from PIL import Image @@ -10,6 +11,8 @@ from facefusion.typing import Frame PREDICTOR = None THREAD_LOCK : threading.Lock = threading.Lock() MAX_PROBABILITY = 0.75 +FRAME_INTERVAL = 25 +STREAM_COUNTER = 0 def get_predictor() -> Model: @@ -27,8 +30,17 @@ def clear_predictor() -> None: PREDICTOR = None -def predict_frame(target_frame : Frame) -> bool: - image = Image.fromarray(target_frame) +def predict_stream(frame : Frame) -> bool: + 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) views = numpy.expand_dims(image, axis = 0) _, probability = get_predictor().predict(views)[0] @@ -42,5 +54,5 @@ def predict_image(image_path : str) -> bool: @lru_cache(maxsize = None) 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) diff --git a/facefusion/processors/frame/choices.py b/facefusion/processors/frame/choices.py new file mode 100644 index 00000000..ad224411 --- /dev/null +++ b/facefusion/processors/frame/choices.py @@ -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' ] diff --git a/facefusion/processors/frame/core.py b/facefusion/processors/frame/core.py index 25fbc118..a0f767b8 100644 --- a/facefusion/processors/frame/core.py +++ b/facefusion/processors/frame/core.py @@ -5,17 +5,22 @@ import psutil from concurrent.futures import ThreadPoolExecutor, as_completed from queue import Queue from types import ModuleType -from typing import Any, List, Callable +from typing import Any, List from tqdm import tqdm import facefusion.globals from facefusion import wording +from facefusion.typing import Process_Frames FRAME_PROCESSORS_MODULES : List[ModuleType] = [] FRAME_PROCESSORS_METHODS =\ [ 'get_frame_processor', 'clear_frame_processor', + 'get_options', + 'set_options', + 'register_args', + 'apply_args', 'pre_check', 'pre_process', 'process_frame', @@ -57,7 +62,7 @@ def clear_frame_processors_modules() -> None: 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}]' 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: diff --git a/facefusion/processors/frame/globals.py b/facefusion/processors/frame/globals.py new file mode 100644 index 00000000..102a4466 --- /dev/null +++ b/facefusion/processors/frame/globals.py @@ -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 diff --git a/facefusion/processors/frame/modules/face_enhancer.py b/facefusion/processors/frame/modules/face_enhancer.py index fda37b0c..d8b6b589 100644 --- a/facefusion/processors/frame/modules/face_enhancer.py +++ b/facefusion/processors/frame/modules/face_enhancer.py @@ -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 -from gfpgan.utils import GFPGANer +import numpy +import onnxruntime import facefusion.globals -from facefusion import wording, utilities +from facefusion import wording from facefusion.core import update_status 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.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 THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore() THREAD_LOCK : threading.Lock = threading.Lock() NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_ENHANCER' -MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/GFPGANv1.4.pth' -MODEL_PATH = resolve_relative_path('../.assets/models/GFPGANv1.4.pth') +MODELS : Dict[str, ModelValue] =\ +{ + '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: @@ -23,11 +55,8 @@ def get_frame_processor() -> Any: with THREAD_LOCK: if FRAME_PROCESSOR is None: - FRAME_PROCESSOR = GFPGANer( - model_path = MODEL_PATH, - upscale = 1, - device = utilities.get_device(facefusion.globals.execution_providers) - ) + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = facefusion.globals.execution_providers) return FRAME_PROCESSOR @@ -37,18 +66,49 @@ def clear_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: if not facefusion.globals.skip_download: 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 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) 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) return False 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() -def enhance_face(target_face : Face, temp_frame : Frame) -> Frame: - start_x, start_y, end_x, end_y = map(int, target_face['bbox']) - padding_x = int((end_x - start_x) * 0.5) - padding_y = int((end_y - start_y) * 0.5) - start_x = max(0, start_x - padding_x) - start_y = max(0, start_y - padding_y) - end_x = max(0, end_x + padding_x) - end_y = max(0, end_y + padding_y) - crop_frame = temp_frame[start_y:end_y, start_x:end_x] - if crop_frame.size: - with THREAD_SEMAPHORE: - _, _, crop_frame = get_frame_processor().enhance( - crop_frame, - paste_back = True - ) - temp_frame[start_y:end_y, start_x:end_x] = crop_frame +def enhance_face(target_face: Face, temp_frame: Frame) -> Frame: + frame_processor = get_frame_processor() + crop_frame, affine_matrix = warp_face(target_face, temp_frame) + crop_frame = prepare_crop_frame(crop_frame) + frame_processor_inputs = {} + for frame_processor_input in frame_processor.get_inputs(): + if frame_processor_input.name == 'input': + frame_processor_inputs[frame_processor_input.name] = crop_frame + if frame_processor_input.name == 'weight': + frame_processor_inputs[frame_processor_input.name] = numpy.array([ 1 ], dtype = numpy.double) + with THREAD_SEMAPHORE: + crop_frame = frame_processor.run(None, frame_processor_inputs)[0][0] + crop_frame = normalize_crop_frame(crop_frame) + paste_frame = paste_back(temp_frame, crop_frame, affine_matrix) + temp_frame = blend_frame(temp_frame, paste_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 @@ -93,7 +208,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : 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: temp_frame = read_image(temp_frame_path) result_frame = process_frame(None, None, temp_frame) diff --git a/facefusion/processors/frame/modules/face_swapper.py b/facefusion/processors/frame/modules/face_swapper.py index 7b76316c..e8513d80 100644 --- a/facefusion/processors/frame/modules/face_swapper.py +++ b/facefusion/processors/frame/modules/face_swapper.py @@ -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 threading @@ -8,15 +9,29 @@ from facefusion import wording 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_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.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 THREAD_LOCK : threading.Lock = threading.Lock() NAME = 'FACEFUSION.FRAME_PROCESSOR.FACE_SWAPPER' -MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx' -MODEL_PATH = resolve_relative_path('../.assets/models/inswapper_128.onnx') +MODELS : Dict[str, ModelValue] =\ +{ + '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: @@ -24,7 +39,8 @@ def get_frame_processor() -> Any: with THREAD_LOCK: 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 @@ -34,18 +50,47 @@ def clear_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: if not facefusion.globals.skip_download: 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 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) 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) return False 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 -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)) reference_face = get_face_reference() if 'reference' in facefusion.globals.face_recognition else None for temp_frame_path in temp_frame_paths: diff --git a/facefusion/processors/frame/modules/frame_enhancer.py b/facefusion/processors/frame/modules/frame_enhancer.py index 55009353..1fe9be71 100644 --- a/facefusion/processors/frame/modules/frame_enhancer.py +++ b/facefusion/processors/frame/modules/frame_enhancer.py @@ -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 cv2 from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer import facefusion.globals 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.face_analyser import clear_face_analyser -from facefusion.typing import Frame, Face, ProcessMode -from facefusion.utilities import conditional_download, resolve_relative_path, is_file, is_download_done +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, get_device 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 THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore() THREAD_LOCK : threading.Lock = threading.Lock() NAME = 'FACEFUSION.FRAME_PROCESSOR.FRAME_ENHANCER' -MODEL_URL = 'https://github.com/facefusion/facefusion-assets/releases/download/models/RealESRGAN_x4plus.pth' -MODEL_PATH = resolve_relative_path('../.assets/models/RealESRGAN_x4plus.pth') +MODELS: Dict[str, ModelValue] =\ +{ + '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: @@ -25,21 +49,17 @@ def get_frame_processor() -> Any: with THREAD_LOCK: if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + model_scale = get_options('model').get('scale') FRAME_PROCESSOR = RealESRGANer( - model_path = MODEL_PATH, + model_path = model_path, model = RRDBNet( num_in_ch = 3, num_out_ch = 3, - num_feat = 64, - num_block = 23, - num_grow_ch = 32, - scale = 4 + scale = model_scale ), - device = utilities.get_device(facefusion.globals.execution_providers), - tile = 512, - tile_pad = 32, - pre_pad = 0, - scale = 4 + device = get_device(facefusion.globals.execution_providers), + scale = model_scale ) return FRAME_PROCESSOR @@ -50,18 +70,49 @@ def clear_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: if not facefusion.globals.skip_download: 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 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) 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) return False if mode == 'output' and not facefusion.globals.output_path: @@ -78,7 +129,15 @@ def post_process() -> None: def enhance_frame(temp_frame : Frame) -> Frame: 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 @@ -86,7 +145,7 @@ def process_frame(source_face : Face, reference_face : Face, temp_frame : 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: temp_frame = read_image(temp_frame_path) result_frame = process_frame(None, None, temp_frame) diff --git a/facefusion/typing.py b/facefusion/typing.py index f9debced..bfbdb849 100644 --- a/facefusion/typing.py +++ b/facefusion/typing.py @@ -1,9 +1,13 @@ -from typing import Any, Literal +from typing import Any, Literal, Callable, List, TypedDict, Dict from insightface.app.common import Face import numpy Face = Face 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' ] FaceRecognition = Literal[ 'reference', 'many' ] @@ -12,3 +16,9 @@ FaceAnalyserAge = Literal[ 'child', 'teen', 'adult', 'senior' ] FaceAnalyserGender = Literal[ 'male', 'female' ] TempFrameFormat = Literal[ 'jpg', 'png' ] OutputVideoEncoder = Literal[ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc' ] + +ModelValue = Dict['str', Any] +OptionsWithModel = TypedDict('OptionsWithModel', +{ + 'model' : ModelValue +}) diff --git a/facefusion/uis/assets/fixes.css b/facefusion/uis/assets/fixes.css new file mode 100644 index 00000000..f65a7cfd --- /dev/null +++ b/facefusion/uis/assets/fixes.css @@ -0,0 +1,7 @@ +:root:root:root button:not([class]) +{ + border-radius: 0.375rem; + float: left; + overflow: hidden; + width: 100%; +} diff --git a/facefusion/uis/assets/overrides.css b/facefusion/uis/assets/overrides.css new file mode 100644 index 00000000..25791ec9 --- /dev/null +++ b/facefusion/uis/assets/overrides.css @@ -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; +} diff --git a/facefusion/uis/choices.py b/facefusion/uis/choices.py index 89820a5f..0f3c919b 100644 --- a/facefusion/uis/choices.py +++ b/facefusion/uis/choices.py @@ -2,6 +2,6 @@ from typing import List from facefusion.uis.typing import WebcamMode -settings : List[str] = [ 'keep-fps', 'keep-temp', 'skip-audio', 'skip-download' ] -webcam_mode : List[WebcamMode] = [ 'inline', 'stream_udp', 'stream_v4l2' ] -webcam_resolution : List[str] = [ '320x240', '640x480', '1280x720', '1920x1080', '2560x1440', '3840x2160' ] +common_options : List[str] = [ 'keep-fps', 'keep-temp', 'skip-audio', 'skip-download' ] +webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ] +webcam_resolutions : List[str] = [ '320x240', '640x480', '1280x720', '1920x1080', '2560x1440', '3840x2160' ] diff --git a/facefusion/uis/components/about.py b/facefusion/uis/components/about.py index ba612206..e2c52caa 100644 --- a/facefusion/uis/components/about.py +++ b/facefusion/uis/components/about.py @@ -1,12 +1,23 @@ from typing import Optional 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: - global ABOUT_HTML + global ABOUT_BUTTON + global DONATE_BUTTON - ABOUT_HTML = gradio.HTML('
' + metadata.get('name') + ' ' + metadata.get('version') + '
') + 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' + ) diff --git a/facefusion/uis/components/benchmark.py b/facefusion/uis/components/benchmark.py index 4e528e25..09afe152 100644 --- a/facefusion/uis/components/benchmark.py +++ b/facefusion/uis/components/benchmark.py @@ -11,9 +11,8 @@ from facefusion.face_cache import clear_faces_cache from facefusion.processors.frame.core import get_frame_processors_modules from facefusion.vision import count_video_frame_total 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.uis.core import get_ui_component BENCHMARK_RESULTS_DATAFRAME : Optional[gradio.Dataframe] = None BENCHMARK_START_BUTTON : Optional[gradio.Button] = None @@ -58,16 +57,18 @@ def render() -> None: ) BENCHMARK_START_BUTTON = gradio.Button( value = wording.get('start_button_label'), - variant = 'primary' + variant = 'primary', + size = 'sm' ) BENCHMARK_CLEAR_BUTTON = gradio.Button( - value = wording.get('clear_button_label') + value = wording.get('clear_button_label'), + size = 'sm' ) def listen() -> None: - benchmark_runs_checkbox_group = ui.get_component('benchmark_runs_checkbox_group') - benchmark_cycles_slider = ui.get_component('benchmark_cycles_slider') + benchmark_runs_checkbox_group = get_ui_component('benchmark_runs_checkbox_group') + benchmark_cycles_slider = get_ui_component('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_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: clear_temp(facefusion.globals.target_path) - return gradio.update(value = None) + return gradio.Dataframe(value = None) diff --git a/facefusion/uis/components/benchmark_settings.py b/facefusion/uis/components/benchmark_options.py similarity index 57% rename from facefusion/uis/components/benchmark_settings.py rename to facefusion/uis/components/benchmark_options.py index 330f9d84..75767a88 100644 --- a/facefusion/uis/components/benchmark_settings.py +++ b/facefusion/uis/components/benchmark_options.py @@ -1,9 +1,8 @@ -from typing import Optional, List +from typing import Optional import gradio from facefusion import wording -from facefusion.uis.typing import Update -from facefusion.uis import core as ui +from facefusion.uis.core import register_ui_component from facefusion.uis.components.benchmark import BENCHMARKS BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None @@ -21,18 +20,10 @@ def render() -> None: ) BENCHMARK_CYCLES_SLIDER = gradio.Slider( label = wording.get('benchmark_cycles_slider_label'), - minimum = 1, - step = 1, value = 3, + step = 1, + minimum = 1, maximum = 10 ) - ui.register_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP) - ui.register_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) + register_ui_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP) + register_ui_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER) diff --git a/facefusion/uis/components/common_options.py b/facefusion/uis/components/common_options.py new file mode 100644 index 00000000..18a6962d --- /dev/null +++ b/facefusion/uis/components/common_options.py @@ -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 diff --git a/facefusion/uis/components/execution.py b/facefusion/uis/components/execution.py index b574d74b..632d38cf 100644 --- a/facefusion/uis/components/execution.py +++ b/facefusion/uis/components/execution.py @@ -6,7 +6,6 @@ import facefusion.globals from facefusion import wording from facefusion.face_analyser import clear_face_analyser 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 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) -def update_execution_providers(execution_providers : List[str]) -> Update: +def update_execution_providers(execution_providers : List[str]) -> gradio.CheckboxGroup: clear_face_analyser() clear_frame_processors_modules() if not execution_providers: execution_providers = encode_execution_providers(onnxruntime.get_available_providers()) facefusion.globals.execution_providers = decode_execution_providers(execution_providers) - return gradio.update(value = execution_providers) + return gradio.CheckboxGroup(value = execution_providers) diff --git a/facefusion/uis/components/execution_queue_count.py b/facefusion/uis/components/execution_queue_count.py index 3f5cf6a5..75f3c455 100644 --- a/facefusion/uis/components/execution_queue_count.py +++ b/facefusion/uis/components/execution_queue_count.py @@ -3,7 +3,6 @@ import gradio import facefusion.globals from facefusion import wording -from facefusion.uis.typing import Update EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None @@ -21,9 +20,9 @@ def render() -> 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 - return gradio.update(value = execution_queue_count) + diff --git a/facefusion/uis/components/execution_thread_count.py b/facefusion/uis/components/execution_thread_count.py index 162ab5b5..2353ec11 100644 --- a/facefusion/uis/components/execution_thread_count.py +++ b/facefusion/uis/components/execution_thread_count.py @@ -3,7 +3,6 @@ import gradio import facefusion.globals from facefusion import wording -from facefusion.uis.typing import Update EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None @@ -21,9 +20,9 @@ def render() -> 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 - return gradio.update(value = execution_thread_count) + diff --git a/facefusion/uis/components/face_analyser.py b/facefusion/uis/components/face_analyser.py index daca1efc..bb533214 100644 --- a/facefusion/uis/components/face_analyser.py +++ b/facefusion/uis/components/face_analyser.py @@ -5,8 +5,7 @@ import gradio import facefusion.choices import facefusion.globals from facefusion import wording -from facefusion.uis import core as ui -from facefusion.uis.typing import Update +from facefusion.uis.core import register_ui_component FACE_ANALYSER_DIRECTION_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( 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 ) FACE_ANALYSER_AGE_DROPDOWN = gradio.Dropdown( 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' ) FACE_ANALYSER_GENDER_DROPDOWN = gradio.Dropdown( 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' ) - ui.register_component('face_analyser_direction_dropdown', FACE_ANALYSER_DIRECTION_DROPDOWN) - ui.register_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_direction_dropdown', FACE_ANALYSER_DIRECTION_DROPDOWN) + register_ui_component('face_analyser_age_dropdown', FACE_ANALYSER_AGE_DROPDOWN) + register_ui_component('face_analyser_gender_dropdown', FACE_ANALYSER_GENDER_DROPDOWN) 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_AGE_DROPDOWN.select(lambda value: update_dropdown('face_analyser_age', value), inputs = FACE_ANALYSER_AGE_DROPDOWN, outputs = 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_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) + 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': setattr(facefusion.globals, name, None) else: setattr(facefusion.globals, name, value) - return gradio.update(value = value) diff --git a/facefusion/uis/components/face_selector.py b/facefusion/uis/components/face_selector.py index 34e1332b..140d9dea 100644 --- a/facefusion/uis/components/face_selector.py +++ b/facefusion/uis/components/face_selector.py @@ -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_reference import clear_face_reference 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.uis.core import get_ui_component, register_ui_component +from facefusion.uis.typing import ComponentName FACE_RECOGNITION_DROPDOWN : Optional[gradio.Dropdown] = 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) FACE_RECOGNITION_DROPDOWN = gradio.Dropdown( label = wording.get('face_recognition_dropdown_label'), - choices = facefusion.choices.face_recognition, + choices = facefusion.choices.face_recognitions, value = facefusion.globals.face_recognition ) REFERENCE_FACE_POSITION_GALLERY = gradio.Gallery(**reference_face_gallery_args) REFERENCE_FACE_DISTANCE_SLIDER = gradio.Slider( label = wording.get('reference_face_distance_slider_label'), value = facefusion.globals.reference_face_distance, - maximum = 3, step = 0.05, + minimum = 0, + maximum = 3, visible = 'reference' in facefusion.globals.face_recognition ) - ui.register_component('face_recognition_dropdown', FACE_RECOGNITION_DROPDOWN) - ui.register_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY) - ui.register_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER) + register_ui_component('face_recognition_dropdown', FACE_RECOGNITION_DROPDOWN) + register_ui_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY) + register_ui_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER) def listen() -> None: @@ -67,7 +68,7 @@ def listen() -> None: 'target_video' ] for component_name in multi_component_names: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: for method in [ 'upload', 'change', 'clear' ]: getattr(component, method)(update_face_reference_position, outputs = REFERENCE_FACE_POSITION_GALLERY) @@ -78,29 +79,29 @@ def listen() -> None: 'face_analyser_gender_dropdown' ] for component_name in select_component_names: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: 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: 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': 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': 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() 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 = [] facefusion.globals.reference_face_position = reference_face_position 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) gallery_frames = extract_gallery_frames(reference_frame) if gallery_frames: - return gradio.update(value = gallery_frames) - return gradio.update(value = None) + return gradio.Gallery(value = gallery_frames) + 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 - return gradio.update(value = reference_face_distance) def extract_gallery_frames(reference_frame : Frame) -> List[Frame]: diff --git a/facefusion/uis/components/processors.py b/facefusion/uis/components/frame_processors.py similarity index 65% rename from facefusion/uis/components/processors.py rename to facefusion/uis/components/frame_processors.py index 3f6de4e5..861e5771 100644 --- a/facefusion/uis/components/processors.py +++ b/facefusion/uis/components/frame_processors.py @@ -4,9 +4,8 @@ import gradio import facefusion.globals from facefusion import wording 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.uis.core import register_ui_component FRAME_PROCESSORS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None @@ -19,23 +18,23 @@ def render() -> None: choices = sort_frame_processors(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: 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: - clear_frame_processors_modules() +def update_frame_processors(frame_processors : List[str]) -> gradio.CheckboxGroup: facefusion.globals.frame_processors = frame_processors + clear_frame_processors_modules() for frame_processor in frame_processors: frame_processor_module = load_frame_processor_module(frame_processor) if not frame_processor_module.pre_check(): - return gradio.update() - return gradio.update(value = frame_processors, choices = sort_frame_processors(frame_processors)) + return gradio.CheckboxGroup() + return gradio.CheckboxGroup(value = frame_processors, choices = sort_frame_processors(frame_processors)) def sort_frame_processors(frame_processors : List[str]) -> list[str]: - frame_processors_names = 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)) + available_frame_processors = list_module_names('facefusion/processors/frame/modules') + return sorted(available_frame_processors, key = lambda frame_processor : frame_processors.index(frame_processor) if frame_processor in frame_processors else len(frame_processors)) diff --git a/facefusion/uis/components/frame_processors_options.py b/facefusion/uis/components/frame_processors_options.py new file mode 100644 index 00000000..7125ea21 --- /dev/null +++ b/facefusion/uis/components/frame_processors_options.py @@ -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) diff --git a/facefusion/uis/components/limit_resources.py b/facefusion/uis/components/limit_resources.py index 36e0030b..7b6a4298 100644 --- a/facefusion/uis/components/limit_resources.py +++ b/facefusion/uis/components/limit_resources.py @@ -3,7 +3,6 @@ import gradio import facefusion.globals from facefusion import wording -from facefusion.uis.typing import Update MAX_MEMORY_SLIDER : Optional[gradio.Slider] = None @@ -13,16 +12,15 @@ def render() -> None: MAX_MEMORY_SLIDER = gradio.Slider( label = wording.get('max_memory_slider_label'), + step = 1, minimum = 0, - maximum = 128, - step = 1 + maximum = 128 ) 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 - return gradio.update(value = max_memory) diff --git a/facefusion/uis/components/output.py b/facefusion/uis/components/output.py index 62cf7014..81156e69 100644 --- a/facefusion/uis/components/output.py +++ b/facefusion/uis/components/output.py @@ -1,16 +1,14 @@ -import tempfile from typing import Tuple, Optional import gradio import facefusion.globals from facefusion import wording 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 OUTPUT_IMAGE : Optional[gradio.Image] = None OUTPUT_VIDEO : Optional[gradio.Video] = None -OUTPUT_PATH_TEXTBOX : Optional[gradio.Textbox] = None OUTPUT_START_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: global OUTPUT_IMAGE global OUTPUT_VIDEO - global OUTPUT_PATH_TEXTBOX global OUTPUT_START_BUTTON global OUTPUT_CLEAR_BUTTON @@ -29,43 +26,36 @@ def render() -> None: OUTPUT_VIDEO = gradio.Video( 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( value = wording.get('start_button_label'), - variant = 'primary' + variant = 'primary', + size = 'sm' ) OUTPUT_CLEAR_BUTTON = gradio.Button( value = wording.get('clear_button_label'), + size = 'sm' ) def listen() -> None: - OUTPUT_PATH_TEXTBOX.change(update_output_path, inputs = OUTPUT_PATH_TEXTBOX, outputs = OUTPUT_PATH_TEXTBOX) - OUTPUT_START_BUTTON.click(start, inputs = OUTPUT_PATH_TEXTBOX, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ]) + output_path_textbox = get_ui_component('output_path_textbox') + 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 ]) -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) limit_resources() conditional_process() 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): - return gradio.update(value = None, visible = False), gradio.update(value = facefusion.globals.output_path, visible = True) - return gradio.update(), gradio.update() + return gradio.Image(value = None, visible = False), gradio.Video(value = facefusion.globals.output_path, visible = True) + return gradio.Image(), gradio.Video() -def update_output_path(output_path : str) -> Update: - facefusion.globals.output_path = output_path - return gradio.update(value = output_path) - - -def clear() -> Tuple[Update, Update]: +def clear() -> Tuple[gradio.Image, gradio.Video]: if 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) diff --git a/facefusion/uis/components/output_settings.py b/facefusion/uis/components/output_options.py similarity index 59% rename from facefusion/uis/components/output_settings.py rename to facefusion/uis/components/output_options.py index af4e6bea..db5d01ec 100644 --- a/facefusion/uis/components/output_settings.py +++ b/facefusion/uis/components/output_options.py @@ -1,33 +1,43 @@ from typing import Optional, Tuple, List +import tempfile import gradio import facefusion.choices import facefusion.globals from facefusion import wording 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.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_VIDEO_ENCODER_DROPDOWN : Optional[gradio.Dropdown] = None OUTPUT_VIDEO_QUALITY_SLIDER : Optional[gradio.Slider] = None def render() -> None: + global OUTPUT_PATH_TEXTBOX global OUTPUT_IMAGE_QUALITY_SLIDER global OUTPUT_VIDEO_ENCODER_DROPDOWN 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( label = wording.get('output_image_quality_slider_label'), value = facefusion.globals.output_image_quality, step = 1, + minimum = 0, + maximum = 100, visible = is_image(facefusion.globals.target_path) ) OUTPUT_VIDEO_ENCODER_DROPDOWN = gradio.Dropdown( 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, visible = is_video(facefusion.globals.target_path) ) @@ -35,14 +45,18 @@ def render() -> None: label = wording.get('output_video_quality_slider_label'), value = facefusion.globals.output_video_quality, step = 1, + minimum = 0, + maximum = 100, visible = is_video(facefusion.globals.target_path) ) + register_ui_component('output_path_textbox', OUTPUT_PATH_TEXTBOX) def listen() -> None: - OUTPUT_IMAGE_QUALITY_SLIDER.change(update_output_image_quality, inputs = OUTPUT_IMAGE_QUALITY_SLIDER, outputs = OUTPUT_IMAGE_QUALITY_SLIDER) - OUTPUT_VIDEO_ENCODER_DROPDOWN.select(update_output_video_encoder, inputs = OUTPUT_VIDEO_ENCODER_DROPDOWN, outputs = OUTPUT_VIDEO_ENCODER_DROPDOWN) - OUTPUT_VIDEO_QUALITY_SLIDER.change(update_output_video_quality, inputs = OUTPUT_VIDEO_QUALITY_SLIDER, outputs = OUTPUT_VIDEO_QUALITY_SLIDER) + OUTPUT_PATH_TEXTBOX.change(update_output_path, inputs = OUTPUT_PATH_TEXTBOX) + OUTPUT_IMAGE_QUALITY_SLIDER.change(update_output_image_quality, inputs = OUTPUT_IMAGE_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] =\ [ 'source_image', @@ -50,30 +64,31 @@ def listen() -> None: 'target_video' ] for component_name in multi_component_names: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: for method in [ 'upload', 'change', 'clear' ]: 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): - 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): - return gradio.update(visible = False), gradio.update(visible = True), gradio.update(visible = True) - return gradio.update(visible = False), gradio.update(visible = False), gradio.update(visible = False) + return gradio.Slider(visible = False), gradio.Dropdown(visible = True), gradio.Slider(visible = True) + 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 - 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 - 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 - return gradio.update(value = output_video_quality) diff --git a/facefusion/uis/components/preview.py b/facefusion/uis/components/preview.py index f03d0d61..92a15b38 100644 --- a/facefusion/uis/components/preview.py +++ b/facefusion/uis/components/preview.py @@ -4,15 +4,15 @@ import gradio import facefusion.globals 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.face_analyser import get_one_face from facefusion.face_reference import get_face_reference, set_face_reference from facefusion.predictor import predict_frame 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.uis.typing import ComponentName +from facefusion.uis.core import get_ui_component, register_ui_component PREVIEW_IMAGE : Optional[gradio.Image] = None PREVIEW_FRAME_SLIDER : Optional[gradio.Slider] = None @@ -24,12 +24,15 @@ def render() -> None: 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] =\ { 'label': wording.get('preview_frame_slider_label'), 'step': 1, + 'minimum': 0, + 'maximum': 100, 'visible': False } conditional_set_face_reference() @@ -49,7 +52,7 @@ def render() -> None: preview_frame_slider_args['visible'] = True PREVIEW_IMAGE = gradio.Image(**preview_image_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: @@ -61,7 +64,7 @@ def listen() -> None: 'target_video' ] for component_name in multi_component_names: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: for method in [ 'upload', 'change', 'clear' ]: 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] =\ [ '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: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) select_component_names : List[ComponentName] =\ @@ -83,15 +89,22 @@ def listen() -> None: 'face_analyser_gender_dropdown' ] for component_name in select_component_names: - component = ui.get_component(component_name) + component = get_ui_component(component_name) if component: component.select(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) - reference_face_distance_slider = ui.get_component('reference_face_distance_slider') - if reference_face_distance_slider: - reference_face_distance_slider.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + change_component_names : List[ComponentName] =\ + [ + '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() 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 @@ -99,30 +112,30 @@ def update_preview_image(frame_number : int = 0) -> Update: target_frame = read_static_image(facefusion.globals.target_path) preview_frame = process_preview_frame(source_face, reference_face, target_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): facefusion.globals.reference_frame_number = 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 = normalize_frame_color(preview_frame) - return gradio.update(value = preview_frame) - return gradio.update(value = None) + return gradio.Image(value = preview_frame) + 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): - 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): facefusion.globals.reference_frame_number = frame_number video_frame_total = count_video_frame_total(facefusion.globals.target_path) - return gradio.update(maximum = video_frame_total, visible = True) - return gradio.update(value = None, maximum = None, visible = False) + return gradio.Slider(maximum = video_frame_total, visible = True) + return gradio.Slider() 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): return cv2.GaussianBlur(temp_frame, (99, 99), 0) - temp_frame = resize_frame_dimension(temp_frame, 480) for frame_processor in facefusion.globals.frame_processors: frame_processor_module = load_frame_processor_module(frame_processor) if frame_processor_module.pre_process('preview'): diff --git a/facefusion/uis/components/settings.py b/facefusion/uis/components/settings.py deleted file mode 100644 index 4c8f6cfd..00000000 --- a/facefusion/uis/components/settings.py +++ /dev/null @@ -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) diff --git a/facefusion/uis/components/source.py b/facefusion/uis/components/source.py index 0a631445..37777ea4 100644 --- a/facefusion/uis/components/source.py +++ b/facefusion/uis/components/source.py @@ -3,9 +3,8 @@ import gradio import facefusion.globals 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.uis.core import register_ui_component SOURCE_FILE : Optional[gradio.File] = None SOURCE_IMAGE : Optional[gradio.Image] = None @@ -32,16 +31,16 @@ def render() -> None: visible = is_source_image, show_label = False ) - ui.register_component('source_image', SOURCE_IMAGE) + register_ui_component('source_image', SOURCE_IMAGE) def listen() -> None: 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): 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 - return gradio.update(value = None, visible = False) + return gradio.Image(value = None, visible = False) diff --git a/facefusion/uis/components/target.py b/facefusion/uis/components/target.py index 0eb53867..cd404bbd 100644 --- a/facefusion/uis/components/target.py +++ b/facefusion/uis/components/target.py @@ -4,9 +4,8 @@ import gradio import facefusion.globals from facefusion import wording 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.uis.core import register_ui_component TARGET_FILE : Optional[gradio.File] = None TARGET_IMAGE : Optional[gradio.Image] = None @@ -42,21 +41,21 @@ def render() -> None: visible = is_target_video, show_label = False ) - ui.register_component('target_image', TARGET_IMAGE) - ui.register_component('target_video', TARGET_VIDEO) + register_ui_component('target_image', TARGET_IMAGE) + register_ui_component('target_video', TARGET_VIDEO) def listen() -> None: 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() if file and is_image(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): 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 - 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) diff --git a/facefusion/uis/components/temp_frame.py b/facefusion/uis/components/temp_frame.py index e0431712..3b345f66 100644 --- a/facefusion/uis/components/temp_frame.py +++ b/facefusion/uis/components/temp_frame.py @@ -5,9 +5,8 @@ import facefusion.choices import facefusion.globals from facefusion import wording 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.uis.core import get_ui_component TEMP_FRAME_FORMAT_DROPDOWN : Optional[gradio.Dropdown] = None TEMP_FRAME_QUALITY_SLIDER : Optional[gradio.Slider] = None @@ -19,7 +18,7 @@ def render() -> None: TEMP_FRAME_FORMAT_DROPDOWN = gradio.Dropdown( 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, visible = is_video(facefusion.globals.target_path) ) @@ -27,30 +26,30 @@ def render() -> None: label = wording.get('temp_frame_quality_slider_label'), value = facefusion.globals.temp_frame_quality, step = 1, + minimum = 0, + maximum = 100, visible = is_video(facefusion.globals.target_path) ) def listen() -> None: - TEMP_FRAME_FORMAT_DROPDOWN.select(update_temp_frame_format, inputs = TEMP_FRAME_FORMAT_DROPDOWN, outputs = TEMP_FRAME_FORMAT_DROPDOWN) - TEMP_FRAME_QUALITY_SLIDER.change(update_temp_frame_quality, inputs = TEMP_FRAME_QUALITY_SLIDER, outputs = TEMP_FRAME_QUALITY_SLIDER) - target_video = ui.get_component('target_video') + 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) + target_video = get_ui_component('target_video') if target_video: for method in [ 'upload', 'change', 'clear' ]: 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): - return gradio.update(visible = True), gradio.update(visible = True) - return gradio.update(visible = False), gradio.update(visible = False) + return gradio.Dropdown(visible = True), gradio.Slider(visible = True) + 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 - 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 - return gradio.update(value = temp_frame_quality) diff --git a/facefusion/uis/components/trim_frame.py b/facefusion/uis/components/trim_frame.py index b7ff6d58..82e771e4 100644 --- a/facefusion/uis/components/trim_frame.py +++ b/facefusion/uis/components/trim_frame.py @@ -4,9 +4,8 @@ import gradio import facefusion.globals from facefusion import wording 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.uis.core import get_ui_component TRIM_FRAME_START_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'), 'step': 1, + 'minimum': 0, + 'maximum': 100, 'visible': False } trim_frame_end_slider_args : Dict[str, Any] =\ { 'label': wording.get('trim_frame_end_slider_label'), 'step': 1, + 'minimum': 0, + 'maximum': 100, 'visible': False } if is_video(facefusion.globals.target_path): @@ -41,29 +44,27 @@ def render() -> 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_END_SLIDER.change(update_trim_frame_end, inputs = TRIM_FRAME_END_SLIDER, outputs = TRIM_FRAME_END_SLIDER) - target_video = ui.get_component('target_video') + 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) + target_video = get_ui_component('target_video') if target_video: for method in [ 'upload', 'change', 'clear' ]: 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): video_frame_total = count_video_frame_total(facefusion.globals.target_path) facefusion.globals.trim_frame_start = 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.update(value = None, maximum = None, visible = False), gradio.update(value = None, maximum = None, visible = False) + 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.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 - 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) facefusion.globals.trim_frame_end = trim_frame_end if trim_frame_end < video_frame_total else None - return gradio.update(value = trim_frame_end) diff --git a/facefusion/uis/components/webcam.py b/facefusion/uis/components/webcam.py index e90d4971..0431228c 100644 --- a/facefusion/uis/components/webcam.py +++ b/facefusion/uis/components/webcam.py @@ -10,13 +10,14 @@ from tqdm import tqdm import facefusion.globals from facefusion import wording +from facefusion.predictor import predict_stream from facefusion.typing import Frame, Face from facefusion.face_analyser import get_one_face -from facefusion.processors.frame.core import load_frame_processor_module -from facefusion.uis import core as ui -from facefusion.uis.typing import StreamMode, WebcamMode, Update +from facefusion.processors.frame.core import get_frame_processors_modules from facefusion.utilities import open_ffmpeg 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_START_BUTTON : Optional[gradio.Button] = None @@ -33,25 +34,27 @@ def render() -> None: ) WEBCAM_START_BUTTON = gradio.Button( value = wording.get('start_button_label'), - variant = 'primary' + variant = 'primary', + size = 'sm' ) WEBCAM_STOP_BUTTON = gradio.Button( - value = wording.get('stop_button_label') + value = wording.get('stop_button_label'), + size = 'sm' ) def listen() -> None: start_event = None - webcam_mode_radio = ui.get_component('webcam_mode_radio') - webcam_resolution_dropdown = ui.get_component('webcam_resolution_dropdown') - webcam_fps_slider = ui.get_component('webcam_fps_slider') + webcam_mode_radio = get_ui_component('webcam_mode_radio') + webcam_resolution_dropdown = get_ui_component('webcam_resolution_dropdown') + webcam_fps_slider = get_ui_component('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) webcam_mode_radio.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_STOP_BUTTON.click(stop, cancels = start_event) - source_image = ui.get_component('source_image') + source_image = get_ui_component('source_image') if source_image: for method in [ 'upload', 'change', 'clear' ]: 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' source_face = get_one_face(read_static_image(facefusion.globals.source_path)) stream = None - if mode == 'stream_udp': - stream = open_stream('udp', resolution, fps) - if mode == 'stream_v4l2': - stream = open_stream('v4l2', resolution, fps) + if mode in [ 'udp', 'v4l2' ]: + stream = open_stream(mode, resolution, fps) # type: ignore[arg-type] capture = capture_webcam(resolution, fps) if capture.isOpened(): 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() while True: _, capture_frame = capture.read() + if predict_stream(capture_frame): + return future = executor.submit(process_stream_frame, source_face, capture_frame) futures.append(future) 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() -def stop() -> Update: - return gradio.update(value = None) +def stop() -> gradio.Image: + return gradio.Image(value = None) 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: - for frame_processor in facefusion.globals.frame_processors: - frame_processor_module = load_frame_processor_module(frame_processor) + for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors): if frame_processor_module.pre_process('stream'): temp_frame = frame_processor_module.process_frame( source_face, diff --git a/facefusion/uis/components/webcam_settings.py b/facefusion/uis/components/webcam_options.py similarity index 60% rename from facefusion/uis/components/webcam_settings.py rename to facefusion/uis/components/webcam_options.py index 321f0438..2876e616 100644 --- a/facefusion/uis/components/webcam_settings.py +++ b/facefusion/uis/components/webcam_options.py @@ -3,8 +3,7 @@ import gradio from facefusion import wording from facefusion.uis import choices -from facefusion.uis import core as ui -from facefusion.uis.typing import Update +from facefusion.uis.core import register_ui_component WEBCAM_MODE_RADIO : Optional[gradio.Radio] = None WEBCAM_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None @@ -18,25 +17,21 @@ def render() -> None: WEBCAM_MODE_RADIO = gradio.Radio( label = wording.get('webcam_mode_radio_label'), - choices = choices.webcam_mode, + choices = choices.webcam_modes, value = 'inline' ) WEBCAM_RESOLUTION_DROPDOWN = gradio.Dropdown( label = wording.get('webcam_resolution_dropdown'), - choices = choices.webcam_resolution, - value = choices.webcam_resolution[0] + choices = choices.webcam_resolutions, + value = choices.webcam_resolutions[0] ) WEBCAM_FPS_SLIDER = gradio.Slider( label = wording.get('webcam_fps_slider'), - minimum = 1, - maximum = 60, + value = 25, step = 1, - value = 25 + minimum = 1, + maximum = 60 ) - ui.register_component('webcam_mode_radio', WEBCAM_MODE_RADIO) - ui.register_component('webcam_resolution_dropdown', WEBCAM_RESOLUTION_DROPDOWN) - ui.register_component('webcam_fps_slider', WEBCAM_FPS_SLIDER) - - -def update() -> Update: - return gradio.update(value = None) + register_ui_component('webcam_mode_radio', WEBCAM_MODE_RADIO) + register_ui_component('webcam_resolution_dropdown', WEBCAM_RESOLUTION_DROPDOWN) + register_ui_component('webcam_fps_slider', WEBCAM_FPS_SLIDER) diff --git a/facefusion/uis/core.py b/facefusion/uis/core.py index f6a02591..8e52e9a5 100644 --- a/facefusion/uis/core.py +++ b/facefusion/uis/core.py @@ -1,5 +1,5 @@ -from types import ModuleType from typing import Dict, Optional, Any, List +from types import ModuleType import importlib import sys import gradio @@ -7,8 +7,9 @@ import gradio import facefusion.globals from facefusion import metadata, wording 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_METHODS =\ [ @@ -43,8 +44,18 @@ def get_ui_layouts_modules(ui_layouts : List[str]) -> List[ModuleType]: 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: - 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: ui_layout_module = load_ui_layout_module(ui_layout) if ui_layout_module.pre_render(): @@ -57,22 +68,63 @@ def launch() -> None: def get_theme() -> gradio.Theme: - return gradio.themes.Soft( + return gradio.themes.Base( primary_hue = gradio.themes.colors.red, - secondary_hue = gradio.themes.colors.gray, - font = gradio.themes.GoogleFont('Inter') + secondary_hue = gradio.themes.colors.neutral, + font = gradio.themes.GoogleFont('Open Sans') ).set( - background_fill_primary = '*neutral_50', - block_label_text_size = '*text_sm', - block_title_text_size = '*text_sm' + background_fill_primary = '*neutral_100', + block_background_fill = 'white', + 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]: - if name in COMPONENTS: - return COMPONENTS[name] - return None - - -def register_component(name: ComponentName, component: Component) -> None: - COMPONENTS[name] = component +def get_css() -> str: + fixes_css_path = resolve_relative_path('uis/assets/fixes.css') + overrides_css_path = resolve_relative_path('uis/assets/overrides.css') + return open(fixes_css_path, 'r').read() + open(overrides_css_path, 'r').read() diff --git a/facefusion/uis/layouts/benchmark.py b/facefusion/uis/layouts/benchmark.py index a39694fd..2a197f6e 100644 --- a/facefusion/uis/layouts/benchmark.py +++ b/facefusion/uis/layouts/benchmark.py @@ -1,8 +1,8 @@ import gradio 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.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: @@ -30,10 +30,11 @@ def render() -> gradio.Blocks: with gradio.Blocks() as layout: with gradio.Row(): with gradio.Column(scale = 2): - with gradio.Box(): + with gradio.Blocks(): about.render() with gradio.Blocks(): - processors.render() + frame_processors.render() + frame_processors_options.render() with gradio.Blocks(): execution.render() execution_thread_count.render() @@ -41,7 +42,7 @@ def render() -> gradio.Blocks: with gradio.Blocks(): limit_resources.render() with gradio.Blocks(): - benchmark_settings.render() + benchmark_options.render() with gradio.Column(scale= 5): with gradio.Blocks(): benchmark.render() @@ -49,15 +50,14 @@ def render() -> gradio.Blocks: def listen() -> None: - processors.listen() + frame_processors.listen() + frame_processors_options.listen() execution.listen() execution_thread_count.listen() execution_queue_count.listen() limit_resources.listen() - benchmark_settings.listen() benchmark.listen() def run(ui : gradio.Blocks) -> None: - ui.queue(concurrency_count = 2, api_open = False) - ui.launch(show_api = False) + ui.queue(concurrency_count = 2, api_open = False).launch(show_api = False) diff --git a/facefusion/uis/layouts/default.py b/facefusion/uis/layouts/default.py index 1ac69068..d13dbad9 100644 --- a/facefusion/uis/layouts/default.py +++ b/facefusion/uis/layouts/default.py @@ -1,6 +1,6 @@ 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: @@ -15,10 +15,11 @@ def render() -> gradio.Blocks: with gradio.Blocks() as layout: with gradio.Row(): with gradio.Column(scale = 2): - with gradio.Box(): + with gradio.Blocks(): about.render() with gradio.Blocks(): - processors.render() + frame_processors.render() + frame_processors_options.render() with gradio.Blocks(): execution.render() execution_thread_count.render() @@ -28,9 +29,7 @@ def render() -> gradio.Blocks: with gradio.Blocks(): temp_frame.render() with gradio.Blocks(): - output_settings.render() - with gradio.Blocks(): - settings.render() + output_options.render() with gradio.Column(scale = 2): with gradio.Blocks(): source.render() @@ -47,18 +46,21 @@ def render() -> gradio.Blocks: face_selector.render() with gradio.Row(): face_analyser.render() + with gradio.Blocks(): + common_options.render() return layout def listen() -> None: - processors.listen() + frame_processors.listen() + frame_processors_options.listen() execution.listen() execution_thread_count.listen() execution_queue_count.listen() limit_resources.listen() temp_frame.listen() - output_settings.listen() - settings.listen() + output_options.listen() + common_options.listen() source.listen() target.listen() preview.listen() diff --git a/facefusion/uis/layouts/webcam.py b/facefusion/uis/layouts/webcam.py index 6feb9482..a5b6e184 100644 --- a/facefusion/uis/layouts/webcam.py +++ b/facefusion/uis/layouts/webcam.py @@ -1,6 +1,6 @@ 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: @@ -15,15 +15,16 @@ def render() -> gradio.Blocks: with gradio.Blocks() as layout: with gradio.Row(): with gradio.Column(scale = 2): - with gradio.Box(): + with gradio.Blocks(): about.render() with gradio.Blocks(): - processors.render() + frame_processors.render() + frame_processors_options.render() with gradio.Blocks(): execution.render() execution_thread_count.render() with gradio.Blocks(): - webcam_settings.render() + webcam_options.render() with gradio.Blocks(): source.render() with gradio.Column(scale = 5): @@ -33,7 +34,8 @@ def render() -> gradio.Blocks: def listen() -> None: - processors.listen() + frame_processors.listen() + frame_processors_options.listen() execution.listen() execution_thread_count.listen() source.listen() @@ -41,5 +43,4 @@ def listen() -> None: def run(ui : gradio.Blocks) -> None: - ui.queue(concurrency_count = 2, api_open = False) - ui.launch(show_api = False) + ui.queue(concurrency_count = 2, api_open = False).launch(show_api = False) diff --git a/facefusion/uis/typing.py b/facefusion/uis/typing.py index 0e888085..e7e800c6 100644 --- a/facefusion/uis/typing.py +++ b/facefusion/uis/typing.py @@ -1,4 +1,4 @@ -from typing import Literal, Dict, Any +from typing import Literal import gradio Component = gradio.File or gradio.Image or gradio.Video or gradio.Slider @@ -15,12 +15,18 @@ ComponentName = Literal\ 'face_analyser_age_dropdown', 'face_analyser_gender_dropdown', '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_cycles_slider', + 'player_url_textbox_label', 'webcam_mode_radio', 'webcam_resolution_dropdown', 'webcam_fps_slider' ] -WebcamMode = Literal[ 'inline', 'stream_udp', 'stream_v4l2' ] +WebcamMode = Literal[ 'inline', 'udp', 'v4l2' ] StreamMode = Literal[ 'udp', 'v4l2' ] -Update = Dict[Any, Any] diff --git a/facefusion/utilities.py b/facefusion/utilities.py index 497467c6..48fb860b 100644 --- a/facefusion/utilities.py +++ b/facefusion/utilities.py @@ -70,13 +70,13 @@ def merge_video(target_path : str, fps : float) -> bool: 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 ] 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) ]) 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) ]) 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([ '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_output_video_path ]) return run_ffmpeg(commands) @@ -187,7 +187,7 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non initial = 0 if initial < total: 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 while current < total: 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) -def get_download_size(url : str) -> Optional[int]: +def get_download_size(url : str) -> int: try: response = urllib.request.urlopen(url) # type: ignore[attr-defined] return int(response.getheader('Content-Length')) except (OSError, ValueError): - return None + return 0 def is_download_done(url : str, file_path : str) -> bool: diff --git a/facefusion/vision.py b/facefusion/vision.py index 5fb67286..baf40bc2 100644 --- a/facefusion/vision.py +++ b/facefusion/vision.py @@ -40,12 +40,13 @@ def normalize_frame_color(frame : Frame) -> Frame: 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] - if height > max_height: - scale = max_height / height - max_width = int(width * scale) - frame = cv2.resize(frame, (max_width, max_height)) + if height > max_height or width > max_width: + scale = min(max_height / height, max_width / width) + new_width = int(width * scale) + new_height = int(height * scale) + return cv2.resize(frame, (new_width, new_height)) return frame diff --git a/facefusion/wording.py b/facefusion/wording.py index 7c8cce75..884d5b10 100644 --- a/facefusion/wording.py +++ b/facefusion/wording.py @@ -2,11 +2,13 @@ WORDING =\ { 'python_not_supported': 'Python version is not supported, upgrade to {version} or higher', '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', 'target_help': 'select a target image or video', 'output_help': 'specify the output file or directory', '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}, ...)', 'keep_fps_help': 'preserve the frames per second (fps) of the target', 'keep_temp_help': 'retain temporary frames after processing', @@ -58,6 +60,7 @@ WORDING =\ '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_implemented': 'UI layout {ui_layout} not implemented correctly', + 'donate_button_label': 'DONATE', 'start_button_label': 'START', 'stop_button_label': 'STOP', 'clear_button_label': 'CLEAR', @@ -82,7 +85,12 @@ WORDING =\ 'preview_image_label': 'PREVIEW', 'preview_frame_slider_label': 'PREVIEW FRAME', '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_quality_slider_label': 'TEMP FRAME QUALITY', 'trim_frame_start_slider_label': 'TRIM FRAME START', diff --git a/install.py b/install.py index c25d3bae..307f686f 100755 --- a/install.py +++ b/install.py @@ -3,4 +3,4 @@ from facefusion import installer if __name__ == '__main__': - installer.run() + installer.cli() diff --git a/requirements.txt b/requirements.txt index 71150e74..49b421fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,15 @@ -gfpgan==1.3.8 -gradio==3.44.3 +basicsr==1.4.2 +gradio==3.47.1 insightface==0.7.3 numpy==1.24.3 onnx==1.14.1 -onnxruntime==1.15.1 -opencv-python==4.8.0.76 +onnxruntime==1.16.0 +opencv-python==4.8.1.78 opennsfw2==0.10.2 pillow==10.0.1 protobuf==4.24.2 psutil==5.9.5 realesrgan==0.3.0 tensorflow==2.13.0 +torch==2.1.0 tqdm==4.66.1 diff --git a/run.py b/run.py index e940ac8b..3b796757 100755 --- a/run.py +++ b/run.py @@ -3,4 +3,4 @@ from facefusion import core if __name__ == '__main__': - core.run() + core.cli() diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 33cf09d3..2fc18ce3 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -143,7 +143,7 @@ def test_is_video() -> 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-360p.mp4') == 370732 - assert get_download_size('invalid') is None + assert get_download_size('invalid') == 0 def test_is_download_done() -> None: