
* Replace audio whenever set via source * add H264_qsv&HEVC_qsv (#768) * Update ffmpeg.py * Update choices.py * Update typing.py * Fix spaces and newlines * Fix return type * Introduce hififace swapper * Disable stream for expression restorer * Webcam polishing part1 (#796) * Cosmetics on ignore comments * Testing for replace audio * Testing for restore audio * Testing for restore audio * Fix replace_audio() * Remove shortest and use fixed video duration * Remove shortest and use fixed video duration * Prevent duplicate entries to local PATH * Do hard exit on invalid args * Need for Python 3.10 * Fix state of face selector * Fix OpenVINO by aliasing GPU.0 to GPU * Fix OpenVINO by aliasing GPU.0 to GPU * Fix/age modifier styleganex 512 (#798) * fix * styleganex template * changes * changes * fix occlusion mask * add age modifier scale * change * change * hardcode * Cleanup * Use model_sizes and model_templates variables * No need for prepare when just 2 lines of code * Someone used spaces over tabs * Revert back [0][0] --------- Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> * Feat/update gradio5 (#799) * Update to Gradio 5 * Remove overrides for Gradio * Fix dark mode for Gradio * Polish errors * More styles for tabs and co * Make slider inputs and reset like a unit * Make slider inputs and reset like a unit * Adjust naming * Improved color matching (#800) * aura fix * fix import * move to vision.py * changes * changes * changes * changes * further reduction * add test * better test * change name * Minor cleanup * Minor cleanup * Minor cleanup * changes (#801) * Switch to official assets repo * Add __pycache__ to gitignore * Gradio pinned python-multipart to 0.0.12 * Update dependencies * Feat/temp path second try (#802) * Terminate base directory from temp helper * Partial adjust program codebase * Move arguments around * Make `-j` absolete * Resolve args * Fix job register keys * Adjust date test * Finalize temp path * Update onnxruntime * Update dependencies * Adjust color for checkboxes * Revert due terrible performance * Fix/enforce vp9 for webm (#805) * Simple fix to enforce vp9 for webm * Remove suggest methods from program helper * Cleanup ffmpeg.py a bit * Update onnxruntime (second try) * Update onnxruntime (second try) * Remove cudnn_conv_algo_search tweaks * Remove cudnn_conv_algo_search tweaks * changes * add both mask instead of multiply * adaptive color correction * changes * remove model size requirement * changes * add to facefusion.ini * changes * changes * changes * Add namespace for dfm creators * Release five frame enhancer models * Remove vendor from model name * Remove vendor from model name * changes * changes * changes * changes * Feat/download providers (#809) * Introduce download providers * update processors download method * add ui * Fix CI * Adjust UI component order, Use download resolver for benchmark * Remove is_download_done() * Introduce download provider set, Remove choices method from execution, cast all dict keys() via list() * Fix spacing --------- Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> * Fix model paths for 3.1.0 * Introduce bulk-run (#810) * Introduce bulk-run * Make bulk run bullet proof * Integration test for bulk-run * new alignment * Add safer global named resolve_file_pattern() (#811) * Allow bulk runner with target pattern only * changes * changes * Update Python to 3.12 for CI (#813) * changes * Improve NVIDIA device lookups * Rename template key to deepfacelive * Fix name * Improve resolve download * Rename bulk-run to batch-run * Make deep swapper inputs universal * Add more deepfacelive models * Use different morph value * Feat/simplify hashes sources download (#814) * Extract download directory path from assets path * Fix lint * Fix force-download command, Fix urls in frame enhancer * changes * fix warp_face_by_bounding_box dtype error * DFM Morph (#816) * changes * Improve wording, Replace [None], SideQuest: clean forward() of age modifier * SideQuest: clean forward() of face enhancer --------- Co-authored-by: henryruhs <info@henryruhs.com> * Fix preview refresh after slide * Add more deepfacelive models (#817) * Add more deepfacelive models * Add more deepfacelive models * Fix deep swapper sizes * Kill accent colors, Number input styles for Chrome * Simplify thumbnail-item looks * Fix first black screen * Introduce model helper * ci.yml: Add macOS on ARM64 to the testing (#818) * ci.yml: Add macOS on ARM64 to the testing * ci.yml: uses: AnimMouse/setup-ffmpeg@v1 * ci.yml: strategy: matrix: os: macos-latest, * - name: Set up FFmpeg * Update .github/workflows/ci.yml * Update ci.yml --------- Co-authored-by: Henry Ruhs <info@henryruhs.com> * Show/hide morph slider for deep swapper (#822) * remove dfl_head and update dfl_whole_face template * Add deep swapper models by Mats * Add deep swapper models by Druuzil * Add deep swapper models by Rumateus * Implement face enhancer weight for codeformer, Side Quest: has proces… (#823) * Implement face enhancer weight for codeformer, Side Quest: has processor checks * Fix typo * Fix face enhancer blend in UI * Use static model set creation * Add deep swapper models by Jen * Introduce create_static_model_set() everywhere (#824) * Move clear over to the UI (#825) * Fix model key * Undo restore_audio() * Switch to latest XSeg * Switch to latest XSeg * Switch to latest XSeg * Use resolve_download_url() everywhere, Vanish --skip-download flag * Fix resolve_download_url * Fix space * Kill resolve_execution_provider_keys() and move fallbacks where they belong * Kill resolve_execution_provider_keys() and move fallbacks where they belong * Remove as this does not work * Change TempFrameFormat order * Fix CoreML partially * Remove duplicates (Rumateus is the creator) * Add deep swapper models by Edel * Introduce download scopes (#826) * Introduce download scopes * Limit download scopes to force-download command * Change source-paths behaviour * Fix space * Update README * Rename create_log_level_program to create_misc_program * Fix wording * Fix wording * Update dependencies * Use tolerant for video_memory_strategy in benchmark * Feat/ffmpeg with progress (#827) * FFmpeg with progress bar * Fix typing * FFmpeg with progress bar part2 * Restore streaming wording * Change order in choices and typing * Introduce File using list_directory() (#830) * Feat/local deep swapper models (#832) * Local model support for deep swapper * Local model support for deep swapper part2 * Local model support for deep swapper part3 * Update yet another dfm by Druuzil * Refactor/choices and naming (#833) * Refactor choices, imports and naming * Refactor choices, imports and naming * Fix styles for tabs, Restore toast * Update yet another dfm by Druuzil * Feat/face masker models (#834) * Introduce face masker models * Introduce face masker models * Introduce face masker models * Register needed step keys * Provide different XSeg models * Simplify model context * Fix out of range for trim frame, Fix ffmpeg extraction count (#836) * Fix out of range for trim frame, Fix ffmpeg extraction count * Move restrict of trim frame to the core, Make sure all values are within the range * Fix and merge testing * Fix typing * Add region mask for deep swapper * Adjust wording * Move FACE_MASK_REGIONS to choices * Update dependencies * Feat/download provider fallback (#837) * Introduce download providers fallback, Use CURL everywhre * Fix CI * Use readlines() over readline() to avoid while * Use readlines() over readline() to avoid while * Use readlines() over readline() to avoid while * Use communicate() over wait() * Minor updates for testing * Stop webcam on source image change * Feat/webcam improvements (#838) * Detect available webcams * Fix CI, Move webcam id dropdown to the sidebar, Disable warnings * Fix CI * Remove signal on hard_exit() to prevent exceptions * Fix border color in toast timer * Prepare release * Update preview * Update preview * Hotfix progress bar --------- Co-authored-by: DDXDB <38449595+DDXDB@users.noreply.github.com> Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com>
311 lines
12 KiB
Python
311 lines
12 KiB
Python
from functools import lru_cache
|
|
from typing import List, Optional, Tuple
|
|
|
|
import cv2
|
|
import numpy
|
|
from cv2.typing import Size
|
|
|
|
import facefusion.choices
|
|
from facefusion.common_helper import is_windows
|
|
from facefusion.filesystem import is_image, is_video, sanitize_path_for_windows
|
|
from facefusion.typing import Duration, Fps, Orientation, Resolution, VisionFrame
|
|
|
|
|
|
@lru_cache(maxsize = 128)
|
|
def read_static_image(image_path : str) -> Optional[VisionFrame]:
|
|
return read_image(image_path)
|
|
|
|
|
|
def read_static_images(image_paths : List[str]) -> List[VisionFrame]:
|
|
frames = []
|
|
|
|
if image_paths:
|
|
for image_path in image_paths:
|
|
frames.append(read_static_image(image_path))
|
|
return frames
|
|
|
|
|
|
def read_image(image_path : str) -> Optional[VisionFrame]:
|
|
if is_image(image_path):
|
|
if is_windows():
|
|
image_path = sanitize_path_for_windows(image_path)
|
|
return cv2.imread(image_path)
|
|
return None
|
|
|
|
|
|
def write_image(image_path : str, vision_frame : VisionFrame) -> bool:
|
|
if image_path:
|
|
if is_windows():
|
|
image_path = sanitize_path_for_windows(image_path)
|
|
return cv2.imwrite(image_path, vision_frame)
|
|
return False
|
|
|
|
|
|
def detect_image_resolution(image_path : str) -> Optional[Resolution]:
|
|
if is_image(image_path):
|
|
image = read_image(image_path)
|
|
height, width = image.shape[:2]
|
|
return width, height
|
|
return None
|
|
|
|
|
|
def restrict_image_resolution(image_path : str, resolution : Resolution) -> Resolution:
|
|
if is_image(image_path):
|
|
image_resolution = detect_image_resolution(image_path)
|
|
if image_resolution < resolution:
|
|
return image_resolution
|
|
return resolution
|
|
|
|
|
|
def create_image_resolutions(resolution : Resolution) -> List[str]:
|
|
resolutions = []
|
|
temp_resolutions = []
|
|
|
|
if resolution:
|
|
width, height = resolution
|
|
temp_resolutions.append(normalize_resolution(resolution))
|
|
for image_template_size in facefusion.choices.image_template_sizes:
|
|
temp_resolutions.append(normalize_resolution((width * image_template_size, height * image_template_size)))
|
|
temp_resolutions = sorted(set(temp_resolutions))
|
|
for temp_resolution in temp_resolutions:
|
|
resolutions.append(pack_resolution(temp_resolution))
|
|
return resolutions
|
|
|
|
|
|
def get_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
|
|
if is_video(video_path):
|
|
if is_windows():
|
|
video_path = sanitize_path_for_windows(video_path)
|
|
video_capture = cv2.VideoCapture(video_path)
|
|
if video_capture.isOpened():
|
|
frame_total = video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
|
video_capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
|
|
has_vision_frame, vision_frame = video_capture.read()
|
|
video_capture.release()
|
|
if has_vision_frame:
|
|
return vision_frame
|
|
return None
|
|
|
|
|
|
def count_video_frame_total(video_path : str) -> int:
|
|
if is_video(video_path):
|
|
if is_windows():
|
|
video_path = sanitize_path_for_windows(video_path)
|
|
video_capture = cv2.VideoCapture(video_path)
|
|
if video_capture.isOpened():
|
|
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
video_capture.release()
|
|
return video_frame_total
|
|
return 0
|
|
|
|
|
|
def detect_video_fps(video_path : str) -> Optional[float]:
|
|
if is_video(video_path):
|
|
if is_windows():
|
|
video_path = sanitize_path_for_windows(video_path)
|
|
video_capture = cv2.VideoCapture(video_path)
|
|
if video_capture.isOpened():
|
|
video_fps = video_capture.get(cv2.CAP_PROP_FPS)
|
|
video_capture.release()
|
|
return video_fps
|
|
return None
|
|
|
|
|
|
def restrict_video_fps(video_path : str, fps : Fps) -> Fps:
|
|
if is_video(video_path):
|
|
video_fps = detect_video_fps(video_path)
|
|
if video_fps < fps:
|
|
return video_fps
|
|
return fps
|
|
|
|
|
|
def detect_video_duration(video_path : str) -> Duration:
|
|
video_frame_total = count_video_frame_total(video_path)
|
|
video_fps = detect_video_fps(video_path)
|
|
|
|
if video_frame_total and video_fps:
|
|
return video_frame_total / video_fps
|
|
return 0
|
|
|
|
|
|
def count_trim_frame_total(video_path : str, trim_frame_start : Optional[int], trim_frame_end : Optional[int]) -> int:
|
|
trim_frame_start, trim_frame_end = restrict_trim_frame(video_path, trim_frame_start, trim_frame_end)
|
|
|
|
return trim_frame_end - trim_frame_start
|
|
|
|
|
|
def restrict_trim_frame(video_path : str, trim_frame_start : Optional[int], trim_frame_end : Optional[int]) -> Tuple[int, int]:
|
|
video_frame_total = count_video_frame_total(video_path)
|
|
|
|
if isinstance(trim_frame_start, int):
|
|
trim_frame_start = max(0, min(trim_frame_start, video_frame_total))
|
|
if isinstance(trim_frame_end, int):
|
|
trim_frame_end = max(0, min(trim_frame_end, video_frame_total))
|
|
|
|
if isinstance(trim_frame_start, int) and isinstance(trim_frame_end, int):
|
|
return trim_frame_start, trim_frame_end
|
|
if isinstance(trim_frame_start, int):
|
|
return trim_frame_start, video_frame_total
|
|
if isinstance(trim_frame_end, int):
|
|
return 0, trim_frame_end
|
|
|
|
return 0, video_frame_total
|
|
|
|
|
|
def detect_video_resolution(video_path : str) -> Optional[Resolution]:
|
|
if is_video(video_path):
|
|
if is_windows():
|
|
video_path = sanitize_path_for_windows(video_path)
|
|
video_capture = cv2.VideoCapture(video_path)
|
|
if video_capture.isOpened():
|
|
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)
|
|
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
|
video_capture.release()
|
|
return int(width), int(height)
|
|
return None
|
|
|
|
|
|
def restrict_video_resolution(video_path : str, resolution : Resolution) -> Resolution:
|
|
if is_video(video_path):
|
|
video_resolution = detect_video_resolution(video_path)
|
|
if video_resolution < resolution:
|
|
return video_resolution
|
|
return resolution
|
|
|
|
|
|
def create_video_resolutions(resolution : Resolution) -> List[str]:
|
|
resolutions = []
|
|
temp_resolutions = []
|
|
|
|
if resolution:
|
|
width, height = resolution
|
|
temp_resolutions.append(normalize_resolution(resolution))
|
|
for video_template_size in facefusion.choices.video_template_sizes:
|
|
if width > height:
|
|
temp_resolutions.append(normalize_resolution((video_template_size * width / height, video_template_size)))
|
|
else:
|
|
temp_resolutions.append(normalize_resolution((video_template_size, video_template_size * height / width)))
|
|
temp_resolutions = sorted(set(temp_resolutions))
|
|
for temp_resolution in temp_resolutions:
|
|
resolutions.append(pack_resolution(temp_resolution))
|
|
return resolutions
|
|
|
|
|
|
def normalize_resolution(resolution : Tuple[float, float]) -> Resolution:
|
|
width, height = resolution
|
|
|
|
if width and height:
|
|
normalize_width = round(width / 2) * 2
|
|
normalize_height = round(height / 2) * 2
|
|
return normalize_width, normalize_height
|
|
return 0, 0
|
|
|
|
|
|
def pack_resolution(resolution : Resolution) -> str:
|
|
width, height = normalize_resolution(resolution)
|
|
return str(width) + 'x' + str(height)
|
|
|
|
|
|
def unpack_resolution(resolution : str) -> Resolution:
|
|
width, height = map(int, resolution.split('x'))
|
|
return width, height
|
|
|
|
|
|
def detect_frame_orientation(vision_frame : VisionFrame) -> Orientation:
|
|
height, width = vision_frame.shape[:2]
|
|
|
|
if width > height:
|
|
return 'landscape'
|
|
return 'portrait'
|
|
|
|
|
|
def resize_frame_resolution(vision_frame : VisionFrame, max_resolution : Resolution) -> VisionFrame:
|
|
height, width = vision_frame.shape[:2]
|
|
max_width, max_height = max_resolution
|
|
|
|
if height > max_height or width > max_width:
|
|
scale = min(max_height / height, max_width / width)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
return cv2.resize(vision_frame, (new_width, new_height))
|
|
return vision_frame
|
|
|
|
|
|
def normalize_frame_color(vision_frame : VisionFrame) -> VisionFrame:
|
|
return cv2.cvtColor(vision_frame, cv2.COLOR_BGR2RGB)
|
|
|
|
|
|
def conditional_match_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> VisionFrame:
|
|
histogram_factor = calc_histogram_difference(source_vision_frame, target_vision_frame)
|
|
target_vision_frame = blend_vision_frames(target_vision_frame, match_frame_color(source_vision_frame, target_vision_frame), histogram_factor)
|
|
return target_vision_frame
|
|
|
|
|
|
def match_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> VisionFrame:
|
|
color_difference_sizes = numpy.linspace(16, target_vision_frame.shape[0], 3, endpoint = False)
|
|
|
|
for color_difference_size in color_difference_sizes:
|
|
source_vision_frame = equalize_frame_color(source_vision_frame, target_vision_frame, normalize_resolution((color_difference_size, color_difference_size)))
|
|
target_vision_frame = equalize_frame_color(source_vision_frame, target_vision_frame, target_vision_frame.shape[:2][::-1])
|
|
return target_vision_frame
|
|
|
|
|
|
def equalize_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame, size : Size) -> VisionFrame:
|
|
source_frame_resize = cv2.resize(source_vision_frame, size, interpolation = cv2.INTER_AREA).astype(numpy.float32)
|
|
target_frame_resize = cv2.resize(target_vision_frame, size, interpolation = cv2.INTER_AREA).astype(numpy.float32)
|
|
color_difference_vision_frame = numpy.subtract(source_frame_resize, target_frame_resize)
|
|
color_difference_vision_frame = cv2.resize(color_difference_vision_frame, target_vision_frame.shape[:2][::-1], interpolation = cv2.INTER_CUBIC)
|
|
target_vision_frame = numpy.add(target_vision_frame, color_difference_vision_frame).clip(0, 255).astype(numpy.uint8)
|
|
return target_vision_frame
|
|
|
|
|
|
def calc_histogram_difference(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> float:
|
|
histogram_source = cv2.calcHist([cv2.cvtColor(source_vision_frame, cv2.COLOR_BGR2HSV)], [ 0, 1 ], None, [ 50, 60 ], [ 0, 180, 0, 256 ])
|
|
histogram_target = cv2.calcHist([cv2.cvtColor(target_vision_frame, cv2.COLOR_BGR2HSV)], [ 0, 1 ], None, [ 50, 60 ], [ 0, 180, 0, 256 ])
|
|
histogram_differnce = float(numpy.interp(cv2.compareHist(histogram_source, histogram_target, cv2.HISTCMP_CORREL), [ -1, 1 ], [ 0, 1 ]))
|
|
return histogram_differnce
|
|
|
|
|
|
def blend_vision_frames(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame, blend_factor : float) -> VisionFrame:
|
|
blend_vision_frame = cv2.addWeighted(source_vision_frame, 1 - blend_factor, target_vision_frame, blend_factor, 0)
|
|
return blend_vision_frame
|
|
|
|
|
|
def create_tile_frames(vision_frame : VisionFrame, size : Size) -> Tuple[List[VisionFrame], int, int]:
|
|
vision_frame = numpy.pad(vision_frame, ((size[1], size[1]), (size[1], size[1]), (0, 0)))
|
|
tile_width = size[0] - 2 * size[2]
|
|
pad_size_bottom = size[2] + tile_width - vision_frame.shape[0] % tile_width
|
|
pad_size_right = size[2] + tile_width - vision_frame.shape[1] % tile_width
|
|
pad_vision_frame = numpy.pad(vision_frame, ((size[2], pad_size_bottom), (size[2], pad_size_right), (0, 0)))
|
|
pad_height, pad_width = pad_vision_frame.shape[:2]
|
|
row_range = range(size[2], pad_height - size[2], tile_width)
|
|
col_range = range(size[2], pad_width - size[2], tile_width)
|
|
tile_vision_frames = []
|
|
|
|
for row_vision_frame in row_range:
|
|
top = row_vision_frame - size[2]
|
|
bottom = row_vision_frame + size[2] + tile_width
|
|
for column_vision_frame in col_range:
|
|
left = column_vision_frame - size[2]
|
|
right = column_vision_frame + size[2] + tile_width
|
|
tile_vision_frames.append(pad_vision_frame[top:bottom, left:right, :])
|
|
return tile_vision_frames, pad_width, pad_height
|
|
|
|
|
|
def merge_tile_frames(tile_vision_frames : List[VisionFrame], temp_width : int, temp_height : int, pad_width : int, pad_height : int, size : Size) -> VisionFrame:
|
|
merge_vision_frame = numpy.zeros((pad_height, pad_width, 3)).astype(numpy.uint8)
|
|
tile_width = tile_vision_frames[0].shape[1] - 2 * size[2]
|
|
tiles_per_row = min(pad_width // tile_width, len(tile_vision_frames))
|
|
|
|
for index, tile_vision_frame in enumerate(tile_vision_frames):
|
|
tile_vision_frame = tile_vision_frame[size[2]:-size[2], size[2]:-size[2]]
|
|
row_index = index // tiles_per_row
|
|
col_index = index % tiles_per_row
|
|
top = row_index * tile_vision_frame.shape[0]
|
|
bottom = top + tile_vision_frame.shape[0]
|
|
left = col_index * tile_vision_frame.shape[1]
|
|
right = left + tile_vision_frame.shape[1]
|
|
merge_vision_frame[top:bottom, left:right, :] = tile_vision_frame
|
|
merge_vision_frame = merge_vision_frame[size[1] : size[1] + temp_height, size[1]: size[1] + temp_width, :]
|
|
return merge_vision_frame
|