salominavina commited on
Commit
efdd9ee
1 Parent(s): efda462

Upload 74 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. facefusion/__init__.py +0 -0
  2. facefusion/audio.py +137 -0
  3. facefusion/choices.py +37 -0
  4. facefusion/common_helper.py +46 -0
  5. facefusion/config.py +91 -0
  6. facefusion/content_analyser.py +112 -0
  7. facefusion/core.py +437 -0
  8. facefusion/download.py +48 -0
  9. facefusion/execution.py +112 -0
  10. facefusion/face_analyser.py +586 -0
  11. facefusion/face_helper.py +169 -0
  12. facefusion/face_masker.py +155 -0
  13. facefusion/face_store.py +48 -0
  14. facefusion/ffmpeg.py +146 -0
  15. facefusion/filesystem.py +135 -0
  16. facefusion/globals.py +60 -0
  17. facefusion/installer.py +78 -0
  18. facefusion/logger.py +47 -0
  19. facefusion/memory.py +21 -0
  20. facefusion/metadata.py +13 -0
  21. facefusion/normalizer.py +39 -0
  22. facefusion/process_manager.py +53 -0
  23. facefusion/processors/__init__.py +0 -0
  24. facefusion/processors/frame/__init__.py +0 -0
  25. facefusion/processors/frame/choices.py +16 -0
  26. facefusion/processors/frame/core.py +116 -0
  27. facefusion/processors/frame/globals.py +14 -0
  28. facefusion/processors/frame/modules/__init__.py +0 -0
  29. facefusion/processors/frame/modules/face_debugger.py +192 -0
  30. facefusion/processors/frame/modules/face_enhancer.py +301 -0
  31. facefusion/processors/frame/modules/face_swapper.py +369 -0
  32. facefusion/processors/frame/modules/frame_colorizer.py +241 -0
  33. facefusion/processors/frame/modules/frame_enhancer.py +263 -0
  34. facefusion/processors/frame/modules/lip_syncer.py +260 -0
  35. facefusion/processors/frame/typings.py +41 -0
  36. facefusion/statistics.py +51 -0
  37. facefusion/thread_helper.py +21 -0
  38. facefusion/typing.py +122 -0
  39. facefusion/uis/__init__.py +0 -0
  40. facefusion/uis/assets/fixes.css +7 -0
  41. facefusion/uis/assets/overrides.css +58 -0
  42. facefusion/uis/choices.py +7 -0
  43. facefusion/uis/components/__init__.py +0 -0
  44. facefusion/uis/components/about.py +23 -0
  45. facefusion/uis/components/benchmark.py +140 -0
  46. facefusion/uis/components/benchmark_options.py +29 -0
  47. facefusion/uis/components/common_options.py +35 -0
  48. facefusion/uis/components/execution.py +33 -0
  49. facefusion/uis/components/execution_queue_count.py +28 -0
  50. facefusion/uis/components/execution_thread_count.py +29 -0
facefusion/__init__.py ADDED
File without changes
facefusion/audio.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Any, List
2
+ from functools import lru_cache
3
+ import numpy
4
+ import scipy
5
+
6
+ from facefusion.filesystem import is_audio
7
+ from facefusion.ffmpeg import read_audio_buffer
8
+ from facefusion.typing import Fps, Audio, AudioFrame, Spectrogram, MelFilterBank
9
+ from facefusion.voice_extractor import batch_extract_voice
10
+
11
+
12
+ @lru_cache(maxsize = 128)
13
+ def read_static_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
14
+ return read_audio(audio_path, fps)
15
+
16
+
17
+ def read_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
18
+ sample_rate = 48000
19
+ channel_total = 2
20
+
21
+ if is_audio(audio_path):
22
+ audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total)
23
+ audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2)
24
+ audio = prepare_audio(audio)
25
+ spectrogram = create_spectrogram(audio)
26
+ audio_frames = extract_audio_frames(spectrogram, fps)
27
+ return audio_frames
28
+ return None
29
+
30
+
31
+ @lru_cache(maxsize = 128)
32
+ def read_static_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
33
+ return read_voice(audio_path, fps)
34
+
35
+
36
+ def read_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]:
37
+ sample_rate = 48000
38
+ channel_total = 2
39
+ chunk_size = 1024 * 240
40
+ step_size = 1024 * 180
41
+
42
+ if is_audio(audio_path):
43
+ audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total)
44
+ audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2)
45
+ audio = batch_extract_voice(audio, chunk_size, step_size)
46
+ audio = prepare_voice(audio)
47
+ spectrogram = create_spectrogram(audio)
48
+ audio_frames = extract_audio_frames(spectrogram, fps)
49
+ return audio_frames
50
+ return None
51
+
52
+
53
+ def get_audio_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]:
54
+ if is_audio(audio_path):
55
+ audio_frames = read_static_audio(audio_path, fps)
56
+ if frame_number in range(len(audio_frames)):
57
+ return audio_frames[frame_number]
58
+ return None
59
+
60
+
61
+ def get_voice_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]:
62
+ if is_audio(audio_path):
63
+ voice_frames = read_static_voice(audio_path, fps)
64
+ if frame_number in range(len(voice_frames)):
65
+ return voice_frames[frame_number]
66
+ return None
67
+
68
+
69
+ def create_empty_audio_frame() -> AudioFrame:
70
+ mel_filter_total = 80
71
+ step_size = 16
72
+ audio_frame = numpy.zeros((mel_filter_total, step_size)).astype(numpy.int16)
73
+ return audio_frame
74
+
75
+
76
+ def prepare_audio(audio : numpy.ndarray[Any, Any]) -> Audio:
77
+ if audio.ndim > 1:
78
+ audio = numpy.mean(audio, axis = 1)
79
+ audio = audio / numpy.max(numpy.abs(audio), axis = 0)
80
+ audio = scipy.signal.lfilter([ 1.0, -0.97 ], [ 1.0 ], audio)
81
+ return audio
82
+
83
+
84
+ def prepare_voice(audio : numpy.ndarray[Any, Any]) -> Audio:
85
+ sample_rate = 48000
86
+ resample_rate = 16000
87
+
88
+ audio = scipy.signal.resample(audio, int(len(audio) * resample_rate / sample_rate))
89
+ audio = prepare_audio(audio)
90
+ return audio
91
+
92
+
93
+ def convert_hertz_to_mel(hertz : float) -> float:
94
+ return 2595 * numpy.log10(1 + hertz / 700)
95
+
96
+
97
+ def convert_mel_to_hertz(mel : numpy.ndarray[Any, Any]) -> numpy.ndarray[Any, Any]:
98
+ return 700 * (10 ** (mel / 2595) - 1)
99
+
100
+
101
+ def create_mel_filter_bank() -> MelFilterBank:
102
+ mel_filter_total = 80
103
+ mel_bin_total = 800
104
+ sample_rate = 16000
105
+ min_frequency = 55.0
106
+ max_frequency = 7600.0
107
+ mel_filter_bank = numpy.zeros((mel_filter_total, mel_bin_total // 2 + 1))
108
+ mel_frequency_range = numpy.linspace(convert_hertz_to_mel(min_frequency), convert_hertz_to_mel(max_frequency), mel_filter_total + 2)
109
+ indices = numpy.floor((mel_bin_total + 1) * convert_mel_to_hertz(mel_frequency_range) / sample_rate).astype(numpy.int16)
110
+
111
+ for index in range(mel_filter_total):
112
+ start = indices[index]
113
+ end = indices[index + 1]
114
+ mel_filter_bank[index, start:end] = scipy.signal.windows.triang(end - start)
115
+ return mel_filter_bank
116
+
117
+
118
+ def create_spectrogram(audio : Audio) -> Spectrogram:
119
+ mel_bin_total = 800
120
+ mel_bin_overlap = 600
121
+ mel_filter_bank = create_mel_filter_bank()
122
+ spectrogram = scipy.signal.stft(audio, nperseg = mel_bin_total, nfft = mel_bin_total, noverlap = mel_bin_overlap)[2]
123
+ spectrogram = numpy.dot(mel_filter_bank, numpy.abs(spectrogram))
124
+ return spectrogram
125
+
126
+
127
+ def extract_audio_frames(spectrogram : Spectrogram, fps : Fps) -> List[AudioFrame]:
128
+ mel_filter_total = 80
129
+ step_size = 16
130
+ audio_frames = []
131
+ indices = numpy.arange(0, spectrogram.shape[1], mel_filter_total / fps).astype(numpy.int16)
132
+ indices = indices[indices >= step_size]
133
+
134
+ for index in indices:
135
+ start = max(0, index - step_size)
136
+ audio_frames.append(spectrogram[:, start:index])
137
+ return audio_frames
facefusion/choices.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict
2
+
3
+ from facefusion.typing import VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceDetectorModel, FaceMaskType, FaceMaskRegion, TempFrameFormat, OutputVideoEncoder, OutputVideoPreset
4
+ from facefusion.common_helper import create_int_range, create_float_range
5
+
6
+ video_memory_strategies : List[VideoMemoryStrategy] = [ 'strict', 'moderate', 'tolerant' ]
7
+ face_analyser_orders : List[FaceAnalyserOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ]
8
+ face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ]
9
+ face_analyser_genders : List[FaceAnalyserGender] = [ 'female', 'male' ]
10
+ face_detector_set : Dict[FaceDetectorModel, List[str]] =\
11
+ {
12
+ 'many': [ '640x640' ],
13
+ 'retinaface': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
14
+ 'scrfd': [ '160x160', '320x320', '480x480', '512x512', '640x640' ],
15
+ 'yoloface': [ '640x640' ],
16
+ 'yunet': [ '160x160', '320x320', '480x480', '512x512', '640x640', '768x768', '960x960', '1024x1024' ]
17
+ }
18
+ face_selector_modes : List[FaceSelectorMode] = [ 'many', 'one', 'reference' ]
19
+ face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'region' ]
20
+ face_mask_regions : List[FaceMaskRegion] = [ 'skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip' ]
21
+ temp_frame_formats : List[TempFrameFormat] = [ 'bmp', 'jpg', 'png' ]
22
+ output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf' ]
23
+ output_video_presets : List[OutputVideoPreset] = [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ]
24
+
25
+ image_template_sizes : List[float] = [ 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4 ]
26
+ video_template_sizes : List[int] = [ 240, 360, 480, 540, 720, 1080, 1440, 2160, 4320 ]
27
+
28
+ execution_thread_count_range : List[int] = create_int_range(1, 128, 1)
29
+ execution_queue_count_range : List[int] = create_int_range(1, 32, 1)
30
+ system_memory_limit_range : List[int] = create_int_range(0, 128, 1)
31
+ face_detector_score_range : List[float] = create_float_range(0.0, 1.0, 0.05)
32
+ face_landmarker_score_range : List[float] = create_float_range(0.0, 1.0, 0.05)
33
+ face_mask_blur_range : List[float] = create_float_range(0.0, 1.0, 0.05)
34
+ face_mask_padding_range : List[int] = create_int_range(0, 100, 1)
35
+ reference_face_distance_range : List[float] = create_float_range(0.0, 1.5, 0.05)
36
+ output_image_quality_range : List[int] = create_int_range(0, 100, 1)
37
+ output_video_quality_range : List[int] = create_int_range(0, 100, 1)
facefusion/common_helper.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Any
2
+ import platform
3
+
4
+
5
+ def create_metavar(ranges : List[Any]) -> str:
6
+ return '[' + str(ranges[0]) + '-' + str(ranges[-1]) + ']'
7
+
8
+
9
+ def create_int_range(start : int, end : int, step : int) -> List[int]:
10
+ int_range = []
11
+ current = start
12
+
13
+ while current <= end:
14
+ int_range.append(current)
15
+ current += step
16
+ return int_range
17
+
18
+
19
+ def create_float_range(start : float, end : float, step : float) -> List[float]:
20
+ float_range = []
21
+ current = start
22
+
23
+ while current <= end:
24
+ float_range.append(round(current, 2))
25
+ current = round(current + step, 2)
26
+ return float_range
27
+
28
+
29
+ def is_linux() -> bool:
30
+ return to_lower_case(platform.system()) == 'linux'
31
+
32
+
33
+ def is_macos() -> bool:
34
+ return to_lower_case(platform.system()) == 'darwin'
35
+
36
+
37
+ def is_windows() -> bool:
38
+ return to_lower_case(platform.system()) == 'windows'
39
+
40
+
41
+ def to_lower_case(__string__ : Any) -> str:
42
+ return str(__string__).lower()
43
+
44
+
45
+ def get_first(__list__ : Any) -> Any:
46
+ return next(iter(__list__), None)
facefusion/config.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from configparser import ConfigParser
2
+ from typing import Any, Optional, List
3
+
4
+ import facefusion.globals
5
+
6
+ CONFIG = None
7
+
8
+
9
+ def get_config() -> ConfigParser:
10
+ global CONFIG
11
+
12
+ if CONFIG is None:
13
+ CONFIG = ConfigParser()
14
+ CONFIG.read(facefusion.globals.config_path, encoding = 'utf-8')
15
+ return CONFIG
16
+
17
+
18
+ def clear_config() -> None:
19
+ global CONFIG
20
+
21
+ CONFIG = None
22
+
23
+
24
+ def get_str_value(key : str, fallback : Optional[str] = None) -> Optional[str]:
25
+ value = get_value_by_notation(key)
26
+
27
+ if value or fallback:
28
+ return str(value or fallback)
29
+ return None
30
+
31
+
32
+ def get_int_value(key : str, fallback : Optional[str] = None) -> Optional[int]:
33
+ value = get_value_by_notation(key)
34
+
35
+ if value or fallback:
36
+ return int(value or fallback)
37
+ return None
38
+
39
+
40
+ def get_float_value(key : str, fallback : Optional[str] = None) -> Optional[float]:
41
+ value = get_value_by_notation(key)
42
+
43
+ if value or fallback:
44
+ return float(value or fallback)
45
+ return None
46
+
47
+
48
+ def get_bool_value(key : str, fallback : Optional[str] = None) -> Optional[bool]:
49
+ value = get_value_by_notation(key)
50
+
51
+ if value == 'True' or fallback == 'True':
52
+ return True
53
+ if value == 'False' or fallback == 'False':
54
+ return False
55
+ return None
56
+
57
+
58
+ def get_str_list(key : str, fallback : Optional[str] = None) -> Optional[List[str]]:
59
+ value = get_value_by_notation(key)
60
+
61
+ if value or fallback:
62
+ return [ str(value) for value in (value or fallback).split(' ') ]
63
+ return None
64
+
65
+
66
+ def get_int_list(key : str, fallback : Optional[str] = None) -> Optional[List[int]]:
67
+ value = get_value_by_notation(key)
68
+
69
+ if value or fallback:
70
+ return [ int(value) for value in (value or fallback).split(' ') ]
71
+ return None
72
+
73
+
74
+ def get_float_list(key : str, fallback : Optional[str] = None) -> Optional[List[float]]:
75
+ value = get_value_by_notation(key)
76
+
77
+ if value or fallback:
78
+ return [ float(value) for value in (value or fallback).split(' ') ]
79
+ return None
80
+
81
+
82
+ def get_value_by_notation(key : str) -> Optional[Any]:
83
+ config = get_config()
84
+
85
+ if '.' in key:
86
+ section, name = key.split('.')
87
+ if section in config and name in config[section]:
88
+ return config[section][name]
89
+ if key in config:
90
+ return config[key]
91
+ return None
facefusion/content_analyser.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+ from functools import lru_cache
3
+ from time import sleep
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+ from tqdm import tqdm
8
+
9
+ import facefusion.globals
10
+ from facefusion import process_manager, wording
11
+ from facefusion.thread_helper import thread_lock, conditional_thread_semaphore
12
+ from facefusion.typing import VisionFrame, ModelSet, Fps
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.vision import get_video_frame, count_video_frame_total, read_image, detect_video_fps
15
+ from facefusion.filesystem import resolve_relative_path, is_file
16
+ from facefusion.download import conditional_download
17
+
18
+ CONTENT_ANALYSER = None
19
+ MODELS : ModelSet =\
20
+ {
21
+ 'open_nsfw':
22
+ {
23
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/open_nsfw.onnx',
24
+ 'path': resolve_relative_path('../.assets/models/open_nsfw.onnx')
25
+ }
26
+ }
27
+ PROBABILITY_LIMIT = 0.80
28
+ RATE_LIMIT = 10
29
+ STREAM_COUNTER = 0
30
+
31
+
32
+ def get_content_analyser() -> Any:
33
+ global CONTENT_ANALYSER
34
+
35
+ with thread_lock():
36
+ while process_manager.is_checking():
37
+ sleep(0.5)
38
+ if CONTENT_ANALYSER is None:
39
+ model_path = MODELS.get('open_nsfw').get('path')
40
+ CONTENT_ANALYSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
41
+ return CONTENT_ANALYSER
42
+
43
+
44
+ def clear_content_analyser() -> None:
45
+ global CONTENT_ANALYSER
46
+
47
+ CONTENT_ANALYSER = None
48
+
49
+
50
+ def pre_check() -> bool:
51
+ download_directory_path = resolve_relative_path('../.assets/models')
52
+ model_url = MODELS.get('open_nsfw').get('url')
53
+ model_path = MODELS.get('open_nsfw').get('path')
54
+
55
+ if not facefusion.globals.skip_download:
56
+ process_manager.check()
57
+ conditional_download(download_directory_path, [ model_url ])
58
+ process_manager.end()
59
+ return is_file(model_path)
60
+
61
+
62
+ def analyse_stream(vision_frame : VisionFrame, video_fps : Fps) -> bool:
63
+ global STREAM_COUNTER
64
+
65
+ STREAM_COUNTER = STREAM_COUNTER + 1
66
+ if STREAM_COUNTER % int(video_fps) == 0:
67
+ return analyse_frame(vision_frame)
68
+ return False
69
+
70
+
71
+ def analyse_frame(vision_frame : VisionFrame) -> bool:
72
+ content_analyser = get_content_analyser()
73
+ vision_frame = prepare_frame(vision_frame)
74
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
75
+ probability = content_analyser.run(None,
76
+ {
77
+ content_analyser.get_inputs()[0].name: vision_frame
78
+ })[0][0][1]
79
+ return probability > PROBABILITY_LIMIT
80
+
81
+
82
+ def prepare_frame(vision_frame : VisionFrame) -> VisionFrame:
83
+ vision_frame = cv2.resize(vision_frame, (224, 224)).astype(numpy.float32)
84
+ vision_frame -= numpy.array([ 104, 117, 123 ]).astype(numpy.float32)
85
+ vision_frame = numpy.expand_dims(vision_frame, axis = 0)
86
+ return vision_frame
87
+
88
+
89
+ @lru_cache(maxsize = None)
90
+ def analyse_image(image_path : str) -> bool:
91
+ frame = read_image(image_path)
92
+ return analyse_frame(frame)
93
+
94
+
95
+ @lru_cache(maxsize = None)
96
+ def analyse_video(video_path : str, start_frame : int, end_frame : int) -> bool:
97
+ video_frame_total = count_video_frame_total(video_path)
98
+ video_fps = detect_video_fps(video_path)
99
+ frame_range = range(start_frame or 0, end_frame or video_frame_total)
100
+ rate = 0.0
101
+ counter = 0
102
+
103
+ with tqdm(total = len(frame_range), desc = wording.get('analysing'), unit = 'frame', ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
104
+ for frame_number in frame_range:
105
+ if frame_number % int(video_fps) == 0:
106
+ frame = get_video_frame(video_path, frame_number)
107
+ if analyse_frame(frame):
108
+ counter += 1
109
+ rate = counter * int(video_fps) / len(frame_range) * 100
110
+ progress.update()
111
+ progress.set_postfix(rate = rate)
112
+ return rate > RATE_LIMIT
facefusion/core.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ['OMP_NUM_THREADS'] = '1'
4
+
5
+ import signal
6
+ import sys
7
+ import warnings
8
+ import shutil
9
+ import numpy
10
+ import onnxruntime
11
+ from time import sleep, time
12
+ from argparse import ArgumentParser, HelpFormatter
13
+
14
+ import facefusion.choices
15
+ import facefusion.globals
16
+ from facefusion.face_analyser import get_one_face, get_average_face
17
+ from facefusion.face_store import get_reference_faces, append_reference_face
18
+ from facefusion import face_analyser, face_masker, content_analyser, config, process_manager, metadata, logger, wording, voice_extractor
19
+ from facefusion.content_analyser import analyse_image, analyse_video
20
+ from facefusion.processors.frame.core import get_frame_processors_modules, load_frame_processor_module
21
+ from facefusion.common_helper import create_metavar, get_first
22
+ from facefusion.execution import encode_execution_providers, decode_execution_providers
23
+ from facefusion.normalizer import normalize_output_path, normalize_padding, normalize_fps
24
+ from facefusion.memory import limit_system_memory
25
+ from facefusion.statistics import conditional_log_statistics
26
+ from facefusion.download import conditional_download
27
+ from facefusion.filesystem import get_temp_frame_paths, get_temp_file_path, create_temp, move_temp, clear_temp, is_image, is_video, filter_audio_paths, resolve_relative_path, list_directory
28
+ from facefusion.ffmpeg import extract_frames, merge_video, copy_image, finalize_image, restore_audio, replace_audio
29
+ from facefusion.vision import read_image, read_static_images, detect_image_resolution, restrict_video_fps, create_image_resolutions, get_video_frame, detect_video_resolution, detect_video_fps, restrict_video_resolution, restrict_image_resolution, create_video_resolutions, pack_resolution, unpack_resolution
30
+
31
+ onnxruntime.set_default_logger_severity(3)
32
+ warnings.filterwarnings('ignore', category = UserWarning, module = 'gradio')
33
+
34
+
35
+ def cli() -> None:
36
+ signal.signal(signal.SIGINT, lambda signal_number, frame: destroy())
37
+ program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 200), add_help = False)
38
+ # general
39
+ program.add_argument('-c', '--config', help = wording.get('help.config'), dest = 'config_path', default = 'facefusion.ini')
40
+ apply_config(program)
41
+ program.add_argument('-s', '--source', help = wording.get('help.source'), action = 'append', dest = 'source_paths', default = config.get_str_list('general.source_paths'))
42
+ program.add_argument('-t', '--target', help = wording.get('help.target'), dest = 'target_path', default = config.get_str_value('general.target_path'))
43
+ program.add_argument('-o', '--output', help = wording.get('help.output'), dest = 'output_path', default = config.get_str_value('general.output_path'))
44
+ program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
45
+ # misc
46
+ group_misc = program.add_argument_group('misc')
47
+ group_misc.add_argument('--force-download', help = wording.get('help.force_download'), action = 'store_true', default = config.get_bool_value('misc.force_download'))
48
+ group_misc.add_argument('--skip-download', help = wording.get('help.skip_download'), action = 'store_true', default = config.get_bool_value('misc.skip_download'))
49
+ group_misc.add_argument('--headless', help = wording.get('help.headless'), action = 'store_true', default = config.get_bool_value('misc.headless'))
50
+ group_misc.add_argument('--log-level', help = wording.get('help.log_level'), default = config.get_str_value('misc.log_level', 'info'), choices = logger.get_log_levels())
51
+ # execution
52
+ execution_providers = encode_execution_providers(onnxruntime.get_available_providers())
53
+ group_execution = program.add_argument_group('execution')
54
+ group_execution.add_argument('--execution-device-id', help = wording.get('help.execution_device_id'), default = config.get_str_value('execution.face_detector_size', '0'))
55
+ group_execution.add_argument('--execution-providers', help = wording.get('help.execution_providers').format(choices = ', '.join(execution_providers)), default = config.get_str_list('execution.execution_providers', 'cpu'), choices = execution_providers, nargs = '+', metavar = 'EXECUTION_PROVIDERS')
56
+ group_execution.add_argument('--execution-thread-count', help = wording.get('help.execution_thread_count'), type = int, default = config.get_int_value('execution.execution_thread_count', '4'), choices = facefusion.choices.execution_thread_count_range, metavar = create_metavar(facefusion.choices.execution_thread_count_range))
57
+ group_execution.add_argument('--execution-queue-count', help = wording.get('help.execution_queue_count'), type = int, default = config.get_int_value('execution.execution_queue_count', '1'), choices = facefusion.choices.execution_queue_count_range, metavar = create_metavar(facefusion.choices.execution_queue_count_range))
58
+ # memory
59
+ group_memory = program.add_argument_group('memory')
60
+ group_memory.add_argument('--video-memory-strategy', help = wording.get('help.video_memory_strategy'), default = config.get_str_value('memory.video_memory_strategy', 'strict'), choices = facefusion.choices.video_memory_strategies)
61
+ group_memory.add_argument('--system-memory-limit', help = wording.get('help.system_memory_limit'), type = int, default = config.get_int_value('memory.system_memory_limit', '0'), choices = facefusion.choices.system_memory_limit_range, metavar = create_metavar(facefusion.choices.system_memory_limit_range))
62
+ # face analyser
63
+ group_face_analyser = program.add_argument_group('face analyser')
64
+ group_face_analyser.add_argument('--face-analyser-order', help = wording.get('help.face_analyser_order'), default = config.get_str_value('face_analyser.face_analyser_order', 'left-right'), choices = facefusion.choices.face_analyser_orders)
65
+ group_face_analyser.add_argument('--face-analyser-age', help = wording.get('help.face_analyser_age'), default = config.get_str_value('face_analyser.face_analyser_age'), choices = facefusion.choices.face_analyser_ages)
66
+ group_face_analyser.add_argument('--face-analyser-gender', help = wording.get('help.face_analyser_gender'), default = config.get_str_value('face_analyser.face_analyser_gender'), choices = facefusion.choices.face_analyser_genders)
67
+ group_face_analyser.add_argument('--face-detector-model', help = wording.get('help.face_detector_model'), default = config.get_str_value('face_analyser.face_detector_model', 'yoloface'), choices = facefusion.choices.face_detector_set.keys())
68
+ group_face_analyser.add_argument('--face-detector-size', help = wording.get('help.face_detector_size'), default = config.get_str_value('face_analyser.face_detector_size', '640x640'))
69
+ group_face_analyser.add_argument('--face-detector-score', help = wording.get('help.face_detector_score'), type = float, default = config.get_float_value('face_analyser.face_detector_score', '0.5'), choices = facefusion.choices.face_detector_score_range, metavar = create_metavar(facefusion.choices.face_detector_score_range))
70
+ group_face_analyser.add_argument('--face-landmarker-score', help = wording.get('help.face_landmarker_score'), type = float, default = config.get_float_value('face_analyser.face_landmarker_score', '0.5'), choices = facefusion.choices.face_landmarker_score_range, metavar = create_metavar(facefusion.choices.face_landmarker_score_range))
71
+ # face selector
72
+ group_face_selector = program.add_argument_group('face selector')
73
+ group_face_selector.add_argument('--face-selector-mode', help = wording.get('help.face_selector_mode'), default = config.get_str_value('face_selector.face_selector_mode', 'reference'), choices = facefusion.choices.face_selector_modes)
74
+ group_face_selector.add_argument('--reference-face-position', help = wording.get('help.reference_face_position'), type = int, default = config.get_int_value('face_selector.reference_face_position', '0'))
75
+ group_face_selector.add_argument('--reference-face-distance', help = wording.get('help.reference_face_distance'), type = float, default = config.get_float_value('face_selector.reference_face_distance', '0.6'), choices = facefusion.choices.reference_face_distance_range, metavar = create_metavar(facefusion.choices.reference_face_distance_range))
76
+ group_face_selector.add_argument('--reference-frame-number', help = wording.get('help.reference_frame_number'), type = int, default = config.get_int_value('face_selector.reference_frame_number', '0'))
77
+ # face mask
78
+ group_face_mask = program.add_argument_group('face mask')
79
+ group_face_mask.add_argument('--face-mask-types', help = wording.get('help.face_mask_types').format(choices = ', '.join(facefusion.choices.face_mask_types)), default = config.get_str_list('face_mask.face_mask_types', 'box'), choices = facefusion.choices.face_mask_types, nargs = '+', metavar = 'FACE_MASK_TYPES')
80
+ group_face_mask.add_argument('--face-mask-blur', help = wording.get('help.face_mask_blur'), type = float, default = config.get_float_value('face_mask.face_mask_blur', '0.3'), choices = facefusion.choices.face_mask_blur_range, metavar = create_metavar(facefusion.choices.face_mask_blur_range))
81
+ group_face_mask.add_argument('--face-mask-padding', help = wording.get('help.face_mask_padding'), type = int, default = config.get_int_list('face_mask.face_mask_padding', '0 0 0 0'), nargs = '+')
82
+ group_face_mask.add_argument('--face-mask-regions', help = wording.get('help.face_mask_regions').format(choices = ', '.join(facefusion.choices.face_mask_regions)), default = config.get_str_list('face_mask.face_mask_regions', ' '.join(facefusion.choices.face_mask_regions)), choices = facefusion.choices.face_mask_regions, nargs = '+', metavar = 'FACE_MASK_REGIONS')
83
+ # frame extraction
84
+ group_frame_extraction = program.add_argument_group('frame extraction')
85
+ group_frame_extraction.add_argument('--trim-frame-start', help = wording.get('help.trim_frame_start'), type = int, default = facefusion.config.get_int_value('frame_extraction.trim_frame_start'))
86
+ group_frame_extraction.add_argument('--trim-frame-end', help = wording.get('help.trim_frame_end'), type = int, default = facefusion.config.get_int_value('frame_extraction.trim_frame_end'))
87
+ group_frame_extraction.add_argument('--temp-frame-format', help = wording.get('help.temp_frame_format'), default = config.get_str_value('frame_extraction.temp_frame_format', 'png'), choices = facefusion.choices.temp_frame_formats)
88
+ group_frame_extraction.add_argument('--keep-temp', help = wording.get('help.keep_temp'), action = 'store_true', default = config.get_bool_value('frame_extraction.keep_temp'))
89
+ # output creation
90
+ group_output_creation = program.add_argument_group('output creation')
91
+ group_output_creation.add_argument('--output-image-quality', help = wording.get('help.output_image_quality'), type = int, default = config.get_int_value('output_creation.output_image_quality', '80'), choices = facefusion.choices.output_image_quality_range, metavar = create_metavar(facefusion.choices.output_image_quality_range))
92
+ group_output_creation.add_argument('--output-image-resolution', help = wording.get('help.output_image_resolution'), default = config.get_str_value('output_creation.output_image_resolution'))
93
+ group_output_creation.add_argument('--output-video-encoder', help = wording.get('help.output_video_encoder'), default = config.get_str_value('output_creation.output_video_encoder', 'libx264'), choices = facefusion.choices.output_video_encoders)
94
+ group_output_creation.add_argument('--output-video-preset', help = wording.get('help.output_video_preset'), default = config.get_str_value('output_creation.output_video_preset', 'veryfast'), choices = facefusion.choices.output_video_presets)
95
+ group_output_creation.add_argument('--output-video-quality', help = wording.get('help.output_video_quality'), type = int, default = config.get_int_value('output_creation.output_video_quality', '80'), choices = facefusion.choices.output_video_quality_range, metavar = create_metavar(facefusion.choices.output_video_quality_range))
96
+ group_output_creation.add_argument('--output-video-resolution', help = wording.get('help.output_video_resolution'), default = config.get_str_value('output_creation.output_video_resolution'))
97
+ group_output_creation.add_argument('--output-video-fps', help = wording.get('help.output_video_fps'), type = float, default = config.get_str_value('output_creation.output_video_fps'))
98
+ group_output_creation.add_argument('--skip-audio', help = wording.get('help.skip_audio'), action = 'store_true', default = config.get_bool_value('output_creation.skip_audio'))
99
+ # frame processors
100
+ available_frame_processors = list_directory('facefusion/processors/frame/modules')
101
+ program = ArgumentParser(parents = [ program ], formatter_class = program.formatter_class, add_help = True)
102
+ group_frame_processors = program.add_argument_group('frame processors')
103
+ group_frame_processors.add_argument('--frame-processors', help = wording.get('help.frame_processors').format(choices = ', '.join(available_frame_processors)), default = config.get_str_list('frame_processors.frame_processors', 'face_swapper'), nargs = '+')
104
+ for frame_processor in available_frame_processors:
105
+ frame_processor_module = load_frame_processor_module(frame_processor)
106
+ frame_processor_module.register_args(group_frame_processors)
107
+ # uis
108
+ available_ui_layouts = list_directory('facefusion/uis/layouts')
109
+ group_uis = program.add_argument_group('uis')
110
+ group_uis.add_argument('--open-browser', help=wording.get('help.open_browser'), action = 'store_true', default = config.get_bool_value('uis.open_browser'))
111
+ group_uis.add_argument('--ui-layouts', help = wording.get('help.ui_layouts').format(choices = ', '.join(available_ui_layouts)), default = config.get_str_list('uis.ui_layouts', 'default'), nargs = '+')
112
+ run(program)
113
+
114
+
115
+ def apply_config(program : ArgumentParser) -> None:
116
+ known_args = program.parse_known_args()
117
+ facefusion.globals.config_path = get_first(known_args).config_path
118
+
119
+
120
+ def validate_args(program : ArgumentParser) -> None:
121
+ try:
122
+ for action in program._actions:
123
+ if action.default:
124
+ if isinstance(action.default, list):
125
+ for default in action.default:
126
+ program._check_value(action, default)
127
+ else:
128
+ program._check_value(action, action.default)
129
+ except Exception as exception:
130
+ program.error(str(exception))
131
+
132
+
133
+ def apply_args(program : ArgumentParser) -> None:
134
+ args = program.parse_args()
135
+ # general
136
+ facefusion.globals.source_paths = args.source_paths
137
+ facefusion.globals.target_path = args.target_path
138
+ facefusion.globals.output_path = args.output_path
139
+ # misc
140
+ facefusion.globals.force_download = args.force_download
141
+ facefusion.globals.skip_download = args.skip_download
142
+ facefusion.globals.headless = args.headless
143
+ facefusion.globals.log_level = args.log_level
144
+ # execution
145
+ facefusion.globals.execution_device_id = args.execution_device_id
146
+ facefusion.globals.execution_providers = decode_execution_providers(args.execution_providers)
147
+ facefusion.globals.execution_thread_count = args.execution_thread_count
148
+ facefusion.globals.execution_queue_count = args.execution_queue_count
149
+ # memory
150
+ facefusion.globals.video_memory_strategy = args.video_memory_strategy
151
+ facefusion.globals.system_memory_limit = args.system_memory_limit
152
+ # face analyser
153
+ facefusion.globals.face_analyser_order = args.face_analyser_order
154
+ facefusion.globals.face_analyser_age = args.face_analyser_age
155
+ facefusion.globals.face_analyser_gender = args.face_analyser_gender
156
+ facefusion.globals.face_detector_model = args.face_detector_model
157
+ if args.face_detector_size in facefusion.choices.face_detector_set[args.face_detector_model]:
158
+ facefusion.globals.face_detector_size = args.face_detector_size
159
+ else:
160
+ facefusion.globals.face_detector_size = '640x640'
161
+ facefusion.globals.face_detector_score = args.face_detector_score
162
+ facefusion.globals.face_landmarker_score = args.face_landmarker_score
163
+ # face selector
164
+ facefusion.globals.face_selector_mode = args.face_selector_mode
165
+ facefusion.globals.reference_face_position = args.reference_face_position
166
+ facefusion.globals.reference_face_distance = args.reference_face_distance
167
+ facefusion.globals.reference_frame_number = args.reference_frame_number
168
+ # face mask
169
+ facefusion.globals.face_mask_types = args.face_mask_types
170
+ facefusion.globals.face_mask_blur = args.face_mask_blur
171
+ facefusion.globals.face_mask_padding = normalize_padding(args.face_mask_padding)
172
+ facefusion.globals.face_mask_regions = args.face_mask_regions
173
+ # frame extraction
174
+ facefusion.globals.trim_frame_start = args.trim_frame_start
175
+ facefusion.globals.trim_frame_end = args.trim_frame_end
176
+ facefusion.globals.temp_frame_format = args.temp_frame_format
177
+ facefusion.globals.keep_temp = args.keep_temp
178
+ # output creation
179
+ facefusion.globals.output_image_quality = args.output_image_quality
180
+ if is_image(args.target_path):
181
+ output_image_resolution = detect_image_resolution(args.target_path)
182
+ output_image_resolutions = create_image_resolutions(output_image_resolution)
183
+ if args.output_image_resolution in output_image_resolutions:
184
+ facefusion.globals.output_image_resolution = args.output_image_resolution
185
+ else:
186
+ facefusion.globals.output_image_resolution = pack_resolution(output_image_resolution)
187
+ facefusion.globals.output_video_encoder = args.output_video_encoder
188
+ facefusion.globals.output_video_preset = args.output_video_preset
189
+ facefusion.globals.output_video_quality = args.output_video_quality
190
+ if is_video(args.target_path):
191
+ output_video_resolution = detect_video_resolution(args.target_path)
192
+ output_video_resolutions = create_video_resolutions(output_video_resolution)
193
+ if args.output_video_resolution in output_video_resolutions:
194
+ facefusion.globals.output_video_resolution = args.output_video_resolution
195
+ else:
196
+ facefusion.globals.output_video_resolution = pack_resolution(output_video_resolution)
197
+ if args.output_video_fps or is_video(args.target_path):
198
+ facefusion.globals.output_video_fps = normalize_fps(args.output_video_fps) or detect_video_fps(args.target_path)
199
+ facefusion.globals.skip_audio = args.skip_audio
200
+ # frame processors
201
+ available_frame_processors = list_directory('facefusion/processors/frame/modules')
202
+ facefusion.globals.frame_processors = args.frame_processors
203
+ for frame_processor in available_frame_processors:
204
+ frame_processor_module = load_frame_processor_module(frame_processor)
205
+ frame_processor_module.apply_args(program)
206
+ # uis
207
+ facefusion.globals.open_browser = args.open_browser
208
+ facefusion.globals.ui_layouts = args.ui_layouts
209
+
210
+
211
+ def run(program : ArgumentParser) -> None:
212
+ validate_args(program)
213
+ apply_args(program)
214
+ logger.init(facefusion.globals.log_level)
215
+
216
+ if facefusion.globals.system_memory_limit > 0:
217
+ limit_system_memory(facefusion.globals.system_memory_limit)
218
+ if facefusion.globals.force_download:
219
+ force_download()
220
+ return
221
+ if not pre_check() or not content_analyser.pre_check() or not face_analyser.pre_check() or not face_masker.pre_check() or not voice_extractor.pre_check():
222
+ return
223
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
224
+ if not frame_processor_module.pre_check():
225
+ return
226
+ if facefusion.globals.headless:
227
+ conditional_process()
228
+ else:
229
+ import facefusion.uis.core as ui
230
+
231
+ for ui_layout in ui.get_ui_layouts_modules(facefusion.globals.ui_layouts):
232
+ if not ui_layout.pre_check():
233
+ return
234
+ ui.launch()
235
+
236
+
237
+ def destroy() -> None:
238
+ process_manager.stop()
239
+ while process_manager.is_processing():
240
+ sleep(0.5)
241
+ if facefusion.globals.target_path:
242
+ clear_temp(facefusion.globals.target_path)
243
+ sys.exit(0)
244
+
245
+
246
+ def pre_check() -> bool:
247
+ if sys.version_info < (3, 9):
248
+ logger.error(wording.get('python_not_supported').format(version = '3.9'), __name__.upper())
249
+ return False
250
+ if not shutil.which('ffmpeg'):
251
+ logger.error(wording.get('ffmpeg_not_installed'), __name__.upper())
252
+ return False
253
+ return True
254
+
255
+
256
+ def conditional_process() -> None:
257
+ start_time = time()
258
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
259
+ while not frame_processor_module.post_check():
260
+ logger.disable()
261
+ sleep(0.5)
262
+ logger.enable()
263
+ if not frame_processor_module.pre_process('output'):
264
+ return
265
+ conditional_append_reference_faces()
266
+ if is_image(facefusion.globals.target_path):
267
+ process_image(start_time)
268
+ if is_video(facefusion.globals.target_path):
269
+ process_video(start_time)
270
+
271
+
272
+ def conditional_append_reference_faces() -> None:
273
+ if 'reference' in facefusion.globals.face_selector_mode and not get_reference_faces():
274
+ source_frames = read_static_images(facefusion.globals.source_paths)
275
+ source_face = get_average_face(source_frames)
276
+ if is_video(facefusion.globals.target_path):
277
+ reference_frame = get_video_frame(facefusion.globals.target_path, facefusion.globals.reference_frame_number)
278
+ else:
279
+ reference_frame = read_image(facefusion.globals.target_path)
280
+ reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
281
+ append_reference_face('origin', reference_face)
282
+ if source_face and reference_face:
283
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
284
+ abstract_reference_frame = frame_processor_module.get_reference_frame(source_face, reference_face, reference_frame)
285
+ if numpy.any(abstract_reference_frame):
286
+ reference_frame = abstract_reference_frame
287
+ reference_face = get_one_face(reference_frame, facefusion.globals.reference_face_position)
288
+ append_reference_face(frame_processor_module.__name__, reference_face)
289
+
290
+
291
+ def force_download() -> None:
292
+ download_directory_path = resolve_relative_path('../.assets/models')
293
+ available_frame_processors = list_directory('facefusion/processors/frame/modules')
294
+ model_list =\
295
+ [
296
+ content_analyser.MODELS,
297
+ face_analyser.MODELS,
298
+ face_masker.MODELS,
299
+ voice_extractor.MODELS
300
+ ]
301
+
302
+ for frame_processor_module in get_frame_processors_modules(available_frame_processors):
303
+ if hasattr(frame_processor_module, 'MODELS'):
304
+ model_list.append(frame_processor_module.MODELS)
305
+ model_urls = [ models[model].get('url') for models in model_list for model in models ]
306
+ conditional_download(download_directory_path, model_urls)
307
+
308
+
309
+ def process_image(start_time : float) -> None:
310
+ normed_output_path = normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path)
311
+ if analyse_image(facefusion.globals.target_path):
312
+ return
313
+ # clear temp
314
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
315
+ clear_temp(facefusion.globals.target_path)
316
+ # create temp
317
+ logger.debug(wording.get('creating_temp'), __name__.upper())
318
+ create_temp(facefusion.globals.target_path)
319
+ # copy image
320
+ process_manager.start()
321
+ temp_image_resolution = pack_resolution(restrict_image_resolution(facefusion.globals.target_path, unpack_resolution(facefusion.globals.output_image_resolution)))
322
+ logger.info(wording.get('copying_image').format(resolution = temp_image_resolution), __name__.upper())
323
+ if copy_image(facefusion.globals.target_path, temp_image_resolution):
324
+ logger.debug(wording.get('copying_image_succeed'), __name__.upper())
325
+ else:
326
+ logger.error(wording.get('copying_image_failed'), __name__.upper())
327
+ return
328
+ # process image
329
+ temp_file_path = get_temp_file_path(facefusion.globals.target_path)
330
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
331
+ logger.info(wording.get('processing'), frame_processor_module.NAME)
332
+ frame_processor_module.process_image(facefusion.globals.source_paths, temp_file_path, temp_file_path)
333
+ frame_processor_module.post_process()
334
+ if is_process_stopping():
335
+ return
336
+ # finalize image
337
+ logger.info(wording.get('finalizing_image').format(resolution = facefusion.globals.output_image_resolution), __name__.upper())
338
+ if finalize_image(facefusion.globals.target_path, normed_output_path, facefusion.globals.output_image_resolution):
339
+ logger.debug(wording.get('finalizing_image_succeed'), __name__.upper())
340
+ else:
341
+ logger.warn(wording.get('finalizing_image_skipped'), __name__.upper())
342
+ # clear temp
343
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
344
+ clear_temp(facefusion.globals.target_path)
345
+ # validate image
346
+ if is_image(normed_output_path):
347
+ seconds = '{:.2f}'.format((time() - start_time) % 60)
348
+ logger.info(wording.get('processing_image_succeed').format(seconds = seconds), __name__.upper())
349
+ conditional_log_statistics()
350
+ else:
351
+ logger.error(wording.get('processing_image_failed'), __name__.upper())
352
+ process_manager.end()
353
+
354
+
355
+ def process_video(start_time : float) -> None:
356
+ normed_output_path = normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path)
357
+ if analyse_video(facefusion.globals.target_path, facefusion.globals.trim_frame_start, facefusion.globals.trim_frame_end):
358
+ return
359
+ # clear temp
360
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
361
+ clear_temp(facefusion.globals.target_path)
362
+ # create temp
363
+ logger.debug(wording.get('creating_temp'), __name__.upper())
364
+ create_temp(facefusion.globals.target_path)
365
+ # extract frames
366
+ process_manager.start()
367
+ temp_video_resolution = pack_resolution(restrict_video_resolution(facefusion.globals.target_path, unpack_resolution(facefusion.globals.output_video_resolution)))
368
+ temp_video_fps = restrict_video_fps(facefusion.globals.target_path, facefusion.globals.output_video_fps)
369
+ logger.info(wording.get('extracting_frames').format(resolution = temp_video_resolution, fps = temp_video_fps), __name__.upper())
370
+ if extract_frames(facefusion.globals.target_path, temp_video_resolution, temp_video_fps):
371
+ logger.debug(wording.get('extracting_frames_succeed'), __name__.upper())
372
+ else:
373
+ if is_process_stopping():
374
+ return
375
+ logger.error(wording.get('extracting_frames_failed'), __name__.upper())
376
+ return
377
+ # process frames
378
+ temp_frame_paths = get_temp_frame_paths(facefusion.globals.target_path)
379
+ if temp_frame_paths:
380
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
381
+ logger.info(wording.get('processing'), frame_processor_module.NAME)
382
+ frame_processor_module.process_video(facefusion.globals.source_paths, temp_frame_paths)
383
+ frame_processor_module.post_process()
384
+ if is_process_stopping():
385
+ return
386
+ else:
387
+ logger.error(wording.get('temp_frames_not_found'), __name__.upper())
388
+ return
389
+ # merge video
390
+ logger.info(wording.get('merging_video').format(resolution = facefusion.globals.output_video_resolution, fps = facefusion.globals.output_video_fps), __name__.upper())
391
+ if merge_video(facefusion.globals.target_path, facefusion.globals.output_video_resolution, facefusion.globals.output_video_fps):
392
+ logger.debug(wording.get('merging_video_succeed'), __name__.upper())
393
+ else:
394
+ if is_process_stopping():
395
+ return
396
+ logger.error(wording.get('merging_video_failed'), __name__.upper())
397
+ return
398
+ # handle audio
399
+ if facefusion.globals.skip_audio:
400
+ logger.info(wording.get('skipping_audio'), __name__.upper())
401
+ move_temp(facefusion.globals.target_path, normed_output_path)
402
+ else:
403
+ if 'lip_syncer' in facefusion.globals.frame_processors:
404
+ source_audio_path = get_first(filter_audio_paths(facefusion.globals.source_paths))
405
+ if source_audio_path and replace_audio(facefusion.globals.target_path, source_audio_path, normed_output_path):
406
+ logger.debug(wording.get('restoring_audio_succeed'), __name__.upper())
407
+ else:
408
+ if is_process_stopping():
409
+ return
410
+ logger.warn(wording.get('restoring_audio_skipped'), __name__.upper())
411
+ move_temp(facefusion.globals.target_path, normed_output_path)
412
+ else:
413
+ if restore_audio(facefusion.globals.target_path, normed_output_path, facefusion.globals.output_video_fps):
414
+ logger.debug(wording.get('restoring_audio_succeed'), __name__.upper())
415
+ else:
416
+ if is_process_stopping():
417
+ return
418
+ logger.warn(wording.get('restoring_audio_skipped'), __name__.upper())
419
+ move_temp(facefusion.globals.target_path, normed_output_path)
420
+ # clear temp
421
+ logger.debug(wording.get('clearing_temp'), __name__.upper())
422
+ clear_temp(facefusion.globals.target_path)
423
+ # validate video
424
+ if is_video(normed_output_path):
425
+ seconds = '{:.2f}'.format((time() - start_time))
426
+ logger.info(wording.get('processing_video_succeed').format(seconds = seconds), __name__.upper())
427
+ conditional_log_statistics()
428
+ else:
429
+ logger.error(wording.get('processing_video_failed'), __name__.upper())
430
+ process_manager.end()
431
+
432
+
433
+ def is_process_stopping() -> bool:
434
+ if process_manager.is_stopping():
435
+ process_manager.end()
436
+ logger.info(wording.get('processing_stopped'), __name__.upper())
437
+ return process_manager.is_pending()
facefusion/download.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import ssl
4
+ import urllib.request
5
+ from typing import List
6
+ from functools import lru_cache
7
+ from tqdm import tqdm
8
+
9
+ import facefusion.globals
10
+ from facefusion import wording
11
+ from facefusion.common_helper import is_macos
12
+ from facefusion.filesystem import get_file_size, is_file
13
+
14
+ if is_macos():
15
+ ssl._create_default_https_context = ssl._create_unverified_context
16
+
17
+
18
+ def conditional_download(download_directory_path : str, urls : List[str]) -> None:
19
+ for url in urls:
20
+ download_file_path = os.path.join(download_directory_path, os.path.basename(url))
21
+ initial_size = get_file_size(download_file_path)
22
+ download_size = get_download_size(url)
23
+ if initial_size < download_size:
24
+ with tqdm(total = download_size, initial = initial_size, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
25
+ subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ])
26
+ current_size = initial_size
27
+ while current_size < download_size:
28
+ if is_file(download_file_path):
29
+ current_size = get_file_size(download_file_path)
30
+ progress.update(current_size - progress.n)
31
+ if download_size and not is_download_done(url, download_file_path):
32
+ os.remove(download_file_path)
33
+ conditional_download(download_directory_path, [ url ])
34
+
35
+
36
+ @lru_cache(maxsize = None)
37
+ def get_download_size(url : str) -> int:
38
+ try:
39
+ response = urllib.request.urlopen(url, timeout = 10)
40
+ return int(response.getheader('Content-Length'))
41
+ except (OSError, ValueError):
42
+ return 0
43
+
44
+
45
+ def is_download_done(url : str, file_path : str) -> bool:
46
+ if is_file(file_path):
47
+ return get_download_size(url) == get_file_size(file_path)
48
+ return False
facefusion/execution.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Any
2
+ from functools import lru_cache
3
+ import subprocess
4
+ import xml.etree.ElementTree as ElementTree
5
+ import onnxruntime
6
+
7
+ from facefusion.typing import ExecutionDevice, ValueAndUnit
8
+
9
+
10
+ def encode_execution_providers(execution_providers : List[str]) -> List[str]:
11
+ return [ execution_provider.replace('ExecutionProvider', '').lower() for execution_provider in execution_providers ]
12
+
13
+
14
+ def decode_execution_providers(execution_providers : List[str]) -> List[str]:
15
+ available_execution_providers = onnxruntime.get_available_providers()
16
+ encoded_execution_providers = encode_execution_providers(available_execution_providers)
17
+
18
+ return [ execution_provider for execution_provider, encoded_execution_provider in zip(available_execution_providers, encoded_execution_providers) if any(execution_provider in encoded_execution_provider for execution_provider in execution_providers) ]
19
+
20
+
21
+ def has_execution_provider(execution_provider : str) -> bool:
22
+ return execution_provider in onnxruntime.get_available_providers()
23
+
24
+
25
+ def apply_execution_provider_options(execution_device_id : str, execution_providers : List[str]) -> List[Any]:
26
+ execution_providers_with_options : List[Any] = []
27
+
28
+ for execution_provider in execution_providers:
29
+ if execution_provider == 'CUDAExecutionProvider':
30
+ execution_providers_with_options.append((execution_provider,
31
+ {
32
+ 'device_id': execution_device_id,
33
+ 'cudnn_conv_algo_search': 'EXHAUSTIVE' if use_exhaustive() else 'DEFAULT'
34
+ }))
35
+ elif execution_provider == 'OpenVINOExecutionProvider':
36
+ execution_providers_with_options.append((execution_provider,
37
+ {
38
+ 'device_id': execution_device_id,
39
+ 'device_type': execution_device_id + '_FP32'
40
+ }))
41
+ elif execution_provider in [ 'DmlExecutionProvider', 'ROCMExecutionProvider' ]:
42
+ execution_providers_with_options.append((execution_provider,
43
+ {
44
+ 'device_id': execution_device_id
45
+ }))
46
+ else:
47
+ execution_providers_with_options.append(execution_provider)
48
+ return execution_providers_with_options
49
+
50
+
51
+ def use_exhaustive() -> bool:
52
+ execution_devices = detect_static_execution_devices()
53
+ product_names = ('GeForce GTX 1630', 'GeForce GTX 1650', 'GeForce GTX 1660')
54
+
55
+ return any(execution_device.get('product').get('name').startswith(product_names) for execution_device in execution_devices)
56
+
57
+
58
+ def run_nvidia_smi() -> subprocess.Popen[bytes]:
59
+ commands = [ 'nvidia-smi', '--query', '--xml-format' ]
60
+ return subprocess.Popen(commands, stdout = subprocess.PIPE)
61
+
62
+
63
+ @lru_cache(maxsize = None)
64
+ def detect_static_execution_devices() -> List[ExecutionDevice]:
65
+ return detect_execution_devices()
66
+
67
+
68
+ def detect_execution_devices() -> List[ExecutionDevice]:
69
+ execution_devices : List[ExecutionDevice] = []
70
+ try:
71
+ output, _ = run_nvidia_smi().communicate()
72
+ root_element = ElementTree.fromstring(output)
73
+ except Exception:
74
+ root_element = ElementTree.Element('xml')
75
+
76
+ for gpu_element in root_element.findall('gpu'):
77
+ execution_devices.append(
78
+ {
79
+ 'driver_version': root_element.find('driver_version').text,
80
+ 'framework':
81
+ {
82
+ 'name': 'CUDA',
83
+ 'version': root_element.find('cuda_version').text
84
+ },
85
+ 'product':
86
+ {
87
+ 'vendor': 'NVIDIA',
88
+ 'name': gpu_element.find('product_name').text.replace('NVIDIA ', '')
89
+ },
90
+ 'video_memory':
91
+ {
92
+ 'total': create_value_and_unit(gpu_element.find('fb_memory_usage/total').text),
93
+ 'free': create_value_and_unit(gpu_element.find('fb_memory_usage/free').text)
94
+ },
95
+ 'utilization':
96
+ {
97
+ 'gpu': create_value_and_unit(gpu_element.find('utilization/gpu_util').text),
98
+ 'memory': create_value_and_unit(gpu_element.find('utilization/memory_util').text)
99
+ }
100
+ })
101
+ return execution_devices
102
+
103
+
104
+ def create_value_and_unit(text : str) -> ValueAndUnit:
105
+ value, unit = text.split()
106
+ value_and_unit : ValueAndUnit =\
107
+ {
108
+ 'value': value,
109
+ 'unit': unit
110
+ }
111
+
112
+ return value_and_unit
facefusion/face_analyser.py ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Optional, List, Tuple
2
+ from time import sleep
3
+ import cv2
4
+ import numpy
5
+ import onnxruntime
6
+
7
+ import facefusion.globals
8
+ from facefusion import process_manager
9
+ from facefusion.common_helper import get_first
10
+ from facefusion.face_helper import estimate_matrix_by_face_landmark_5, warp_face_by_face_landmark_5, warp_face_by_translation, create_static_anchors, distance_to_face_landmark_5, distance_to_bounding_box, convert_face_landmark_68_to_5, apply_nms, categorize_age, categorize_gender
11
+ from facefusion.face_store import get_static_faces, set_static_faces
12
+ from facefusion.execution import apply_execution_provider_options
13
+ from facefusion.download import conditional_download
14
+ from facefusion.filesystem import resolve_relative_path, is_file
15
+ from facefusion.thread_helper import thread_lock, thread_semaphore, conditional_thread_semaphore
16
+ from facefusion.typing import VisionFrame, Face, FaceSet, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, ModelSet, BoundingBox, FaceLandmarkSet, FaceLandmark5, FaceLandmark68, Score, FaceScoreSet, Embedding
17
+ from facefusion.vision import resize_frame_resolution, unpack_resolution
18
+
19
+ FACE_ANALYSER = None
20
+ MODELS : ModelSet =\
21
+ {
22
+ 'face_detector_retinaface':
23
+ {
24
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/retinaface_10g.onnx',
25
+ 'path': resolve_relative_path('../.assets/models/retinaface_10g.onnx')
26
+ },
27
+ 'face_detector_scrfd':
28
+ {
29
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/scrfd_2.5g.onnx',
30
+ 'path': resolve_relative_path('../.assets/models/scrfd_2.5g.onnx')
31
+ },
32
+ 'face_detector_yoloface':
33
+ {
34
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yoloface_8n.onnx',
35
+ 'path': resolve_relative_path('../.assets/models/yoloface_8n.onnx')
36
+ },
37
+ 'face_detector_yunet':
38
+ {
39
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yunet_2023mar.onnx',
40
+ 'path': resolve_relative_path('../.assets/models/yunet_2023mar.onnx')
41
+ },
42
+ 'face_recognizer_arcface_blendswap':
43
+ {
44
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
45
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
46
+ },
47
+ 'face_recognizer_arcface_inswapper':
48
+ {
49
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
50
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
51
+ },
52
+ 'face_recognizer_arcface_simswap':
53
+ {
54
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_simswap.onnx',
55
+ 'path': resolve_relative_path('../.assets/models/arcface_simswap.onnx')
56
+ },
57
+ 'face_recognizer_arcface_uniface':
58
+ {
59
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx',
60
+ 'path': resolve_relative_path('../.assets/models/arcface_w600k_r50.onnx')
61
+ },
62
+ 'face_landmarker_68':
63
+ {
64
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/2dfan4.onnx',
65
+ 'path': resolve_relative_path('../.assets/models/2dfan4.onnx')
66
+ },
67
+ 'face_landmarker_68_5':
68
+ {
69
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_landmarker_68_5.onnx',
70
+ 'path': resolve_relative_path('../.assets/models/face_landmarker_68_5.onnx')
71
+ },
72
+ 'gender_age':
73
+ {
74
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gender_age.onnx',
75
+ 'path': resolve_relative_path('../.assets/models/gender_age.onnx')
76
+ }
77
+ }
78
+
79
+
80
+ def get_face_analyser() -> Any:
81
+ global FACE_ANALYSER
82
+
83
+ face_detectors = {}
84
+ face_landmarkers = {}
85
+
86
+ with thread_lock():
87
+ while process_manager.is_checking():
88
+ sleep(0.5)
89
+ if FACE_ANALYSER is None:
90
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface' ]:
91
+ face_detectors['retinaface'] = onnxruntime.InferenceSession(MODELS.get('face_detector_retinaface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
92
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
93
+ face_detectors['scrfd'] = onnxruntime.InferenceSession(MODELS.get('face_detector_scrfd').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
94
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
95
+ face_detectors['yoloface'] = onnxruntime.InferenceSession(MODELS.get('face_detector_yoloface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
96
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
97
+ face_detectors['yunet'] = cv2.FaceDetectorYN.create(MODELS.get('face_detector_yunet').get('path'), '', (0, 0))
98
+ if facefusion.globals.face_recognizer_model == 'arcface_blendswap':
99
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_blendswap').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
100
+ if facefusion.globals.face_recognizer_model == 'arcface_inswapper':
101
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_inswapper').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
102
+ if facefusion.globals.face_recognizer_model == 'arcface_simswap':
103
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_simswap').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
104
+ if facefusion.globals.face_recognizer_model == 'arcface_uniface':
105
+ face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_uniface').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
106
+ face_landmarkers['68'] = onnxruntime.InferenceSession(MODELS.get('face_landmarker_68').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
107
+ face_landmarkers['68_5'] = onnxruntime.InferenceSession(MODELS.get('face_landmarker_68_5').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
108
+ gender_age = onnxruntime.InferenceSession(MODELS.get('gender_age').get('path'), providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
109
+ FACE_ANALYSER =\
110
+ {
111
+ 'face_detectors': face_detectors,
112
+ 'face_recognizer': face_recognizer,
113
+ 'face_landmarkers': face_landmarkers,
114
+ 'gender_age': gender_age
115
+ }
116
+ return FACE_ANALYSER
117
+
118
+
119
+ def clear_face_analyser() -> Any:
120
+ global FACE_ANALYSER
121
+
122
+ FACE_ANALYSER = None
123
+
124
+
125
+ def pre_check() -> bool:
126
+ download_directory_path = resolve_relative_path('../.assets/models')
127
+ model_urls =\
128
+ [
129
+ MODELS.get('face_landmarker_68').get('url'),
130
+ MODELS.get('face_landmarker_68_5').get('url'),
131
+ MODELS.get('gender_age').get('url')
132
+ ]
133
+ model_paths =\
134
+ [
135
+ MODELS.get('face_landmarker_68').get('path'),
136
+ MODELS.get('face_landmarker_68_5').get('path'),
137
+ MODELS.get('gender_age').get('path')
138
+ ]
139
+
140
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface' ]:
141
+ model_urls.append(MODELS.get('face_detector_retinaface').get('url'))
142
+ model_paths.append(MODELS.get('face_detector_retinaface').get('path'))
143
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
144
+ model_urls.append(MODELS.get('face_detector_scrfd').get('url'))
145
+ model_paths.append(MODELS.get('face_detector_scrfd').get('path'))
146
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
147
+ model_urls.append(MODELS.get('face_detector_yoloface').get('url'))
148
+ model_paths.append(MODELS.get('face_detector_yoloface').get('path'))
149
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
150
+ model_urls.append(MODELS.get('face_detector_yunet').get('url'))
151
+ model_paths.append(MODELS.get('face_detector_yunet').get('path'))
152
+ if facefusion.globals.face_recognizer_model == 'arcface_blendswap':
153
+ model_urls.append(MODELS.get('face_recognizer_arcface_blendswap').get('url'))
154
+ model_paths.append(MODELS.get('face_recognizer_arcface_blendswap').get('path'))
155
+ if facefusion.globals.face_recognizer_model == 'arcface_inswapper':
156
+ model_urls.append(MODELS.get('face_recognizer_arcface_inswapper').get('url'))
157
+ model_paths.append(MODELS.get('face_recognizer_arcface_inswapper').get('path'))
158
+ if facefusion.globals.face_recognizer_model == 'arcface_simswap':
159
+ model_urls.append(MODELS.get('face_recognizer_arcface_simswap').get('url'))
160
+ model_paths.append(MODELS.get('face_recognizer_arcface_simswap').get('path'))
161
+ if facefusion.globals.face_recognizer_model == 'arcface_uniface':
162
+ model_urls.append(MODELS.get('face_recognizer_arcface_uniface').get('url'))
163
+ model_paths.append(MODELS.get('face_recognizer_arcface_uniface').get('path'))
164
+
165
+ if not facefusion.globals.skip_download:
166
+ process_manager.check()
167
+ conditional_download(download_directory_path, model_urls)
168
+ process_manager.end()
169
+ return all(is_file(model_path) for model_path in model_paths)
170
+
171
+
172
+ def detect_with_retinaface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
173
+ face_detector = get_face_analyser().get('face_detectors').get('retinaface')
174
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
175
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
176
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
177
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
178
+ feature_strides = [ 8, 16, 32 ]
179
+ feature_map_channel = 3
180
+ anchor_total = 2
181
+ bounding_box_list = []
182
+ face_landmark_5_list = []
183
+ score_list = []
184
+
185
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
186
+ with thread_semaphore():
187
+ detections = face_detector.run(None,
188
+ {
189
+ face_detector.get_inputs()[0].name: detect_vision_frame
190
+ })
191
+ for index, feature_stride in enumerate(feature_strides):
192
+ keep_indices = numpy.where(detections[index] >= facefusion.globals.face_detector_score)[0]
193
+ if keep_indices.any():
194
+ stride_height = face_detector_height // feature_stride
195
+ stride_width = face_detector_width // feature_stride
196
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
197
+ bounding_box_raw = detections[index + feature_map_channel] * feature_stride
198
+ face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride
199
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
200
+ bounding_box_list.append(numpy.array(
201
+ [
202
+ bounding_box[0] * ratio_width,
203
+ bounding_box[1] * ratio_height,
204
+ bounding_box[2] * ratio_width,
205
+ bounding_box[3] * ratio_height
206
+ ]))
207
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
208
+ face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ])
209
+ for score in detections[index][keep_indices]:
210
+ score_list.append(score[0])
211
+ return bounding_box_list, face_landmark_5_list, score_list
212
+
213
+
214
+ def detect_with_scrfd(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
215
+ face_detector = get_face_analyser().get('face_detectors').get('scrfd')
216
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
217
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
218
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
219
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
220
+ feature_strides = [ 8, 16, 32 ]
221
+ feature_map_channel = 3
222
+ anchor_total = 2
223
+ bounding_box_list = []
224
+ face_landmark_5_list = []
225
+ score_list = []
226
+
227
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
228
+ with thread_semaphore():
229
+ detections = face_detector.run(None,
230
+ {
231
+ face_detector.get_inputs()[0].name: detect_vision_frame
232
+ })
233
+ for index, feature_stride in enumerate(feature_strides):
234
+ keep_indices = numpy.where(detections[index] >= facefusion.globals.face_detector_score)[0]
235
+ if keep_indices.any():
236
+ stride_height = face_detector_height // feature_stride
237
+ stride_width = face_detector_width // feature_stride
238
+ anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width)
239
+ bounding_box_raw = detections[index + feature_map_channel] * feature_stride
240
+ face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride
241
+ for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]:
242
+ bounding_box_list.append(numpy.array(
243
+ [
244
+ bounding_box[0] * ratio_width,
245
+ bounding_box[1] * ratio_height,
246
+ bounding_box[2] * ratio_width,
247
+ bounding_box[3] * ratio_height
248
+ ]))
249
+ for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]:
250
+ face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ])
251
+ for score in detections[index][keep_indices]:
252
+ score_list.append(score[0])
253
+ return bounding_box_list, face_landmark_5_list, score_list
254
+
255
+
256
+ def detect_with_yoloface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
257
+ face_detector = get_face_analyser().get('face_detectors').get('yoloface')
258
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
259
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
260
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
261
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
262
+ bounding_box_list = []
263
+ face_landmark_5_list = []
264
+ score_list = []
265
+
266
+ detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size)
267
+ with thread_semaphore():
268
+ detections = face_detector.run(None,
269
+ {
270
+ face_detector.get_inputs()[0].name: detect_vision_frame
271
+ })
272
+ detections = numpy.squeeze(detections).T
273
+ bounding_box_raw, score_raw, face_landmark_5_raw = numpy.split(detections, [ 4, 5 ], axis = 1)
274
+ keep_indices = numpy.where(score_raw > facefusion.globals.face_detector_score)[0]
275
+ if keep_indices.any():
276
+ bounding_box_raw, face_landmark_5_raw, score_raw = bounding_box_raw[keep_indices], face_landmark_5_raw[keep_indices], score_raw[keep_indices]
277
+ for bounding_box in bounding_box_raw:
278
+ bounding_box_list.append(numpy.array(
279
+ [
280
+ (bounding_box[0] - bounding_box[2] / 2) * ratio_width,
281
+ (bounding_box[1] - bounding_box[3] / 2) * ratio_height,
282
+ (bounding_box[0] + bounding_box[2] / 2) * ratio_width,
283
+ (bounding_box[1] + bounding_box[3] / 2) * ratio_height
284
+ ]))
285
+ face_landmark_5_raw[:, 0::3] = (face_landmark_5_raw[:, 0::3]) * ratio_width
286
+ face_landmark_5_raw[:, 1::3] = (face_landmark_5_raw[:, 1::3]) * ratio_height
287
+ for face_landmark_5 in face_landmark_5_raw:
288
+ face_landmark_5_list.append(numpy.array(face_landmark_5.reshape(-1, 3)[:, :2]))
289
+ score_list = score_raw.ravel().tolist()
290
+ return bounding_box_list, face_landmark_5_list, score_list
291
+
292
+
293
+ def detect_with_yunet(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]:
294
+ face_detector = get_face_analyser().get('face_detectors').get('yunet')
295
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
296
+ temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height))
297
+ ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0]
298
+ ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1]
299
+ bounding_box_list = []
300
+ face_landmark_5_list = []
301
+ score_list = []
302
+
303
+ face_detector.setInputSize((temp_vision_frame.shape[1], temp_vision_frame.shape[0]))
304
+ face_detector.setScoreThreshold(facefusion.globals.face_detector_score)
305
+ with thread_semaphore():
306
+ _, detections = face_detector.detect(temp_vision_frame)
307
+ if numpy.any(detections):
308
+ for detection in detections:
309
+ bounding_box_list.append(numpy.array(
310
+ [
311
+ detection[0] * ratio_width,
312
+ detection[1] * ratio_height,
313
+ (detection[0] + detection[2]) * ratio_width,
314
+ (detection[1] + detection[3]) * ratio_height
315
+ ]))
316
+ face_landmark_5_list.append(detection[4:14].reshape((5, 2)) * [ ratio_width, ratio_height ])
317
+ score_list.append(detection[14])
318
+ return bounding_box_list, face_landmark_5_list, score_list
319
+
320
+
321
+ def prepare_detect_frame(temp_vision_frame : VisionFrame, face_detector_size : str) -> VisionFrame:
322
+ face_detector_width, face_detector_height = unpack_resolution(face_detector_size)
323
+ detect_vision_frame = numpy.zeros((face_detector_height, face_detector_width, 3))
324
+ detect_vision_frame[:temp_vision_frame.shape[0], :temp_vision_frame.shape[1], :] = temp_vision_frame
325
+ detect_vision_frame = (detect_vision_frame - 127.5) / 128.0
326
+ detect_vision_frame = numpy.expand_dims(detect_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
327
+ return detect_vision_frame
328
+
329
+
330
+ def create_faces(vision_frame : VisionFrame, bounding_box_list : List[BoundingBox], face_landmark_5_list : List[FaceLandmark5], score_list : List[Score]) -> List[Face]:
331
+ faces = []
332
+ if facefusion.globals.face_detector_score > 0:
333
+ sort_indices = numpy.argsort(-numpy.array(score_list))
334
+ bounding_box_list = [ bounding_box_list[index] for index in sort_indices ]
335
+ face_landmark_5_list = [face_landmark_5_list[index] for index in sort_indices]
336
+ score_list = [ score_list[index] for index in sort_indices ]
337
+ iou_threshold = 0.1 if facefusion.globals.face_detector_model == 'many' else 0.4
338
+ keep_indices = apply_nms(bounding_box_list, iou_threshold)
339
+ for index in keep_indices:
340
+ bounding_box = bounding_box_list[index]
341
+ face_landmark_5_68 = face_landmark_5_list[index]
342
+ face_landmark_68_5 = expand_face_landmark_68_from_5(face_landmark_5_68)
343
+ face_landmark_68 = face_landmark_68_5
344
+ face_landmark_68_score = 0.0
345
+ if facefusion.globals.face_landmarker_score > 0:
346
+ face_landmark_68, face_landmark_68_score = detect_face_landmark_68(vision_frame, bounding_box)
347
+ if face_landmark_68_score > facefusion.globals.face_landmarker_score:
348
+ face_landmark_5_68 = convert_face_landmark_68_to_5(face_landmark_68)
349
+ landmarks : FaceLandmarkSet =\
350
+ {
351
+ '5': face_landmark_5_list[index],
352
+ '5/68': face_landmark_5_68,
353
+ '68': face_landmark_68,
354
+ '68/5': face_landmark_68_5
355
+ }
356
+ scores : FaceScoreSet = \
357
+ {
358
+ 'detector': score_list[index],
359
+ 'landmarker': face_landmark_68_score
360
+ }
361
+ embedding, normed_embedding = calc_embedding(vision_frame, landmarks.get('5/68'))
362
+ gender, age = detect_gender_age(vision_frame, bounding_box)
363
+ faces.append(Face(
364
+ bounding_box = bounding_box,
365
+ landmarks = landmarks,
366
+ scores = scores,
367
+ embedding = embedding,
368
+ normed_embedding = normed_embedding,
369
+ gender = gender,
370
+ age = age
371
+ ))
372
+ return faces
373
+
374
+
375
+ def calc_embedding(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5) -> Tuple[Embedding, Embedding]:
376
+ face_recognizer = get_face_analyser().get('face_recognizer')
377
+ crop_vision_frame, matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, 'arcface_112_v2', (112, 112))
378
+ crop_vision_frame = crop_vision_frame / 127.5 - 1
379
+ crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32)
380
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
381
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
382
+ embedding = face_recognizer.run(None,
383
+ {
384
+ face_recognizer.get_inputs()[0].name: crop_vision_frame
385
+ })[0]
386
+ embedding = embedding.ravel()
387
+ normed_embedding = embedding / numpy.linalg.norm(embedding)
388
+ return embedding, normed_embedding
389
+
390
+
391
+ def detect_face_landmark_68(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[FaceLandmark68, Score]:
392
+ face_landmarker = get_face_analyser().get('face_landmarkers').get('68')
393
+ scale = 195 / numpy.subtract(bounding_box[2:], bounding_box[:2]).max()
394
+ translation = (256 - numpy.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5
395
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (256, 256))
396
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_RGB2Lab)
397
+ if numpy.mean(crop_vision_frame[:, :, 0]) < 30:
398
+ crop_vision_frame[:, :, 0] = cv2.createCLAHE(clipLimit = 2).apply(crop_vision_frame[:, :, 0])
399
+ crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_Lab2RGB)
400
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1).astype(numpy.float32) / 255.0
401
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
402
+ face_landmark_68, face_heatmap = face_landmarker.run(None,
403
+ {
404
+ face_landmarker.get_inputs()[0].name: [ crop_vision_frame ]
405
+ })
406
+ face_landmark_68 = face_landmark_68[:, :, :2][0] / 64
407
+ face_landmark_68 = face_landmark_68.reshape(1, -1, 2) * 256
408
+ face_landmark_68 = cv2.transform(face_landmark_68, cv2.invertAffineTransform(affine_matrix))
409
+ face_landmark_68 = face_landmark_68.reshape(-1, 2)
410
+ face_landmark_68_score = numpy.amax(face_heatmap, axis = (2, 3))
411
+ face_landmark_68_score = numpy.mean(face_landmark_68_score)
412
+ return face_landmark_68, face_landmark_68_score
413
+
414
+
415
+ def expand_face_landmark_68_from_5(face_landmark_5 : FaceLandmark5) -> FaceLandmark68:
416
+ face_landmarker = get_face_analyser().get('face_landmarkers').get('68_5')
417
+ affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, 'ffhq_512', (1, 1))
418
+ face_landmark_5 = cv2.transform(face_landmark_5.reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
419
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
420
+ face_landmark_68_5 = face_landmarker.run(None,
421
+ {
422
+ face_landmarker.get_inputs()[0].name: [ face_landmark_5 ]
423
+ })[0][0]
424
+ face_landmark_68_5 = cv2.transform(face_landmark_68_5.reshape(1, -1, 2), cv2.invertAffineTransform(affine_matrix)).reshape(-1, 2)
425
+ return face_landmark_68_5
426
+
427
+
428
+ def detect_gender_age(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[int, int]:
429
+ gender_age = get_face_analyser().get('gender_age')
430
+ bounding_box = bounding_box.reshape(2, -1)
431
+ scale = 64 / numpy.subtract(*bounding_box[::-1]).max()
432
+ translation = 48 - bounding_box.sum(axis = 0) * scale * 0.5
433
+ crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (96, 96))
434
+ crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32)
435
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
436
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
437
+ prediction = gender_age.run(None,
438
+ {
439
+ gender_age.get_inputs()[0].name: crop_vision_frame
440
+ })[0][0]
441
+ gender = int(numpy.argmax(prediction[:2]))
442
+ age = int(numpy.round(prediction[2] * 100))
443
+ return gender, age
444
+
445
+
446
+ def get_one_face(vision_frame : VisionFrame, position : int = 0) -> Optional[Face]:
447
+ many_faces = get_many_faces(vision_frame)
448
+ if many_faces:
449
+ try:
450
+ return many_faces[position]
451
+ except IndexError:
452
+ return many_faces[-1]
453
+ return None
454
+
455
+
456
+ def get_average_face(vision_frames : List[VisionFrame], position : int = 0) -> Optional[Face]:
457
+ average_face = None
458
+ faces = []
459
+ embedding_list = []
460
+ normed_embedding_list = []
461
+
462
+ for vision_frame in vision_frames:
463
+ face = get_one_face(vision_frame, position)
464
+ if face:
465
+ faces.append(face)
466
+ embedding_list.append(face.embedding)
467
+ normed_embedding_list.append(face.normed_embedding)
468
+ if faces:
469
+ first_face = get_first(faces)
470
+ average_face = Face(
471
+ bounding_box = first_face.bounding_box,
472
+ landmarks = first_face.landmarks,
473
+ scores = first_face.scores,
474
+ embedding = numpy.mean(embedding_list, axis = 0),
475
+ normed_embedding = numpy.mean(normed_embedding_list, axis = 0),
476
+ gender = first_face.gender,
477
+ age = first_face.age
478
+ )
479
+ return average_face
480
+
481
+
482
+ def get_many_faces(vision_frame : VisionFrame) -> List[Face]:
483
+ faces = []
484
+ try:
485
+ faces_cache = get_static_faces(vision_frame)
486
+ if faces_cache:
487
+ faces = faces_cache
488
+ else:
489
+ bounding_box_list = []
490
+ face_landmark_5_list = []
491
+ score_list = []
492
+
493
+ if facefusion.globals.face_detector_model in [ 'many', 'retinaface']:
494
+ bounding_box_list_retinaface, face_landmark_5_list_retinaface, score_list_retinaface = detect_with_retinaface(vision_frame, facefusion.globals.face_detector_size)
495
+ bounding_box_list.extend(bounding_box_list_retinaface)
496
+ face_landmark_5_list.extend(face_landmark_5_list_retinaface)
497
+ score_list.extend(score_list_retinaface)
498
+ if facefusion.globals.face_detector_model in [ 'many', 'scrfd' ]:
499
+ bounding_box_list_scrfd, face_landmark_5_list_scrfd, score_list_scrfd = detect_with_scrfd(vision_frame, facefusion.globals.face_detector_size)
500
+ bounding_box_list.extend(bounding_box_list_scrfd)
501
+ face_landmark_5_list.extend(face_landmark_5_list_scrfd)
502
+ score_list.extend(score_list_scrfd)
503
+ if facefusion.globals.face_detector_model in [ 'many', 'yoloface' ]:
504
+ bounding_box_list_yoloface, face_landmark_5_list_yoloface, score_list_yoloface = detect_with_yoloface(vision_frame, facefusion.globals.face_detector_size)
505
+ bounding_box_list.extend(bounding_box_list_yoloface)
506
+ face_landmark_5_list.extend(face_landmark_5_list_yoloface)
507
+ score_list.extend(score_list_yoloface)
508
+ if facefusion.globals.face_detector_model in [ 'yunet' ]:
509
+ bounding_box_list_yunet, face_landmark_5_list_yunet, score_list_yunet = detect_with_yunet(vision_frame, facefusion.globals.face_detector_size)
510
+ bounding_box_list.extend(bounding_box_list_yunet)
511
+ face_landmark_5_list.extend(face_landmark_5_list_yunet)
512
+ score_list.extend(score_list_yunet)
513
+ if bounding_box_list and face_landmark_5_list and score_list:
514
+ faces = create_faces(vision_frame, bounding_box_list, face_landmark_5_list, score_list)
515
+ if faces:
516
+ set_static_faces(vision_frame, faces)
517
+ if facefusion.globals.face_analyser_order:
518
+ faces = sort_by_order(faces, facefusion.globals.face_analyser_order)
519
+ if facefusion.globals.face_analyser_age:
520
+ faces = filter_by_age(faces, facefusion.globals.face_analyser_age)
521
+ if facefusion.globals.face_analyser_gender:
522
+ faces = filter_by_gender(faces, facefusion.globals.face_analyser_gender)
523
+ except (AttributeError, ValueError):
524
+ pass
525
+ return faces
526
+
527
+
528
+ def find_similar_faces(reference_faces : FaceSet, vision_frame : VisionFrame, face_distance : float) -> List[Face]:
529
+ similar_faces : List[Face] = []
530
+ many_faces = get_many_faces(vision_frame)
531
+
532
+ if reference_faces:
533
+ for reference_set in reference_faces:
534
+ if not similar_faces:
535
+ for reference_face in reference_faces[reference_set]:
536
+ for face in many_faces:
537
+ if compare_faces(face, reference_face, face_distance):
538
+ similar_faces.append(face)
539
+ return similar_faces
540
+
541
+
542
+ def compare_faces(face : Face, reference_face : Face, face_distance : float) -> bool:
543
+ current_face_distance = calc_face_distance(face, reference_face)
544
+ return current_face_distance < face_distance
545
+
546
+
547
+ def calc_face_distance(face : Face, reference_face : Face) -> float:
548
+ if hasattr(face, 'normed_embedding') and hasattr(reference_face, 'normed_embedding'):
549
+ return 1 - numpy.dot(face.normed_embedding, reference_face.normed_embedding)
550
+ return 0
551
+
552
+
553
+ def sort_by_order(faces : List[Face], order : FaceAnalyserOrder) -> List[Face]:
554
+ if order == 'left-right':
555
+ return sorted(faces, key = lambda face: face.bounding_box[0])
556
+ if order == 'right-left':
557
+ return sorted(faces, key = lambda face: face.bounding_box[0], reverse = True)
558
+ if order == 'top-bottom':
559
+ return sorted(faces, key = lambda face: face.bounding_box[1])
560
+ if order == 'bottom-top':
561
+ return sorted(faces, key = lambda face: face.bounding_box[1], reverse = True)
562
+ if order == 'small-large':
563
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]))
564
+ if order == 'large-small':
565
+ return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]), reverse = True)
566
+ if order == 'best-worst':
567
+ return sorted(faces, key = lambda face: face.scores.get('detector'), reverse = True)
568
+ if order == 'worst-best':
569
+ return sorted(faces, key = lambda face: face.scores.get('detector'))
570
+ return faces
571
+
572
+
573
+ def filter_by_age(faces : List[Face], age : FaceAnalyserAge) -> List[Face]:
574
+ filter_faces = []
575
+ for face in faces:
576
+ if categorize_age(face.age) == age:
577
+ filter_faces.append(face)
578
+ return filter_faces
579
+
580
+
581
+ def filter_by_gender(faces : List[Face], gender : FaceAnalyserGender) -> List[Face]:
582
+ filter_faces = []
583
+ for face in faces:
584
+ if categorize_gender(face.gender) == gender:
585
+ filter_faces.append(face)
586
+ return filter_faces
facefusion/face_helper.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Tuple, List
2
+ from cv2.typing import Size
3
+ from functools import lru_cache
4
+ import cv2
5
+ import numpy
6
+
7
+ from facefusion.typing import BoundingBox, FaceLandmark5, FaceLandmark68, VisionFrame, Mask, Matrix, Translation, WarpTemplate, WarpTemplateSet, FaceAnalyserAge, FaceAnalyserGender
8
+
9
+ WARP_TEMPLATES : WarpTemplateSet =\
10
+ {
11
+ 'arcface_112_v1': numpy.array(
12
+ [
13
+ [ 0.35473214, 0.45658929 ],
14
+ [ 0.64526786, 0.45658929 ],
15
+ [ 0.50000000, 0.61154464 ],
16
+ [ 0.37913393, 0.77687500 ],
17
+ [ 0.62086607, 0.77687500 ]
18
+ ]),
19
+ 'arcface_112_v2': numpy.array(
20
+ [
21
+ [ 0.34191607, 0.46157411 ],
22
+ [ 0.65653393, 0.45983393 ],
23
+ [ 0.50022500, 0.64050536 ],
24
+ [ 0.37097589, 0.82469196 ],
25
+ [ 0.63151696, 0.82325089 ]
26
+ ]),
27
+ 'arcface_128_v2': numpy.array(
28
+ [
29
+ [ 0.36167656, 0.40387734 ],
30
+ [ 0.63696719, 0.40235469 ],
31
+ [ 0.50019687, 0.56044219 ],
32
+ [ 0.38710391, 0.72160547 ],
33
+ [ 0.61507734, 0.72034453 ]
34
+ ]),
35
+ 'ffhq_512': numpy.array(
36
+ [
37
+ [ 0.37691676, 0.46864664 ],
38
+ [ 0.62285697, 0.46912813 ],
39
+ [ 0.50123859, 0.61331904 ],
40
+ [ 0.39308822, 0.72541100 ],
41
+ [ 0.61150205, 0.72490465 ]
42
+ ])
43
+ }
44
+
45
+
46
+ def estimate_matrix_by_face_landmark_5(face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Matrix:
47
+ normed_warp_template = WARP_TEMPLATES.get(warp_template) * crop_size
48
+ affine_matrix = cv2.estimateAffinePartial2D(face_landmark_5, normed_warp_template, method = cv2.RANSAC, ransacReprojThreshold = 100)[0]
49
+ return affine_matrix
50
+
51
+
52
+ def warp_face_by_face_landmark_5(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
53
+ affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, warp_template, crop_size)
54
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, borderMode = cv2.BORDER_REPLICATE, flags = cv2.INTER_AREA)
55
+ return crop_vision_frame, affine_matrix
56
+
57
+
58
+ def warp_face_by_bounding_box(temp_vision_frame : VisionFrame, bounding_box : BoundingBox, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
59
+ 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)
60
+ target_points = numpy.array([ [ 0, 0 ], [ crop_size[0], 0 ], [ 0, crop_size[1] ] ]).astype(numpy.float32)
61
+ affine_matrix = cv2.getAffineTransform(source_points, target_points)
62
+ if bounding_box[2] - bounding_box[0] > crop_size[0] or bounding_box[3] - bounding_box[1] > crop_size[1]:
63
+ interpolation_method = cv2.INTER_AREA
64
+ else:
65
+ interpolation_method = cv2.INTER_LINEAR
66
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, flags = interpolation_method)
67
+ return crop_vision_frame, affine_matrix
68
+
69
+
70
+ def warp_face_by_translation(temp_vision_frame : VisionFrame, translation : Translation, scale : float, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
71
+ affine_matrix = numpy.array([ [ scale, 0, translation[0] ], [ 0, scale, translation[1] ] ])
72
+ crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size)
73
+ return crop_vision_frame, affine_matrix
74
+
75
+
76
+ def paste_back(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, crop_mask : Mask, affine_matrix : Matrix) -> VisionFrame:
77
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
78
+ temp_size = temp_vision_frame.shape[:2][::-1]
79
+ inverse_mask = cv2.warpAffine(crop_mask, inverse_matrix, temp_size).clip(0, 1)
80
+ inverse_vision_frame = cv2.warpAffine(crop_vision_frame, inverse_matrix, temp_size, borderMode = cv2.BORDER_REPLICATE)
81
+ paste_vision_frame = temp_vision_frame.copy()
82
+ paste_vision_frame[:, :, 0] = inverse_mask * inverse_vision_frame[:, :, 0] + (1 - inverse_mask) * temp_vision_frame[:, :, 0]
83
+ paste_vision_frame[:, :, 1] = inverse_mask * inverse_vision_frame[:, :, 1] + (1 - inverse_mask) * temp_vision_frame[:, :, 1]
84
+ paste_vision_frame[:, :, 2] = inverse_mask * inverse_vision_frame[:, :, 2] + (1 - inverse_mask) * temp_vision_frame[:, :, 2]
85
+ return paste_vision_frame
86
+
87
+
88
+ @lru_cache(maxsize = None)
89
+ def create_static_anchors(feature_stride : int, anchor_total : int, stride_height : int, stride_width : int) -> numpy.ndarray[Any, Any]:
90
+ y, x = numpy.mgrid[:stride_height, :stride_width][::-1]
91
+ anchors = numpy.stack((y, x), axis = -1)
92
+ anchors = (anchors * feature_stride).reshape((-1, 2))
93
+ anchors = numpy.stack([ anchors ] * anchor_total, axis = 1).reshape((-1, 2))
94
+ return anchors
95
+
96
+
97
+ def create_bounding_box_from_face_landmark_68(face_landmark_68 : FaceLandmark68) -> BoundingBox:
98
+ min_x, min_y = numpy.min(face_landmark_68, axis = 0)
99
+ max_x, max_y = numpy.max(face_landmark_68, axis = 0)
100
+ bounding_box = numpy.array([ min_x, min_y, max_x, max_y ]).astype(numpy.int16)
101
+ return bounding_box
102
+
103
+
104
+ def distance_to_bounding_box(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> BoundingBox:
105
+ x1 = points[:, 0] - distance[:, 0]
106
+ y1 = points[:, 1] - distance[:, 1]
107
+ x2 = points[:, 0] + distance[:, 2]
108
+ y2 = points[:, 1] + distance[:, 3]
109
+ bounding_box = numpy.column_stack([ x1, y1, x2, y2 ])
110
+ return bounding_box
111
+
112
+
113
+ def distance_to_face_landmark_5(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> FaceLandmark5:
114
+ x = points[:, 0::2] + distance[:, 0::2]
115
+ y = points[:, 1::2] + distance[:, 1::2]
116
+ face_landmark_5 = numpy.stack((x, y), axis = -1)
117
+ return face_landmark_5
118
+
119
+
120
+ def convert_face_landmark_68_to_5(face_landmark_68 : FaceLandmark68) -> FaceLandmark5:
121
+ face_landmark_5 = numpy.array(
122
+ [
123
+ numpy.mean(face_landmark_68[36:42], axis = 0),
124
+ numpy.mean(face_landmark_68[42:48], axis = 0),
125
+ face_landmark_68[30],
126
+ face_landmark_68[48],
127
+ face_landmark_68[54]
128
+ ])
129
+ return face_landmark_5
130
+
131
+
132
+ def apply_nms(bounding_box_list : List[BoundingBox], iou_threshold : float) -> List[int]:
133
+ keep_indices = []
134
+ dimension_list = numpy.reshape(bounding_box_list, (-1, 4))
135
+ x1 = dimension_list[:, 0]
136
+ y1 = dimension_list[:, 1]
137
+ x2 = dimension_list[:, 2]
138
+ y2 = dimension_list[:, 3]
139
+ areas = (x2 - x1 + 1) * (y2 - y1 + 1)
140
+ indices = numpy.arange(len(bounding_box_list))
141
+ while indices.size > 0:
142
+ index = indices[0]
143
+ remain_indices = indices[1:]
144
+ keep_indices.append(index)
145
+ xx1 = numpy.maximum(x1[index], x1[remain_indices])
146
+ yy1 = numpy.maximum(y1[index], y1[remain_indices])
147
+ xx2 = numpy.minimum(x2[index], x2[remain_indices])
148
+ yy2 = numpy.minimum(y2[index], y2[remain_indices])
149
+ width = numpy.maximum(0, xx2 - xx1 + 1)
150
+ height = numpy.maximum(0, yy2 - yy1 + 1)
151
+ iou = width * height / (areas[index] + areas[remain_indices] - width * height)
152
+ indices = indices[numpy.where(iou <= iou_threshold)[0] + 1]
153
+ return keep_indices
154
+
155
+
156
+ def categorize_age(age : int) -> FaceAnalyserAge:
157
+ if age < 13:
158
+ return 'child'
159
+ elif age < 19:
160
+ return 'teen'
161
+ elif age < 60:
162
+ return 'adult'
163
+ return 'senior'
164
+
165
+
166
+ def categorize_gender(gender : int) -> FaceAnalyserGender:
167
+ if gender == 0:
168
+ return 'female'
169
+ return 'male'
facefusion/face_masker.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List
2
+ from cv2.typing import Size
3
+ from functools import lru_cache
4
+ from time import sleep
5
+ import cv2
6
+ import numpy
7
+ import onnxruntime
8
+
9
+ import facefusion.globals
10
+ from facefusion import process_manager
11
+ from facefusion.thread_helper import thread_lock, conditional_thread_semaphore
12
+ from facefusion.typing import FaceLandmark68, VisionFrame, Mask, Padding, FaceMaskRegion, ModelSet
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.filesystem import resolve_relative_path, is_file
15
+ from facefusion.download import conditional_download
16
+
17
+ FACE_OCCLUDER = None
18
+ FACE_PARSER = None
19
+ MODELS : ModelSet =\
20
+ {
21
+ 'face_occluder':
22
+ {
23
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_occluder.onnx',
24
+ 'path': resolve_relative_path('../.assets/models/face_occluder.onnx')
25
+ },
26
+ 'face_parser':
27
+ {
28
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_parser.onnx',
29
+ 'path': resolve_relative_path('../.assets/models/face_parser.onnx')
30
+ }
31
+ }
32
+ FACE_MASK_REGIONS : Dict[FaceMaskRegion, int] =\
33
+ {
34
+ 'skin': 1,
35
+ 'left-eyebrow': 2,
36
+ 'right-eyebrow': 3,
37
+ 'left-eye': 4,
38
+ 'right-eye': 5,
39
+ 'glasses': 6,
40
+ 'nose': 10,
41
+ 'mouth': 11,
42
+ 'upper-lip': 12,
43
+ 'lower-lip': 13
44
+ }
45
+
46
+
47
+ def get_face_occluder() -> Any:
48
+ global FACE_OCCLUDER
49
+
50
+ with thread_lock():
51
+ while process_manager.is_checking():
52
+ sleep(0.5)
53
+ if FACE_OCCLUDER is None:
54
+ model_path = MODELS.get('face_occluder').get('path')
55
+ FACE_OCCLUDER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
56
+ return FACE_OCCLUDER
57
+
58
+
59
+ def get_face_parser() -> Any:
60
+ global FACE_PARSER
61
+
62
+ with thread_lock():
63
+ while process_manager.is_checking():
64
+ sleep(0.5)
65
+ if FACE_PARSER is None:
66
+ model_path = MODELS.get('face_parser').get('path')
67
+ FACE_PARSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
68
+ return FACE_PARSER
69
+
70
+
71
+ def clear_face_occluder() -> None:
72
+ global FACE_OCCLUDER
73
+
74
+ FACE_OCCLUDER = None
75
+
76
+
77
+ def clear_face_parser() -> None:
78
+ global FACE_PARSER
79
+
80
+ FACE_PARSER = None
81
+
82
+
83
+ def pre_check() -> bool:
84
+ download_directory_path = resolve_relative_path('../.assets/models')
85
+ model_urls =\
86
+ [
87
+ MODELS.get('face_occluder').get('url'),
88
+ MODELS.get('face_parser').get('url')
89
+ ]
90
+ model_paths =\
91
+ [
92
+ MODELS.get('face_occluder').get('path'),
93
+ MODELS.get('face_parser').get('path')
94
+ ]
95
+
96
+ if not facefusion.globals.skip_download:
97
+ process_manager.check()
98
+ conditional_download(download_directory_path, model_urls)
99
+ process_manager.end()
100
+ return all(is_file(model_path) for model_path in model_paths)
101
+
102
+
103
+ @lru_cache(maxsize = None)
104
+ def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Mask:
105
+ blur_amount = int(crop_size[0] * 0.5 * face_mask_blur)
106
+ blur_area = max(blur_amount // 2, 1)
107
+ box_mask : Mask = numpy.ones(crop_size, numpy.float32)
108
+ box_mask[:max(blur_area, int(crop_size[1] * face_mask_padding[0] / 100)), :] = 0
109
+ box_mask[-max(blur_area, int(crop_size[1] * face_mask_padding[2] / 100)):, :] = 0
110
+ box_mask[:, :max(blur_area, int(crop_size[0] * face_mask_padding[3] / 100))] = 0
111
+ box_mask[:, -max(blur_area, int(crop_size[0] * face_mask_padding[1] / 100)):] = 0
112
+ if blur_amount > 0:
113
+ box_mask = cv2.GaussianBlur(box_mask, (0, 0), blur_amount * 0.25)
114
+ return box_mask
115
+
116
+
117
+ def create_occlusion_mask(crop_vision_frame : VisionFrame) -> Mask:
118
+ face_occluder = get_face_occluder()
119
+ prepare_vision_frame = cv2.resize(crop_vision_frame, face_occluder.get_inputs()[0].shape[1:3][::-1])
120
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32) / 255
121
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 1, 2, 3)
122
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
123
+ occlusion_mask : Mask = face_occluder.run(None,
124
+ {
125
+ face_occluder.get_inputs()[0].name: prepare_vision_frame
126
+ })[0][0]
127
+ occlusion_mask = occlusion_mask.transpose(0, 1, 2).clip(0, 1).astype(numpy.float32)
128
+ occlusion_mask = cv2.resize(occlusion_mask, crop_vision_frame.shape[:2][::-1])
129
+ occlusion_mask = (cv2.GaussianBlur(occlusion_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
130
+ return occlusion_mask
131
+
132
+
133
+ def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List[FaceMaskRegion]) -> Mask:
134
+ face_parser = get_face_parser()
135
+ prepare_vision_frame = cv2.flip(cv2.resize(crop_vision_frame, (512, 512)), 1)
136
+ prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32)[:, :, ::-1] / 127.5 - 1
137
+ prepare_vision_frame = prepare_vision_frame.transpose(0, 3, 1, 2)
138
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
139
+ region_mask : Mask = face_parser.run(None,
140
+ {
141
+ face_parser.get_inputs()[0].name: prepare_vision_frame
142
+ })[0][0]
143
+ region_mask = numpy.isin(region_mask.argmax(0), [ FACE_MASK_REGIONS[region] for region in face_mask_regions ])
144
+ region_mask = cv2.resize(region_mask.astype(numpy.float32), crop_vision_frame.shape[:2][::-1])
145
+ region_mask = (cv2.GaussianBlur(region_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
146
+ return region_mask
147
+
148
+
149
+ def create_mouth_mask(face_landmark_68 : FaceLandmark68) -> Mask:
150
+ convex_hull = cv2.convexHull(face_landmark_68[numpy.r_[3:14, 31:36]].astype(numpy.int32))
151
+ mouth_mask : Mask = numpy.zeros((512, 512)).astype(numpy.float32)
152
+ mouth_mask = cv2.fillConvexPoly(mouth_mask, convex_hull, 1.0)
153
+ mouth_mask = cv2.erode(mouth_mask.clip(0, 1), numpy.ones((21, 3)))
154
+ mouth_mask = cv2.GaussianBlur(mouth_mask, (0, 0), sigmaX = 1, sigmaY = 15)
155
+ return mouth_mask
facefusion/face_store.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, List
2
+ import hashlib
3
+ import numpy
4
+
5
+ from facefusion.typing import VisionFrame, Face, FaceStore, FaceSet
6
+
7
+ FACE_STORE: FaceStore =\
8
+ {
9
+ 'static_faces': {},
10
+ 'reference_faces': {}
11
+ }
12
+
13
+
14
+ def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
15
+ frame_hash = create_frame_hash(vision_frame)
16
+ if frame_hash in FACE_STORE['static_faces']:
17
+ return FACE_STORE['static_faces'][frame_hash]
18
+ return None
19
+
20
+
21
+ def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
22
+ frame_hash = create_frame_hash(vision_frame)
23
+ if frame_hash:
24
+ FACE_STORE['static_faces'][frame_hash] = faces
25
+
26
+
27
+ def clear_static_faces() -> None:
28
+ FACE_STORE['static_faces'] = {}
29
+
30
+
31
+ def create_frame_hash(vision_frame : VisionFrame) -> Optional[str]:
32
+ return hashlib.sha1(vision_frame.tobytes()).hexdigest() if numpy.any(vision_frame) else None
33
+
34
+
35
+ def get_reference_faces() -> Optional[FaceSet]:
36
+ if FACE_STORE['reference_faces']:
37
+ return FACE_STORE['reference_faces']
38
+ return None
39
+
40
+
41
+ def append_reference_face(name : str, face : Face) -> None:
42
+ if name not in FACE_STORE['reference_faces']:
43
+ FACE_STORE['reference_faces'][name] = []
44
+ FACE_STORE['reference_faces'][name].append(face)
45
+
46
+
47
+ def clear_reference_faces() -> None:
48
+ FACE_STORE['reference_faces'] = {}
facefusion/ffmpeg.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import os
3
+ import subprocess
4
+ import filetype
5
+
6
+ import facefusion.globals
7
+ from facefusion import logger, process_manager
8
+ from facefusion.typing import OutputVideoPreset, Fps, AudioBuffer
9
+ from facefusion.filesystem import get_temp_frames_pattern, get_temp_file_path
10
+ from facefusion.vision import restrict_video_fps
11
+
12
+
13
+ def run_ffmpeg(args : List[str]) -> bool:
14
+ commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'error' ]
15
+ commands.extend(args)
16
+ process = subprocess.Popen(commands, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
17
+
18
+ while process_manager.is_processing():
19
+ try:
20
+ if facefusion.globals.log_level == 'debug':
21
+ log_debug(process)
22
+ return process.wait(timeout = 0.5) == 0
23
+ except subprocess.TimeoutExpired:
24
+ continue
25
+ return process.returncode == 0
26
+
27
+
28
+ def open_ffmpeg(args : List[str]) -> subprocess.Popen[bytes]:
29
+ commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'quiet' ]
30
+ commands.extend(args)
31
+ return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
32
+
33
+
34
+ def log_debug(process : subprocess.Popen[bytes]) -> None:
35
+ _, stderr = process.communicate()
36
+ errors = stderr.decode().split(os.linesep)
37
+
38
+ for error in errors:
39
+ if error.strip():
40
+ logger.debug(error.strip(), __name__.upper())
41
+
42
+
43
+ def extract_frames(target_path : str, temp_video_resolution : str, temp_video_fps : Fps) -> bool:
44
+ trim_frame_start = facefusion.globals.trim_frame_start
45
+ trim_frame_end = facefusion.globals.trim_frame_end
46
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d')
47
+ commands = [ '-i', target_path, '-s', str(temp_video_resolution), '-q:v', '0' ]
48
+
49
+ if trim_frame_start is not None and trim_frame_end is not None:
50
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ':end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ])
51
+ elif trim_frame_start is not None:
52
+ commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ',fps=' + str(temp_video_fps) ])
53
+ elif trim_frame_end is not None:
54
+ commands.extend([ '-vf', 'trim=end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ])
55
+ else:
56
+ commands.extend([ '-vf', 'fps=' + str(temp_video_fps) ])
57
+ commands.extend([ '-vsync', '0', temp_frames_pattern ])
58
+ return run_ffmpeg(commands)
59
+
60
+
61
+ def merge_video(target_path : str, output_video_resolution : str, output_video_fps : Fps) -> bool:
62
+ temp_video_fps = restrict_video_fps(target_path, output_video_fps)
63
+ temp_file_path = get_temp_file_path(target_path)
64
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d')
65
+ commands = [ '-r', str(temp_video_fps), '-i', temp_frames_pattern, '-s', str(output_video_resolution), '-c:v', facefusion.globals.output_video_encoder ]
66
+
67
+ if facefusion.globals.output_video_encoder in [ 'libx264', 'libx265' ]:
68
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
69
+ commands.extend([ '-crf', str(output_video_compression), '-preset', facefusion.globals.output_video_preset ])
70
+ if facefusion.globals.output_video_encoder in [ 'libvpx-vp9' ]:
71
+ output_video_compression = round(63 - (facefusion.globals.output_video_quality * 0.63))
72
+ commands.extend([ '-crf', str(output_video_compression) ])
73
+ if facefusion.globals.output_video_encoder in [ 'h264_nvenc', 'hevc_nvenc' ]:
74
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
75
+ commands.extend([ '-cq', str(output_video_compression), '-preset', map_nvenc_preset(facefusion.globals.output_video_preset) ])
76
+ if facefusion.globals.output_video_encoder in [ 'h264_amf', 'hevc_amf' ]:
77
+ output_video_compression = round(51 - (facefusion.globals.output_video_quality * 0.51))
78
+ commands.extend([ '-qp_i', str(output_video_compression), '-qp_p', str(output_video_compression), '-quality', map_amf_preset(facefusion.globals.output_video_preset) ])
79
+ commands.extend([ '-vf', 'framerate=fps=' + str(output_video_fps), '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_file_path ])
80
+ return run_ffmpeg(commands)
81
+
82
+
83
+ def copy_image(target_path : str, temp_image_resolution : str) -> bool:
84
+ temp_file_path = get_temp_file_path(target_path)
85
+ is_webp = filetype.guess_mime(target_path) == 'image/webp'
86
+ temp_image_compression = 100 if is_webp else 0
87
+ commands = [ '-i', target_path, '-s', str(temp_image_resolution), '-q:v', str(temp_image_compression), '-y', temp_file_path ]
88
+ return run_ffmpeg(commands)
89
+
90
+
91
+ def finalize_image(target_path : str, output_path : str, output_image_resolution : str) -> bool:
92
+ temp_file_path = get_temp_file_path(target_path)
93
+ output_image_compression = round(31 - (facefusion.globals.output_image_quality * 0.31))
94
+ commands = [ '-i', temp_file_path, '-s', str(output_image_resolution), '-q:v', str(output_image_compression), '-y', output_path ]
95
+ return run_ffmpeg(commands)
96
+
97
+
98
+ def read_audio_buffer(target_path : str, sample_rate : int, channel_total : int) -> Optional[AudioBuffer]:
99
+ commands = [ '-i', target_path, '-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sample_rate), '-ac', str(channel_total), '-']
100
+ process = open_ffmpeg(commands)
101
+ audio_buffer, _ = process.communicate()
102
+ if process.returncode == 0:
103
+ return audio_buffer
104
+ return None
105
+
106
+
107
+ def restore_audio(target_path : str, output_path : str, output_video_fps : Fps) -> bool:
108
+ trim_frame_start = facefusion.globals.trim_frame_start
109
+ trim_frame_end = facefusion.globals.trim_frame_end
110
+ temp_file_path = get_temp_file_path(target_path)
111
+ commands = [ '-i', temp_file_path ]
112
+
113
+ if trim_frame_start is not None:
114
+ start_time = trim_frame_start / output_video_fps
115
+ commands.extend([ '-ss', str(start_time) ])
116
+ if trim_frame_end is not None:
117
+ end_time = trim_frame_end / output_video_fps
118
+ commands.extend([ '-to', str(end_time) ])
119
+ commands.extend([ '-i', target_path, '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest', '-y', output_path ])
120
+ return run_ffmpeg(commands)
121
+
122
+
123
+ def replace_audio(target_path : str, audio_path : str, output_path : str) -> bool:
124
+ temp_file_path = get_temp_file_path(target_path)
125
+ commands = [ '-i', temp_file_path, '-i', audio_path, '-af', 'apad', '-shortest', '-y', output_path ]
126
+ return run_ffmpeg(commands)
127
+
128
+
129
+ def map_nvenc_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
130
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast' ]:
131
+ return 'fast'
132
+ if output_video_preset == 'medium':
133
+ return 'medium'
134
+ if output_video_preset in [ 'slow', 'slower', 'veryslow' ]:
135
+ return 'slow'
136
+ return None
137
+
138
+
139
+ def map_amf_preset(output_video_preset : OutputVideoPreset) -> Optional[str]:
140
+ if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast' ]:
141
+ return 'speed'
142
+ if output_video_preset in [ 'faster', 'fast', 'medium' ]:
143
+ return 'balanced'
144
+ if output_video_preset in [ 'slow', 'slower', 'veryslow' ]:
145
+ return 'quality'
146
+ return None
facefusion/filesystem.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import glob
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ import filetype
7
+ from pathlib import Path
8
+
9
+ import facefusion.globals
10
+ from facefusion.common_helper import is_windows
11
+
12
+ if is_windows():
13
+ import ctypes
14
+
15
+
16
+ def get_temp_frame_paths(target_path : str) -> List[str]:
17
+ temp_frames_pattern = get_temp_frames_pattern(target_path, '*')
18
+ return sorted(glob.glob(temp_frames_pattern))
19
+
20
+
21
+ def get_temp_frames_pattern(target_path : str, temp_frame_prefix : str) -> str:
22
+ temp_directory_path = get_temp_directory_path(target_path)
23
+ return os.path.join(temp_directory_path, temp_frame_prefix + '.' + facefusion.globals.temp_frame_format)
24
+
25
+
26
+ def get_temp_file_path(target_path : str) -> str:
27
+ _, target_extension = os.path.splitext(os.path.basename(target_path))
28
+ temp_directory_path = get_temp_directory_path(target_path)
29
+ return os.path.join(temp_directory_path, 'temp' + target_extension)
30
+
31
+
32
+ def get_temp_directory_path(target_path : str) -> str:
33
+ target_name, _ = os.path.splitext(os.path.basename(target_path))
34
+ temp_directory_path = os.path.join(tempfile.gettempdir(), 'facefusion')
35
+ return os.path.join(temp_directory_path, target_name)
36
+
37
+
38
+ def create_temp(target_path : str) -> None:
39
+ temp_directory_path = get_temp_directory_path(target_path)
40
+ Path(temp_directory_path).mkdir(parents = True, exist_ok = True)
41
+
42
+
43
+ def move_temp(target_path : str, output_path : str) -> None:
44
+ temp_file_path = get_temp_file_path(target_path)
45
+
46
+ if is_file(temp_file_path):
47
+ if is_file(output_path):
48
+ os.remove(output_path)
49
+ shutil.move(temp_file_path, output_path)
50
+
51
+
52
+ def clear_temp(target_path : str) -> None:
53
+ temp_directory_path = get_temp_directory_path(target_path)
54
+ parent_directory_path = os.path.dirname(temp_directory_path)
55
+
56
+ if not facefusion.globals.keep_temp and is_directory(temp_directory_path):
57
+ shutil.rmtree(temp_directory_path, ignore_errors = True)
58
+ if os.path.exists(parent_directory_path) and not os.listdir(parent_directory_path):
59
+ os.rmdir(parent_directory_path)
60
+
61
+
62
+ def get_file_size(file_path : str) -> int:
63
+ if is_file(file_path):
64
+ return os.path.getsize(file_path)
65
+ return 0
66
+
67
+
68
+ def is_file(file_path : str) -> bool:
69
+ return bool(file_path and os.path.isfile(file_path))
70
+
71
+
72
+ def is_directory(directory_path : str) -> bool:
73
+ return bool(directory_path and os.path.isdir(directory_path))
74
+
75
+
76
+ def is_audio(audio_path : str) -> bool:
77
+ return is_file(audio_path) and filetype.helpers.is_audio(audio_path)
78
+
79
+
80
+ def has_audio(audio_paths : List[str]) -> bool:
81
+ if audio_paths:
82
+ return any(is_audio(audio_path) for audio_path in audio_paths)
83
+ return False
84
+
85
+
86
+ def is_image(image_path : str) -> bool:
87
+ return is_file(image_path) and filetype.helpers.is_image(image_path)
88
+
89
+
90
+ def has_image(image_paths: List[str]) -> bool:
91
+ if image_paths:
92
+ return any(is_image(image_path) for image_path in image_paths)
93
+ return False
94
+
95
+
96
+ def is_video(video_path : str) -> bool:
97
+ return is_file(video_path) and filetype.helpers.is_video(video_path)
98
+
99
+
100
+ def filter_audio_paths(paths : List[str]) -> List[str]:
101
+ if paths:
102
+ return [ path for path in paths if is_audio(path) ]
103
+ return []
104
+
105
+
106
+ def filter_image_paths(paths : List[str]) -> List[str]:
107
+ if paths:
108
+ return [ path for path in paths if is_image(path) ]
109
+ return []
110
+
111
+
112
+ def resolve_relative_path(path : str) -> str:
113
+ return os.path.abspath(os.path.join(os.path.dirname(__file__), path))
114
+
115
+
116
+ def list_directory(directory_path : str) -> Optional[List[str]]:
117
+ if is_directory(directory_path):
118
+ files = os.listdir(directory_path)
119
+ files = [ Path(file).stem for file in files if not Path(file).stem.startswith(('.', '__')) ]
120
+ return sorted(files)
121
+ return None
122
+
123
+
124
+ def sanitize_path_for_windows(full_path : str) -> Optional[str]:
125
+ buffer_size = 0
126
+
127
+ while True:
128
+ unicode_buffer = ctypes.create_unicode_buffer(buffer_size)
129
+ buffer_threshold = ctypes.windll.kernel32.GetShortPathNameW(full_path, unicode_buffer, buffer_size) #type:ignore[attr-defined]
130
+
131
+ if buffer_size > buffer_threshold:
132
+ return unicode_buffer.value
133
+ if buffer_threshold == 0:
134
+ return None
135
+ buffer_size = buffer_threshold
facefusion/globals.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from facefusion.typing import LogLevel, VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceMaskType, FaceMaskRegion, OutputVideoEncoder, OutputVideoPreset, FaceDetectorModel, FaceRecognizerModel, TempFrameFormat, Padding
4
+
5
+ # general
6
+ config_path : Optional[str] = None
7
+ source_paths : Optional[List[str]] = None
8
+ target_path : Optional[str] = None
9
+ output_path : Optional[str] = None
10
+ # misc
11
+ force_download : Optional[bool] = None
12
+ skip_download : Optional[bool] = None
13
+ headless : Optional[bool] = None
14
+ log_level : Optional[LogLevel] = None
15
+ # execution
16
+ execution_device_id : Optional[str] = None
17
+ execution_providers : List[str] = []
18
+ execution_thread_count : Optional[int] = None
19
+ execution_queue_count : Optional[int] = None
20
+ # memory
21
+ video_memory_strategy : Optional[VideoMemoryStrategy] = None
22
+ system_memory_limit : Optional[int] = None
23
+ # face analyser
24
+ face_analyser_order : Optional[FaceAnalyserOrder] = None
25
+ face_analyser_age : Optional[FaceAnalyserAge] = None
26
+ face_analyser_gender : Optional[FaceAnalyserGender] = None
27
+ face_detector_model : Optional[FaceDetectorModel] = None
28
+ face_detector_size : Optional[str] = None
29
+ face_detector_score : Optional[float] = None
30
+ face_landmarker_score : Optional[float] = None
31
+ face_recognizer_model : Optional[FaceRecognizerModel] = None
32
+ # face selector
33
+ face_selector_mode : Optional[FaceSelectorMode] = None
34
+ reference_face_position : Optional[int] = None
35
+ reference_face_distance : Optional[float] = None
36
+ reference_frame_number : Optional[int] = None
37
+ # face mask
38
+ face_mask_types : Optional[List[FaceMaskType]] = None
39
+ face_mask_blur : Optional[float] = None
40
+ face_mask_padding : Optional[Padding] = None
41
+ face_mask_regions : Optional[List[FaceMaskRegion]] = None
42
+ # frame extraction
43
+ trim_frame_start : Optional[int] = None
44
+ trim_frame_end : Optional[int] = None
45
+ temp_frame_format : Optional[TempFrameFormat] = None
46
+ keep_temp : Optional[bool] = None
47
+ # output creation
48
+ output_image_quality : Optional[int] = None
49
+ output_image_resolution : Optional[str] = None
50
+ output_video_encoder : Optional[OutputVideoEncoder] = None
51
+ output_video_preset : Optional[OutputVideoPreset] = None
52
+ output_video_quality : Optional[int] = None
53
+ output_video_resolution : Optional[str] = None
54
+ output_video_fps : Optional[float] = None
55
+ skip_audio : Optional[bool] = None
56
+ # frame processors
57
+ frame_processors : List[str] = []
58
+ # uis
59
+ open_browser : Optional[bool] = None
60
+ ui_layouts : List[str] = []
facefusion/installer.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Tuple
2
+ import sys
3
+ import os
4
+ import tempfile
5
+ import subprocess
6
+ import inquirer
7
+ from argparse import ArgumentParser, HelpFormatter
8
+
9
+ from facefusion import metadata, wording
10
+ from facefusion.common_helper import is_linux, is_macos, is_windows
11
+
12
+ if is_macos():
13
+ os.environ['SYSTEM_VERSION_COMPAT'] = '0'
14
+
15
+ ONNXRUNTIMES : Dict[str, Tuple[str, str]] = {}
16
+
17
+ if is_macos():
18
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.17.3')
19
+ else:
20
+ ONNXRUNTIMES['default'] = ('onnxruntime', '1.17.3')
21
+ ONNXRUNTIMES['cuda-12.2'] = ('onnxruntime-gpu', '1.17.1')
22
+ ONNXRUNTIMES['cuda-11.8'] = ('onnxruntime-gpu', '1.17.1')
23
+ ONNXRUNTIMES['openvino'] = ('onnxruntime-openvino', '1.15.0')
24
+ if is_linux():
25
+ ONNXRUNTIMES['rocm-5.4.2'] = ('onnxruntime-rocm', '1.16.3')
26
+ ONNXRUNTIMES['rocm-5.6'] = ('onnxruntime-rocm', '1.16.3')
27
+ if is_windows():
28
+ ONNXRUNTIMES['directml'] = ('onnxruntime-directml', '1.17.3')
29
+
30
+
31
+ def cli() -> None:
32
+ program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 200))
33
+ program.add_argument('--onnxruntime', help = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIMES.keys())
34
+ program.add_argument('--skip-conda', help = wording.get('help.skip_conda'), action = 'store_true')
35
+ program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
36
+ run(program)
37
+
38
+
39
+ def run(program : ArgumentParser) -> None:
40
+ args = program.parse_args()
41
+ python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor)
42
+
43
+ if not args.skip_conda and 'CONDA_PREFIX' not in os.environ:
44
+ sys.stdout.write(wording.get('conda_not_activated') + os.linesep)
45
+ sys.exit(1)
46
+ if args.onnxruntime:
47
+ answers =\
48
+ {
49
+ 'onnxruntime': args.onnxruntime
50
+ }
51
+ else:
52
+ answers = inquirer.prompt(
53
+ [
54
+ inquirer.List('onnxruntime', message = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = list(ONNXRUNTIMES.keys()))
55
+ ])
56
+ if answers:
57
+ onnxruntime = answers['onnxruntime']
58
+ onnxruntime_name, onnxruntime_version = ONNXRUNTIMES[onnxruntime]
59
+
60
+ subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--force-reinstall' ])
61
+ if onnxruntime == 'rocm-5.4.2' or onnxruntime == 'rocm-5.6':
62
+ if python_id in [ 'cp39', 'cp310', 'cp311' ]:
63
+ rocm_version = onnxruntime.replace('-', '')
64
+ rocm_version = rocm_version.replace('.', '')
65
+ wheel_name = 'onnxruntime_training-' + onnxruntime_version + '+' + rocm_version + '-' + python_id + '-' + python_id + '-manylinux_2_17_x86_64.manylinux2014_x86_64.whl'
66
+ wheel_path = os.path.join(tempfile.gettempdir(), wheel_name)
67
+ wheel_url = 'https://download.onnxruntime.ai/' + wheel_name
68
+ subprocess.call([ 'curl', '--silent', '--location', '--continue-at', '-', '--output', wheel_path, wheel_url ])
69
+ subprocess.call([ 'pip', 'uninstall', wheel_path, '-y', '-q' ])
70
+ subprocess.call([ 'pip', 'install', wheel_path, '--force-reinstall' ])
71
+ os.remove(wheel_path)
72
+ else:
73
+ subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ])
74
+ if onnxruntime == 'cuda-12.2':
75
+ subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--extra-index-url', 'https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple', '--force-reinstall' ])
76
+ else:
77
+ subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--force-reinstall' ])
78
+ subprocess.call([ 'pip', 'install', 'numpy==1.26.4', '--force-reinstall' ])
facefusion/logger.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+ from logging import basicConfig, getLogger, Logger, DEBUG, INFO, WARNING, ERROR
3
+
4
+ from facefusion.typing import LogLevel
5
+
6
+
7
+ def init(log_level : LogLevel) -> None:
8
+ basicConfig(format = None)
9
+ get_package_logger().setLevel(get_log_levels()[log_level])
10
+
11
+
12
+ def get_package_logger() -> Logger:
13
+ return getLogger('facefusion')
14
+
15
+
16
+ def debug(message : str, scope : str) -> None:
17
+ get_package_logger().debug('[' + scope + '] ' + message)
18
+
19
+
20
+ def info(message : str, scope : str) -> None:
21
+ get_package_logger().info('[' + scope + '] ' + message)
22
+
23
+
24
+ def warn(message : str, scope : str) -> None:
25
+ get_package_logger().warning('[' + scope + '] ' + message)
26
+
27
+
28
+ def error(message : str, scope : str) -> None:
29
+ get_package_logger().error('[' + scope + '] ' + message)
30
+
31
+
32
+ def enable() -> None:
33
+ get_package_logger().disabled = False
34
+
35
+
36
+ def disable() -> None:
37
+ get_package_logger().disabled = True
38
+
39
+
40
+ def get_log_levels() -> Dict[LogLevel, int]:
41
+ return\
42
+ {
43
+ 'error': ERROR,
44
+ 'warn': WARNING,
45
+ 'info': INFO,
46
+ 'debug': DEBUG
47
+ }
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,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ METADATA =\
2
+ {
3
+ 'name': 'FaceFusion',
4
+ 'description': 'Next generation face swapper and enhancer',
5
+ 'version': '2.6.1',
6
+ 'license': 'MIT',
7
+ 'author': 'Henry Ruhs',
8
+ 'url': 'https://facefusion.io'
9
+ }
10
+
11
+
12
+ def get(key : str) -> str:
13
+ return METADATA[key]
facefusion/normalizer.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import hashlib
3
+ import os
4
+
5
+ import facefusion.globals
6
+ from facefusion.filesystem import is_directory
7
+ from facefusion.typing import Padding, Fps
8
+
9
+
10
+ def normalize_output_path(target_path : Optional[str], output_path : Optional[str]) -> Optional[str]:
11
+ if target_path and output_path:
12
+ target_name, target_extension = os.path.splitext(os.path.basename(target_path))
13
+ if is_directory(output_path):
14
+ output_hash = hashlib.sha1(str(facefusion.globals.__dict__).encode('utf-8')).hexdigest()[:8]
15
+ output_name = target_name + '-' + output_hash
16
+ return os.path.join(output_path, output_name + target_extension)
17
+ output_name, output_extension = os.path.splitext(os.path.basename(output_path))
18
+ output_directory_path = os.path.dirname(output_path)
19
+ if is_directory(output_directory_path) and output_extension:
20
+ return os.path.join(output_directory_path, output_name + target_extension)
21
+ return None
22
+
23
+
24
+ def normalize_padding(padding : Optional[List[int]]) -> Optional[Padding]:
25
+ if padding and len(padding) == 1:
26
+ return tuple([ padding[0] ] * 4) #type:ignore[return-value]
27
+ if padding and len(padding) == 2:
28
+ return tuple([ padding[0], padding[1], padding[0], padding[1] ]) #type:ignore[return-value]
29
+ if padding and len(padding) == 3:
30
+ return tuple([ padding[0], padding[1], padding[2], padding[1] ]) #type:ignore[return-value]
31
+ if padding and len(padding) == 4:
32
+ return tuple(padding) #type:ignore[return-value]
33
+ return None
34
+
35
+
36
+ def normalize_fps(fps : Optional[float]) -> Optional[Fps]:
37
+ if fps is not None:
38
+ return max(1.0, min(fps, 60.0))
39
+ return None
facefusion/process_manager.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Generator, List
2
+
3
+ from facefusion.typing import QueuePayload, ProcessState
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/frame/__init__.py ADDED
File without changes
facefusion/processors/frame/choices.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.common_helper import create_int_range
4
+ from facefusion.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel
5
+
6
+ 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' ]
7
+ 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' ]
8
+ face_swapper_models : List[FaceSwapperModel] = [ 'blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256' ]
9
+ frame_colorizer_models : List[FrameColorizerModel] = [ 'ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable' ]
10
+ frame_colorizer_sizes : List[str] = [ '192x192', '256x256', '384x384', '512x512' ]
11
+ 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_hatgan_x4', 'span_kendata_x4', 'ultra_sharp_x4' ]
12
+ lip_syncer_models : List[LipSyncerModel] = [ 'wav2lip_gan' ]
13
+
14
+ face_enhancer_blend_range : List[int] = create_int_range(0, 100, 1)
15
+ frame_colorizer_blend_range : List[int] = create_int_range(0, 100, 1)
16
+ frame_enhancer_blend_range : List[int] = create_int_range(0, 100, 1)
facefusion/processors/frame/core.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import importlib
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from queue import Queue
6
+ from types import ModuleType
7
+ from typing import Any, List
8
+ from tqdm import tqdm
9
+
10
+ import facefusion.globals
11
+ from facefusion.typing import ProcessFrames, QueuePayload
12
+ from facefusion.execution import encode_execution_providers
13
+ from facefusion import logger, wording
14
+
15
+ FRAME_PROCESSORS_MODULES : List[ModuleType] = []
16
+ FRAME_PROCESSORS_METHODS =\
17
+ [
18
+ 'get_frame_processor',
19
+ 'clear_frame_processor',
20
+ 'get_options',
21
+ 'set_options',
22
+ 'register_args',
23
+ 'apply_args',
24
+ 'pre_check',
25
+ 'post_check',
26
+ 'pre_process',
27
+ 'post_process',
28
+ 'get_reference_frame',
29
+ 'process_frame',
30
+ 'process_frames',
31
+ 'process_image',
32
+ 'process_video'
33
+ ]
34
+
35
+
36
+ def load_frame_processor_module(frame_processor : str) -> Any:
37
+ try:
38
+ frame_processor_module = importlib.import_module('facefusion.processors.frame.modules.' + frame_processor)
39
+ for method_name in FRAME_PROCESSORS_METHODS:
40
+ if not hasattr(frame_processor_module, method_name):
41
+ raise NotImplementedError
42
+ except ModuleNotFoundError as exception:
43
+ logger.error(wording.get('frame_processor_not_loaded').format(frame_processor = frame_processor), __name__.upper())
44
+ logger.debug(exception.msg, __name__.upper())
45
+ sys.exit(1)
46
+ except NotImplementedError:
47
+ logger.error(wording.get('frame_processor_not_implemented').format(frame_processor = frame_processor), __name__.upper())
48
+ sys.exit(1)
49
+ return frame_processor_module
50
+
51
+
52
+ def get_frame_processors_modules(frame_processors : List[str]) -> List[ModuleType]:
53
+ global FRAME_PROCESSORS_MODULES
54
+
55
+ if not FRAME_PROCESSORS_MODULES:
56
+ for frame_processor in frame_processors:
57
+ frame_processor_module = load_frame_processor_module(frame_processor)
58
+ FRAME_PROCESSORS_MODULES.append(frame_processor_module)
59
+ return FRAME_PROCESSORS_MODULES
60
+
61
+
62
+ def clear_frame_processors_modules() -> None:
63
+ global FRAME_PROCESSORS_MODULES
64
+
65
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
66
+ frame_processor_module.clear_frame_processor()
67
+ FRAME_PROCESSORS_MODULES = []
68
+
69
+
70
+ def multi_process_frames(source_paths : List[str], temp_frame_paths : List[str], process_frames : ProcessFrames) -> None:
71
+ queue_payloads = create_queue_payloads(temp_frame_paths)
72
+ with tqdm(total = len(queue_payloads), desc = wording.get('processing'), unit = 'frame', ascii = ' =', disable = facefusion.globals.log_level in [ 'warn', 'error' ]) as progress:
73
+ progress.set_postfix(
74
+ {
75
+ 'execution_providers': encode_execution_providers(facefusion.globals.execution_providers),
76
+ 'execution_thread_count': facefusion.globals.execution_thread_count,
77
+ 'execution_queue_count': facefusion.globals.execution_queue_count
78
+ })
79
+ with ThreadPoolExecutor(max_workers = facefusion.globals.execution_thread_count) as executor:
80
+ futures = []
81
+ queue : Queue[QueuePayload] = create_queue(queue_payloads)
82
+ queue_per_future = max(len(queue_payloads) // facefusion.globals.execution_thread_count * facefusion.globals.execution_queue_count, 1)
83
+ while not queue.empty():
84
+ future = executor.submit(process_frames, source_paths, pick_queue(queue, queue_per_future), progress.update)
85
+ futures.append(future)
86
+ for future_done in as_completed(futures):
87
+ future_done.result()
88
+
89
+
90
+ def create_queue(queue_payloads : List[QueuePayload]) -> Queue[QueuePayload]:
91
+ queue : Queue[QueuePayload] = Queue()
92
+ for queue_payload in queue_payloads:
93
+ queue.put(queue_payload)
94
+ return queue
95
+
96
+
97
+ def pick_queue(queue : Queue[QueuePayload], queue_per_future : int) -> List[QueuePayload]:
98
+ queues = []
99
+ for _ in range(queue_per_future):
100
+ if not queue.empty():
101
+ queues.append(queue.get())
102
+ return queues
103
+
104
+
105
+ def create_queue_payloads(temp_frame_paths : List[str]) -> List[QueuePayload]:
106
+ queue_payloads = []
107
+ temp_frame_paths = sorted(temp_frame_paths, key = os.path.basename)
108
+
109
+ for frame_number, frame_path in enumerate(temp_frame_paths):
110
+ frame_payload : QueuePayload =\
111
+ {
112
+ 'frame_number': frame_number,
113
+ 'frame_path': frame_path
114
+ }
115
+ queue_payloads.append(frame_payload)
116
+ return queue_payloads
facefusion/processors/frame/globals.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+
3
+ from facefusion.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel
4
+
5
+ face_debugger_items : Optional[List[FaceDebuggerItem]] = None
6
+ face_enhancer_model : Optional[FaceEnhancerModel] = None
7
+ face_enhancer_blend : Optional[int] = None
8
+ face_swapper_model : Optional[FaceSwapperModel] = None
9
+ frame_colorizer_model : Optional[FrameColorizerModel] = None
10
+ frame_colorizer_blend : Optional[int] = None
11
+ frame_colorizer_size : Optional[str] = None
12
+ frame_enhancer_model : Optional[FrameEnhancerModel] = None
13
+ frame_enhancer_blend : Optional[int] = None
14
+ lip_syncer_model : Optional[LipSyncerModel] = None
facefusion/processors/frame/modules/__init__.py ADDED
File without changes
facefusion/processors/frame/modules/face_debugger.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal
2
+ from argparse import ArgumentParser
3
+ import cv2
4
+ import numpy
5
+
6
+ import facefusion.globals
7
+ import facefusion.processors.frame.core as frame_processors
8
+ from facefusion import config, process_manager, wording
9
+ from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
10
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_region_mask, clear_face_occluder, clear_face_parser
11
+ from facefusion.face_helper import warp_face_by_face_landmark_5, categorize_age, categorize_gender
12
+ from facefusion.face_store import get_reference_faces
13
+ from facefusion.content_analyser import clear_content_analyser
14
+ from facefusion.typing import Face, VisionFrame, UpdateProgress, ProcessMode, QueuePayload
15
+ from facefusion.vision import read_image, read_static_image, write_image
16
+ from facefusion.processors.frame.typings import FaceDebuggerInputs
17
+ from facefusion.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices
18
+
19
+ NAME = __name__.upper()
20
+
21
+
22
+ def get_frame_processor() -> None:
23
+ pass
24
+
25
+
26
+ def clear_frame_processor() -> None:
27
+ pass
28
+
29
+
30
+ def get_options(key : Literal['model']) -> None:
31
+ pass
32
+
33
+
34
+ def set_options(key : Literal['model'], value : Any) -> None:
35
+ pass
36
+
37
+
38
+ def register_args(program : ArgumentParser) -> None:
39
+ program.add_argument('--face-debugger-items', help = wording.get('help.face_debugger_items').format(choices = ', '.join(frame_processors_choices.face_debugger_items)), default = config.get_str_list('frame_processors.face_debugger_items', 'face-landmark-5/68 face-mask'), choices = frame_processors_choices.face_debugger_items, nargs = '+', metavar = 'FACE_DEBUGGER_ITEMS')
40
+
41
+
42
+ def apply_args(program : ArgumentParser) -> None:
43
+ args = program.parse_args()
44
+ frame_processors_globals.face_debugger_items = args.face_debugger_items
45
+
46
+
47
+ def pre_check() -> bool:
48
+ return True
49
+
50
+
51
+ def post_check() -> bool:
52
+ return True
53
+
54
+
55
+ def pre_process(mode : ProcessMode) -> bool:
56
+ return True
57
+
58
+
59
+ def post_process() -> None:
60
+ read_static_image.cache_clear()
61
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
62
+ clear_frame_processor()
63
+ if facefusion.globals.video_memory_strategy == 'strict':
64
+ clear_face_analyser()
65
+ clear_content_analyser()
66
+ clear_face_occluder()
67
+ clear_face_parser()
68
+
69
+
70
+ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
71
+ primary_color = (0, 0, 255)
72
+ secondary_color = (0, 255, 0)
73
+ tertiary_color = (255, 255, 0)
74
+ bounding_box = target_face.bounding_box.astype(numpy.int32)
75
+ temp_vision_frame = temp_vision_frame.copy()
76
+ has_face_landmark_5_fallback = numpy.array_equal(target_face.landmarks.get('5'), target_face.landmarks.get('5/68'))
77
+ has_face_landmark_68_fallback = numpy.array_equal(target_face.landmarks.get('68'), target_face.landmarks.get('68/5'))
78
+
79
+ if 'bounding-box' in frame_processors_globals.face_debugger_items:
80
+ cv2.rectangle(temp_vision_frame, (bounding_box[0], bounding_box[1]), (bounding_box[2], bounding_box[3]), primary_color, 2)
81
+ if 'face-mask' in frame_processors_globals.face_debugger_items:
82
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'arcface_128_v2', (512, 512))
83
+ inverse_matrix = cv2.invertAffineTransform(affine_matrix)
84
+ temp_size = temp_vision_frame.shape[:2][::-1]
85
+ crop_mask_list = []
86
+ if 'box' in facefusion.globals.face_mask_types:
87
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], 0, facefusion.globals.face_mask_padding)
88
+ crop_mask_list.append(box_mask)
89
+ if 'occlusion' in facefusion.globals.face_mask_types:
90
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
91
+ crop_mask_list.append(occlusion_mask)
92
+ if 'region' in facefusion.globals.face_mask_types:
93
+ region_mask = create_region_mask(crop_vision_frame, facefusion.globals.face_mask_regions)
94
+ crop_mask_list.append(region_mask)
95
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
96
+ crop_mask = (crop_mask * 255).astype(numpy.uint8)
97
+ inverse_vision_frame = cv2.warpAffine(crop_mask, inverse_matrix, temp_size)
98
+ inverse_vision_frame = cv2.threshold(inverse_vision_frame, 100, 255, cv2.THRESH_BINARY)[1]
99
+ inverse_vision_frame[inverse_vision_frame > 0] = 255
100
+ inverse_contours = cv2.findContours(inverse_vision_frame, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)[0]
101
+ cv2.drawContours(temp_vision_frame, inverse_contours, -1, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2)
102
+ if 'face-landmark-5' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5')):
103
+ face_landmark_5 = target_face.landmarks.get('5').astype(numpy.int32)
104
+ for index in range(face_landmark_5.shape[0]):
105
+ cv2.circle(temp_vision_frame, (face_landmark_5[index][0], face_landmark_5[index][1]), 3, primary_color, -1)
106
+ if 'face-landmark-5/68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5/68')):
107
+ face_landmark_5_68 = target_face.landmarks.get('5/68').astype(numpy.int32)
108
+ for index in range(face_landmark_5_68.shape[0]):
109
+ 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)
110
+ if 'face-landmark-68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('68')):
111
+ face_landmark_68 = target_face.landmarks.get('68').astype(numpy.int32)
112
+ for index in range(face_landmark_68.shape[0]):
113
+ 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)
114
+ if 'face-landmark-68/5' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('68')):
115
+ face_landmark_68 = target_face.landmarks.get('68/5').astype(numpy.int32)
116
+ for index in range(face_landmark_68.shape[0]):
117
+ cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, primary_color, -1)
118
+ if bounding_box[3] - bounding_box[1] > 50 and bounding_box[2] - bounding_box[0] > 50:
119
+ top = bounding_box[1]
120
+ left = bounding_box[0] - 20
121
+ if 'face-detector-score' in frame_processors_globals.face_debugger_items:
122
+ face_score_text = str(round(target_face.scores.get('detector'), 2))
123
+ top = top + 20
124
+ cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
125
+ if 'face-landmarker-score' in frame_processors_globals.face_debugger_items:
126
+ face_score_text = str(round(target_face.scores.get('landmarker'), 2))
127
+ top = top + 20
128
+ 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)
129
+ if 'age' in frame_processors_globals.face_debugger_items:
130
+ face_age_text = categorize_age(target_face.age)
131
+ top = top + 20
132
+ cv2.putText(temp_vision_frame, face_age_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
133
+ if 'gender' in frame_processors_globals.face_debugger_items:
134
+ face_gender_text = categorize_gender(target_face.gender)
135
+ top = top + 20
136
+ cv2.putText(temp_vision_frame, face_gender_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2)
137
+ return temp_vision_frame
138
+
139
+
140
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
141
+ pass
142
+
143
+
144
+ def process_frame(inputs : FaceDebuggerInputs) -> VisionFrame:
145
+ reference_faces = inputs.get('reference_faces')
146
+ target_vision_frame = inputs.get('target_vision_frame')
147
+
148
+ if facefusion.globals.face_selector_mode == 'many':
149
+ many_faces = get_many_faces(target_vision_frame)
150
+ if many_faces:
151
+ for target_face in many_faces:
152
+ target_vision_frame = debug_face(target_face, target_vision_frame)
153
+ if facefusion.globals.face_selector_mode == 'one':
154
+ target_face = get_one_face(target_vision_frame)
155
+ if target_face:
156
+ target_vision_frame = debug_face(target_face, target_vision_frame)
157
+ if facefusion.globals.face_selector_mode == 'reference':
158
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
159
+ if similar_faces:
160
+ for similar_face in similar_faces:
161
+ target_vision_frame = debug_face(similar_face, target_vision_frame)
162
+ return target_vision_frame
163
+
164
+
165
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
166
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
167
+
168
+ for queue_payload in process_manager.manage(queue_payloads):
169
+ target_vision_path = queue_payload['frame_path']
170
+ target_vision_frame = read_image(target_vision_path)
171
+ output_vision_frame = process_frame(
172
+ {
173
+ 'reference_faces': reference_faces,
174
+ 'target_vision_frame': target_vision_frame
175
+ })
176
+ write_image(target_vision_path, output_vision_frame)
177
+ update_progress(1)
178
+
179
+
180
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
181
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
182
+ target_vision_frame = read_static_image(target_path)
183
+ output_vision_frame = process_frame(
184
+ {
185
+ 'reference_faces': reference_faces,
186
+ 'target_vision_frame': target_vision_frame
187
+ })
188
+ write_image(output_path, output_vision_frame)
189
+
190
+
191
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
192
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/face_enhancer.py ADDED
@@ -0,0 +1,301 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+
8
+ import facefusion.globals
9
+ import facefusion.processors.frame.core as frame_processors
10
+ from facefusion import config, process_manager, logger, wording
11
+ from facefusion.face_analyser import get_many_faces, clear_face_analyser, find_similar_faces, get_one_face
12
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, clear_face_occluder
13
+ from facefusion.face_helper import warp_face_by_face_landmark_5, paste_back
14
+ from facefusion.execution import apply_execution_provider_options
15
+ from facefusion.content_analyser import clear_content_analyser
16
+ from facefusion.face_store import get_reference_faces
17
+ from facefusion.normalizer import normalize_output_path
18
+ from facefusion.thread_helper import thread_lock, thread_semaphore
19
+ from facefusion.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
20
+ from facefusion.common_helper import create_metavar
21
+ from facefusion.filesystem import is_file, is_image, is_video, resolve_relative_path
22
+ from facefusion.download import conditional_download, is_download_done
23
+ from facefusion.vision import read_image, read_static_image, write_image
24
+ from facefusion.processors.frame.typings import FaceEnhancerInputs
25
+ from facefusion.processors.frame import globals as frame_processors_globals
26
+ from facefusion.processors.frame import choices as frame_processors_choices
27
+
28
+ FRAME_PROCESSOR = None
29
+ NAME = __name__.upper()
30
+ MODELS : ModelSet =\
31
+ {
32
+ 'codeformer':
33
+ {
34
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx',
35
+ 'path': resolve_relative_path('../.assets/models/codeformer.onnx'),
36
+ 'template': 'ffhq_512',
37
+ 'size': (512, 512)
38
+ },
39
+ 'gfpgan_1.2':
40
+ {
41
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.2.onnx',
42
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.2.onnx'),
43
+ 'template': 'ffhq_512',
44
+ 'size': (512, 512)
45
+ },
46
+ 'gfpgan_1.3':
47
+ {
48
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.3.onnx',
49
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.3.onnx'),
50
+ 'template': 'ffhq_512',
51
+ 'size': (512, 512)
52
+ },
53
+ 'gfpgan_1.4':
54
+ {
55
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.4.onnx',
56
+ 'path': resolve_relative_path('../.assets/models/gfpgan_1.4.onnx'),
57
+ 'template': 'ffhq_512',
58
+ 'size': (512, 512)
59
+ },
60
+ 'gpen_bfr_256':
61
+ {
62
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_256.onnx',
63
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_256.onnx'),
64
+ 'template': 'arcface_128_v2',
65
+ 'size': (256, 256)
66
+ },
67
+ 'gpen_bfr_512':
68
+ {
69
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_512.onnx',
70
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_512.onnx'),
71
+ 'template': 'ffhq_512',
72
+ 'size': (512, 512)
73
+ },
74
+ 'gpen_bfr_1024':
75
+ {
76
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_1024.onnx',
77
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_1024.onnx'),
78
+ 'template': 'ffhq_512',
79
+ 'size': (1024, 1024)
80
+ },
81
+ 'gpen_bfr_2048':
82
+ {
83
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_2048.onnx',
84
+ 'path': resolve_relative_path('../.assets/models/gpen_bfr_2048.onnx'),
85
+ 'template': 'ffhq_512',
86
+ 'size': (2048, 2048)
87
+ },
88
+ 'restoreformer_plus_plus':
89
+ {
90
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/restoreformer_plus_plus.onnx',
91
+ 'path': resolve_relative_path('../.assets/models/restoreformer_plus_plus.onnx'),
92
+ 'template': 'ffhq_512',
93
+ 'size': (512, 512)
94
+ }
95
+ }
96
+ OPTIONS : Optional[OptionsWithModel] = None
97
+
98
+
99
+ def get_frame_processor() -> Any:
100
+ global FRAME_PROCESSOR
101
+
102
+ with thread_lock():
103
+ while process_manager.is_checking():
104
+ sleep(0.5)
105
+ if FRAME_PROCESSOR is None:
106
+ model_path = get_options('model').get('path')
107
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
108
+ return FRAME_PROCESSOR
109
+
110
+
111
+ def clear_frame_processor() -> None:
112
+ global FRAME_PROCESSOR
113
+
114
+ FRAME_PROCESSOR = None
115
+
116
+
117
+ def get_options(key : Literal['model']) -> Any:
118
+ global OPTIONS
119
+
120
+ if OPTIONS is None:
121
+ OPTIONS =\
122
+ {
123
+ 'model': MODELS[frame_processors_globals.face_enhancer_model]
124
+ }
125
+ return OPTIONS.get(key)
126
+
127
+
128
+ def set_options(key : Literal['model'], value : Any) -> None:
129
+ global OPTIONS
130
+
131
+ OPTIONS[key] = value
132
+
133
+
134
+ def register_args(program : ArgumentParser) -> None:
135
+ program.add_argument('--face-enhancer-model', help = wording.get('help.face_enhancer_model'), default = config.get_str_value('frame_processors.face_enhancer_model', 'gfpgan_1.4'), choices = frame_processors_choices.face_enhancer_models)
136
+ program.add_argument('--face-enhancer-blend', help = wording.get('help.face_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.face_enhancer_blend', '80'), choices = frame_processors_choices.face_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.face_enhancer_blend_range))
137
+
138
+
139
+ def apply_args(program : ArgumentParser) -> None:
140
+ args = program.parse_args()
141
+ frame_processors_globals.face_enhancer_model = args.face_enhancer_model
142
+ frame_processors_globals.face_enhancer_blend = args.face_enhancer_blend
143
+
144
+
145
+ def pre_check() -> bool:
146
+ download_directory_path = resolve_relative_path('../.assets/models')
147
+ model_url = get_options('model').get('url')
148
+ model_path = get_options('model').get('path')
149
+
150
+ if not facefusion.globals.skip_download:
151
+ process_manager.check()
152
+ conditional_download(download_directory_path, [ model_url ])
153
+ process_manager.end()
154
+ return is_file(model_path)
155
+
156
+
157
+ def post_check() -> bool:
158
+ model_url = get_options('model').get('url')
159
+ model_path = get_options('model').get('path')
160
+
161
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
162
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
163
+ return False
164
+ if not is_file(model_path):
165
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
166
+ return False
167
+ return True
168
+
169
+
170
+ def pre_process(mode : ProcessMode) -> bool:
171
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
172
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
173
+ return False
174
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
175
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
176
+ return False
177
+ return True
178
+
179
+
180
+ def post_process() -> None:
181
+ read_static_image.cache_clear()
182
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
183
+ clear_frame_processor()
184
+ if facefusion.globals.video_memory_strategy == 'strict':
185
+ clear_face_analyser()
186
+ clear_content_analyser()
187
+ clear_face_occluder()
188
+
189
+
190
+ def enhance_face(target_face: Face, temp_vision_frame : VisionFrame) -> VisionFrame:
191
+ model_template = get_options('model').get('template')
192
+ model_size = get_options('model').get('size')
193
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size)
194
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, (0, 0, 0, 0))
195
+ crop_mask_list =\
196
+ [
197
+ box_mask
198
+ ]
199
+
200
+ if 'occlusion' in facefusion.globals.face_mask_types:
201
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
202
+ crop_mask_list.append(occlusion_mask)
203
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
204
+ crop_vision_frame = apply_enhance(crop_vision_frame)
205
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
206
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
207
+ paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
208
+ temp_vision_frame = blend_frame(temp_vision_frame, paste_vision_frame)
209
+ return temp_vision_frame
210
+
211
+
212
+ def apply_enhance(crop_vision_frame : VisionFrame) -> VisionFrame:
213
+ frame_processor = get_frame_processor()
214
+ frame_processor_inputs = {}
215
+
216
+ for frame_processor_input in frame_processor.get_inputs():
217
+ if frame_processor_input.name == 'input':
218
+ frame_processor_inputs[frame_processor_input.name] = crop_vision_frame
219
+ if frame_processor_input.name == 'weight':
220
+ weight = numpy.array([ 1 ]).astype(numpy.double)
221
+ frame_processor_inputs[frame_processor_input.name] = weight
222
+ with thread_semaphore():
223
+ crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
224
+ return crop_vision_frame
225
+
226
+
227
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
228
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
229
+ crop_vision_frame = (crop_vision_frame - 0.5) / 0.5
230
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
231
+ return crop_vision_frame
232
+
233
+
234
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
235
+ crop_vision_frame = numpy.clip(crop_vision_frame, -1, 1)
236
+ crop_vision_frame = (crop_vision_frame + 1) / 2
237
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
238
+ crop_vision_frame = (crop_vision_frame * 255.0).round()
239
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1]
240
+ return crop_vision_frame
241
+
242
+
243
+ def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame:
244
+ face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100)
245
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, face_enhancer_blend, paste_vision_frame, 1 - face_enhancer_blend, 0)
246
+ return temp_vision_frame
247
+
248
+
249
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
250
+ return enhance_face(target_face, temp_vision_frame)
251
+
252
+
253
+ def process_frame(inputs : FaceEnhancerInputs) -> VisionFrame:
254
+ reference_faces = inputs.get('reference_faces')
255
+ target_vision_frame = inputs.get('target_vision_frame')
256
+
257
+ if facefusion.globals.face_selector_mode == 'many':
258
+ many_faces = get_many_faces(target_vision_frame)
259
+ if many_faces:
260
+ for target_face in many_faces:
261
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
262
+ if facefusion.globals.face_selector_mode == 'one':
263
+ target_face = get_one_face(target_vision_frame)
264
+ if target_face:
265
+ target_vision_frame = enhance_face(target_face, target_vision_frame)
266
+ if facefusion.globals.face_selector_mode == 'reference':
267
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
268
+ if similar_faces:
269
+ for similar_face in similar_faces:
270
+ target_vision_frame = enhance_face(similar_face, target_vision_frame)
271
+ return target_vision_frame
272
+
273
+
274
+ def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
275
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
276
+
277
+ for queue_payload in process_manager.manage(queue_payloads):
278
+ target_vision_path = queue_payload['frame_path']
279
+ target_vision_frame = read_image(target_vision_path)
280
+ output_vision_frame = process_frame(
281
+ {
282
+ 'reference_faces': reference_faces,
283
+ 'target_vision_frame': target_vision_frame
284
+ })
285
+ write_image(target_vision_path, output_vision_frame)
286
+ update_progress(1)
287
+
288
+
289
+ def process_image(source_path : str, target_path : str, output_path : str) -> None:
290
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
291
+ target_vision_frame = read_static_image(target_path)
292
+ output_vision_frame = process_frame(
293
+ {
294
+ 'reference_faces': reference_faces,
295
+ 'target_vision_frame': target_vision_frame
296
+ })
297
+ write_image(output_path, output_vision_frame)
298
+
299
+
300
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
301
+ frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/face_swapper.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import numpy
5
+ import onnx
6
+ import onnxruntime
7
+ from onnx import numpy_helper
8
+
9
+ import facefusion.globals
10
+ import facefusion.processors.frame.core as frame_processors
11
+ from facefusion import config, process_manager, logger, wording
12
+ from facefusion.execution import has_execution_provider, apply_execution_provider_options
13
+ from facefusion.face_analyser import get_one_face, get_average_face, get_many_faces, find_similar_faces, clear_face_analyser
14
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_region_mask, clear_face_occluder, clear_face_parser
15
+ from facefusion.face_helper import warp_face_by_face_landmark_5, paste_back
16
+ from facefusion.face_store import get_reference_faces
17
+ from facefusion.content_analyser import clear_content_analyser
18
+ from facefusion.normalizer import normalize_output_path
19
+ from facefusion.thread_helper import thread_lock, conditional_thread_semaphore
20
+ from facefusion.typing import Face, Embedding, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
21
+ from facefusion.filesystem import is_file, is_image, has_image, is_video, filter_image_paths, resolve_relative_path
22
+ from facefusion.download import conditional_download, is_download_done
23
+ from facefusion.vision import read_image, read_static_image, read_static_images, write_image
24
+ from facefusion.processors.frame.typings import FaceSwapperInputs
25
+ from facefusion.processors.frame import globals as frame_processors_globals
26
+ from facefusion.processors.frame import choices as frame_processors_choices
27
+
28
+ FRAME_PROCESSOR = None
29
+ MODEL_INITIALIZER = None
30
+ NAME = __name__.upper()
31
+ MODELS : ModelSet =\
32
+ {
33
+ 'blendswap_256':
34
+ {
35
+ 'type': 'blendswap',
36
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/blendswap_256.onnx',
37
+ 'path': resolve_relative_path('../.assets/models/blendswap_256.onnx'),
38
+ 'template': 'ffhq_512',
39
+ 'size': (256, 256),
40
+ 'mean': [ 0.0, 0.0, 0.0 ],
41
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
42
+ },
43
+ 'inswapper_128':
44
+ {
45
+ 'type': 'inswapper',
46
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx',
47
+ 'path': resolve_relative_path('../.assets/models/inswapper_128.onnx'),
48
+ 'template': 'arcface_128_v2',
49
+ 'size': (128, 128),
50
+ 'mean': [ 0.0, 0.0, 0.0 ],
51
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
52
+ },
53
+ 'inswapper_128_fp16':
54
+ {
55
+ 'type': 'inswapper',
56
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx',
57
+ 'path': resolve_relative_path('../.assets/models/inswapper_128_fp16.onnx'),
58
+ 'template': 'arcface_128_v2',
59
+ 'size': (128, 128),
60
+ 'mean': [ 0.0, 0.0, 0.0 ],
61
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
62
+ },
63
+ 'simswap_256':
64
+ {
65
+ 'type': 'simswap',
66
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_256.onnx',
67
+ 'path': resolve_relative_path('../.assets/models/simswap_256.onnx'),
68
+ 'template': 'arcface_112_v1',
69
+ 'size': (256, 256),
70
+ 'mean': [ 0.485, 0.456, 0.406 ],
71
+ 'standard_deviation': [ 0.229, 0.224, 0.225 ]
72
+ },
73
+ 'simswap_512_unofficial':
74
+ {
75
+ 'type': 'simswap',
76
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_512_unofficial.onnx',
77
+ 'path': resolve_relative_path('../.assets/models/simswap_512_unofficial.onnx'),
78
+ 'template': 'arcface_112_v1',
79
+ 'size': (512, 512),
80
+ 'mean': [ 0.0, 0.0, 0.0 ],
81
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
82
+ },
83
+ 'uniface_256':
84
+ {
85
+ 'type': 'uniface',
86
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/uniface_256.onnx',
87
+ 'path': resolve_relative_path('../.assets/models/uniface_256.onnx'),
88
+ 'template': 'ffhq_512',
89
+ 'size': (256, 256),
90
+ 'mean': [ 0.0, 0.0, 0.0 ],
91
+ 'standard_deviation': [ 1.0, 1.0, 1.0 ]
92
+ }
93
+ }
94
+ OPTIONS : Optional[OptionsWithModel] = None
95
+
96
+
97
+ def get_frame_processor() -> Any:
98
+ global FRAME_PROCESSOR
99
+
100
+ with thread_lock():
101
+ while process_manager.is_checking():
102
+ sleep(0.5)
103
+ if FRAME_PROCESSOR is None:
104
+ model_path = get_options('model').get('path')
105
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
106
+ return FRAME_PROCESSOR
107
+
108
+
109
+ def clear_frame_processor() -> None:
110
+ global FRAME_PROCESSOR
111
+
112
+ FRAME_PROCESSOR = None
113
+
114
+
115
+ def get_model_initializer() -> Any:
116
+ global MODEL_INITIALIZER
117
+
118
+ with thread_lock():
119
+ while process_manager.is_checking():
120
+ sleep(0.5)
121
+ if MODEL_INITIALIZER is None:
122
+ model_path = get_options('model').get('path')
123
+ model = onnx.load(model_path)
124
+ MODEL_INITIALIZER = numpy_helper.to_array(model.graph.initializer[-1])
125
+ return MODEL_INITIALIZER
126
+
127
+
128
+ def clear_model_initializer() -> None:
129
+ global MODEL_INITIALIZER
130
+
131
+ MODEL_INITIALIZER = None
132
+
133
+
134
+ def get_options(key : Literal['model']) -> Any:
135
+ global OPTIONS
136
+
137
+ if OPTIONS is None:
138
+ OPTIONS =\
139
+ {
140
+ 'model': MODELS[frame_processors_globals.face_swapper_model]
141
+ }
142
+ return OPTIONS.get(key)
143
+
144
+
145
+ def set_options(key : Literal['model'], value : Any) -> None:
146
+ global OPTIONS
147
+
148
+ OPTIONS[key] = value
149
+
150
+
151
+ def register_args(program : ArgumentParser) -> None:
152
+ if has_execution_provider('CoreMLExecutionProvider') or has_execution_provider('OpenVINOExecutionProvider'):
153
+ face_swapper_model_fallback = 'inswapper_128'
154
+ else:
155
+ face_swapper_model_fallback = 'inswapper_128_fp16'
156
+ program.add_argument('--face-swapper-model', help = wording.get('help.face_swapper_model'), default = config.get_str_value('frame_processors.face_swapper_model', face_swapper_model_fallback), choices = frame_processors_choices.face_swapper_models)
157
+
158
+
159
+ def apply_args(program : ArgumentParser) -> None:
160
+ args = program.parse_args()
161
+ frame_processors_globals.face_swapper_model = args.face_swapper_model
162
+ if args.face_swapper_model == 'blendswap_256':
163
+ facefusion.globals.face_recognizer_model = 'arcface_blendswap'
164
+ if args.face_swapper_model == 'inswapper_128' or args.face_swapper_model == 'inswapper_128_fp16':
165
+ facefusion.globals.face_recognizer_model = 'arcface_inswapper'
166
+ if args.face_swapper_model == 'simswap_256' or args.face_swapper_model == 'simswap_512_unofficial':
167
+ facefusion.globals.face_recognizer_model = 'arcface_simswap'
168
+ if args.face_swapper_model == 'uniface_256':
169
+ facefusion.globals.face_recognizer_model = 'arcface_uniface'
170
+
171
+
172
+ def pre_check() -> bool:
173
+ download_directory_path = resolve_relative_path('../.assets/models')
174
+ model_url = get_options('model').get('url')
175
+ model_path = get_options('model').get('path')
176
+
177
+ if not facefusion.globals.skip_download:
178
+ process_manager.check()
179
+ conditional_download(download_directory_path, [ model_url ])
180
+ process_manager.end()
181
+ return is_file(model_path)
182
+
183
+
184
+ def post_check() -> bool:
185
+ model_url = get_options('model').get('url')
186
+ model_path = get_options('model').get('path')
187
+
188
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
189
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
190
+ return False
191
+ if not is_file(model_path):
192
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
193
+ return False
194
+ return True
195
+
196
+
197
+ def pre_process(mode : ProcessMode) -> bool:
198
+ if not has_image(facefusion.globals.source_paths):
199
+ logger.error(wording.get('select_image_source') + wording.get('exclamation_mark'), NAME)
200
+ return False
201
+ source_image_paths = filter_image_paths(facefusion.globals.source_paths)
202
+ source_frames = read_static_images(source_image_paths)
203
+ for source_frame in source_frames:
204
+ if not get_one_face(source_frame):
205
+ logger.error(wording.get('no_source_face_detected') + wording.get('exclamation_mark'), NAME)
206
+ return False
207
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
208
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
209
+ return False
210
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
211
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
212
+ return False
213
+ return True
214
+
215
+
216
+ def post_process() -> None:
217
+ read_static_image.cache_clear()
218
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
219
+ clear_model_initializer()
220
+ clear_frame_processor()
221
+ if facefusion.globals.video_memory_strategy == 'strict':
222
+ clear_face_analyser()
223
+ clear_content_analyser()
224
+ clear_face_occluder()
225
+ clear_face_parser()
226
+
227
+
228
+ def swap_face(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
229
+ model_template = get_options('model').get('template')
230
+ model_size = get_options('model').get('size')
231
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size)
232
+ crop_mask_list = []
233
+
234
+ if 'box' in facefusion.globals.face_mask_types:
235
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, facefusion.globals.face_mask_padding)
236
+ crop_mask_list.append(box_mask)
237
+ if 'occlusion' in facefusion.globals.face_mask_types:
238
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
239
+ crop_mask_list.append(occlusion_mask)
240
+ crop_vision_frame = prepare_crop_frame(crop_vision_frame)
241
+ crop_vision_frame = apply_swap(source_face, crop_vision_frame)
242
+ crop_vision_frame = normalize_crop_frame(crop_vision_frame)
243
+ if 'region' in facefusion.globals.face_mask_types:
244
+ region_mask = create_region_mask(crop_vision_frame, facefusion.globals.face_mask_regions)
245
+ crop_mask_list.append(region_mask)
246
+ crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1)
247
+ temp_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
248
+ return temp_vision_frame
249
+
250
+
251
+ def apply_swap(source_face : Face, crop_vision_frame : VisionFrame) -> VisionFrame:
252
+ frame_processor = get_frame_processor()
253
+ model_type = get_options('model').get('type')
254
+ frame_processor_inputs = {}
255
+
256
+ for frame_processor_input in frame_processor.get_inputs():
257
+ if frame_processor_input.name == 'source':
258
+ if model_type == 'blendswap' or model_type == 'uniface':
259
+ frame_processor_inputs[frame_processor_input.name] = prepare_source_frame(source_face)
260
+ else:
261
+ frame_processor_inputs[frame_processor_input.name] = prepare_source_embedding(source_face)
262
+ if frame_processor_input.name == 'target':
263
+ frame_processor_inputs[frame_processor_input.name] = crop_vision_frame
264
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
265
+ crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0]
266
+ return crop_vision_frame
267
+
268
+
269
+ def prepare_source_frame(source_face : Face) -> VisionFrame:
270
+ model_type = get_options('model').get('type')
271
+ source_vision_frame = read_static_image(facefusion.globals.source_paths[0])
272
+ if model_type == 'blendswap':
273
+ source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'arcface_112_v2', (112, 112))
274
+ if model_type == 'uniface':
275
+ source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'ffhq_512', (256, 256))
276
+ source_vision_frame = source_vision_frame[:, :, ::-1] / 255.0
277
+ source_vision_frame = source_vision_frame.transpose(2, 0, 1)
278
+ source_vision_frame = numpy.expand_dims(source_vision_frame, axis = 0).astype(numpy.float32)
279
+ return source_vision_frame
280
+
281
+
282
+ def prepare_source_embedding(source_face : Face) -> Embedding:
283
+ model_type = get_options('model').get('type')
284
+ if model_type == 'inswapper':
285
+ model_initializer = get_model_initializer()
286
+ source_embedding = source_face.embedding.reshape((1, -1))
287
+ source_embedding = numpy.dot(source_embedding, model_initializer) / numpy.linalg.norm(source_embedding)
288
+ else:
289
+ source_embedding = source_face.normed_embedding.reshape(1, -1)
290
+ return source_embedding
291
+
292
+
293
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
294
+ model_mean = get_options('model').get('mean')
295
+ model_standard_deviation = get_options('model').get('standard_deviation')
296
+ crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0
297
+ crop_vision_frame = (crop_vision_frame - model_mean) / model_standard_deviation
298
+ crop_vision_frame = crop_vision_frame.transpose(2, 0, 1)
299
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0).astype(numpy.float32)
300
+ return crop_vision_frame
301
+
302
+
303
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
304
+ crop_vision_frame = crop_vision_frame.transpose(1, 2, 0)
305
+ crop_vision_frame = (crop_vision_frame * 255.0).round()
306
+ crop_vision_frame = crop_vision_frame[:, :, ::-1]
307
+ return crop_vision_frame
308
+
309
+
310
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
311
+ return swap_face(source_face, target_face, temp_vision_frame)
312
+
313
+
314
+ def process_frame(inputs : FaceSwapperInputs) -> VisionFrame:
315
+ reference_faces = inputs.get('reference_faces')
316
+ source_face = inputs.get('source_face')
317
+ target_vision_frame = inputs.get('target_vision_frame')
318
+
319
+ if facefusion.globals.face_selector_mode == 'many':
320
+ many_faces = get_many_faces(target_vision_frame)
321
+ if many_faces:
322
+ for target_face in many_faces:
323
+ target_vision_frame = swap_face(source_face, target_face, target_vision_frame)
324
+ if facefusion.globals.face_selector_mode == 'one':
325
+ target_face = get_one_face(target_vision_frame)
326
+ if target_face:
327
+ target_vision_frame = swap_face(source_face, target_face, target_vision_frame)
328
+ if facefusion.globals.face_selector_mode == 'reference':
329
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
330
+ if similar_faces:
331
+ for similar_face in similar_faces:
332
+ target_vision_frame = swap_face(source_face, similar_face, target_vision_frame)
333
+ return target_vision_frame
334
+
335
+
336
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
337
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
338
+ source_frames = read_static_images(source_paths)
339
+ source_face = get_average_face(source_frames)
340
+
341
+ for queue_payload in process_manager.manage(queue_payloads):
342
+ target_vision_path = queue_payload['frame_path']
343
+ target_vision_frame = read_image(target_vision_path)
344
+ output_vision_frame = process_frame(
345
+ {
346
+ 'reference_faces': reference_faces,
347
+ 'source_face': source_face,
348
+ 'target_vision_frame': target_vision_frame
349
+ })
350
+ write_image(target_vision_path, output_vision_frame)
351
+ update_progress(1)
352
+
353
+
354
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
355
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
356
+ source_frames = read_static_images(source_paths)
357
+ source_face = get_average_face(source_frames)
358
+ target_vision_frame = read_static_image(target_path)
359
+ output_vision_frame = process_frame(
360
+ {
361
+ 'reference_faces': reference_faces,
362
+ 'source_face': source_face,
363
+ 'target_vision_frame': target_vision_frame
364
+ })
365
+ write_image(output_path, output_vision_frame)
366
+
367
+
368
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
369
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/frame_colorizer.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+
8
+ import facefusion.globals
9
+ import facefusion.processors.frame.core as frame_processors
10
+ from facefusion import config, process_manager, logger, wording
11
+ from facefusion.face_analyser import clear_face_analyser
12
+ from facefusion.content_analyser import clear_content_analyser
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.normalizer import normalize_output_path
15
+ from facefusion.thread_helper import thread_lock, thread_semaphore
16
+ from facefusion.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
17
+ from facefusion.common_helper import create_metavar
18
+ from facefusion.filesystem import is_file, resolve_relative_path, is_image, is_video
19
+ from facefusion.download import conditional_download, is_download_done
20
+ from facefusion.vision import read_image, read_static_image, write_image, unpack_resolution
21
+ from facefusion.processors.frame.typings import FrameColorizerInputs
22
+ from facefusion.processors.frame import globals as frame_processors_globals
23
+ from facefusion.processors.frame import choices as frame_processors_choices
24
+
25
+ FRAME_PROCESSOR = None
26
+ NAME = __name__.upper()
27
+ MODELS : ModelSet =\
28
+ {
29
+ 'ddcolor':
30
+ {
31
+ 'type': 'ddcolor',
32
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ddcolor.onnx',
33
+ 'path': resolve_relative_path('../.assets/models/ddcolor.onnx')
34
+ },
35
+ 'ddcolor_artistic':
36
+ {
37
+ 'type': 'ddcolor',
38
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ddcolor_artistic.onnx',
39
+ 'path': resolve_relative_path('../.assets/models/ddcolor_artistic.onnx')
40
+ },
41
+ 'deoldify':
42
+ {
43
+ 'type': 'deoldify',
44
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify.onnx',
45
+ 'path': resolve_relative_path('../.assets/models/deoldify.onnx')
46
+ },
47
+ 'deoldify_artistic':
48
+ {
49
+ 'type': 'deoldify',
50
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify_artistic.onnx',
51
+ 'path': resolve_relative_path('../.assets/models/deoldify_artistic.onnx')
52
+ },
53
+ 'deoldify_stable':
54
+ {
55
+ 'type': 'deoldify',
56
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify_stable.onnx',
57
+ 'path': resolve_relative_path('../.assets/models/deoldify_stable.onnx')
58
+ }
59
+ }
60
+ OPTIONS : Optional[OptionsWithModel] = None
61
+
62
+
63
+ def get_frame_processor() -> Any:
64
+ global FRAME_PROCESSOR
65
+
66
+ with thread_lock():
67
+ while process_manager.is_checking():
68
+ sleep(0.5)
69
+ if FRAME_PROCESSOR is None:
70
+ model_path = get_options('model').get('path')
71
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
72
+ return FRAME_PROCESSOR
73
+
74
+
75
+ def clear_frame_processor() -> None:
76
+ global FRAME_PROCESSOR
77
+
78
+ FRAME_PROCESSOR = None
79
+
80
+
81
+ def get_options(key : Literal['model']) -> Any:
82
+ global OPTIONS
83
+
84
+ if OPTIONS is None:
85
+ OPTIONS =\
86
+ {
87
+ 'model': MODELS[frame_processors_globals.frame_colorizer_model]
88
+ }
89
+ return OPTIONS.get(key)
90
+
91
+
92
+ def set_options(key : Literal['model'], value : Any) -> None:
93
+ global OPTIONS
94
+
95
+ OPTIONS[key] = value
96
+
97
+
98
+ def register_args(program : ArgumentParser) -> None:
99
+ program.add_argument('--frame-colorizer-model', help = wording.get('help.frame_colorizer_model'), default = config.get_str_value('frame_processors.frame_colorizer_model', 'ddcolor'), choices = frame_processors_choices.frame_colorizer_models)
100
+ program.add_argument('--frame-colorizer-blend', help = wording.get('help.frame_colorizer_blend'), type = int, default = config.get_int_value('frame_processors.frame_colorizer_blend', '100'), choices = frame_processors_choices.frame_colorizer_blend_range, metavar = create_metavar(frame_processors_choices.frame_colorizer_blend_range))
101
+ program.add_argument('--frame-colorizer-size', help = wording.get('help.frame_colorizer_size'), type = str, default = config.get_str_value('frame_processors.frame_colorizer_size', '256x256'), choices = frame_processors_choices.frame_colorizer_sizes)
102
+
103
+
104
+ def apply_args(program : ArgumentParser) -> None:
105
+ args = program.parse_args()
106
+ frame_processors_globals.frame_colorizer_model = args.frame_colorizer_model
107
+ frame_processors_globals.frame_colorizer_blend = args.frame_colorizer_blend
108
+ frame_processors_globals.frame_colorizer_size = args.frame_colorizer_size
109
+
110
+
111
+ def pre_check() -> bool:
112
+ download_directory_path = resolve_relative_path('../.assets/models')
113
+ model_url = get_options('model').get('url')
114
+ model_path = get_options('model').get('path')
115
+
116
+ if not facefusion.globals.skip_download:
117
+ process_manager.check()
118
+ conditional_download(download_directory_path, [ model_url ])
119
+ process_manager.end()
120
+ return is_file(model_path)
121
+
122
+
123
+ def post_check() -> bool:
124
+ model_url = get_options('model').get('url')
125
+ model_path = get_options('model').get('path')
126
+
127
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
128
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
129
+ return False
130
+ if not is_file(model_path):
131
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
132
+ return False
133
+ return True
134
+
135
+
136
+ def pre_process(mode : ProcessMode) -> bool:
137
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
138
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
139
+ return False
140
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
141
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
142
+ return False
143
+ return True
144
+
145
+
146
+ def post_process() -> None:
147
+ read_static_image.cache_clear()
148
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
149
+ clear_frame_processor()
150
+ if facefusion.globals.video_memory_strategy == 'strict':
151
+ clear_face_analyser()
152
+ clear_content_analyser()
153
+
154
+
155
+ def colorize_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
156
+ frame_processor = get_frame_processor()
157
+ prepare_vision_frame = prepare_temp_frame(temp_vision_frame)
158
+ with thread_semaphore():
159
+ color_vision_frame = frame_processor.run(None,
160
+ {
161
+ frame_processor.get_inputs()[0].name: prepare_vision_frame
162
+ })[0][0]
163
+ color_vision_frame = merge_color_frame(temp_vision_frame, color_vision_frame)
164
+ color_vision_frame = blend_frame(temp_vision_frame, color_vision_frame)
165
+ return color_vision_frame
166
+
167
+
168
+ def prepare_temp_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
169
+ model_size = unpack_resolution(frame_processors_globals.frame_colorizer_size)
170
+ model_type = get_options('model').get('type')
171
+ temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_BGR2GRAY)
172
+ temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_GRAY2RGB)
173
+ if model_type == 'ddcolor':
174
+ temp_vision_frame = (temp_vision_frame / 255.0).astype(numpy.float32)
175
+ temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_RGB2LAB)[:, :, :1]
176
+ temp_vision_frame = numpy.concatenate((temp_vision_frame, numpy.zeros_like(temp_vision_frame), numpy.zeros_like(temp_vision_frame)), axis = -1)
177
+ temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_LAB2RGB)
178
+ temp_vision_frame = cv2.resize(temp_vision_frame, model_size)
179
+ temp_vision_frame = temp_vision_frame.transpose((2, 0, 1))
180
+ temp_vision_frame = numpy.expand_dims(temp_vision_frame, axis = 0).astype(numpy.float32)
181
+ return temp_vision_frame
182
+
183
+
184
+ def merge_color_frame(temp_vision_frame : VisionFrame, color_vision_frame : VisionFrame) -> VisionFrame:
185
+ model_type = get_options('model').get('type')
186
+ color_vision_frame = color_vision_frame.transpose(1, 2, 0)
187
+ color_vision_frame = cv2.resize(color_vision_frame, (temp_vision_frame.shape[1], temp_vision_frame.shape[0]))
188
+ if model_type == 'ddcolor':
189
+ temp_vision_frame = (temp_vision_frame / 255.0).astype(numpy.float32)
190
+ temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_BGR2LAB)[:, :, :1]
191
+ color_vision_frame = numpy.concatenate((temp_vision_frame, color_vision_frame), axis = -1)
192
+ color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_LAB2BGR)
193
+ color_vision_frame = (color_vision_frame * 255.0).round().astype(numpy.uint8)
194
+ if model_type == 'deoldify':
195
+ temp_blue_channel, _, _ = cv2.split(temp_vision_frame)
196
+ color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_BGR2RGB).astype(numpy.uint8)
197
+ color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_BGR2LAB)
198
+ _, color_green_channel, color_red_channel = cv2.split(color_vision_frame)
199
+ color_vision_frame = cv2.merge((temp_blue_channel, color_green_channel, color_red_channel))
200
+ color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_LAB2BGR)
201
+ return color_vision_frame
202
+
203
+
204
+ def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame:
205
+ frame_colorizer_blend = 1 - (frame_processors_globals.frame_colorizer_blend / 100)
206
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, frame_colorizer_blend, paste_vision_frame, 1 - frame_colorizer_blend, 0)
207
+ return temp_vision_frame
208
+
209
+
210
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
211
+ pass
212
+
213
+
214
+ def process_frame(inputs : FrameColorizerInputs) -> VisionFrame:
215
+ target_vision_frame = inputs.get('target_vision_frame')
216
+ return colorize_frame(target_vision_frame)
217
+
218
+
219
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
220
+ for queue_payload in process_manager.manage(queue_payloads):
221
+ target_vision_path = queue_payload['frame_path']
222
+ target_vision_frame = read_image(target_vision_path)
223
+ output_vision_frame = process_frame(
224
+ {
225
+ 'target_vision_frame': target_vision_frame
226
+ })
227
+ write_image(target_vision_path, output_vision_frame)
228
+ update_progress(1)
229
+
230
+
231
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
232
+ target_vision_frame = read_static_image(target_path)
233
+ output_vision_frame = process_frame(
234
+ {
235
+ 'target_vision_frame': target_vision_frame
236
+ })
237
+ write_image(output_path, output_vision_frame)
238
+
239
+
240
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
241
+ frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/frame_enhancer.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+
8
+ import facefusion.globals
9
+ import facefusion.processors.frame.core as frame_processors
10
+ from facefusion import config, process_manager, logger, wording
11
+ from facefusion.face_analyser import clear_face_analyser
12
+ from facefusion.content_analyser import clear_content_analyser
13
+ from facefusion.execution import apply_execution_provider_options
14
+ from facefusion.normalizer import normalize_output_path
15
+ from facefusion.thread_helper import thread_lock, conditional_thread_semaphore
16
+ from facefusion.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload
17
+ from facefusion.common_helper import create_metavar
18
+ from facefusion.filesystem import is_file, resolve_relative_path, is_image, is_video
19
+ from facefusion.download import conditional_download, is_download_done
20
+ from facefusion.vision import read_image, read_static_image, write_image, merge_tile_frames, create_tile_frames
21
+ from facefusion.processors.frame.typings import FrameEnhancerInputs
22
+ from facefusion.processors.frame import globals as frame_processors_globals
23
+ from facefusion.processors.frame import choices as frame_processors_choices
24
+
25
+ FRAME_PROCESSOR = None
26
+ NAME = __name__.upper()
27
+ MODELS : ModelSet =\
28
+ {
29
+ 'clear_reality_x4':
30
+ {
31
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/clear_reality_x4.onnx',
32
+ 'path': resolve_relative_path('../.assets/models/clear_reality_x4.onnx'),
33
+ 'size': (128, 8, 4),
34
+ 'scale': 4
35
+ },
36
+ 'lsdir_x4':
37
+ {
38
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/lsdir_x4.onnx',
39
+ 'path': resolve_relative_path('../.assets/models/lsdir_x4.onnx'),
40
+ 'size': (128, 8, 4),
41
+ 'scale': 4
42
+ },
43
+ 'nomos8k_sc_x4':
44
+ {
45
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/nomos8k_sc_x4.onnx',
46
+ 'path': resolve_relative_path('../.assets/models/nomos8k_sc_x4.onnx'),
47
+ 'size': (128, 8, 4),
48
+ 'scale': 4
49
+ },
50
+ 'real_esrgan_x2':
51
+ {
52
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x2.onnx',
53
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x2.onnx'),
54
+ 'size': (256, 16, 8),
55
+ 'scale': 2
56
+ },
57
+ 'real_esrgan_x2_fp16':
58
+ {
59
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x2_fp16.onnx',
60
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x2_fp16.onnx'),
61
+ 'size': (256, 16, 8),
62
+ 'scale': 2
63
+ },
64
+ 'real_esrgan_x4':
65
+ {
66
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4.onnx',
67
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x4.onnx'),
68
+ 'size': (256, 16, 8),
69
+ 'scale': 4
70
+ },
71
+ 'real_esrgan_x4_fp16':
72
+ {
73
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4_fp16.onnx',
74
+ 'path': resolve_relative_path('../.assets/models/real_esrgan_x4_fp16.onnx'),
75
+ 'size': (256, 16, 8),
76
+ 'scale': 4
77
+ },
78
+ 'real_hatgan_x4':
79
+ {
80
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_hatgan_x4.onnx',
81
+ 'path': resolve_relative_path('../.assets/models/real_hatgan_x4.onnx'),
82
+ 'size': (256, 16, 8),
83
+ 'scale': 4
84
+ },
85
+ 'span_kendata_x4':
86
+ {
87
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/span_kendata_x4.onnx',
88
+ 'path': resolve_relative_path('../.assets/models/span_kendata_x4.onnx'),
89
+ 'size': (128, 8, 4),
90
+ 'scale': 4
91
+ },
92
+ 'ultra_sharp_x4':
93
+ {
94
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ultra_sharp_x4.onnx',
95
+ 'path': resolve_relative_path('../.assets/models/ultra_sharp_x4.onnx'),
96
+ 'size': (128, 8, 4),
97
+ 'scale': 4
98
+ }
99
+ }
100
+ OPTIONS : Optional[OptionsWithModel] = None
101
+
102
+
103
+ def get_frame_processor() -> Any:
104
+ global FRAME_PROCESSOR
105
+
106
+ with thread_lock():
107
+ while process_manager.is_checking():
108
+ sleep(0.5)
109
+ if FRAME_PROCESSOR is None:
110
+ model_path = get_options('model').get('path')
111
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
112
+ return FRAME_PROCESSOR
113
+
114
+
115
+ def clear_frame_processor() -> None:
116
+ global FRAME_PROCESSOR
117
+
118
+ FRAME_PROCESSOR = None
119
+
120
+
121
+ def get_options(key : Literal['model']) -> Any:
122
+ global OPTIONS
123
+
124
+ if OPTIONS is None:
125
+ OPTIONS =\
126
+ {
127
+ 'model': MODELS[frame_processors_globals.frame_enhancer_model]
128
+ }
129
+ return OPTIONS.get(key)
130
+
131
+
132
+ def set_options(key : Literal['model'], value : Any) -> None:
133
+ global OPTIONS
134
+
135
+ OPTIONS[key] = value
136
+
137
+
138
+ def register_args(program : ArgumentParser) -> None:
139
+ program.add_argument('--frame-enhancer-model', help = wording.get('help.frame_enhancer_model'), default = config.get_str_value('frame_processors.frame_enhancer_model', 'span_kendata_x4'), choices = frame_processors_choices.frame_enhancer_models)
140
+ program.add_argument('--frame-enhancer-blend', help = wording.get('help.frame_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.frame_enhancer_blend', '80'), choices = frame_processors_choices.frame_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.frame_enhancer_blend_range))
141
+
142
+
143
+ def apply_args(program : ArgumentParser) -> None:
144
+ args = program.parse_args()
145
+ frame_processors_globals.frame_enhancer_model = args.frame_enhancer_model
146
+ frame_processors_globals.frame_enhancer_blend = args.frame_enhancer_blend
147
+
148
+
149
+ def pre_check() -> bool:
150
+ download_directory_path = resolve_relative_path('../.assets/models')
151
+ model_url = get_options('model').get('url')
152
+ model_path = get_options('model').get('path')
153
+
154
+ if not facefusion.globals.skip_download:
155
+ process_manager.check()
156
+ conditional_download(download_directory_path, [ model_url ])
157
+ process_manager.end()
158
+ return is_file(model_path)
159
+
160
+
161
+ def post_check() -> bool:
162
+ model_url = get_options('model').get('url')
163
+ model_path = get_options('model').get('path')
164
+
165
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
166
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
167
+ return False
168
+ if not is_file(model_path):
169
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
170
+ return False
171
+ return True
172
+
173
+
174
+ def pre_process(mode : ProcessMode) -> bool:
175
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
176
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
177
+ return False
178
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
179
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
180
+ return False
181
+ return True
182
+
183
+
184
+ def post_process() -> None:
185
+ read_static_image.cache_clear()
186
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
187
+ clear_frame_processor()
188
+ if facefusion.globals.video_memory_strategy == 'strict':
189
+ clear_face_analyser()
190
+ clear_content_analyser()
191
+
192
+
193
+ def enhance_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
194
+ frame_processor = get_frame_processor()
195
+ size = get_options('model').get('size')
196
+ scale = get_options('model').get('scale')
197
+ temp_height, temp_width = temp_vision_frame.shape[:2]
198
+ tile_vision_frames, pad_width, pad_height = create_tile_frames(temp_vision_frame, size)
199
+
200
+ for index, tile_vision_frame in enumerate(tile_vision_frames):
201
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
202
+ tile_vision_frame = frame_processor.run(None,
203
+ {
204
+ frame_processor.get_inputs()[0].name : prepare_tile_frame(tile_vision_frame)
205
+ })[0]
206
+ tile_vision_frames[index] = normalize_tile_frame(tile_vision_frame)
207
+ merge_vision_frame = merge_tile_frames(tile_vision_frames, temp_width * scale, temp_height * scale, pad_width * scale, pad_height * scale, (size[0] * scale, size[1] * scale, size[2] * scale))
208
+ temp_vision_frame = blend_frame(temp_vision_frame, merge_vision_frame)
209
+ return temp_vision_frame
210
+
211
+
212
+ def prepare_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame:
213
+ vision_tile_frame = numpy.expand_dims(vision_tile_frame[:, :, ::-1], axis = 0)
214
+ vision_tile_frame = vision_tile_frame.transpose(0, 3, 1, 2)
215
+ vision_tile_frame = vision_tile_frame.astype(numpy.float32) / 255
216
+ return vision_tile_frame
217
+
218
+
219
+ def normalize_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame:
220
+ vision_tile_frame = vision_tile_frame.transpose(0, 2, 3, 1).squeeze(0) * 255
221
+ vision_tile_frame = vision_tile_frame.clip(0, 255).astype(numpy.uint8)[:, :, ::-1]
222
+ return vision_tile_frame
223
+
224
+
225
+ def blend_frame(temp_vision_frame : VisionFrame, merge_vision_frame : VisionFrame) -> VisionFrame:
226
+ frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100)
227
+ temp_vision_frame = cv2.resize(temp_vision_frame, (merge_vision_frame.shape[1], merge_vision_frame.shape[0]))
228
+ temp_vision_frame = cv2.addWeighted(temp_vision_frame, frame_enhancer_blend, merge_vision_frame, 1 - frame_enhancer_blend, 0)
229
+ return temp_vision_frame
230
+
231
+
232
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
233
+ pass
234
+
235
+
236
+ def process_frame(inputs : FrameEnhancerInputs) -> VisionFrame:
237
+ target_vision_frame = inputs.get('target_vision_frame')
238
+ return enhance_frame(target_vision_frame)
239
+
240
+
241
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
242
+ for queue_payload in process_manager.manage(queue_payloads):
243
+ target_vision_path = queue_payload['frame_path']
244
+ target_vision_frame = read_image(target_vision_path)
245
+ output_vision_frame = process_frame(
246
+ {
247
+ 'target_vision_frame': target_vision_frame
248
+ })
249
+ write_image(target_vision_path, output_vision_frame)
250
+ update_progress(1)
251
+
252
+
253
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
254
+ target_vision_frame = read_static_image(target_path)
255
+ output_vision_frame = process_frame(
256
+ {
257
+ 'target_vision_frame': target_vision_frame
258
+ })
259
+ write_image(output_path, output_vision_frame)
260
+
261
+
262
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
263
+ frame_processors.multi_process_frames(None, temp_frame_paths, process_frames)
facefusion/processors/frame/modules/lip_syncer.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Literal, Optional
2
+ from argparse import ArgumentParser
3
+ from time import sleep
4
+ import cv2
5
+ import numpy
6
+ import onnxruntime
7
+
8
+ import facefusion.globals
9
+ import facefusion.processors.frame.core as frame_processors
10
+ from facefusion import config, process_manager, logger, wording
11
+ from facefusion.execution import apply_execution_provider_options
12
+ from facefusion.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser
13
+ from facefusion.face_masker import create_static_box_mask, create_occlusion_mask, create_mouth_mask, clear_face_occluder, clear_face_parser
14
+ from facefusion.face_helper import warp_face_by_face_landmark_5, warp_face_by_bounding_box, paste_back, create_bounding_box_from_face_landmark_68
15
+ from facefusion.face_store import get_reference_faces
16
+ from facefusion.content_analyser import clear_content_analyser
17
+ from facefusion.normalizer import normalize_output_path
18
+ from facefusion.thread_helper import thread_lock, conditional_thread_semaphore
19
+ from facefusion.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, AudioFrame, QueuePayload
20
+ from facefusion.filesystem import is_file, has_audio, resolve_relative_path
21
+ from facefusion.download import conditional_download, is_download_done
22
+ from facefusion.audio import read_static_voice, get_voice_frame, create_empty_audio_frame
23
+ from facefusion.filesystem import is_image, is_video, filter_audio_paths
24
+ from facefusion.common_helper import get_first
25
+ from facefusion.vision import read_image, read_static_image, write_image, restrict_video_fps
26
+ from facefusion.processors.frame.typings import LipSyncerInputs
27
+ from facefusion.voice_extractor import clear_voice_extractor
28
+ from facefusion.processors.frame import globals as frame_processors_globals
29
+ from facefusion.processors.frame import choices as frame_processors_choices
30
+
31
+ FRAME_PROCESSOR = None
32
+ NAME = __name__.upper()
33
+ MODELS : ModelSet =\
34
+ {
35
+ 'wav2lip_gan':
36
+ {
37
+ 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/wav2lip_gan.onnx',
38
+ 'path': resolve_relative_path('../.assets/models/wav2lip_gan.onnx')
39
+ }
40
+ }
41
+ OPTIONS : Optional[OptionsWithModel] = None
42
+
43
+
44
+ def get_frame_processor() -> Any:
45
+ global FRAME_PROCESSOR
46
+
47
+ with thread_lock():
48
+ while process_manager.is_checking():
49
+ sleep(0.5)
50
+ if FRAME_PROCESSOR is None:
51
+ model_path = get_options('model').get('path')
52
+ FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(facefusion.globals.execution_device_id, facefusion.globals.execution_providers))
53
+ return FRAME_PROCESSOR
54
+
55
+
56
+ def clear_frame_processor() -> None:
57
+ global FRAME_PROCESSOR
58
+
59
+ FRAME_PROCESSOR = None
60
+
61
+
62
+ def get_options(key : Literal['model']) -> Any:
63
+ global OPTIONS
64
+
65
+ if OPTIONS is None:
66
+ OPTIONS =\
67
+ {
68
+ 'model': MODELS[frame_processors_globals.lip_syncer_model]
69
+ }
70
+ return OPTIONS.get(key)
71
+
72
+
73
+ def set_options(key : Literal['model'], value : Any) -> None:
74
+ global OPTIONS
75
+
76
+ OPTIONS[key] = value
77
+
78
+
79
+ def register_args(program : ArgumentParser) -> None:
80
+ program.add_argument('--lip-syncer-model', help = wording.get('help.lip_syncer_model'), default = config.get_str_value('frame_processors.lip_syncer_model', 'wav2lip_gan'), choices = frame_processors_choices.lip_syncer_models)
81
+
82
+
83
+ def apply_args(program : ArgumentParser) -> None:
84
+ args = program.parse_args()
85
+ frame_processors_globals.lip_syncer_model = args.lip_syncer_model
86
+
87
+
88
+ def pre_check() -> bool:
89
+ download_directory_path = resolve_relative_path('../.assets/models')
90
+ model_url = get_options('model').get('url')
91
+ model_path = get_options('model').get('path')
92
+
93
+ if not facefusion.globals.skip_download:
94
+ process_manager.check()
95
+ conditional_download(download_directory_path, [ model_url ])
96
+ process_manager.end()
97
+ return is_file(model_path)
98
+
99
+
100
+ def post_check() -> bool:
101
+ model_url = get_options('model').get('url')
102
+ model_path = get_options('model').get('path')
103
+
104
+ if not facefusion.globals.skip_download and not is_download_done(model_url, model_path):
105
+ logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME)
106
+ return False
107
+ if not is_file(model_path):
108
+ logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME)
109
+ return False
110
+ return True
111
+
112
+
113
+ def pre_process(mode : ProcessMode) -> bool:
114
+ if not has_audio(facefusion.globals.source_paths):
115
+ logger.error(wording.get('select_audio_source') + wording.get('exclamation_mark'), NAME)
116
+ return False
117
+ if mode in [ 'output', 'preview' ] and not is_image(facefusion.globals.target_path) and not is_video(facefusion.globals.target_path):
118
+ logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME)
119
+ return False
120
+ if mode == 'output' and not normalize_output_path(facefusion.globals.target_path, facefusion.globals.output_path):
121
+ logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME)
122
+ return False
123
+ return True
124
+
125
+
126
+ def post_process() -> None:
127
+ read_static_image.cache_clear()
128
+ read_static_voice.cache_clear()
129
+ if facefusion.globals.video_memory_strategy == 'strict' or facefusion.globals.video_memory_strategy == 'moderate':
130
+ clear_frame_processor()
131
+ if facefusion.globals.video_memory_strategy == 'strict':
132
+ clear_face_analyser()
133
+ clear_content_analyser()
134
+ clear_face_occluder()
135
+ clear_face_parser()
136
+ clear_voice_extractor()
137
+
138
+
139
+ def sync_lip(target_face : Face, temp_audio_frame : AudioFrame, temp_vision_frame : VisionFrame) -> VisionFrame:
140
+ frame_processor = get_frame_processor()
141
+ crop_mask_list = []
142
+ temp_audio_frame = prepare_audio_frame(temp_audio_frame)
143
+ crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'ffhq_512', (512, 512))
144
+ face_landmark_68 = cv2.transform(target_face.landmarks.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2)
145
+ bounding_box = create_bounding_box_from_face_landmark_68(face_landmark_68)
146
+ bounding_box[1] -= numpy.abs(bounding_box[3] - bounding_box[1]) * 0.125
147
+ mouth_mask = create_mouth_mask(face_landmark_68)
148
+ crop_mask_list.append(mouth_mask)
149
+ box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], facefusion.globals.face_mask_blur, facefusion.globals.face_mask_padding)
150
+ crop_mask_list.append(box_mask)
151
+
152
+ if 'occlusion' in facefusion.globals.face_mask_types:
153
+ occlusion_mask = create_occlusion_mask(crop_vision_frame)
154
+ crop_mask_list.append(occlusion_mask)
155
+ close_vision_frame, close_matrix = warp_face_by_bounding_box(crop_vision_frame, bounding_box, (96, 96))
156
+ close_vision_frame = prepare_crop_frame(close_vision_frame)
157
+ with conditional_thread_semaphore(facefusion.globals.execution_providers):
158
+ close_vision_frame = frame_processor.run(None,
159
+ {
160
+ 'source': temp_audio_frame,
161
+ 'target': close_vision_frame
162
+ })[0]
163
+ crop_vision_frame = normalize_crop_frame(close_vision_frame)
164
+ crop_vision_frame = cv2.warpAffine(crop_vision_frame, cv2.invertAffineTransform(close_matrix), (512, 512), borderMode = cv2.BORDER_REPLICATE)
165
+ crop_mask = numpy.minimum.reduce(crop_mask_list)
166
+ paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
167
+ return paste_vision_frame
168
+
169
+
170
+ def prepare_audio_frame(temp_audio_frame : AudioFrame) -> AudioFrame:
171
+ temp_audio_frame = numpy.maximum(numpy.exp(-5 * numpy.log(10)), temp_audio_frame)
172
+ temp_audio_frame = numpy.log10(temp_audio_frame) * 1.6 + 3.2
173
+ temp_audio_frame = temp_audio_frame.clip(-4, 4).astype(numpy.float32)
174
+ temp_audio_frame = numpy.expand_dims(temp_audio_frame, axis = (0, 1))
175
+ return temp_audio_frame
176
+
177
+
178
+ def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
179
+ crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0)
180
+ prepare_vision_frame = crop_vision_frame.copy()
181
+ prepare_vision_frame[:, 48:] = 0
182
+ crop_vision_frame = numpy.concatenate((prepare_vision_frame, crop_vision_frame), axis = 3)
183
+ crop_vision_frame = crop_vision_frame.transpose(0, 3, 1, 2).astype('float32') / 255.0
184
+ return crop_vision_frame
185
+
186
+
187
+ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
188
+ crop_vision_frame = crop_vision_frame[0].transpose(1, 2, 0)
189
+ crop_vision_frame = crop_vision_frame.clip(0, 1) * 255
190
+ crop_vision_frame = crop_vision_frame.astype(numpy.uint8)
191
+ return crop_vision_frame
192
+
193
+
194
+ def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
195
+ pass
196
+
197
+
198
+ def process_frame(inputs : LipSyncerInputs) -> VisionFrame:
199
+ reference_faces = inputs.get('reference_faces')
200
+ source_audio_frame = inputs.get('source_audio_frame')
201
+ target_vision_frame = inputs.get('target_vision_frame')
202
+
203
+ if facefusion.globals.face_selector_mode == 'many':
204
+ many_faces = get_many_faces(target_vision_frame)
205
+ if many_faces:
206
+ for target_face in many_faces:
207
+ target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame)
208
+ if facefusion.globals.face_selector_mode == 'one':
209
+ target_face = get_one_face(target_vision_frame)
210
+ if target_face:
211
+ target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame)
212
+ if facefusion.globals.face_selector_mode == 'reference':
213
+ similar_faces = find_similar_faces(reference_faces, target_vision_frame, facefusion.globals.reference_face_distance)
214
+ if similar_faces:
215
+ for similar_face in similar_faces:
216
+ target_vision_frame = sync_lip(similar_face, source_audio_frame, target_vision_frame)
217
+ return target_vision_frame
218
+
219
+
220
+ def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None:
221
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
222
+ source_audio_path = get_first(filter_audio_paths(source_paths))
223
+ temp_video_fps = restrict_video_fps(facefusion.globals.target_path, facefusion.globals.output_video_fps)
224
+
225
+ for queue_payload in process_manager.manage(queue_payloads):
226
+ frame_number = queue_payload['frame_number']
227
+ target_vision_path = queue_payload['frame_path']
228
+ source_audio_frame = get_voice_frame(source_audio_path, temp_video_fps, frame_number)
229
+ if not numpy.any(source_audio_frame):
230
+ source_audio_frame = create_empty_audio_frame()
231
+ target_vision_frame = read_image(target_vision_path)
232
+ output_vision_frame = process_frame(
233
+ {
234
+ 'reference_faces': reference_faces,
235
+ 'source_audio_frame': source_audio_frame,
236
+ 'target_vision_frame': target_vision_frame
237
+ })
238
+ write_image(target_vision_path, output_vision_frame)
239
+ update_progress(1)
240
+
241
+
242
+ def process_image(source_paths : List[str], target_path : str, output_path : str) -> None:
243
+ reference_faces = get_reference_faces() if 'reference' in facefusion.globals.face_selector_mode else None
244
+ source_audio_frame = create_empty_audio_frame()
245
+ target_vision_frame = read_static_image(target_path)
246
+ output_vision_frame = process_frame(
247
+ {
248
+ 'reference_faces': reference_faces,
249
+ 'source_audio_frame': source_audio_frame,
250
+ 'target_vision_frame': target_vision_frame
251
+ })
252
+ write_image(output_path, output_vision_frame)
253
+
254
+
255
+ def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None:
256
+ source_audio_paths = filter_audio_paths(facefusion.globals.source_paths)
257
+ temp_video_fps = restrict_video_fps(facefusion.globals.target_path, facefusion.globals.output_video_fps)
258
+ for source_audio_path in source_audio_paths:
259
+ read_static_voice(source_audio_path, temp_video_fps)
260
+ frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames)
facefusion/processors/frame/typings.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal, TypedDict
2
+
3
+ from facefusion.typing import Face, FaceSet, AudioFrame, VisionFrame
4
+
5
+ FaceDebuggerItem = Literal['bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-landmark-68/5', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender']
6
+ FaceEnhancerModel = Literal['codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'gpen_bfr_1024', 'gpen_bfr_2048', 'restoreformer_plus_plus']
7
+ FaceSwapperModel = Literal['blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256']
8
+ FrameColorizerModel = Literal['ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable']
9
+ FrameEnhancerModel = Literal['clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_hatgan_x4', 'span_kendata_x4', 'ultra_sharp_x4']
10
+ LipSyncerModel = Literal['wav2lip_gan']
11
+
12
+ FaceDebuggerInputs = TypedDict('FaceDebuggerInputs',
13
+ {
14
+ 'reference_faces' : FaceSet,
15
+ 'target_vision_frame' : VisionFrame
16
+ })
17
+ FaceEnhancerInputs = TypedDict('FaceEnhancerInputs',
18
+ {
19
+ 'reference_faces' : FaceSet,
20
+ 'target_vision_frame' : VisionFrame
21
+ })
22
+ FaceSwapperInputs = TypedDict('FaceSwapperInputs',
23
+ {
24
+ 'reference_faces' : FaceSet,
25
+ 'source_face' : Face,
26
+ 'target_vision_frame' : VisionFrame
27
+ })
28
+ FrameColorizerInputs = TypedDict('FrameColorizerInputs',
29
+ {
30
+ 'target_vision_frame' : VisionFrame
31
+ })
32
+ FrameEnhancerInputs = TypedDict('FrameEnhancerInputs',
33
+ {
34
+ 'target_vision_frame' : VisionFrame
35
+ })
36
+ LipSyncerInputs = TypedDict('LipSyncerInputs',
37
+ {
38
+ 'reference_faces' : FaceSet,
39
+ 'source_audio_frame' : AudioFrame,
40
+ 'target_vision_frame' : VisionFrame
41
+ })
facefusion/statistics.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+ import numpy
3
+
4
+ import facefusion.globals
5
+ from facefusion.face_store import FACE_STORE
6
+ from facefusion.typing import FaceSet
7
+ from facefusion import logger
8
+
9
+
10
+ def create_statistics(static_faces : FaceSet) -> Dict[str, Any]:
11
+ face_detector_score_list = []
12
+ face_landmarker_score_list = []
13
+ statistics =\
14
+ {
15
+ 'min_face_detector_score': 0,
16
+ 'min_face_landmarker_score': 0,
17
+ 'max_face_detector_score': 0,
18
+ 'max_face_landmarker_score': 0,
19
+ 'average_face_detector_score': 0,
20
+ 'average_face_landmarker_score': 0,
21
+ 'total_face_landmark_5_fallbacks': 0,
22
+ 'total_frames_with_faces': 0,
23
+ 'total_faces': 0
24
+ }
25
+
26
+ for faces in static_faces.values():
27
+ statistics['total_frames_with_faces'] = statistics.get('total_frames_with_faces') + 1
28
+ for face in faces:
29
+ statistics['total_faces'] = statistics.get('total_faces') + 1
30
+ face_detector_score_list.append(face.scores.get('detector'))
31
+ face_landmarker_score_list.append(face.scores.get('landmarker'))
32
+ if numpy.array_equal(face.landmarks.get('5'), face.landmarks.get('5/68')):
33
+ statistics['total_face_landmark_5_fallbacks'] = statistics.get('total_face_landmark_5_fallbacks') + 1
34
+
35
+ if face_detector_score_list:
36
+ statistics['min_face_detector_score'] = round(min(face_detector_score_list), 2)
37
+ statistics['max_face_detector_score'] = round(max(face_detector_score_list), 2)
38
+ statistics['average_face_detector_score'] = round(numpy.mean(face_detector_score_list), 2)
39
+ if face_landmarker_score_list:
40
+ statistics['min_face_landmarker_score'] = round(min(face_landmarker_score_list), 2)
41
+ statistics['max_face_landmarker_score'] = round(max(face_landmarker_score_list), 2)
42
+ statistics['average_face_landmarker_score'] = round(numpy.mean(face_landmarker_score_list), 2)
43
+ return statistics
44
+
45
+
46
+ def conditional_log_statistics() -> None:
47
+ if facefusion.globals.log_level == 'debug':
48
+ statistics = create_statistics(FACE_STORE.get('static_faces'))
49
+
50
+ for name, value in statistics.items():
51
+ logger.debug(str(name) + ': ' + str(value), __name__.upper())
facefusion/thread_helper.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Union, ContextManager
2
+ import threading
3
+ from contextlib import nullcontext
4
+
5
+ THREAD_LOCK : threading.Lock = threading.Lock()
6
+ THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore()
7
+ NULL_CONTEXT : ContextManager[None] = nullcontext()
8
+
9
+
10
+ def thread_lock() -> threading.Lock:
11
+ return THREAD_LOCK
12
+
13
+
14
+ def thread_semaphore() -> threading.Semaphore:
15
+ return THREAD_SEMAPHORE
16
+
17
+
18
+ def conditional_thread_semaphore(execution_providers : List[str]) -> Union[threading.Semaphore, ContextManager[None]]:
19
+ if 'DmlExecutionProvider' in execution_providers:
20
+ return THREAD_SEMAPHORE
21
+ return NULL_CONTEXT
facefusion/typing.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Literal, Callable, List, Tuple, Dict, TypedDict
2
+ from collections import namedtuple
3
+ import numpy
4
+
5
+ BoundingBox = numpy.ndarray[Any, Any]
6
+ FaceLandmark5 = numpy.ndarray[Any, Any]
7
+ FaceLandmark68 = numpy.ndarray[Any, Any]
8
+ FaceLandmarkSet = TypedDict('FaceLandmarkSet',
9
+ {
10
+ '5' : FaceLandmark5, #type:ignore[valid-type]
11
+ '5/68' : FaceLandmark5, #type:ignore[valid-type]
12
+ '68' : FaceLandmark68, #type:ignore[valid-type]
13
+ '68/5' : FaceLandmark68 #type:ignore[valid-type]
14
+ })
15
+ Score = float
16
+ FaceScoreSet = TypedDict('FaceScoreSet',
17
+ {
18
+ 'detector' : Score,
19
+ 'landmarker' : Score
20
+ })
21
+ Embedding = numpy.ndarray[Any, Any]
22
+ Face = namedtuple('Face',
23
+ [
24
+ 'bounding_box',
25
+ 'landmarks',
26
+ 'scores',
27
+ 'embedding',
28
+ 'normed_embedding',
29
+ 'gender',
30
+ 'age'
31
+ ])
32
+ FaceSet = Dict[str, List[Face]]
33
+ FaceStore = TypedDict('FaceStore',
34
+ {
35
+ 'static_faces' : FaceSet,
36
+ 'reference_faces': FaceSet
37
+ })
38
+
39
+ VisionFrame = numpy.ndarray[Any, Any]
40
+ Mask = numpy.ndarray[Any, Any]
41
+ Matrix = numpy.ndarray[Any, Any]
42
+ Translation = numpy.ndarray[Any, Any]
43
+
44
+ AudioBuffer = bytes
45
+ Audio = numpy.ndarray[Any, Any]
46
+ AudioChunk = numpy.ndarray[Any, Any]
47
+ AudioFrame = numpy.ndarray[Any, Any]
48
+ Spectrogram = numpy.ndarray[Any, Any]
49
+ MelFilterBank = numpy.ndarray[Any, Any]
50
+
51
+ Fps = float
52
+ Padding = Tuple[int, int, int, int]
53
+ Resolution = Tuple[int, int]
54
+
55
+ ProcessState = Literal['checking', 'processing', 'stopping', 'pending']
56
+ QueuePayload = TypedDict('QueuePayload',
57
+ {
58
+ 'frame_number' : int,
59
+ 'frame_path' : str
60
+ })
61
+ UpdateProgress = Callable[[int], None]
62
+ ProcessFrames = Callable[[List[str], List[QueuePayload], UpdateProgress], None]
63
+
64
+ WarpTemplate = Literal['arcface_112_v1', 'arcface_112_v2', 'arcface_128_v2', 'ffhq_512']
65
+ WarpTemplateSet = Dict[WarpTemplate, numpy.ndarray[Any, Any]]
66
+ ProcessMode = Literal['output', 'preview', 'stream']
67
+
68
+ LogLevel = Literal['error', 'warn', 'info', 'debug']
69
+ VideoMemoryStrategy = Literal['strict', 'moderate', 'tolerant']
70
+ FaceSelectorMode = Literal['many', 'one', 'reference']
71
+ FaceAnalyserOrder = Literal['left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best']
72
+ FaceAnalyserAge = Literal['child', 'teen', 'adult', 'senior']
73
+ FaceAnalyserGender = Literal['female', 'male']
74
+ FaceDetectorModel = Literal['many', 'retinaface', 'scrfd', 'yoloface', 'yunet']
75
+ FaceDetectorTweak = Literal['low-luminance', 'high-luminance']
76
+ FaceRecognizerModel = Literal['arcface_blendswap', 'arcface_inswapper', 'arcface_simswap', 'arcface_uniface']
77
+ FaceMaskType = Literal['box', 'occlusion', 'region']
78
+ FaceMaskRegion = Literal['skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip']
79
+ TempFrameFormat = Literal['jpg', 'png', 'bmp']
80
+ OutputVideoEncoder = Literal['libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf']
81
+ OutputVideoPreset = Literal['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow']
82
+
83
+ ModelValue = Dict[str, Any]
84
+ ModelSet = Dict[str, ModelValue]
85
+ OptionsWithModel = TypedDict('OptionsWithModel',
86
+ {
87
+ 'model' : ModelValue
88
+ })
89
+
90
+ ValueAndUnit = TypedDict('ValueAndUnit',
91
+ {
92
+ 'value' : str,
93
+ 'unit' : str
94
+ })
95
+ ExecutionDeviceFramework = TypedDict('ExecutionDeviceFramework',
96
+ {
97
+ 'name' : str,
98
+ 'version' : str
99
+ })
100
+ ExecutionDeviceProduct = TypedDict('ExecutionDeviceProduct',
101
+ {
102
+ 'vendor' : str,
103
+ 'name' : str
104
+ })
105
+ ExecutionDeviceVideoMemory = TypedDict('ExecutionDeviceVideoMemory',
106
+ {
107
+ 'total' : ValueAndUnit,
108
+ 'free' : ValueAndUnit
109
+ })
110
+ ExecutionDeviceUtilization = TypedDict('ExecutionDeviceUtilization',
111
+ {
112
+ 'gpu' : ValueAndUnit,
113
+ 'memory' : ValueAndUnit
114
+ })
115
+ ExecutionDevice = TypedDict('ExecutionDevice',
116
+ {
117
+ 'driver_version' : str,
118
+ 'framework' : ExecutionDeviceFramework,
119
+ 'product' : ExecutionDeviceProduct,
120
+ 'video_memory' : ExecutionDeviceVideoMemory,
121
+ 'utilization' : ExecutionDeviceUtilization
122
+ })
facefusion/uis/__init__.py ADDED
File without changes
facefusion/uis/assets/fixes.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ :root:root:root button:not([class])
2
+ {
3
+ border-radius: 0.375rem;
4
+ float: left;
5
+ overflow: hidden;
6
+ width: 100%;
7
+ }
facefusion/uis/assets/overrides.css ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root:root:root input[type="number"]
2
+ {
3
+ max-width: 6rem;
4
+ }
5
+
6
+ :root:root:root [type="checkbox"],
7
+ :root:root:root [type="radio"]
8
+ {
9
+ border-radius: 50%;
10
+ height: 1.125rem;
11
+ width: 1.125rem;
12
+ }
13
+
14
+ :root:root:root input[type="range"]
15
+ {
16
+ height: 0.5rem;
17
+ }
18
+
19
+ :root:root:root input[type="range"]::-moz-range-thumb,
20
+ :root:root:root input[type="range"]::-webkit-slider-thumb
21
+ {
22
+ background: var(--neutral-300);
23
+ border: unset;
24
+ border-radius: 50%;
25
+ height: 1.125rem;
26
+ width: 1.125rem;
27
+ }
28
+
29
+ :root:root:root input[type="range"]::-webkit-slider-thumb
30
+ {
31
+ margin-top: 0.375rem;
32
+ }
33
+
34
+ :root:root:root .grid-wrap.fixed-height
35
+ {
36
+ min-height: unset;
37
+ }
38
+
39
+ :root:root:root .grid-container
40
+ {
41
+ grid-auto-rows: minmax(5em, 1fr);
42
+ grid-template-columns: repeat(var(--grid-cols), minmax(5em, 1fr));
43
+ grid-template-rows: repeat(var(--grid-rows), minmax(5em, 1fr));
44
+ }
45
+
46
+ :root:root:root .tab-nav > button
47
+ {
48
+ border: unset;
49
+ border-bottom: 0.125rem solid transparent;
50
+ font-size: 1.125em;
51
+ margin: 0.5rem 1rem;
52
+ padding: 0;
53
+ }
54
+
55
+ :root:root:root .tab-nav > button.selected
56
+ {
57
+ border-bottom: 0.125rem solid;
58
+ }
facefusion/uis/choices.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.uis.typing import WebcamMode
4
+
5
+ common_options : List[str] = [ 'keep-temp', 'skip-audio', 'skip-download' ]
6
+ webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ]
7
+ webcam_resolutions : List[str] = [ '320x240', '640x480', '800x600', '1024x768', '1280x720', '1280x960', '1920x1080', '2560x1440', '3840x2160' ]
facefusion/uis/components/__init__.py ADDED
File without changes
facefusion/uis/components/about.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import gradio
3
+
4
+ from facefusion import metadata, wording
5
+
6
+ ABOUT_BUTTON : Optional[gradio.HTML] = None
7
+ DONATE_BUTTON : Optional[gradio.HTML] = None
8
+
9
+
10
+ def render() -> None:
11
+ global ABOUT_BUTTON
12
+ global DONATE_BUTTON
13
+
14
+ ABOUT_BUTTON = gradio.Button(
15
+ value = metadata.get('name') + ' ' + metadata.get('version'),
16
+ variant = 'primary',
17
+ link = metadata.get('url')
18
+ )
19
+ DONATE_BUTTON = gradio.Button(
20
+ value = wording.get('uis.donate_button'),
21
+ link = 'https://donate.facefusion.io',
22
+ size = 'sm'
23
+ )
facefusion/uis/components/benchmark.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Optional, List, Dict, Generator
2
+ from time import sleep, perf_counter
3
+ import tempfile
4
+ import statistics
5
+ import gradio
6
+
7
+ import facefusion.globals
8
+ from facefusion import process_manager, wording
9
+ from facefusion.face_store import clear_static_faces
10
+ from facefusion.processors.frame.core import get_frame_processors_modules
11
+ from facefusion.vision import count_video_frame_total, detect_video_resolution, detect_video_fps, pack_resolution
12
+ from facefusion.core import conditional_process
13
+ from facefusion.memory import limit_system_memory
14
+ from facefusion.filesystem import clear_temp
15
+ from facefusion.uis.core import get_ui_component
16
+
17
+ BENCHMARK_RESULTS_DATAFRAME : Optional[gradio.Dataframe] = None
18
+ BENCHMARK_START_BUTTON : Optional[gradio.Button] = None
19
+ BENCHMARK_CLEAR_BUTTON : Optional[gradio.Button] = None
20
+ BENCHMARKS : Dict[str, str] =\
21
+ {
22
+ '240p': '.assets/examples/target-240p.mp4',
23
+ '360p': '.assets/examples/target-360p.mp4',
24
+ '540p': '.assets/examples/target-540p.mp4',
25
+ '720p': '.assets/examples/target-720p.mp4',
26
+ '1080p': '.assets/examples/target-1080p.mp4',
27
+ '1440p': '.assets/examples/target-1440p.mp4',
28
+ '2160p': '.assets/examples/target-2160p.mp4'
29
+ }
30
+
31
+
32
+ def render() -> None:
33
+ global BENCHMARK_RESULTS_DATAFRAME
34
+ global BENCHMARK_START_BUTTON
35
+ global BENCHMARK_CLEAR_BUTTON
36
+
37
+ BENCHMARK_RESULTS_DATAFRAME = gradio.Dataframe(
38
+ label = wording.get('uis.benchmark_results_dataframe'),
39
+ headers =
40
+ [
41
+ 'target_path',
42
+ 'benchmark_cycles',
43
+ 'average_run',
44
+ 'fastest_run',
45
+ 'slowest_run',
46
+ 'relative_fps'
47
+ ],
48
+ datatype =
49
+ [
50
+ 'str',
51
+ 'number',
52
+ 'number',
53
+ 'number',
54
+ 'number',
55
+ 'number'
56
+ ]
57
+ )
58
+ BENCHMARK_START_BUTTON = gradio.Button(
59
+ value = wording.get('uis.start_button'),
60
+ variant = 'primary',
61
+ size = 'sm'
62
+ )
63
+ BENCHMARK_CLEAR_BUTTON = gradio.Button(
64
+ value = wording.get('uis.clear_button'),
65
+ size = 'sm'
66
+ )
67
+
68
+
69
+ def listen() -> None:
70
+ benchmark_runs_checkbox_group = get_ui_component('benchmark_runs_checkbox_group')
71
+ benchmark_cycles_slider = get_ui_component('benchmark_cycles_slider')
72
+
73
+ if benchmark_runs_checkbox_group and benchmark_cycles_slider:
74
+ BENCHMARK_START_BUTTON.click(start, inputs = [ benchmark_runs_checkbox_group, benchmark_cycles_slider ], outputs = BENCHMARK_RESULTS_DATAFRAME)
75
+ BENCHMARK_CLEAR_BUTTON.click(clear, outputs = BENCHMARK_RESULTS_DATAFRAME)
76
+
77
+
78
+ def start(benchmark_runs : List[str], benchmark_cycles : int) -> Generator[List[Any], None, None]:
79
+ facefusion.globals.source_paths = [ '.assets/examples/source.jpg', '.assets/examples/source.mp3' ]
80
+ facefusion.globals.output_path = tempfile.gettempdir()
81
+ facefusion.globals.face_landmarker_score = 0
82
+ facefusion.globals.temp_frame_format = 'bmp'
83
+ facefusion.globals.output_video_preset = 'ultrafast'
84
+ benchmark_results = []
85
+ target_paths = [ BENCHMARKS[benchmark_run] for benchmark_run in benchmark_runs if benchmark_run in BENCHMARKS ]
86
+
87
+ if target_paths:
88
+ pre_process()
89
+ for target_path in target_paths:
90
+ facefusion.globals.target_path = target_path
91
+ benchmark_results.append(benchmark(benchmark_cycles))
92
+ yield benchmark_results
93
+ post_process()
94
+
95
+
96
+ def pre_process() -> None:
97
+ if facefusion.globals.system_memory_limit > 0:
98
+ limit_system_memory(facefusion.globals.system_memory_limit)
99
+ for frame_processor_module in get_frame_processors_modules(facefusion.globals.frame_processors):
100
+ frame_processor_module.get_frame_processor()
101
+
102
+
103
+ def post_process() -> None:
104
+ clear_static_faces()
105
+
106
+
107
+ def benchmark(benchmark_cycles : int) -> List[Any]:
108
+ process_times = []
109
+ video_frame_total = count_video_frame_total(facefusion.globals.target_path)
110
+ output_video_resolution = detect_video_resolution(facefusion.globals.target_path)
111
+ facefusion.globals.output_video_resolution = pack_resolution(output_video_resolution)
112
+ facefusion.globals.output_video_fps = detect_video_fps(facefusion.globals.target_path)
113
+
114
+ for index in range(benchmark_cycles):
115
+ start_time = perf_counter()
116
+ conditional_process()
117
+ end_time = perf_counter()
118
+ process_times.append(end_time - start_time)
119
+ average_run = round(statistics.mean(process_times), 2)
120
+ fastest_run = round(min(process_times), 2)
121
+ slowest_run = round(max(process_times), 2)
122
+ relative_fps = round(video_frame_total * benchmark_cycles / sum(process_times), 2)
123
+
124
+ return\
125
+ [
126
+ facefusion.globals.target_path,
127
+ benchmark_cycles,
128
+ average_run,
129
+ fastest_run,
130
+ slowest_run,
131
+ relative_fps
132
+ ]
133
+
134
+
135
+ def clear() -> gradio.Dataframe:
136
+ while process_manager.is_processing():
137
+ sleep(0.5)
138
+ if facefusion.globals.target_path:
139
+ clear_temp(facefusion.globals.target_path)
140
+ return gradio.Dataframe(value = None)
facefusion/uis/components/benchmark_options.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import gradio
3
+
4
+ from facefusion import wording
5
+ from facefusion.uis.core import register_ui_component
6
+ from facefusion.uis.components.benchmark import BENCHMARKS
7
+
8
+ BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
9
+ BENCHMARK_CYCLES_SLIDER : Optional[gradio.Button] = None
10
+
11
+
12
+ def render() -> None:
13
+ global BENCHMARK_RUNS_CHECKBOX_GROUP
14
+ global BENCHMARK_CYCLES_SLIDER
15
+
16
+ BENCHMARK_RUNS_CHECKBOX_GROUP = gradio.CheckboxGroup(
17
+ label = wording.get('uis.benchmark_runs_checkbox_group'),
18
+ value = list(BENCHMARKS.keys()),
19
+ choices = list(BENCHMARKS.keys())
20
+ )
21
+ BENCHMARK_CYCLES_SLIDER = gradio.Slider(
22
+ label = wording.get('uis.benchmark_cycles_slider'),
23
+ value = 5,
24
+ step = 1,
25
+ minimum = 1,
26
+ maximum = 10
27
+ )
28
+ register_ui_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP)
29
+ register_ui_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER)
facefusion/uis/components/common_options.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, List
2
+ import gradio
3
+
4
+ import facefusion.globals
5
+ from facefusion import wording
6
+ from facefusion.uis import choices as uis_choices
7
+
8
+ COMMON_OPTIONS_CHECKBOX_GROUP : Optional[gradio.Checkboxgroup] = None
9
+
10
+
11
+ def render() -> None:
12
+ global COMMON_OPTIONS_CHECKBOX_GROUP
13
+
14
+ value = []
15
+ if facefusion.globals.keep_temp:
16
+ value.append('keep-temp')
17
+ if facefusion.globals.skip_audio:
18
+ value.append('skip-audio')
19
+ if facefusion.globals.skip_download:
20
+ value.append('skip-download')
21
+ COMMON_OPTIONS_CHECKBOX_GROUP = gradio.Checkboxgroup(
22
+ label = wording.get('uis.common_options_checkbox_group'),
23
+ choices = uis_choices.common_options,
24
+ value = value
25
+ )
26
+
27
+
28
+ def listen() -> None:
29
+ COMMON_OPTIONS_CHECKBOX_GROUP.change(update, inputs = COMMON_OPTIONS_CHECKBOX_GROUP)
30
+
31
+
32
+ def update(common_options : List[str]) -> None:
33
+ facefusion.globals.keep_temp = 'keep-temp' in common_options
34
+ facefusion.globals.skip_audio = 'skip-audio' in common_options
35
+ facefusion.globals.skip_download = 'skip-download' in common_options
facefusion/uis/components/execution.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import gradio
3
+ import onnxruntime
4
+
5
+ import facefusion.globals
6
+ from facefusion import wording
7
+ from facefusion.face_analyser import clear_face_analyser
8
+ from facefusion.processors.frame.core import clear_frame_processors_modules
9
+ from facefusion.execution import encode_execution_providers, decode_execution_providers
10
+
11
+ EXECUTION_PROVIDERS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None
12
+
13
+
14
+ def render() -> None:
15
+ global EXECUTION_PROVIDERS_CHECKBOX_GROUP
16
+
17
+ EXECUTION_PROVIDERS_CHECKBOX_GROUP = gradio.CheckboxGroup(
18
+ label = wording.get('uis.execution_providers_checkbox_group'),
19
+ choices = encode_execution_providers(onnxruntime.get_available_providers()),
20
+ value = encode_execution_providers(facefusion.globals.execution_providers)
21
+ )
22
+
23
+
24
+ def listen() -> None:
25
+ EXECUTION_PROVIDERS_CHECKBOX_GROUP.change(update_execution_providers, inputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP, outputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP)
26
+
27
+
28
+ def update_execution_providers(execution_providers : List[str]) -> gradio.CheckboxGroup:
29
+ clear_face_analyser()
30
+ clear_frame_processors_modules()
31
+ execution_providers = execution_providers or encode_execution_providers(onnxruntime.get_available_providers())
32
+ facefusion.globals.execution_providers = decode_execution_providers(execution_providers)
33
+ return gradio.CheckboxGroup(value = execution_providers)
facefusion/uis/components/execution_queue_count.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import gradio
3
+
4
+ import facefusion.globals
5
+ import facefusion.choices
6
+ from facefusion import wording
7
+
8
+ EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None
9
+
10
+
11
+ def render() -> None:
12
+ global EXECUTION_QUEUE_COUNT_SLIDER
13
+
14
+ EXECUTION_QUEUE_COUNT_SLIDER = gradio.Slider(
15
+ label = wording.get('uis.execution_queue_count_slider'),
16
+ value = facefusion.globals.execution_queue_count,
17
+ step = facefusion.choices.execution_queue_count_range[1] - facefusion.choices.execution_queue_count_range[0],
18
+ minimum = facefusion.choices.execution_queue_count_range[0],
19
+ maximum = facefusion.choices.execution_queue_count_range[-1]
20
+ )
21
+
22
+
23
+ def listen() -> None:
24
+ EXECUTION_QUEUE_COUNT_SLIDER.release(update_execution_queue_count, inputs = EXECUTION_QUEUE_COUNT_SLIDER)
25
+
26
+
27
+ def update_execution_queue_count(execution_queue_count : int = 1) -> None:
28
+ facefusion.globals.execution_queue_count = execution_queue_count
facefusion/uis/components/execution_thread_count.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ import gradio
3
+
4
+ import facefusion.globals
5
+ import facefusion.choices
6
+ from facefusion import wording
7
+
8
+ EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None
9
+
10
+
11
+ def render() -> None:
12
+ global EXECUTION_THREAD_COUNT_SLIDER
13
+
14
+ EXECUTION_THREAD_COUNT_SLIDER = gradio.Slider(
15
+ label = wording.get('uis.execution_thread_count_slider'),
16
+ value = facefusion.globals.execution_thread_count,
17
+ step = facefusion.choices.execution_thread_count_range[1] - facefusion.choices.execution_thread_count_range[0],
18
+ minimum = facefusion.choices.execution_thread_count_range[0],
19
+ maximum = facefusion.choices.execution_thread_count_range[-1]
20
+ )
21
+
22
+
23
+ def listen() -> None:
24
+ EXECUTION_THREAD_COUNT_SLIDER.release(update_execution_thread_count, inputs = EXECUTION_THREAD_COUNT_SLIDER)
25
+
26
+
27
+ def update_execution_thread_count(execution_thread_count : int = 1) -> None:
28
+ facefusion.globals.execution_thread_count = execution_thread_count
29
+