Merge pull request #902 from facefusion/next

3.3.0
This commit is contained in:
Henry Ruhs
2025-06-22 17:19:10 +02:00
committed by GitHub
55 changed files with 1185 additions and 516 deletions

BIN
.github/preview.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -37,6 +37,7 @@ commands:
headless-run run the program in headless mode
batch-run run the program in batch mode
force-download force automate downloads and exit
benchmark benchmark the program
job-list list jobs by status
job-create create a drafted job
job-submit submit a drafted job to become a queued job

View File

@@ -35,9 +35,10 @@ reference_frame_number =
face_occluder_model =
face_parser_model =
face_mask_types =
face_mask_areas =
face_mask_regions =
face_mask_blur =
face_mask_padding =
face_mask_regions =
[frame_extraction]
trim_frame_start =
@@ -92,22 +93,27 @@ frame_colorizer_blend =
frame_enhancer_model =
frame_enhancer_blend =
lip_syncer_model =
lip_syncer_weight =
[uis]
open_browser =
ui_layouts =
ui_workflow =
[download]
download_providers =
download_scope =
[benchmark]
benchmark_resolutions =
benchmark_cycle_count =
[execution]
execution_device_id =
execution_providers =
execution_thread_count =
execution_queue_count =
[download]
download_providers =
download_scope =
[memory]
video_memory_strategy =
system_memory_limit =

View File

@@ -74,9 +74,10 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('face_occluder_model', args.get('face_occluder_model'))
apply_state_item('face_parser_model', args.get('face_parser_model'))
apply_state_item('face_mask_types', args.get('face_mask_types'))
apply_state_item('face_mask_areas', args.get('face_mask_areas'))
apply_state_item('face_mask_regions', args.get('face_mask_regions'))
apply_state_item('face_mask_blur', args.get('face_mask_blur'))
apply_state_item('face_mask_padding', normalize_padding(args.get('face_mask_padding')))
apply_state_item('face_mask_regions', args.get('face_mask_regions'))
# frame extraction
apply_state_item('trim_frame_start', args.get('trim_frame_start'))
apply_state_item('trim_frame_end', args.get('trim_frame_end'))
@@ -124,6 +125,9 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
# download
apply_state_item('download_providers', args.get('download_providers'))
apply_state_item('download_scope', args.get('download_scope'))
# benchmark
apply_state_item('benchmark_resolutions', args.get('benchmark_resolutions'))
apply_state_item('benchmark_cycle_count', args.get('benchmark_cycle_count'))
# memory
apply_state_item('video_memory_strategy', args.get('video_memory_strategy'))
apply_state_item('system_memory_limit', args.get('system_memory_limit'))

106
facefusion/benchmarker.py Normal file
View File

@@ -0,0 +1,106 @@
import hashlib
import os
import statistics
import tempfile
from time import perf_counter
from typing import Generator, List
import facefusion.choices
from facefusion import core, state_manager
from facefusion.cli_helper import render_table
from facefusion.download import conditional_download, resolve_download_url
from facefusion.filesystem import get_file_extension
from facefusion.types import BenchmarkCycleSet
from facefusion.vision import count_video_frame_total, detect_video_fps, detect_video_resolution, pack_resolution
def pre_check() -> bool:
conditional_download('.assets/examples',
[
resolve_download_url('examples-3.0.0', 'source.jpg'),
resolve_download_url('examples-3.0.0', 'source.mp3'),
resolve_download_url('examples-3.0.0', 'target-240p.mp4'),
resolve_download_url('examples-3.0.0', 'target-360p.mp4'),
resolve_download_url('examples-3.0.0', 'target-540p.mp4'),
resolve_download_url('examples-3.0.0', 'target-720p.mp4'),
resolve_download_url('examples-3.0.0', 'target-1080p.mp4'),
resolve_download_url('examples-3.0.0', 'target-1440p.mp4'),
resolve_download_url('examples-3.0.0', 'target-2160p.mp4')
])
return True
def run() -> Generator[List[BenchmarkCycleSet], None, None]:
benchmark_resolutions = state_manager.get_item('benchmark_resolutions')
benchmark_cycle_count = state_manager.get_item('benchmark_cycle_count')
state_manager.init_item('source_paths', [ '.assets/examples/source.jpg', '.assets/examples/source.mp3' ])
state_manager.init_item('face_landmarker_score', 0)
state_manager.init_item('temp_frame_format', 'bmp')
state_manager.init_item('output_audio_volume', 0)
state_manager.init_item('output_video_preset', 'ultrafast')
state_manager.init_item('video_memory_strategy', 'tolerant')
benchmarks = []
target_paths = [facefusion.choices.benchmark_set.get(benchmark_resolution) for benchmark_resolution in benchmark_resolutions if benchmark_resolution in facefusion.choices.benchmark_set]
for target_path in target_paths:
state_manager.set_item('target_path', target_path)
state_manager.set_item('output_path', suggest_output_path(state_manager.get_item('target_path')))
benchmarks.append(cycle(benchmark_cycle_count))
yield benchmarks
def cycle(cycle_count : int) -> BenchmarkCycleSet:
process_times = []
video_frame_total = count_video_frame_total(state_manager.get_item('target_path'))
output_video_resolution = detect_video_resolution(state_manager.get_item('target_path'))
state_manager.set_item('output_video_resolution', pack_resolution(output_video_resolution))
state_manager.set_item('output_video_fps', detect_video_fps(state_manager.get_item('target_path')))
core.conditional_process()
for index in range(cycle_count):
start_time = perf_counter()
core.conditional_process()
end_time = perf_counter()
process_times.append(end_time - start_time)
average_run = round(statistics.mean(process_times), 2)
fastest_run = round(min(process_times), 2)
slowest_run = round(max(process_times), 2)
relative_fps = round(video_frame_total * cycle_count / sum(process_times), 2)
return\
{
'target_path': state_manager.get_item('target_path'),
'cycle_count': cycle_count,
'average_run': average_run,
'fastest_run': fastest_run,
'slowest_run': slowest_run,
'relative_fps': relative_fps
}
def suggest_output_path(target_path : str) -> str:
target_file_extension = get_file_extension(target_path)
return os.path.join(tempfile.gettempdir(), hashlib.sha1().hexdigest()[:8] + target_file_extension)
def render() -> None:
benchmarks = []
headers =\
[
'target_path',
'cycle_count',
'average_run',
'fastest_run',
'slowest_run',
'relative_fps'
]
for benchmark in run():
benchmarks = benchmark
contents = [ list(benchmark_set.values()) for benchmark_set in benchmarks ]
render_table(headers, contents)

View File

@@ -2,7 +2,7 @@ import logging
from typing import List, Sequence
from facefusion.common_helper import create_float_range, create_int_range
from facefusion.types import Angle, AudioEncoder, AudioFormat, AudioTypeSet, DownloadProvider, DownloadProviderSet, DownloadScope, EncoderSet, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorMode, FaceSelectorOrder, Gender, ImageFormat, ImageTypeSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, UiWorkflow, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoTypeSet, WebcamMode
from facefusion.types import Angle, AudioEncoder, AudioFormat, AudioTypeSet, BenchmarkResolution, BenchmarkSet, DownloadProvider, DownloadProviderSet, DownloadScope, EncoderSet, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskArea, FaceMaskAreaSet, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorMode, FaceSelectorOrder, Gender, ImageFormat, ImageTypeSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, UiWorkflow, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoTypeSet, WebcamMode
face_detector_set : FaceDetectorSet =\
{
@@ -19,7 +19,13 @@ face_selector_genders : List[Gender] = [ 'female', 'male' ]
face_selector_races : List[Race] = [ 'white', 'black', 'latino', 'asian', 'indian', 'arabic' ]
face_occluder_models : List[FaceOccluderModel] = [ 'xseg_1', 'xseg_2', 'xseg_3' ]
face_parser_models : List[FaceParserModel] = [ 'bisenet_resnet_18', 'bisenet_resnet_34' ]
face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'region' ]
face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'area', 'region' ]
face_mask_area_set : FaceMaskAreaSet =\
{
'upper-face': [ 0, 1, 2, 31, 32, 33, 34, 35, 14, 15, 16, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17 ],
'lower-face': [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 35, 34, 33, 32, 31 ],
'mouth': [ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67 ]
}
face_mask_region_set : FaceMaskRegionSet =\
{
'skin': 1,
@@ -33,6 +39,7 @@ face_mask_region_set : FaceMaskRegionSet =\
'upper-lip': 12,
'lower-lip': 13
}
face_mask_areas : List[FaceMaskArea] = list(face_mask_area_set.keys())
face_mask_regions : List[FaceMaskRegion] = list(face_mask_region_set.keys())
audio_type_set : AudioTypeSet =\
@@ -78,6 +85,18 @@ output_video_presets : List[VideoPreset] = [ 'ultrafast', 'superfast', 'veryfast
image_template_sizes : List[float] = [ 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4 ]
video_template_sizes : List[int] = [ 240, 360, 480, 540, 720, 1080, 1440, 2160, 4320 ]
benchmark_set : BenchmarkSet =\
{
'240p': '.assets/examples/target-240p.mp4',
'360p': '.assets/examples/target-360p.mp4',
'540p': '.assets/examples/target-540p.mp4',
'720p': '.assets/examples/target-720p.mp4',
'1080p': '.assets/examples/target-1080p.mp4',
'1440p': '.assets/examples/target-1440p.mp4',
'2160p': '.assets/examples/target-2160p.mp4'
}
benchmark_resolutions : List[BenchmarkResolution] = list(benchmark_set.keys())
webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ]
webcam_resolutions : List[str] = [ '320x240', '640x480', '800x600', '1024x768', '1280x720', '1280x960', '1920x1080', '2560x1440', '3840x2160' ]
@@ -129,6 +148,7 @@ log_levels : List[LogLevel] = list(log_level_set.keys())
ui_workflows : List[UiWorkflow] = [ 'instant_runner', 'job_runner', 'job_manager' ]
job_statuses : List[JobStatus] = [ 'drafted', 'queued', 'completed', 'failed' ]
benchmark_cycle_count_range : Sequence[int] = create_int_range(1, 10, 1)
execution_thread_count_range : Sequence[int] = create_int_range(1, 32, 1)
execution_queue_count_range : Sequence[int] = create_int_range(1, 4, 1)
system_memory_limit_range : Sequence[int] = create_int_range(0, 128, 4)

View File

@@ -8,15 +8,15 @@ def render_table(headers : TableHeaders, contents : TableContents) -> None:
package_logger = get_package_logger()
table_column, table_separator = create_table_parts(headers, contents)
package_logger.info(table_separator)
package_logger.info(table_column.format(*headers))
package_logger.info(table_separator)
package_logger.critical(table_separator)
package_logger.critical(table_column.format(*headers))
package_logger.critical(table_separator)
for content in contents:
content = [ value if value else '' for value in content ]
package_logger.info(table_column.format(*content))
content = [ str(value) for value in content ]
package_logger.critical(table_column.format(*content))
package_logger.info(table_separator)
package_logger.critical(table_separator)
def create_table_parts(headers : TableHeaders, contents : TableContents) -> Tuple[str, str]:

View File

@@ -1,14 +1,15 @@
from functools import lru_cache
from typing import List
from typing import List, Tuple
import numpy
from tqdm import tqdm
from facefusion import inference_manager, state_manager, wording
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.thread_helper import conditional_thread_semaphore
from facefusion.types import Detection, DownloadScope, Fps, InferencePool, ModelOptions, ModelSet, Score, VisionFrame
from facefusion.types import Detection, DownloadScope, DownloadSet, ExecutionProvider, Fps, InferencePool, ModelSet, VisionFrame
from facefusion.vision import detect_video_fps, fit_frame, read_image, read_video_frame
STREAM_COUNTER = 0
@@ -18,48 +19,107 @@ STREAM_COUNTER = 0
def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
return\
{
'yolo_nsfw':
'nsfw_1':
{
'hashes':
{
'content_analyser':
{
'url': resolve_download_url('models-3.2.0', 'yolo_11m_nsfw.hash'),
'path': resolve_relative_path('../.assets/models/yolo_11m_nsfw.hash')
'url': resolve_download_url('models-3.3.0', 'nsfw_1.hash'),
'path': resolve_relative_path('../.assets/models/nsfw_1.hash')
}
},
'sources':
{
'content_analyser':
{
'url': resolve_download_url('models-3.2.0', 'yolo_11m_nsfw.onnx'),
'path': resolve_relative_path('../.assets/models/yolo_11m_nsfw.onnx')
'url': resolve_download_url('models-3.3.0', 'nsfw_1.onnx'),
'path': resolve_relative_path('../.assets/models/nsfw_1.onnx')
}
},
'size': (640, 640)
'size': (640, 640),
'mean': (0.0, 0.0, 0.0),
'standard_deviation': (1.0, 1.0, 1.0)
},
'nsfw_2':
{
'hashes':
{
'content_analyser':
{
'url': resolve_download_url('models-3.3.0', 'nsfw_2.hash'),
'path': resolve_relative_path('../.assets/models/nsfw_2.hash')
}
},
'sources':
{
'content_analyser':
{
'url': resolve_download_url('models-3.3.0', 'nsfw_2.onnx'),
'path': resolve_relative_path('../.assets/models/nsfw_2.onnx')
}
},
'size': (384, 384),
'mean': (0.5, 0.5, 0.5),
'standard_deviation': (0.5, 0.5, 0.5)
},
'nsfw_3':
{
'hashes':
{
'content_analyser':
{
'url': resolve_download_url('models-3.3.0', 'nsfw_3.hash'),
'path': resolve_relative_path('../.assets/models/nsfw_3.hash')
}
},
'sources':
{
'content_analyser':
{
'url': resolve_download_url('models-3.3.0', 'nsfw_3.onnx'),
'path': resolve_relative_path('../.assets/models/nsfw_3.onnx')
}
},
'size': (448, 448),
'mean': (0.48145466, 0.4578275, 0.40821073),
'standard_deviation': (0.26862954, 0.26130258, 0.27577711)
}
}
def get_inference_pool() -> InferencePool:
model_names = [ 'yolo_nsfw' ]
model_source_set = get_model_options().get('sources')
model_names = [ 'nsfw_1', 'nsfw_2', 'nsfw_3' ]
_, model_source_set = collect_model_downloads()
return inference_manager.get_inference_pool(__name__, model_names, model_source_set)
def clear_inference_pool() -> None:
model_names = [ 'yolo_nsfw' ]
model_names = [ 'nsfw_1', 'nsfw_2', 'nsfw_3' ]
inference_manager.clear_inference_pool(__name__, model_names)
def get_model_options() -> ModelOptions:
return create_static_model_set('full').get('yolo_nsfw')
def resolve_execution_providers() -> List[ExecutionProvider]:
if has_execution_provider('coreml'):
return [ 'cpu' ]
return state_manager.get_item('execution_providers')
def collect_model_downloads() -> Tuple[DownloadSet, DownloadSet]:
model_set = create_static_model_set('full')
model_hash_set = {}
model_source_set = {}
for content_analyser_model in [ 'nsfw_1', 'nsfw_2', 'nsfw_3' ]:
model_hash_set[content_analyser_model] = model_set.get(content_analyser_model).get('hashes').get('content_analyser')
model_source_set[content_analyser_model] = model_set.get(content_analyser_model).get('sources').get('content_analyser')
return model_hash_set, model_source_set
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
model_hash_set, model_source_set = collect_model_downloads()
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -74,9 +134,7 @@ def analyse_stream(vision_frame : VisionFrame, video_fps : Fps) -> bool:
def analyse_frame(vision_frame : VisionFrame) -> bool:
nsfw_scores = detect_nsfw(vision_frame)
return len(nsfw_scores) > 0
return detect_nsfw(vision_frame)
@lru_cache(maxsize = None)
@@ -106,39 +164,62 @@ def analyse_video(video_path : str, trim_frame_start : int, trim_frame_end : int
progress.set_postfix(rate = rate)
progress.update()
return rate > 10.0
return bool(rate > 10.0)
def detect_nsfw(vision_frame : VisionFrame) -> List[Score]:
nsfw_scores = []
model_size = get_model_options().get('size')
temp_vision_frame = fit_frame(vision_frame, model_size)
detect_vision_frame = prepare_detect_frame(temp_vision_frame)
detection = forward(detect_vision_frame)
detection = numpy.squeeze(detection).T
nsfw_scores_raw = numpy.amax(detection[:, 4:], axis = 1)
keep_indices = numpy.where(nsfw_scores_raw > 0.2)[0]
def detect_nsfw(vision_frame : VisionFrame) -> bool:
is_nsfw_1 = detect_with_nsfw_1(vision_frame)
is_nsfw_2 = detect_with_nsfw_2(vision_frame)
is_nsfw_3 = detect_with_nsfw_3(vision_frame)
if numpy.any(keep_indices):
nsfw_scores_raw = nsfw_scores_raw[keep_indices]
nsfw_scores = nsfw_scores_raw.ravel().tolist()
return nsfw_scores
return is_nsfw_1 and is_nsfw_2 or is_nsfw_1 and is_nsfw_3 or is_nsfw_2 and is_nsfw_3
def forward(vision_frame : VisionFrame) -> Detection:
content_analyser = get_inference_pool().get('content_analyser')
def detect_with_nsfw_1(vision_frame : VisionFrame) -> bool:
detect_vision_frame = prepare_detect_frame(vision_frame, 'nsfw_1')
detection = forward_nsfw(detect_vision_frame, 'nsfw_1')
detection_score = numpy.max(numpy.amax(detection[:, 4:], axis = 1))
return bool(detection_score > 0.2)
def detect_with_nsfw_2(vision_frame : VisionFrame) -> bool:
detect_vision_frame = prepare_detect_frame(vision_frame, 'nsfw_2')
detection = forward_nsfw(detect_vision_frame, 'nsfw_2')
detection_score = detection[0] - detection[1]
return bool(detection_score > 0.25)
def detect_with_nsfw_3(vision_frame : VisionFrame) -> bool:
detect_vision_frame = prepare_detect_frame(vision_frame, 'nsfw_3')
detection = forward_nsfw(detect_vision_frame, 'nsfw_3')
detection_score = (detection[2] + detection[3]) - (detection[0] + detection[1])
return bool(detection_score > 10.5)
def forward_nsfw(vision_frame : VisionFrame, nsfw_model : str) -> Detection:
content_analyser = get_inference_pool().get(nsfw_model)
with conditional_thread_semaphore():
detection = content_analyser.run(None,
{
'input': vision_frame
})
})[0]
if nsfw_model in [ 'nsfw_2', 'nsfw_3' ]:
return detection[0]
return detection
def prepare_detect_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
detect_vision_frame = temp_vision_frame / 255.0
def prepare_detect_frame(temp_vision_frame : VisionFrame, model_name : str) -> VisionFrame:
model_set = create_static_model_set('full').get(model_name)
model_size = model_set.get('size')
model_mean = model_set.get('mean')
model_standard_deviation = model_set.get('standard_deviation')
detect_vision_frame = fit_frame(temp_vision_frame, model_size)
detect_vision_frame = detect_vision_frame[:, :, ::-1] / 255.0
detect_vision_frame -= model_mean
detect_vision_frame /= model_standard_deviation
detect_vision_frame = numpy.expand_dims(detect_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
return detect_vision_frame

View File

@@ -6,12 +6,12 @@ from time import time
import numpy
from facefusion import cli_helper, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, voice_extractor, wording
from facefusion import benchmarker, cli_helper, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, video_manager, voice_extractor, wording
from facefusion.args import apply_args, collect_job_args, reduce_job_args, reduce_step_args
from facefusion.common_helper import get_first
from facefusion.content_analyser import analyse_image, analyse_video
from facefusion.download import conditional_download_hashes, conditional_download_sources
from facefusion.exit_helper import graceful_exit, hard_exit
from facefusion.exit_helper import hard_exit, signal_exit
from facefusion.face_analyser import get_average_face, get_many_faces, get_one_face
from facefusion.face_selector import sort_and_filter_faces
from facefusion.face_store import append_reference_face, clear_reference_faces, get_reference_faces
@@ -23,7 +23,6 @@ from facefusion.memory import limit_system_memory
from facefusion.processors.core import get_processors_modules
from facefusion.program import create_program
from facefusion.program_helper import validate_args
from facefusion.statistics import conditional_log_statistics
from facefusion.temp_helper import clear_temp_directory, create_temp_directory, get_temp_file_path, move_temp_file, resolve_temp_frame_paths
from facefusion.types import Args, ErrorCode
from facefusion.vision import pack_resolution, read_image, read_static_images, read_video_frame, restrict_image_resolution, restrict_trim_frame, restrict_video_fps, restrict_video_resolution, unpack_resolution
@@ -31,7 +30,7 @@ from facefusion.vision import pack_resolution, read_image, read_static_images, r
def cli() -> None:
if pre_check():
signal.signal(signal.SIGINT, lambda signal_number, frame: graceful_exit(0))
signal.signal(signal.SIGINT, signal_exit)
program = create_program()
if validate_args(program):
@@ -59,6 +58,11 @@ def route(args : Args) -> None:
error_code = force_download()
return hard_exit(error_code)
if state_manager.get_item('command') == 'benchmark':
if not common_pre_check() or not processors_pre_check() or not benchmarker.pre_check():
return hard_exit(2)
benchmarker.render()
if state_manager.get_item('command') in [ 'job-list', 'job-create', 'job-submit', 'job-submit-all', 'job-delete', 'job-delete-all', 'job-add-step', 'job-remix-step', 'job-insert-step', 'job-remove-step' ]:
if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
hard_exit(1)
@@ -383,10 +387,10 @@ def process_image(start_time : float) -> ErrorCode:
process_manager.end()
return 1
temp_file_path = get_temp_file_path(state_manager.get_item('target_path'))
temp_image_path = get_temp_file_path(state_manager.get_item('target_path'))
for processor_module in get_processors_modules(state_manager.get_item('processors')):
logger.info(wording.get('processing'), processor_module.__name__)
processor_module.process_image(state_manager.get_item('source_paths'), temp_file_path, temp_file_path)
processor_module.process_image(state_manager.get_item('source_paths'), temp_image_path, temp_image_path)
processor_module.post_process()
if is_process_stopping():
process_manager.end()
@@ -404,7 +408,6 @@ def process_image(start_time : float) -> ErrorCode:
if is_image(state_manager.get_item('output_path')):
seconds = '{:.2f}'.format((time() - start_time) % 60)
logger.info(wording.get('processing_image_succeed').format(seconds = seconds), __name__)
conditional_log_statistics()
else:
logger.error(wording.get('processing_image_failed'), __name__)
process_manager.end()
@@ -468,8 +471,10 @@ def process_video(start_time : float) -> ErrorCode:
source_audio_path = get_first(filter_audio_paths(state_manager.get_item('source_paths')))
if source_audio_path:
if replace_audio(state_manager.get_item('target_path'), source_audio_path, state_manager.get_item('output_path')):
video_manager.clear_video_pool()
logger.debug(wording.get('replacing_audio_succeed'), __name__)
else:
video_manager.clear_video_pool()
if is_process_stopping():
process_manager.end()
return 4
@@ -477,8 +482,10 @@ def process_video(start_time : float) -> ErrorCode:
move_temp_file(state_manager.get_item('target_path'), state_manager.get_item('output_path'))
else:
if restore_audio(state_manager.get_item('target_path'), state_manager.get_item('output_path'), trim_frame_start, trim_frame_end):
video_manager.clear_video_pool()
logger.debug(wording.get('restoring_audio_succeed'), __name__)
else:
video_manager.clear_video_pool()
if is_process_stopping():
process_manager.end()
return 4
@@ -491,7 +498,6 @@ def process_video(start_time : float) -> ErrorCode:
if is_video(state_manager.get_item('output_path')):
seconds = '{:.2f}'.format((time() - start_time))
logger.info(wording.get('processing_video_succeed').format(seconds = seconds), __name__)
conditional_log_statistics()
else:
logger.error(wording.get('processing_video_failed'), __name__)
process_manager.end()

View File

@@ -1,6 +1,7 @@
import signal
import sys
from time import sleep
from types import FrameType
from facefusion import process_manager, state_manager
from facefusion.temp_helper import clear_temp_directory
@@ -12,6 +13,10 @@ def hard_exit(error_code : ErrorCode) -> None:
sys.exit(error_code)
def signal_exit(signum : int, frame : FrameType) -> None:
graceful_exit(0)
def graceful_exit(error_code : ErrorCode) -> None:
process_manager.stop()
while process_manager.is_processing():

View File

@@ -99,15 +99,35 @@ def warp_face_by_translation(temp_vision_frame : VisionFrame, translation : Tran
def paste_back(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, crop_mask : Mask, affine_matrix : Matrix) -> VisionFrame:
paste_bounding_box, paste_matrix = calc_paste_area(temp_vision_frame, crop_vision_frame, affine_matrix)
x_min, y_min, x_max, y_max = paste_bounding_box
paste_width = x_max - x_min
paste_height = y_max - y_min
inverse_mask = cv2.warpAffine(crop_mask, paste_matrix, (paste_width, paste_height)).clip(0, 1)
inverse_mask = numpy.expand_dims(inverse_mask, axis = -1)
inverse_vision_frame = cv2.warpAffine(crop_vision_frame, paste_matrix, (paste_width, paste_height), borderMode = cv2.BORDER_REPLICATE)
temp_vision_frame = temp_vision_frame.copy()
paste_vision_frame = temp_vision_frame[y_min:y_max, x_min:x_max]
paste_vision_frame = paste_vision_frame * (1 - inverse_mask) + inverse_vision_frame * inverse_mask
temp_vision_frame[y_min:y_max, x_min:x_max] = paste_vision_frame.astype(temp_vision_frame.dtype)
return temp_vision_frame
def calc_paste_area(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, affine_matrix : Matrix) -> Tuple[BoundingBox, Matrix]:
temp_height, temp_width = temp_vision_frame.shape[:2]
crop_height, crop_width = crop_vision_frame.shape[:2]
inverse_matrix = cv2.invertAffineTransform(affine_matrix)
temp_size = temp_vision_frame.shape[:2][::-1]
inverse_mask = cv2.warpAffine(crop_mask, inverse_matrix, temp_size).clip(0, 1)
inverse_vision_frame = cv2.warpAffine(crop_vision_frame, inverse_matrix, temp_size, borderMode = cv2.BORDER_REPLICATE)
paste_vision_frame = temp_vision_frame.copy()
paste_vision_frame[:, :, 0] = inverse_mask * inverse_vision_frame[:, :, 0] + (1 - inverse_mask) * temp_vision_frame[:, :, 0]
paste_vision_frame[:, :, 1] = inverse_mask * inverse_vision_frame[:, :, 1] + (1 - inverse_mask) * temp_vision_frame[:, :, 1]
paste_vision_frame[:, :, 2] = inverse_mask * inverse_vision_frame[:, :, 2] + (1 - inverse_mask) * temp_vision_frame[:, :, 2]
return paste_vision_frame
crop_points = numpy.array([ [ 0, 0 ], [ crop_width, 0 ], [ crop_width, crop_height ], [ 0, crop_height ] ])
paste_region_points = transform_points(crop_points, inverse_matrix)
min_point = numpy.floor(paste_region_points.min(axis = 0)).astype(int)
max_point = numpy.ceil(paste_region_points.max(axis = 0)).astype(int)
x_min, y_min = numpy.clip(min_point, 0, [ temp_width, temp_height ])
x_max, y_max = numpy.clip(max_point, 0, [ temp_width, temp_height ])
paste_bounding_box = numpy.array([ x_min, y_min, x_max, y_max ])
paste_matrix = inverse_matrix.copy()
paste_matrix[0, 2] -= x_min
paste_matrix[1, 2] -= y_min
return paste_bounding_box, paste_matrix
@lru_cache(maxsize = None)

View File

@@ -3,14 +3,13 @@ from typing import List, Tuple
import cv2
import numpy
from cv2.typing import Size
import facefusion.choices
from facefusion import inference_manager, state_manager
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.filesystem import resolve_relative_path
from facefusion.thread_helper import conditional_thread_semaphore
from facefusion.types import DownloadScope, DownloadSet, FaceLandmark68, FaceMaskRegion, InferencePool, Mask, ModelSet, Padding, VisionFrame
from facefusion.types import DownloadScope, DownloadSet, FaceLandmark68, FaceMaskArea, FaceMaskRegion, InferencePool, Mask, ModelSet, Padding, VisionFrame
@lru_cache(maxsize = None)
@@ -156,8 +155,8 @@ def pre_check() -> bool:
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@lru_cache(maxsize = None)
def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Mask:
def create_box_mask(crop_vision_frame : VisionFrame, face_mask_blur : float, face_mask_padding : Padding) -> Mask:
crop_size = crop_vision_frame.shape[:2][::-1]
blur_amount = int(crop_size[0] * 0.5 * face_mask_blur)
blur_area = max(blur_amount // 2, 1)
box_mask : Mask = numpy.ones(crop_size).astype(numpy.float32)
@@ -165,6 +164,7 @@ def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_p
box_mask[-max(blur_area, int(crop_size[1] * face_mask_padding[2] / 100)):, :] = 0
box_mask[:, :max(blur_area, int(crop_size[0] * face_mask_padding[3] / 100))] = 0
box_mask[:, -max(blur_area, int(crop_size[0] * face_mask_padding[1] / 100)):] = 0
if blur_amount > 0:
box_mask = cv2.GaussianBlur(box_mask, (0, 0), blur_amount * 0.25)
return box_mask
@@ -183,6 +183,21 @@ def create_occlusion_mask(crop_vision_frame : VisionFrame) -> Mask:
return occlusion_mask
def create_area_mask(crop_vision_frame : VisionFrame, face_landmark_68 : FaceLandmark68, face_mask_areas : List[FaceMaskArea]) -> Mask:
crop_size = crop_vision_frame.shape[:2][::-1]
landmark_points = []
for face_mask_area in face_mask_areas:
if face_mask_area in facefusion.choices.face_mask_area_set:
landmark_points.extend(facefusion.choices.face_mask_area_set.get(face_mask_area))
convex_hull = cv2.convexHull(face_landmark_68[landmark_points].astype(numpy.int32))
area_mask = numpy.zeros(crop_size).astype(numpy.float32)
cv2.fillConvexPoly(area_mask, convex_hull, 1.0) # type: ignore[call-overload]
area_mask = (cv2.GaussianBlur(area_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
return area_mask
def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List[FaceMaskRegion]) -> Mask:
model_name = state_manager.get_item('face_parser_model')
model_size = create_static_model_set('full').get(model_name).get('size')
@@ -199,15 +214,6 @@ def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List
return region_mask
def create_mouth_mask(face_landmark_68 : FaceLandmark68) -> Mask:
convex_hull = cv2.convexHull(face_landmark_68[numpy.r_[3:14, 31:36]].astype(numpy.int32))
mouth_mask : Mask = numpy.zeros((512, 512)).astype(numpy.float32)
mouth_mask = cv2.fillConvexPoly(mouth_mask, convex_hull, 1.0) #type:ignore[call-overload]
mouth_mask = cv2.erode(mouth_mask.clip(0, 1), numpy.ones((21, 3)))
mouth_mask = cv2.GaussianBlur(mouth_mask, (0, 0), sigmaX = 1, sigmaY = 15)
return mouth_mask
def forward_occlude_face(prepare_vision_frame : VisionFrame) -> Mask:
model_name = state_manager.get_item('face_occluder_model')
face_occluder = get_inference_pool().get(model_name)

View File

@@ -3,7 +3,7 @@ from typing import List
import numpy
from facefusion import state_manager
from facefusion.types import Face, FaceSelectorOrder, FaceSet, Gender, Race
from facefusion.types import Face, FaceSelectorOrder, FaceSet, Gender, Race, Score
def find_similar_faces(faces : List[Face], reference_faces : FaceSet, face_distance : float) -> List[Face]:
@@ -46,24 +46,40 @@ def sort_and_filter_faces(faces : List[Face]) -> List[Face]:
def sort_faces_by_order(faces : List[Face], order : FaceSelectorOrder) -> List[Face]:
if order == 'left-right':
return sorted(faces, key = lambda face: face.bounding_box[0])
return sorted(faces, key = get_bounding_box_left)
if order == 'right-left':
return sorted(faces, key = lambda face: face.bounding_box[0], reverse = True)
return sorted(faces, key = get_bounding_box_left, reverse = True)
if order == 'top-bottom':
return sorted(faces, key = lambda face: face.bounding_box[1])
return sorted(faces, key = get_bounding_box_top)
if order == 'bottom-top':
return sorted(faces, key = lambda face: face.bounding_box[1], reverse = True)
return sorted(faces, key = get_bounding_box_top, reverse = True)
if order == 'small-large':
return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]))
return sorted(faces, key = get_bounding_box_area)
if order == 'large-small':
return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]), reverse = True)
return sorted(faces, key = get_bounding_box_area, reverse = True)
if order == 'best-worst':
return sorted(faces, key = lambda face: face.score_set.get('detector'), reverse = True)
return sorted(faces, key = get_face_detector_score, reverse = True)
if order == 'worst-best':
return sorted(faces, key = lambda face: face.score_set.get('detector'))
return sorted(faces, key = get_face_detector_score)
return faces
def get_bounding_box_left(face : Face) -> float:
return face.bounding_box[0]
def get_bounding_box_top(face : Face) -> float:
return face.bounding_box[1]
def get_bounding_box_area(face : Face) -> float:
return (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1])
def get_face_detector_score(face : Face) -> Score:
return face.score_set.get('detector')
def filter_faces_by_gender(faces : List[Face], gender : Gender) -> List[Face]:
filter_faces = []

View File

@@ -1,8 +1,6 @@
import hashlib
from typing import List, Optional
import numpy
from facefusion.hash_helper import create_hash
from facefusion.types import Face, FaceSet, FaceStore, VisionFrame
FACE_STORE : FaceStore =\
@@ -17,27 +15,22 @@ def get_face_store() -> FaceStore:
def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
frame_hash = create_frame_hash(vision_frame)
if frame_hash in FACE_STORE['static_faces']:
return FACE_STORE['static_faces'][frame_hash]
vision_area = crop_vision_area(vision_frame)
vision_hash = create_hash(vision_area.tobytes())
if vision_hash in FACE_STORE['static_faces']:
return FACE_STORE['static_faces'][vision_hash]
return None
def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
frame_hash = create_frame_hash(vision_frame)
if frame_hash:
FACE_STORE['static_faces'][frame_hash] = faces
vision_area = crop_vision_area(vision_frame)
vision_hash = create_hash(vision_area.tobytes())
if vision_hash:
FACE_STORE['static_faces'][vision_hash] = faces
def clear_static_faces() -> None:
FACE_STORE['static_faces'] = {}
def create_frame_hash(vision_frame : VisionFrame) -> Optional[str]:
if numpy.any(vision_frame):
frame_hash = hashlib.blake2b(vision_frame.tobytes(), digest_size = 16).hexdigest()
return frame_hash
return None
FACE_STORE['static_faces'].clear()
def get_reference_faces() -> Optional[FaceSet]:
@@ -53,4 +46,11 @@ def append_reference_face(name : str, face : Face) -> None:
def clear_reference_faces() -> None:
FACE_STORE['reference_faces'] = {}
FACE_STORE['reference_faces'].clear()
def crop_vision_area(vision_frame : VisionFrame) -> VisionFrame:
height, width = vision_frame.shape[:2]
center_y, center_x = height // 2, width // 2
vision_area = vision_frame[center_y - 16 : center_y + 16, center_x - 16 : center_x + 16]
return vision_area

View File

@@ -1,7 +1,8 @@
import os
import subprocess
import tempfile
from typing import List, Optional
from functools import partial
from typing import List, Optional, cast
from tqdm import tqdm
@@ -9,7 +10,7 @@ import facefusion.choices
from facefusion import ffmpeg_builder, logger, process_manager, state_manager, wording
from facefusion.filesystem import get_file_format, remove_file
from facefusion.temp_helper import get_temp_file_path, get_temp_frames_pattern
from facefusion.types import AudioBuffer, Commands, EncoderSet, Fps, UpdateProgress
from facefusion.types import AudioBuffer, AudioEncoder, Commands, EncoderSet, Fps, UpdateProgress, VideoEncoder, VideoFormat
from facefusion.vision import detect_video_duration, detect_video_fps, predict_video_frame_total
@@ -40,6 +41,10 @@ def run_ffmpeg_with_progress(commands : Commands, update_progress : UpdateProgre
return process
def update_progress(progress : tqdm, frame_number : int) -> None:
progress.update(frame_number - progress.n)
def run_ffmpeg(commands : Commands) -> subprocess.Popen[bytes]:
log_level = state_manager.get_item('log_level')
commands = ffmpeg_builder.run(commands)
@@ -114,26 +119,26 @@ def extract_frames(target_path : str, temp_video_resolution : str, temp_video_fp
)
with tqdm(total = extract_frame_total, desc = wording.get('extracting'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
process = run_ffmpeg_with_progress(commands, lambda frame_number: progress.update(frame_number - progress.n))
process = run_ffmpeg_with_progress(commands, partial(update_progress, progress))
return process.returncode == 0
def copy_image(target_path : str, temp_image_resolution : str) -> bool:
temp_file_path = get_temp_file_path(target_path)
temp_image_path = get_temp_file_path(target_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(target_path),
ffmpeg_builder.set_media_resolution(temp_image_resolution),
ffmpeg_builder.set_image_quality(target_path, 100),
ffmpeg_builder.force_output(temp_file_path)
ffmpeg_builder.force_output(temp_image_path)
)
return run_ffmpeg(commands).returncode == 0
def finalize_image(target_path : str, output_path : str, output_image_resolution : str) -> bool:
output_image_quality = state_manager.get_item('output_image_quality')
temp_file_path = get_temp_file_path(target_path)
temp_image_path = get_temp_file_path(target_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(temp_file_path),
ffmpeg_builder.set_input(temp_image_path),
ffmpeg_builder.set_media_resolution(output_image_resolution),
ffmpeg_builder.set_image_quality(target_path, output_image_quality),
ffmpeg_builder.force_output(output_path)
@@ -163,11 +168,13 @@ def restore_audio(target_path : str, output_path : str, trim_frame_start : int,
output_audio_quality = state_manager.get_item('output_audio_quality')
output_audio_volume = state_manager.get_item('output_audio_volume')
target_video_fps = detect_video_fps(target_path)
temp_file_path = get_temp_file_path(target_path)
temp_video_duration = detect_video_duration(temp_file_path)
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_video_duration = detect_video_duration(temp_video_path)
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(temp_file_path),
ffmpeg_builder.set_input(temp_video_path),
ffmpeg_builder.select_media_range(trim_frame_start, trim_frame_end, target_video_fps),
ffmpeg_builder.set_input(target_path),
ffmpeg_builder.copy_video_encoder(),
@@ -186,11 +193,13 @@ def replace_audio(target_path : str, audio_path : str, output_path : str) -> boo
output_audio_encoder = state_manager.get_item('output_audio_encoder')
output_audio_quality = state_manager.get_item('output_audio_quality')
output_audio_volume = state_manager.get_item('output_audio_volume')
temp_file_path = get_temp_file_path(target_path)
temp_video_duration = detect_video_duration(temp_file_path)
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_video_duration = detect_video_duration(temp_video_path)
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(temp_file_path),
ffmpeg_builder.set_input(temp_video_path),
ffmpeg_builder.set_input(audio_path),
ffmpeg_builder.copy_video_encoder(),
ffmpeg_builder.set_audio_encoder(output_audio_encoder),
@@ -207,14 +216,13 @@ def merge_video(target_path : str, temp_video_fps : Fps, output_video_resolution
output_video_quality = state_manager.get_item('output_video_quality')
output_video_preset = state_manager.get_item('output_video_preset')
merge_frame_total = predict_video_frame_total(target_path, output_video_fps, trim_frame_start, trim_frame_end)
temp_file_path = get_temp_file_path(target_path)
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_frames_pattern = get_temp_frames_pattern(target_path, '%08d')
if get_file_format(target_path) == 'webm':
output_video_encoder = 'libvpx-vp9'
output_video_encoder = fix_video_encoder(temp_video_format, output_video_encoder)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_conditional_fps(temp_video_fps),
ffmpeg_builder.set_input_fps(temp_video_fps),
ffmpeg_builder.set_input(temp_frames_pattern),
ffmpeg_builder.set_media_resolution(output_video_resolution),
ffmpeg_builder.set_video_encoder(output_video_encoder),
@@ -223,11 +231,11 @@ def merge_video(target_path : str, temp_video_fps : Fps, output_video_resolution
ffmpeg_builder.set_video_fps(output_video_fps),
ffmpeg_builder.set_pixel_format(output_video_encoder),
ffmpeg_builder.set_video_colorspace('bt709'),
ffmpeg_builder.force_output(temp_file_path)
ffmpeg_builder.force_output(temp_video_path)
)
with tqdm(total = merge_frame_total, desc = wording.get('merging'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
process = run_ffmpeg_with_progress(commands, lambda frame_number: progress.update(frame_number - progress.n))
process = run_ffmpeg_with_progress(commands, partial(update_progress, progress))
return process.returncode == 0
@@ -252,3 +260,27 @@ def concat_video(output_path : str, temp_output_paths : List[str]) -> bool:
process.communicate()
remove_file(concat_video_path)
return process.returncode == 0
def fix_audio_encoder(video_format : VideoFormat, audio_encoder : AudioEncoder) -> AudioEncoder:
if video_format == 'avi' and audio_encoder == 'libopus':
return 'aac'
if video_format == 'm4v':
return 'aac'
if video_format == 'mov' and audio_encoder in [ 'flac', 'libopus' ]:
return 'aac'
if video_format == 'webm':
return 'libopus'
return audio_encoder
def fix_video_encoder(video_format : VideoFormat, video_encoder : VideoEncoder) -> VideoEncoder:
if video_format == 'm4v':
return 'libx264'
if video_format in [ 'mkv', 'mp4' ] and video_encoder == 'rawvideo':
return 'libx264'
if video_format == 'mov' and video_encoder == 'libvpx-vp9':
return 'libx264'
if video_format == 'webm':
return 'libvpx-vp9'
return video_encoder

View File

@@ -20,6 +20,10 @@ def get_encoders() -> Commands:
return [ '-encoders' ]
def set_hardware_accelerator(value : str) -> Commands:
return [ '-hwaccel', value ]
def set_progress() -> Commands:
return [ '-progress' ]
@@ -28,8 +32,8 @@ def set_input(input_path : str) -> Commands:
return [ '-i', input_path ]
def set_conditional_fps(conditional_fps : Fps) -> Commands:
return [ '-r', str(conditional_fps) ]
def set_input_fps(input_fps : Fps) -> Commands:
return [ '-r', str(input_fps)]
def set_output(output_path : str) -> Commands:
@@ -52,6 +56,10 @@ def set_stream_mode(stream_mode : StreamMode) -> Commands:
return []
def set_stream_quality(stream_quality : int) -> Commands:
return [ '-b:v', str(stream_quality) + 'k' ]
def unsafe_concat() -> Commands:
return [ '-f', 'concat', '-safe', '0' ]
@@ -138,7 +146,7 @@ def set_audio_quality(audio_encoder : AudioEncoder, audio_quality : int) -> Comm
audio_compression = round(numpy.interp(audio_quality, [ 0, 100 ], [ 9, 0 ]))
return [ '-q:a', str(audio_compression) ]
if audio_encoder == 'libopus':
audio_bit_rate = round(numpy.interp(audio_quality, [ 0, 100 ], [ 64, 320 ]))
audio_bit_rate = round(numpy.interp(audio_quality, [ 0, 100 ], [ 64, 256 ]))
return [ '-b:a', str(audio_bit_rate) + 'k' ]
if audio_encoder == 'libvorbis':
audio_compression = round(numpy.interp(audio_quality, [ 0, 100 ], [ -1, 10 ]), 1)
@@ -205,7 +213,7 @@ def set_video_duration(video_duration : Duration) -> Commands:
def capture_video() -> Commands:
return [ '-f', 'rawvideo' ]
return [ '-f', 'rawvideo', '-pix_fmt', 'rgb24' ]
def ignore_video_stream() -> Commands:

View File

@@ -4,17 +4,19 @@ import signal
import subprocess
import sys
from argparse import ArgumentParser, HelpFormatter
from functools import partial
from types import FrameType
from facefusion import metadata, wording
from facefusion.common_helper import is_linux, is_windows
ONNXRUNTIME_SET =\
{
'default': ('onnxruntime', '1.21.1')
'default': ('onnxruntime', '1.22.0')
}
if is_windows() or is_linux():
ONNXRUNTIME_SET['cuda'] = ('onnxruntime-gpu', '1.21.1')
ONNXRUNTIME_SET['openvino'] = ('onnxruntime-openvino', '1.21.0')
ONNXRUNTIME_SET['cuda'] = ('onnxruntime-gpu', '1.22.0')
ONNXRUNTIME_SET['openvino'] = ('onnxruntime-openvino', '1.22.0')
if is_windows():
ONNXRUNTIME_SET['directml'] = ('onnxruntime-directml', '1.17.3')
if is_linux():
@@ -22,14 +24,18 @@ if is_linux():
def cli() -> None:
signal.signal(signal.SIGINT, lambda signal_number, frame: sys.exit(0))
program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 50))
signal.signal(signal.SIGINT, signal_exit)
program = ArgumentParser(formatter_class = partial(HelpFormatter, max_help_position = 50))
program.add_argument('--onnxruntime', help = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIME_SET.keys(), required = True)
program.add_argument('--skip-conda', help = wording.get('help.skip_conda'), action = 'store_true')
program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
run(program)
def signal_exit(signum : int, frame : FrameType) -> None:
sys.exit(0)
def run(program : ArgumentParser) -> None:
args = program.parse_args()
has_conda = 'CONDA_PREFIX' in os.environ

View File

@@ -4,7 +4,7 @@ METADATA =\
{
'name': 'FaceFusion',
'description': 'Industry leading face manipulation platform',
'version': '3.2.0',
'version': '3.3.0',
'license': 'OpenRAIL-AS',
'author': 'Henry Ruhs',
'url': 'https://facefusion.io'

View File

@@ -7,6 +7,7 @@ from facefusion.processors.types import AgeModifierModel, DeepSwapperModel, Expr
age_modifier_models : List[AgeModifierModel] = [ 'styleganex_age' ]
deep_swapper_models : List[DeepSwapperModel] =\
[
'druuzil/adam_levine_320',
'druuzil/adrianne_palicki_384',
'druuzil/agnetha_falskog_224',
'druuzil/alan_ritchson_320',
@@ -14,6 +15,7 @@ deep_swapper_models : List[DeepSwapperModel] =\
'druuzil/amber_midthunder_320',
'druuzil/andras_arato_384',
'druuzil/andrew_tate_320',
'druuzil/angelina_jolie_384',
'druuzil/anne_hathaway_320',
'druuzil/anya_chalotra_320',
'druuzil/arnold_schwarzenegger_320',
@@ -21,6 +23,7 @@ deep_swapper_models : List[DeepSwapperModel] =\
'druuzil/benjamin_stiller_384',
'druuzil/bradley_pitt_224',
'druuzil/brie_larson_384',
'druuzil/bruce_campbell_384',
'druuzil/bryan_cranston_320',
'druuzil/catherine_blanchett_352',
'druuzil/christian_bale_320',
@@ -50,6 +53,7 @@ deep_swapper_models : List[DeepSwapperModel] =\
'druuzil/hugh_jackman_384',
'druuzil/idris_elba_320',
'druuzil/jack_nicholson_320',
'druuzil/james_carrey_384',
'druuzil/james_mcavoy_320',
'druuzil/james_varney_320',
'druuzil/jason_momoa_320',
@@ -61,6 +65,7 @@ deep_swapper_models : List[DeepSwapperModel] =\
'druuzil/kate_beckinsale_384',
'druuzil/laurence_fishburne_384',
'druuzil/lili_reinhart_320',
'druuzil/luke_evans_384',
'druuzil/mads_mikkelsen_384',
'druuzil/mary_winstead_320',
'druuzil/margaret_qualley_384',
@@ -69,13 +74,16 @@ deep_swapper_models : List[DeepSwapperModel] =\
'druuzil/michael_fox_320',
'druuzil/millie_bobby_brown_320',
'druuzil/morgan_freeman_320',
'druuzil/patrick_stewart_320',
'druuzil/patrick_stewart_224',
'druuzil/rachel_weisz_384',
'druuzil/rebecca_ferguson_320',
'druuzil/scarlett_johansson_320',
'druuzil/shannen_doherty_384',
'druuzil/seth_macfarlane_384',
'druuzil/thomas_cruise_320',
'druuzil/thomas_hanks_384',
'druuzil/william_murray_384',
'druuzil/zoe_saldana_384',
'edel/emma_roberts_224',
'edel/ivanka_trump_224',
'edel/lize_dzjabrailova_224',
@@ -177,6 +185,9 @@ face_swapper_set : FaceSwapperSet =\
'ghost_2_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'ghost_3_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'hififace_unofficial_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'hyperswap_1a_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'hyperswap_1b_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'hyperswap_1c_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
'inswapper_128': [ '128x128', '256x256', '384x384', '512x512', '768x768', '1024x1024' ],
'inswapper_128_fp16': [ '128x128', '256x256', '384x384', '512x512', '768x768', '1024x1024' ],
'simswap_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
@@ -186,8 +197,8 @@ face_swapper_set : FaceSwapperSet =\
face_swapper_models : List[FaceSwapperModel] = list(face_swapper_set.keys())
frame_colorizer_models : List[FrameColorizerModel] = [ 'ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable' ]
frame_colorizer_sizes : List[str] = [ '192x192', '256x256', '384x384', '512x512' ]
frame_enhancer_models : List[FrameEnhancerModel] = [ 'clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'ultra_sharp_x4' ]
lip_syncer_models : List[LipSyncerModel] = [ 'wav2lip_96', 'wav2lip_gan_96' ]
frame_enhancer_models : List[FrameEnhancerModel] = [ 'clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'ultra_sharp_x4', 'ultra_sharp_2_x4' ]
lip_syncer_models : List[LipSyncerModel] = [ 'edtalk_256', 'wav2lip_96', 'wav2lip_gan_96' ]
age_modifier_direction_range : Sequence[int] = create_int_range(-100, 100, 1)
deep_swapper_morph_range : Sequence[int] = create_int_range(0, 100, 1)
@@ -210,3 +221,4 @@ face_enhancer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
face_enhancer_weight_range : Sequence[float] = create_float_range(0.0, 1.0, 0.05)
frame_colorizer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
frame_enhancer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
lip_syncer_weight_range : Sequence[float] = create_float_range(0.0, 1.0, 0.05)

View File

@@ -9,13 +9,13 @@ import facefusion.choices
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import merge_matrix, paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
@@ -115,6 +115,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -134,7 +135,7 @@ def modify_age(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFra
extend_face_landmark_5 = scale_face_landmark_5(face_landmark_5, 0.875)
extend_vision_frame, extend_affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, extend_face_landmark_5, model_templates.get('target_with_background'), model_sizes.get('target_with_background'))
extend_vision_frame_raw = extend_vision_frame.copy()
box_mask = create_static_box_mask(model_sizes.get('target_with_background'), state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
box_mask = create_box_mask(extend_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask

View File

@@ -9,12 +9,12 @@ from cv2.typing import Size
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_region_mask, create_static_box_mask
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask, create_region_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import get_file_name, in_directory, is_image, is_video, resolve_file_paths, resolve_relative_path, same_file_extension
@@ -33,6 +33,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
if download_scope == 'full':
model_config.extend(
[
('druuzil', 'adam_levine_320'),
('druuzil', 'adrianne_palicki_384'),
('druuzil', 'agnetha_falskog_224'),
('druuzil', 'alan_ritchson_320'),
@@ -40,6 +41,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
('druuzil', 'amber_midthunder_320'),
('druuzil', 'andras_arato_384'),
('druuzil', 'andrew_tate_320'),
('druuzil', 'angelina_jolie_384'),
('druuzil', 'anne_hathaway_320'),
('druuzil', 'anya_chalotra_320'),
('druuzil', 'arnold_schwarzenegger_320'),
@@ -47,6 +49,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
('druuzil', 'benjamin_stiller_384'),
('druuzil', 'bradley_pitt_224'),
('druuzil', 'brie_larson_384'),
('druuzil', 'bruce_campbell_384'),
('druuzil', 'bryan_cranston_320'),
('druuzil', 'catherine_blanchett_352'),
('druuzil', 'christian_bale_320'),
@@ -76,6 +79,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
('druuzil', 'hugh_jackman_384'),
('druuzil', 'idris_elba_320'),
('druuzil', 'jack_nicholson_320'),
('druuzil', 'james_carrey_384'),
('druuzil', 'james_mcavoy_320'),
('druuzil', 'james_varney_320'),
('druuzil', 'jason_momoa_320'),
@@ -87,6 +91,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
('druuzil', 'kate_beckinsale_384'),
('druuzil', 'laurence_fishburne_384'),
('druuzil', 'lili_reinhart_320'),
('druuzil', 'luke_evans_384'),
('druuzil', 'mads_mikkelsen_384'),
('druuzil', 'mary_winstead_320'),
('druuzil', 'margaret_qualley_384'),
@@ -95,13 +100,16 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
('druuzil', 'michael_fox_320'),
('druuzil', 'millie_bobby_brown_320'),
('druuzil', 'morgan_freeman_320'),
('druuzil', 'patrick_stewart_320'),
('druuzil', 'patrick_stewart_224'),
('druuzil', 'rachel_weisz_384'),
('druuzil', 'rebecca_ferguson_320'),
('druuzil', 'scarlett_johansson_320'),
('druuzil', 'shannen_doherty_384'),
('druuzil', 'seth_macfarlane_384'),
('druuzil', 'thomas_cruise_320'),
('druuzil', 'thomas_hanks_384'),
('druuzil', 'william_murray_384'),
('druuzil', 'zoe_saldana_384'),
('edel', 'emma_roberts_224'),
('edel', 'ivanka_trump_224'),
('edel', 'lize_dzjabrailova_224'),
@@ -303,6 +311,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -319,7 +328,7 @@ def swap_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFram
model_size = get_model_size()
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmark_set.get('5/68'), model_template, model_size)
crop_vision_frame_raw = crop_vision_frame.copy()
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
crop_masks =\
[
box_mask
@@ -336,6 +345,11 @@ def swap_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFram
crop_vision_frame = conditional_match_frame_color(crop_vision_frame_raw, crop_vision_frame)
crop_masks.append(prepare_crop_mask(crop_source_mask, crop_target_mask))
if 'area' in state_manager.get_item('face_mask_types'):
face_landmark_68 = cv2.transform(target_face.landmark_set.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
area_mask = create_area_mask(crop_vision_frame, face_landmark_68, state_manager.get_item('face_mask_areas'))
crop_masks.append(area_mask)
if 'region' in state_manager.get_item('face_mask_types'):
region_mask = create_region_mask(crop_vision_frame, state_manager.get_item('face_mask_regions'))
crop_masks.append(region_mask)

View File

@@ -8,12 +8,12 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
@@ -129,6 +129,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -147,7 +148,7 @@ def restore_expression(source_vision_frame : VisionFrame, target_face : Face, te
source_vision_frame = cv2.resize(source_vision_frame, temp_vision_frame.shape[:2][::-1])
source_crop_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, target_face.landmark_set.get('5/68'), model_template, model_size)
target_crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmark_set.get('5/68'), model_template, model_size)
box_mask = create_static_box_mask(target_crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
box_mask = create_box_mask(target_crop_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask

View File

@@ -7,10 +7,10 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, video_manager, wording
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_region_mask, create_static_box_mask
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask, create_region_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import in_directory, same_file_extension
@@ -56,6 +56,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
@@ -82,11 +83,11 @@ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFra
if target_face.angle == 0:
cv2.line(temp_vision_frame, (x1, y1), (x2, y1), primary_light_color, 3)
elif target_face.angle == 180:
if target_face.angle == 180:
cv2.line(temp_vision_frame, (x1, y2), (x2, y2), primary_light_color, 3)
elif target_face.angle == 90:
if target_face.angle == 90:
cv2.line(temp_vision_frame, (x2, y1), (x2, y2), primary_light_color, 3)
elif target_face.angle == 270:
if target_face.angle == 270:
cv2.line(temp_vision_frame, (x1, y1), (x1, y2), primary_light_color, 3)
if 'face-mask' in face_debugger_items:
@@ -96,13 +97,18 @@ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFra
crop_masks = []
if 'box' in state_manager.get_item('face_mask_types'):
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], 0, state_manager.get_item('face_mask_padding'))
box_mask = create_box_mask(crop_vision_frame, 0, state_manager.get_item('face_mask_padding'))
crop_masks.append(box_mask)
if 'occlusion' in state_manager.get_item('face_mask_types'):
occlusion_mask = create_occlusion_mask(crop_vision_frame)
crop_masks.append(occlusion_mask)
if 'area' in state_manager.get_item('face_mask_types'):
face_landmark_68 = cv2.transform(target_face.landmark_set.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
area_mask = create_area_mask(crop_vision_frame, face_landmark_68, state_manager.get_item('face_mask_areas'))
crop_masks.append(area_mask)
if 'region' in state_manager.get_item('face_mask_types'):
region_mask = create_region_mask(crop_vision_frame, state_manager.get_item('face_mask_regions'))
crop_masks.append(region_mask)

View File

@@ -8,12 +8,12 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_float_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
from facefusion.face_masker import create_static_box_mask
from facefusion.face_masker import create_box_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
@@ -182,6 +182,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -198,7 +199,7 @@ def edit_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFram
model_size = get_model_options().get('size')
face_landmark_5 = scale_face_landmark_5(target_face.landmark_set.get('5/68'), 1.5)
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_template, model_size)
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_vision_frame = prepare_crop_frame(crop_vision_frame)
crop_vision_frame = apply_edit(crop_vision_frame, target_face.landmark_set.get('68'))
crop_vision_frame = normalize_crop_frame(crop_vision_frame)

View File

@@ -8,12 +8,12 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_float_metavar, create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
@@ -275,6 +275,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -290,7 +291,7 @@ def enhance_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionF
model_template = get_model_options().get('template')
model_size = get_model_options().get('size')
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmark_set.get('5/68'), model_template, model_size)
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask

View File

@@ -2,19 +2,20 @@ from argparse import ArgumentParser
from functools import lru_cache
from typing import List, Tuple
import cv2
import numpy
import facefusion.choices
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import get_first
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.face_analyser import get_average_face, get_many_faces, get_one_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_occlusion_mask, create_region_mask, create_static_box_mask
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask, create_region_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces, sort_faces_by_order
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import filter_image_paths, has_image, in_directory, is_image, is_video, resolve_relative_path, same_file_extension
@@ -192,6 +193,78 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
},
'hyperswap_1a_256':
{
'hashes':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1a_256.hash'),
'path': resolve_relative_path('../.assets/models/hyperswap_1a_256.hash')
}
},
'sources':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1a_256.onnx'),
'path': resolve_relative_path('../.assets/models/hyperswap_1a_256.onnx')
}
},
'type': 'hyperswap',
'template': 'arcface_128',
'size': (256, 256),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
},
'hyperswap_1b_256':
{
'hashes':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1b_256.hash'),
'path': resolve_relative_path('../.assets/models/hyperswap_1b_256.hash')
}
},
'sources':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1b_256.onnx'),
'path': resolve_relative_path('../.assets/models/hyperswap_1b_256.onnx')
}
},
'type': 'hyperswap',
'template': 'arcface_128',
'size': (256, 256),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
},
'hyperswap_1c_256':
{
'hashes':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1c_256.hash'),
'path': resolve_relative_path('../.assets/models/hyperswap_1c_256.hash')
}
},
'sources':
{
'face_swapper':
{
'url': resolve_download_url('models-3.3.0', 'hyperswap_1c_256.onnx'),
'path': resolve_relative_path('../.assets/models/hyperswap_1c_256.onnx')
}
},
'type': 'hyperswap',
'template': 'arcface_128',
'size': (256, 256),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
},
'inswapper_128':
{
'hashes':
@@ -406,9 +479,10 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
get_static_model_initializer.cache_clear()
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
@@ -428,7 +502,7 @@ def swap_face(source_face : Face, target_face : Face, temp_vision_frame : Vision
crop_masks = []
if 'box' in state_manager.get_item('face_mask_types'):
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
crop_masks.append(box_mask)
if 'occlusion' in state_manager.get_item('face_mask_types'):
@@ -443,6 +517,11 @@ def swap_face(source_face : Face, target_face : Face, temp_vision_frame : Vision
temp_vision_frames.append(pixel_boost_vision_frame)
crop_vision_frame = explode_pixel_boost(temp_vision_frames, pixel_boost_total, model_size, pixel_boost_size)
if 'area' in state_manager.get_item('face_mask_types'):
face_landmark_68 = cv2.transform(target_face.landmark_set.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
area_mask = create_area_mask(crop_vision_frame, face_landmark_68, state_manager.get_item('face_mask_areas'))
crop_masks.append(area_mask)
if 'region' in state_manager.get_item('face_mask_types'):
region_mask = create_region_mask(crop_vision_frame, state_manager.get_item('face_mask_regions'))
crop_masks.append(region_mask)
@@ -507,12 +586,19 @@ def prepare_source_embedding(source_face : Face) -> Embedding:
if model_type == 'ghost':
source_embedding, _ = convert_embedding(source_face)
source_embedding = source_embedding.reshape(1, -1)
elif model_type == 'inswapper':
return source_embedding
if model_type == 'hyperswap':
source_embedding = source_face.normed_embedding.reshape((1, -1))
return source_embedding
if model_type == 'inswapper':
model_path = get_model_options().get('sources').get('face_swapper').get('path')
model_initializer = get_static_model_initializer(model_path)
source_embedding = source_face.embedding.reshape((1, -1))
source_embedding = numpy.dot(source_embedding, model_initializer) / numpy.linalg.norm(source_embedding)
else:
return source_embedding
_, source_normed_embedding = convert_embedding(source_face)
source_embedding = source_normed_embedding.reshape(1, -1)
return source_embedding
@@ -543,7 +629,7 @@ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
model_standard_deviation = get_model_options().get('standard_deviation')
crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
if model_type in [ 'ghost', 'hififace', 'uniface' ]:
if model_type in [ 'ghost', 'hififace', 'hyperswap', 'uniface' ]:
crop_vision_frame = crop_vision_frame * model_standard_deviation + model_mean
crop_vision_frame = crop_vision_frame.clip(0, 1)
crop_vision_frame = crop_vision_frame[:, :, ::-1] * 255

View File

@@ -8,7 +8,7 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
@@ -188,6 +188,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':

View File

@@ -8,7 +8,7 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, inference_manager, logger, process_manager, state_manager, wording
from facefusion import config, content_analyser, inference_manager, logger, process_manager, state_manager, video_manager, wording
from facefusion.common_helper import create_int_metavar
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
@@ -381,6 +381,27 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
},
'size': (128, 8, 4),
'scale': 4
},
'ultra_sharp_2_x4':
{
'hashes':
{
'frame_enhancer':
{
'url': resolve_download_url('models-3.3.0', 'ultra_sharp_2_x4.hash'),
'path': resolve_relative_path('../.assets/models/ultra_sharp_2_x4.hash')
}
},
'sources':
{
'frame_enhancer':
{
'url': resolve_download_url('models-3.3.0', 'ultra_sharp_2_x4.onnx'),
'path': resolve_relative_path('../.assets/models/ultra_sharp_2_x4.onnx')
}
},
'size': (1024, 64, 32),
'scale': 4
}
}
@@ -450,6 +471,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':

View File

@@ -8,21 +8,22 @@ import numpy
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
import facefusion.processors.core as processors
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, voice_extractor, wording
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, video_manager, voice_extractor, wording
from facefusion.audio import create_empty_audio_frame, get_voice_frame, read_static_voice
from facefusion.common_helper import create_float_metavar
from facefusion.common_helper import get_first
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.face_helper import create_bounding_box, paste_back, warp_face_by_bounding_box, warp_face_by_face_landmark_5
from facefusion.face_masker import create_mouth_mask, create_occlusion_mask, create_static_box_mask
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask
from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
from facefusion.face_store import get_reference_faces
from facefusion.filesystem import filter_audio_paths, has_audio, in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.processors import choices as processors_choices
from facefusion.processors.types import LipSyncerInputs
from facefusion.processors.types import LipSyncerInputs, LipSyncerWeight
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import conditional_thread_semaphore
from facefusion.types import ApplyStateItem, Args, AudioFrame, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
from facefusion.types import ApplyStateItem, Args, AudioFrame, BoundingBox, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
from facefusion.vision import read_image, read_static_image, restrict_video_fps, write_image
@@ -30,6 +31,27 @@ from facefusion.vision import read_image, read_static_image, restrict_video_fps,
def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
return\
{
'edtalk_256':
{
'hashes':
{
'lip_syncer':
{
'url': resolve_download_url('models-3.3.0', 'edtalk_256.hash'),
'path': resolve_relative_path('../.assets/models/edtalk_256.hash')
}
},
'sources':
{
'lip_syncer':
{
'url': resolve_download_url('models-3.3.0', 'edtalk_256.onnx'),
'path': resolve_relative_path('../.assets/models/edtalk_256.onnx')
}
},
'type': 'edtalk',
'size': (256, 256)
},
'wav2lip_96':
{
'hashes':
@@ -48,6 +70,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/wav2lip_96.onnx')
}
},
'type': 'wav2lip',
'size': (96, 96)
},
'wav2lip_gan_96':
@@ -68,6 +91,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/wav2lip_gan_96.onnx')
}
},
'type': 'wav2lip',
'size': (96, 96)
}
}
@@ -94,11 +118,13 @@ def register_args(program : ArgumentParser) -> None:
group_processors = find_argument_group(program, 'processors')
if group_processors:
group_processors.add_argument('--lip-syncer-model', help = wording.get('help.lip_syncer_model'), default = config.get_str_value('processors', 'lip_syncer_model', 'wav2lip_gan_96'), choices = processors_choices.lip_syncer_models)
facefusion.jobs.job_store.register_step_keys([ 'lip_syncer_model' ])
group_processors.add_argument('--lip-syncer-weight', help = wording.get('help.lip_syncer_weight'), type = float, default = config.get_float_value('processors', 'lip_syncer_weight', '0.5'), choices = processors_choices.lip_syncer_weight_range, metavar = create_float_metavar(processors_choices.lip_syncer_weight_range))
facefusion.jobs.job_store.register_step_keys([ 'lip_syncer_model', 'lip_syncer_weight' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('lip_syncer_model', args.get('lip_syncer_model'))
apply_state_item('lip_syncer_weight', args.get('lip_syncer_weight'))
def pre_check() -> bool:
@@ -127,6 +153,7 @@ def pre_process(mode : ProcessMode) -> bool:
def post_process() -> None:
read_static_image.cache_clear()
read_static_voice.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
@@ -140,68 +167,115 @@ def post_process() -> None:
def sync_lip(target_face : Face, temp_audio_frame : AudioFrame, temp_vision_frame : VisionFrame) -> VisionFrame:
model_type = get_model_options().get('type')
model_size = get_model_options().get('size')
temp_audio_frame = prepare_audio_frame(temp_audio_frame)
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmark_set.get('5/68'), 'ffhq_512', (512, 512))
face_landmark_68 = cv2.transform(target_face.landmark_set.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
bounding_box = create_bounding_box(face_landmark_68)
bounding_box[1] -= numpy.abs(bounding_box[3] - bounding_box[1]) * 0.125
mouth_mask = create_mouth_mask(face_landmark_68)
box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
crop_masks =\
[
mouth_mask,
box_mask
]
crop_masks = []
if 'occlusion' in state_manager.get_item('face_mask_types'):
occlusion_mask = create_occlusion_mask(crop_vision_frame)
crop_masks.append(occlusion_mask)
close_vision_frame, close_matrix = warp_face_by_bounding_box(crop_vision_frame, bounding_box, model_size)
close_vision_frame = prepare_crop_frame(close_vision_frame)
close_vision_frame = forward(temp_audio_frame, close_vision_frame)
close_vision_frame = normalize_close_frame(close_vision_frame)
crop_vision_frame = cv2.warpAffine(close_vision_frame, cv2.invertAffineTransform(close_matrix), (512, 512), borderMode = cv2.BORDER_REPLICATE)
if model_type == 'edtalk':
lip_syncer_weight = numpy.array([ state_manager.get_item('lip_syncer_weight') ]).astype(numpy.float32)
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), state_manager.get_item('face_mask_padding'))
crop_masks.append(box_mask)
crop_vision_frame = prepare_crop_frame(crop_vision_frame)
crop_vision_frame = forward_edtalk(temp_audio_frame, crop_vision_frame, lip_syncer_weight)
crop_vision_frame = normalize_crop_frame(crop_vision_frame)
if model_type == 'wav2lip':
face_landmark_68 = cv2.transform(target_face.landmark_set.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
area_mask = create_area_mask(crop_vision_frame, face_landmark_68, [ 'lower-face' ])
crop_masks.append(area_mask)
bounding_box = create_bounding_box(face_landmark_68)
bounding_box = resize_bounding_box(bounding_box, 1 / 8)
area_vision_frame, area_matrix = warp_face_by_bounding_box(crop_vision_frame, bounding_box, model_size)
area_vision_frame = prepare_crop_frame(area_vision_frame)
area_vision_frame = forward_wav2lip(temp_audio_frame, area_vision_frame)
area_vision_frame = normalize_crop_frame(area_vision_frame)
crop_vision_frame = cv2.warpAffine(area_vision_frame, cv2.invertAffineTransform(area_matrix), (512, 512), borderMode = cv2.BORDER_REPLICATE)
crop_mask = numpy.minimum.reduce(crop_masks)
paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
return paste_vision_frame
def forward(temp_audio_frame : AudioFrame, close_vision_frame : VisionFrame) -> VisionFrame:
def forward_edtalk(temp_audio_frame : AudioFrame, crop_vision_frame : VisionFrame, lip_syncer_weight : LipSyncerWeight) -> VisionFrame:
lip_syncer = get_inference_pool().get('lip_syncer')
with conditional_thread_semaphore():
close_vision_frame = lip_syncer.run(None,
crop_vision_frame = lip_syncer.run(None,
{
'source': temp_audio_frame,
'target': close_vision_frame
'target': crop_vision_frame,
'weight': lip_syncer_weight
})[0]
return close_vision_frame
return crop_vision_frame
def forward_wav2lip(temp_audio_frame : AudioFrame, area_vision_frame : VisionFrame) -> VisionFrame:
lip_syncer = get_inference_pool().get('lip_syncer')
with conditional_thread_semaphore():
area_vision_frame = lip_syncer.run(None,
{
'source': temp_audio_frame,
'target': area_vision_frame
})[0]
return area_vision_frame
def prepare_audio_frame(temp_audio_frame : AudioFrame) -> AudioFrame:
model_type = get_model_options().get('type')
temp_audio_frame = numpy.maximum(numpy.exp(-5 * numpy.log(10)), temp_audio_frame)
temp_audio_frame = numpy.log10(temp_audio_frame) * 1.6 + 3.2
temp_audio_frame = temp_audio_frame.clip(-4, 4).astype(numpy.float32)
if model_type == 'wav2lip':
temp_audio_frame = temp_audio_frame * state_manager.get_item('lip_syncer_weight') * 2.0
temp_audio_frame = numpy.expand_dims(temp_audio_frame, axis = (0, 1))
return temp_audio_frame
def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
model_type = get_model_options().get('type')
model_size = get_model_options().get('size')
if model_type == 'edtalk':
crop_vision_frame = cv2.resize(crop_vision_frame, model_size, interpolation = cv2.INTER_AREA)
crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
if model_type == 'wav2lip':
crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
prepare_vision_frame = crop_vision_frame.copy()
prepare_vision_frame[:, 48:] = 0
prepare_vision_frame[:, model_size[0] // 2:] = 0
crop_vision_frame = numpy.concatenate((prepare_vision_frame, crop_vision_frame), axis = 3)
crop_vision_frame = crop_vision_frame.transpose(0, 3, 1, 2).astype('float32') / 255.0
return crop_vision_frame
def normalize_close_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
def resize_bounding_box(bounding_box : BoundingBox, aspect_ratio : float) -> BoundingBox:
x1, y1, x2, y2 = bounding_box
y1 -= numpy.abs(y2 - y1) * aspect_ratio
bounding_box[1] = max(y1, 0)
return bounding_box
def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
model_type = get_model_options().get('type')
crop_vision_frame = crop_vision_frame[0].transpose(1, 2, 0)
crop_vision_frame = crop_vision_frame.clip(0, 1) * 255
crop_vision_frame = crop_vision_frame.astype(numpy.uint8)
if model_type == 'edtalk':
crop_vision_frame = crop_vision_frame[:, :, ::-1]
crop_vision_frame = cv2.resize(crop_vision_frame, (512, 512), interpolation = cv2.INTER_CUBIC)
return crop_vision_frame

View File

@@ -10,10 +10,10 @@ ExpressionRestorerModel = Literal['live_portrait']
FaceDebuggerItem = Literal['bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-landmark-68/5', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender', 'race']
FaceEditorModel = Literal['live_portrait']
FaceEnhancerModel = Literal['codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'gpen_bfr_1024', 'gpen_bfr_2048', 'restoreformer_plus_plus']
FaceSwapperModel = Literal['blendswap_256', 'ghost_1_256', 'ghost_2_256', 'ghost_3_256', 'hififace_unofficial_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_unofficial_512', 'uniface_256']
FaceSwapperModel = Literal['blendswap_256', 'ghost_1_256', 'ghost_2_256', 'ghost_3_256', 'hififace_unofficial_256', 'hyperswap_1a_256', 'hyperswap_1b_256', 'hyperswap_1c_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_unofficial_512', 'uniface_256']
FrameColorizerModel = Literal['ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable']
FrameEnhancerModel = Literal['clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'ultra_sharp_x4']
LipSyncerModel = Literal['wav2lip_96', 'wav2lip_gan_96']
FrameEnhancerModel = Literal['clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'ultra_sharp_x4', 'ultra_sharp_2_x4']
LipSyncerModel = Literal['edtalk_256', 'wav2lip_96', 'wav2lip_gan_96']
FaceSwapperSet : TypeAlias = Dict[FaceSwapperModel, List[str]]
@@ -103,7 +103,8 @@ ProcessorStateKey = Literal\
'frame_colorizer_blend',
'frame_enhancer_model',
'frame_enhancer_blend',
'lip_syncer_model'
'lip_syncer_model',
'lip_syncer_weight'
]
ProcessorState = TypedDict('ProcessorState',
{
@@ -146,6 +147,7 @@ ProcessorStateSet : TypeAlias = Dict[AppContext, ProcessorState]
AgeModifierDirection : TypeAlias = NDArray[Any]
DeepSwapperMorph : TypeAlias = NDArray[Any]
FaceEnhancerWeight : TypeAlias = NDArray[Any]
LipSyncerWeight : TypeAlias = NDArray[Any]
LivePortraitPitch : TypeAlias = float
LivePortraitYaw : TypeAlias = float
LivePortraitRoll : TypeAlias = float

View File

@@ -136,10 +136,11 @@ def create_face_masker_program() -> ArgumentParser:
group_face_masker.add_argument('--face-occluder-model', help = wording.get('help.face_occluder_model'), default = config.get_str_value('face_masker', 'face_occluder_model', 'xseg_1'), choices = facefusion.choices.face_occluder_models)
group_face_masker.add_argument('--face-parser-model', help = wording.get('help.face_parser_model'), default = config.get_str_value('face_masker', 'face_parser_model', 'bisenet_resnet_34'), choices = facefusion.choices.face_parser_models)
group_face_masker.add_argument('--face-mask-types', help = wording.get('help.face_mask_types').format(choices = ', '.join(facefusion.choices.face_mask_types)), default = config.get_str_list('face_masker', 'face_mask_types', 'box'), choices = facefusion.choices.face_mask_types, nargs = '+', metavar = 'FACE_MASK_TYPES')
group_face_masker.add_argument('--face-mask-areas', help = wording.get('help.face_mask_areas').format(choices = ', '.join(facefusion.choices.face_mask_areas)), default = config.get_str_list('face_masker', 'face_mask_areas', ' '.join(facefusion.choices.face_mask_areas)), choices = facefusion.choices.face_mask_areas, nargs = '+', metavar = 'FACE_MASK_AREAS')
group_face_masker.add_argument('--face-mask-regions', help = wording.get('help.face_mask_regions').format(choices = ', '.join(facefusion.choices.face_mask_regions)), default = config.get_str_list('face_masker', 'face_mask_regions', ' '.join(facefusion.choices.face_mask_regions)), choices = facefusion.choices.face_mask_regions, nargs = '+', metavar = 'FACE_MASK_REGIONS')
group_face_masker.add_argument('--face-mask-blur', help = wording.get('help.face_mask_blur'), type = float, default = config.get_float_value('face_masker', 'face_mask_blur', '0.3'), choices = facefusion.choices.face_mask_blur_range, metavar = create_float_metavar(facefusion.choices.face_mask_blur_range))
group_face_masker.add_argument('--face-mask-padding', help = wording.get('help.face_mask_padding'), type = int, default = config.get_int_list('face_masker', 'face_mask_padding', '0 0 0 0'), nargs = '+')
group_face_masker.add_argument('--face-mask-regions', help = wording.get('help.face_mask_regions').format(choices = ', '.join(facefusion.choices.face_mask_regions)), default = config.get_str_list('face_masker', 'face_mask_regions', ' '.join(facefusion.choices.face_mask_regions)), choices = facefusion.choices.face_mask_regions, nargs = '+', metavar = 'FACE_MASK_REGIONS')
job_store.register_step_keys([ 'face_occluder_model', 'face_parser_model', 'face_mask_types', 'face_mask_blur', 'face_mask_padding', 'face_mask_regions' ])
job_store.register_step_keys([ 'face_occluder_model', 'face_parser_model', 'face_mask_types', 'face_mask_areas', 'face_mask_regions', 'face_mask_blur', 'face_mask_padding' ])
return program
@@ -193,18 +194,6 @@ def create_uis_program() -> ArgumentParser:
return program
def create_execution_program() -> ArgumentParser:
program = ArgumentParser(add_help = False)
available_execution_providers = get_available_execution_providers()
group_execution = program.add_argument_group('execution')
group_execution.add_argument('--execution-device-id', help = wording.get('help.execution_device_id'), default = config.get_str_value('execution', 'execution_device_id', '0'))
group_execution.add_argument('--execution-providers', help = wording.get('help.execution_providers').format(choices = ', '.join(available_execution_providers)), default = config.get_str_list('execution', 'execution_providers', get_first(available_execution_providers)), choices = available_execution_providers, nargs = '+', metavar = 'EXECUTION_PROVIDERS')
group_execution.add_argument('--execution-thread-count', help = wording.get('help.execution_thread_count'), type = int, default = config.get_int_value('execution', 'execution_thread_count', '4'), choices = facefusion.choices.execution_thread_count_range, metavar = create_int_metavar(facefusion.choices.execution_thread_count_range))
group_execution.add_argument('--execution-queue-count', help = wording.get('help.execution_queue_count'), type = int, default = config.get_int_value('execution', 'execution_queue_count', '1'), choices = facefusion.choices.execution_queue_count_range, metavar = create_int_metavar(facefusion.choices.execution_queue_count_range))
job_store.register_job_keys([ 'execution_device_id', 'execution_providers', 'execution_thread_count', 'execution_queue_count' ])
return program
def create_download_providers_program() -> ArgumentParser:
program = ArgumentParser(add_help = False)
group_download = program.add_argument_group('download')
@@ -221,6 +210,26 @@ def create_download_scope_program() -> ArgumentParser:
return program
def create_benchmark_program() -> ArgumentParser:
program = ArgumentParser(add_help = False)
group_benchmark = program.add_argument_group('benchmark')
group_benchmark.add_argument('--benchmark-resolutions', help = wording.get('help.benchmark_resolutions'), default = config.get_str_list('benchmark', 'benchmark_resolutions', get_first(facefusion.choices.benchmark_resolutions)), choices = facefusion.choices.benchmark_resolutions, nargs = '+')
group_benchmark.add_argument('--benchmark-cycle-count', help = wording.get('help.benchmark_cycle_count'), type = int, default = config.get_int_value('benchmark', 'benchmark_cycle_count', '5'), choices = facefusion.choices.benchmark_cycle_count_range)
return program
def create_execution_program() -> ArgumentParser:
program = ArgumentParser(add_help = False)
available_execution_providers = get_available_execution_providers()
group_execution = program.add_argument_group('execution')
group_execution.add_argument('--execution-device-id', help = wording.get('help.execution_device_id'), default = config.get_str_value('execution', 'execution_device_id', '0'))
group_execution.add_argument('--execution-providers', help = wording.get('help.execution_providers').format(choices = ', '.join(available_execution_providers)), default = config.get_str_list('execution', 'execution_providers', get_first(available_execution_providers)), choices = available_execution_providers, nargs = '+', metavar = 'EXECUTION_PROVIDERS')
group_execution.add_argument('--execution-thread-count', help = wording.get('help.execution_thread_count'), type = int, default = config.get_int_value('execution', 'execution_thread_count', '4'), choices = facefusion.choices.execution_thread_count_range, metavar = create_int_metavar(facefusion.choices.execution_thread_count_range))
group_execution.add_argument('--execution-queue-count', help = wording.get('help.execution_queue_count'), type = int, default = config.get_int_value('execution', 'execution_queue_count', '1'), choices = facefusion.choices.execution_queue_count_range, metavar = create_int_metavar(facefusion.choices.execution_queue_count_range))
job_store.register_job_keys([ 'execution_device_id', 'execution_providers', 'execution_thread_count', 'execution_queue_count' ])
return program
def create_memory_program() -> ArgumentParser:
program = ArgumentParser(add_help = False)
group_memory = program.add_argument_group('memory')
@@ -283,6 +292,7 @@ def create_program() -> ArgumentParser:
sub_program.add_parser('headless-run', help = wording.get('help.headless_run'), parents = [ create_config_path_program(), create_temp_path_program(), create_jobs_path_program(), create_source_paths_program(), create_target_path_program(), create_output_path_program(), collect_step_program(), collect_job_program() ], formatter_class = create_help_formatter_large)
sub_program.add_parser('batch-run', help = wording.get('help.batch_run'), parents = [ create_config_path_program(), create_temp_path_program(), create_jobs_path_program(), create_source_pattern_program(), create_target_pattern_program(), create_output_pattern_program(), collect_step_program(), collect_job_program() ], formatter_class = create_help_formatter_large)
sub_program.add_parser('force-download', help = wording.get('help.force_download'), parents = [ create_download_providers_program(), create_download_scope_program(), create_log_level_program() ], formatter_class = create_help_formatter_large)
sub_program.add_parser('benchmark', help = wording.get('help.benchmark'), parents = [ create_temp_path_program(), collect_step_program(), create_benchmark_program(), collect_job_program() ], formatter_class = create_help_formatter_large)
# job manager
sub_program.add_parser('job-list', help = wording.get('help.job_list'), parents = [ create_job_status_program(), create_jobs_path_program(), create_log_level_program() ], formatter_class = create_help_formatter_large)
sub_program.add_parser('job-create', help = wording.get('help.job_create'), parents = [ create_job_id_program(), create_jobs_path_program(), create_log_level_program() ], formatter_class = create_help_formatter_large)

View File

@@ -1,51 +0,0 @@
from typing import Any, Dict
import numpy
from facefusion import logger, state_manager
from facefusion.face_store import get_face_store
from facefusion.types import FaceSet
def create_statistics(static_faces : FaceSet) -> Dict[str, Any]:
face_detector_scores = []
face_landmarker_scores = []
statistics =\
{
'min_face_detector_score': 0,
'min_face_landmarker_score': 0,
'max_face_detector_score': 0,
'max_face_landmarker_score': 0,
'average_face_detector_score': 0,
'average_face_landmarker_score': 0,
'total_face_landmark_5_fallbacks': 0,
'total_frames_with_faces': 0,
'total_faces': 0
}
for faces in static_faces.values():
statistics['total_frames_with_faces'] = statistics.get('total_frames_with_faces') + 1
for face in faces:
statistics['total_faces'] = statistics.get('total_faces') + 1
face_detector_scores.append(face.score_set.get('detector'))
face_landmarker_scores.append(face.score_set.get('landmarker'))
if numpy.array_equal(face.landmark_set.get('5'), face.landmark_set.get('5/68')):
statistics['total_face_landmark_5_fallbacks'] = statistics.get('total_face_landmark_5_fallbacks') + 1
if face_detector_scores:
statistics['min_face_detector_score'] = round(min(face_detector_scores), 2)
statistics['max_face_detector_score'] = round(max(face_detector_scores), 2)
statistics['average_face_detector_score'] = round(numpy.mean(face_detector_scores), 2)
if face_landmarker_scores:
statistics['min_face_landmarker_score'] = round(min(face_landmarker_scores), 2)
statistics['max_face_landmarker_score'] = round(max(face_landmarker_scores), 2)
statistics['average_face_landmarker_score'] = round(numpy.mean(face_landmarker_scores), 2)
return statistics
def conditional_log_statistics() -> None:
if state_manager.get_item('log_level') == 'debug':
statistics = create_statistics(get_face_store().get('static_faces'))
for name, value in statistics.items():
logger.debug(str(name) + ': ' + str(value), __name__)

View File

@@ -1,6 +1,7 @@
from collections import namedtuple
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, TypeAlias, TypedDict
import cv2
import numpy
from numpy.typing import NDArray
from onnxruntime import InferenceSession
@@ -49,6 +50,7 @@ FaceStore = TypedDict('FaceStore',
'static_faces' : FaceSet,
'reference_faces' : FaceSet
})
VideoPoolSet : TypeAlias = Dict[str, cv2.VideoCapture]
VisionFrame : TypeAlias = NDArray[Any]
Mask : TypeAlias = NDArray[Any]
@@ -105,9 +107,11 @@ FaceSelectorMode = Literal['many', 'one', 'reference']
FaceSelectorOrder = Literal['left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best']
FaceOccluderModel = Literal['xseg_1', 'xseg_2', 'xseg_3']
FaceParserModel = Literal['bisenet_resnet_18', 'bisenet_resnet_34']
FaceMaskType = Literal['box', 'occlusion', 'region']
FaceMaskType = Literal['box', 'occlusion', 'area', 'region']
FaceMaskArea = Literal['upper-face', 'lower-face', 'mouth']
FaceMaskRegion = Literal['skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip']
FaceMaskRegionSet : TypeAlias = Dict[FaceMaskRegion, int]
FaceMaskAreaSet : TypeAlias = Dict[FaceMaskArea, List[int]]
AudioFormat = Literal['flac', 'm4a', 'mp3', 'ogg', 'opus', 'wav']
ImageFormat = Literal['bmp', 'jpeg', 'png', 'tiff', 'webp']
@@ -126,6 +130,18 @@ EncoderSet = TypedDict('EncoderSet',
})
VideoPreset = Literal['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow']
BenchmarkResolution = Literal['240p', '360p', '540p', '720p', '1080p', '1440p', '2160p']
BenchmarkSet : TypeAlias = Dict[BenchmarkResolution, str]
BenchmarkCycleSet = TypedDict('BenchmarkCycleSet',
{
'target_path' : str,
'cycle_count' : int,
'average_run' : float,
'fastest_run' : float,
'slowest_run' : float,
'relative_fps' : float
})
WebcamMode = Literal['inline', 'udp', 'v4l2']
StreamMode = Literal['udp', 'v4l2']
@@ -222,7 +238,6 @@ Job = TypedDict('Job',
})
JobSet : TypeAlias = Dict[str, Job]
ApplyStateItem : TypeAlias = Callable[[Any, Any], None]
StateKey = Literal\
[
'command',
@@ -235,6 +250,10 @@ StateKey = Literal\
'source_pattern',
'target_pattern',
'output_pattern',
'download_providers',
'download_scope',
'benchmark_resolutions',
'benchmark_cycle_count',
'face_detector_model',
'face_detector_size',
'face_detector_angles',
@@ -253,9 +272,10 @@ StateKey = Literal\
'face_occluder_model',
'face_parser_model',
'face_mask_types',
'face_mask_areas',
'face_mask_regions',
'face_mask_blur',
'face_mask_padding',
'face_mask_regions',
'trim_frame_start',
'trim_frame_end',
'temp_frame_format',
@@ -278,8 +298,6 @@ StateKey = Literal\
'execution_providers',
'execution_thread_count',
'execution_queue_count',
'download_providers',
'download_scope',
'video_memory_strategy',
'system_memory_limit',
'log_level',
@@ -300,6 +318,10 @@ State = TypedDict('State',
'source_pattern' : str,
'target_pattern' : str,
'output_pattern' : str,
'download_providers': List[DownloadProvider],
'download_scope': DownloadScope,
'benchmark_resolutions': List[BenchmarkResolution],
'benchmark_cycle_count': int,
'face_detector_model' : FaceDetectorModel,
'face_detector_size' : str,
'face_detector_angles' : List[Angle],
@@ -318,9 +340,10 @@ State = TypedDict('State',
'face_occluder_model' : FaceOccluderModel,
'face_parser_model' : FaceParserModel,
'face_mask_types' : List[FaceMaskType],
'face_mask_areas' : List[FaceMaskArea],
'face_mask_regions' : List[FaceMaskRegion],
'face_mask_blur' : float,
'face_mask_padding' : Padding,
'face_mask_regions' : List[FaceMaskRegion],
'trim_frame_start' : int,
'trim_frame_end' : int,
'temp_frame_format' : TempFrameFormat,
@@ -343,8 +366,6 @@ State = TypedDict('State',
'execution_providers' : List[ExecutionProvider],
'execution_thread_count' : int,
'execution_queue_count' : int,
'download_providers' : List[DownloadProvider],
'download_scope' : DownloadScope,
'video_memory_strategy' : VideoMemoryStrategy,
'system_memory_limit' : int,
'log_level' : LogLevel,
@@ -353,5 +374,6 @@ State = TypedDict('State',
'job_status' : JobStatus,
'step_index' : int
})
ApplyStateItem : TypeAlias = Callable[[Any, Any], None]
StateSet : TypeAlias = Dict[AppContext, State]

View File

@@ -1,31 +1,13 @@
import hashlib
import os
import statistics
import tempfile
from time import perf_counter
from typing import Any, Dict, Generator, List, Optional
from typing import Any, Generator, List, Optional
import gradio
from facefusion import state_manager, wording
from facefusion.core import conditional_process
from facefusion.filesystem import get_file_extension, is_video
from facefusion.memory import limit_system_memory
from facefusion import benchmarker, state_manager, wording
from facefusion.types import BenchmarkResolution
from facefusion.uis.core import get_ui_component
from facefusion.vision import count_video_frame_total, detect_video_fps, detect_video_resolution, pack_resolution
BENCHMARK_BENCHMARKS_DATAFRAME : Optional[gradio.Dataframe] = None
BENCHMARK_START_BUTTON : Optional[gradio.Button] = None
BENCHMARKS : Dict[str, str] =\
{
'240p': '.assets/examples/target-240p.mp4',
'360p': '.assets/examples/target-360p.mp4',
'540p': '.assets/examples/target-540p.mp4',
'720p': '.assets/examples/target-720p.mp4',
'1080p': '.assets/examples/target-1080p.mp4',
'1440p': '.assets/examples/target-1440p.mp4',
'2160p': '.assets/examples/target-2160p.mp4'
}
def render() -> None:
@@ -36,7 +18,7 @@ def render() -> None:
headers =
[
'target_path',
'benchmark_cycles',
'cycle_count',
'average_run',
'fastest_run',
'slowest_run',
@@ -61,72 +43,19 @@ def render() -> None:
def listen() -> None:
benchmark_runs_checkbox_group = get_ui_component('benchmark_runs_checkbox_group')
benchmark_cycles_slider = get_ui_component('benchmark_cycles_slider')
benchmark_resolutions_checkbox_group = get_ui_component('benchmark_resolutions_checkbox_group')
benchmark_cycle_count_slider = get_ui_component('benchmark_cycle_count_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_BENCHMARKS_DATAFRAME)
if benchmark_resolutions_checkbox_group and benchmark_cycle_count_slider:
BENCHMARK_START_BUTTON.click(start, inputs = [ benchmark_resolutions_checkbox_group, benchmark_cycle_count_slider ], outputs = BENCHMARK_BENCHMARKS_DATAFRAME)
def suggest_output_path(target_path : str) -> Optional[str]:
if is_video(target_path):
target_file_extension = get_file_extension(target_path)
return os.path.join(tempfile.gettempdir(), hashlib.sha1().hexdigest()[:8] + target_file_extension)
return None
def start(benchmark_runs : List[str], benchmark_cycles : int) -> Generator[List[Any], None, None]:
state_manager.init_item('source_paths', [ '.assets/examples/source.jpg', '.assets/examples/source.mp3' ])
state_manager.init_item('face_landmarker_score', 0)
state_manager.init_item('temp_frame_format', 'bmp')
state_manager.init_item('output_audio_volume', 0)
state_manager.init_item('output_video_preset', 'ultrafast')
def start(benchmark_resolutions : List[BenchmarkResolution], benchmark_cycle_count : int) -> Generator[List[Any], None, None]:
state_manager.set_item('benchmark_resolutions', benchmark_resolutions)
state_manager.set_item('benchmark_cycle_count', benchmark_cycle_count)
state_manager.sync_item('execution_providers')
state_manager.sync_item('execution_thread_count')
state_manager.sync_item('execution_queue_count')
state_manager.sync_item('system_memory_limit')
benchmark_results = []
target_paths = [ BENCHMARKS[benchmark_run] for benchmark_run in benchmark_runs if benchmark_run in BENCHMARKS ]
if target_paths:
pre_process()
for target_path in target_paths:
state_manager.init_item('target_path', target_path)
state_manager.init_item('output_path', suggest_output_path(state_manager.get_item('target_path')))
benchmark_results.append(benchmark(benchmark_cycles))
yield benchmark_results
def pre_process() -> None:
system_memory_limit = state_manager.get_item('system_memory_limit')
if system_memory_limit and system_memory_limit > 0:
limit_system_memory(system_memory_limit)
def benchmark(benchmark_cycles : int) -> List[Any]:
process_times = []
video_frame_total = count_video_frame_total(state_manager.get_item('target_path'))
output_video_resolution = detect_video_resolution(state_manager.get_item('target_path'))
state_manager.init_item('output_video_resolution', pack_resolution(output_video_resolution))
state_manager.init_item('output_video_fps', detect_video_fps(state_manager.get_item('target_path')))
conditional_process()
for index in range(benchmark_cycles):
start_time = perf_counter()
conditional_process()
end_time = perf_counter()
process_times.append(end_time - start_time)
average_run = round(statistics.mean(process_times), 2)
fastest_run = round(min(process_times), 2)
slowest_run = round(max(process_times), 2)
relative_fps = round(video_frame_total * benchmark_cycles / sum(process_times), 2)
return\
[
state_manager.get_item('target_path'),
benchmark_cycles,
average_run,
fastest_run,
slowest_run,
relative_fps
]
for benchmark in benchmarker.run():
yield [ list(benchmark_set.values()) for benchmark_set in benchmark ]

View File

@@ -2,29 +2,29 @@ from typing import Optional
import gradio
import facefusion.choices
from facefusion import wording
from facefusion.uis.components.benchmark import BENCHMARKS
from facefusion.uis.core import register_ui_component
BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
BENCHMARK_CYCLES_SLIDER : Optional[gradio.Button] = None
BENCHMARK_RESOLUTIONS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
BENCHMARK_CYCLE_COUNT_SLIDER : Optional[gradio.Button] = None
def render() -> None:
global BENCHMARK_RUNS_CHECKBOX_GROUP
global BENCHMARK_CYCLES_SLIDER
global BENCHMARK_RESOLUTIONS_CHECKBOX_GROUP
global BENCHMARK_CYCLE_COUNT_SLIDER
BENCHMARK_RUNS_CHECKBOX_GROUP = gradio.CheckboxGroup(
label = wording.get('uis.benchmark_runs_checkbox_group'),
choices = list(BENCHMARKS.keys()),
value = list(BENCHMARKS.keys())
BENCHMARK_RESOLUTIONS_CHECKBOX_GROUP = gradio.CheckboxGroup(
label = wording.get('uis.benchmark_resolutions_checkbox_group'),
choices = facefusion.choices.benchmark_resolutions,
value = facefusion.choices.benchmark_resolutions
)
BENCHMARK_CYCLES_SLIDER = gradio.Slider(
label = wording.get('uis.benchmark_cycles_slider'),
BENCHMARK_CYCLE_COUNT_SLIDER = gradio.Slider(
label = wording.get('uis.benchmark_cycle_count_slider'),
value = 5,
step = 1,
minimum = 1,
maximum = 10
minimum = min(facefusion.choices.benchmark_cycle_count_range),
maximum = max(facefusion.choices.benchmark_cycle_count_range)
)
register_ui_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP)
register_ui_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER)
register_ui_component('benchmark_resolutions_checkbox_group', BENCHMARK_RESOLUTIONS_CHECKBOX_GROUP)
register_ui_component('benchmark_cycle_count_slider', BENCHMARK_CYCLE_COUNT_SLIDER)

View File

@@ -5,12 +5,13 @@ import gradio
import facefusion.choices
from facefusion import face_masker, state_manager, wording
from facefusion.common_helper import calc_float_step, calc_int_step
from facefusion.types import FaceMaskRegion, FaceMaskType, FaceOccluderModel, FaceParserModel
from facefusion.types import FaceMaskArea, FaceMaskRegion, FaceMaskType, FaceOccluderModel, FaceParserModel
from facefusion.uis.core import register_ui_component
FACE_OCCLUDER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
FACE_PARSER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
FACE_MASK_TYPES_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
FACE_MASK_AREAS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
FACE_MASK_REGIONS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
FACE_MASK_BLUR_SLIDER : Optional[gradio.Slider] = None
FACE_MASK_PADDING_TOP_SLIDER : Optional[gradio.Slider] = None
@@ -23,6 +24,7 @@ def render() -> None:
global FACE_OCCLUDER_MODEL_DROPDOWN
global FACE_PARSER_MODEL_DROPDOWN
global FACE_MASK_TYPES_CHECKBOX_GROUP
global FACE_MASK_AREAS_CHECKBOX_GROUP
global FACE_MASK_REGIONS_CHECKBOX_GROUP
global FACE_MASK_BLUR_SLIDER
global FACE_MASK_PADDING_TOP_SLIDER
@@ -32,6 +34,7 @@ def render() -> None:
has_box_mask = 'box' in state_manager.get_item('face_mask_types')
has_region_mask = 'region' in state_manager.get_item('face_mask_types')
has_area_mask = 'area' in state_manager.get_item('face_mask_types')
with gradio.Row():
FACE_OCCLUDER_MODEL_DROPDOWN = gradio.Dropdown(
label = wording.get('uis.face_occluder_model_dropdown'),
@@ -48,6 +51,12 @@ def render() -> None:
choices = facefusion.choices.face_mask_types,
value = state_manager.get_item('face_mask_types')
)
FACE_MASK_AREAS_CHECKBOX_GROUP = gradio.CheckboxGroup(
label = wording.get('uis.face_mask_areas_checkbox_group'),
choices = facefusion.choices.face_mask_areas,
value = state_manager.get_item('face_mask_areas'),
visible = has_area_mask
)
FACE_MASK_REGIONS_CHECKBOX_GROUP = gradio.CheckboxGroup(
label = wording.get('uis.face_mask_regions_checkbox_group'),
choices = facefusion.choices.face_mask_regions,
@@ -100,6 +109,7 @@ def render() -> None:
register_ui_component('face_occluder_model_dropdown', FACE_OCCLUDER_MODEL_DROPDOWN)
register_ui_component('face_parser_model_dropdown', FACE_PARSER_MODEL_DROPDOWN)
register_ui_component('face_mask_types_checkbox_group', FACE_MASK_TYPES_CHECKBOX_GROUP)
register_ui_component('face_mask_areas_checkbox_group', FACE_MASK_AREAS_CHECKBOX_GROUP)
register_ui_component('face_mask_regions_checkbox_group', FACE_MASK_REGIONS_CHECKBOX_GROUP)
register_ui_component('face_mask_blur_slider', FACE_MASK_BLUR_SLIDER)
register_ui_component('face_mask_padding_top_slider', FACE_MASK_PADDING_TOP_SLIDER)
@@ -111,9 +121,11 @@ def render() -> None:
def listen() -> None:
FACE_OCCLUDER_MODEL_DROPDOWN.change(update_face_occluder_model, inputs = FACE_OCCLUDER_MODEL_DROPDOWN)
FACE_PARSER_MODEL_DROPDOWN.change(update_face_parser_model, inputs = FACE_PARSER_MODEL_DROPDOWN)
FACE_MASK_TYPES_CHECKBOX_GROUP.change(update_face_mask_types, inputs = FACE_MASK_TYPES_CHECKBOX_GROUP, outputs = [ FACE_MASK_TYPES_CHECKBOX_GROUP, FACE_MASK_REGIONS_CHECKBOX_GROUP, FACE_MASK_BLUR_SLIDER, FACE_MASK_PADDING_TOP_SLIDER, FACE_MASK_PADDING_RIGHT_SLIDER, FACE_MASK_PADDING_BOTTOM_SLIDER, FACE_MASK_PADDING_LEFT_SLIDER ])
FACE_MASK_TYPES_CHECKBOX_GROUP.change(update_face_mask_types, inputs = FACE_MASK_TYPES_CHECKBOX_GROUP, outputs = [ FACE_MASK_TYPES_CHECKBOX_GROUP, FACE_MASK_AREAS_CHECKBOX_GROUP, FACE_MASK_REGIONS_CHECKBOX_GROUP, FACE_MASK_BLUR_SLIDER, FACE_MASK_PADDING_TOP_SLIDER, FACE_MASK_PADDING_RIGHT_SLIDER, FACE_MASK_PADDING_BOTTOM_SLIDER, FACE_MASK_PADDING_LEFT_SLIDER ])
FACE_MASK_AREAS_CHECKBOX_GROUP.change(update_face_mask_areas, inputs = FACE_MASK_AREAS_CHECKBOX_GROUP, outputs = FACE_MASK_AREAS_CHECKBOX_GROUP)
FACE_MASK_REGIONS_CHECKBOX_GROUP.change(update_face_mask_regions, inputs = FACE_MASK_REGIONS_CHECKBOX_GROUP, outputs = FACE_MASK_REGIONS_CHECKBOX_GROUP)
FACE_MASK_BLUR_SLIDER.release(update_face_mask_blur, inputs = FACE_MASK_BLUR_SLIDER)
face_mask_padding_sliders = [ FACE_MASK_PADDING_TOP_SLIDER, FACE_MASK_PADDING_RIGHT_SLIDER, FACE_MASK_PADDING_BOTTOM_SLIDER, FACE_MASK_PADDING_LEFT_SLIDER ]
for face_mask_padding_slider in face_mask_padding_sliders:
face_mask_padding_slider.release(update_face_mask_padding, inputs = face_mask_padding_sliders)
@@ -137,12 +149,19 @@ def update_face_parser_model(face_parser_model : FaceParserModel) -> gradio.Drop
return gradio.Dropdown()
def update_face_mask_types(face_mask_types : List[FaceMaskType]) -> Tuple[gradio.CheckboxGroup, gradio.CheckboxGroup, gradio.Slider, gradio.Slider, gradio.Slider, gradio.Slider, gradio.Slider]:
def update_face_mask_types(face_mask_types : List[FaceMaskType]) -> Tuple[gradio.CheckboxGroup, gradio.CheckboxGroup, gradio.CheckboxGroup, gradio.Slider, gradio.Slider, gradio.Slider, gradio.Slider, gradio.Slider]:
face_mask_types = face_mask_types or facefusion.choices.face_mask_types
state_manager.set_item('face_mask_types', face_mask_types)
has_box_mask = 'box' in face_mask_types
has_area_mask = 'area' in face_mask_types
has_region_mask = 'region' in face_mask_types
return gradio.CheckboxGroup(value = state_manager.get_item('face_mask_types')), gradio.CheckboxGroup(visible = has_region_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask)
return gradio.CheckboxGroup(value = state_manager.get_item('face_mask_types')), gradio.CheckboxGroup(visible = has_area_mask), gradio.CheckboxGroup(visible = has_region_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask), gradio.Slider(visible = has_box_mask)
def update_face_mask_areas(face_mask_areas : List[FaceMaskArea]) -> gradio.CheckboxGroup:
face_mask_areas = face_mask_areas or facefusion.choices.face_mask_areas
state_manager.set_item('face_mask_areas', face_mask_areas)
return gradio.CheckboxGroup(value = state_manager.get_item('face_mask_areas'))
def update_face_mask_regions(face_mask_regions : List[FaceMaskRegion]) -> gradio.CheckboxGroup:

View File

@@ -1,18 +1,21 @@
from typing import List, Optional
from typing import List, Optional, Tuple
import gradio
from facefusion import state_manager, wording
from facefusion.common_helper import calc_float_step
from facefusion.processors import choices as processors_choices
from facefusion.processors.core import load_processor_module
from facefusion.processors.types import LipSyncerModel
from facefusion.uis.core import get_ui_component, register_ui_component
LIP_SYNCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None
LIP_SYNCER_WEIGHT_SLIDER : Optional[gradio.Slider] = None
def render() -> None:
global LIP_SYNCER_MODEL_DROPDOWN
global LIP_SYNCER_WEIGHT_SLIDER
has_lip_syncer = 'lip_syncer' in state_manager.get_item('processors')
LIP_SYNCER_MODEL_DROPDOWN = gradio.Dropdown(
@@ -21,20 +24,30 @@ def render() -> None:
value = state_manager.get_item('lip_syncer_model'),
visible = has_lip_syncer
)
LIP_SYNCER_WEIGHT_SLIDER = gradio.Slider(
label = wording.get('uis.lip_syncer_weight_slider'),
value = state_manager.get_item('lip_syncer_weight'),
step = calc_float_step(processors_choices.lip_syncer_weight_range),
minimum = processors_choices.lip_syncer_weight_range[0],
maximum = processors_choices.lip_syncer_weight_range[-1],
visible = has_lip_syncer
)
register_ui_component('lip_syncer_model_dropdown', LIP_SYNCER_MODEL_DROPDOWN)
register_ui_component('lip_syncer_weight_slider', LIP_SYNCER_WEIGHT_SLIDER)
def listen() -> None:
LIP_SYNCER_MODEL_DROPDOWN.change(update_lip_syncer_model, inputs = LIP_SYNCER_MODEL_DROPDOWN, outputs = LIP_SYNCER_MODEL_DROPDOWN)
LIP_SYNCER_WEIGHT_SLIDER.release(update_lip_syncer_weight, inputs = LIP_SYNCER_WEIGHT_SLIDER)
processors_checkbox_group = get_ui_component('processors_checkbox_group')
if processors_checkbox_group:
processors_checkbox_group.change(remote_update, inputs = processors_checkbox_group, outputs = LIP_SYNCER_MODEL_DROPDOWN)
processors_checkbox_group.change(remote_update, inputs = processors_checkbox_group, outputs = [ LIP_SYNCER_MODEL_DROPDOWN, LIP_SYNCER_WEIGHT_SLIDER ])
def remote_update(processors : List[str]) -> gradio.Dropdown:
def remote_update(processors : List[str]) -> Tuple[gradio.Dropdown, gradio.Slider]:
has_lip_syncer = 'lip_syncer' in processors
return gradio.Dropdown(visible = has_lip_syncer)
return gradio.Dropdown(visible = has_lip_syncer), gradio.Slider(visible = has_lip_syncer)
def update_lip_syncer_model(lip_syncer_model : LipSyncerModel) -> gradio.Dropdown:
@@ -45,3 +58,7 @@ def update_lip_syncer_model(lip_syncer_model : LipSyncerModel) -> gradio.Dropdow
if lip_syncer_module.pre_check():
return gradio.Dropdown(value = state_manager.get_item('lip_syncer_model'))
return gradio.Dropdown()
def update_lip_syncer_weight(lip_syncer_weight : float) -> None:
state_manager.set_item('lip_syncer_weight', lip_syncer_weight)

View File

@@ -104,6 +104,7 @@ def listen() -> None:
'face_debugger_items_checkbox_group',
'frame_colorizer_size_dropdown',
'face_mask_types_checkbox_group',
'face_mask_areas_checkbox_group',
'face_mask_regions_checkbox_group'
]):
ui_component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE)
@@ -131,6 +132,7 @@ def listen() -> None:
'face_enhancer_weight_slider',
'frame_colorizer_blend_slider',
'frame_enhancer_blend_slider',
'lip_syncer_weight_slider',
'reference_face_distance_slider',
'face_selector_age_range_slider',
'face_mask_blur_slider',

View File

@@ -40,4 +40,10 @@ def update_processors(processors : List[str]) -> gradio.CheckboxGroup:
def sort_processors(processors : List[str]) -> List[str]:
available_processors = [ get_file_name(file_path) for file_path in resolve_file_paths('facefusion/processors/modules') ]
return sorted(available_processors, key = lambda processor : processors.index(processor) if processor in processors else len(processors))
current_processors = []
for processor in processors + available_processors:
if processor in available_processors and processor not in current_processors:
current_processors.append(processor)
return current_processors

View File

@@ -107,8 +107,9 @@ def start(webcam_device_id : int, webcam_mode : WebcamMode, webcam_resolution :
webcam_capture.set(cv2.CAP_PROP_FPS, webcam_fps)
for capture_frame in multi_process_capture(source_face, webcam_capture, webcam_fps):
capture_frame = normalize_frame_color(capture_frame)
if webcam_mode == 'inline':
yield normalize_frame_color(capture_frame)
yield capture_frame
else:
try:
stream.stdin.write(capture_frame.tobytes())
@@ -166,19 +167,20 @@ def open_stream(stream_mode : StreamMode, stream_resolution : str, stream_fps :
commands = ffmpeg_builder.chain(
ffmpeg_builder.capture_video(),
ffmpeg_builder.set_media_resolution(stream_resolution),
ffmpeg_builder.set_conditional_fps(stream_fps)
ffmpeg_builder.set_input_fps(stream_fps)
)
if stream_mode == 'udp':
commands.extend(ffmpeg_builder.set_input('-'))
commands.extend(ffmpeg_builder.set_stream_mode('udp'))
commands.extend(ffmpeg_builder.set_stream_quality(2000))
commands.extend(ffmpeg_builder.set_output('udp://localhost:27000?pkt_size=1316'))
if stream_mode == 'v4l2':
device_directory_path = '/sys/devices/virtual/video4linux'
commands.extend(ffmpeg_builder.set_input('-'))
commands.extend(ffmpeg_builder.set_stream_mode('v4l2'))
if is_directory(device_directory_path):
device_names = os.listdir(device_directory_path)

View File

@@ -1,24 +1,12 @@
import gradio
from facefusion import state_manager
from facefusion.download import conditional_download, resolve_download_url
from facefusion.benchmarker import pre_check as benchmarker_pre_check
from facefusion.uis.components import about, age_modifier_options, benchmark, benchmark_options, deep_swapper_options, download, execution, execution_queue_count, execution_thread_count, expression_restorer_options, face_debugger_options, face_editor_options, face_enhancer_options, face_swapper_options, frame_colorizer_options, frame_enhancer_options, lip_syncer_options, memory, processors
def pre_check() -> bool:
conditional_download('.assets/examples',
[
resolve_download_url('examples-3.0.0', 'source.jpg'),
resolve_download_url('examples-3.0.0', 'source.mp3'),
resolve_download_url('examples-3.0.0', 'target-240p.mp4'),
resolve_download_url('examples-3.0.0', 'target-360p.mp4'),
resolve_download_url('examples-3.0.0', 'target-540p.mp4'),
resolve_download_url('examples-3.0.0', 'target-720p.mp4'),
resolve_download_url('examples-3.0.0', 'target-1080p.mp4'),
resolve_download_url('examples-3.0.0', 'target-1440p.mp4'),
resolve_download_url('examples-3.0.0', 'target-2160p.mp4')
])
return True
return benchmarker_pre_check()
def render() -> gradio.Blocks:

View File

@@ -1,5 +1,3 @@
from gradio.processing_utils import video_is_playable
from facefusion import ffmpeg_builder
from facefusion.ffmpeg import run_ffmpeg
from facefusion.filesystem import get_file_size
@@ -10,28 +8,14 @@ def convert_video_to_playable_mp4(video_path : str) -> str:
video_file_size = get_file_size(video_path)
max_file_size = 512 * 1024 * 1024
create_temp_directory(video_path)
temp_video_path = get_temp_file_path(video_path)
commands = ffmpeg_builder.set_input(video_path)
if video_file_size > max_file_size:
create_temp_directory(video_path)
temp_video_path = get_temp_file_path(video_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(video_path),
ffmpeg_builder.set_video_duration(10),
ffmpeg_builder.force_output(temp_video_path)
)
commands.extend(ffmpeg_builder.set_video_duration(10))
process = run_ffmpeg(commands)
process.communicate()
if process.returncode == 0:
return temp_video_path
if not video_is_playable(video_path):
create_temp_directory(video_path)
temp_video_path = get_temp_file_path(video_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(video_path),
ffmpeg_builder.force_output(temp_video_path)
)
commands.extend(ffmpeg_builder.force_output(temp_video_path))
process = run_ffmpeg(commands)
process.communicate()

View File

@@ -1,14 +1,12 @@
from typing import Any, Dict, IO, Literal, TypeAlias
File : TypeAlias = IO[Any]
Component : TypeAlias = Any
ComponentOptions : TypeAlias = Dict[str, Any]
ComponentName = Literal\
[
'age_modifier_direction_slider',
'age_modifier_model_dropdown',
'benchmark_cycles_slider',
'benchmark_runs_checkbox_group',
'benchmark_cycle_count_slider',
'benchmark_resolutions_checkbox_group',
'deep_swapper_model_dropdown',
'deep_swapper_morph_slider',
'expression_restorer_factor_slider',
@@ -38,13 +36,14 @@ ComponentName = Literal\
'face_enhancer_weight_slider',
'face_landmarker_model_dropdown',
'face_landmarker_score_slider',
'face_mask_types_checkbox_group',
'face_mask_areas_checkbox_group',
'face_mask_regions_checkbox_group',
'face_mask_blur_slider',
'face_mask_padding_bottom_slider',
'face_mask_padding_left_slider',
'face_mask_padding_right_slider',
'face_mask_padding_top_slider',
'face_mask_regions_checkbox_group',
'face_mask_types_checkbox_group',
'face_selector_age_range_slider',
'face_selector_gender_dropdown',
'face_selector_mode_dropdown',
@@ -61,6 +60,7 @@ ComponentName = Literal\
'frame_enhancer_model_dropdown',
'job_list_job_status_checkbox_group',
'lip_syncer_model_dropdown',
'lip_syncer_weight_slider',
'output_image',
'output_video',
'output_video_fps_slider',
@@ -78,6 +78,8 @@ ComponentName = Literal\
'webcam_mode_radio',
'webcam_resolution_dropdown'
]
Component : TypeAlias = Any
ComponentOptions : TypeAlias = Dict[str, Any]
JobManagerAction = Literal['job-create', 'job-submit', 'job-delete', 'job-add-step', 'job-remix-step', 'job-insert-step', 'job-remove-step']
JobRunnerAction = Literal['job-run', 'job-run-all', 'job-retry', 'job-retry-all']

View File

@@ -0,0 +1,19 @@
import cv2
from facefusion.types import VideoPoolSet
VIDEO_POOL_SET : VideoPoolSet = {}
def get_video_capture(video_path : str) -> cv2.VideoCapture:
if video_path not in VIDEO_POOL_SET:
VIDEO_POOL_SET[video_path] = cv2.VideoCapture(video_path)
return VIDEO_POOL_SET.get(video_path)
def clear_video_pool() -> None:
for video_capture in VIDEO_POOL_SET.values():
video_capture.release()
VIDEO_POOL_SET.clear()

View File

@@ -9,7 +9,9 @@ from cv2.typing import Size
import facefusion.choices
from facefusion.common_helper import is_windows
from facefusion.filesystem import get_file_extension, is_image, is_video
from facefusion.thread_helper import thread_semaphore
from facefusion.types import Duration, Fps, Orientation, Resolution, VisionFrame
from facefusion.video_manager import get_video_capture
@lru_cache()
@@ -81,42 +83,50 @@ def create_image_resolutions(resolution : Resolution) -> List[str]:
def read_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
if is_video(video_path):
video_capture = cv2.VideoCapture(video_path)
video_capture = get_video_capture(video_path)
if video_capture.isOpened():
frame_total = video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
with thread_semaphore():
video_capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
has_vision_frame, vision_frame = video_capture.read()
video_capture.release()
if has_vision_frame:
return vision_frame
return None
def count_video_frame_total(video_path : str) -> int:
if is_video(video_path):
video_capture = cv2.VideoCapture(video_path)
video_capture = get_video_capture(video_path)
if video_capture.isOpened():
with thread_semaphore():
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
video_capture.release()
return video_frame_total
return 0
def predict_video_frame_total(video_path : str, fps : Fps, trim_frame_start : int, trim_frame_end : int) -> int:
if is_video(video_path):
target_video_fps = detect_video_fps(video_path)
extract_frame_total = count_trim_frame_total(video_path, trim_frame_start, trim_frame_end) * fps / target_video_fps
video_fps = detect_video_fps(video_path)
extract_frame_total = count_trim_frame_total(video_path, trim_frame_start, trim_frame_end) * fps / video_fps
return math.floor(extract_frame_total)
return 0
def detect_video_fps(video_path : str) -> Optional[float]:
if is_video(video_path):
video_capture = cv2.VideoCapture(video_path)
video_capture = get_video_capture(video_path)
if video_capture.isOpened():
with thread_semaphore():
video_fps = video_capture.get(cv2.CAP_PROP_FPS)
video_capture.release()
return video_fps
return None
@@ -163,12 +173,14 @@ def restrict_trim_frame(video_path : str, trim_frame_start : Optional[int], trim
def detect_video_resolution(video_path : str) -> Optional[Resolution]:
if is_video(video_path):
video_capture = cv2.VideoCapture(video_path)
video_capture = get_video_capture(video_path)
if video_capture.isOpened():
with thread_semaphore():
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
video_capture.release()
return int(width), int(height)
return None

View File

@@ -4,7 +4,7 @@ WORDING : Dict[str, Any] =\
{
'conda_not_activated': 'Conda is not activated',
'python_not_supported': 'Python version is not supported, upgrade to {version} or higher',
'curl_not_installed': 'CURL is not installed',
'curl_not_installed': 'cURL is not installed',
'ffmpeg_not_installed': 'FFMpeg is not installed',
'creating_temp': 'Creating temporary resources',
'extracting_frames': 'Extracting frames with a resolution of {resolution} and {fps} frames per second',
@@ -129,9 +129,10 @@ WORDING : Dict[str, Any] =\
'face_occluder_model': 'choose the model responsible for the occlusion mask',
'face_parser_model': 'choose the model responsible for the region mask',
'face_mask_types': 'mix and match different face mask types (choices: {choices})',
'face_mask_areas': 'choose the items used for the area mask (choices: {choices})',
'face_mask_regions': 'choose the items used for the region mask (choices: {choices})',
'face_mask_blur': 'specify the degree of blur applied to the box mask',
'face_mask_padding': 'apply top, right, bottom and left padding to the box mask',
'face_mask_regions': 'choose the facial features used for the region mask (choices: {choices})',
# frame extraction
'trim_frame_start': 'specify the starting frame of the target video',
'trim_frame_end': 'specify the ending frame of the target video',
@@ -183,18 +184,22 @@ WORDING : Dict[str, Any] =\
'frame_enhancer_model': 'choose the model responsible for enhancing the frame',
'frame_enhancer_blend': 'blend the enhanced into the previous frame',
'lip_syncer_model': 'choose the model responsible for syncing the lips',
'lip_syncer_weight': 'specify the degree of weight applied to the lips',
# uis
'open_browser': 'open the browser once the program is ready',
'ui_layouts': 'launch a single or multiple UI layouts (choices: {choices}, ...)',
'ui_workflow': 'choose the ui workflow',
# download
'download_providers': 'download using different providers (choices: {choices}, ...)',
'download_scope': 'specify the download scope',
# benchmark
'benchmark_resolutions': 'choose the resolutions for the benchmarks (choices: {choices}, ...)',
'benchmark_cycle_count': 'specify the amount of cycles per benchmark',
# execution
'execution_device_id': 'specify the device used for processing',
'execution_providers': 'inference using different providers (choices: {choices}, ...)',
'execution_thread_count': 'specify the amount of parallel threads while processing',
'execution_queue_count': 'specify the amount of frames each thread is processing',
# download
'download_providers': 'download using different providers (choices: {choices}, ...)',
'download_scope': 'specify the download scope',
# memory
'video_memory_strategy': 'balance fast processing and low VRAM usage',
'system_memory_limit': 'limit the available RAM that can be used while processing',
@@ -206,6 +211,7 @@ WORDING : Dict[str, Any] =\
'headless_run': 'run the program in headless mode',
'batch_run': 'run the program in batch mode',
'force_download': 'force automate downloads and exit',
'benchmark': 'benchmark the program',
# jobs
'job_id': 'specify the job id',
'job_status': 'specify the job status',
@@ -238,8 +244,8 @@ WORDING : Dict[str, Any] =\
'age_modifier_direction_slider': 'AGE MODIFIER DIRECTION',
'age_modifier_model_dropdown': 'AGE MODIFIER MODEL',
'apply_button': 'APPLY',
'benchmark_cycles_slider': 'BENCHMARK CYCLES',
'benchmark_runs_checkbox_group': 'BENCHMARK RUNS',
'benchmark_cycle_count_slider': 'BENCHMARK CYCLE COUNT',
'benchmark_resolutions_checkbox_group': 'BENCHMARK RESOLUTIONS',
'clear_button': 'CLEAR',
'common_options_checkbox_group': 'OPTIONS',
'download_providers_checkbox_group': 'DOWNLOAD PROVIDERS',
@@ -280,6 +286,7 @@ WORDING : Dict[str, Any] =\
'face_mask_padding_left_slider': 'FACE MASK PADDING LEFT',
'face_mask_padding_right_slider': 'FACE MASK PADDING RIGHT',
'face_mask_padding_top_slider': 'FACE MASK PADDING TOP',
'face_mask_areas_checkbox_group': 'FACE MASK AREAS',
'face_mask_regions_checkbox_group': 'FACE MASK REGIONS',
'face_mask_types_checkbox_group': 'FACE MASK TYPES',
'face_selector_age_range_slider': 'FACE SELECTOR AGE',
@@ -303,6 +310,7 @@ WORDING : Dict[str, Any] =\
'job_runner_job_action_dropdown': 'JOB ACTION',
'job_runner_job_id_dropdown': 'JOB ID',
'lip_syncer_model_dropdown': 'LIP SYNCER MODEL',
'lip_syncer_weight_slider': 'LIP SYNCER WEIGHT',
'log_level_dropdown': 'LOG LEVEL',
'output_audio_encoder_dropdown': 'OUTPUT AUDIO ENCODER',
'output_audio_quality_slider': 'OUTPUT AUDIO QUALITY',

View File

@@ -2,7 +2,7 @@ gradio==5.25.2
gradio-rangeslider==0.0.8
numpy==2.2.4
onnx==1.17.0
onnxruntime==1.21.1
onnxruntime==1.22.0
opencv-python==4.11.0.86
psutil==7.0.0
tqdm==4.67.1

View File

@@ -1,13 +1,16 @@
import os
import subprocess
import tempfile
import pytest
import facefusion.ffmpeg
from facefusion import process_manager, state_manager
from facefusion.download import conditional_download
from facefusion.ffmpeg import concat_video, extract_frames, get_available_encoder_set, read_audio_buffer, replace_audio, restore_audio
from facefusion.ffmpeg import concat_video, extract_frames, merge_video, read_audio_buffer, replace_audio, restore_audio
from facefusion.filesystem import copy_file
from facefusion.temp_helper import clear_temp_directory, create_temp_directory, get_temp_file_path, resolve_temp_frame_paths
from facefusion.types import EncoderSet
from .helper import get_test_example_file, get_test_examples_directory, get_test_output_file, prepare_test_output_directory
@@ -24,13 +27,19 @@ def before_all() -> None:
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('target-240p.mp4'), '-vf', 'fps=25', get_test_example_file('target-240p-25fps.mp4') ])
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('target-240p.mp4'), '-vf', 'fps=30', get_test_example_file('target-240p-30fps.mp4') ])
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('target-240p.mp4'), '-vf', 'fps=60', get_test_example_file('target-240p-60fps.mp4') ])
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('source.mp3'), '-i', get_test_example_file('target-240p.mp4'), '-ar', '16000', get_test_example_file('target-240p-16khz.mp4') ])
for output_video_format in [ 'avi', 'm4v', 'mkv', 'mov', 'mp4', 'webm' ]:
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('source.mp3'), '-i', get_test_example_file('target-240p.mp4'), '-ar', '16000', get_test_example_file('target-240p-16khz.' + output_video_format) ])
subprocess.run([ 'ffmpeg', '-i', get_test_example_file('source.mp3'), '-i', get_test_example_file('target-240p.mp4'), '-ar', '48000', get_test_example_file('target-240p-48khz.mp4') ])
state_manager.init_item('temp_path', tempfile.gettempdir())
state_manager.init_item('temp_frame_format', 'png')
state_manager.init_item('output_audio_encoder', 'aac')
state_manager.init_item('output_audio_quality', 80)
state_manager.init_item('output_audio_quality', 100)
state_manager.init_item('output_audio_volume', 100)
state_manager.init_item('output_video_encoder', 'libx264')
state_manager.init_item('output_video_quality', 100)
state_manager.init_item('output_video_preset', 'ultrafast')
@pytest.fixture(scope = 'function', autouse = True)
@@ -38,7 +47,16 @@ def before_each() -> None:
prepare_test_output_directory()
@pytest.mark.skip()
def get_available_encoder_set() -> EncoderSet:
if os.getenv('CI'):
return\
{
'audio': [ 'aac' ],
'video': [ 'libx264' ]
}
return facefusion.ffmpeg.get_available_encoder_set()
def test_get_available_encoder_set() -> None:
available_encoder_set = get_available_encoder_set()
@@ -47,7 +65,7 @@ def test_get_available_encoder_set() -> None:
def test_extract_frames() -> None:
extract_set =\
test_set =\
[
(get_test_example_file('target-240p-25fps.mp4'), 0, 270, 324),
(get_test_example_file('target-240p-25fps.mp4'), 224, 270, 55),
@@ -63,7 +81,7 @@ def test_extract_frames() -> None:
(get_test_example_file('target-240p-60fps.mp4'), 0, 100, 50)
]
for target_path, trim_frame_start, trim_frame_end, frame_total in extract_set:
for target_path, trim_frame_start, trim_frame_end, frame_total in test_set:
create_temp_directory(target_path)
assert extract_frames(target_path, '452x240', 30.0, trim_frame_start, trim_frame_end) is True
@@ -72,12 +90,37 @@ def test_extract_frames() -> None:
clear_temp_directory(target_path)
def test_merge_video() -> None:
target_paths =\
[
get_test_example_file('target-240p-16khz.avi'),
get_test_example_file('target-240p-16khz.m4v'),
get_test_example_file('target-240p-16khz.mkv'),
get_test_example_file('target-240p-16khz.mp4'),
get_test_example_file('target-240p-16khz.mov'),
get_test_example_file('target-240p-16khz.webm')
]
output_video_encoders = get_available_encoder_set().get('video')
for target_path in target_paths:
for output_video_encoder in output_video_encoders:
state_manager.init_item('output_video_encoder', output_video_encoder)
create_temp_directory(target_path)
extract_frames(target_path, '452x240', 25.0, 0, 1)
assert merge_video(target_path, 25.0, '452x240', 25.0, 0, 1) is True
clear_temp_directory(target_path)
state_manager.init_item('output_video_encoder', 'libx264')
def test_concat_video() -> None:
output_path = get_test_output_file('test-concat-video.mp4')
temp_output_paths =\
[
get_test_example_file('target-240p.mp4'),
get_test_example_file('target-240p.mp4')
get_test_example_file('target-240p-16khz.mp4'),
get_test_example_file('target-240p-16khz.mp4')
]
assert concat_video(output_path, temp_output_paths) is True
@@ -90,30 +133,55 @@ def test_read_audio_buffer() -> None:
def test_restore_audio() -> None:
target_paths =\
test_set =\
[
get_test_example_file('target-240p-16khz.mp4'),
get_test_example_file('target-240p-48khz.mp4')
(get_test_example_file('target-240p-16khz.avi'), get_test_output_file('target-240p-16khz.avi')),
(get_test_example_file('target-240p-16khz.m4v'), get_test_output_file('target-240p-16khz.m4v')),
(get_test_example_file('target-240p-16khz.mkv'), get_test_output_file('target-240p-16khz.mkv')),
(get_test_example_file('target-240p-16khz.mov'), get_test_output_file('target-240p-16khz.mov')),
(get_test_example_file('target-240p-16khz.mp4'), get_test_output_file('target-240p-16khz.mp4')),
(get_test_example_file('target-240p-48khz.mp4'), get_test_output_file('target-240p-48khz.mp4')),
(get_test_example_file('target-240p-16khz.webm'), get_test_output_file('target-240p-16khz.webm'))
]
output_path = get_test_output_file('test-restore-audio.mp4')
output_audio_encoders = get_available_encoder_set().get('audio')
for target_path in target_paths:
for target_path, output_path in test_set:
create_temp_directory(target_path)
for output_audio_encoder in output_audio_encoders:
state_manager.init_item('output_audio_encoder', output_audio_encoder)
copy_file(target_path, get_temp_file_path(target_path))
assert restore_audio(target_path, output_path, 0, 270) is True
clear_temp_directory(target_path)
state_manager.init_item('output_audio_encoder', 'aac')
def test_replace_audio() -> None:
target_path = get_test_example_file('target-240p.mp4')
output_path = get_test_output_file('test-replace-audio.mp4')
test_set =\
[
(get_test_example_file('target-240p-16khz.avi'), get_test_output_file('target-240p-16khz.avi')),
(get_test_example_file('target-240p-16khz.m4v'), get_test_output_file('target-240p-16khz.m4v')),
(get_test_example_file('target-240p-16khz.mkv'), get_test_output_file('target-240p-16khz.mkv')),
(get_test_example_file('target-240p-16khz.mov'), get_test_output_file('target-240p-16khz.mov')),
(get_test_example_file('target-240p-16khz.mp4'), get_test_output_file('target-240p-16khz.mp4')),
(get_test_example_file('target-240p-48khz.mp4'), get_test_output_file('target-240p-48khz.mp4')),
(get_test_example_file('target-240p-16khz.webm'), get_test_output_file('target-240p-16khz.webm'))
]
output_audio_encoders = get_available_encoder_set().get('audio')
for target_path, output_path in test_set:
create_temp_directory(target_path)
for output_audio_encoder in output_audio_encoders:
state_manager.init_item('output_audio_encoder', output_audio_encoder)
copy_file(target_path, get_temp_file_path(target_path))
assert replace_audio(target_path, get_test_example_file('source.mp3'), output_path) is True
assert replace_audio(target_path, get_test_example_file('source.wav'), output_path) is True
clear_temp_directory(target_path)
state_manager.init_item('output_audio_encoder', 'aac')

View File

@@ -37,8 +37,8 @@ def test_set_audio_quality() -> None:
assert set_audio_quality('libmp3lame', 50) == [ '-q:a', '4' ]
assert set_audio_quality('libmp3lame', 100) == [ '-q:a', '0' ]
assert set_audio_quality('libopus', 0) == [ '-b:a', '64k' ]
assert set_audio_quality('libopus', 50) == [ '-b:a', '192k' ]
assert set_audio_quality('libopus', 100) == [ '-b:a', '320k' ]
assert set_audio_quality('libopus', 50) == [ '-b:a', '160k' ]
assert set_audio_quality('libopus', 100) == [ '-b:a', '256k' ]
assert set_audio_quality('libvorbis', 0) == [ '-q:a', '-1.0' ]
assert set_audio_quality('libvorbis', 50) == [ '-q:a', '4.5' ]
assert set_audio_quality('libvorbis', 100) == [ '-q:a', '10.0' ]

View File

@@ -16,17 +16,17 @@ def before_all() -> None:
def test_get_inference_pool() -> None:
model_names = [ 'yolo_nsfw' ]
model_source_set = content_analyser.get_model_options().get('sources')
model_names = [ 'nsfw_1', 'nsfw_2', 'nsfw_3' ]
_, model_source_set = content_analyser.collect_model_downloads()
with patch('facefusion.inference_manager.detect_app_context', return_value = 'cli'):
get_inference_pool('facefusion.content_analyser', model_names, model_source_set)
assert isinstance(INFERENCE_POOL_SET.get('cli').get('facefusion.content_analyser.yolo_nsfw.0.cpu').get('content_analyser'), InferenceSession)
assert isinstance(INFERENCE_POOL_SET.get('cli').get('facefusion.content_analyser.nsfw_1.nsfw_2.nsfw_3.0.cpu').get('nsfw_1'), InferenceSession)
with patch('facefusion.inference_manager.detect_app_context', return_value = 'ui'):
get_inference_pool('facefusion.content_analyser', model_names, model_source_set)
assert isinstance(INFERENCE_POOL_SET.get('ui').get('facefusion.content_analyser.yolo_nsfw.0.cpu').get('content_analyser'), InferenceSession)
assert isinstance(INFERENCE_POOL_SET.get('cli').get('facefusion.content_analyser.nsfw_1.nsfw_2.nsfw_3.0.cpu').get('nsfw_1'), InferenceSession)
assert INFERENCE_POOL_SET.get('cli').get('facefusion.content_analyser.yolo_nsfw.0.cpu').get('content_analyser') == INFERENCE_POOL_SET.get('ui').get('facefusion.content_analyser.yolo_nsfw.0.cpu').get('content_analyser')
assert INFERENCE_POOL_SET.get('cli').get('facefusion.content_analyser.nsfw_1.nsfw_2.nsfw_3.0.cpu').get('nsfw_1') == INFERENCE_POOL_SET.get('ui').get('facefusion.content_analyser.nsfw_1.nsfw_2.nsfw_3.0.cpu').get('nsfw_1')

View File

@@ -3,7 +3,7 @@ from time import sleep
import pytest
from facefusion.jobs.job_helper import get_step_output_path
from facefusion.jobs.job_manager import add_step, clear_jobs, count_step_total, create_job, delete_job, delete_jobs, find_job_ids, get_steps, init_jobs, insert_step, move_job_file, remix_step, remove_step, set_step_status, set_steps_status, submit_job, submit_jobs
from facefusion.jobs.job_manager import add_step, clear_jobs, count_step_total, create_job, delete_job, delete_jobs, find_job_ids, find_jobs, get_steps, init_jobs, insert_step, move_job_file, remix_step, remove_step, set_step_status, set_steps_status, submit_job, submit_jobs
from .helper import get_test_jobs_directory
@@ -99,9 +99,19 @@ def test_delete_jobs() -> None:
assert delete_jobs(halt_on_error) is True
@pytest.mark.skip()
def test_find_jobs() -> None:
pass
create_job('job-test-find-jobs-1')
sleep(0.5)
create_job('job-test-find-jobs-2')
assert 'job-test-find-jobs-1' in find_jobs('drafted')
assert 'job-test-find-jobs-2' in find_jobs('drafted')
assert not find_jobs('queued')
move_job_file('job-test-find-jobs-1', 'queued')
assert 'job-test-find-jobs-2' in find_jobs('drafted')
assert 'job-test-find-jobs-1' in find_jobs('queued')
def test_find_job_ids() -> None:

View File

@@ -4,8 +4,8 @@ import pytest
from facefusion.download import conditional_download
from facefusion.filesystem import copy_file
from facefusion.jobs.job_manager import add_step, clear_jobs, create_job, init_jobs, submit_job, submit_jobs
from facefusion.jobs.job_runner import collect_output_set, finalize_steps, run_job, run_jobs, run_steps
from facefusion.jobs.job_manager import add_step, clear_jobs, create_job, init_jobs, move_job_file, submit_job, submit_jobs
from facefusion.jobs.job_runner import collect_output_set, finalize_steps, retry_job, retry_jobs, run_job, run_jobs, run_steps
from facefusion.types import Args
from .helper import get_test_example_file, get_test_examples_directory, get_test_jobs_directory, get_test_output_file, is_test_output_file, prepare_test_output_directory
@@ -48,7 +48,7 @@ def test_run_job() -> None:
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-1.jpg')
'output_path': get_test_output_file('output-3.jpg')
}
assert run_job('job-invalid', process_step) is False
@@ -83,7 +83,7 @@ def test_run_jobs() -> None:
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-1.jpg')
'output_path': get_test_output_file('output-3.jpg')
}
halt_on_error = True
@@ -103,14 +103,63 @@ def test_run_jobs() -> None:
assert run_jobs(process_step, halt_on_error) is True
@pytest.mark.skip()
def test_retry_job() -> None:
pass
args_1 =\
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.mp4'),
'output_path': get_test_output_file('output-1.mp4')
}
assert retry_job('job-invalid', process_step) is False
create_job('job-test-retry-job')
add_step('job-test-retry-job', args_1)
submit_job('job-test-retry-job')
assert retry_job('job-test-retry-job', process_step) is False
move_job_file('job-test-retry-job', 'failed')
assert retry_job('job-test-retry-job', process_step) is True
@pytest.mark.skip()
def test_retry_jobs() -> None:
pass
args_1 =\
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.mp4'),
'output_path': get_test_output_file('output-1.mp4')
}
args_2 =\
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.mp4'),
'output_path': get_test_output_file('output-2.mp4')
}
args_3 =\
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-3.jpg')
}
halt_on_error = True
assert retry_jobs(process_step, halt_on_error) is False
create_job('job-test-retry-jobs-1')
create_job('job-test-retry-jobs-2')
add_step('job-test-retry-jobs-1', args_1)
add_step('job-test-retry-jobs-1', args_1)
add_step('job-test-retry-jobs-2', args_2)
add_step('job-test-retry-jobs-3', args_3)
assert retry_jobs(process_step, halt_on_error) is False
move_job_file('job-test-retry-jobs-1', 'failed')
move_job_file('job-test-retry-jobs-2', 'failed')
assert retry_jobs(process_step, halt_on_error) is True
def test_run_steps() -> None:
@@ -130,7 +179,7 @@ def test_run_steps() -> None:
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-1.jpg')
'output_path': get_test_output_file('output-3.jpg')
}
assert run_steps('job-invalid', process_step) is False
@@ -161,7 +210,7 @@ def test_finalize_steps() -> None:
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-1.jpg')
'output_path': get_test_output_file('output-3.jpg')
}
create_job('job-test-finalize-steps')
@@ -173,12 +222,12 @@ def test_finalize_steps() -> None:
copy_file(args_1.get('target_path'), get_test_output_file('output-1-job-test-finalize-steps-0.mp4'))
copy_file(args_1.get('target_path'), get_test_output_file('output-1-job-test-finalize-steps-1.mp4'))
copy_file(args_2.get('target_path'), get_test_output_file('output-2-job-test-finalize-steps-2.mp4'))
copy_file(args_3.get('target_path'), get_test_output_file('output-1-job-test-finalize-steps-3.jpg'))
copy_file(args_3.get('target_path'), get_test_output_file('output-3-job-test-finalize-steps-3.jpg'))
assert finalize_steps('job-test-finalize-steps') is True
assert is_test_output_file('output-1.mp4') is True
assert is_test_output_file('output-2.mp4') is True
assert is_test_output_file('output-1.jpg') is True
assert is_test_output_file('output-3.jpg') is True
def test_collect_output_set() -> None:
@@ -198,7 +247,7 @@ def test_collect_output_set() -> None:
{
'source_path': get_test_example_file('source.jpg'),
'target_path': get_test_example_file('target-240p.jpg'),
'output_path': get_test_output_file('output-1.jpg')
'output_path': get_test_output_file('output-3.jpg')
}
create_job('job-test-collect-output-set')
@@ -218,9 +267,9 @@ def test_collect_output_set() -> None:
[
get_test_output_file('output-2-job-test-collect-output-set-2.mp4')
],
get_test_output_file('output-1.jpg'):
get_test_output_file('output-3.jpg'):
[
get_test_output_file('output-1-job-test-collect-output-set-3.jpg')
get_test_output_file('output-3-job-test-collect-output-set-3.jpg')
]
}

View File

@@ -0,0 +1,35 @@
from typing import Union
import pytest
from facefusion.processors.types import ProcessorState
from facefusion.state_manager import STATE_SET, get_item, init_item, set_item
from facefusion.types import AppContext, State
def get_state(app_context : AppContext) -> Union[State, ProcessorState]:
return STATE_SET.get(app_context)
def clear_state(app_context : AppContext) -> None:
STATE_SET[app_context] = {} #type:ignore[typeddict-item]
@pytest.fixture(scope = 'function', autouse = True)
def before_each() -> None:
clear_state('cli')
clear_state('ui')
def test_init_item() -> None:
init_item('video_memory_strategy', 'tolerant')
assert get_state('cli').get('video_memory_strategy') == 'tolerant'
assert get_state('ui').get('video_memory_strategy') == 'tolerant'
def test_get_item_and_set_item() -> None:
set_item('video_memory_strategy', 'tolerant')
assert get_item('video_memory_strategy') == 'tolerant'
assert get_state('ui').get('video_memory_strategy') is None