wsj1995 commited on
Commit
b214db1
·
1 Parent(s): be0c7c3

feat: init

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. app.py +10 -0
  2. facefusion/__init__.py +0 -0
  3. facefusion/app_context.py +16 -0
  4. facefusion/args.py +117 -0
  5. facefusion/audio.py +139 -0
  6. facefusion/choices.py +64 -0
  7. facefusion/common_helper.py +72 -0
  8. facefusion/config.py +92 -0
  9. facefusion/content_analyser.py +124 -0
  10. facefusion/core.py +445 -0
  11. facefusion/date_helper.py +28 -0
  12. facefusion/download.py +131 -0
  13. facefusion/execution.py +134 -0
  14. facefusion/exit_helper.py +24 -0
  15. facefusion/face_analyser.py +124 -0
  16. facefusion/face_classifier.py +128 -0
  17. facefusion/face_detector.py +309 -0
  18. facefusion/face_helper.py +210 -0
  19. facefusion/face_landmarker.py +217 -0
  20. facefusion/face_masker.py +173 -0
  21. facefusion/face_recognizer.py +81 -0
  22. facefusion/face_selector.py +91 -0
  23. facefusion/face_store.py +53 -0
  24. facefusion/ffmpeg.py +176 -0
  25. facefusion/filesystem.py +140 -0
  26. facefusion/hash_helper.py +32 -0
  27. facefusion/inference_manager.py +79 -0
  28. facefusion/installer.py +93 -0
  29. facefusion/jobs/__init__.py +0 -0
  30. facefusion/jobs/job_helper.py +15 -0
  31. facefusion/jobs/job_list.py +34 -0
  32. facefusion/jobs/job_manager.py +263 -0
  33. facefusion/jobs/job_runner.py +106 -0
  34. facefusion/jobs/job_store.py +27 -0
  35. facefusion/json.py +22 -0
  36. facefusion/logger.py +80 -0
  37. facefusion/memory.py +21 -0
  38. facefusion/metadata.py +17 -0
  39. facefusion/normalizer.py +21 -0
  40. facefusion/process_manager.py +53 -0
  41. facefusion/processors/__init__.py +0 -0
  42. facefusion/processors/choices.py +46 -0
  43. facefusion/processors/core.py +110 -0
  44. facefusion/processors/live_portrait.py +101 -0
  45. facefusion/processors/modules/__init__.py +0 -0
  46. facefusion/processors/modules/age_modifier.py +268 -0
  47. facefusion/processors/modules/expression_restorer.py +290 -0
  48. facefusion/processors/modules/face_debugger.py +222 -0
  49. facefusion/processors/modules/face_editor.py +528 -0
  50. facefusion/processors/modules/face_enhancer.py +397 -0
app.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+
5
+ os.environ['OMP_NUM_THREADS'] = '1'
6
+
7
+ from facefusion import core
8
+
9
+ if __name__ == '__main__':
10
+ core.cli()
facefusion/__init__.py ADDED
File without changes
facefusion/app_context.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ from facefusion.typing import AppContext
5
+
6
+
7
+ def detect_app_context() -> AppContext:
8
+ frame = sys._getframe(1)
9
+
10
+ while frame:
11
+ if os.path.join('facefusion', 'jobs') in frame.f_code.co_filename:
12
+ return 'cli'
13
+ if os.path.join('facefusion', 'uis') in frame.f_code.co_filename:
14
+ return 'ui'
15
+ frame = frame.f_back
16
+ return 'cli'
facefusion/args.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion import state_manager
2
+ from facefusion.filesystem import is_image, is_video, list_directory
3
+ from facefusion.jobs import job_store
4
+ from facefusion.normalizer import normalize_fps, normalize_padding
5
+ from facefusion.processors.core import get_processors_modules
6
+ from facefusion.typing import ApplyStateItem, Args
7
+ from facefusion.vision import create_image_resolutions, create_video_resolutions, detect_image_resolution, detect_video_fps, detect_video_resolution, pack_resolution
8
+
9
+
10
+ def reduce_step_args(args : Args) -> Args:
11
+ step_args =\
12
+ {
13
+ key: args[key] for key in args if key in job_store.get_step_keys()
14
+ }
15
+ return step_args
16
+
17
+
18
+ def collect_step_args() -> Args:
19
+ step_args =\
20
+ {
21
+ key: state_manager.get_item(key) for key in job_store.get_step_keys() #type:ignore[arg-type]
22
+ }
23
+ return step_args
24
+
25
+
26
+ def collect_job_args() -> Args:
27
+ job_args =\
28
+ {
29
+ key: state_manager.get_item(key) for key in job_store.get_job_keys() #type:ignore[arg-type]
30
+ }
31
+ return job_args
32
+
33
+
34
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
35
+ # general
36
+ apply_state_item('command', args.get('command'))
37
+ # paths
38
+ apply_state_item('jobs_path', args.get('jobs_path'))
39
+ apply_state_item('source_paths', args.get('source_paths'))
40
+ apply_state_item('target_path', args.get('target_path'))
41
+ apply_state_item('output_path', args.get('output_path'))
42
+ # face detector
43
+ apply_state_item('face_detector_model', args.get('face_detector_model'))
44
+ apply_state_item('face_detector_size', args.get('face_detector_size'))
45
+ apply_state_item('face_detector_angles', args.get('face_detector_angles'))
46
+ apply_state_item('face_detector_score', args.get('face_detector_score'))
47
+ # face landmarker
48
+ apply_state_item('face_landmarker_model', args.get('face_landmarker_model'))
49
+ apply_state_item('face_landmarker_score', args.get('face_landmarker_score'))
50
+ # face selector
51
+ state_manager.init_item('face_selector_mode', args.get('face_selector_mode'))
52
+ state_manager.init_item('face_selector_order', args.get('face_selector_order'))
53
+ state_manager.init_item('face_selector_age_start', args.get('face_selector_age_start'))
54
+ state_manager.init_item('face_selector_age_end', args.get('face_selector_age_end'))
55
+ state_manager.init_item('face_selector_gender', args.get('face_selector_gender'))
56
+ state_manager.init_item('face_selector_race', args.get('face_selector_race'))
57
+ state_manager.init_item('reference_face_position', args.get('reference_face_position'))
58
+ state_manager.init_item('reference_face_distance', args.get('reference_face_distance'))
59
+ state_manager.init_item('reference_frame_number', args.get('reference_frame_number'))
60
+ # face masker
61
+ apply_state_item('face_mask_types', args.get('face_mask_types'))
62
+ apply_state_item('face_mask_blur', args.get('face_mask_blur'))
63
+ apply_state_item('face_mask_padding', normalize_padding(args.get('face_mask_padding')))
64
+ apply_state_item('face_mask_regions', args.get('face_mask_regions'))
65
+ # frame extraction
66
+ apply_state_item('trim_frame_start', args.get('trim_frame_start'))
67
+ apply_state_item('trim_frame_end', args.get('trim_frame_end'))
68
+ apply_state_item('temp_frame_format', args.get('temp_frame_format'))
69
+ apply_state_item('keep_temp', args.get('keep_temp'))
70
+ # output creation
71
+ apply_state_item('output_image_quality', args.get('output_image_quality'))
72
+ if is_image(args.get('target_path')):
73
+ output_image_resolution = detect_image_resolution(args.get('target_path'))
74
+ output_image_resolutions = create_image_resolutions(output_image_resolution)
75
+ if args.get('output_image_resolution') in output_image_resolutions:
76
+ apply_state_item('output_image_resolution', args.get('output_image_resolution'))
77
+ else:
78
+ apply_state_item('output_image_resolution', pack_resolution(output_image_resolution))
79
+ apply_state_item('output_audio_encoder', args.get('output_audio_encoder'))
80
+ apply_state_item('output_video_encoder', args.get('output_video_encoder'))
81
+ apply_state_item('output_video_preset', args.get('output_video_preset'))
82
+ apply_state_item('output_video_quality', args.get('output_video_quality'))
83
+ if is_video(args.get('target_path')):
84
+ output_video_resolution = detect_video_resolution(args.get('target_path'))
85
+ output_video_resolutions = create_video_resolutions(output_video_resolution)
86
+ if args.get('output_video_resolution') in output_video_resolutions:
87
+ apply_state_item('output_video_resolution', args.get('output_video_resolution'))
88
+ else:
89
+ apply_state_item('output_video_resolution', pack_resolution(output_video_resolution))
90
+ if args.get('output_video_fps') or is_video(args.get('target_path')):
91
+ output_video_fps = normalize_fps(args.get('output_video_fps')) or detect_video_fps(args.get('target_path'))
92
+ apply_state_item('output_video_fps', output_video_fps)
93
+ apply_state_item('skip_audio', args.get('skip_audio'))
94
+ # processors
95
+ available_processors = list_directory('facefusion/processors/modules')
96
+ apply_state_item('processors', args.get('processors'))
97
+ for processor_module in get_processors_modules(available_processors):
98
+ processor_module.apply_args(args, apply_state_item)
99
+ # uis
100
+ apply_state_item('open_browser', args.get('open_browser'))
101
+ apply_state_item('ui_layouts', args.get('ui_layouts'))
102
+ apply_state_item('ui_workflow', args.get('ui_workflow'))
103
+ # execution
104
+ apply_state_item('execution_device_id', args.get('execution_device_id'))
105
+ apply_state_item('execution_providers', args.get('execution_providers'))
106
+ apply_state_item('execution_thread_count', args.get('execution_thread_count'))
107
+ apply_state_item('execution_queue_count', args.get('execution_queue_count'))
108
+ # memory
109
+ apply_state_item('video_memory_strategy', args.get('video_memory_strategy'))
110
+ apply_state_item('system_memory_limit', args.get('system_memory_limit'))
111
+ # misc
112
+ apply_state_item('skip_download', args.get('skip_download'))
113
+ apply_state_item('log_level', args.get('log_level'))
114
+ # jobs
115
+ apply_state_item('job_id', args.get('job_id'))
116
+ apply_state_item('job_status', args.get('job_status'))
117
+ apply_state_item('step_index', args.get('step_index'))
facefusion/audio.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ from typing import Any, List, Optional
3
+
4
+ import numpy
5
+ import scipy
6
+ from numpy._typing import NDArray
7
+
8
+ from facefusion.ffmpeg import read_audio_buffer
9
+ from facefusion.filesystem import is_audio
10
+ from facefusion.typing import Audio, AudioFrame, Fps, Mel, MelFilterBank, Spectrogram
11
+ from facefusion.voice_extractor import batch_extract_voice
12
+
13
+
14
+ @lru_cache(maxsize = 128)
15
+ def read_static_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
16
+ return read_audio(audio_path, fps)
17
+
18
+
19
+ def read_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
20
+ sample_rate = 48000
21
+ channel_total = 2
22
+
23
+ if is_audio(audio_path):
24
+ audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total)
25
+ audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2)
26
+ audio = prepare_audio(audio)
27
+ spectrogram = create_spectrogram(audio)
28
+ audio_frames = extract_audio_frames(spectrogram, fps)
29
+ return audio_frames
30
+ return None
31
+
32
+
33
+ @lru_cache(maxsize = 128)
34
+ def read_static_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
35
+ return read_voice(audio_path, fps)
36
+
37
+
38
+ def read_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
39
+ sample_rate = 48000
40
+ channel_total = 2
41
+ chunk_size = 240 * 1024
42
+ step_size = 180 * 1024
43
+
44
+ if is_audio(audio_path):
45
+ audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total)
46
+ audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2)
47
+ audio = batch_extract_voice(audio, chunk_size, step_size)
48
+ audio = prepare_voice(audio)
49
+ spectrogram = create_spectrogram(audio)
50
+ audio_frames = extract_audio_frames(spectrogram, fps)
51
+ return audio_frames
52
+ return None
53
+
54
+
55
+ def get_audio_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]:
56
+ if is_audio(audio_path):
57
+ audio_frames = read_static_audio(audio_path, fps)
58
+ if frame_number in range(len(audio_frames)):
59
+ return audio_frames[frame_number]
60
+ return None
61
+
62
+
63
+ def get_voice_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]:
64
+ if is_audio(audio_path):
65
+ voice_frames = read_static_voice(audio_path, fps)
66
+ if frame_number in range(len(voice_frames)):
67
+ return voice_frames[frame_number]
68
+ return None
69
+
70
+
71
+ def create_empty_audio_frame() -> AudioFrame:
72
+ mel_filter_total = 80
73
+ step_size = 16
74
+ audio_frame = numpy.zeros((mel_filter_total, step_size)).astype(numpy.int16)
75
+ return audio_frame
76
+
77
+
78
+ def prepare_audio(audio : Audio) -> Audio:
79
+ if audio.ndim > 1:
80
+ audio = numpy.mean(audio, axis = 1)
81
+ audio = audio / numpy.max(numpy.abs(audio), axis = 0)
82
+ audio = scipy.signal.lfilter([ 1.0, -0.97 ], [ 1.0 ], audio)
83
+ return audio
84
+
85
+
86
+ def prepare_voice(audio : Audio) -> Audio:
87
+ sample_rate = 48000
88
+ resample_rate = 16000
89
+
90
+ audio = scipy.signal.resample(audio, int(len(audio) * resample_rate / sample_rate))
91
+ audio = prepare_audio(audio)
92
+ return audio
93
+
94
+
95
+ def convert_hertz_to_mel(hertz : float) -> float:
96
+ return 2595 * numpy.log10(1 + hertz / 700)
97
+
98
+
99
+ def convert_mel_to_hertz(mel : Mel) -> NDArray[Any]:
100
+ return 700 * (10 ** (mel / 2595) - 1)
101
+
102
+
103
+ def create_mel_filter_bank() -> MelFilterBank:
104
+ mel_filter_total = 80
105
+ mel_bin_total = 800
106
+ sample_rate = 16000
107
+ min_frequency = 55.0
108
+ max_frequency = 7600.0
109
+ mel_filter_bank = numpy.zeros((mel_filter_total, mel_bin_total // 2 + 1))
110
+ mel_frequency_range = numpy.linspace(convert_hertz_to_mel(min_frequency), convert_hertz_to_mel(max_frequency), mel_filter_total + 2)
111
+ indices = numpy.floor((mel_bin_total + 1) * convert_mel_to_hertz(mel_frequency_range) / sample_rate).astype(numpy.int16)
112
+
113
+ for index in range(mel_filter_total):
114
+ start = indices[index]
115
+ end = indices[index + 1]
116
+ mel_filter_bank[index, start:end] = scipy.signal.windows.triang(end - start)
117
+ return mel_filter_bank
118
+
119
+
120
+ def create_spectrogram(audio : Audio) -> Spectrogram:
121
+ mel_bin_total = 800
122
+ mel_bin_overlap = 600
123
+ mel_filter_bank = create_mel_filter_bank()
124
+ spectrogram = scipy.signal.stft(audio, nperseg = mel_bin_total, nfft = mel_bin_total, noverlap = mel_bin_overlap)[2]
125
+ spectrogram = numpy.dot(mel_filter_bank, numpy.abs(spectrogram))
126
+ return spectrogram
127
+
128
+
129
+ def extract_audio_frames(spectrogram : Spectrogram, fps : Fps) -> List[AudioFrame]:
130
+ mel_filter_total = 80
131
+ step_size = 16
132
+ audio_frames = []
133
+ indices = numpy.arange(0, spectrogram.shape[1], mel_filter_total / fps).astype(numpy.int16)
134
+ indices = indices[indices >= step_size]
135
+
136
+ for index in indices:
137
+ start = max(0, index - step_size)
138
+ audio_frames.append(spectrogram[:, start:index])
139
+ return audio_frames
facefusion/choices.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import List, Sequence
3
+
4
+ from facefusion.common_helper import create_float_range, create_int_range
5
+ from facefusion.typing import Angle, ExecutionProviderSet, FaceDetectorSet, FaceLandmarkerModel, FaceMaskRegion, FaceMaskType, FaceSelectorMode, FaceSelectorOrder, Gender, JobStatus, LogLevelSet, OutputAudioEncoder, OutputVideoEncoder, OutputVideoPreset, Race, Score, TempFrameFormat, UiWorkflow, VideoMemoryStrategy
6
+
7
+ video_memory_strategies : List[VideoMemoryStrategy] = [ 'strict', 'moderate', 'tolerant' ]
8
+
9
+ face_detector_set : FaceDetectorSet =\
10
+ {
11
+ 'many': [ '640x640' ],
12
+ 'retinaface': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
13
+ 'scrfd': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
14
+ 'yoloface': [ '640x640' ]
15
+ }
16
+ face_landmarker_models : List[FaceLandmarkerModel] = [ 'many', '2dfan4', 'peppa_wutz' ]
17
+ face_selector_modes : List[FaceSelectorMode] = [ 'many', 'one', 'reference' ]
18
+ face_selector_orders : List[FaceSelectorOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ]
19
+ face_selector_genders : List[Gender] = ['female', 'male']
20
+ face_selector_races : List[Race] = ['white', 'black', 'latino', 'asian', 'indian', 'arabic']
21
+ face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'region' ]
22
+ face_mask_regions : List[FaceMaskRegion] = [ 'skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip' ]
23
+ temp_frame_formats : List[TempFrameFormat] = [ 'bmp', 'jpg', 'png' ]
24
+ output_audio_encoders : List[OutputAudioEncoder] = [ 'aac', 'libmp3lame', 'libopus', 'libvorbis' ]
25
+ output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf', 'h264_videotoolbox', 'hevc_videotoolbox' ]
26
+ output_video_presets : List[OutputVideoPreset] = [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ]
27
+
28
+ image_template_sizes : List[float] = [ 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4 ]
29
+ video_template_sizes : List[int] = [ 240, 360, 480, 540, 720, 1080, 1440, 2160, 4320 ]
30
+
31
+ log_level_set : LogLevelSet =\
32
+ {
33
+ 'error': logging.ERROR,
34
+ 'warn': logging.WARNING,
35
+ 'info': logging.INFO,
36
+ 'debug': logging.DEBUG
37
+ }
38
+
39
+ execution_provider_set : ExecutionProviderSet =\
40
+ {
41
+ 'cpu': 'CPUExecutionProvider',
42
+ 'coreml': 'CoreMLExecutionProvider',
43
+ 'cuda': 'CUDAExecutionProvider',
44
+ 'directml': 'DmlExecutionProvider',
45
+ 'openvino': 'OpenVINOExecutionProvider',
46
+ 'rocm': 'ROCMExecutionProvider',
47
+ 'tensorrt': 'TensorrtExecutionProvider'
48
+ }
49
+
50
+ ui_workflows : List[UiWorkflow] = [ 'instant_runner', 'job_runner', 'job_manager' ]
51
+ job_statuses : List[JobStatus] = [ 'drafted', 'queued', 'completed', 'failed' ]
52
+
53
+ execution_thread_count_range : Sequence[int] = create_int_range(1, 32, 1)
54
+ execution_queue_count_range : Sequence[int] = create_int_range(1, 4, 1)
55
+ system_memory_limit_range : Sequence[int] = create_int_range(0, 128, 4)
56
+ face_detector_angles : Sequence[Angle] = create_int_range(0, 270, 90)
57
+ face_detector_score_range : Sequence[Score] = create_float_range(0.0, 1.0, 0.05)
58
+ face_landmarker_score_range : Sequence[Score] = create_float_range(0.0, 1.0, 0.05)
59
+ face_mask_blur_range : Sequence[float] = create_float_range(0.0, 1.0, 0.05)
60
+ face_mask_padding_range : Sequence[int] = create_int_range(0, 100, 1)
61
+ face_selector_age_range : Sequence[int] = create_int_range(0, 100, 1)
62
+ reference_face_distance_range : Sequence[float] = create_float_range(0.0, 1.5, 0.05)
63
+ output_image_quality_range : Sequence[int] = create_int_range(0, 100, 1)
64
+ output_video_quality_range : Sequence[int] = create_int_range(0, 100, 1)
facefusion/common_helper.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+ from typing import Any, Optional, Sequence
3
+
4
+
5
+ def is_linux() -> bool:
6
+ return platform.system().lower() == 'linux'
7
+
8
+
9
+ def is_macos() -> bool:
10
+ return platform.system().lower() == 'darwin'
11
+
12
+
13
+ def is_windows() -> bool:
14
+ return platform.system().lower() == 'windows'
15
+
16
+
17
+ def create_int_metavar(int_range : Sequence[int]) -> str:
18
+ return '[' + str(int_range[0]) + '..' + str(int_range[-1]) + ':' + str(calc_int_step(int_range)) + ']'
19
+
20
+
21
+ def create_float_metavar(float_range : Sequence[float]) -> str:
22
+ return '[' + str(float_range[0]) + '..' + str(float_range[-1]) + ':' + str(calc_float_step(float_range)) + ']'
23
+
24
+
25
+ def create_int_range(start : int, end : int, step : int) -> Sequence[int]:
26
+ int_range = []
27
+ current = start
28
+
29
+ while current <= end:
30
+ int_range.append(current)
31
+ current += step
32
+ return int_range
33
+
34
+
35
+ def create_float_range(start : float, end : float, step : float) -> Sequence[float]:
36
+ float_range = []
37
+ current = start
38
+
39
+ while current <= end:
40
+ float_range.append(round(current, 2))
41
+ current = round(current + step, 2)
42
+ return float_range
43
+
44
+
45
+ def calc_int_step(int_range : Sequence[int]) -> int:
46
+ return int_range[1] - int_range[0]
47
+
48
+
49
+ def calc_float_step(float_range : Sequence[float]) -> float:
50
+ return round(float_range[1] - float_range[0], 2)
51
+
52
+
53
+ def cast_int(value : Any) -> Optional[Any]:
54
+ try:
55
+ return int(value)
56
+ except (ValueError, TypeError):
57
+ return None
58
+
59
+
60
+ def cast_float(value : Any) -> Optional[Any]:
61
+ try:
62
+ return float(value)
63
+ except (ValueError, TypeError):
64
+ return None
65
+
66
+
67
+ def get_first(__list__ : Any) -> Any:
68
+ return next(iter(__list__), None)
69
+
70
+
71
+ def get_last(__list__ : Any) -> Any:
72
+ return next(reversed(__list__), None)
facefusion/config.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from configparser import ConfigParser
2
+ from typing import Any, List, Optional
3
+
4
+ from facefusion import state_manager
5
+ from facefusion.common_helper import cast_float, cast_int
6
+
7
+ CONFIG = None
8
+
9
+
10
+ def get_config() -> ConfigParser:
11
+ global CONFIG
12
+
13
+ if CONFIG is None:
14
+ CONFIG = ConfigParser()
15
+ CONFIG.read(state_manager.get_item('config_path'), encoding = 'utf-8')
16
+ return CONFIG
17
+
18
+
19
+ def clear_config() -> None:
20
+ global CONFIG
21
+
22
+ CONFIG = None
23
+
24
+
25
+ def get_str_value(key : str, fallback : Optional[str] = None) -> Optional[str]:
26
+ value = get_value_by_notation(key)
27
+
28
+ if value or fallback:
29
+ return str(value or fallback)
30
+ return None
31
+
32
+
33
+ def get_int_value(key : str, fallback : Optional[str] = None) -> Optional[int]:
34
+ value = get_value_by_notation(key)
35
+
36
+ if value or fallback:
37
+ return cast_int(value or fallback)
38
+ return None
39
+
40
+
41
+ def get_float_value(key : str, fallback : Optional[str] = None) -> Optional[float]:
42
+ value = get_value_by_notation(key)
43
+
44
+ if value or fallback:
45
+ return cast_float(value or fallback)
46
+ return None
47
+
48
+
49
+ def get_bool_value(key : str, fallback : Optional[str] = None) -> Optional[bool]:
50
+ value = get_value_by_notation(key)
51
+
52
+ if value == 'True' or fallback == 'True':
53
+ return True
54
+ if value == 'False' or fallback == 'False':
55
+ return False
56
+ return None
57
+
58
+
59
+ def get_str_list(key : str, fallback : Optional[str] = None) -> Optional[List[str]]:
60
+ value = get_value_by_notation(key)
61
+
62
+ if value or fallback:
63
+ return [ str(value) for value in (value or fallback).split(' ') ]
64
+ return None
65
+
66
+
67
+ def get_int_list(key : str, fallback : Optional[str] = None) -> Optional[List[int]]:
68
+ value = get_value_by_notation(key)
69
+
70
+ if value or fallback:
71
+ return [ cast_int(value) for value in (value or fallback).split(' ') ]
72
+ return None
73
+
74
+
75
+ def get_float_list(key : str, fallback : Optional[str] = None) -> Optional[List[float]]:
76
+ value = get_value_by_notation(key)
77
+
78
+ if value or fallback:
79
+ return [ cast_float(value) for value in (value or fallback).split(' ') ]
80
+ return None
81
+
82
+
83
+ def get_value_by_notation(key : str) -> Optional[Any]:
84
+ config = get_config()
85
+
86
+ if '.' in key:
87
+ section, name = key.split('.')
88
+ if section in config and name in config[section]:
89
+ return config[section][name]
90
+ if key in config:
91
+ return config[key]
92
+ return None
facefusion/content_analyser.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+
3
+ import cv2
4
+ import numpy
5
+ from tqdm import tqdm
6
+
7
+ from facefusion import inference_manager, state_manager, wording
8
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
9
+ from facefusion.filesystem import resolve_relative_path
10
+ from facefusion.thread_helper import conditional_thread_semaphore
11
+ from facefusion.typing import Fps, InferencePool, ModelOptions, ModelSet, VisionFrame
12
+ from facefusion.vision import count_video_frame_total, detect_video_fps, get_video_frame, read_image
13
+
14
+ MODEL_SET : ModelSet =\
15
+ {
16
+ 'open_nsfw':
17
+ {
18
+ 'hashes':
19
+ {
20
+ 'content_analyser':
21
+ {
22
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/open_nsfw.hash',
23
+ 'path': resolve_relative_path('../.assets/models/open_nsfw.hash')
24
+ }
25
+ },
26
+ 'sources':
27
+ {
28
+ 'content_analyser':
29
+ {
30
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/open_nsfw.onnx',
31
+ 'path': resolve_relative_path('../.assets/models/open_nsfw.onnx')
32
+ }
33
+ },
34
+ 'size': (224, 224),
35
+ 'mean': [ 104, 117, 123 ]
36
+ }
37
+ }
38
+ PROBABILITY_LIMIT = 0.80
39
+ RATE_LIMIT = 10
40
+ STREAM_COUNTER = 0
41
+
42
+
43
+ def get_inference_pool() -> InferencePool:
44
+ model_sources = get_model_options().get('sources')
45
+ return inference_manager.get_inference_pool(__name__, model_sources)
46
+
47
+
48
+ def clear_inference_pool() -> None:
49
+ inference_manager.clear_inference_pool(__name__)
50
+
51
+
52
+ def get_model_options() -> ModelOptions:
53
+ return MODEL_SET.get('open_nsfw')
54
+
55
+
56
+ def pre_check() -> bool:
57
+ download_directory_path = resolve_relative_path('../.assets/models')
58
+ model_hashes = get_model_options().get('hashes')
59
+ model_sources = get_model_options().get('sources')
60
+
61
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
62
+
63
+
64
+ def analyse_stream(vision_frame : VisionFrame, video_fps : Fps) -> bool:
65
+ global STREAM_COUNTER
66
+
67
+ STREAM_COUNTER = STREAM_COUNTER + 1
68
+ if STREAM_COUNTER % int(video_fps) == 0:
69
+ return analyse_frame(vision_frame)
70
+ return False
71
+
72
+
73
+ def analyse_frame(vision_frame : VisionFrame) -> bool:
74
+ vision_frame = prepare_frame(vision_frame)
75
+ probability = forward(vision_frame)
76
+
77
+ return probability > PROBABILITY_LIMIT
78
+
79
+
80
+ def forward(vision_frame : VisionFrame) -> float:
81
+ content_analyser = get_inference_pool().get('content_analyser')
82
+
83
+ with conditional_thread_semaphore():
84
+ probability = content_analyser.run(None,
85
+ {
86
+ 'input': vision_frame
87
+ })[0][0][1]
88
+
89
+ return probability
90
+
91
+
92
+ def prepare_frame(vision_frame : VisionFrame) -> VisionFrame:
93
+ model_size = get_model_options().get('size')
94
+ model_mean = get_model_options().get('mean')
95
+ vision_frame = cv2.resize(vision_frame, model_size).astype(numpy.float32)
96
+ vision_frame -= numpy.array(model_mean).astype(numpy.float32)
97
+ vision_frame = numpy.expand_dims(vision_frame, axis = 0)
98
+ return vision_frame
99
+
100
+
101
+ @lru_cache(maxsize = None)
102
+ def analyse_image(image_path : str) -> bool:
103
+ frame = read_image(image_path)
104
+ return analyse_frame(frame)
105
+
106
+
107
+ @lru_cache(maxsize = None)
108
+ def analyse_video(video_path : str, start_frame : int, end_frame : int) -> bool:
109
+ video_frame_total = count_video_frame_total(video_path)
110
+ video_fps = detect_video_fps(video_path)
111
+ frame_range = range(start_frame or 0, end_frame or video_frame_total)
112
+ rate = 0.0
113
+ counter = 0
114
+
115
+ with tqdm(total = len(frame_range), desc = wording.get('analysing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
116
+ for frame_number in frame_range:
117
+ if frame_number % int(video_fps) == 0:
118
+ frame = get_video_frame(video_path, frame_number)
119
+ if analyse_frame(frame):
120
+ counter += 1
121
+ rate = counter * int(video_fps) / len(frame_range) * 100
122
+ progress.update()
123
+ progress.set_postfix(rate = rate)
124
+ return rate > RATE_LIMIT
facefusion/core.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import shutil
2
+ import signal
3
+ import sys
4
+ from time import time
5
+
6
+ import numpy
7
+
8
+ from facefusion import content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, voice_extractor, wording
9
+ from facefusion.args import apply_args, collect_job_args, reduce_step_args
10
+ from facefusion.common_helper import get_first
11
+ from facefusion.content_analyser import analyse_image, analyse_video
12
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
13
+ from facefusion.exit_helper import conditional_exit, graceful_exit, hard_exit
14
+ from facefusion.face_analyser import get_average_face, get_many_faces, get_one_face
15
+ from facefusion.face_selector import sort_and_filter_faces
16
+ from facefusion.face_store import append_reference_face, clear_reference_faces, get_reference_faces
17
+ from facefusion.ffmpeg import copy_image, extract_frames, finalize_image, merge_video, replace_audio, restore_audio
18
+ from facefusion.filesystem import filter_audio_paths, is_image, is_video, list_directory, resolve_relative_path
19
+ from facefusion.jobs import job_helper, job_manager, job_runner
20
+ from facefusion.jobs.job_list import compose_job_list
21
+ from facefusion.memory import limit_system_memory
22
+ from facefusion.processors.core import get_processors_modules
23
+ from facefusion.program import create_program
24
+ from facefusion.program_helper import validate_args
25
+ from facefusion.statistics import conditional_log_statistics
26
+ from facefusion.temp_helper import clear_temp_directory, create_temp_directory, get_temp_file_path, get_temp_frame_paths, move_temp_file
27
+ from facefusion.typing import Args, ErrorCode
28
+ from facefusion.vision import get_video_frame, pack_resolution, read_image, read_static_images, restrict_image_resolution, restrict_video_fps, restrict_video_resolution, unpack_resolution
29
+
30
+
31
+ def cli() -> None:
32
+ signal.signal(signal.SIGINT, lambda signal_number, frame: graceful_exit(0))
33
+ program = create_program()
34
+
35
+ if validate_args(program):
36
+ args = vars(program.parse_args())
37
+ apply_args(args, state_manager.init_item)
38
+
39
+ if state_manager.get_item('command'):
40
+ logger.init(state_manager.get_item('log_level'))
41
+ route(args)
42
+ else:
43
+ program.print_help()
44
+
45
+
46
+ def route(args : Args) -> None:
47
+ system_memory_limit = state_manager.get_item('system_memory_limit')
48
+ if system_memory_limit and system_memory_limit > 0:
49
+ limit_system_memory(system_memory_limit)
50
+ if state_manager.get_item('command') == 'force-download':
51
+ error_code = force_download()
52
+ return conditional_exit(error_code)
53
+ 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' ]:
54
+ if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
55
+ hard_exit(1)
56
+ error_code = route_job_manager(args)
57
+ hard_exit(error_code)
58
+ if not pre_check():
59
+ return conditional_exit(2)
60
+ if state_manager.get_item('command') == 'run':
61
+ import facefusion.uis.core as ui
62
+
63
+ if not common_pre_check() or not processors_pre_check():
64
+ return conditional_exit(2)
65
+ for ui_layout in ui.get_ui_layouts_modules(state_manager.get_item('ui_layouts')):
66
+ if not ui_layout.pre_check():
67
+ return conditional_exit(2)
68
+ ui.launch()
69
+ if state_manager.get_item('command') == 'headless-run':
70
+ if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
71
+ hard_exit(1)
72
+ error_core = process_headless(args)
73
+ hard_exit(error_core)
74
+ if state_manager.get_item('command') in [ 'job-run', 'job-run-all', 'job-retry', 'job-retry-all' ]:
75
+ if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
76
+ hard_exit(1)
77
+ error_code = route_job_runner()
78
+ hard_exit(error_code)
79
+
80
+
81
+ def pre_check() -> bool:
82
+ if sys.version_info < (3, 9):
83
+ logger.error(wording.get('python_not_supported').format(version = '3.9'), __name__)
84
+ return False
85
+ if not shutil.which('curl'):
86
+ logger.error(wording.get('curl_not_installed'), __name__)
87
+ return False
88
+ if not shutil.which('ffmpeg'):
89
+ logger.error(wording.get('ffmpeg_not_installed'), __name__)
90
+ return False
91
+ return True
92
+
93
+
94
+ def common_pre_check() -> bool:
95
+ modules =\
96
+ [
97
+ content_analyser,
98
+ face_classifier,
99
+ face_detector,
100
+ face_landmarker,
101
+ face_masker,
102
+ face_recognizer,
103
+ voice_extractor
104
+ ]
105
+
106
+ return all(module.pre_check() for module in modules)
107
+
108
+
109
+ def processors_pre_check() -> bool:
110
+ for processor_module in get_processors_modules(state_manager.get_item('processors')):
111
+ if not processor_module.pre_check():
112
+ return False
113
+ return True
114
+
115
+
116
+ def conditional_process() -> ErrorCode:
117
+ start_time = time()
118
+ for processor_module in get_processors_modules(state_manager.get_item('processors')):
119
+ if not processor_module.pre_process('output'):
120
+ return 2
121
+ conditional_append_reference_faces()
122
+ if is_image(state_manager.get_item('target_path')):
123
+ return process_image(start_time)
124
+ if is_video(state_manager.get_item('target_path')):
125
+ return process_video(start_time)
126
+ return 0
127
+
128
+
129
+ def conditional_append_reference_faces() -> None:
130
+ if 'reference' in state_manager.get_item('face_selector_mode') and not get_reference_faces():
131
+ source_frames = read_static_images(state_manager.get_item('source_paths'))
132
+ source_faces = get_many_faces(source_frames)
133
+ source_face = get_average_face(source_faces)
134
+ if is_video(state_manager.get_item('target_path')):
135
+ reference_frame = get_video_frame(state_manager.get_item('target_path'), state_manager.get_item('reference_frame_number'))
136
+ else:
137
+ reference_frame = read_image(state_manager.get_item('target_path'))
138
+ reference_faces = sort_and_filter_faces(get_many_faces([ reference_frame ]))
139
+ reference_face = get_one_face(reference_faces, state_manager.get_item('reference_face_position'))
140
+ append_reference_face('origin', reference_face)
141
+
142
+ if source_face and reference_face:
143
+ for processor_module in get_processors_modules(state_manager.get_item('processors')):
144
+ abstract_reference_frame = processor_module.get_reference_frame(source_face, reference_face, reference_frame)
145
+ if numpy.any(abstract_reference_frame):
146
+ abstract_reference_faces = sort_and_filter_faces(get_many_faces([ abstract_reference_frame ]))
147
+ abstract_reference_face = get_one_face(abstract_reference_faces, state_manager.get_item('reference_face_position'))
148
+ append_reference_face(processor_module.__name__, abstract_reference_face)
149
+
150
+
151
+ def force_download() -> ErrorCode:
152
+ download_directory_path = resolve_relative_path('../.assets/models')
153
+ available_processors = list_directory('facefusion/processors/modules')
154
+ common_modules =\
155
+ [
156
+ content_analyser,
157
+ face_classifier,
158
+ face_detector,
159
+ face_landmarker,
160
+ face_recognizer,
161
+ face_masker,
162
+ voice_extractor
163
+ ]
164
+ processor_modules = get_processors_modules(available_processors)
165
+
166
+ for module in common_modules + processor_modules:
167
+ if hasattr(module, 'MODEL_SET'):
168
+ for model in module.MODEL_SET.values():
169
+ model_hashes = model.get('hashes')
170
+ model_sources = model.get('sources')
171
+
172
+ if model_hashes and model_sources:
173
+ if not conditional_download_hashes(download_directory_path, model_hashes) or not conditional_download_sources(download_directory_path, model_sources):
174
+ return 1
175
+
176
+ return 0
177
+
178
+
179
+ def route_job_manager(args : Args) -> ErrorCode:
180
+ if state_manager.get_item('command') == 'job-list':
181
+ job_headers, job_contents = compose_job_list(state_manager.get_item('job_status'))
182
+
183
+ if job_contents:
184
+ logger.table(job_headers, job_contents)
185
+ return 0
186
+ return 1
187
+ if state_manager.get_item('command') == 'job-create':
188
+ if job_manager.create_job(state_manager.get_item('job_id')):
189
+ logger.info(wording.get('job_created').format(job_id = state_manager.get_item('job_id')), __name__)
190
+ return 0
191
+ logger.error(wording.get('job_not_created').format(job_id = state_manager.get_item('job_id')), __name__)
192
+ return 1
193
+ if state_manager.get_item('command') == 'job-submit':
194
+ if job_manager.submit_job(state_manager.get_item('job_id')):
195
+ logger.info(wording.get('job_submitted').format(job_id = state_manager.get_item('job_id')), __name__)
196
+ return 0
197
+ logger.error(wording.get('job_not_submitted').format(job_id = state_manager.get_item('job_id')), __name__)
198
+ return 1
199
+ if state_manager.get_item('command') == 'job-submit-all':
200
+ if job_manager.submit_jobs():
201
+ logger.info(wording.get('job_all_submitted'), __name__)
202
+ return 0
203
+ logger.error(wording.get('job_all_not_submitted'), __name__)
204
+ return 1
205
+ if state_manager.get_item('command') == 'job-delete':
206
+ if job_manager.delete_job(state_manager.get_item('job_id')):
207
+ logger.info(wording.get('job_deleted').format(job_id = state_manager.get_item('job_id')), __name__)
208
+ return 0
209
+ logger.error(wording.get('job_not_deleted').format(job_id = state_manager.get_item('job_id')), __name__)
210
+ return 1
211
+ if state_manager.get_item('command') == 'job-delete-all':
212
+ if job_manager.delete_jobs():
213
+ logger.info(wording.get('job_all_deleted'), __name__)
214
+ return 0
215
+ logger.error(wording.get('job_all_not_deleted'), __name__)
216
+ return 1
217
+ if state_manager.get_item('command') == 'job-add-step':
218
+ step_args = reduce_step_args(args)
219
+
220
+ if job_manager.add_step(state_manager.get_item('job_id'), step_args):
221
+ logger.info(wording.get('job_step_added').format(job_id = state_manager.get_item('job_id')), __name__)
222
+ return 0
223
+ logger.error(wording.get('job_step_not_added').format(job_id = state_manager.get_item('job_id')), __name__)
224
+ return 1
225
+ if state_manager.get_item('command') == 'job-remix-step':
226
+ step_args = reduce_step_args(args)
227
+
228
+ if job_manager.remix_step(state_manager.get_item('job_id'), state_manager.get_item('step_index'), step_args):
229
+ logger.info(wording.get('job_remix_step_added').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
230
+ return 0
231
+ logger.error(wording.get('job_remix_step_not_added').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
232
+ return 1
233
+ if state_manager.get_item('command') == 'job-insert-step':
234
+ step_args = reduce_step_args(args)
235
+
236
+ if job_manager.insert_step(state_manager.get_item('job_id'), state_manager.get_item('step_index'), step_args):
237
+ logger.info(wording.get('job_step_inserted').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
238
+ return 0
239
+ logger.error(wording.get('job_step_not_inserted').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
240
+ return 1
241
+ if state_manager.get_item('command') == 'job-remove-step':
242
+ if job_manager.remove_step(state_manager.get_item('job_id'), state_manager.get_item('step_index')):
243
+ logger.info(wording.get('job_step_removed').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
244
+ return 0
245
+ logger.error(wording.get('job_step_not_removed').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
246
+ return 1
247
+ return 1
248
+
249
+
250
+ def route_job_runner() -> ErrorCode:
251
+ if state_manager.get_item('command') == 'job-run':
252
+ logger.info(wording.get('running_job').format(job_id = state_manager.get_item('job_id')), __name__)
253
+ if job_runner.run_job(state_manager.get_item('job_id'), process_step):
254
+ logger.info(wording.get('processing_job_succeed').format(job_id = state_manager.get_item('job_id')), __name__)
255
+ return 0
256
+ logger.info(wording.get('processing_job_failed').format(job_id = state_manager.get_item('job_id')), __name__)
257
+ return 1
258
+ if state_manager.get_item('command') == 'job-run-all':
259
+ logger.info(wording.get('running_jobs'), __name__)
260
+ if job_runner.run_jobs(process_step):
261
+ logger.info(wording.get('processing_jobs_succeed'), __name__)
262
+ return 0
263
+ logger.info(wording.get('processing_jobs_failed'), __name__)
264
+ return 1
265
+ if state_manager.get_item('command') == 'job-retry':
266
+ logger.info(wording.get('retrying_job').format(job_id = state_manager.get_item('job_id')), __name__)
267
+ if job_runner.retry_job(state_manager.get_item('job_id'), process_step):
268
+ logger.info(wording.get('processing_job_succeed').format(job_id = state_manager.get_item('job_id')), __name__)
269
+ return 0
270
+ logger.info(wording.get('processing_job_failed').format(job_id = state_manager.get_item('job_id')), __name__)
271
+ return 1
272
+ if state_manager.get_item('command') == 'job-retry-all':
273
+ logger.info(wording.get('retrying_jobs'), __name__)
274
+ if job_runner.retry_jobs(process_step):
275
+ logger.info(wording.get('processing_jobs_succeed'), __name__)
276
+ return 0
277
+ logger.info(wording.get('processing_jobs_failed'), __name__)
278
+ return 1
279
+ return 2
280
+
281
+
282
+ def process_step(job_id : str, step_index : int, step_args : Args) -> bool:
283
+ clear_reference_faces()
284
+ step_total = job_manager.count_step_total(job_id)
285
+ step_args.update(collect_job_args())
286
+ apply_args(step_args, state_manager.set_item)
287
+
288
+ logger.info(wording.get('processing_step').format(step_current = step_index + 1, step_total = step_total), __name__)
289
+ if common_pre_check() and processors_pre_check():
290
+ error_code = conditional_process()
291
+ return error_code == 0
292
+ return False
293
+
294
+
295
+ def process_headless(args : Args) -> ErrorCode:
296
+ job_id = job_helper.suggest_job_id('headless')
297
+ step_args = reduce_step_args(args)
298
+
299
+ if job_manager.create_job(job_id) and job_manager.add_step(job_id, step_args) and job_manager.submit_job(job_id) and job_runner.run_job(job_id, process_step):
300
+ return 0
301
+ return 1
302
+
303
+
304
+ def process_image(start_time : float) -> ErrorCode:
305
+ if analyse_image(state_manager.get_item('target_path')):
306
+ return 3
307
+ # clear temp
308
+ logger.debug(wording.get('clearing_temp'), __name__)
309
+ clear_temp_directory(state_manager.get_item('target_path'))
310
+ # create temp
311
+ logger.debug(wording.get('creating_temp'), __name__)
312
+ create_temp_directory(state_manager.get_item('target_path'))
313
+ # copy image
314
+ process_manager.start()
315
+ temp_image_resolution = pack_resolution(restrict_image_resolution(state_manager.get_item('target_path'), unpack_resolution(state_manager.get_item('output_image_resolution'))))
316
+ logger.info(wording.get('copying_image').format(resolution = temp_image_resolution), __name__)
317
+ if copy_image(state_manager.get_item('target_path'), temp_image_resolution):
318
+ logger.debug(wording.get('copying_image_succeed'), __name__)
319
+ else:
320
+ logger.error(wording.get('copying_image_failed'), __name__)
321
+ process_manager.end()
322
+ return 1
323
+ # process image
324
+ temp_file_path = get_temp_file_path(state_manager.get_item('target_path'))
325
+ for processor_module in get_processors_modules(state_manager.get_item('processors')):
326
+ logger.info(wording.get('processing'), processor_module.__name__)
327
+ processor_module.process_image(state_manager.get_item('source_paths'), temp_file_path, temp_file_path)
328
+ processor_module.post_process()
329
+ if is_process_stopping():
330
+ process_manager.end()
331
+ return 4
332
+ # finalize image
333
+ logger.info(wording.get('finalizing_image').format(resolution = state_manager.get_item('output_image_resolution')), __name__)
334
+ if finalize_image(state_manager.get_item('target_path'), state_manager.get_item('output_path'), state_manager.get_item('output_image_resolution')):
335
+ logger.debug(wording.get('finalizing_image_succeed'), __name__)
336
+ else:
337
+ logger.warn(wording.get('finalizing_image_skipped'), __name__)
338
+ # clear temp
339
+ logger.debug(wording.get('clearing_temp'), __name__)
340
+ clear_temp_directory(state_manager.get_item('target_path'))
341
+ # validate image
342
+ if is_image(state_manager.get_item('output_path')):
343
+ seconds = '{:.2f}'.format((time() - start_time) % 60)
344
+ logger.info(wording.get('processing_image_succeed').format(seconds = seconds), __name__)
345
+ conditional_log_statistics()
346
+ else:
347
+ logger.error(wording.get('processing_image_failed'), __name__)
348
+ process_manager.end()
349
+ return 1
350
+ process_manager.end()
351
+ return 0
352
+
353
+
354
+ def process_video(start_time : float) -> ErrorCode:
355
+ if analyse_video(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end')):
356
+ return 3
357
+ # clear temp
358
+ logger.debug(wording.get('clearing_temp'), __name__)
359
+ clear_temp_directory(state_manager.get_item('target_path'))
360
+ # create temp
361
+ logger.debug(wording.get('creating_temp'), __name__)
362
+ create_temp_directory(state_manager.get_item('target_path'))
363
+ # extract frames
364
+ process_manager.start()
365
+ temp_video_resolution = pack_resolution(restrict_video_resolution(state_manager.get_item('target_path'), unpack_resolution(state_manager.get_item('output_video_resolution'))))
366
+ temp_video_fps = restrict_video_fps(state_manager.get_item('target_path'), state_manager.get_item('output_video_fps'))
367
+ logger.info(wording.get('extracting_frames').format(resolution = temp_video_resolution, fps = temp_video_fps), __name__)
368
+ if extract_frames(state_manager.get_item('target_path'), temp_video_resolution, temp_video_fps):
369
+ logger.debug(wording.get('extracting_frames_succeed'), __name__)
370
+ else:
371
+ if is_process_stopping():
372
+ process_manager.end()
373
+ return 4
374
+ logger.error(wording.get('extracting_frames_failed'), __name__)
375
+ process_manager.end()
376
+ return 1
377
+ # process frames
378
+ temp_frame_paths = get_temp_frame_paths(state_manager.get_item('target_path'))
379
+ if temp_frame_paths:
380
+ for processor_module in get_processors_modules(state_manager.get_item('processors')):
381
+ logger.info(wording.get('processing'), processor_module.__name__)
382
+ processor_module.process_video(state_manager.get_item('source_paths'), temp_frame_paths)
383
+ processor_module.post_process()
384
+ if is_process_stopping():
385
+ return 4
386
+ else:
387
+ logger.error(wording.get('temp_frames_not_found'), __name__)
388
+ process_manager.end()
389
+ return 1
390
+ # merge video
391
+ logger.info(wording.get('merging_video').format(resolution = state_manager.get_item('output_video_resolution'), fps = state_manager.get_item('output_video_fps')), __name__)
392
+ if merge_video(state_manager.get_item('target_path'), state_manager.get_item('output_video_resolution'), state_manager.get_item('output_video_fps')):
393
+ logger.debug(wording.get('merging_video_succeed'), __name__)
394
+ else:
395
+ if is_process_stopping():
396
+ process_manager.end()
397
+ return 4
398
+ logger.error(wording.get('merging_video_failed'), __name__)
399
+ process_manager.end()
400
+ return 1
401
+ # handle audio
402
+ if state_manager.get_item('skip_audio'):
403
+ logger.info(wording.get('skipping_audio'), __name__)
404
+ move_temp_file(state_manager.get_item('target_path'), state_manager.get_item('output_path'))
405
+ else:
406
+ source_audio_path = get_first(filter_audio_paths(state_manager.get_item('source_paths')))
407
+ if source_audio_path:
408
+ if replace_audio(state_manager.get_item('target_path'), source_audio_path, state_manager.get_item('output_path')):
409
+ logger.debug(wording.get('replacing_audio_succeed'), __name__)
410
+ else:
411
+ if is_process_stopping():
412
+ process_manager.end()
413
+ return 4
414
+ logger.warn(wording.get('replacing_audio_skipped'), __name__)
415
+ move_temp_file(state_manager.get_item('target_path'), state_manager.get_item('output_path'))
416
+ else:
417
+ if restore_audio(state_manager.get_item('target_path'), state_manager.get_item('output_path'), state_manager.get_item('output_video_fps')):
418
+ logger.debug(wording.get('restoring_audio_succeed'), __name__)
419
+ else:
420
+ if is_process_stopping():
421
+ process_manager.end()
422
+ return 4
423
+ logger.warn(wording.get('restoring_audio_skipped'), __name__)
424
+ move_temp_file(state_manager.get_item('target_path'), state_manager.get_item('output_path'))
425
+ # clear temp
426
+ logger.debug(wording.get('clearing_temp'), __name__)
427
+ clear_temp_directory(state_manager.get_item('target_path'))
428
+ # validate video
429
+ if is_video(state_manager.get_item('output_path')):
430
+ seconds = '{:.2f}'.format((time() - start_time))
431
+ logger.info(wording.get('processing_video_succeed').format(seconds = seconds), __name__)
432
+ conditional_log_statistics()
433
+ else:
434
+ logger.error(wording.get('processing_video_failed'), __name__)
435
+ process_manager.end()
436
+ return 1
437
+ process_manager.end()
438
+ return 0
439
+
440
+
441
+ def is_process_stopping() -> bool:
442
+ if process_manager.is_stopping():
443
+ process_manager.end()
444
+ logger.info(wording.get('processing_stopped'), __name__)
445
+ return process_manager.is_pending()
facefusion/date_helper.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional, Tuple
3
+
4
+ from facefusion import wording
5
+
6
+
7
+ def get_current_date_time() -> datetime:
8
+ return datetime.now().astimezone()
9
+
10
+
11
+ def split_time_delta(time_delta : timedelta) -> Tuple[int, int, int, int]:
12
+ days, hours = divmod(time_delta.total_seconds(), 86400)
13
+ hours, minutes = divmod(hours, 3600)
14
+ minutes, seconds = divmod(minutes, 60)
15
+ return int(days), int(hours), int(minutes), int(seconds)
16
+
17
+
18
+ def describe_time_ago(date_time : datetime) -> Optional[str]:
19
+ time_ago = datetime.now(date_time.tzinfo) - date_time
20
+ days, hours, minutes, _ = split_time_delta(time_ago)
21
+
22
+ if timedelta(days = 1) < time_ago:
23
+ return wording.get('time_ago_days').format(days = days, hours = hours, minutes = minutes)
24
+ if timedelta(hours = 1) < time_ago:
25
+ return wording.get('time_ago_hours').format(hours = hours, minutes = minutes)
26
+ if timedelta(minutes = 1) < time_ago:
27
+ return wording.get('time_ago_minutes').format(minutes = minutes)
28
+ return wording.get('time_ago_now')
facefusion/download.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import ssl
4
+ import subprocess
5
+ import urllib.request
6
+ from functools import lru_cache
7
+ from typing import List, Tuple
8
+ from urllib.parse import urlparse
9
+
10
+ from tqdm import tqdm
11
+
12
+ from facefusion import logger, process_manager, state_manager, wording
13
+ from facefusion.common_helper import is_macos
14
+ from facefusion.filesystem import get_file_size, is_file, remove_file
15
+ from facefusion.hash_helper import validate_hash
16
+ from facefusion.typing import DownloadSet
17
+
18
+ if is_macos():
19
+ ssl._create_default_https_context = ssl._create_unverified_context
20
+
21
+
22
+ def conditional_download(download_directory_path : str, urls : List[str]) -> None:
23
+ for url in urls:
24
+ download_file_name = os.path.basename(urlparse(url).path)
25
+ download_file_path = os.path.join(download_directory_path, download_file_name)
26
+ initial_size = get_file_size(download_file_path)
27
+ download_size = get_download_size(url)
28
+
29
+ if initial_size < download_size:
30
+ with tqdm(total = download_size, initial = initial_size, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
31
+ subprocess.Popen([ shutil.which('curl'), '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ])
32
+ current_size = initial_size
33
+
34
+ progress.set_postfix(file = download_file_name)
35
+ while current_size < download_size:
36
+ if is_file(download_file_path):
37
+ current_size = get_file_size(download_file_path)
38
+ progress.update(current_size - progress.n)
39
+
40
+
41
+ @lru_cache(maxsize = None)
42
+ def get_download_size(url : str) -> int:
43
+ try:
44
+ response = urllib.request.urlopen(url, timeout = 10)
45
+ content_length = response.headers.get('Content-Length')
46
+ return int(content_length)
47
+ except (OSError, TypeError, ValueError):
48
+ return 0
49
+
50
+
51
+ def is_download_done(url : str, file_path : str) -> bool:
52
+ if is_file(file_path):
53
+ return get_download_size(url) == get_file_size(file_path)
54
+ return False
55
+
56
+
57
+ def conditional_download_hashes(download_directory_path : str, hashes : DownloadSet) -> bool:
58
+ hash_paths = [ hashes.get(hash_key).get('path') for hash_key in hashes.keys() ]
59
+
60
+ process_manager.check()
61
+ if not state_manager.get_item('skip_download'):
62
+ _, invalid_hash_paths = validate_hash_paths(hash_paths)
63
+ if invalid_hash_paths:
64
+ for index in hashes:
65
+ if hashes.get(index).get('path') in invalid_hash_paths:
66
+ invalid_hash_url = hashes.get(index).get('url')
67
+ conditional_download(download_directory_path, [ invalid_hash_url ])
68
+
69
+ valid_hash_paths, invalid_hash_paths = validate_hash_paths(hash_paths)
70
+ for valid_hash_path in valid_hash_paths:
71
+ valid_hash_file_name, _ = os.path.splitext(os.path.basename(valid_hash_path))
72
+ logger.debug(wording.get('validating_hash_succeed').format(hash_file_name = valid_hash_file_name), __name__)
73
+ for invalid_hash_path in invalid_hash_paths:
74
+ invalid_hash_file_name, _ = os.path.splitext(os.path.basename(invalid_hash_path))
75
+ logger.error(wording.get('validating_hash_failed').format(hash_file_name = invalid_hash_file_name), __name__)
76
+
77
+ if not invalid_hash_paths:
78
+ process_manager.end()
79
+ return not invalid_hash_paths
80
+
81
+
82
+ def conditional_download_sources(download_directory_path : str, sources : DownloadSet) -> bool:
83
+ source_paths = [ sources.get(source_key).get('path') for source_key in sources.keys() ]
84
+
85
+ process_manager.check()
86
+ if not state_manager.get_item('skip_download'):
87
+ _, invalid_source_paths = validate_source_paths(source_paths)
88
+ if invalid_source_paths:
89
+ for index in sources:
90
+ if sources.get(index).get('path') in invalid_source_paths:
91
+ invalid_source_url = sources.get(index).get('url')
92
+ conditional_download(download_directory_path, [ invalid_source_url ])
93
+
94
+ valid_source_paths, invalid_source_paths = validate_source_paths(source_paths)
95
+ for valid_source_path in valid_source_paths:
96
+ valid_source_file_name, _ = os.path.splitext(os.path.basename(valid_source_path))
97
+ logger.debug(wording.get('validating_source_succeed').format(source_file_name = valid_source_file_name), __name__)
98
+ for invalid_source_path in invalid_source_paths:
99
+ invalid_source_file_name, _ = os.path.splitext(os.path.basename(invalid_source_path))
100
+ logger.error(wording.get('validating_source_failed').format(source_file_name = invalid_source_file_name), __name__)
101
+
102
+ if remove_file(invalid_source_path):
103
+ logger.error(wording.get('deleting_corrupt_source').format(source_file_name = invalid_source_file_name), __name__)
104
+
105
+ if not invalid_source_paths:
106
+ process_manager.end()
107
+ return not invalid_source_paths
108
+
109
+
110
+ def validate_hash_paths(hash_paths : List[str]) -> Tuple[List[str], List[str]]:
111
+ valid_hash_paths = []
112
+ invalid_hash_paths = []
113
+
114
+ for hash_path in hash_paths:
115
+ if is_file(hash_path):
116
+ valid_hash_paths.append(hash_path)
117
+ else:
118
+ invalid_hash_paths.append(hash_path)
119
+ return valid_hash_paths, invalid_hash_paths
120
+
121
+
122
+ def validate_source_paths(source_paths : List[str]) -> Tuple[List[str], List[str]]:
123
+ valid_source_paths = []
124
+ invalid_source_paths = []
125
+
126
+ for source_path in source_paths:
127
+ if validate_hash(source_path):
128
+ valid_source_paths.append(source_path)
129
+ else:
130
+ invalid_source_paths.append(source_path)
131
+ return valid_source_paths, invalid_source_paths
facefusion/execution.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import xml.etree.ElementTree as ElementTree
3
+ from functools import lru_cache
4
+ from typing import Any, List
5
+
6
+ from onnxruntime import get_available_providers, set_default_logger_severity
7
+
8
+ from facefusion.choices import execution_provider_set
9
+ from facefusion.typing import ExecutionDevice, ExecutionProviderKey, ExecutionProviderSet, ValueAndUnit
10
+
11
+ set_default_logger_severity(3)
12
+
13
+
14
+ def get_execution_provider_choices() -> List[ExecutionProviderKey]:
15
+ return list(get_available_execution_provider_set().keys())
16
+
17
+
18
+ def has_execution_provider(execution_provider_key : ExecutionProviderKey) -> bool:
19
+ return execution_provider_key in get_execution_provider_choices()
20
+
21
+
22
+ def get_available_execution_provider_set() -> ExecutionProviderSet:
23
+ available_execution_providers = get_available_providers()
24
+ available_execution_provider_set : ExecutionProviderSet = {}
25
+
26
+ for execution_provider_key, execution_provider_value in execution_provider_set.items():
27
+ if execution_provider_value in available_execution_providers:
28
+ available_execution_provider_set[execution_provider_key] = execution_provider_value
29
+ return available_execution_provider_set
30
+
31
+
32
+ def create_execution_providers(execution_device_id : str, execution_provider_keys : List[ExecutionProviderKey]) -> List[Any]:
33
+ execution_providers : List[Any] = []
34
+
35
+ for execution_provider_key in execution_provider_keys:
36
+ if execution_provider_key == 'cuda':
37
+ execution_providers.append((execution_provider_set.get(execution_provider_key),
38
+ {
39
+ 'device_id': execution_device_id,
40
+ 'cudnn_conv_algo_search': 'EXHAUSTIVE' if use_exhaustive() else 'DEFAULT'
41
+ }))
42
+ if execution_provider_key == 'tensorrt':
43
+ execution_providers.append((execution_provider_set.get(execution_provider_key),
44
+ {
45
+ 'device_id': execution_device_id,
46
+ 'trt_engine_cache_enable': True,
47
+ 'trt_engine_cache_path': '.caches',
48
+ 'trt_timing_cache_enable': True,
49
+ 'trt_timing_cache_path': '.caches',
50
+ 'trt_builder_optimization_level': 5
51
+ }))
52
+ if execution_provider_key == 'openvino':
53
+ execution_providers.append((execution_provider_set.get(execution_provider_key),
54
+ {
55
+ 'device_type': 'GPU.' + execution_device_id,
56
+ 'precision': 'FP32'
57
+ }))
58
+ if execution_provider_key in [ 'directml', 'rocm' ]:
59
+ execution_providers.append((execution_provider_set.get(execution_provider_key),
60
+ {
61
+ 'device_id': execution_device_id
62
+ }))
63
+ if execution_provider_key == 'coreml':
64
+ execution_providers.append(execution_provider_set.get(execution_provider_key))
65
+
66
+ if 'cpu' in execution_provider_keys:
67
+ execution_providers.append(execution_provider_set.get('cpu'))
68
+
69
+ return execution_providers
70
+
71
+
72
+ def use_exhaustive() -> bool:
73
+ execution_devices = detect_static_execution_devices()
74
+ product_names = ('GeForce GTX 1630', 'GeForce GTX 1650', 'GeForce GTX 1660')
75
+
76
+ return any(execution_device.get('product').get('name').startswith(product_names) for execution_device in execution_devices)
77
+
78
+
79
+ def run_nvidia_smi() -> subprocess.Popen[bytes]:
80
+ commands = [ 'nvidia-smi', '--query', '--xml-format' ]
81
+ return subprocess.Popen(commands, stdout = subprocess.PIPE)
82
+
83
+
84
+ @lru_cache(maxsize = None)
85
+ def detect_static_execution_devices() -> List[ExecutionDevice]:
86
+ return detect_execution_devices()
87
+
88
+
89
+ def detect_execution_devices() -> List[ExecutionDevice]:
90
+ execution_devices : List[ExecutionDevice] = []
91
+
92
+ try:
93
+ output, _ = run_nvidia_smi().communicate()
94
+ root_element = ElementTree.fromstring(output)
95
+ except Exception:
96
+ root_element = ElementTree.Element('xml')
97
+
98
+ for gpu_element in root_element.findall('gpu'):
99
+ execution_devices.append(
100
+ {
101
+ 'driver_version': root_element.find('driver_version').text,
102
+ 'framework':
103
+ {
104
+ 'name': 'CUDA',
105
+ 'version': root_element.find('cuda_version').text
106
+ },
107
+ 'product':
108
+ {
109
+ 'vendor': 'NVIDIA',
110
+ 'name': gpu_element.find('product_name').text.replace('NVIDIA ', '')
111
+ },
112
+ 'video_memory':
113
+ {
114
+ 'total': create_value_and_unit(gpu_element.find('fb_memory_usage/total').text),
115
+ 'free': create_value_and_unit(gpu_element.find('fb_memory_usage/free').text)
116
+ },
117
+ 'utilization':
118
+ {
119
+ 'gpu': create_value_and_unit(gpu_element.find('utilization/gpu_util').text),
120
+ 'memory': create_value_and_unit(gpu_element.find('utilization/memory_util').text)
121
+ }
122
+ })
123
+ return execution_devices
124
+
125
+
126
+ def create_value_and_unit(text : str) -> ValueAndUnit:
127
+ value, unit = text.split()
128
+ value_and_unit : ValueAndUnit =\
129
+ {
130
+ 'value': int(value),
131
+ 'unit': str(unit)
132
+ }
133
+
134
+ return value_and_unit
facefusion/exit_helper.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from time import sleep
3
+
4
+ from facefusion import process_manager, state_manager
5
+ from facefusion.temp_helper import clear_temp_directory
6
+ from facefusion.typing import ErrorCode
7
+
8
+
9
+ def hard_exit(error_code : ErrorCode) -> None:
10
+ sys.exit(error_code)
11
+
12
+
13
+ def conditional_exit(error_code : ErrorCode) -> None:
14
+ if state_manager.get_item('command') == 'headless-run':
15
+ hard_exit(error_code)
16
+
17
+
18
+ def graceful_exit(error_code : ErrorCode) -> None:
19
+ process_manager.stop()
20
+ while process_manager.is_processing():
21
+ sleep(0.5)
22
+ if state_manager.get_item('target_path'):
23
+ clear_temp_directory(state_manager.get_item('target_path'))
24
+ hard_exit(error_code)
facefusion/face_analyser.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ import numpy
4
+
5
+ from facefusion import state_manager
6
+ from facefusion.common_helper import get_first
7
+ from facefusion.face_classifier import classify_face
8
+ from facefusion.face_detector import detect_faces, detect_rotated_faces
9
+ from facefusion.face_helper import apply_nms, convert_to_face_landmark_5, estimate_face_angle, get_nms_threshold
10
+ from facefusion.face_landmarker import detect_face_landmarks, estimate_face_landmark_68_5
11
+ from facefusion.face_recognizer import calc_embedding
12
+ from facefusion.face_store import get_static_faces, set_static_faces
13
+ from facefusion.typing import BoundingBox, Face, FaceLandmark5, FaceLandmarkSet, FaceScoreSet, Score, VisionFrame
14
+
15
+
16
+ def create_faces(vision_frame : VisionFrame, bounding_boxes : List[BoundingBox], face_scores : List[Score], face_landmarks_5 : List[FaceLandmark5]) -> List[Face]:
17
+ faces = []
18
+ nms_threshold = get_nms_threshold(state_manager.get_item('face_detector_model'), state_manager.get_item('face_detector_angles'))
19
+ keep_indices = apply_nms(bounding_boxes, face_scores, state_manager.get_item('face_detector_score'), nms_threshold)
20
+
21
+ for index in keep_indices:
22
+ bounding_box = bounding_boxes[index]
23
+ face_score = face_scores[index]
24
+ face_landmark_5 = face_landmarks_5[index]
25
+ face_landmark_5_68 = face_landmark_5
26
+ face_landmark_68_5 = estimate_face_landmark_68_5(face_landmark_5_68)
27
+ face_landmark_68 = face_landmark_68_5
28
+ face_landmark_score_68 = 0.0
29
+ face_angle = estimate_face_angle(face_landmark_68_5)
30
+
31
+ if state_manager.get_item('face_landmarker_score') > 0:
32
+ face_landmark_68, face_landmark_score_68 = detect_face_landmarks(vision_frame, bounding_box, face_angle)
33
+ if face_landmark_score_68 > state_manager.get_item('face_landmarker_score'):
34
+ face_landmark_5_68 = convert_to_face_landmark_5(face_landmark_68)
35
+
36
+ face_landmark_set : FaceLandmarkSet =\
37
+ {
38
+ '5': face_landmark_5,
39
+ '5/68': face_landmark_5_68,
40
+ '68': face_landmark_68,
41
+ '68/5': face_landmark_68_5
42
+ }
43
+ face_score_set : FaceScoreSet =\
44
+ {
45
+ 'detector': face_score,
46
+ 'landmarker': face_landmark_score_68
47
+ }
48
+ embedding, normed_embedding = calc_embedding(vision_frame, face_landmark_set.get('5/68'))
49
+ gender, age, race = classify_face(vision_frame, face_landmark_set.get('5/68'))
50
+ faces.append(Face(
51
+ bounding_box = bounding_box,
52
+ score_set = face_score_set,
53
+ landmark_set = face_landmark_set,
54
+ angle = face_angle,
55
+ embedding = embedding,
56
+ normed_embedding = normed_embedding,
57
+ gender = gender,
58
+ age = age,
59
+ race = race
60
+ ))
61
+ return faces
62
+
63
+
64
+ def get_one_face(faces : List[Face], position : int = 0) -> Optional[Face]:
65
+ if faces:
66
+ position = min(position, len(faces) - 1)
67
+ return faces[position]
68
+ return None
69
+
70
+
71
+ def get_average_face(faces : List[Face]) -> Optional[Face]:
72
+ embeddings = []
73
+ normed_embeddings = []
74
+
75
+ if faces:
76
+ first_face = get_first(faces)
77
+
78
+ for face in faces:
79
+ embeddings.append(face.embedding)
80
+ normed_embeddings.append(face.normed_embedding)
81
+
82
+ return Face(
83
+ bounding_box = first_face.bounding_box,
84
+ score_set = first_face.score_set,
85
+ landmark_set = first_face.landmark_set,
86
+ angle = first_face.angle,
87
+ embedding = numpy.mean(embeddings, axis = 0),
88
+ normed_embedding = numpy.mean(normed_embeddings, axis = 0),
89
+ gender = first_face.gender,
90
+ age = first_face.age,
91
+ race = first_face.race
92
+ )
93
+ return None
94
+
95
+
96
+ def get_many_faces(vision_frames : List[VisionFrame]) -> List[Face]:
97
+ many_faces : List[Face] = []
98
+
99
+ for vision_frame in vision_frames:
100
+ if numpy.any(vision_frame):
101
+ static_faces = get_static_faces(vision_frame)
102
+ if static_faces:
103
+ many_faces.extend(static_faces)
104
+ else:
105
+ all_bounding_boxes = []
106
+ all_face_scores = []
107
+ all_face_landmarks_5 = []
108
+
109
+ for face_detector_angle in state_manager.get_item('face_detector_angles'):
110
+ if face_detector_angle == 0:
111
+ bounding_boxes, face_scores, face_landmarks_5 = detect_faces(vision_frame)
112
+ else:
113
+ bounding_boxes, face_scores, face_landmarks_5 = detect_rotated_faces(vision_frame, face_detector_angle)
114
+ all_bounding_boxes.extend(bounding_boxes)
115
+ all_face_scores.extend(face_scores)
116
+ all_face_landmarks_5.extend(face_landmarks_5)
117
+
118
+ if all_bounding_boxes and all_face_scores and all_face_landmarks_5 and state_manager.get_item('face_detector_score') > 0:
119
+ faces = create_faces(vision_frame, all_bounding_boxes, all_face_scores, all_face_landmarks_5)
120
+
121
+ if faces:
122
+ many_faces.extend(faces)
123
+ set_static_faces(vision_frame, faces)
124
+ return many_faces
facefusion/face_classifier.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+
3
+ import numpy
4
+
5
+ from facefusion import inference_manager
6
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
7
+ from facefusion.face_helper import warp_face_by_face_landmark_5
8
+ from facefusion.filesystem import resolve_relative_path
9
+ from facefusion.thread_helper import conditional_thread_semaphore
10
+ from facefusion.typing import Age, FaceLandmark5, Gender, InferencePool, ModelOptions, ModelSet, Race, VisionFrame
11
+
12
+ MODEL_SET : ModelSet =\
13
+ {
14
+ 'fairface':
15
+ {
16
+ 'hashes':
17
+ {
18
+ 'face_classifier':
19
+ {
20
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/fairface.hash',
21
+ 'path': resolve_relative_path('../.assets/models/fairface.hash')
22
+ }
23
+ },
24
+ 'sources':
25
+ {
26
+ 'face_classifier':
27
+ {
28
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/fairface.onnx',
29
+ 'path': resolve_relative_path('../.assets/models/fairface.onnx')
30
+ }
31
+ },
32
+ 'template': 'arcface_112_v2',
33
+ 'size': (224, 224),
34
+ 'mean': [ 0.485, 0.456, 0.406 ],
35
+ 'standard_deviation': [ 0.229, 0.224, 0.225 ]
36
+ }
37
+ }
38
+
39
+
40
+ def get_inference_pool() -> InferencePool:
41
+ model_sources = get_model_options().get('sources')
42
+ return inference_manager.get_inference_pool(__name__, model_sources)
43
+
44
+
45
+ def clear_inference_pool() -> None:
46
+ inference_manager.clear_inference_pool(__name__)
47
+
48
+
49
+ def get_model_options() -> ModelOptions:
50
+ return MODEL_SET.get('fairface')
51
+
52
+
53
+ def pre_check() -> bool:
54
+ download_directory_path = resolve_relative_path('../.assets/models')
55
+ model_hashes = get_model_options().get('hashes')
56
+ model_sources = get_model_options().get('sources')
57
+
58
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
59
+
60
+
61
+ def classify_face(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5) -> Tuple[Gender, Age, Race]:
62
+ model_template = get_model_options().get('template')
63
+ model_size = get_model_options().get('size')
64
+ model_mean = get_model_options().get('mean')
65
+ model_standard_deviation = get_model_options().get('standard_deviation')
66
+ crop_vision_frame, _ = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_template, model_size)
67
+ crop_vision_frame = crop_vision_frame.astype(numpy.float32)[:, :, ::-1] / 255
68
+ crop_vision_frame -= model_mean
69
+ crop_vision_frame /= model_standard_deviation
70
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1)
71
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
72
+ gender_id, age_id, race_id = forward(crop_vision_frame)
73
+ gender = categorize_gender(gender_id[0])
74
+ age = categorize_age(age_id[0])
75
+ race = categorize_race(race_id[0])
76
+ return gender, age, race
77
+
78
+
79
+ def forward(crop_vision_frame : VisionFrame) -> Tuple[List[int], List[int], List[int]]:
80
+ face_classifier = get_inference_pool().get('face_classifier')
81
+
82
+ with conditional_thread_semaphore():
83
+ race_id, gender_id, age_id = face_classifier.run(None,
84
+ {
85
+ 'input': crop_vision_frame
86
+ })
87
+
88
+ return gender_id, age_id, race_id
89
+
90
+
91
+ def categorize_gender(gender_id : int) -> Gender:
92
+ if gender_id == 1:
93
+ return 'female'
94
+ return 'male'
95
+
96
+
97
+ def categorize_age(age_id : int) -> Age:
98
+ if age_id == 0:
99
+ return range(0, 2)
100
+ if age_id == 1:
101
+ return range(3, 9)
102
+ if age_id == 2:
103
+ return range(10, 19)
104
+ if age_id == 3:
105
+ return range(20, 29)
106
+ if age_id == 4:
107
+ return range(30, 39)
108
+ if age_id == 5:
109
+ return range(40, 49)
110
+ if age_id == 6:
111
+ return range(50, 59)
112
+ if age_id == 7:
113
+ return range(60, 69)
114
+ return range(70, 100)
115
+
116
+
117
+ def categorize_race(race_id : int) -> Race:
118
+ if race_id == 1:
119
+ return 'black'
120
+ if race_id == 2:
121
+ return 'latino'
122
+ if race_id == 3 or race_id == 4:
123
+ return 'asian'
124
+ if race_id == 5:
125
+ return 'indian'
126
+ if race_id == 6:
127
+ return 'arabic'
128
+ return 'white'
facefusion/face_detector.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+
3
+ import cv2
4
+ import numpy
5
+
6
+ from facefusion import inference_manager, state_manager
7
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
8
+ from facefusion.face_helper import create_rotated_matrix_and_size, create_static_anchors, distance_to_bounding_box, distance_to_face_landmark_5, normalize_bounding_box, transform_bounding_box, transform_points
9
+ from facefusion.filesystem import resolve_relative_path
10
+ from facefusion.thread_helper import thread_semaphore
11
+ from facefusion.typing import Angle, BoundingBox, Detection, DownloadSet, FaceLandmark5, InferencePool, ModelSet, Score, VisionFrame
12
+ from facefusion.vision import resize_frame_resolution, unpack_resolution
13
+
14
+ MODEL_SET : ModelSet =\
15
+ {
16
+ 'retinaface':
17
+ {
18
+ 'hashes':
19
+ {
20
+ 'retinaface':
21
+ {
22
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/retinaface_10g.hash',
23
+ 'path': resolve_relative_path('../.assets/models/retinaface_10g.hash')
24
+ }
25
+ },
26
+ 'sources':
27
+ {
28
+ 'retinaface':
29
+ {
30
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/retinaface_10g.onnx',
31
+ 'path': resolve_relative_path('../.assets/models/retinaface_10g.onnx')
32
+ }
33
+ }
34
+ },
35
+ 'scrfd':
36
+ {
37
+ 'hashes':
38
+ {
39
+ 'scrfd':
40
+ {
41
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/scrfd_2.5g.hash',
42
+ 'path': resolve_relative_path('../.assets/models/scrfd_2.5g.hash')
43
+ }
44
+ },
45
+ 'sources':
46
+ {
47
+ 'scrfd':
48
+ {
49
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/scrfd_2.5g.onnx',
50
+ 'path': resolve_relative_path('../.assets/models/scrfd_2.5g.onnx')
51
+ }
52
+ }
53
+ },
54
+ 'yoloface':
55
+ {
56
+ 'hashes':
57
+ {
58
+ 'yoloface':
59
+ {
60
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/yoloface_8n.hash',
61
+ 'path': resolve_relative_path('../.assets/models/yoloface_8n.hash')
62
+ }
63
+ },
64
+ 'sources':
65
+ {
66
+ 'yoloface':
67
+ {
68
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/yoloface_8n.onnx',
69
+ 'path': resolve_relative_path('../.assets/models/yoloface_8n.onnx')
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+
76
+ def get_inference_pool() -> InferencePool:
77
+ _, model_sources = collect_model_downloads()
78
+ model_context = __name__ + '.' + state_manager.get_item('face_detector_model')
79
+ return inference_manager.get_inference_pool(model_context, model_sources)
80
+
81
+
82
+ def clear_inference_pool() -> None:
83
+ model_context = __name__ + '.' + state_manager.get_item('face_detector_model')
84
+ inference_manager.clear_inference_pool(model_context)
85
+
86
+
87
+ def collect_model_downloads() -> Tuple[DownloadSet, DownloadSet]:
88
+ model_hashes = {}
89
+ model_sources = {}
90
+
91
+ if state_manager.get_item('face_detector_model') in [ 'many', 'retinaface' ]:
92
+ model_hashes['retinaface'] = MODEL_SET.get('retinaface').get('hashes').get('retinaface')
93
+ model_sources['retinaface'] = MODEL_SET.get('retinaface').get('sources').get('retinaface')
94
+ if state_manager.get_item('face_detector_model') in [ 'many', 'scrfd' ]:
95
+ model_hashes['scrfd'] = MODEL_SET.get('scrfd').get('hashes').get('scrfd')
96
+ model_sources['scrfd'] = MODEL_SET.get('scrfd').get('sources').get('scrfd')
97
+ if state_manager.get_item('face_detector_model') in [ 'many', 'yoloface' ]:
98
+ model_hashes['yoloface'] = MODEL_SET.get('yoloface').get('hashes').get('yoloface')
99
+ model_sources['yoloface'] = MODEL_SET.get('yoloface').get('sources').get('yoloface')
100
+ return model_hashes, model_sources
101
+
102
+
103
+ def pre_check() -> bool:
104
+ download_directory_path = resolve_relative_path('../.assets/models')
105
+ model_hashes, model_sources = collect_model_downloads()
106
+
107
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
108
+
109
+
110
+ def detect_faces(vision_frame : VisionFrame) -> Tuple[List[BoundingBox], List[Score], List[FaceLandmark5]]:
111
+ all_bounding_boxes : List[BoundingBox] = []
112
+ all_face_scores : List[Score] = []
113
+ all_face_landmarks_5 : List[FaceLandmark5] = []
114
+
115
+ if state_manager.get_item('face_detector_model') in [ 'many', 'retinaface' ]:
116
+ bounding_boxes, face_scores, face_landmarks_5 = detect_with_retinaface(vision_frame, state_manager.get_item('face_detector_size'))
117
+ all_bounding_boxes.extend(bounding_boxes)
118
+ all_face_scores.extend(face_scores)
119
+ all_face_landmarks_5.extend(face_landmarks_5)
120
+
121
+ if state_manager.get_item('face_detector_model') in [ 'many', 'scrfd' ]:
122
+ bounding_boxes, face_scores, face_landmarks_5 = detect_with_scrfd(vision_frame, state_manager.get_item('face_detector_size'))
123
+ all_bounding_boxes.extend(bounding_boxes)
124
+ all_face_scores.extend(face_scores)
125
+ all_face_landmarks_5.extend(face_landmarks_5)
126
+
127
+ if state_manager.get_item('face_detector_model') in [ 'many', 'yoloface' ]:
128
+ bounding_boxes, face_scores, face_landmarks_5 = detect_with_yoloface(vision_frame, state_manager.get_item('face_detector_size'))
129
+ all_bounding_boxes.extend(bounding_boxes)
130
+ all_face_scores.extend(face_scores)
131
+ all_face_landmarks_5.extend(face_landmarks_5)
132
+
133
+ all_bounding_boxes = [ normalize_bounding_box(all_bounding_box) for all_bounding_box in all_bounding_boxes ]
134
+ return all_bounding_boxes, all_face_scores, all_face_landmarks_5
135
+
136
+
137
+ def detect_rotated_faces(vision_frame : VisionFrame, angle : Angle) -> Tuple[List[BoundingBox], List[Score], List[FaceLandmark5]]:
138
+ rotated_matrix, rotated_size = create_rotated_matrix_and_size(angle, vision_frame.shape[:2][::-1])
139
+ rotated_vision_frame = cv2.warpAffine(vision_frame, rotated_matrix, rotated_size)
140
+ rotated_inverse_matrix = cv2.invertAffineTransform(rotated_matrix)
141
+ bounding_boxes, face_scores, face_landmarks_5 = detect_faces(rotated_vision_frame)
142
+ bounding_boxes = [ transform_bounding_box(bounding_box, rotated_inverse_matrix) for bounding_box in bounding_boxes ]
143
+ face_landmarks_5 = [ transform_points(face_landmark_5, rotated_inverse_matrix) for face_landmark_5 in face_landmarks_5 ]
144
+ return bounding_boxes, face_scores, face_landmarks_5
145
+
146
+
147
+ def detect_with_retinaface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[Score], List[FaceLandmark5]]:
148
+ bounding_boxes = []
149
+ face_scores = []
150
+ face_landmarks_5 = []
151
+ feature_strides = [ 8, 16, 32 ]
152
+ feature_map_channel = 3
153
+ anchor_total = 2
154
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
155
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
156
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
157
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
158
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
159
+ detection = forward_with_retinaface(detect_vision_frame)
160
+
161
+ for index, feature_stride in enumerate(feature_strides):
162
+ keep_indices = numpy.where(detection[index] >= state_manager.get_item('face_detector_score'))[0]
163
+
164
+ if numpy.any(keep_indices):
165
+ stride_height = face_detector_height // feature_stride
166
+ stride_width = face_detector_width // feature_stride
167
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
168
+ bounding_box_raw = detection[index + feature_map_channel] * feature_stride
169
+ face_landmark_5_raw = detection[index + feature_map_channel * 2] * feature_stride
170
+
171
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
172
+ bounding_boxes.append(numpy.array(
173
+ [
174
+ bounding_box[0] * ratio_width,
175
+ bounding_box[1] * ratio_height,
176
+ bounding_box[2] * ratio_width,
177
+ bounding_box[3] * ratio_height,
178
+ ]))
179
+
180
+ for score in detection[index][keep_indices]:
181
+ face_scores.append(score[0])
182
+
183
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
184
+ face_landmarks_5.append(face_landmark_5 * [ ratio_width, ratio_height ])
185
+
186
+ return bounding_boxes, face_scores, face_landmarks_5
187
+
188
+
189
+ def detect_with_scrfd(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[Score], List[FaceLandmark5]]:
190
+ bounding_boxes = []
191
+ face_scores = []
192
+ face_landmarks_5 = []
193
+ feature_strides = [ 8, 16, 32 ]
194
+ feature_map_channel = 3
195
+ anchor_total = 2
196
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
197
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
198
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
199
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
200
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
201
+ detection = forward_with_scrfd(detect_vision_frame)
202
+
203
+ for index, feature_stride in enumerate(feature_strides):
204
+ keep_indices = numpy.where(detection[index] >= state_manager.get_item('face_detector_score'))[0]
205
+
206
+ if numpy.any(keep_indices):
207
+ stride_height = face_detector_height // feature_stride
208
+ stride_width = face_detector_width // feature_stride
209
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
210
+ bounding_box_raw = detection[index + feature_map_channel] * feature_stride
211
+ face_landmark_5_raw = detection[index + feature_map_channel * 2] * feature_stride
212
+
213
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
214
+ bounding_boxes.append(numpy.array(
215
+ [
216
+ bounding_box[0] * ratio_width,
217
+ bounding_box[1] * ratio_height,
218
+ bounding_box[2] * ratio_width,
219
+ bounding_box[3] * ratio_height,
220
+ ]))
221
+
222
+ for score in detection[index][keep_indices]:
223
+ face_scores.append(score[0])
224
+
225
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
226
+ face_landmarks_5.append(face_landmark_5 * [ ratio_width, ratio_height ])
227
+
228
+ return bounding_boxes, face_scores, face_landmarks_5
229
+
230
+
231
+ def detect_with_yoloface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[Score], List[FaceLandmark5]]:
232
+ bounding_boxes = []
233
+ face_scores = []
234
+ face_landmarks_5 = []
235
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
236
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
237
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
238
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
239
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
240
+ detection = forward_with_yoloface(detect_vision_frame)
241
+ detection = numpy.squeeze(detection).T
242
+ bounding_box_raw, score_raw, face_landmark_5_raw = numpy.split(detection, [ 4, 5 ], axis = 1)
243
+ keep_indices = numpy.where(score_raw > state_manager.get_item('face_detector_score'))[0]
244
+
245
+ if numpy.any(keep_indices):
246
+ bounding_box_raw, face_landmark_5_raw, score_raw = bounding_box_raw[keep_indices], face_landmark_5_raw[keep_indices], score_raw[keep_indices]
247
+
248
+ for bounding_box in bounding_box_raw:
249
+ bounding_boxes.append(numpy.array(
250
+ [
251
+ (bounding_box[0] - bounding_box[2] / 2) * ratio_width,
252
+ (bounding_box[1] - bounding_box[3] / 2) * ratio_height,
253
+ (bounding_box[0] + bounding_box[2] / 2) * ratio_width,
254
+ (bounding_box[1] + bounding_box[3] / 2) * ratio_height,
255
+ ]))
256
+
257
+ face_scores = score_raw.ravel().tolist()
258
+ face_landmark_5_raw[:, 0::3] = (face_landmark_5_raw[:, 0::3]) * ratio_width
259
+ face_landmark_5_raw[:, 1::3] = (face_landmark_5_raw[:, 1::3]) * ratio_height
260
+
261
+ for face_landmark_5 in face_landmark_5_raw:
262
+ face_landmarks_5.append(numpy.array(face_landmark_5.reshape(-1, 3)[:, :2]))
263
+
264
+ return bounding_boxes, face_scores, face_landmarks_5
265
+
266
+
267
+ def forward_with_retinaface(detect_vision_frame : VisionFrame) -> Detection:
268
+ face_detector = get_inference_pool().get('retinaface')
269
+
270
+ with thread_semaphore():
271
+ detection = face_detector.run(None,
272
+ {
273
+ 'input': detect_vision_frame
274
+ })
275
+
276
+ return detection
277
+
278
+
279
+ def forward_with_scrfd(detect_vision_frame : VisionFrame) -> Detection:
280
+ face_detector = get_inference_pool().get('scrfd')
281
+
282
+ with thread_semaphore():
283
+ detection = face_detector.run(None,
284
+ {
285
+ 'input': detect_vision_frame
286
+ })
287
+
288
+ return detection
289
+
290
+
291
+ def forward_with_yoloface(detect_vision_frame : VisionFrame) -> Detection:
292
+ face_detector = get_inference_pool().get('yoloface')
293
+
294
+ with thread_semaphore():
295
+ detection = face_detector.run(None,
296
+ {
297
+ 'input': detect_vision_frame
298
+ })
299
+
300
+ return detection
301
+
302
+
303
+ def prepare_detect_frame(temp_vision_frame : VisionFrame, face_detector_size : str) -> VisionFrame:
304
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
305
+ detect_vision_frame = numpy.zeros((face_detector_height, face_detector_width, 3))
306
+ detect_vision_frame[:temp_vision_frame.shape[0], :temp_vision_frame.shape[1], :] = temp_vision_frame
307
+ detect_vision_frame = (detect_vision_frame - 127.5) / 128.0
308
+ detect_vision_frame = numpy.expand_dims(detect_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
309
+ return detect_vision_frame
facefusion/face_helper.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ from typing import List, Sequence, Tuple
3
+
4
+ import cv2
5
+ import numpy
6
+ from cv2.typing import Size
7
+
8
+ from facefusion.typing import Anchors, Angle, BoundingBox, Distance, FaceDetectorModel, FaceLandmark5, FaceLandmark68, Mask, Matrix, Points, Scale, Score, Translation, VisionFrame, WarpTemplate, WarpTemplateSet
9
+
10
+ WARP_TEMPLATES : WarpTemplateSet =\
11
+ {
12
+ 'arcface_112_v1': numpy.array(
13
+ [
14
+ [ 0.35473214, 0.45658929 ],
15
+ [ 0.64526786, 0.45658929 ],
16
+ [ 0.50000000, 0.61154464 ],
17
+ [ 0.37913393, 0.77687500 ],
18
+ [ 0.62086607, 0.77687500 ]
19
+ ]),
20
+ 'arcface_112_v2': numpy.array(
21
+ [
22
+ [ 0.34191607, 0.46157411 ],
23
+ [ 0.65653393, 0.45983393 ],
24
+ [ 0.50022500, 0.64050536 ],
25
+ [ 0.37097589, 0.82469196 ],
26
+ [ 0.63151696, 0.82325089 ]
27
+ ]),
28
+ 'arcface_128_v2': numpy.array(
29
+ [
30
+ [ 0.36167656, 0.40387734 ],
31
+ [ 0.63696719, 0.40235469 ],
32
+ [ 0.50019687, 0.56044219 ],
33
+ [ 0.38710391, 0.72160547 ],
34
+ [ 0.61507734, 0.72034453 ]
35
+ ]),
36
+ 'ffhq_512': numpy.array(
37
+ [
38
+ [ 0.37691676, 0.46864664 ],
39
+ [ 0.62285697, 0.46912813 ],
40
+ [ 0.50123859, 0.61331904 ],
41
+ [ 0.39308822, 0.72541100 ],
42
+ [ 0.61150205, 0.72490465 ]
43
+ ])
44
+ }
45
+
46
+
47
+ def estimate_matrix_by_face_landmark_5(face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Matrix:
48
+ normed_warp_template = WARP_TEMPLATES.get(warp_template) * crop_size
49
+ affine_matrix = cv2.estimateAffinePartial2D(face_landmark_5, normed_warp_template, method = cv2.RANSAC, ransacReprojThreshold = 100)[0]
50
+ return affine_matrix
51
+
52
+
53
+ def warp_face_by_face_landmark_5(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
54
+ affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, warp_template, crop_size)
55
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, borderMode = cv2.BORDER_REPLICATE, flags = cv2.INTER_AREA)
56
+ return crop_vision_frame, affine_matrix
57
+
58
+
59
+ def warp_face_by_bounding_box(temp_vision_frame : VisionFrame, bounding_box : BoundingBox, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
60
+ source_points = numpy.array([ [ bounding_box[0], bounding_box[1] ], [bounding_box[2], bounding_box[1] ], [ bounding_box[0], bounding_box[3] ] ]).astype(numpy.float32)
61
+ target_points = numpy.array([ [ 0, 0 ], [ crop_size[0], 0 ], [ 0, crop_size[1] ] ]).astype(numpy.float32)
62
+ affine_matrix = cv2.getAffineTransform(source_points, target_points)
63
+ if bounding_box[2] - bounding_box[0] > crop_size[0] or bounding_box[3] - bounding_box[1] > crop_size[1]:
64
+ interpolation_method = cv2.INTER_AREA
65
+ else:
66
+ interpolation_method = cv2.INTER_LINEAR
67
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, flags = interpolation_method)
68
+ return crop_vision_frame, affine_matrix
69
+
70
+
71
+ def warp_face_by_translation(temp_vision_frame : VisionFrame, translation : Translation, scale : float, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
72
+ affine_matrix = numpy.array([ [ scale, 0, translation[0] ], [ 0, scale, translation[1] ] ])
73
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size)
74
+ return crop_vision_frame, affine_matrix
75
+
76
+
77
+ def paste_back(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, crop_mask : Mask, affine_matrix : Matrix) -> VisionFrame:
78
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
79
+ temp_size = temp_vision_frame.shape[:2][::-1]
80
+ inverse_mask = cv2.warpAffine(crop_mask, inverse_matrix, temp_size).clip(0, 1)
81
+ inverse_vision_frame = cv2.warpAffine(crop_vision_frame, inverse_matrix, temp_size, borderMode = cv2.BORDER_REPLICATE)
82
+ paste_vision_frame = temp_vision_frame.copy()
83
+ paste_vision_frame[:, :, 0] = inverse_mask * inverse_vision_frame[:, :, 0] + (1 - inverse_mask) * temp_vision_frame[:, :, 0]
84
+ paste_vision_frame[:, :, 1] = inverse_mask * inverse_vision_frame[:, :, 1] + (1 - inverse_mask) * temp_vision_frame[:, :, 1]
85
+ paste_vision_frame[:, :, 2] = inverse_mask * inverse_vision_frame[:, :, 2] + (1 - inverse_mask) * temp_vision_frame[:, :, 2]
86
+ return paste_vision_frame
87
+
88
+
89
+ @lru_cache(maxsize = None)
90
+ def create_static_anchors(feature_stride : int, anchor_total : int, stride_height : int, stride_width : int) -> Anchors:
91
+ y, x = numpy.mgrid[:stride_height, :stride_width][::-1]
92
+ anchors = numpy.stack((y, x), axis = -1)
93
+ anchors = (anchors * feature_stride).reshape((-1, 2))
94
+ anchors = numpy.stack([ anchors ] * anchor_total, axis = 1).reshape((-1, 2))
95
+ return anchors
96
+
97
+
98
+ def create_rotated_matrix_and_size(angle : Angle, size : Size) -> Tuple[Matrix, Size]:
99
+ rotated_matrix = cv2.getRotationMatrix2D((size[0] / 2, size[1] / 2), angle, 1)
100
+ rotated_size = numpy.dot(numpy.abs(rotated_matrix[:, :2]), size)
101
+ rotated_matrix[:, -1] += (rotated_size - size) * 0.5 #type:ignore[misc]
102
+ rotated_size = int(rotated_size[0]), int(rotated_size[1])
103
+ return rotated_matrix, rotated_size
104
+
105
+
106
+ def create_bounding_box(face_landmark_68 : FaceLandmark68) -> BoundingBox:
107
+ min_x, min_y = numpy.min(face_landmark_68, axis = 0)
108
+ max_x, max_y = numpy.max(face_landmark_68, axis = 0)
109
+ bounding_box = normalize_bounding_box(numpy.array([ min_x, min_y, max_x, max_y ]))
110
+ return bounding_box
111
+
112
+
113
+ def normalize_bounding_box(bounding_box : BoundingBox) -> BoundingBox:
114
+ x1, y1, x2, y2 = bounding_box
115
+ x1, x2 = sorted([ x1, x2 ])
116
+ y1, y2 = sorted([ y1, y2 ])
117
+ return numpy.array([ x1, y1, x2, y2 ])
118
+
119
+
120
+ def transform_points(points : Points, matrix : Matrix) -> Points:
121
+ points = points.reshape(-1, 1, 2)
122
+ points = cv2.transform(points, matrix) #type:ignore[assignment]
123
+ points = points.reshape(-1, 2)
124
+ return points
125
+
126
+
127
+ def transform_bounding_box(bounding_box : BoundingBox, matrix : Matrix) -> BoundingBox:
128
+ points = numpy.array(
129
+ [
130
+ [ bounding_box[0], bounding_box[1] ],
131
+ [ bounding_box[2], bounding_box[1] ],
132
+ [ bounding_box[2], bounding_box[3] ],
133
+ [ bounding_box[0], bounding_box[3] ]
134
+ ])
135
+ points = transform_points(points, matrix)
136
+ x1, y1 = numpy.min(points, axis = 0)
137
+ x2, y2 = numpy.max(points, axis = 0)
138
+ return normalize_bounding_box(numpy.array([ x1, y1, x2, y2 ]))
139
+
140
+
141
+ def distance_to_bounding_box(points : Points, distance : Distance) -> BoundingBox:
142
+ x1 = points[:, 0] - distance[:, 0]
143
+ y1 = points[:, 1] - distance[:, 1]
144
+ x2 = points[:, 0] + distance[:, 2]
145
+ y2 = points[:, 1] + distance[:, 3]
146
+ bounding_box = numpy.column_stack([ x1, y1, x2, y2 ])
147
+ return bounding_box
148
+
149
+
150
+ def distance_to_face_landmark_5(points : Points, distance : Distance) -> FaceLandmark5:
151
+ x = points[:, 0::2] + distance[:, 0::2]
152
+ y = points[:, 1::2] + distance[:, 1::2]
153
+ face_landmark_5 = numpy.stack((x, y), axis = -1)
154
+ return face_landmark_5
155
+
156
+
157
+ def scale_face_landmark_5(face_landmark_5 : FaceLandmark5, scale : Scale) -> FaceLandmark5:
158
+ face_landmark_5_scale = face_landmark_5 - face_landmark_5[2]
159
+ face_landmark_5_scale *= scale
160
+ face_landmark_5_scale += face_landmark_5[2]
161
+ return face_landmark_5_scale
162
+
163
+
164
+ def convert_to_face_landmark_5(face_landmark_68 : FaceLandmark68) -> FaceLandmark5:
165
+ face_landmark_5 = numpy.array(
166
+ [
167
+ numpy.mean(face_landmark_68[36:42], axis = 0),
168
+ numpy.mean(face_landmark_68[42:48], axis = 0),
169
+ face_landmark_68[30],
170
+ face_landmark_68[48],
171
+ face_landmark_68[54]
172
+ ])
173
+ return face_landmark_5
174
+
175
+
176
+ def estimate_face_angle(face_landmark_68 : FaceLandmark68) -> Angle:
177
+ x1, y1 = face_landmark_68[0]
178
+ x2, y2 = face_landmark_68[16]
179
+ theta = numpy.arctan2(y2 - y1, x2 - x1)
180
+ theta = numpy.degrees(theta) % 360
181
+ angles = numpy.linspace(0, 360, 5)
182
+ index = numpy.argmin(numpy.abs(angles - theta))
183
+ face_angle = int(angles[index] % 360)
184
+ return face_angle
185
+
186
+
187
+ def apply_nms(bounding_boxes : List[BoundingBox], face_scores : List[Score], score_threshold : float, nms_threshold : float) -> Sequence[int]:
188
+ normed_bounding_boxes = [ (x1, y1, x2 - x1, y2 - y1) for (x1, y1, x2, y2) in bounding_boxes ]
189
+ keep_indices = cv2.dnn.NMSBoxes(normed_bounding_boxes, face_scores, score_threshold = score_threshold, nms_threshold = nms_threshold)
190
+ return keep_indices
191
+
192
+
193
+ def get_nms_threshold(face_detector_model : FaceDetectorModel, face_detector_angles : List[Angle]) -> float:
194
+ if face_detector_model == 'many':
195
+ return 0.1
196
+ if len(face_detector_angles) == 2:
197
+ return 0.3
198
+ if len(face_detector_angles) == 3:
199
+ return 0.2
200
+ if len(face_detector_angles) == 4:
201
+ return 0.1
202
+ return 0.4
203
+
204
+
205
+ def merge_matrix(matrices : List[Matrix]) -> Matrix:
206
+ merged_matrix = numpy.vstack([ matrices[0], [ 0, 0, 1 ] ])
207
+ for matrix in matrices[1:]:
208
+ matrix = numpy.vstack([ matrix, [ 0, 0, 1 ] ])
209
+ merged_matrix = numpy.dot(merged_matrix, matrix)
210
+ return merged_matrix[:2, :]
facefusion/face_landmarker.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+
3
+ import cv2
4
+ import numpy
5
+
6
+ from facefusion import inference_manager, state_manager
7
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
8
+ from facefusion.face_helper import create_rotated_matrix_and_size, estimate_matrix_by_face_landmark_5, transform_points, warp_face_by_translation
9
+ from facefusion.filesystem import resolve_relative_path
10
+ from facefusion.thread_helper import conditional_thread_semaphore
11
+ from facefusion.typing import Angle, BoundingBox, DownloadSet, FaceLandmark5, FaceLandmark68, InferencePool, ModelSet, Prediction, Score, VisionFrame
12
+
13
+ MODEL_SET : ModelSet =\
14
+ {
15
+ '2dfan4':
16
+ {
17
+ 'hashes':
18
+ {
19
+ '2dfan4':
20
+ {
21
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/2dfan4.hash',
22
+ 'path': resolve_relative_path('../.assets/models/2dfan4.hash')
23
+ }
24
+ },
25
+ 'sources':
26
+ {
27
+ '2dfan4':
28
+ {
29
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/2dfan4.onnx',
30
+ 'path': resolve_relative_path('../.assets/models/2dfan4.onnx')
31
+ }
32
+ },
33
+ 'size': (256, 256)
34
+ },
35
+ 'peppa_wutz':
36
+ {
37
+ 'hashes':
38
+ {
39
+ 'peppa_wutz':
40
+ {
41
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/peppa_wutz.hash',
42
+ 'path': resolve_relative_path('../.assets/models/peppa_wutz.hash')
43
+ }
44
+ },
45
+ 'sources':
46
+ {
47
+ 'peppa_wutz':
48
+ {
49
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/peppa_wutz.onnx',
50
+ 'path': resolve_relative_path('../.assets/models/peppa_wutz.onnx')
51
+ }
52
+ },
53
+ 'size': (256, 256)
54
+ },
55
+ 'fan_68_5':
56
+ {
57
+ 'hashes':
58
+ {
59
+ 'fan_68_5':
60
+ {
61
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/fan_68_5.hash',
62
+ 'path': resolve_relative_path('../.assets/models/fan_68_5.hash')
63
+ }
64
+ },
65
+ 'sources':
66
+ {
67
+ 'fan_68_5':
68
+ {
69
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/fan_68_5.onnx',
70
+ 'path': resolve_relative_path('../.assets/models/fan_68_5.onnx')
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+
77
+ def get_inference_pool() -> InferencePool:
78
+ _, model_sources = collect_model_downloads()
79
+ model_context = __name__ + '.' + state_manager.get_item('face_landmarker_model')
80
+ return inference_manager.get_inference_pool(model_context, model_sources)
81
+
82
+
83
+ def clear_inference_pool() -> None:
84
+ model_context = __name__ + '.' + state_manager.get_item('face_landmarker_model')
85
+ inference_manager.clear_inference_pool(model_context)
86
+
87
+
88
+ def collect_model_downloads() -> Tuple[DownloadSet, DownloadSet]:
89
+ model_hashes =\
90
+ {
91
+ 'fan_68_5': MODEL_SET.get('fan_68_5').get('hashes').get('fan_68_5')
92
+ }
93
+ model_sources =\
94
+ {
95
+ 'fan_68_5': MODEL_SET.get('fan_68_5').get('sources').get('fan_68_5')
96
+ }
97
+
98
+ if state_manager.get_item('face_landmarker_model') in [ 'many', '2dfan4' ]:
99
+ model_hashes['2dfan4'] = MODEL_SET.get('2dfan4').get('hashes').get('2dfan4')
100
+ model_sources['2dfan4'] = MODEL_SET.get('2dfan4').get('sources').get('2dfan4')
101
+ if state_manager.get_item('face_landmarker_model') in [ 'many', 'peppa_wutz' ]:
102
+ model_hashes['peppa_wutz'] = MODEL_SET.get('peppa_wutz').get('hashes').get('peppa_wutz')
103
+ model_sources['peppa_wutz'] = MODEL_SET.get('peppa_wutz').get('sources').get('peppa_wutz')
104
+ return model_hashes, model_sources
105
+
106
+
107
+ def pre_check() -> bool:
108
+ download_directory_path = resolve_relative_path('../.assets/models')
109
+ model_hashes, model_sources = collect_model_downloads()
110
+
111
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
112
+
113
+
114
+ def detect_face_landmarks(vision_frame : VisionFrame, bounding_box : BoundingBox, face_angle : Angle) -> Tuple[FaceLandmark68, Score]:
115
+ face_landmark_2dfan4 = None
116
+ face_landmark_peppa_wutz = None
117
+ face_landmark_score_2dfan4 = 0.0
118
+ face_landmark_score_peppa_wutz = 0.0
119
+
120
+ if state_manager.get_item('face_landmarker_model') in [ 'many', '2dfan4' ]:
121
+ face_landmark_2dfan4, face_landmark_score_2dfan4 = detect_with_2dfan4(vision_frame, bounding_box, face_angle)
122
+ if state_manager.get_item('face_landmarker_model') in [ 'many', 'peppa_wutz' ]:
123
+ face_landmark_peppa_wutz, face_landmark_score_peppa_wutz = detect_with_peppa_wutz(vision_frame, bounding_box, face_angle)
124
+
125
+ if face_landmark_score_2dfan4 > face_landmark_score_peppa_wutz - 0.2:
126
+ return face_landmark_2dfan4, face_landmark_score_2dfan4
127
+ return face_landmark_peppa_wutz, face_landmark_score_peppa_wutz
128
+
129
+
130
+ def detect_with_2dfan4(temp_vision_frame: VisionFrame, bounding_box: BoundingBox, face_angle: Angle) -> Tuple[FaceLandmark68, Score]:
131
+ model_size = MODEL_SET.get('2dfan4').get('size')
132
+ scale = 195 / numpy.subtract(bounding_box[2:], bounding_box[:2]).max().clip(1, None)
133
+ translation = (model_size[0] - numpy.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5
134
+ rotated_matrix, rotated_size = create_rotated_matrix_and_size(face_angle, model_size)
135
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, model_size)
136
+ crop_vision_frame = cv2.warpAffine(crop_vision_frame, rotated_matrix, rotated_size)
137
+ crop_vision_frame = conditional_optimize_contrast(crop_vision_frame)
138
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1).astype(numpy.float32) / 255.0
139
+ face_landmark_68, face_heatmap = forward_with_2dfan4(crop_vision_frame)
140
+ face_landmark_68 = face_landmark_68[:, :, :2][0] / 64 * 256
141
+ face_landmark_68 = transform_points(face_landmark_68, cv2.invertAffineTransform(rotated_matrix))
142
+ face_landmark_68 = transform_points(face_landmark_68, cv2.invertAffineTransform(affine_matrix))
143
+ face_landmark_score_68 = numpy.amax(face_heatmap, axis = (2, 3))
144
+ face_landmark_score_68 = numpy.mean(face_landmark_score_68)
145
+ face_landmark_score_68 = numpy.interp(face_landmark_score_68, [ 0, 0.9 ], [ 0, 1 ])
146
+ return face_landmark_68, face_landmark_score_68
147
+
148
+
149
+ def detect_with_peppa_wutz(temp_vision_frame : VisionFrame, bounding_box : BoundingBox, face_angle : Angle) -> Tuple[FaceLandmark68, Score]:
150
+ model_size = MODEL_SET.get('peppa_wutz').get('size')
151
+ scale = 195 / numpy.subtract(bounding_box[2:], bounding_box[:2]).max().clip(1, None)
152
+ translation = (model_size[0] - numpy.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5
153
+ rotated_matrix, rotated_size = create_rotated_matrix_and_size(face_angle, model_size)
154
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, model_size)
155
+ crop_vision_frame = cv2.warpAffine(crop_vision_frame, rotated_matrix, rotated_size)
156
+ crop_vision_frame = conditional_optimize_contrast(crop_vision_frame)
157
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1).astype(numpy.float32) / 255.0
158
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
159
+ prediction = forward_with_peppa_wutz(crop_vision_frame)
160
+ face_landmark_68 = prediction.reshape(-1, 3)[:, :2] / 64 * model_size[0]
161
+ face_landmark_68 = transform_points(face_landmark_68, cv2.invertAffineTransform(rotated_matrix))
162
+ face_landmark_68 = transform_points(face_landmark_68, cv2.invertAffineTransform(affine_matrix))
163
+ face_landmark_score_68 = prediction.reshape(-1, 3)[:, 2].mean()
164
+ face_landmark_score_68 = numpy.interp(face_landmark_score_68, [ 0, 0.95 ], [ 0, 1 ])
165
+ return face_landmark_68, face_landmark_score_68
166
+
167
+
168
+ def conditional_optimize_contrast(crop_vision_frame : VisionFrame) -> VisionFrame:
169
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_RGB2Lab)
170
+ if numpy.mean(crop_vision_frame[:, :, 0]) < 30: # type:ignore[arg-type]
171
+ crop_vision_frame[:, :, 0] = cv2.createCLAHE(clipLimit = 2).apply(crop_vision_frame[:, :, 0])
172
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_Lab2RGB)
173
+ return crop_vision_frame
174
+
175
+
176
+ def estimate_face_landmark_68_5(face_landmark_5 : FaceLandmark5) -> FaceLandmark68:
177
+ affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, 'ffhq_512', (1, 1))
178
+ face_landmark_5 = cv2.transform(face_landmark_5.reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
179
+ face_landmark_68_5 = forward_fan_68_5(face_landmark_5)
180
+ face_landmark_68_5 = cv2.transform(face_landmark_68_5.reshape(1, -1, 2), cv2.invertAffineTransform(affine_matrix)).reshape(-1, 2)
181
+ return face_landmark_68_5
182
+
183
+
184
+ def forward_with_2dfan4(crop_vision_frame : VisionFrame) -> Tuple[Prediction, Prediction]:
185
+ face_landmarker = get_inference_pool().get('2dfan4')
186
+
187
+ with conditional_thread_semaphore():
188
+ prediction = face_landmarker.run(None,
189
+ {
190
+ 'input': [ crop_vision_frame ]
191
+ })
192
+
193
+ return prediction
194
+
195
+
196
+ def forward_with_peppa_wutz(crop_vision_frame : VisionFrame) -> Prediction:
197
+ face_landmarker = get_inference_pool().get('peppa_wutz')
198
+
199
+ with conditional_thread_semaphore():
200
+ prediction = face_landmarker.run(None,
201
+ {
202
+ 'input': crop_vision_frame
203
+ })[0]
204
+
205
+ return prediction
206
+
207
+
208
+ def forward_fan_68_5(face_landmark_5 : FaceLandmark5) -> FaceLandmark68:
209
+ face_landmarker = get_inference_pool().get('fan_68_5')
210
+
211
+ with conditional_thread_semaphore():
212
+ face_landmark_68_5 = face_landmarker.run(None,
213
+ {
214
+ 'input': [ face_landmark_5 ]
215
+ })[0][0]
216
+
217
+ return face_landmark_68_5
facefusion/face_masker.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ from typing import Dict, List, Tuple
3
+
4
+ import cv2
5
+ import numpy
6
+ from cv2.typing import Size
7
+
8
+ from facefusion import inference_manager
9
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
10
+ from facefusion.filesystem import resolve_relative_path
11
+ from facefusion.thread_helper import conditional_thread_semaphore
12
+ from facefusion.typing import DownloadSet, FaceLandmark68, FaceMaskRegion, InferencePool, Mask, ModelSet, Padding, VisionFrame
13
+
14
+ MODEL_SET : ModelSet =\
15
+ {
16
+ 'face_occluder':
17
+ {
18
+ 'hashes':
19
+ {
20
+ 'face_occluder':
21
+ {
22
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/dfl_xseg.hash',
23
+ 'path': resolve_relative_path('../.assets/models/dfl_xseg.hash')
24
+ }
25
+ },
26
+ 'sources':
27
+ {
28
+ 'face_occluder':
29
+ {
30
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/dfl_xseg.onnx',
31
+ 'path': resolve_relative_path('../.assets/models/dfl_xseg.onnx')
32
+ }
33
+ },
34
+ 'size': (256, 256)
35
+ },
36
+ 'face_parser':
37
+ {
38
+ 'hashes':
39
+ {
40
+ 'face_parser':
41
+ {
42
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/bisenet_resnet_34.hash',
43
+ 'path': resolve_relative_path('../.assets/models/bisenet_resnet_34.hash')
44
+ }
45
+ },
46
+ 'sources':
47
+ {
48
+ 'face_parser':
49
+ {
50
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/bisenet_resnet_34.onnx',
51
+ 'path': resolve_relative_path('../.assets/models/bisenet_resnet_34.onnx')
52
+ }
53
+ },
54
+ 'size': (512, 512)
55
+ }
56
+ }
57
+ FACE_MASK_REGIONS : Dict[FaceMaskRegion, int] =\
58
+ {
59
+ 'skin': 1,
60
+ 'left-eyebrow': 2,
61
+ 'right-eyebrow': 3,
62
+ 'left-eye': 4,
63
+ 'right-eye': 5,
64
+ 'glasses': 6,
65
+ 'nose': 10,
66
+ 'mouth': 11,
67
+ 'upper-lip': 12,
68
+ 'lower-lip': 13
69
+ }
70
+
71
+
72
+ def get_inference_pool() -> InferencePool:
73
+ _, model_sources = collect_model_downloads()
74
+ return inference_manager.get_inference_pool(__name__, model_sources)
75
+
76
+
77
+ def clear_inference_pool() -> None:
78
+ inference_manager.clear_inference_pool(__name__)
79
+
80
+
81
+ def collect_model_downloads() -> Tuple[DownloadSet, DownloadSet]:
82
+ model_hashes =\
83
+ {
84
+ 'face_occluder': MODEL_SET.get('face_occluder').get('hashes').get('face_occluder'),
85
+ 'face_parser': MODEL_SET.get('face_parser').get('hashes').get('face_parser')
86
+ }
87
+ model_sources =\
88
+ {
89
+ 'face_occluder': MODEL_SET.get('face_occluder').get('sources').get('face_occluder'),
90
+ 'face_parser': MODEL_SET.get('face_parser').get('sources').get('face_parser')
91
+ }
92
+ return model_hashes, model_sources
93
+
94
+
95
+ def pre_check() -> bool:
96
+ download_directory_path = resolve_relative_path('../.assets/models')
97
+ model_hashes, model_sources = collect_model_downloads()
98
+
99
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
100
+
101
+
102
+ @lru_cache(maxsize = None)
103
+ def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Mask:
104
+ blur_amount = int(crop_size[0] * 0.5 * face_mask_blur)
105
+ blur_area = max(blur_amount // 2, 1)
106
+ box_mask : Mask = numpy.ones(crop_size).astype(numpy.float32)
107
+ box_mask[:max(blur_area, int(crop_size[1] * face_mask_padding[0] / 100)), :] = 0
108
+ box_mask[-max(blur_area, int(crop_size[1] * face_mask_padding[2] / 100)):, :] = 0
109
+ box_mask[:, :max(blur_area, int(crop_size[0] * face_mask_padding[3] / 100))] = 0
110
+ box_mask[:, -max(blur_area, int(crop_size[0] * face_mask_padding[1] / 100)):] = 0
111
+ if blur_amount > 0:
112
+ box_mask = cv2.GaussianBlur(box_mask, (0, 0), blur_amount * 0.25)
113
+ return box_mask
114
+
115
+
116
+ def create_occlusion_mask(crop_vision_frame : VisionFrame) -> Mask:
117
+ model_size = MODEL_SET.get('face_occluder').get('size')
118
+ prepare_vision_frame = cv2.resize(crop_vision_frame, model_size)
119
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32) / 255
120
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 1, 2, 3)
121
+ occlusion_mask = forward_occlude_face(prepare_vision_frame)
122
+ occlusion_mask = occlusion_mask.transpose(0, 1, 2).clip(0, 1).astype(numpy.float32)
123
+ occlusion_mask = cv2.resize(occlusion_mask, crop_vision_frame.shape[:2][::-1])
124
+ occlusion_mask = (cv2.GaussianBlur(occlusion_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
125
+ return occlusion_mask
126
+
127
+
128
+ def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List[FaceMaskRegion]) -> Mask:
129
+ model_size = MODEL_SET.get('face_parser').get('size')
130
+ prepare_vision_frame = cv2.resize(crop_vision_frame, model_size)
131
+ prepare_vision_frame = prepare_vision_frame[:, :, ::-1].astype(numpy.float32) / 255
132
+ prepare_vision_frame = numpy.subtract(prepare_vision_frame, numpy.array([ 0.485, 0.456, 0.406 ]).astype(numpy.float32))
133
+ prepare_vision_frame = numpy.divide(prepare_vision_frame, numpy.array([ 0.229, 0.224, 0.225 ]).astype(numpy.float32))
134
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0)
135
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 3, 1, 2)
136
+ region_mask = forward_parse_face(prepare_vision_frame)
137
+ region_mask = numpy.isin(region_mask.argmax(0), [ FACE_MASK_REGIONS[region] for region in face_mask_regions ])
138
+ region_mask = cv2.resize(region_mask.astype(numpy.float32), crop_vision_frame.shape[:2][::-1])
139
+ region_mask = (cv2.GaussianBlur(region_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
140
+ return region_mask
141
+
142
+
143
+ def create_mouth_mask(face_landmark_68 : FaceLandmark68) -> Mask:
144
+ convex_hull = cv2.convexHull(face_landmark_68[numpy.r_[3:14, 31:36]].astype(numpy.int32))
145
+ mouth_mask : Mask = numpy.zeros((512, 512)).astype(numpy.float32)
146
+ mouth_mask = cv2.fillConvexPoly(mouth_mask, convex_hull, 1.0) #type:ignore[call-overload]
147
+ mouth_mask = cv2.erode(mouth_mask.clip(0, 1), numpy.ones((21, 3)))
148
+ mouth_mask = cv2.GaussianBlur(mouth_mask, (0, 0), sigmaX = 1, sigmaY = 15)
149
+ return mouth_mask
150
+
151
+
152
+ def forward_occlude_face(prepare_vision_frame : VisionFrame) -> Mask:
153
+ face_occluder = get_inference_pool().get('face_occluder')
154
+
155
+ with conditional_thread_semaphore():
156
+ occlusion_mask : Mask = face_occluder.run(None,
157
+ {
158
+ 'input': prepare_vision_frame
159
+ })[0][0]
160
+
161
+ return occlusion_mask
162
+
163
+
164
+ def forward_parse_face(prepare_vision_frame : VisionFrame) -> Mask:
165
+ face_parser = get_inference_pool().get('face_parser')
166
+
167
+ with conditional_thread_semaphore():
168
+ region_mask : Mask = face_parser.run(None,
169
+ {
170
+ 'input': prepare_vision_frame
171
+ })[0][0]
172
+
173
+ return region_mask
facefusion/face_recognizer.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+
3
+ import numpy
4
+
5
+ from facefusion import inference_manager
6
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
7
+ from facefusion.face_helper import warp_face_by_face_landmark_5
8
+ from facefusion.filesystem import resolve_relative_path
9
+ from facefusion.thread_helper import conditional_thread_semaphore
10
+ from facefusion.typing import Embedding, FaceLandmark5, InferencePool, ModelOptions, ModelSet, VisionFrame
11
+
12
+ MODEL_SET : ModelSet =\
13
+ {
14
+ 'arcface':
15
+ {
16
+ 'hashes':
17
+ {
18
+ 'face_recognizer':
19
+ {
20
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/arcface_w600k_r50.hash',
21
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.hash')
22
+ }
23
+ },
24
+ 'sources':
25
+ {
26
+ 'face_recognizer':
27
+ {
28
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/arcface_w600k_r50.onnx',
29
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
30
+ }
31
+ },
32
+ 'template': 'arcface_112_v2',
33
+ 'size': (112, 112)
34
+ }
35
+ }
36
+
37
+
38
+ def get_inference_pool() -> InferencePool:
39
+ model_sources = get_model_options().get('sources')
40
+ return inference_manager.get_inference_pool(__name__, model_sources)
41
+
42
+
43
+ def clear_inference_pool() -> None:
44
+ inference_manager.clear_inference_pool(__name__)
45
+
46
+
47
+ def get_model_options() -> ModelOptions:
48
+ return MODEL_SET.get('arcface')
49
+
50
+
51
+ def pre_check() -> bool:
52
+ download_directory_path = resolve_relative_path('../.assets/models')
53
+ model_hashes = get_model_options().get('hashes')
54
+ model_sources = get_model_options().get('sources')
55
+
56
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
57
+
58
+
59
+ def calc_embedding(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5) -> Tuple[Embedding, Embedding]:
60
+ model_template = get_model_options().get('template')
61
+ model_size = get_model_options().get('size')
62
+ crop_vision_frame, matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_template, model_size)
63
+ crop_vision_frame = crop_vision_frame / 127.5 - 1
64
+ crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32)
65
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
66
+ embedding = forward(crop_vision_frame)
67
+ embedding = embedding.ravel()
68
+ normed_embedding = embedding / numpy.linalg.norm(embedding)
69
+ return embedding, normed_embedding
70
+
71
+
72
+ def forward(crop_vision_frame : VisionFrame) -> Embedding:
73
+ face_recognizer = get_inference_pool().get('face_recognizer')
74
+
75
+ with conditional_thread_semaphore():
76
+ embedding = face_recognizer.run(None,
77
+ {
78
+ 'input': crop_vision_frame
79
+ })[0]
80
+
81
+ return embedding
facefusion/face_selector.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ import numpy
4
+
5
+ from facefusion import state_manager
6
+ from facefusion.typing import Face, FaceSelectorOrder, FaceSet, Gender, Race
7
+
8
+
9
+ def find_similar_faces(faces : List[Face], reference_faces : FaceSet, face_distance : float) -> List[Face]:
10
+ similar_faces : List[Face] = []
11
+
12
+ if faces and reference_faces:
13
+ for reference_set in reference_faces:
14
+ if not similar_faces:
15
+ for reference_face in reference_faces[reference_set]:
16
+ for face in faces:
17
+ if compare_faces(face, reference_face, face_distance):
18
+ similar_faces.append(face)
19
+ return similar_faces
20
+
21
+
22
+ def compare_faces(face : Face, reference_face : Face, face_distance : float) -> bool:
23
+ current_face_distance = calc_face_distance(face, reference_face)
24
+ return current_face_distance < face_distance
25
+
26
+
27
+ def calc_face_distance(face : Face, reference_face : Face) -> float:
28
+ if hasattr(face, 'normed_embedding') and hasattr(reference_face, 'normed_embedding'):
29
+ return 1 - numpy.dot(face.normed_embedding, reference_face.normed_embedding)
30
+ return 0
31
+
32
+
33
+ def sort_and_filter_faces(faces : List[Face]) -> List[Face]:
34
+ if faces:
35
+ if state_manager.get_item('face_selector_order'):
36
+ faces = sort_by_order(faces, state_manager.get_item('face_selector_order'))
37
+ if state_manager.get_item('face_selector_gender'):
38
+ faces = filter_by_gender(faces, state_manager.get_item('face_selector_gender'))
39
+ if state_manager.get_item('face_selector_race'):
40
+ faces = filter_by_race(faces, state_manager.get_item('face_selector_race'))
41
+ if state_manager.get_item('face_selector_age_start') or state_manager.get_item('face_selector_age_end'):
42
+ faces = filter_by_age(faces, state_manager.get_item('face_selector_age_start'), state_manager.get_item('face_selector_age_end'))
43
+ return faces
44
+
45
+
46
+ def sort_by_order(faces : List[Face], order : FaceSelectorOrder) -> List[Face]:
47
+ if order == 'left-right':
48
+ return sorted(faces, key = lambda face: face.bounding_box[0])
49
+ if order == 'right-left':
50
+ return sorted(faces, key = lambda face: face.bounding_box[0], reverse = True)
51
+ if order == 'top-bottom':
52
+ return sorted(faces, key = lambda face: face.bounding_box[1])
53
+ if order == 'bottom-top':
54
+ return sorted(faces, key = lambda face: face.bounding_box[1], reverse = True)
55
+ if order == 'small-large':
56
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]))
57
+ if order == 'large-small':
58
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]), reverse = True)
59
+ if order == 'best-worst':
60
+ return sorted(faces, key = lambda face: face.score_set.get('detector'), reverse = True)
61
+ if order == 'worst-best':
62
+ return sorted(faces, key = lambda face: face.score_set.get('detector'))
63
+ return faces
64
+
65
+
66
+ def filter_by_gender(faces : List[Face], gender : Gender) -> List[Face]:
67
+ filter_faces = []
68
+
69
+ for face in faces:
70
+ if face.gender == gender:
71
+ filter_faces.append(face)
72
+ return filter_faces
73
+
74
+
75
+ def filter_by_age(faces : List[Face], face_selector_age_start : int, face_selector_age_end : int) -> List[Face]:
76
+ filter_faces = []
77
+ age = range(face_selector_age_start, face_selector_age_end)
78
+
79
+ for face in faces:
80
+ if set(face.age) & set(age):
81
+ filter_faces.append(face)
82
+ return filter_faces
83
+
84
+
85
+ def filter_by_race(faces : List[Face], race : Race) -> List[Face]:
86
+ filter_faces = []
87
+
88
+ for face in faces:
89
+ if face.race == race:
90
+ filter_faces.append(face)
91
+ return filter_faces
facefusion/face_store.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ from typing import List, Optional
3
+
4
+ import numpy
5
+
6
+ from facefusion.typing import Face, FaceSet, FaceStore, VisionFrame
7
+
8
+ FACE_STORE : FaceStore =\
9
+ {
10
+ 'static_faces': {},
11
+ 'reference_faces': {}
12
+ }
13
+
14
+
15
+ def get_face_store() -> FaceStore:
16
+ return FACE_STORE
17
+
18
+
19
+ def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
20
+ frame_hash = create_frame_hash(vision_frame)
21
+ if frame_hash in FACE_STORE['static_faces']:
22
+ return FACE_STORE['static_faces'][frame_hash]
23
+ return None
24
+
25
+
26
+ def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
27
+ frame_hash = create_frame_hash(vision_frame)
28
+ if frame_hash:
29
+ FACE_STORE['static_faces'][frame_hash] = faces
30
+
31
+
32
+ def clear_static_faces() -> None:
33
+ FACE_STORE['static_faces'] = {}
34
+
35
+
36
+ def create_frame_hash(vision_frame : VisionFrame) -> Optional[str]:
37
+ return hashlib.sha1(vision_frame.tobytes()).hexdigest() if numpy.any(vision_frame) else None
38
+
39
+
40
+ def get_reference_faces() -> Optional[FaceSet]:
41
+ if FACE_STORE['reference_faces']:
42
+ return FACE_STORE['reference_faces']
43
+ return None
44
+
45
+
46
+ def append_reference_face(name : str, face : Face) -> None:
47
+ if name not in FACE_STORE['reference_faces']:
48
+ FACE_STORE['reference_faces'][name] = []
49
+ FACE_STORE['reference_faces'][name].append(face)
50
+
51
+
52
+ def clear_reference_faces() -> None:
53
+ FACE_STORE['reference_faces'] = {}
facefusion/ffmpeg.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import tempfile
5
+ from typing import List, Optional
6
+
7
+ import filetype
8
+
9
+ from facefusion import logger, process_manager, state_manager
10
+ from facefusion.filesystem import remove_file
11
+ from facefusion.temp_helper import get_temp_file_path, get_temp_frames_pattern
12
+ from facefusion.typing import AudioBuffer, Fps, OutputVideoPreset
13
+ from facefusion.vision import restrict_video_fps
14
+
15
+
16
+ def run_ffmpeg(args : List[str]) -> subprocess.Popen[bytes]:
17
+ commands = [ shutil.which('ffmpeg'), '-hide_banner', '-loglevel', 'error' ]
18
+ commands.extend(args)
19
+ process = subprocess.Popen(commands, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
20
+
21
+ while process_manager.is_processing():
22
+ try:
23
+ if state_manager.get_item('log_level') == 'debug':
24
+ log_debug(process)
25
+ process.wait(timeout = 0.5)
26
+ except subprocess.TimeoutExpired:
27
+ continue
28
+ return process
29
+
30
+ if process_manager.is_stopping():
31
+ process.terminate()
32
+ return process
33
+
34
+
35
+ def open_ffmpeg(args : List[str]) -> subprocess.Popen[bytes]:
36
+ commands = [ shutil.which('ffmpeg'), '-hide_banner', '-loglevel', 'quiet' ]
37
+ commands.extend(args)
38
+ return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
39
+
40
+
41
+ def log_debug(process : subprocess.Popen[bytes]) -> None:
42
+ _, stderr = process.communicate()
43
+ errors = stderr.decode().split(os.linesep)
44
+
45
+ for error in errors:
46
+ if error.strip():
47
+ logger.debug(error.strip(), __name__)
48
+
49
+
50
+ def extract_frames(target_path : str, temp_video_resolution : str, temp_video_fps : Fps) -> bool:
51
+ trim_frame_start = state_manager.get_item('trim_frame_start')
52
+ trim_frame_end = state_manager.get_item('trim_frame_end')
53
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%08d')
54
+ commands = [ '-i', target_path, '-s', str(temp_video_resolution), '-q:v', '0' ]
55
+
56
+ if isinstance(trim_frame_start, int) and isinstance(trim_frame_end, int):
57
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ':end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ])
58
+ elif isinstance(trim_frame_start, int):
59
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ',fps=' + str(temp_video_fps) ])
60
+ elif isinstance(trim_frame_end, int):
61
+ commands.extend([ '-vf', 'trim=end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ])
62
+ else:
63
+ commands.extend([ '-vf', 'fps=' + str(temp_video_fps) ])
64
+ commands.extend([ '-vsync', '0', temp_frames_pattern ])
65
+ return run_ffmpeg(commands).returncode == 0
66
+
67
+
68
+ def merge_video(target_path : str, output_video_resolution : str, output_video_fps : Fps) -> bool:
69
+ temp_video_fps = restrict_video_fps(target_path, output_video_fps)
70
+ temp_file_path = get_temp_file_path(target_path)
71
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%08d')
72
+ commands = [ '-r', str(temp_video_fps), '-i', temp_frames_pattern, '-s', str(output_video_resolution), '-c:v', state_manager.get_item('output_video_encoder') ]
73
+
74
+ if state_manager.get_item('output_video_encoder') in [ 'libx264', 'libx265' ]:
75
+ output_video_compression = round(51 - (state_manager.get_item('output_video_quality') * 0.51))
76
+ commands.extend([ '-crf', str(output_video_compression), '-preset', state_manager.get_item('output_video_preset') ])
77
+ if state_manager.get_item('output_video_encoder') in [ 'libvpx-vp9' ]:
78
+ output_video_compression = round(63 - (state_manager.get_item('output_video_quality') * 0.63))
79
+ commands.extend([ '-crf', str(output_video_compression) ])
80
+ if state_manager.get_item('output_video_encoder') in [ 'h264_nvenc', 'hevc_nvenc' ]:
81
+ output_video_compression = round(51 - (state_manager.get_item('output_video_quality') * 0.51))
82
+ commands.extend([ '-cq', str(output_video_compression), '-preset', map_nvenc_preset(state_manager.get_item('output_video_preset')) ])
83
+ if state_manager.get_item('output_video_encoder') in [ 'h264_amf', 'hevc_amf' ]:
84
+ output_video_compression = round(51 - (state_manager.get_item('output_video_quality') * 0.51))
85
+ commands.extend([ '-qp_i', str(output_video_compression), '-qp_p', str(output_video_compression), '-quality', map_amf_preset(state_manager.get_item('output_video_preset')) ])
86
+ if state_manager.get_item('output_video_encoder') in [ 'h264_videotoolbox', 'hevc_videotoolbox' ]:
87
+ commands.extend([ '-q:v', str(state_manager.get_item('output_video_quality')) ])
88
+ commands.extend([ '-vf', 'framerate=fps=' + str(output_video_fps), '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_file_path ])
89
+ return run_ffmpeg(commands).returncode == 0
90
+
91
+
92
+ def concat_video(output_path : str, temp_output_paths : List[str]) -> bool:
93
+ concat_video_path = tempfile.mktemp()
94
+
95
+ with open(concat_video_path, 'w') as concat_video_file:
96
+ for temp_output_path in temp_output_paths:
97
+ concat_video_file.write('file \'' + os.path.abspath(temp_output_path) + '\'' + os.linesep)
98
+ concat_video_file.flush()
99
+ concat_video_file.close()
100
+ commands = [ '-f', 'concat', '-safe', '0', '-i', concat_video_file.name, '-c:v', 'copy', '-c:a', state_manager.get_item('output_audio_encoder'), '-y', os.path.abspath(output_path) ]
101
+ process = run_ffmpeg(commands)
102
+ process.communicate()
103
+ remove_file(concat_video_path)
104
+ return process.returncode == 0
105
+
106
+
107
+ def copy_image(target_path : str, temp_image_resolution : str) -> bool:
108
+ temp_file_path = get_temp_file_path(target_path)
109
+ temp_image_compression = calc_image_compression(target_path, 100)
110
+ commands = [ '-i', target_path, '-s', str(temp_image_resolution), '-q:v', str(temp_image_compression), '-y', temp_file_path ]
111
+ return run_ffmpeg(commands).returncode == 0
112
+
113
+
114
+ def finalize_image(target_path : str, output_path : str, output_image_resolution : str) -> bool:
115
+ temp_file_path = get_temp_file_path(target_path)
116
+ output_image_compression = calc_image_compression(target_path, state_manager.get_item('output_image_quality'))
117
+ commands = [ '-i', temp_file_path, '-s', str(output_image_resolution), '-q:v', str(output_image_compression), '-y', output_path ]
118
+ return run_ffmpeg(commands).returncode == 0
119
+
120
+
121
+ def calc_image_compression(image_path : str, image_quality : int) -> int:
122
+ is_webp = filetype.guess_mime(image_path) == 'image/webp'
123
+ if is_webp:
124
+ image_quality = 100 - image_quality
125
+ return round(31 - (image_quality * 0.31))
126
+
127
+
128
+ def read_audio_buffer(target_path : str, sample_rate : int, channel_total : int) -> Optional[AudioBuffer]:
129
+ commands = [ '-i', target_path, '-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sample_rate), '-ac', str(channel_total), '-' ]
130
+ process = open_ffmpeg(commands)
131
+ audio_buffer, _ = process.communicate()
132
+ if process.returncode == 0:
133
+ return audio_buffer
134
+ return None
135
+
136
+
137
+ def restore_audio(target_path : str, output_path : str, output_video_fps : Fps) -> bool:
138
+ trim_frame_start = state_manager.get_item('trim_frame_start')
139
+ trim_frame_end = state_manager.get_item('trim_frame_end')
140
+ temp_file_path = get_temp_file_path(target_path)
141
+ commands = [ '-i', temp_file_path ]
142
+
143
+ if isinstance(trim_frame_start, int):
144
+ start_time = trim_frame_start / output_video_fps
145
+ commands.extend([ '-ss', str(start_time) ])
146
+ if isinstance(trim_frame_end, int):
147
+ end_time = trim_frame_end / output_video_fps
148
+ commands.extend([ '-to', str(end_time) ])
149
+ commands.extend([ '-i', target_path, '-c:v', 'copy', '-c:a', state_manager.get_item('output_audio_encoder'), '-map', '0:v:0', '-map', '1:a:0', '-shortest', '-y', output_path ])
150
+ return run_ffmpeg(commands).returncode == 0
151
+
152
+
153
+ def replace_audio(target_path : str, audio_path : str, output_path : str) -> bool:
154
+ temp_file_path = get_temp_file_path(target_path)
155
+ commands = [ '-i', temp_file_path, '-i', audio_path, '-c:a', state_manager.get_item('output_audio_encoder'), '-af', 'apad', '-shortest', '-y', output_path ]
156
+ return run_ffmpeg(commands).returncode == 0
157
+
158
+
159
+ def map_nvenc_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
160
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast' ]:
161
+ return 'fast'
162
+ if output_video_preset == 'medium':
163
+ return 'medium'
164
+ if output_video_preset in [ 'slow', 'slower', 'veryslow' ]:
165
+ return 'slow'
166
+ return None
167
+
168
+
169
+ def map_amf_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
170
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast' ]:
171
+ return 'speed'
172
+ if output_video_preset in [ 'faster', 'fast', 'medium' ]:
173
+ return 'balanced'
174
+ if output_video_preset in [ 'slow', 'slower', 'veryslow' ]:
175
+ return 'quality'
176
+ return None
facefusion/filesystem.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import List, Optional
5
+
6
+ import filetype
7
+
8
+ from facefusion.common_helper import is_windows
9
+
10
+ if is_windows():
11
+ import ctypes
12
+
13
+
14
+ def get_file_size(file_path : str) -> int:
15
+ if is_file(file_path):
16
+ return os.path.getsize(file_path)
17
+ return 0
18
+
19
+
20
+ def same_file_extension(file_paths : List[str]) -> bool:
21
+ file_extensions : List[str] = []
22
+
23
+ for file_path in file_paths:
24
+ _, file_extension = os.path.splitext(file_path.lower())
25
+
26
+ if file_extensions and file_extension not in file_extensions:
27
+ return False
28
+ file_extensions.append(file_extension)
29
+ return True
30
+
31
+
32
+ def is_file(file_path : str) -> bool:
33
+ return bool(file_path and os.path.isfile(file_path))
34
+
35
+
36
+ def is_directory(directory_path : str) -> bool:
37
+ return bool(directory_path and os.path.isdir(directory_path))
38
+
39
+
40
+ def in_directory(file_path : str) -> bool:
41
+ if file_path and not is_directory(file_path):
42
+ return is_directory(os.path.dirname(file_path))
43
+ return False
44
+
45
+
46
+ def is_audio(audio_path : str) -> bool:
47
+ return is_file(audio_path) and filetype.helpers.is_audio(audio_path)
48
+
49
+
50
+ def has_audio(audio_paths : List[str]) -> bool:
51
+ if audio_paths:
52
+ return any(is_audio(audio_path) for audio_path in audio_paths)
53
+ return False
54
+
55
+
56
+ def is_image(image_path : str) -> bool:
57
+ return is_file(image_path) and filetype.helpers.is_image(image_path)
58
+
59
+
60
+ def has_image(image_paths: List[str]) -> bool:
61
+ if image_paths:
62
+ return any(is_image(image_path) for image_path in image_paths)
63
+ return False
64
+
65
+
66
+ def is_video(video_path : str) -> bool:
67
+ return is_file(video_path) and filetype.helpers.is_video(video_path)
68
+
69
+
70
+ def filter_audio_paths(paths : List[str]) -> List[str]:
71
+ if paths:
72
+ return [ path for path in paths if is_audio(path) ]
73
+ return []
74
+
75
+
76
+ def filter_image_paths(paths : List[str]) -> List[str]:
77
+ if paths:
78
+ return [ path for path in paths if is_image(path) ]
79
+ return []
80
+
81
+
82
+ def resolve_relative_path(path : str) -> str:
83
+ return os.path.abspath(os.path.join(os.path.dirname(__file__), path))
84
+
85
+
86
+ def sanitize_path_for_windows(full_path : str) -> Optional[str]:
87
+ buffer_size = 0
88
+
89
+ while True:
90
+ unicode_buffer = ctypes.create_unicode_buffer(buffer_size)
91
+ buffer_limit = ctypes.windll.kernel32.GetShortPathNameW(full_path, unicode_buffer, buffer_size) #type:ignore[attr-defined]
92
+
93
+ if buffer_size > buffer_limit:
94
+ return unicode_buffer.value
95
+ if buffer_limit == 0:
96
+ return None
97
+ buffer_size = buffer_limit
98
+
99
+
100
+ def copy_file(file_path : str, move_path : str) -> bool:
101
+ if is_file(file_path):
102
+ shutil.copy(file_path, move_path)
103
+ return is_file(move_path)
104
+ return False
105
+
106
+
107
+ def move_file(file_path : str, move_path : str) -> bool:
108
+ if is_file(file_path):
109
+ shutil.move(file_path, move_path)
110
+ return not is_file(file_path) and is_file(move_path)
111
+ return False
112
+
113
+
114
+ def remove_file(file_path : str) -> bool:
115
+ if is_file(file_path):
116
+ os.remove(file_path)
117
+ return not is_file(file_path)
118
+ return False
119
+
120
+
121
+ def create_directory(directory_path : str) -> bool:
122
+ if directory_path and not is_file(directory_path):
123
+ Path(directory_path).mkdir(parents = True, exist_ok = True)
124
+ return is_directory(directory_path)
125
+ return False
126
+
127
+
128
+ def list_directory(directory_path : str) -> Optional[List[str]]:
129
+ if is_directory(directory_path):
130
+ files = os.listdir(directory_path)
131
+ files = [ Path(file).stem for file in files if not Path(file).stem.startswith(('.', '__')) ]
132
+ return sorted(files)
133
+ return None
134
+
135
+
136
+ def remove_directory(directory_path : str) -> bool:
137
+ if is_directory(directory_path):
138
+ shutil.rmtree(directory_path, ignore_errors = True)
139
+ return not is_directory(directory_path)
140
+ return False
facefusion/hash_helper.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import zlib
3
+ from typing import Optional
4
+
5
+ from facefusion.filesystem import is_file
6
+
7
+
8
+ def create_hash(content : bytes) -> str:
9
+ return format(zlib.crc32(content), '08x')
10
+
11
+
12
+ def validate_hash(validate_path : str) -> bool:
13
+ hash_path = get_hash_path(validate_path)
14
+
15
+ if is_file(hash_path):
16
+ with open(hash_path, 'r') as hash_file:
17
+ hash_content = hash_file.read().strip()
18
+
19
+ with open(validate_path, 'rb') as validate_file:
20
+ validate_content = validate_file.read()
21
+
22
+ return create_hash(validate_content) == hash_content
23
+ return False
24
+
25
+
26
+ def get_hash_path(validate_path : str) -> Optional[str]:
27
+ if is_file(validate_path):
28
+ validate_directory_path, _ = os.path.split(validate_path)
29
+ validate_file_name, _ = os.path.splitext(_)
30
+
31
+ return os.path.join(validate_directory_path, validate_file_name + '.hash')
32
+ return None
facefusion/inference_manager.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ from time import sleep
3
+ from typing import List
4
+
5
+ import onnx
6
+ from onnxruntime import InferenceSession
7
+
8
+ from facefusion import process_manager, state_manager
9
+ from facefusion.app_context import detect_app_context
10
+ from facefusion.execution import create_execution_providers, has_execution_provider
11
+ from facefusion.thread_helper import thread_lock
12
+ from facefusion.typing import DownloadSet, ExecutionProviderKey, InferencePool, InferencePoolSet, ModelInitializer
13
+
14
+ INFERENCE_POOLS : InferencePoolSet =\
15
+ {
16
+ 'cli': {}, # type:ignore[typeddict-item]
17
+ 'ui': {} # type:ignore[typeddict-item]
18
+ }
19
+
20
+
21
+ def get_inference_pool(model_context : str, model_sources : DownloadSet) -> InferencePool:
22
+ global INFERENCE_POOLS
23
+
24
+ with thread_lock():
25
+ while process_manager.is_checking():
26
+ sleep(0.5)
27
+ app_context = detect_app_context()
28
+ inference_context = get_inference_context(model_context)
29
+
30
+ if app_context == 'cli' and INFERENCE_POOLS.get('ui').get(inference_context):
31
+ INFERENCE_POOLS['cli'][inference_context] = INFERENCE_POOLS.get('ui').get(inference_context)
32
+ if app_context == 'ui' and INFERENCE_POOLS.get('cli').get(inference_context):
33
+ INFERENCE_POOLS['ui'][inference_context] = INFERENCE_POOLS.get('cli').get(inference_context)
34
+ if not INFERENCE_POOLS.get(app_context).get(inference_context):
35
+ execution_provider_keys = resolve_execution_provider_keys(model_context)
36
+ INFERENCE_POOLS[app_context][inference_context] = create_inference_pool(model_sources, state_manager.get_item('execution_device_id'), execution_provider_keys)
37
+
38
+ return INFERENCE_POOLS.get(app_context).get(inference_context)
39
+
40
+
41
+ def create_inference_pool(model_sources : DownloadSet, execution_device_id : str, execution_provider_keys : List[ExecutionProviderKey]) -> InferencePool:
42
+ inference_pool : InferencePool = {}
43
+
44
+ for model_name in model_sources.keys():
45
+ inference_pool[model_name] = create_inference_session(model_sources.get(model_name).get('path'), execution_device_id, execution_provider_keys)
46
+ return inference_pool
47
+
48
+
49
+ def clear_inference_pool(model_context : str) -> None:
50
+ global INFERENCE_POOLS
51
+
52
+ app_context = detect_app_context()
53
+ inference_context = get_inference_context(model_context)
54
+
55
+ if INFERENCE_POOLS.get(app_context).get(inference_context):
56
+ del INFERENCE_POOLS[app_context][inference_context]
57
+
58
+
59
+ def create_inference_session(model_path : str, execution_device_id : str, execution_provider_keys : List[ExecutionProviderKey]) -> InferenceSession:
60
+ execution_providers = create_execution_providers(execution_device_id, execution_provider_keys)
61
+ return InferenceSession(model_path, providers = execution_providers)
62
+
63
+
64
+ @lru_cache(maxsize = None)
65
+ def get_static_model_initializer(model_path : str) -> ModelInitializer:
66
+ model = onnx.load(model_path)
67
+ return onnx.numpy_helper.to_array(model.graph.initializer[-1])
68
+
69
+
70
+ def resolve_execution_provider_keys(model_context : str) -> List[ExecutionProviderKey]:
71
+ if has_execution_provider('coreml') and (model_context.startswith('facefusion.processors.modules.age_modifier') or model_context.startswith('facefusion.processors.modules.frame_colorizer')):
72
+ return [ 'cpu' ]
73
+ return state_manager.get_item('execution_providers')
74
+
75
+
76
+ def get_inference_context(model_context : str) -> str:
77
+ execution_provider_keys = resolve_execution_provider_keys(model_context)
78
+ inference_context = model_context + '.' + '_'.join(execution_provider_keys)
79
+ return inference_context
facefusion/installer.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import signal
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ from argparse import ArgumentParser, HelpFormatter
8
+ from typing import Dict, Tuple
9
+
10
+ from facefusion import metadata, wording
11
+ from facefusion.common_helper import is_linux, is_macos, is_windows
12
+
13
+ ONNXRUNTIMES : Dict[str, Tuple[str, str]] = {}
14
+
15
+ if is_macos():
16
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.19.2')
17
+ else:
18
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.19.2')
19
+ ONNXRUNTIMES['cuda'] = ('onnxruntime-gpu', '1.19.2')
20
+ ONNXRUNTIMES['openvino'] = ('onnxruntime-openvino', '1.19.0')
21
+ if is_linux():
22
+ ONNXRUNTIMES['rocm'] = ('onnxruntime-rocm', '1.18.0')
23
+ if is_windows():
24
+ ONNXRUNTIMES['directml'] = ('onnxruntime-directml', '1.17.3')
25
+
26
+
27
+ def cli() -> None:
28
+ signal.signal(signal.SIGINT, lambda signal_number, frame: sys.exit(0))
29
+ program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 50))
30
+ program.add_argument('--onnxruntime', help = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIMES.keys(), required = True)
31
+ program.add_argument('--skip-conda', help = wording.get('help.skip_conda'), action = 'store_true')
32
+ program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
33
+ run(program)
34
+
35
+
36
+ def run(program : ArgumentParser) -> None:
37
+ args = program.parse_args()
38
+ has_conda = 'CONDA_PREFIX' in os.environ
39
+ onnxruntime_name, onnxruntime_version = ONNXRUNTIMES.get(args.onnxruntime)
40
+
41
+ if not args.skip_conda and not has_conda:
42
+ sys.stdout.write(wording.get('conda_not_activated') + os.linesep)
43
+ sys.exit(1)
44
+
45
+ subprocess.call([ shutil.which('pip'), 'install', '-r', 'requirements.txt', '--force-reinstall' ])
46
+
47
+ if args.onnxruntime == 'rocm':
48
+ python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor)
49
+
50
+ if python_id == 'cp310':
51
+ wheel_name = 'onnxruntime_rocm-' + onnxruntime_version +'-' + python_id + '-' + python_id + '-linux_x86_64.whl'
52
+ wheel_path = os.path.join(tempfile.gettempdir(), wheel_name)
53
+ wheel_url = 'https://repo.radeon.com/rocm/manylinux/rocm-rel-6.2/' + wheel_name
54
+ subprocess.call([ shutil.which('curl'), '--silent', '--location', '--continue-at', '-', '--output', wheel_path, wheel_url ])
55
+ subprocess.call([ shutil.which('pip'), 'uninstall', 'onnxruntime', wheel_path, '-y', '-q' ])
56
+ subprocess.call([ shutil.which('pip'), 'install', wheel_path, '--force-reinstall' ])
57
+ os.remove(wheel_path)
58
+ else:
59
+ subprocess.call([ shutil.which('pip'), 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ])
60
+ subprocess.call([ shutil.which('pip'), 'install', onnxruntime_name + '==' + onnxruntime_version, '--force-reinstall' ])
61
+
62
+ if args.onnxruntime == 'cuda' and has_conda:
63
+ library_paths = []
64
+
65
+ if is_linux():
66
+ if os.getenv('LD_LIBRARY_PATH'):
67
+ library_paths = os.getenv('LD_LIBRARY_PATH').split(os.pathsep)
68
+
69
+ python_id = 'python' + str(sys.version_info.major) + '.' + str(sys.version_info.minor)
70
+ library_paths.extend(
71
+ [
72
+ os.path.join(os.getenv('CONDA_PREFIX'), 'lib'),
73
+ os.path.join(os.getenv('CONDA_PREFIX'), 'lib', python_id, 'site-packages', 'tensorrt_libs')
74
+ ])
75
+ library_paths = [ library_path for library_path in library_paths if os.path.exists(library_path) ]
76
+
77
+ subprocess.call([ shutil.which('conda'), 'env', 'config', 'vars', 'set', 'LD_LIBRARY_PATH=' + os.pathsep.join(library_paths) ])
78
+
79
+ if is_windows():
80
+ if os.getenv('PATH'):
81
+ library_paths = os.getenv('PATH').split(os.pathsep)
82
+
83
+ library_paths.extend(
84
+ [
85
+ os.path.join(os.getenv('CONDA_PREFIX'), 'Lib'),
86
+ os.path.join(os.getenv('CONDA_PREFIX'), 'Lib', 'site-packages', 'tensorrt_libs')
87
+ ])
88
+ library_paths = [ library_path for library_path in library_paths if os.path.exists(library_path) ]
89
+
90
+ subprocess.call([ shutil.which('conda'), 'env', 'config', 'vars', 'set', 'PATH=' + os.pathsep.join(library_paths) ])
91
+
92
+ if onnxruntime_version < '1.19.0':
93
+ subprocess.call([ shutil.which('pip'), 'install', 'numpy==1.26.4', '--force-reinstall' ])
facefusion/jobs/__init__.py ADDED
File without changes
facefusion/jobs/job_helper.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+
6
+ def get_step_output_path(job_id : str, step_index : int, output_path : str) -> Optional[str]:
7
+ if output_path:
8
+ output_directory_path, _ = os.path.split(output_path)
9
+ output_file_name, output_file_extension = os.path.splitext(_)
10
+ return os.path.join(output_directory_path, output_file_name + '-' + job_id + '-' + str(step_index) + output_file_extension)
11
+ return None
12
+
13
+
14
+ def suggest_job_id(job_prefix : str = 'job') -> str:
15
+ return job_prefix + '-' + datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
facefusion/jobs/job_list.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import Optional, Tuple
3
+
4
+ from facefusion.date_helper import describe_time_ago
5
+ from facefusion.jobs import job_manager
6
+ from facefusion.typing import JobStatus, TableContents, TableHeaders
7
+
8
+
9
+ def compose_job_list(job_status : JobStatus) -> Tuple[TableHeaders, TableContents]:
10
+ jobs = job_manager.find_jobs(job_status)
11
+ job_headers : TableHeaders = [ 'job id', 'steps', 'date created', 'date updated', 'job status' ]
12
+ job_contents : TableContents = []
13
+
14
+ for index, job_id in enumerate(jobs):
15
+ if job_manager.validate_job(job_id):
16
+ job = jobs[job_id]
17
+ step_total = job_manager.count_step_total(job_id)
18
+ date_created = prepare_describe_datetime(job.get('date_created'))
19
+ date_updated = prepare_describe_datetime(job.get('date_updated'))
20
+ job_contents.append(
21
+ [
22
+ job_id,
23
+ step_total,
24
+ date_created,
25
+ date_updated,
26
+ job_status
27
+ ])
28
+ return job_headers, job_contents
29
+
30
+
31
+ def prepare_describe_datetime(date_time : Optional[str]) -> Optional[str]:
32
+ if date_time:
33
+ return describe_time_ago(datetime.fromisoformat(date_time))
34
+ return None
facefusion/jobs/job_manager.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob
2
+ import os
3
+ from copy import copy
4
+ from typing import List, Optional
5
+
6
+ from facefusion.choices import job_statuses
7
+ from facefusion.date_helper import get_current_date_time
8
+ from facefusion.filesystem import create_directory, is_directory, is_file, move_file, remove_directory, remove_file
9
+ from facefusion.jobs.job_helper import get_step_output_path
10
+ from facefusion.json import read_json, write_json
11
+ from facefusion.temp_helper import create_base_directory
12
+ from facefusion.typing import Args, Job, JobSet, JobStatus, JobStep, JobStepStatus
13
+
14
+ JOBS_PATH : Optional[str] = None
15
+
16
+
17
+ def init_jobs(jobs_path : str) -> bool:
18
+ global JOBS_PATH
19
+
20
+ JOBS_PATH = jobs_path
21
+ job_status_paths = [ os.path.join(JOBS_PATH, job_status) for job_status in job_statuses ]
22
+
23
+ create_base_directory()
24
+ for job_status_path in job_status_paths:
25
+ create_directory(job_status_path)
26
+ return all(is_directory(status_path) for status_path in job_status_paths)
27
+
28
+
29
+ def clear_jobs(jobs_path : str) -> bool:
30
+ return remove_directory(jobs_path)
31
+
32
+
33
+ def create_job(job_id : str) -> bool:
34
+ job : Job =\
35
+ {
36
+ 'version': '1',
37
+ 'date_created': get_current_date_time().isoformat(),
38
+ 'date_updated': None,
39
+ 'steps': []
40
+ }
41
+
42
+ return create_job_file(job_id, job)
43
+
44
+
45
+ def submit_job(job_id : str) -> bool:
46
+ drafted_job_ids = find_job_ids('drafted')
47
+ steps = get_steps(job_id)
48
+
49
+ if job_id in drafted_job_ids and steps:
50
+ return set_steps_status(job_id, 'queued') and move_job_file(job_id, 'queued')
51
+ return False
52
+
53
+
54
+ def submit_jobs() -> bool:
55
+ drafted_job_ids = find_job_ids('drafted')
56
+
57
+ if drafted_job_ids:
58
+ for job_id in drafted_job_ids:
59
+ if not submit_job(job_id):
60
+ return False
61
+ return True
62
+ return False
63
+
64
+
65
+ def delete_job(job_id : str) -> bool:
66
+ return delete_job_file(job_id)
67
+
68
+
69
+ def delete_jobs() -> bool:
70
+ job_ids = find_job_ids('drafted') + find_job_ids('queued') + find_job_ids('failed') + find_job_ids('completed')
71
+
72
+ if job_ids:
73
+ for job_id in job_ids:
74
+ if not delete_job(job_id):
75
+ return False
76
+ return True
77
+ return False
78
+
79
+
80
+ def find_jobs(job_status : JobStatus) -> JobSet:
81
+ job_ids = find_job_ids(job_status)
82
+ jobs : JobSet = {}
83
+
84
+ for job_id in job_ids:
85
+ jobs[job_id] = read_job_file(job_id)
86
+ return jobs
87
+
88
+
89
+ def find_job_ids(job_status : JobStatus) -> List[str]:
90
+ job_pattern = os.path.join(JOBS_PATH, job_status, '*.json')
91
+ job_files = glob.glob(job_pattern)
92
+ job_files.sort(key = os.path.getmtime)
93
+ job_ids = []
94
+
95
+ for job_file in job_files:
96
+ job_id, _ = os.path.splitext(os.path.basename(job_file))
97
+ job_ids.append(job_id)
98
+ return job_ids
99
+
100
+
101
+ def validate_job(job_id : str) -> bool:
102
+ job = read_job_file(job_id)
103
+ return bool(job and 'version' in job and 'date_created' in job and 'date_updated' in job and 'steps' in job)
104
+
105
+
106
+ def has_step(job_id : str, step_index : int) -> bool:
107
+ step_total = count_step_total(job_id)
108
+ return step_index in range(step_total)
109
+
110
+
111
+ def add_step(job_id : str, step_args : Args) -> bool:
112
+ job = read_job_file(job_id)
113
+
114
+ if job:
115
+ job.get('steps').append(
116
+ {
117
+ 'args': step_args,
118
+ 'status': 'drafted'
119
+ })
120
+ return update_job_file(job_id, job)
121
+ return False
122
+
123
+
124
+ def remix_step(job_id : str, step_index : int, step_args : Args) -> bool:
125
+ steps = get_steps(job_id)
126
+ step_args = copy(step_args)
127
+
128
+ if step_index and step_index < 0:
129
+ step_index = count_step_total(job_id) - 1
130
+
131
+ if has_step(job_id, step_index):
132
+ output_path = steps[step_index].get('args').get('output_path')
133
+ step_args['target_path'] = get_step_output_path(job_id, step_index, output_path)
134
+ return add_step(job_id, step_args)
135
+ return False
136
+
137
+
138
+ def insert_step(job_id : str, step_index : int, step_args : Args) -> bool:
139
+ job = read_job_file(job_id)
140
+ step_args = copy(step_args)
141
+
142
+ if step_index and step_index < 0:
143
+ step_index = count_step_total(job_id) - 1
144
+
145
+ if job and has_step(job_id, step_index):
146
+ job.get('steps').insert(step_index,
147
+ {
148
+ 'args': step_args,
149
+ 'status': 'drafted'
150
+ })
151
+ return update_job_file(job_id, job)
152
+ return False
153
+
154
+
155
+ def remove_step(job_id : str, step_index : int) -> bool:
156
+ job = read_job_file(job_id)
157
+
158
+ if step_index and step_index < 0:
159
+ step_index = count_step_total(job_id) - 1
160
+
161
+ if job and has_step(job_id, step_index):
162
+ job.get('steps').pop(step_index)
163
+ return update_job_file(job_id, job)
164
+ return False
165
+
166
+
167
+ def get_steps(job_id : str) -> List[JobStep]:
168
+ job = read_job_file(job_id)
169
+
170
+ if job:
171
+ return job.get('steps')
172
+ return []
173
+
174
+
175
+ def count_step_total(job_id : str) -> int:
176
+ steps = get_steps(job_id)
177
+
178
+ if steps:
179
+ return len(steps)
180
+ return 0
181
+
182
+
183
+ def set_step_status(job_id : str, step_index : int, step_status : JobStepStatus) -> bool:
184
+ job = read_job_file(job_id)
185
+
186
+ if job:
187
+ steps = job.get('steps')
188
+
189
+ if has_step(job_id, step_index):
190
+ steps[step_index]['status'] = step_status
191
+ return update_job_file(job_id, job)
192
+ return False
193
+
194
+
195
+ def set_steps_status(job_id : str, step_status : JobStepStatus) -> bool:
196
+ job = read_job_file(job_id)
197
+
198
+ if job:
199
+ for step in job.get('steps'):
200
+ step['status'] = step_status
201
+ return update_job_file(job_id, job)
202
+ return False
203
+
204
+
205
+ def read_job_file(job_id : str) -> Optional[Job]:
206
+ job_path = find_job_path(job_id)
207
+ return read_json(job_path) #type:ignore[return-value]
208
+
209
+
210
+ def create_job_file(job_id : str, job : Job) -> bool:
211
+ job_path = find_job_path(job_id)
212
+
213
+ if not is_file(job_path):
214
+ job_create_path = suggest_job_path(job_id, 'drafted')
215
+ return write_json(job_create_path, job) #type:ignore[arg-type]
216
+ return False
217
+
218
+
219
+ def update_job_file(job_id : str, job : Job) -> bool:
220
+ job_path = find_job_path(job_id)
221
+
222
+ if is_file(job_path):
223
+ job['date_updated'] = get_current_date_time().isoformat()
224
+ return write_json(job_path, job) #type:ignore[arg-type]
225
+ return False
226
+
227
+
228
+ def move_job_file(job_id : str, job_status : JobStatus) -> bool:
229
+ job_path = find_job_path(job_id)
230
+ job_move_path = suggest_job_path(job_id, job_status)
231
+ return move_file(job_path, job_move_path)
232
+
233
+
234
+ def delete_job_file(job_id : str) -> bool:
235
+ job_path = find_job_path(job_id)
236
+ return remove_file(job_path)
237
+
238
+
239
+ def suggest_job_path(job_id : str, job_status : JobStatus) -> Optional[str]:
240
+ job_file_name = get_job_file_name(job_id)
241
+
242
+ if job_file_name:
243
+ return os.path.join(JOBS_PATH, job_status, job_file_name)
244
+ return None
245
+
246
+
247
+ def find_job_path(job_id : str) -> Optional[str]:
248
+ job_file_name = get_job_file_name(job_id)
249
+
250
+ if job_file_name:
251
+ for job_status in job_statuses:
252
+ job_pattern = os.path.join(JOBS_PATH, job_status, job_file_name)
253
+ job_paths = glob.glob(job_pattern)
254
+
255
+ for job_path in job_paths:
256
+ return job_path
257
+ return None
258
+
259
+
260
+ def get_job_file_name(job_id : str) -> Optional[str]:
261
+ if job_id:
262
+ return job_id + '.json'
263
+ return None
facefusion/jobs/job_runner.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion.ffmpeg import concat_video
2
+ from facefusion.filesystem import is_image, is_video, move_file, remove_file
3
+ from facefusion.jobs import job_helper, job_manager
4
+ from facefusion.typing import JobOutputSet, JobStep, ProcessStep
5
+
6
+
7
+ def run_job(job_id : str, process_step : ProcessStep) -> bool:
8
+ queued_job_ids = job_manager.find_job_ids('queued')
9
+
10
+ if job_id in queued_job_ids:
11
+ if run_steps(job_id, process_step) and finalize_steps(job_id):
12
+ clean_steps(job_id)
13
+ return job_manager.move_job_file(job_id, 'completed')
14
+ clean_steps(job_id)
15
+ job_manager.move_job_file(job_id, 'failed')
16
+ return False
17
+
18
+
19
+ def run_jobs(process_step : ProcessStep) -> bool:
20
+ queued_job_ids = job_manager.find_job_ids('queued')
21
+
22
+ if queued_job_ids:
23
+ for job_id in queued_job_ids:
24
+ if not run_job(job_id, process_step):
25
+ return False
26
+ return True
27
+ return False
28
+
29
+
30
+ def retry_job(job_id : str, process_step : ProcessStep) -> bool:
31
+ failed_job_ids = job_manager.find_job_ids('failed')
32
+
33
+ if job_id in failed_job_ids:
34
+ return job_manager.set_steps_status(job_id, 'queued') and job_manager.move_job_file(job_id, 'queued') and run_job(job_id, process_step)
35
+ return False
36
+
37
+
38
+ def retry_jobs(process_step : ProcessStep) -> bool:
39
+ failed_job_ids = job_manager.find_job_ids('failed')
40
+
41
+ if failed_job_ids:
42
+ for job_id in failed_job_ids:
43
+ if not retry_job(job_id, process_step):
44
+ return False
45
+ return True
46
+ return False
47
+
48
+
49
+ def run_step(job_id : str, step_index : int, step : JobStep, process_step : ProcessStep) -> bool:
50
+ step_args = step.get('args')
51
+
52
+ if job_manager.set_step_status(job_id, step_index, 'started') and process_step(job_id, step_index, step_args):
53
+ output_path = step_args.get('output_path')
54
+ step_output_path = job_helper.get_step_output_path(job_id, step_index, output_path)
55
+
56
+ return move_file(output_path, step_output_path) and job_manager.set_step_status(job_id, step_index, 'completed')
57
+ job_manager.set_step_status(job_id, step_index, 'failed')
58
+ return False
59
+
60
+
61
+ def run_steps(job_id : str, process_step : ProcessStep) -> bool:
62
+ steps = job_manager.get_steps(job_id)
63
+
64
+ if steps:
65
+ for index, step in enumerate(steps):
66
+ if not run_step(job_id, index, step, process_step):
67
+ return False
68
+ return True
69
+ return False
70
+
71
+
72
+ def finalize_steps(job_id : str) -> bool:
73
+ output_set = collect_output_set(job_id)
74
+
75
+ for output_path, temp_output_paths in output_set.items():
76
+ if all(map(is_video, temp_output_paths)):
77
+ if not concat_video(output_path, temp_output_paths):
78
+ return False
79
+ if any(map(is_image, temp_output_paths)):
80
+ for temp_output_path in temp_output_paths:
81
+ if not move_file(temp_output_path, output_path):
82
+ return False
83
+ return True
84
+
85
+
86
+ def clean_steps(job_id: str) -> bool:
87
+ output_set = collect_output_set(job_id)
88
+
89
+ for temp_output_paths in output_set.values():
90
+ for temp_output_path in temp_output_paths:
91
+ if not remove_file(temp_output_path):
92
+ return False
93
+ return True
94
+
95
+
96
+ def collect_output_set(job_id : str) -> JobOutputSet:
97
+ steps = job_manager.get_steps(job_id)
98
+ output_set : JobOutputSet = {}
99
+
100
+ for index, step in enumerate(steps):
101
+ output_path = step.get('args').get('output_path')
102
+
103
+ if output_path:
104
+ step_output_path = job_manager.get_step_output_path(job_id, index, output_path)
105
+ output_set.setdefault(output_path, []).append(step_output_path)
106
+ return output_set
facefusion/jobs/job_store.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.typing import JobStore
4
+
5
+ JOB_STORE : JobStore =\
6
+ {
7
+ 'job_keys': [],
8
+ 'step_keys': []
9
+ }
10
+
11
+
12
+ def get_job_keys() -> List[str]:
13
+ return JOB_STORE.get('job_keys')
14
+
15
+
16
+ def get_step_keys() -> List[str]:
17
+ return JOB_STORE.get('step_keys')
18
+
19
+
20
+ def register_job_keys(step_keys : List[str]) -> None:
21
+ for step_key in step_keys:
22
+ JOB_STORE['job_keys'].append(step_key)
23
+
24
+
25
+ def register_step_keys(job_keys : List[str]) -> None:
26
+ for job_key in job_keys:
27
+ JOB_STORE['step_keys'].append(job_key)
facefusion/json.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from json import JSONDecodeError
3
+ from typing import Optional
4
+
5
+ from facefusion.filesystem import is_file
6
+ from facefusion.typing import Content
7
+
8
+
9
+ def read_json(json_path : str) -> Optional[Content]:
10
+ if is_file(json_path):
11
+ try:
12
+ with open(json_path, 'r') as json_file:
13
+ return json.load(json_file)
14
+ except JSONDecodeError:
15
+ pass
16
+ return None
17
+
18
+
19
+ def write_json(json_path : str, content : Content) -> bool:
20
+ with open(json_path, 'w') as json_file:
21
+ json.dump(content, json_file, indent = 4)
22
+ return is_file(json_path)
facefusion/logger.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from logging import Logger, basicConfig, getLogger
2
+ from typing import Tuple
3
+
4
+ from facefusion.choices import log_level_set
5
+ from facefusion.common_helper import get_first, get_last
6
+ from facefusion.typing import LogLevel, TableContents, TableHeaders
7
+
8
+
9
+ def init(log_level : LogLevel) -> None:
10
+ basicConfig(format = '%(message)s')
11
+ get_package_logger().setLevel(log_level_set.get(log_level))
12
+
13
+
14
+ def get_package_logger() -> Logger:
15
+ return getLogger('facefusion')
16
+
17
+
18
+ def debug(message : str, module_name : str) -> None:
19
+ get_package_logger().debug(create_message(message, module_name))
20
+
21
+
22
+ def info(message : str, module_name : str) -> None:
23
+ get_package_logger().info(create_message(message, module_name))
24
+
25
+
26
+ def warn(message : str, module_name : str) -> None:
27
+ get_package_logger().warning(create_message(message, module_name))
28
+
29
+
30
+ def error(message : str, module_name : str) -> None:
31
+ get_package_logger().error(create_message(message, module_name))
32
+
33
+
34
+ def create_message(message : str, module_name : str) -> str:
35
+ scopes = module_name.split('.')
36
+ first_scope = get_first(scopes)
37
+ last_scope = get_last(scopes)
38
+
39
+ if first_scope and last_scope:
40
+ return '[' + first_scope.upper() + '.' + last_scope.upper() + '] ' + message
41
+ return message
42
+
43
+
44
+ def table(headers : TableHeaders, contents : TableContents) -> None:
45
+ package_logger = get_package_logger()
46
+ table_column, table_separator = create_table_parts(headers, contents)
47
+
48
+ package_logger.info(table_separator)
49
+ package_logger.info(table_column.format(*headers))
50
+ package_logger.info(table_separator)
51
+
52
+ for content in contents:
53
+ content = [ value if value else '' for value in content ]
54
+ package_logger.info(table_column.format(*content))
55
+
56
+ package_logger.info(table_separator)
57
+
58
+
59
+ def create_table_parts(headers : TableHeaders, contents : TableContents) -> Tuple[str, str]:
60
+ column_parts = []
61
+ separator_parts = []
62
+ widths = [ len(header) for header in headers ]
63
+
64
+ for content in contents:
65
+ for index, value in enumerate(content):
66
+ widths[index] = max(widths[index], len(str(value)))
67
+
68
+ for width in widths:
69
+ column_parts.append('{:<' + str(width) + '}')
70
+ separator_parts.append('-' * width)
71
+
72
+ return '| ' + ' | '.join(column_parts) + ' |', '+-' + '-+-'.join(separator_parts) + '-+'
73
+
74
+
75
+ def enable() -> None:
76
+ get_package_logger().disabled = False
77
+
78
+
79
+ def disable() -> None:
80
+ get_package_logger().disabled = True
facefusion/memory.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion.common_helper import is_macos, is_windows
2
+
3
+ if is_windows():
4
+ import ctypes
5
+ else:
6
+ import resource
7
+
8
+
9
+ def limit_system_memory(system_memory_limit : int = 1) -> bool:
10
+ if is_macos():
11
+ system_memory_limit = system_memory_limit * (1024 ** 6)
12
+ else:
13
+ system_memory_limit = system_memory_limit * (1024 ** 3)
14
+ try:
15
+ if is_windows():
16
+ ctypes.windll.kernel32.SetProcessWorkingSetSize(-1, ctypes.c_size_t(system_memory_limit), ctypes.c_size_t(system_memory_limit)) #type:ignore[attr-defined]
17
+ else:
18
+ resource.setrlimit(resource.RLIMIT_DATA, (system_memory_limit, system_memory_limit))
19
+ return True
20
+ except Exception:
21
+ return False
facefusion/metadata.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ METADATA =\
4
+ {
5
+ 'name': 'FaceFusion',
6
+ 'description': 'Industry leading face manipulation platform',
7
+ 'version': '3.0.0',
8
+ 'license': 'MIT',
9
+ 'author': 'Henry Ruhs',
10
+ 'url': 'https://facefusion.io'
11
+ }
12
+
13
+
14
+ def get(key : str) -> Optional[str]:
15
+ if key in METADATA:
16
+ return METADATA.get(key)
17
+ return None
facefusion/normalizer.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from facefusion.typing import Fps, Padding
4
+
5
+
6
+ def normalize_padding(padding : Optional[List[int]]) -> Optional[Padding]:
7
+ if padding and len(padding) == 1:
8
+ return tuple([ padding[0] ] * 4) #type:ignore[return-value]
9
+ if padding and len(padding) == 2:
10
+ return tuple([ padding[0], padding[1], padding[0], padding[1] ]) #type:ignore[return-value]
11
+ if padding and len(padding) == 3:
12
+ return tuple([ padding[0], padding[1], padding[2], padding[1] ]) #type:ignore[return-value]
13
+ if padding and len(padding) == 4:
14
+ return tuple(padding) #type:ignore[return-value]
15
+ return None
16
+
17
+
18
+ def normalize_fps(fps : Optional[float]) -> Optional[Fps]:
19
+ if isinstance(fps, (int, float)):
20
+ return max(1.0, min(fps, 60.0))
21
+ return None
facefusion/process_manager.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Generator, List
2
+
3
+ from facefusion.typing import ProcessState, QueuePayload
4
+
5
+ PROCESS_STATE : ProcessState = 'pending'
6
+
7
+
8
+ def get_process_state() -> ProcessState:
9
+ return PROCESS_STATE
10
+
11
+
12
+ def set_process_state(process_state : ProcessState) -> None:
13
+ global PROCESS_STATE
14
+
15
+ PROCESS_STATE = process_state
16
+
17
+
18
+ def is_checking() -> bool:
19
+ return get_process_state() == 'checking'
20
+
21
+
22
+ def is_processing() -> bool:
23
+ return get_process_state() == 'processing'
24
+
25
+
26
+ def is_stopping() -> bool:
27
+ return get_process_state() == 'stopping'
28
+
29
+
30
+ def is_pending() -> bool:
31
+ return get_process_state() == 'pending'
32
+
33
+
34
+ def check() -> None:
35
+ set_process_state('checking')
36
+
37
+
38
+ def start() -> None:
39
+ set_process_state('processing')
40
+
41
+
42
+ def stop() -> None:
43
+ set_process_state('stopping')
44
+
45
+
46
+ def end() -> None:
47
+ set_process_state('pending')
48
+
49
+
50
+ def manage(queue_payloads : List[QueuePayload]) -> Generator[QueuePayload, None, None]:
51
+ for query_payload in queue_payloads:
52
+ if is_processing():
53
+ yield query_payload
facefusion/processors/__init__.py ADDED
File without changes
facefusion/processors/choices.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Sequence
2
+
3
+ from facefusion.common_helper import create_float_range, create_int_range
4
+ from facefusion.processors.typing import AgeModifierModel, ExpressionRestorerModel, FaceDebuggerItem, FaceEditorModel, FaceEnhancerModel, FaceSwapperSet, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel
5
+
6
+ age_modifier_models : List[AgeModifierModel] = [ 'styleganex_age' ]
7
+ expression_restorer_models : List[ExpressionRestorerModel] = [ 'live_portrait' ]
8
+ face_debugger_items : List[FaceDebuggerItem] = [ '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' ]
9
+ face_editor_models : List[FaceEditorModel] = [ 'live_portrait' ]
10
+ face_enhancer_models : List[FaceEnhancerModel] = [ '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' ]
11
+ face_swapper_set : FaceSwapperSet =\
12
+ {
13
+ 'blendswap_256': [ '256x256', '384x384', '512x512', '768x768', '1024x1024' ],
14
+ 'ghost_1_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
15
+ 'ghost_2_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
16
+ 'ghost_3_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
17
+ 'inswapper_128': [ '128x128', '256x256', '384x384', '512x512', '768x768', '1024x1024' ],
18
+ 'inswapper_128_fp16': [ '128x128', '256x256', '384x384', '512x512', '768x768', '1024x1024' ],
19
+ 'simswap_256': [ '256x256', '512x512', '768x768', '1024x1024' ],
20
+ 'simswap_unofficial_512': [ '512x512', '768x768', '1024x1024' ],
21
+ 'uniface_256': [ '256x256', '512x512', '768x768', '1024x1024' ]
22
+ }
23
+ frame_colorizer_models : List[FrameColorizerModel] = [ 'ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable' ]
24
+ frame_colorizer_sizes : List[str] = [ '192x192', '256x256', '384x384', '512x512' ]
25
+ 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', 'span_kendata_x4', 'ultra_sharp_x4' ]
26
+ lip_syncer_models : List[LipSyncerModel] = [ 'wav2lip_96', 'wav2lip_gan_96' ]
27
+
28
+ age_modifier_direction_range : Sequence[int] = create_int_range(-100, 100, 1)
29
+ expression_restorer_factor_range : Sequence[int] = create_int_range(0, 100, 1)
30
+ face_editor_eyebrow_direction_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
31
+ face_editor_eye_gaze_horizontal_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
32
+ face_editor_eye_gaze_vertical_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
33
+ face_editor_eye_open_ratio_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
34
+ face_editor_lip_open_ratio_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
35
+ face_editor_mouth_grim_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
36
+ face_editor_mouth_pout_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
37
+ face_editor_mouth_purse_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
38
+ face_editor_mouth_smile_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
39
+ face_editor_mouth_position_horizontal_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
40
+ face_editor_mouth_position_vertical_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
41
+ face_editor_head_pitch_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
42
+ face_editor_head_yaw_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
43
+ face_editor_head_roll_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
44
+ face_enhancer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
45
+ frame_colorizer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
46
+ frame_enhancer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
facefusion/processors/core.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib
2
+ import os
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from queue import Queue
5
+ from types import ModuleType
6
+ from typing import Any, List
7
+
8
+ from tqdm import tqdm
9
+
10
+ from facefusion import logger, state_manager, wording
11
+ from facefusion.exit_helper import hard_exit
12
+ from facefusion.typing import ProcessFrames, QueuePayload
13
+
14
+ PROCESSORS_METHODS =\
15
+ [
16
+ 'get_inference_pool',
17
+ 'clear_inference_pool',
18
+ 'register_args',
19
+ 'apply_args',
20
+ 'pre_check',
21
+ 'pre_process',
22
+ 'post_process',
23
+ 'get_reference_frame',
24
+ 'process_frame',
25
+ 'process_frames',
26
+ 'process_image',
27
+ 'process_video'
28
+ ]
29
+
30
+
31
+ def load_processor_module(processor : str) -> Any:
32
+ try:
33
+ processor_module = importlib.import_module('facefusion.processors.modules.' + processor)
34
+ for method_name in PROCESSORS_METHODS:
35
+ if not hasattr(processor_module, method_name):
36
+ raise NotImplementedError
37
+ except ModuleNotFoundError as exception:
38
+ logger.error(wording.get('processor_not_loaded').format(processor = processor), __name__)
39
+ logger.debug(exception.msg, __name__)
40
+ hard_exit(1)
41
+ except NotImplementedError:
42
+ logger.error(wording.get('processor_not_implemented').format(processor = processor), __name__)
43
+ hard_exit(1)
44
+ return processor_module
45
+
46
+
47
+ def get_processors_modules(processors : List[str]) -> List[ModuleType]:
48
+ processor_modules = []
49
+
50
+ for processor in processors:
51
+ processor_module = load_processor_module(processor)
52
+ processor_modules.append(processor_module)
53
+ return processor_modules
54
+
55
+
56
+ def clear_processors_modules(processors : List[str]) -> None:
57
+ for processor in processors:
58
+ processor_module = load_processor_module(processor)
59
+ processor_module.clear_inference_pool()
60
+
61
+
62
+ def multi_process_frames(source_paths : List[str], temp_frame_paths : List[str], process_frames : ProcessFrames) -> None:
63
+ queue_payloads = create_queue_payloads(temp_frame_paths)
64
+ with tqdm(total = len(queue_payloads), desc = wording.get('processing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
65
+ progress.set_postfix(
66
+ {
67
+ 'execution_providers': state_manager.get_item('execution_providers'),
68
+ 'execution_thread_count': state_manager.get_item('execution_thread_count'),
69
+ 'execution_queue_count': state_manager.get_item('execution_queue_count')
70
+ })
71
+ with ThreadPoolExecutor(max_workers = state_manager.get_item('execution_thread_count')) as executor:
72
+ futures = []
73
+ queue : Queue[QueuePayload] = create_queue(queue_payloads)
74
+ queue_per_future = max(len(queue_payloads) // state_manager.get_item('execution_thread_count') * state_manager.get_item('execution_queue_count'), 1)
75
+
76
+ while not queue.empty():
77
+ future = executor.submit(process_frames, source_paths, pick_queue(queue, queue_per_future), progress.update)
78
+ futures.append(future)
79
+
80
+ for future_done in as_completed(futures):
81
+ future_done.result()
82
+
83
+
84
+ def create_queue(queue_payloads : List[QueuePayload]) -> Queue[QueuePayload]:
85
+ queue : Queue[QueuePayload] = Queue()
86
+ for queue_payload in queue_payloads:
87
+ queue.put(queue_payload)
88
+ return queue
89
+
90
+
91
+ def pick_queue(queue : Queue[QueuePayload], queue_per_future : int) -> List[QueuePayload]:
92
+ queues = []
93
+ for _ in range(queue_per_future):
94
+ if not queue.empty():
95
+ queues.append(queue.get())
96
+ return queues
97
+
98
+
99
+ def create_queue_payloads(temp_frame_paths : List[str]) -> List[QueuePayload]:
100
+ queue_payloads = []
101
+ temp_frame_paths = sorted(temp_frame_paths, key = os.path.basename)
102
+
103
+ for frame_number, frame_path in enumerate(temp_frame_paths):
104
+ frame_payload : QueuePayload =\
105
+ {
106
+ 'frame_number': frame_number,
107
+ 'frame_path': frame_path
108
+ }
109
+ queue_payloads.append(frame_payload)
110
+ return queue_payloads
facefusion/processors/live_portrait.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple
2
+
3
+ import numpy
4
+ import scipy
5
+
6
+ from facefusion.processors.typing import LivePortraitExpression, LivePortraitPitch, LivePortraitRoll, LivePortraitRotation, LivePortraitYaw
7
+
8
+ EXPRESSION_MIN = numpy.array(
9
+ [
10
+ [
11
+ [ -2.88067125e-02, -8.12731311e-02, -1.70541159e-03 ],
12
+ [ -4.88598682e-02, -3.32196616e-02, -1.67431499e-04 ],
13
+ [ -6.75425082e-02, -4.28681746e-02, -1.98950816e-04 ],
14
+ [ -7.23103955e-02, -3.28503326e-02, -7.31324719e-04 ],
15
+ [ -3.87073644e-02, -6.01546466e-02, -5.50269964e-04 ],
16
+ [ -6.38048723e-02, -2.23840728e-01, -7.13261834e-04 ],
17
+ [ -3.02710701e-02, -3.93195450e-02, -8.24086510e-06 ],
18
+ [ -2.95799859e-02, -5.39318882e-02, -1.74219604e-04 ],
19
+ [ -2.92359516e-02, -1.53050944e-02, -6.30460854e-05 ],
20
+ [ -5.56493877e-03, -2.34344602e-02, -1.26858242e-04 ],
21
+ [ -4.37593013e-02, -2.77768299e-02, -2.70503685e-02 ],
22
+ [ -1.76926646e-02, -1.91676542e-02, -1.15090821e-04 ],
23
+ [ -8.34268332e-03, -3.99775570e-03, -3.27481248e-05 ],
24
+ [ -3.40162888e-02, -2.81868968e-02, -1.96679524e-04 ],
25
+ [ -2.91855410e-02, -3.97511162e-02, -2.81230678e-05 ],
26
+ [ -1.50395725e-02, -2.49494594e-02, -9.42573533e-05 ],
27
+ [ -1.67938769e-02, -2.00953931e-02, -4.00750607e-04 ],
28
+ [ -1.86435618e-02, -2.48535164e-02, -2.74416432e-02 ],
29
+ [ -4.61211195e-03, -1.21660791e-02, -2.93173041e-04 ],
30
+ [ -4.10017073e-02, -7.43824020e-02, -4.42762971e-02 ],
31
+ [ -1.90370996e-02, -3.74363363e-02, -1.34740388e-02 ]
32
+ ]
33
+ ]).astype(numpy.float32)
34
+ EXPRESSION_MAX = numpy.array(
35
+ [
36
+ [
37
+ [ 4.46682945e-02, 7.08772913e-02, 4.08344204e-04 ],
38
+ [ 2.14308221e-02, 6.15894832e-02, 4.85319615e-05 ],
39
+ [ 3.02363783e-02, 4.45043296e-02, 1.28298725e-05 ],
40
+ [ 3.05869691e-02, 3.79812494e-02, 6.57040102e-04 ],
41
+ [ 4.45670523e-02, 3.97259220e-02, 7.10966764e-04 ],
42
+ [ 9.43699256e-02, 9.85926315e-02, 2.02551950e-04 ],
43
+ [ 1.61131397e-02, 2.92906128e-02, 3.44733417e-06 ],
44
+ [ 5.23825921e-02, 1.07065082e-01, 6.61510974e-04 ],
45
+ [ 2.85718683e-03, 8.32320191e-03, 2.39314613e-04 ],
46
+ [ 2.57947259e-02, 1.60935968e-02, 2.41853559e-05 ],
47
+ [ 4.90833223e-02, 3.43903080e-02, 3.22353356e-02 ],
48
+ [ 1.44766076e-02, 3.39248963e-02, 1.42291479e-04 ],
49
+ [ 8.75749043e-04, 6.82212645e-03, 2.76097053e-05 ],
50
+ [ 1.86958015e-02, 3.84016186e-02, 7.33085908e-05 ],
51
+ [ 2.01714113e-02, 4.90544215e-02, 2.34028921e-05 ],
52
+ [ 2.46518422e-02, 3.29151377e-02, 3.48571630e-05 ],
53
+ [ 2.22457591e-02, 1.21796541e-02, 1.56396593e-04 ],
54
+ [ 1.72109623e-02, 3.01626958e-02, 1.36556877e-02 ],
55
+ [ 1.83460284e-02, 1.61141958e-02, 2.87440169e-04 ],
56
+ [ 3.57594155e-02, 1.80554688e-01, 2.75554154e-02 ],
57
+ [ 2.17450950e-02, 8.66811201e-02, 3.34241726e-02 ]
58
+ ]
59
+ ]).astype(numpy.float32)
60
+
61
+
62
+ def limit_expression(expression : LivePortraitExpression) -> LivePortraitExpression:
63
+ return numpy.clip(expression, EXPRESSION_MIN, EXPRESSION_MAX)
64
+
65
+
66
+ def limit_euler_angles(target_pitch : LivePortraitPitch, target_yaw : LivePortraitYaw, target_roll : LivePortraitRoll, output_pitch : LivePortraitPitch, output_yaw : LivePortraitYaw, output_roll : LivePortraitRoll) -> Tuple[LivePortraitPitch, LivePortraitYaw, LivePortraitRoll]:
67
+ pitch_min, pitch_max, yaw_min, yaw_max, roll_min, roll_max = calc_euler_limits(target_pitch, target_yaw, target_roll)
68
+ output_pitch = numpy.clip(output_pitch, pitch_min, pitch_max)
69
+ output_yaw = numpy.clip(output_yaw, yaw_min, yaw_max)
70
+ output_roll = numpy.clip(output_roll, roll_min, roll_max)
71
+ return output_pitch, output_yaw, output_roll
72
+
73
+
74
+ def calc_euler_limits(pitch : LivePortraitPitch, yaw : LivePortraitYaw, roll : LivePortraitRoll) -> Tuple[float, float, float, float, float, float]:
75
+ pitch_min = -30.0
76
+ pitch_max = 30.0
77
+ yaw_min = -60.0
78
+ yaw_max = 60.0
79
+ roll_min = -20.0
80
+ roll_max = 20.0
81
+
82
+ if pitch < 0:
83
+ pitch_min = min(pitch, pitch_min)
84
+ else:
85
+ pitch_max = max(pitch, pitch_max)
86
+ if yaw < 0:
87
+ yaw_min = min(yaw, yaw_min)
88
+ else:
89
+ yaw_max = max(yaw, yaw_max)
90
+ if roll < 0:
91
+ roll_min = min(roll, roll_min)
92
+ else:
93
+ roll_max = max(roll, roll_max)
94
+
95
+ return pitch_min, pitch_max, yaw_min, yaw_max, roll_min, roll_max
96
+
97
+
98
+ def create_rotation(pitch : LivePortraitPitch, yaw : LivePortraitYaw, roll : LivePortraitRoll) -> LivePortraitRotation:
99
+ rotation = scipy.spatial.transform.Rotation.from_euler('xyz', [ pitch, yaw, roll ], degrees = True).as_matrix()
100
+ rotation = rotation.astype(numpy.float32)
101
+ return rotation
facefusion/processors/modules/__init__.py ADDED
File without changes
facefusion/processors/modules/age_modifier.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from argparse import ArgumentParser
2
+ from typing import Any, List
3
+
4
+ import cv2
5
+ import numpy
6
+ from cv2.typing import Size
7
+ from numpy.typing import NDArray
8
+
9
+ import facefusion.jobs.job_manager
10
+ import facefusion.jobs.job_store
11
+ import facefusion.processors.core as processors
12
+ from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
13
+ from facefusion.common_helper import create_int_metavar
14
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
15
+ from facefusion.face_analyser import get_many_faces, get_one_face
16
+ from facefusion.face_helper import merge_matrix, paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
17
+ from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
18
+ from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
19
+ from facefusion.face_store import get_reference_faces
20
+ from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
21
+ from facefusion.processors import choices as processors_choices
22
+ from facefusion.processors.typing import AgeModifierInputs
23
+ from facefusion.program_helper import find_argument_group
24
+ from facefusion.thread_helper import thread_semaphore
25
+ from facefusion.typing import ApplyStateItem, Args, Face, InferencePool, Mask, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
26
+ from facefusion.vision import read_image, read_static_image, write_image
27
+
28
+ MODEL_SET : ModelSet =\
29
+ {
30
+ 'styleganex_age':
31
+ {
32
+ 'hashes':
33
+ {
34
+ 'age_modifier':
35
+ {
36
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/styleganex_age.hash',
37
+ 'path': resolve_relative_path('../.assets/models/styleganex_age.hash')
38
+ }
39
+ },
40
+ 'sources':
41
+ {
42
+ 'age_modifier':
43
+ {
44
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/styleganex_age.onnx',
45
+ 'path': resolve_relative_path('../.assets/models/styleganex_age.onnx')
46
+ }
47
+ },
48
+ 'template': 'ffhq_512',
49
+ 'size': (512, 512)
50
+ }
51
+ }
52
+
53
+
54
+ def get_inference_pool() -> InferencePool:
55
+ model_sources = get_model_options().get('sources')
56
+ model_context = __name__ + '.' + state_manager.get_item('age_modifier_model')
57
+ return inference_manager.get_inference_pool(model_context, model_sources)
58
+
59
+
60
+ def clear_inference_pool() -> None:
61
+ model_context = __name__ + '.' + state_manager.get_item('age_modifier_model')
62
+ inference_manager.clear_inference_pool(model_context)
63
+
64
+
65
+ def get_model_options() -> ModelOptions:
66
+ age_modifier_model = state_manager.get_item('age_modifier_model')
67
+ return MODEL_SET.get(age_modifier_model)
68
+
69
+
70
+ def register_args(program : ArgumentParser) -> None:
71
+ group_processors = find_argument_group(program, 'processors')
72
+ if group_processors:
73
+ group_processors.add_argument('--age-modifier-model', help = wording.get('help.age_modifier_model'), default = config.get_str_value('processors.age_modifier_model', 'styleganex_age'), choices = processors_choices.age_modifier_models)
74
+ group_processors.add_argument('--age-modifier-direction', help = wording.get('help.age_modifier_direction'), type = int, default = config.get_int_value('processors.age_modifier_direction', '0'), choices = processors_choices.age_modifier_direction_range, metavar = create_int_metavar(processors_choices.age_modifier_direction_range))
75
+ facefusion.jobs.job_store.register_step_keys([ 'age_modifier_model', 'age_modifier_direction' ])
76
+
77
+
78
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
79
+ apply_state_item('age_modifier_model', args.get('age_modifier_model'))
80
+ apply_state_item('age_modifier_direction', args.get('age_modifier_direction'))
81
+
82
+
83
+ def pre_check() -> bool:
84
+ download_directory_path = resolve_relative_path('../.assets/models')
85
+ model_hashes = get_model_options().get('hashes')
86
+ model_sources = get_model_options().get('sources')
87
+
88
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
89
+
90
+
91
+ def pre_process(mode : ProcessMode) -> bool:
92
+ if mode in [ 'output', 'preview' ] and not is_image(state_manager.get_item('target_path')) and not is_video(state_manager.get_item('target_path')):
93
+ logger.error(wording.get('choose_image_or_video_target') + wording.get('exclamation_mark'), __name__)
94
+ return False
95
+ if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
96
+ logger.error(wording.get('specify_image_or_video_output') + wording.get('exclamation_mark'), __name__)
97
+ return False
98
+ if mode == 'output' and not same_file_extension([ state_manager.get_item('target_path'), state_manager.get_item('output_path') ]):
99
+ logger.error(wording.get('match_target_and_output_extension') + wording.get('exclamation_mark'), __name__)
100
+ return False
101
+ return True
102
+
103
+
104
+ def post_process() -> None:
105
+ read_static_image.cache_clear()
106
+ if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
107
+ clear_inference_pool()
108
+ if state_manager.get_item('video_memory_strategy') == 'strict':
109
+ content_analyser.clear_inference_pool()
110
+ face_classifier.clear_inference_pool()
111
+ face_detector.clear_inference_pool()
112
+ face_landmarker.clear_inference_pool()
113
+ face_masker.clear_inference_pool()
114
+ face_recognizer.clear_inference_pool()
115
+
116
+
117
+ def modify_age(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
118
+ model_template = get_model_options().get('template')
119
+ model_size = get_model_options().get('size')
120
+ crop_size = (model_size[0] // 2, model_size[1] // 2)
121
+ face_landmark_5 = target_face.landmark_set.get('5/68').copy()
122
+ extend_face_landmark_5 = scale_face_landmark_5(face_landmark_5, 2.0)
123
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_template, crop_size)
124
+ extend_vision_frame, extend_affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, extend_face_landmark_5, model_template, model_size)
125
+ extend_vision_frame_raw = extend_vision_frame.copy()
126
+ box_mask = create_static_box_mask(model_size, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
127
+ crop_masks =\
128
+ [
129
+ box_mask
130
+ ]
131
+
132
+ if 'occlusion' in state_manager.get_item('face_mask_types'):
133
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
134
+ combined_matrix = merge_matrix([ extend_affine_matrix, cv2.invertAffineTransform(affine_matrix) ])
135
+ occlusion_mask = cv2.warpAffine(occlusion_mask, combined_matrix, model_size)
136
+ crop_masks.append(occlusion_mask)
137
+
138
+ crop_vision_frame = prepare_vision_frame(crop_vision_frame)
139
+ extend_vision_frame = prepare_vision_frame(extend_vision_frame)
140
+ extend_vision_frame = forward(crop_vision_frame, extend_vision_frame)
141
+ extend_vision_frame = normalize_extend_frame(extend_vision_frame)
142
+ extend_vision_frame = fix_color(extend_vision_frame_raw, extend_vision_frame)
143
+ extend_crop_mask = cv2.pyrUp(numpy.minimum.reduce(crop_masks).clip(0, 1))
144
+ extend_affine_matrix *= extend_vision_frame.shape[0] / 512
145
+ paste_vision_frame = paste_back(temp_vision_frame, extend_vision_frame, extend_crop_mask, extend_affine_matrix)
146
+ return paste_vision_frame
147
+
148
+
149
+ def forward(crop_vision_frame : VisionFrame, extend_vision_frame : VisionFrame) -> VisionFrame:
150
+ age_modifier = get_inference_pool().get('age_modifier')
151
+ age_modifier_inputs = {}
152
+
153
+ for age_modifier_input in age_modifier.get_inputs():
154
+ if age_modifier_input.name == 'target':
155
+ age_modifier_inputs[age_modifier_input.name] = crop_vision_frame
156
+ if age_modifier_input.name == 'target_with_background':
157
+ age_modifier_inputs[age_modifier_input.name] = extend_vision_frame
158
+ if age_modifier_input.name == 'direction':
159
+ age_modifier_inputs[age_modifier_input.name] = prepare_direction(state_manager.get_item('age_modifier_direction'))
160
+
161
+ with thread_semaphore():
162
+ crop_vision_frame = age_modifier.run(None, age_modifier_inputs)[0][0]
163
+
164
+ return crop_vision_frame
165
+
166
+
167
+ def fix_color(extend_vision_frame_raw : VisionFrame, extend_vision_frame : VisionFrame) -> VisionFrame:
168
+ color_difference = compute_color_difference(extend_vision_frame_raw, extend_vision_frame, (48, 48))
169
+ color_difference_mask = create_static_box_mask(extend_vision_frame.shape[:2][::-1], 1.0, (0, 0, 0, 0))
170
+ color_difference_mask = numpy.stack((color_difference_mask, ) * 3, axis = -1)
171
+ extend_vision_frame = normalize_color_difference(color_difference, color_difference_mask, extend_vision_frame)
172
+ return extend_vision_frame
173
+
174
+
175
+ def compute_color_difference(extend_vision_frame_raw : VisionFrame, extend_vision_frame : VisionFrame, size : Size) -> VisionFrame:
176
+ extend_vision_frame_raw = extend_vision_frame_raw.astype(numpy.float32) / 255
177
+ extend_vision_frame_raw = cv2.resize(extend_vision_frame_raw, size, interpolation = cv2.INTER_AREA)
178
+ extend_vision_frame = extend_vision_frame.astype(numpy.float32) / 255
179
+ extend_vision_frame = cv2.resize(extend_vision_frame, size, interpolation = cv2.INTER_AREA)
180
+ color_difference = extend_vision_frame_raw - extend_vision_frame
181
+ return color_difference
182
+
183
+
184
+ def normalize_color_difference(color_difference : VisionFrame, color_difference_mask : Mask, extend_vision_frame : VisionFrame) -> VisionFrame:
185
+ color_difference = cv2.resize(color_difference, extend_vision_frame.shape[:2][::-1], interpolation = cv2.INTER_CUBIC)
186
+ color_difference_mask = 1 - color_difference_mask.clip(0, 0.75)
187
+ extend_vision_frame = extend_vision_frame.astype(numpy.float32) / 255
188
+ extend_vision_frame += color_difference * color_difference_mask
189
+ extend_vision_frame = extend_vision_frame.clip(0, 1)
190
+ extend_vision_frame = numpy.multiply(extend_vision_frame, 255).astype(numpy.uint8)
191
+ return extend_vision_frame
192
+
193
+
194
+ def prepare_direction(direction : int) -> NDArray[Any]:
195
+ direction = numpy.interp(float(direction), [ -100, 100 ], [ 2.5, -2.5 ]) #type:ignore[assignment]
196
+ return numpy.array(direction).astype(numpy.float32)
197
+
198
+
199
+ def prepare_vision_frame(vision_frame : VisionFrame) -> VisionFrame:
200
+ vision_frame = vision_frame[:, :, ::-1] / 255.0
201
+ vision_frame = (vision_frame - 0.5) / 0.5
202
+ vision_frame = numpy.expand_dims(vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
203
+ return vision_frame
204
+
205
+
206
+ def normalize_extend_frame(extend_vision_frame : VisionFrame) -> VisionFrame:
207
+ extend_vision_frame = numpy.clip(extend_vision_frame, -1, 1)
208
+ extend_vision_frame = (extend_vision_frame + 1) / 2
209
+ extend_vision_frame = extend_vision_frame.transpose(1, 2, 0).clip(0, 255)
210
+ extend_vision_frame = (extend_vision_frame * 255.0)
211
+ extend_vision_frame = extend_vision_frame.astype(numpy.uint8)[:, :, ::-1]
212
+ extend_vision_frame = cv2.pyrDown(extend_vision_frame)
213
+ return extend_vision_frame
214
+
215
+
216
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
217
+ return modify_age(target_face, temp_vision_frame)
218
+
219
+
220
+ def process_frame(inputs : AgeModifierInputs) -> VisionFrame:
221
+ reference_faces = inputs.get('reference_faces')
222
+ target_vision_frame = inputs.get('target_vision_frame')
223
+ many_faces = sort_and_filter_faces(get_many_faces([ target_vision_frame ]))
224
+
225
+ if state_manager.get_item('face_selector_mode') == 'many':
226
+ if many_faces:
227
+ for target_face in many_faces:
228
+ target_vision_frame = modify_age(target_face, target_vision_frame)
229
+ if state_manager.get_item('face_selector_mode') == 'one':
230
+ target_face = get_one_face(many_faces)
231
+ if target_face:
232
+ target_vision_frame = modify_age(target_face, target_vision_frame)
233
+ if state_manager.get_item('face_selector_mode') == 'reference':
234
+ similar_faces = find_similar_faces(many_faces, reference_faces, state_manager.get_item('reference_face_distance'))
235
+ if similar_faces:
236
+ for similar_face in similar_faces:
237
+ target_vision_frame = modify_age(similar_face, target_vision_frame)
238
+ return target_vision_frame
239
+
240
+
241
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
242
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
243
+
244
+ for queue_payload in process_manager.manage(queue_payloads):
245
+ target_vision_path = queue_payload['frame_path']
246
+ target_vision_frame = read_image(target_vision_path)
247
+ output_vision_frame = process_frame(
248
+ {
249
+ 'reference_faces': reference_faces,
250
+ 'target_vision_frame': target_vision_frame
251
+ })
252
+ write_image(target_vision_path, output_vision_frame)
253
+ update_progress(1)
254
+
255
+
256
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
257
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
258
+ target_vision_frame = read_static_image(target_path)
259
+ output_vision_frame = process_frame(
260
+ {
261
+ 'reference_faces': reference_faces,
262
+ 'target_vision_frame': target_vision_frame
263
+ })
264
+ write_image(output_path, output_vision_frame)
265
+
266
+
267
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
268
+ processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/modules/expression_restorer.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from argparse import ArgumentParser
2
+ from typing import List, Tuple
3
+
4
+ import cv2
5
+ import numpy
6
+
7
+ import facefusion.jobs.job_manager
8
+ import facefusion.jobs.job_store
9
+ import facefusion.processors.core as processors
10
+ from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
11
+ from facefusion.common_helper import create_int_metavar
12
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
13
+ from facefusion.face_analyser import get_many_faces, get_one_face
14
+ from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
15
+ from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
16
+ from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
17
+ from facefusion.face_store import get_reference_faces
18
+ from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
19
+ from facefusion.processors import choices as processors_choices
20
+ from facefusion.processors.live_portrait import create_rotation, limit_expression
21
+ from facefusion.processors.typing import ExpressionRestorerInputs
22
+ from facefusion.processors.typing import LivePortraitExpression, LivePortraitFeatureVolume, LivePortraitMotionPoints, LivePortraitPitch, LivePortraitRoll, LivePortraitScale, LivePortraitTranslation, LivePortraitYaw
23
+ from facefusion.program_helper import find_argument_group
24
+ from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
25
+ from facefusion.typing import ApplyStateItem, Args, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
26
+ from facefusion.vision import get_video_frame, read_image, read_static_image, write_image
27
+
28
+ MODEL_SET : ModelSet =\
29
+ {
30
+ 'live_portrait':
31
+ {
32
+ 'hashes':
33
+ {
34
+ 'feature_extractor':
35
+ {
36
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_feature_extractor.hash',
37
+ 'path': resolve_relative_path('../.assets/models/live_portrait_feature_extractor.hash')
38
+ },
39
+ 'motion_extractor':
40
+ {
41
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_motion_extractor.hash',
42
+ 'path': resolve_relative_path('../.assets/models/live_portrait_motion_extractor.hash')
43
+ },
44
+ 'generator':
45
+ {
46
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_generator.hash',
47
+ 'path': resolve_relative_path('../.assets/models/live_portrait_generator.hash')
48
+ }
49
+ },
50
+ 'sources':
51
+ {
52
+ 'feature_extractor':
53
+ {
54
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_feature_extractor.onnx',
55
+ 'path': resolve_relative_path('../.assets/models/live_portrait_feature_extractor.onnx')
56
+ },
57
+ 'motion_extractor':
58
+ {
59
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_motion_extractor.onnx',
60
+ 'path': resolve_relative_path('../.assets/models/live_portrait_motion_extractor.onnx')
61
+ },
62
+ 'generator':
63
+ {
64
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_generator.onnx',
65
+ 'path': resolve_relative_path('../.assets/models/live_portrait_generator.onnx')
66
+ }
67
+ },
68
+ 'template': 'arcface_128_v2',
69
+ 'size': (512, 512)
70
+ }
71
+ }
72
+
73
+
74
+ def get_inference_pool() -> InferencePool:
75
+ model_sources = get_model_options().get('sources')
76
+ model_context = __name__ + '.' + state_manager.get_item('expression_restorer_model')
77
+ return inference_manager.get_inference_pool(model_context, model_sources)
78
+
79
+
80
+ def clear_inference_pool() -> None:
81
+ inference_manager.clear_inference_pool(__name__)
82
+
83
+
84
+ def get_model_options() -> ModelOptions:
85
+ expression_restorer_model = state_manager.get_item('expression_restorer_model')
86
+ return MODEL_SET.get(expression_restorer_model)
87
+
88
+
89
+ def register_args(program : ArgumentParser) -> None:
90
+ group_processors = find_argument_group(program, 'processors')
91
+ if group_processors:
92
+ group_processors.add_argument('--expression-restorer-model', help = wording.get('help.expression_restorer_model'), default = config.get_str_value('processors.expression_restorer_model', 'live_portrait'), choices = processors_choices.expression_restorer_models)
93
+ group_processors.add_argument('--expression-restorer-factor', help = wording.get('help.expression_restorer_factor'), type = int, default = config.get_int_value('processors.expression_restorer_factor', '80'), choices = processors_choices.expression_restorer_factor_range, metavar = create_int_metavar(processors_choices.expression_restorer_factor_range))
94
+ facefusion.jobs.job_store.register_step_keys([ 'expression_restorer_model','expression_restorer_factor' ])
95
+
96
+
97
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
98
+ apply_state_item('expression_restorer_model', args.get('expression_restorer_model'))
99
+ apply_state_item('expression_restorer_factor', args.get('expression_restorer_factor'))
100
+
101
+
102
+ def pre_check() -> bool:
103
+ download_directory_path = resolve_relative_path('../.assets/models')
104
+ model_hashes = get_model_options().get('hashes')
105
+ model_sources = get_model_options().get('sources')
106
+
107
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
108
+
109
+
110
+ def pre_process(mode : ProcessMode) -> bool:
111
+ if mode in [ 'output', 'preview' ] and not is_image(state_manager.get_item('target_path')) and not is_video(state_manager.get_item('target_path')):
112
+ logger.error(wording.get('choose_image_or_video_target') + wording.get('exclamation_mark'), __name__)
113
+ return False
114
+ if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
115
+ logger.error(wording.get('specify_image_or_video_output') + wording.get('exclamation_mark'), __name__)
116
+ return False
117
+ if mode == 'output' and not same_file_extension([ state_manager.get_item('target_path'), state_manager.get_item('output_path') ]):
118
+ logger.error(wording.get('match_target_and_output_extension') + wording.get('exclamation_mark'), __name__)
119
+ return False
120
+ return True
121
+
122
+
123
+ def post_process() -> None:
124
+ read_static_image.cache_clear()
125
+ if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
126
+ clear_inference_pool()
127
+ if state_manager.get_item('video_memory_strategy') == 'strict':
128
+ content_analyser.clear_inference_pool()
129
+ face_classifier.clear_inference_pool()
130
+ face_detector.clear_inference_pool()
131
+ face_landmarker.clear_inference_pool()
132
+ face_masker.clear_inference_pool()
133
+ face_recognizer.clear_inference_pool()
134
+
135
+
136
+ def restore_expression(source_vision_frame : VisionFrame, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
137
+ model_template = get_model_options().get('template')
138
+ model_size = get_model_options().get('size')
139
+ expression_restorer_factor = float(numpy.interp(float(state_manager.get_item('expression_restorer_factor')), [ 0, 100 ], [ 0, 1.2 ]))
140
+ source_vision_frame = cv2.resize(source_vision_frame, temp_vision_frame.shape[:2][::-1])
141
+ source_crop_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, target_face.landmark_set.get('5/68'), model_template, model_size)
142
+ 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)
143
+ box_mask = create_static_box_mask(target_crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
144
+ crop_masks =\
145
+ [
146
+ box_mask
147
+ ]
148
+
149
+ if 'occlusion' in state_manager.get_item('face_mask_types'):
150
+ occlusion_mask = create_occlusion_mask(target_crop_vision_frame)
151
+ crop_masks.append(occlusion_mask)
152
+
153
+ source_crop_vision_frame = prepare_crop_frame(source_crop_vision_frame)
154
+ target_crop_vision_frame = prepare_crop_frame(target_crop_vision_frame)
155
+ target_crop_vision_frame = apply_restore(source_crop_vision_frame, target_crop_vision_frame, expression_restorer_factor)
156
+ target_crop_vision_frame = normalize_crop_frame(target_crop_vision_frame)
157
+ crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
158
+ temp_vision_frame = paste_back(temp_vision_frame, target_crop_vision_frame, crop_mask, affine_matrix)
159
+ return temp_vision_frame
160
+
161
+
162
+ def apply_restore(source_crop_vision_frame : VisionFrame, target_crop_vision_frame : VisionFrame, expression_restorer_factor : float) -> VisionFrame:
163
+ feature_volume = forward_extract_feature(target_crop_vision_frame)
164
+ source_expression = forward_extract_motion(source_crop_vision_frame)[5]
165
+ pitch, yaw, roll, scale, translation, target_expression, motion_points = forward_extract_motion(target_crop_vision_frame)
166
+ rotation = create_rotation(pitch, yaw, roll)
167
+ source_expression[:, [ 0, 4, 5, 8, 9 ]] = target_expression[:, [ 0, 4, 5, 8, 9 ]]
168
+ source_expression = source_expression * expression_restorer_factor + target_expression * (1 - expression_restorer_factor)
169
+ source_expression = limit_expression(source_expression)
170
+ source_motion_points = scale * (motion_points @ rotation.T + source_expression) + translation
171
+ target_motion_points = scale * (motion_points @ rotation.T + target_expression) + translation
172
+ crop_vision_frame = forward_generate_frame(feature_volume, source_motion_points, target_motion_points)
173
+ return crop_vision_frame
174
+
175
+
176
+ def forward_extract_feature(crop_vision_frame : VisionFrame) -> LivePortraitFeatureVolume:
177
+ feature_extractor = get_inference_pool().get('feature_extractor')
178
+
179
+ with conditional_thread_semaphore():
180
+ feature_volume = feature_extractor.run(None,
181
+ {
182
+ 'input': crop_vision_frame
183
+ })[0]
184
+
185
+ return feature_volume
186
+
187
+
188
+ def forward_extract_motion(crop_vision_frame : VisionFrame) -> Tuple[LivePortraitPitch, LivePortraitYaw, LivePortraitRoll, LivePortraitScale, LivePortraitTranslation, LivePortraitExpression, LivePortraitMotionPoints]:
189
+ motion_extractor = get_inference_pool().get('motion_extractor')
190
+
191
+ with conditional_thread_semaphore():
192
+ pitch, yaw, roll, scale, translation, expression, motion_points = motion_extractor.run(None,
193
+ {
194
+ 'input': crop_vision_frame
195
+ })
196
+
197
+ return pitch, yaw, roll, scale, translation, expression, motion_points
198
+
199
+
200
+ def forward_generate_frame(feature_volume : LivePortraitFeatureVolume, source_motion_points : LivePortraitMotionPoints, target_motion_points : LivePortraitMotionPoints) -> VisionFrame:
201
+ generator = get_inference_pool().get('generator')
202
+
203
+ with thread_semaphore():
204
+ crop_vision_frame = generator.run(None,
205
+ {
206
+ 'feature_volume': feature_volume,
207
+ 'source': source_motion_points,
208
+ 'target': target_motion_points
209
+ })[0][0]
210
+
211
+ return crop_vision_frame
212
+
213
+
214
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
215
+ model_size = get_model_options().get('size')
216
+ prepare_size = (model_size[0] // 2, model_size[1] // 2)
217
+ crop_vision_frame = cv2.resize(crop_vision_frame, prepare_size, interpolation = cv2.INTER_AREA)
218
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
219
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
220
+ return crop_vision_frame
221
+
222
+
223
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
224
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0).clip(0, 1)
225
+ crop_vision_frame = (crop_vision_frame * 255.0)
226
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1]
227
+ return crop_vision_frame
228
+
229
+
230
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
231
+ pass
232
+
233
+
234
+ def process_frame(inputs : ExpressionRestorerInputs) -> VisionFrame:
235
+ reference_faces = inputs.get('reference_faces')
236
+ source_vision_frame = inputs.get('source_vision_frame')
237
+ target_vision_frame = inputs.get('target_vision_frame')
238
+ many_faces = sort_and_filter_faces(get_many_faces([ target_vision_frame ]))
239
+
240
+ if state_manager.get_item('face_selector_mode') == 'many':
241
+ if many_faces:
242
+ for target_face in many_faces:
243
+ target_vision_frame = restore_expression(source_vision_frame, target_face, target_vision_frame)
244
+ if state_manager.get_item('face_selector_mode') == 'one':
245
+ target_face = get_one_face(many_faces)
246
+ if target_face:
247
+ target_vision_frame = restore_expression(source_vision_frame, target_face, target_vision_frame)
248
+ if state_manager.get_item('face_selector_mode') == 'reference':
249
+ similar_faces = find_similar_faces(many_faces, reference_faces, state_manager.get_item('reference_face_distance'))
250
+ if similar_faces:
251
+ for similar_face in similar_faces:
252
+ target_vision_frame = restore_expression(source_vision_frame, similar_face, target_vision_frame)
253
+ return target_vision_frame
254
+
255
+
256
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
257
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
258
+
259
+ for queue_payload in process_manager.manage(queue_payloads):
260
+ frame_number = queue_payload.get('frame_number')
261
+ if state_manager.get_item('trim_frame_start'):
262
+ frame_number += state_manager.get_item('trim_frame_start')
263
+ source_vision_frame = get_video_frame(state_manager.get_item('target_path'), frame_number)
264
+ target_vision_path = queue_payload.get('frame_path')
265
+ target_vision_frame = read_image(target_vision_path)
266
+ output_vision_frame = process_frame(
267
+ {
268
+ 'reference_faces': reference_faces,
269
+ 'source_vision_frame': source_vision_frame,
270
+ 'target_vision_frame': target_vision_frame
271
+ })
272
+ write_image(target_vision_path, output_vision_frame)
273
+ update_progress(1)
274
+
275
+
276
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
277
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
278
+ source_vision_frame = read_static_image(state_manager.get_item('target_path'))
279
+ target_vision_frame = read_static_image(target_path)
280
+ output_vision_frame = process_frame(
281
+ {
282
+ 'reference_faces': reference_faces,
283
+ 'source_vision_frame': source_vision_frame,
284
+ 'target_vision_frame': target_vision_frame
285
+ })
286
+ write_image(output_path, output_vision_frame)
287
+
288
+
289
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
290
+ processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/modules/face_debugger.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from argparse import ArgumentParser
2
+ from typing import List
3
+
4
+ import cv2
5
+ import numpy
6
+
7
+ import facefusion.jobs.job_manager
8
+ import facefusion.jobs.job_store
9
+ import facefusion.processors.core as processors
10
+ from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, process_manager, state_manager, wording
11
+ from facefusion.face_analyser import get_many_faces, get_one_face
12
+ from facefusion.face_helper import warp_face_by_face_landmark_5
13
+ from facefusion.face_masker import create_occlusion_mask, create_region_mask, create_static_box_mask
14
+ from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
15
+ from facefusion.face_store import get_reference_faces
16
+ from facefusion.filesystem import in_directory, same_file_extension
17
+ from facefusion.processors import choices as processors_choices
18
+ from facefusion.processors.typing import FaceDebuggerInputs
19
+ from facefusion.program_helper import find_argument_group
20
+ from facefusion.typing import ApplyStateItem, Args, Face, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
21
+ from facefusion.vision import read_image, read_static_image, write_image
22
+
23
+
24
+ def get_inference_pool() -> None:
25
+ pass
26
+
27
+
28
+ def clear_inference_pool() -> None:
29
+ pass
30
+
31
+
32
+ def register_args(program : ArgumentParser) -> None:
33
+ group_processors = find_argument_group(program, 'processors')
34
+ if group_processors:
35
+ group_processors.add_argument('--face-debugger-items', help = wording.get('help.face_debugger_items').format(choices = ', '.join(processors_choices.face_debugger_items)), default = config.get_str_list('processors.face_debugger_items', 'face-landmark-5/68 face-mask'), choices = processors_choices.face_debugger_items, nargs = '+', metavar = 'FACE_DEBUGGER_ITEMS')
36
+ facefusion.jobs.job_store.register_step_keys([ 'face_debugger_items' ])
37
+
38
+
39
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
40
+ apply_state_item('face_debugger_items', args.get('face_debugger_items'))
41
+
42
+
43
+ def pre_check() -> bool:
44
+ return True
45
+
46
+
47
+ def pre_process(mode : ProcessMode) -> bool:
48
+ if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
49
+ logger.error(wording.get('specify_image_or_video_output') + wording.get('exclamation_mark'), __name__)
50
+ return False
51
+ if mode == 'output' and not same_file_extension([ state_manager.get_item('target_path'), state_manager.get_item('output_path') ]):
52
+ logger.error(wording.get('match_target_and_output_extension') + wording.get('exclamation_mark'), __name__)
53
+ return False
54
+ return True
55
+
56
+
57
+ def post_process() -> None:
58
+ read_static_image.cache_clear()
59
+ if state_manager.get_item('video_memory_strategy') == 'strict':
60
+ content_analyser.clear_inference_pool()
61
+ face_classifier.clear_inference_pool()
62
+ face_detector.clear_inference_pool()
63
+ face_landmarker.clear_inference_pool()
64
+ face_masker.clear_inference_pool()
65
+ face_recognizer.clear_inference_pool()
66
+
67
+
68
+ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
69
+ primary_color = (0, 0, 255)
70
+ primary_light_color = (100, 100, 255)
71
+ secondary_color = (0, 255, 0)
72
+ tertiary_color = (255, 255, 0)
73
+ bounding_box = target_face.bounding_box.astype(numpy.int32)
74
+ temp_vision_frame = temp_vision_frame.copy()
75
+ has_face_landmark_5_fallback = numpy.array_equal(target_face.landmark_set.get('5'), target_face.landmark_set.get('5/68'))
76
+ has_face_landmark_68_fallback = numpy.array_equal(target_face.landmark_set.get('68'), target_face.landmark_set.get('68/5'))
77
+ face_debugger_items = state_manager.get_item('face_debugger_items')
78
+
79
+ if 'bounding-box' in face_debugger_items:
80
+ x1, y1, x2, y2 = bounding_box
81
+ cv2.rectangle(temp_vision_frame, (x1, y1), (x2, y2), primary_color, 2)
82
+
83
+ if target_face.angle == 0:
84
+ cv2.line(temp_vision_frame, (x1, y1), (x2, y1), primary_light_color, 3)
85
+ elif target_face.angle == 180:
86
+ cv2.line(temp_vision_frame, (x1, y2), (x2, y2), primary_light_color, 3)
87
+ elif target_face.angle == 90:
88
+ cv2.line(temp_vision_frame, (x2, y1), (x2, y2), primary_light_color, 3)
89
+ elif target_face.angle == 270:
90
+ cv2.line(temp_vision_frame, (x1, y1), (x1, y2), primary_light_color, 3)
91
+
92
+ if 'face-mask' in face_debugger_items:
93
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmark_set.get('5/68'), 'arcface_128_v2', (512, 512))
94
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
95
+ temp_size = temp_vision_frame.shape[:2][::-1]
96
+ crop_masks = []
97
+
98
+ if 'box' in state_manager.get_item('face_mask_types'):
99
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], 0, state_manager.get_item('face_mask_padding'))
100
+ crop_masks.append(box_mask)
101
+
102
+ if 'occlusion' in state_manager.get_item('face_mask_types'):
103
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
104
+ crop_masks.append(occlusion_mask)
105
+
106
+ if 'region' in state_manager.get_item('face_mask_types'):
107
+ region_mask = create_region_mask(crop_vision_frame, state_manager.get_item('face_mask_regions'))
108
+ crop_masks.append(region_mask)
109
+
110
+ crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
111
+ crop_mask = (crop_mask * 255).astype(numpy.uint8)
112
+ inverse_vision_frame = cv2.warpAffine(crop_mask, inverse_matrix, temp_size)
113
+ inverse_vision_frame = cv2.threshold(inverse_vision_frame, 100, 255, cv2.THRESH_BINARY)[1]
114
+ inverse_vision_frame[inverse_vision_frame > 0] = 255 #type:ignore[operator]
115
+ inverse_contours = cv2.findContours(inverse_vision_frame, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)[0]
116
+ cv2.drawContours(temp_vision_frame, inverse_contours, -1, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2)
117
+
118
+ if 'face-landmark-5' in face_debugger_items and numpy.any(target_face.landmark_set.get('5')):
119
+ face_landmark_5 = target_face.landmark_set.get('5').astype(numpy.int32)
120
+ for index in range(face_landmark_5.shape[0]):
121
+ cv2.circle(temp_vision_frame, (face_landmark_5[index][0], face_landmark_5[index][1]), 3, primary_color, -1)
122
+
123
+ if 'face-landmark-5/68' in face_debugger_items and numpy.any(target_face.landmark_set.get('5/68')):
124
+ face_landmark_5_68 = target_face.landmark_set.get('5/68').astype(numpy.int32)
125
+ for index in range(face_landmark_5_68.shape[0]):
126
+ cv2.circle(temp_vision_frame, (face_landmark_5_68[index][0], face_landmark_5_68[index][1]), 3, tertiary_color if has_face_landmark_5_fallback else secondary_color, -1)
127
+
128
+ if 'face-landmark-68' in face_debugger_items and numpy.any(target_face.landmark_set.get('68')):
129
+ face_landmark_68 = target_face.landmark_set.get('68').astype(numpy.int32)
130
+ for index in range(face_landmark_68.shape[0]):
131
+ cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, tertiary_color if has_face_landmark_68_fallback else secondary_color, -1)
132
+
133
+ if 'face-landmark-68/5' in face_debugger_items and numpy.any(target_face.landmark_set.get('68')):
134
+ face_landmark_68 = target_face.landmark_set.get('68/5').astype(numpy.int32)
135
+ for index in range(face_landmark_68.shape[0]):
136
+ cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, tertiary_color, -1)
137
+
138
+ if bounding_box[3] - bounding_box[1] > 50 and bounding_box[2] - bounding_box[0] > 50:
139
+ top = bounding_box[1]
140
+ left = bounding_box[0] - 20
141
+
142
+ if 'face-detector-score' in face_debugger_items:
143
+ face_score_text = str(round(target_face.score_set.get('detector'), 2))
144
+ top = top + 20
145
+ cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
146
+
147
+ if 'face-landmarker-score' in face_debugger_items:
148
+ face_score_text = str(round(target_face.score_set.get('landmarker'), 2))
149
+ top = top + 20
150
+ cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2)
151
+
152
+ if 'age' in face_debugger_items:
153
+ face_age_text = str(target_face.age.start) + '-' + str(target_face.age.stop)
154
+ top = top + 20
155
+ cv2.putText(temp_vision_frame, face_age_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
156
+
157
+ if 'gender' in face_debugger_items:
158
+ face_gender_text = target_face.gender
159
+ top = top + 20
160
+ cv2.putText(temp_vision_frame, face_gender_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
161
+
162
+ if 'race' in face_debugger_items:
163
+ face_race_text = target_face.race
164
+ top = top + 20
165
+ cv2.putText(temp_vision_frame, face_race_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
166
+
167
+ return temp_vision_frame
168
+
169
+
170
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
171
+ pass
172
+
173
+
174
+ def process_frame(inputs : FaceDebuggerInputs) -> VisionFrame:
175
+ reference_faces = inputs.get('reference_faces')
176
+ target_vision_frame = inputs.get('target_vision_frame')
177
+ many_faces = sort_and_filter_faces(get_many_faces([ target_vision_frame ]))
178
+
179
+ if state_manager.get_item('face_selector_mode') == 'many':
180
+ if many_faces:
181
+ for target_face in many_faces:
182
+ target_vision_frame = debug_face(target_face, target_vision_frame)
183
+ if state_manager.get_item('face_selector_mode') == 'one':
184
+ target_face = get_one_face(many_faces)
185
+ if target_face:
186
+ target_vision_frame = debug_face(target_face, target_vision_frame)
187
+ if state_manager.get_item('face_selector_mode') == 'reference':
188
+ similar_faces = find_similar_faces(many_faces, reference_faces, state_manager.get_item('reference_face_distance'))
189
+ if similar_faces:
190
+ for similar_face in similar_faces:
191
+ target_vision_frame = debug_face(similar_face, target_vision_frame)
192
+ return target_vision_frame
193
+
194
+
195
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
196
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
197
+
198
+ for queue_payload in process_manager.manage(queue_payloads):
199
+ target_vision_path = queue_payload['frame_path']
200
+ target_vision_frame = read_image(target_vision_path)
201
+ output_vision_frame = process_frame(
202
+ {
203
+ 'reference_faces': reference_faces,
204
+ 'target_vision_frame': target_vision_frame
205
+ })
206
+ write_image(target_vision_path, output_vision_frame)
207
+ update_progress(1)
208
+
209
+
210
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
211
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
212
+ target_vision_frame = read_static_image(target_path)
213
+ output_vision_frame = process_frame(
214
+ {
215
+ 'reference_faces': reference_faces,
216
+ 'target_vision_frame': target_vision_frame
217
+ })
218
+ write_image(output_path, output_vision_frame)
219
+
220
+
221
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
222
+ processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/modules/face_editor.py ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from argparse import ArgumentParser
2
+ from typing import List, Tuple
3
+
4
+ import cv2
5
+ import numpy
6
+
7
+ import facefusion.jobs.job_manager
8
+ import facefusion.jobs.job_store
9
+ import facefusion.processors.core as processors
10
+ from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
11
+ from facefusion.common_helper import create_float_metavar
12
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
13
+ from facefusion.face_analyser import get_many_faces, get_one_face
14
+ from facefusion.face_helper import paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
15
+ from facefusion.face_masker import create_static_box_mask
16
+ from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
17
+ from facefusion.face_store import get_reference_faces
18
+ from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
19
+ from facefusion.processors import choices as processors_choices
20
+ from facefusion.processors.live_portrait import create_rotation, limit_euler_angles, limit_expression
21
+ from facefusion.processors.typing import FaceEditorInputs, LivePortraitExpression, LivePortraitFeatureVolume, LivePortraitMotionPoints, LivePortraitPitch, LivePortraitRoll, LivePortraitRotation, LivePortraitScale, LivePortraitTranslation, LivePortraitYaw
22
+ from facefusion.program_helper import find_argument_group
23
+ from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
24
+ from facefusion.typing import ApplyStateItem, Args, Face, FaceLandmark68, InferencePool, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
25
+ from facefusion.vision import read_image, read_static_image, write_image
26
+
27
+ MODEL_SET : ModelSet =\
28
+ {
29
+ 'live_portrait':
30
+ {
31
+ 'hashes':
32
+ {
33
+ 'feature_extractor':
34
+ {
35
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_feature_extractor.hash',
36
+ 'path': resolve_relative_path('../.assets/models/live_portrait_feature_extractor.hash')
37
+ },
38
+ 'motion_extractor':
39
+ {
40
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_motion_extractor.hash',
41
+ 'path': resolve_relative_path('../.assets/models/live_portrait_motion_extractor.hash')
42
+ },
43
+ 'eye_retargeter':
44
+ {
45
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_eye_retargeter.hash',
46
+ 'path': resolve_relative_path('../.assets/models/live_portrait_eye_retargeter.hash')
47
+ },
48
+ 'lip_retargeter':
49
+ {
50
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_lip_retargeter.hash',
51
+ 'path': resolve_relative_path('../.assets/models/live_portrait_lip_retargeter.hash')
52
+ },
53
+ 'stitcher':
54
+ {
55
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_stitcher.hash',
56
+ 'path': resolve_relative_path('../.assets/models/live_portrait_stitcher.hash')
57
+ },
58
+ 'generator':
59
+ {
60
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_generator.hash',
61
+ 'path': resolve_relative_path('../.assets/models/live_portrait_generator.hash')
62
+ }
63
+ },
64
+ 'sources':
65
+ {
66
+ 'feature_extractor':
67
+ {
68
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_feature_extractor.onnx',
69
+ 'path': resolve_relative_path('../.assets/models/live_portrait_feature_extractor.onnx')
70
+ },
71
+ 'motion_extractor':
72
+ {
73
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_motion_extractor.onnx',
74
+ 'path': resolve_relative_path('../.assets/models/live_portrait_motion_extractor.onnx')
75
+ },
76
+ 'eye_retargeter':
77
+ {
78
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_eye_retargeter.onnx',
79
+ 'path': resolve_relative_path('../.assets/models/live_portrait_eye_retargeter.onnx')
80
+ },
81
+ 'lip_retargeter':
82
+ {
83
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_lip_retargeter.onnx',
84
+ 'path': resolve_relative_path('../.assets/models/live_portrait_lip_retargeter.onnx')
85
+ },
86
+ 'stitcher':
87
+ {
88
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_stitcher.onnx',
89
+ 'path': resolve_relative_path('../.assets/models/live_portrait_stitcher.onnx')
90
+ },
91
+ 'generator':
92
+ {
93
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/live_portrait_generator.onnx',
94
+ 'path': resolve_relative_path('../.assets/models/live_portrait_generator.onnx')
95
+ }
96
+ },
97
+ 'template': 'ffhq_512',
98
+ 'size': (512, 512)
99
+ }
100
+ }
101
+
102
+
103
+ def get_inference_pool() -> InferencePool:
104
+ model_sources = get_model_options().get('sources')
105
+ model_context = __name__ + '.' + state_manager.get_item('face_editor_model')
106
+ return inference_manager.get_inference_pool(model_context, model_sources)
107
+
108
+
109
+ def clear_inference_pool() -> None:
110
+ model_context = __name__ + '.' + state_manager.get_item('face_editor_model')
111
+ inference_manager.clear_inference_pool(model_context)
112
+
113
+
114
+ def get_model_options() -> ModelOptions:
115
+ face_editor_model = state_manager.get_item('face_editor_model')
116
+ return MODEL_SET.get(face_editor_model)
117
+
118
+
119
+ def register_args(program : ArgumentParser) -> None:
120
+ group_processors = find_argument_group(program, 'processors')
121
+ if group_processors:
122
+ group_processors.add_argument('--face-editor-model', help = wording.get('help.face_editor_model'), default = config.get_str_value('processors.face_editor_model', 'live_portrait'), choices = processors_choices.face_editor_models)
123
+ group_processors.add_argument('--face-editor-eyebrow-direction', help = wording.get('help.face_editor_eyebrow_direction'), type = float, default = config.get_float_value('processors.face_editor_eyebrow_direction', '0'), choices = processors_choices.face_editor_eyebrow_direction_range, metavar = create_float_metavar(processors_choices.face_editor_eyebrow_direction_range))
124
+ group_processors.add_argument('--face-editor-eye-gaze-horizontal', help = wording.get('help.face_editor_eye_gaze_horizontal'), type = float, default = config.get_float_value('processors.face_editor_eye_gaze_horizontal', '0'), choices = processors_choices.face_editor_eye_gaze_horizontal_range, metavar = create_float_metavar(processors_choices.face_editor_eye_gaze_horizontal_range))
125
+ group_processors.add_argument('--face-editor-eye-gaze-vertical', help = wording.get('help.face_editor_eye_gaze_vertical'), type = float, default = config.get_float_value('processors.face_editor_eye_gaze_vertical', '0'), choices = processors_choices.face_editor_eye_gaze_vertical_range, metavar = create_float_metavar(processors_choices.face_editor_eye_gaze_vertical_range))
126
+ group_processors.add_argument('--face-editor-eye-open-ratio', help = wording.get('help.face_editor_eye_open_ratio'), type = float, default = config.get_float_value('processors.face_editor_eye_open_ratio', '0'), choices = processors_choices.face_editor_eye_open_ratio_range, metavar = create_float_metavar(processors_choices.face_editor_eye_open_ratio_range))
127
+ group_processors.add_argument('--face-editor-lip-open-ratio', help = wording.get('help.face_editor_lip_open_ratio'), type = float, default = config.get_float_value('processors.face_editor_lip_open_ratio', '0'), choices = processors_choices.face_editor_lip_open_ratio_range, metavar = create_float_metavar(processors_choices.face_editor_lip_open_ratio_range))
128
+ group_processors.add_argument('--face-editor-mouth-grim', help = wording.get('help.face_editor_mouth_grim'), type = float, default = config.get_float_value('processors.face_editor_mouth_grim', '0'), choices = processors_choices.face_editor_mouth_grim_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_grim_range))
129
+ group_processors.add_argument('--face-editor-mouth-pout', help = wording.get('help.face_editor_mouth_pout'), type = float, default = config.get_float_value('processors.face_editor_mouth_pout', '0'), choices = processors_choices.face_editor_mouth_pout_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_pout_range))
130
+ group_processors.add_argument('--face-editor-mouth-purse', help = wording.get('help.face_editor_mouth_purse'), type = float, default = config.get_float_value('processors.face_editor_mouth_purse', '0'), choices = processors_choices.face_editor_mouth_purse_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_purse_range))
131
+ group_processors.add_argument('--face-editor-mouth-smile', help = wording.get('help.face_editor_mouth_smile'), type = float, default = config.get_float_value('processors.face_editor_mouth_smile', '0'), choices = processors_choices.face_editor_mouth_smile_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_smile_range))
132
+ group_processors.add_argument('--face-editor-mouth-position-horizontal', help = wording.get('help.face_editor_mouth_position_horizontal'), type = float, default = config.get_float_value('processors.face_editor_mouth_position_horizontal', '0'), choices = processors_choices.face_editor_mouth_position_horizontal_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_position_horizontal_range))
133
+ group_processors.add_argument('--face-editor-mouth-position-vertical', help = wording.get('help.face_editor_mouth_position_vertical'), type = float, default = config.get_float_value('processors.face_editor_mouth_position_vertical', '0'), choices = processors_choices.face_editor_mouth_position_vertical_range, metavar = create_float_metavar(processors_choices.face_editor_mouth_position_vertical_range))
134
+ group_processors.add_argument('--face-editor-head-pitch', help = wording.get('help.face_editor_head_pitch'), type = float, default = config.get_float_value('processors.face_editor_head_pitch', '0'), choices = processors_choices.face_editor_head_pitch_range, metavar = create_float_metavar(processors_choices.face_editor_head_pitch_range))
135
+ group_processors.add_argument('--face-editor-head-yaw', help = wording.get('help.face_editor_head_yaw'), type = float, default = config.get_float_value('processors.face_editor_head_yaw', '0'), choices = processors_choices.face_editor_head_yaw_range, metavar = create_float_metavar(processors_choices.face_editor_head_yaw_range))
136
+ group_processors.add_argument('--face-editor-head-roll', help = wording.get('help.face_editor_head_roll'), type = float, default = config.get_float_value('processors.face_editor_head_roll', '0'), choices = processors_choices.face_editor_head_roll_range, metavar = create_float_metavar(processors_choices.face_editor_head_roll_range))
137
+ facefusion.jobs.job_store.register_step_keys([ 'face_editor_model', 'face_editor_eyebrow_direction', 'face_editor_eye_gaze_horizontal', 'face_editor_eye_gaze_vertical', 'face_editor_eye_open_ratio', 'face_editor_lip_open_ratio', 'face_editor_mouth_grim', 'face_editor_mouth_pout', 'face_editor_mouth_purse', 'face_editor_mouth_smile', 'face_editor_mouth_position_horizontal', 'face_editor_mouth_position_vertical', 'face_editor_head_pitch', 'face_editor_head_yaw', 'face_editor_head_roll' ])
138
+
139
+
140
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
141
+ apply_state_item('face_editor_model', args.get('face_editor_model'))
142
+ apply_state_item('face_editor_eyebrow_direction', args.get('face_editor_eyebrow_direction'))
143
+ apply_state_item('face_editor_eye_gaze_horizontal', args.get('face_editor_eye_gaze_horizontal'))
144
+ apply_state_item('face_editor_eye_gaze_vertical', args.get('face_editor_eye_gaze_vertical'))
145
+ apply_state_item('face_editor_eye_open_ratio', args.get('face_editor_eye_open_ratio'))
146
+ apply_state_item('face_editor_lip_open_ratio', args.get('face_editor_lip_open_ratio'))
147
+ apply_state_item('face_editor_mouth_grim', args.get('face_editor_mouth_grim'))
148
+ apply_state_item('face_editor_mouth_pout', args.get('face_editor_mouth_pout'))
149
+ apply_state_item('face_editor_mouth_purse', args.get('face_editor_mouth_purse'))
150
+ apply_state_item('face_editor_mouth_smile', args.get('face_editor_mouth_smile'))
151
+ apply_state_item('face_editor_mouth_position_horizontal', args.get('face_editor_mouth_position_horizontal'))
152
+ apply_state_item('face_editor_mouth_position_vertical', args.get('face_editor_mouth_position_vertical'))
153
+ apply_state_item('face_editor_head_pitch', args.get('face_editor_head_pitch'))
154
+ apply_state_item('face_editor_head_yaw', args.get('face_editor_head_yaw'))
155
+ apply_state_item('face_editor_head_roll', args.get('face_editor_head_roll'))
156
+
157
+
158
+ def pre_check() -> bool:
159
+ download_directory_path = resolve_relative_path('../.assets/models')
160
+ model_hashes = get_model_options().get('hashes')
161
+ model_sources = get_model_options().get('sources')
162
+
163
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
164
+
165
+
166
+ def pre_process(mode : ProcessMode) -> bool:
167
+ if mode in [ 'output', 'preview' ] and not is_image(state_manager.get_item('target_path')) and not is_video(state_manager.get_item('target_path')):
168
+ logger.error(wording.get('choose_image_or_video_target') + wording.get('exclamation_mark'), __name__)
169
+ return False
170
+ if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
171
+ logger.error(wording.get('specify_image_or_video_output') + wording.get('exclamation_mark'), __name__)
172
+ return False
173
+ if mode == 'output' and not same_file_extension([ state_manager.get_item('target_path'), state_manager.get_item('output_path') ]):
174
+ logger.error(wording.get('match_target_and_output_extension') + wording.get('exclamation_mark'), __name__)
175
+ return False
176
+ return True
177
+
178
+
179
+ def post_process() -> None:
180
+ read_static_image.cache_clear()
181
+ if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
182
+ clear_inference_pool()
183
+ if state_manager.get_item('video_memory_strategy') == 'strict':
184
+ content_analyser.clear_inference_pool()
185
+ face_classifier.clear_inference_pool()
186
+ face_detector.clear_inference_pool()
187
+ face_landmarker.clear_inference_pool()
188
+ face_masker.clear_inference_pool()
189
+ face_recognizer.clear_inference_pool()
190
+
191
+
192
+ def edit_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
193
+ model_template = get_model_options().get('template')
194
+ model_size = get_model_options().get('size')
195
+ face_landmark_5 = scale_face_landmark_5(target_face.landmark_set.get('5/68'), 1.5)
196
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_template, model_size)
197
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
198
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
199
+ crop_vision_frame = apply_edit(crop_vision_frame, target_face.landmark_set.get('68'))
200
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
201
+ temp_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, box_mask, affine_matrix)
202
+ return temp_vision_frame
203
+
204
+
205
+ def apply_edit(crop_vision_frame : VisionFrame, face_landmark_68 : FaceLandmark68) -> VisionFrame:
206
+ feature_volume = forward_extract_feature(crop_vision_frame)
207
+ pitch, yaw, roll, scale, translation, expression, motion_points = forward_extract_motion(crop_vision_frame)
208
+ rotation = create_rotation(pitch, yaw, roll)
209
+ motion_points_target = scale * (motion_points @ rotation.T + expression) + translation
210
+ expression = edit_eye_gaze(expression)
211
+ expression = edit_mouth_grim(expression)
212
+ expression = edit_mouth_position(expression)
213
+ expression = edit_mouth_pout(expression)
214
+ expression = edit_mouth_purse(expression)
215
+ expression = edit_mouth_smile(expression)
216
+ expression = edit_eyebrow_direction(expression)
217
+ expression = limit_expression(expression)
218
+ rotation = edit_head_rotation(pitch, yaw, roll)
219
+ motion_points_source = motion_points @ rotation.T
220
+ motion_points_source += expression
221
+ motion_points_source *= scale
222
+ motion_points_source += translation
223
+ motion_points_source += edit_eye_open(motion_points_target, face_landmark_68)
224
+ motion_points_source += edit_lip_open(motion_points_target, face_landmark_68)
225
+ motion_points_source = forward_stitch_motion_points(motion_points_source, motion_points_target)
226
+ crop_vision_frame = forward_generate_frame(feature_volume, motion_points_source, motion_points_target)
227
+ return crop_vision_frame
228
+
229
+
230
+ def forward_extract_feature(crop_vision_frame : VisionFrame) -> LivePortraitFeatureVolume:
231
+ feature_extractor = get_inference_pool().get('feature_extractor')
232
+
233
+ with conditional_thread_semaphore():
234
+ feature_volume = feature_extractor.run(None,
235
+ {
236
+ 'input': crop_vision_frame
237
+ })[0]
238
+
239
+ return feature_volume
240
+
241
+
242
+ def forward_extract_motion(crop_vision_frame : VisionFrame) -> Tuple[LivePortraitPitch, LivePortraitYaw, LivePortraitRoll, LivePortraitScale, LivePortraitTranslation, LivePortraitExpression, LivePortraitMotionPoints]:
243
+ motion_extractor = get_inference_pool().get('motion_extractor')
244
+
245
+ with conditional_thread_semaphore():
246
+ pitch, yaw, roll, scale, translation, expression, motion_points = motion_extractor.run(None,
247
+ {
248
+ 'input': crop_vision_frame
249
+ })
250
+
251
+ return pitch, yaw, roll, scale, translation, expression, motion_points
252
+
253
+
254
+ def forward_retarget_eye(eye_motion_points : LivePortraitMotionPoints) -> LivePortraitMotionPoints:
255
+ eye_retargeter = get_inference_pool().get('eye_retargeter')
256
+
257
+ with conditional_thread_semaphore():
258
+ eye_motion_points = eye_retargeter.run(None,
259
+ {
260
+ 'input': eye_motion_points
261
+ })[0]
262
+
263
+ return eye_motion_points
264
+
265
+
266
+ def forward_retarget_lip(lip_motion_points : LivePortraitMotionPoints) -> LivePortraitMotionPoints:
267
+ lip_retargeter = get_inference_pool().get('lip_retargeter')
268
+
269
+ with conditional_thread_semaphore():
270
+ lip_motion_points = lip_retargeter.run(None,
271
+ {
272
+ 'input': lip_motion_points
273
+ })[0]
274
+
275
+ return lip_motion_points
276
+
277
+
278
+ def forward_stitch_motion_points(source_motion_points : LivePortraitMotionPoints, target_motion_points : LivePortraitMotionPoints) -> LivePortraitMotionPoints:
279
+ stitcher = get_inference_pool().get('stitcher')
280
+
281
+ with thread_semaphore():
282
+ motion_points = stitcher.run(None,
283
+ {
284
+ 'source': source_motion_points,
285
+ 'target': target_motion_points
286
+ })[0]
287
+
288
+ return motion_points
289
+
290
+
291
+ def forward_generate_frame(feature_volume : LivePortraitFeatureVolume, source_motion_points : LivePortraitMotionPoints, target_motion_points : LivePortraitMotionPoints) -> VisionFrame:
292
+ generator = get_inference_pool().get('generator')
293
+
294
+ with thread_semaphore():
295
+ crop_vision_frame = generator.run(None,
296
+ {
297
+ 'feature_volume': feature_volume,
298
+ 'source': source_motion_points,
299
+ 'target': target_motion_points
300
+ })[0][0]
301
+
302
+ return crop_vision_frame
303
+
304
+
305
+ def edit_eyebrow_direction(expression : LivePortraitExpression) -> LivePortraitExpression:
306
+ face_editor_eyebrow = state_manager.get_item('face_editor_eyebrow_direction')
307
+
308
+ if face_editor_eyebrow > 0:
309
+ expression[0, 1, 1] += numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.015, 0.015 ])
310
+ expression[0, 2, 1] -= numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.020, 0.020 ])
311
+ else:
312
+ expression[0, 1, 0] -= numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.015, 0.015 ])
313
+ expression[0, 2, 0] += numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.020, 0.020 ])
314
+ expression[0, 1, 1] += numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.005, 0.005 ])
315
+ expression[0, 2, 1] -= numpy.interp(face_editor_eyebrow, [ -1, 1 ], [ -0.005, 0.005 ])
316
+ return expression
317
+
318
+
319
+ def edit_eye_gaze(expression : LivePortraitExpression) -> LivePortraitExpression:
320
+ face_editor_eye_gaze_horizontal = state_manager.get_item('face_editor_eye_gaze_horizontal')
321
+ face_editor_eye_gaze_vertical = state_manager.get_item('face_editor_eye_gaze_vertical')
322
+
323
+ if face_editor_eye_gaze_horizontal > 0:
324
+ expression[0, 11, 0] += numpy.interp(face_editor_eye_gaze_horizontal, [ -1, 1 ], [ -0.015, 0.015 ])
325
+ expression[0, 15, 0] += numpy.interp(face_editor_eye_gaze_horizontal, [ -1, 1 ], [ -0.020, 0.020 ])
326
+ else:
327
+ expression[0, 11, 0] += numpy.interp(face_editor_eye_gaze_horizontal, [ -1, 1 ], [ -0.020, 0.020 ])
328
+ expression[0, 15, 0] += numpy.interp(face_editor_eye_gaze_horizontal, [ -1, 1 ], [ -0.015, 0.015 ])
329
+ expression[0, 1, 1] += numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.0025, 0.0025 ])
330
+ expression[0, 2, 1] -= numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.0025, 0.0025 ])
331
+ expression[0, 11, 1] -= numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.010, 0.010 ])
332
+ expression[0, 13, 1] -= numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.005, 0.005 ])
333
+ expression[0, 15, 1] -= numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.010, 0.010 ])
334
+ expression[0, 16, 1] -= numpy.interp(face_editor_eye_gaze_vertical, [ -1, 1 ], [ -0.005, 0.005 ])
335
+ return expression
336
+
337
+
338
+ def edit_eye_open(motion_points : LivePortraitMotionPoints, face_landmark_68 : FaceLandmark68) -> LivePortraitMotionPoints:
339
+ face_editor_eye_open_ratio = state_manager.get_item('face_editor_eye_open_ratio')
340
+ left_eye_ratio = calc_distance_ratio(face_landmark_68, 37, 40, 39, 36)
341
+ right_eye_ratio = calc_distance_ratio(face_landmark_68, 43, 46, 45, 42)
342
+
343
+ if face_editor_eye_open_ratio < 0:
344
+ eye_motion_points = numpy.concatenate([ motion_points.ravel(), [ left_eye_ratio, right_eye_ratio, 0.0 ] ])
345
+ else:
346
+ eye_motion_points = numpy.concatenate([ motion_points.ravel(), [ left_eye_ratio, right_eye_ratio, 0.6 ] ])
347
+ eye_motion_points = eye_motion_points.reshape(1, -1).astype(numpy.float32)
348
+ eye_motion_points = forward_retarget_eye(eye_motion_points) * numpy.abs(face_editor_eye_open_ratio)
349
+ eye_motion_points = eye_motion_points.reshape(-1, 21, 3)
350
+ return eye_motion_points
351
+
352
+
353
+ def edit_lip_open(motion_points : LivePortraitMotionPoints, face_landmark_68 : FaceLandmark68) -> LivePortraitMotionPoints:
354
+ face_editor_lip_open_ratio = state_manager.get_item('face_editor_lip_open_ratio')
355
+ lip_ratio = calc_distance_ratio(face_landmark_68, 62, 66, 54, 48)
356
+
357
+ if face_editor_lip_open_ratio < 0:
358
+ lip_motion_points = numpy.concatenate([ motion_points.ravel(), [ lip_ratio, 0.0 ] ])
359
+ else:
360
+ lip_motion_points = numpy.concatenate([ motion_points.ravel(), [ lip_ratio, 1.0 ] ])
361
+ lip_motion_points = lip_motion_points.reshape(1, -1).astype(numpy.float32)
362
+ lip_motion_points = forward_retarget_lip(lip_motion_points) * numpy.abs(face_editor_lip_open_ratio)
363
+ lip_motion_points = lip_motion_points.reshape(-1, 21, 3)
364
+ return lip_motion_points
365
+
366
+
367
+ def edit_mouth_grim(expression : LivePortraitExpression) -> LivePortraitExpression:
368
+ face_editor_mouth_grim = state_manager.get_item('face_editor_mouth_grim')
369
+ if face_editor_mouth_grim > 0:
370
+ expression[0, 17, 2] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.005, 0.005 ])
371
+ expression[0, 19, 2] += numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.01, 0.01 ])
372
+ expression[0, 20, 1] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.06, 0.06 ])
373
+ expression[0, 20, 2] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.03, 0.03 ])
374
+ else:
375
+ expression[0, 19, 1] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.05, 0.05 ])
376
+ expression[0, 19, 2] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.02, 0.02 ])
377
+ expression[0, 20, 2] -= numpy.interp(face_editor_mouth_grim, [ -1, 1 ], [ -0.03, 0.03 ])
378
+ return expression
379
+
380
+
381
+ def edit_mouth_position(expression : LivePortraitExpression) -> LivePortraitExpression:
382
+ face_editor_mouth_position_horizontal = state_manager.get_item('face_editor_mouth_position_horizontal')
383
+ face_editor_mouth_position_vertical = state_manager.get_item('face_editor_mouth_position_vertical')
384
+ expression[0, 19, 0] += numpy.interp(face_editor_mouth_position_horizontal, [ -1, 1 ], [ -0.05, 0.05 ])
385
+ expression[0, 20, 0] += numpy.interp(face_editor_mouth_position_horizontal, [ -1, 1 ], [ -0.04, 0.04 ])
386
+ if face_editor_mouth_position_vertical > 0:
387
+ expression[0, 19, 1] -= numpy.interp(face_editor_mouth_position_vertical, [ -1, 1 ], [ -0.04, 0.04 ])
388
+ expression[0, 20, 1] -= numpy.interp(face_editor_mouth_position_vertical, [ -1, 1 ], [ -0.02, 0.02 ])
389
+ else:
390
+ expression[0, 19, 1] -= numpy.interp(face_editor_mouth_position_vertical, [ -1, 1 ], [ -0.05, 0.05 ])
391
+ expression[0, 20, 1] -= numpy.interp(face_editor_mouth_position_vertical, [ -1, 1 ], [ -0.04, 0.04 ])
392
+ return expression
393
+
394
+
395
+ def edit_mouth_pout(expression : LivePortraitExpression) -> LivePortraitExpression:
396
+ face_editor_mouth_pout = state_manager.get_item('face_editor_mouth_pout')
397
+ if face_editor_mouth_pout > 0:
398
+ expression[0, 19, 1] -= numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.022, 0.022 ])
399
+ expression[0, 19, 2] += numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.025, 0.025 ])
400
+ expression[0, 20, 2] -= numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.002, 0.002 ])
401
+ else:
402
+ expression[0, 19, 1] += numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.022, 0.022 ])
403
+ expression[0, 19, 2] += numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.025, 0.025 ])
404
+ expression[0, 20, 2] -= numpy.interp(face_editor_mouth_pout, [ -1, 1 ], [ -0.002, 0.002 ])
405
+ return expression
406
+
407
+
408
+ def edit_mouth_purse(expression : LivePortraitExpression) -> LivePortraitExpression:
409
+ face_editor_mouth_purse = state_manager.get_item('face_editor_mouth_purse')
410
+ if face_editor_mouth_purse > 0:
411
+ expression[0, 19, 1] -= numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.04, 0.04 ])
412
+ expression[0, 19, 2] -= numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.02, 0.02 ])
413
+ else:
414
+ expression[0, 14, 1] -= numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.02, 0.02 ])
415
+ expression[0, 17, 2] += numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.01, 0.01 ])
416
+ expression[0, 19, 2] -= numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.015, 0.015 ])
417
+ expression[0, 20, 2] -= numpy.interp(face_editor_mouth_purse, [ -1, 1 ], [ -0.002, 0.002 ])
418
+ return expression
419
+
420
+
421
+ def edit_mouth_smile(expression : LivePortraitExpression) -> LivePortraitExpression:
422
+ face_editor_mouth_smile = state_manager.get_item('face_editor_mouth_smile')
423
+ if face_editor_mouth_smile > 0:
424
+ expression[0, 20, 1] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.015, 0.015 ])
425
+ expression[0, 14, 1] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.025, 0.025 ])
426
+ expression[0, 17, 1] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.01, 0.01 ])
427
+ expression[0, 17, 2] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.004, 0.004 ])
428
+ expression[0, 3, 1] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.0045, 0.0045 ])
429
+ expression[0, 7, 1] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.0045, 0.0045 ])
430
+ else:
431
+ expression[0, 14, 1] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.02, 0.02 ])
432
+ expression[0, 17, 1] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.003, 0.003 ])
433
+ expression[0, 19, 1] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.02, 0.02 ])
434
+ expression[0, 19, 2] -= numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.005, 0.005 ])
435
+ expression[0, 20, 2] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.01, 0.01 ])
436
+ expression[0, 3, 1] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.0045, 0.0045 ])
437
+ expression[0, 7, 1] += numpy.interp(face_editor_mouth_smile, [ -1, 1 ], [ -0.0045, 0.0045 ])
438
+ return expression
439
+
440
+
441
+ def edit_head_rotation(pitch : LivePortraitPitch, yaw : LivePortraitYaw, roll : LivePortraitRoll) -> LivePortraitRotation:
442
+ face_editor_head_pitch = state_manager.get_item('face_editor_head_pitch')
443
+ face_editor_head_yaw = state_manager.get_item('face_editor_head_yaw')
444
+ face_editor_head_roll = state_manager.get_item('face_editor_head_roll')
445
+ edit_pitch = pitch + float(numpy.interp(face_editor_head_pitch, [ -1, 1 ], [ 20, -20 ]))
446
+ edit_yaw = yaw + float(numpy.interp(face_editor_head_yaw, [ -1, 1 ], [ 60, -60 ]))
447
+ edit_roll = roll + float(numpy.interp(face_editor_head_roll, [ -1, 1 ], [ -15, 15 ]))
448
+ edit_pitch, edit_yaw, edit_roll = limit_euler_angles(pitch, yaw, roll, edit_pitch, edit_yaw, edit_roll)
449
+ rotation = create_rotation(edit_pitch, edit_yaw, edit_roll)
450
+ return rotation
451
+
452
+
453
+ def calc_distance_ratio(face_landmark_68 : FaceLandmark68, top_index : int, bottom_index : int, left_index : int, right_index : int) -> float:
454
+ vertical_direction = face_landmark_68[top_index] - face_landmark_68[bottom_index]
455
+ horizontal_direction = face_landmark_68[left_index] - face_landmark_68[right_index]
456
+ distance_ratio = float(numpy.linalg.norm(vertical_direction) / (numpy.linalg.norm(horizontal_direction) + 1e-6))
457
+ return distance_ratio
458
+
459
+
460
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
461
+ model_size = get_model_options().get('size')
462
+ prepare_size = (model_size[0] // 2, model_size[1] // 2)
463
+ crop_vision_frame = cv2.resize(crop_vision_frame, prepare_size, interpolation = cv2.INTER_AREA)
464
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
465
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
466
+ return crop_vision_frame
467
+
468
+
469
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
470
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0).clip(0, 1)
471
+ crop_vision_frame = (crop_vision_frame * 255.0)
472
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1]
473
+ return crop_vision_frame
474
+
475
+
476
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
477
+ pass
478
+
479
+
480
+ def process_frame(inputs : FaceEditorInputs) -> VisionFrame:
481
+ reference_faces = inputs.get('reference_faces')
482
+ target_vision_frame = inputs.get('target_vision_frame')
483
+ many_faces = sort_and_filter_faces(get_many_faces([ target_vision_frame ]))
484
+
485
+ if state_manager.get_item('face_selector_mode') == 'many':
486
+ if many_faces:
487
+ for target_face in many_faces:
488
+ target_vision_frame = edit_face(target_face, target_vision_frame)
489
+ if state_manager.get_item('face_selector_mode') == 'one':
490
+ target_face = get_one_face(many_faces)
491
+ if target_face:
492
+ target_vision_frame = edit_face(target_face, target_vision_frame)
493
+ if state_manager.get_item('face_selector_mode') == 'reference':
494
+ similar_faces = find_similar_faces(many_faces, reference_faces, state_manager.get_item('reference_face_distance'))
495
+ if similar_faces:
496
+ for similar_face in similar_faces:
497
+ target_vision_frame = edit_face(similar_face, target_vision_frame)
498
+ return target_vision_frame
499
+
500
+
501
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
502
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
503
+
504
+ for queue_payload in process_manager.manage(queue_payloads):
505
+ target_vision_path = queue_payload['frame_path']
506
+ target_vision_frame = read_image(target_vision_path)
507
+ output_vision_frame = process_frame(
508
+ {
509
+ 'reference_faces': reference_faces,
510
+ 'target_vision_frame': target_vision_frame
511
+ })
512
+ write_image(target_vision_path, output_vision_frame)
513
+ update_progress(1)
514
+
515
+
516
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
517
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
518
+ target_vision_frame = read_static_image(target_path)
519
+ output_vision_frame = process_frame(
520
+ {
521
+ 'reference_faces': reference_faces,
522
+ 'target_vision_frame': target_vision_frame
523
+ })
524
+ write_image(output_path, output_vision_frame)
525
+
526
+
527
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
528
+ processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/modules/face_enhancer.py ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from argparse import ArgumentParser
2
+ from typing import List
3
+
4
+ import cv2
5
+ import numpy
6
+
7
+ import facefusion.jobs.job_manager
8
+ import facefusion.jobs.job_store
9
+ import facefusion.processors.core as processors
10
+ from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, process_manager, state_manager, wording
11
+ from facefusion.common_helper import create_int_metavar
12
+ from facefusion.download import conditional_download_hashes, conditional_download_sources
13
+ from facefusion.face_analyser import get_many_faces, get_one_face
14
+ from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
15
+ from facefusion.face_masker import create_occlusion_mask, create_static_box_mask
16
+ from facefusion.face_selector import find_similar_faces, sort_and_filter_faces
17
+ from facefusion.face_store import get_reference_faces
18
+ from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
19
+ from facefusion.processors import choices as processors_choices
20
+ from facefusion.processors.typing import FaceEnhancerInputs
21
+ from facefusion.program_helper import find_argument_group
22
+ from facefusion.thread_helper import thread_semaphore
23
+ from facefusion.typing import ApplyStateItem, Args, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, QueuePayload, UpdateProgress, VisionFrame
24
+ from facefusion.vision import read_image, read_static_image, write_image
25
+
26
+ MODEL_SET : ModelSet =\
27
+ {
28
+ 'codeformer':
29
+ {
30
+ 'hashes':
31
+ {
32
+ 'face_enhancer':
33
+ {
34
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/codeformer.hash',
35
+ 'path': resolve_relative_path('../.assets/models/codeformer.hash')
36
+ }
37
+ },
38
+ 'sources':
39
+ {
40
+ 'face_enhancer':
41
+ {
42
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/codeformer.onnx',
43
+ 'path': resolve_relative_path('../.assets/models/codeformer.onnx')
44
+ }
45
+ },
46
+ 'template': 'ffhq_512',
47
+ 'size': (512, 512)
48
+ },
49
+ 'gfpgan_1.2':
50
+ {
51
+ 'hashes':
52
+ {
53
+ 'face_enhancer':
54
+ {
55
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.2.hash',
56
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.2.hash')
57
+ }
58
+ },
59
+ 'sources':
60
+ {
61
+ 'face_enhancer':
62
+ {
63
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.2.onnx',
64
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.2.onnx')
65
+ }
66
+ },
67
+ 'template': 'ffhq_512',
68
+ 'size': (512, 512)
69
+ },
70
+ 'gfpgan_1.3':
71
+ {
72
+ 'hashes':
73
+ {
74
+ 'face_enhancer':
75
+ {
76
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.3.hash',
77
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.3.hash')
78
+ }
79
+ },
80
+ 'sources':
81
+ {
82
+ 'face_enhancer':
83
+ {
84
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.3.onnx',
85
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.3.onnx')
86
+ }
87
+ },
88
+ 'template': 'ffhq_512',
89
+ 'size': (512, 512)
90
+ },
91
+ 'gfpgan_1.4':
92
+ {
93
+ 'hashes':
94
+ {
95
+ 'face_enhancer':
96
+ {
97
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.4.hash',
98
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.4.hash')
99
+ }
100
+ },
101
+ 'sources':
102
+ {
103
+ 'face_enhancer':
104
+ {
105
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gfpgan_1.4.onnx',
106
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.4.onnx')
107
+ }
108
+ },
109
+ 'template': 'ffhq_512',
110
+ 'size': (512, 512)
111
+ },
112
+ 'gpen_bfr_256':
113
+ {
114
+ 'hashes':
115
+ {
116
+ 'face_enhancer':
117
+ {
118
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_256.hash',
119
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_256.hash')
120
+ }
121
+ },
122
+ 'sources':
123
+ {
124
+ 'face_enhancer':
125
+ {
126
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_256.onnx',
127
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_256.onnx')
128
+ }
129
+ },
130
+ 'template': 'arcface_128_v2',
131
+ 'size': (256, 256)
132
+ },
133
+ 'gpen_bfr_512':
134
+ {
135
+ 'hashes':
136
+ {
137
+ 'face_enhancer':
138
+ {
139
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_512.hash',
140
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_512.hash')
141
+ }
142
+ },
143
+ 'sources':
144
+ {
145
+ 'face_enhancer':
146
+ {
147
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_512.onnx',
148
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_512.onnx')
149
+ }
150
+ },
151
+ 'template': 'ffhq_512',
152
+ 'size': (512, 512)
153
+ },
154
+ 'gpen_bfr_1024':
155
+ {
156
+ 'hashes':
157
+ {
158
+ 'face_enhancer':
159
+ {
160
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_1024.hash',
161
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_1024.hash')
162
+ }
163
+ },
164
+ 'sources':
165
+ {
166
+ 'face_enhancer':
167
+ {
168
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_1024.onnx',
169
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_1024.onnx')
170
+ }
171
+ },
172
+ 'template': 'ffhq_512',
173
+ 'size': (1024, 1024)
174
+ },
175
+ 'gpen_bfr_2048':
176
+ {
177
+ 'hashes':
178
+ {
179
+ 'face_enhancer':
180
+ {
181
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_2048.hash',
182
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_2048.hash')
183
+ }
184
+ },
185
+ 'sources':
186
+ {
187
+ 'face_enhancer':
188
+ {
189
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/gpen_bfr_2048.onnx',
190
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_2048.onnx')
191
+ }
192
+ },
193
+ 'template': 'ffhq_512',
194
+ 'size': (2048, 2048)
195
+ },
196
+ 'restoreformer_plus_plus':
197
+ {
198
+ 'hashes':
199
+ {
200
+ 'face_enhancer':
201
+ {
202
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/restoreformer_plus_plus.hash',
203
+ 'path': resolve_relative_path('../.assets/models/restoreformer_plus_plus.hash')
204
+ }
205
+ },
206
+ 'sources':
207
+ {
208
+ 'face_enhancer':
209
+ {
210
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models-3.0.0/restoreformer_plus_plus.onnx',
211
+ 'path': resolve_relative_path('../.assets/models/restoreformer_plus_plus.onnx')
212
+ }
213
+ },
214
+ 'template': 'ffhq_512',
215
+ 'size': (512, 512)
216
+ }
217
+ }
218
+
219
+
220
+ def get_inference_pool() -> InferencePool:
221
+ model_sources = get_model_options().get('sources')
222
+ model_context = __name__ + '.' + state_manager.get_item('face_enhancer_model')
223
+ return inference_manager.get_inference_pool(model_context, model_sources)
224
+
225
+
226
+ def clear_inference_pool() -> None:
227
+ model_context = __name__ + '.' + state_manager.get_item('face_enhancer_model')
228
+ inference_manager.clear_inference_pool(model_context)
229
+
230
+
231
+ def get_model_options() -> ModelOptions:
232
+ face_enhancer_model = state_manager.get_item('face_enhancer_model')
233
+ return MODEL_SET.get(face_enhancer_model)
234
+
235
+
236
+ def register_args(program : ArgumentParser) -> None:
237
+ group_processors = find_argument_group(program, 'processors')
238
+ if group_processors:
239
+ group_processors.add_argument('--face-enhancer-model', help = wording.get('help.face_enhancer_model'), default = config.get_str_value('processors.face_enhancer_model', 'gfpgan_1.4'), choices = processors_choices.face_enhancer_models)
240
+ group_processors.add_argument('--face-enhancer-blend', help = wording.get('help.face_enhancer_blend'), type = int, default = config.get_int_value('processors.face_enhancer_blend', '80'), choices = processors_choices.face_enhancer_blend_range, metavar = create_int_metavar(processors_choices.face_enhancer_blend_range))
241
+ facefusion.jobs.job_store.register_step_keys([ 'face_enhancer_model', 'face_enhancer_blend' ])
242
+
243
+
244
+ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
245
+ apply_state_item('face_enhancer_model', args.get('face_enhancer_model'))
246
+ apply_state_item('face_enhancer_blend', args.get('face_enhancer_blend'))
247
+
248
+
249
+ def pre_check() -> bool:
250
+ download_directory_path = resolve_relative_path('../.assets/models')
251
+ model_hashes = get_model_options().get('hashes')
252
+ model_sources = get_model_options().get('sources')
253
+
254
+ return conditional_download_hashes(download_directory_path, model_hashes) and conditional_download_sources(download_directory_path, model_sources)
255
+
256
+
257
+ def pre_process(mode : ProcessMode) -> bool:
258
+ if mode in [ 'output', 'preview' ] and not is_image(state_manager.get_item('target_path')) and not is_video(state_manager.get_item('target_path')):
259
+ logger.error(wording.get('choose_image_or_video_target') + wording.get('exclamation_mark'), __name__)
260
+ return False
261
+ if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
262
+ logger.error(wording.get('specify_image_or_video_output') + wording.get('exclamation_mark'), __name__)
263
+ return False
264
+ if mode == 'output' and not same_file_extension([ state_manager.get_item('target_path'), state_manager.get_item('output_path') ]):
265
+ logger.error(wording.get('match_target_and_output_extension') + wording.get('exclamation_mark'), __name__)
266
+ return False
267
+ return True
268
+
269
+
270
+ def post_process() -> None:
271
+ read_static_image.cache_clear()
272
+ if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
273
+ clear_inference_pool()
274
+ if state_manager.get_item('video_memory_strategy') == 'strict':
275
+ content_analyser.clear_inference_pool()
276
+ face_classifier.clear_inference_pool()
277
+ face_detector.clear_inference_pool()
278
+ face_landmarker.clear_inference_pool()
279
+ face_masker.clear_inference_pool()
280
+ face_recognizer.clear_inference_pool()
281
+
282
+
283
+ def enhance_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
284
+ model_template = get_model_options().get('template')
285
+ model_size = get_model_options().get('size')
286
+ 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)
287
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
288
+ crop_masks =\
289
+ [
290
+ box_mask
291
+ ]
292
+
293
+ if 'occlusion' in state_manager.get_item('face_mask_types'):
294
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
295
+ crop_masks.append(occlusion_mask)
296
+
297
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
298
+ crop_vision_frame = forward(crop_vision_frame)
299
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
300
+ crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
301
+ paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
302
+ temp_vision_frame = blend_frame(temp_vision_frame, paste_vision_frame)
303
+ return temp_vision_frame
304
+
305
+
306
+ def forward(crop_vision_frame : VisionFrame) -> VisionFrame:
307
+ face_enhancer = get_inference_pool().get('face_enhancer')
308
+ face_enhancer_inputs = {}
309
+
310
+ for face_enhancer_input in face_enhancer.get_inputs():
311
+ if face_enhancer_input.name == 'input':
312
+ face_enhancer_inputs[face_enhancer_input.name] = crop_vision_frame
313
+ if face_enhancer_input.name == 'weight':
314
+ weight = numpy.array([ 1 ]).astype(numpy.double)
315
+ face_enhancer_inputs[face_enhancer_input.name] = weight
316
+
317
+ with thread_semaphore():
318
+ crop_vision_frame = face_enhancer.run(None, face_enhancer_inputs)[0][0]
319
+
320
+ return crop_vision_frame
321
+
322
+
323
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
324
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
325
+ crop_vision_frame = (crop_vision_frame - 0.5) / 0.5
326
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
327
+ return crop_vision_frame
328
+
329
+
330
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
331
+ crop_vision_frame = numpy.clip(crop_vision_frame, -1, 1)
332
+ crop_vision_frame = (crop_vision_frame + 1) / 2
333
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
334
+ crop_vision_frame = (crop_vision_frame * 255.0).round()
335
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1]
336
+ return crop_vision_frame
337
+
338
+
339
+ def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame:
340
+ face_enhancer_blend = 1 - (state_manager.get_item('face_enhancer_blend') / 100)
341
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, face_enhancer_blend, paste_vision_frame, 1 - face_enhancer_blend, 0)
342
+ return temp_vision_frame
343
+
344
+
345
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
346
+ return enhance_face(target_face, temp_vision_frame)
347
+
348
+
349
+ def process_frame(inputs : FaceEnhancerInputs) -> VisionFrame:
350
+ reference_faces = inputs.get('reference_faces')
351
+ target_vision_frame = inputs.get('target_vision_frame')
352
+ many_faces = sort_and_filter_faces(get_many_faces([ target_vision_frame ]))
353
+
354
+ if state_manager.get_item('face_selector_mode') == 'many':
355
+ if many_faces:
356
+ for target_face in many_faces:
357
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
358
+ if state_manager.get_item('face_selector_mode') == 'one':
359
+ target_face = get_one_face(many_faces)
360
+ if target_face:
361
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
362
+ if state_manager.get_item('face_selector_mode') == 'reference':
363
+ similar_faces = find_similar_faces(many_faces, reference_faces, state_manager.get_item('reference_face_distance'))
364
+ if similar_faces:
365
+ for similar_face in similar_faces:
366
+ target_vision_frame = enhance_face(similar_face, target_vision_frame)
367
+ return target_vision_frame
368
+
369
+
370
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
371
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
372
+
373
+ for queue_payload in process_manager.manage(queue_payloads):
374
+ target_vision_path = queue_payload['frame_path']
375
+ target_vision_frame = read_image(target_vision_path)
376
+ output_vision_frame = process_frame(
377
+ {
378
+ 'reference_faces': reference_faces,
379
+ 'target_vision_frame': target_vision_frame
380
+ })
381
+ write_image(target_vision_path, output_vision_frame)
382
+ update_progress(1)
383
+
384
+
385
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
386
+ reference_faces = get_reference_faces() if 'reference' in state_manager.get_item('face_selector_mode') else None
387
+ target_vision_frame = read_static_image(target_path)
388
+ output_vision_frame = process_frame(
389
+ {
390
+ 'reference_faces': reference_faces,
391
+ 'target_vision_frame': target_vision_frame
392
+ })
393
+ write_image(output_path, output_vision_frame)
394
+
395
+
396
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
397
+ processors.multi_process_frames(None, temp_frame_paths, process_frames)