mirror of
https://github.com/facefusion/facefusion.git
synced 2026-08-07 09:38:39 +02:00
Merge branch 'master' into v4
Resolve 57 conflicts keeping v4's architecture (UI removal, api app context, path isolation, streaming/codecs/rtc, workflow-mode superset, args_helper) and taking master's post-fork features so none are dropped: inference override/adjust provider hooks (+ face_swapper 3.8.1 coreml fix), is_vision_frame validity guards, resolve_temp_frame_set frame numbering with the source audio/voice trim offset, the ffmpeg color pipeline (restrict_color_transfer / convert_color_space / temp_pixel_format), ffprobe select_stream v:0 + format duration, pre_check ffprobe with dependency_not_installed, onnxruntime arena-leak guard + version lru_cache, installer cuda@12/@13 provider split, workflow_strategy disk/memory (memory default), and the dependency version bumps. content_analyser hash guard rehashed to 3c6ce25e. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V7MdZEmd1GE8uSDTMyq34r
This commit is contained in:
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
uses: AnimMouse/setup-ffmpeg@v1
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
||||
+5
-3
@@ -1,6 +1,3 @@
|
||||
[workflow]
|
||||
workflow_mode =
|
||||
|
||||
[paths]
|
||||
temp_path =
|
||||
jobs_path =
|
||||
@@ -54,6 +51,7 @@ voice_extractor_model =
|
||||
trim_frame_start =
|
||||
trim_frame_end =
|
||||
temp_frame_format =
|
||||
temp_pixel_format =
|
||||
|
||||
[frame_distribution]
|
||||
target_frame_amount =
|
||||
@@ -71,6 +69,10 @@ output_video_quality =
|
||||
output_video_scale =
|
||||
output_video_fps =
|
||||
|
||||
[workflow]
|
||||
workflow_mode =
|
||||
workflow_strategy =
|
||||
|
||||
[processors]
|
||||
processors =
|
||||
age_modifier_model =
|
||||
|
||||
@@ -14,13 +14,15 @@ from facefusion.apis.stream_audio import receive_audio_frames, run_audio_encode_
|
||||
from facefusion.apis.stream_video import receive_video_frames, run_video_encode_loop
|
||||
from facefusion.libraries import datachannel as datachannel_module
|
||||
from facefusion.types import AudioCodec, AudioFrame, BufferPack, PeerConnection, RtcPeer, RtcPeerAudio, SdpAnswer, SdpOffer, SessionId, Time, VideoCodec, VisionFrame
|
||||
from facefusion.vision import read_static_images
|
||||
|
||||
|
||||
async def process_image(websocket : WebSocket) -> None:
|
||||
capture_vision_frame = await anext(receive_vision_frames(websocket), None)
|
||||
|
||||
if numpy.any(capture_vision_frame):
|
||||
output_vision_frame = streamer.process_stream_frame(capture_vision_frame)
|
||||
source_vision_frames = read_static_images(state_manager.get_item('source_paths'))
|
||||
output_vision_frame = streamer.process_stream_frame(source_vision_frames, capture_vision_frame)
|
||||
is_success, output_frame_buffer = cv2.imencode('.jpg', output_vision_frame)
|
||||
|
||||
if is_success:
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from queue import Queue
|
||||
from typing import Optional, Tuple
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy
|
||||
|
||||
from facefusion import rtc, streamer
|
||||
from facefusion import rtc, state_manager, streamer
|
||||
from facefusion.apis.stream_event import create_receive_event
|
||||
from facefusion.codecs import aom_decoder, aom_encoder, vpx_decoder, vpx_encoder
|
||||
from facefusion.types import AomDecoder, AomEncoder, BitRate, Buffer, BufferPack, Resolution, RtcPeer, RtcPeerVideo, Time, VideoCodec, VisionFrame, VpxDecoder, VpxEncoder
|
||||
from facefusion.vision import read_static_images
|
||||
|
||||
|
||||
def run_video_encode_loop(rtc_peer : RtcPeer, video_queue : Queue[Tuple[Time, Future[BufferPack]]]) -> None:
|
||||
@@ -59,8 +60,9 @@ def receive_video_frames(rtc_peer_video : RtcPeerVideo, video_queue : Queue[Tupl
|
||||
video_track = rtc_peer_video.get('receiver_track')
|
||||
video_codec = rtc_peer_video.get('codec')
|
||||
video_decoder = create_video_decoder(video_codec)
|
||||
source_vision_frames = read_static_images(state_manager.get_item('source_paths'))
|
||||
|
||||
video_frame_handler = partial(handle_video_frame, video_codec, video_decoder, video_queue, video_executor)
|
||||
video_frame_handler = partial(handle_video_frame, source_vision_frames, video_codec, video_decoder, video_queue, video_executor)
|
||||
receive_event = create_receive_event(video_track, video_frame_handler)
|
||||
receive_event.wait()
|
||||
|
||||
@@ -70,8 +72,8 @@ def receive_video_frames(rtc_peer_video : RtcPeerVideo, video_queue : Queue[Tupl
|
||||
destroy_video_decoder(video_codec, video_decoder)
|
||||
|
||||
|
||||
def process_video_frame(input_vision_frame : VisionFrame) -> BufferPack:
|
||||
output_vision_frame = streamer.process_stream_frame(input_vision_frame)
|
||||
def process_video_frame(source_vision_frames : List[VisionFrame], input_vision_frame : VisionFrame) -> BufferPack:
|
||||
output_vision_frame = streamer.process_stream_frame(source_vision_frames, input_vision_frame)
|
||||
output_resolution : Resolution = (output_vision_frame.shape[1], output_vision_frame.shape[0])
|
||||
output_buffer = cv2.cvtColor(output_vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
|
||||
return BufferPack(buffer = output_buffer, resolution = output_resolution)
|
||||
@@ -165,10 +167,10 @@ def update_video_encoder_bitrate(video_codec : VideoCodec, video_encoder : VpxEn
|
||||
return False
|
||||
|
||||
|
||||
def handle_video_frame(video_codec : VideoCodec, video_decoder : VpxDecoder | AomDecoder, video_queue : Queue[Tuple[Time, Future[BufferPack]]], video_executor : ThreadPoolExecutor, video_buffer : Buffer, video_timestamp : int) -> None:
|
||||
def handle_video_frame(source_vision_frames : List[VisionFrame], video_codec : VideoCodec, video_decoder : VpxDecoder | AomDecoder, video_queue : Queue[Tuple[Time, Future[BufferPack]]], video_executor : ThreadPoolExecutor, video_buffer : Buffer, video_timestamp : int) -> None:
|
||||
vision_frame = decode_video_frame(video_codec, video_decoder, video_buffer)
|
||||
|
||||
if numpy.any(vision_frame) and video_queue.qsize() < video_queue.maxsize:
|
||||
video_future = video_executor.submit(process_video_frame, vision_frame)
|
||||
video_future = video_executor.submit(process_video_frame, source_vision_frames, vision_frame)
|
||||
video_time = rtc.convert_timestamp_to_time(video_codec, video_timestamp)
|
||||
video_queue.put((video_time, video_future))
|
||||
|
||||
@@ -11,7 +11,6 @@ from facefusion.vision import detect_video_fps
|
||||
|
||||
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
|
||||
apply_state_item('command', args.get('command'))
|
||||
apply_state_item('workflow_mode', args.get('workflow_mode'))
|
||||
apply_state_item('temp_path', args.get('temp_path'))
|
||||
apply_state_item('jobs_path', args.get('jobs_path'))
|
||||
apply_state_item('source_paths', args.get('source_paths'))
|
||||
@@ -48,6 +47,7 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
|
||||
apply_state_item('trim_frame_start', args.get('trim_frame_start'))
|
||||
apply_state_item('trim_frame_end', args.get('trim_frame_end'))
|
||||
apply_state_item('temp_frame_format', args.get('temp_frame_format'))
|
||||
apply_state_item('temp_pixel_format', args.get('temp_pixel_format'))
|
||||
apply_state_item('target_frame_amount', args.get('target_frame_amount'))
|
||||
apply_state_item('output_image_quality', args.get('output_image_quality'))
|
||||
apply_state_item('output_image_scale', args.get('output_image_scale'))
|
||||
@@ -64,6 +64,8 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
|
||||
output_video_fps = normalize_fps(args.get('output_video_fps')) or detect_video_fps(args.get('target_path'))
|
||||
apply_state_item('output_video_fps', output_video_fps)
|
||||
|
||||
apply_state_item('workflow_mode', args.get('workflow_mode'))
|
||||
apply_state_item('workflow_strategy', args.get('workflow_strategy'))
|
||||
available_processors = [ get_file_name(file_path) for file_path in resolve_file_paths('facefusion/processors/modules') ]
|
||||
apply_state_item('processors', args.get('processors'))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
from typing import List, Sequence, get_args
|
||||
|
||||
from facefusion.common_helper import create_float_range, create_int_range
|
||||
from facefusion.types import Angle, ApiSecurityStrategy, AudioEncoder, AudioFormat, AudioSet, BenchmarkMode, BenchmarkResolution, BenchmarkSet, DownloadProvider, DownloadProviderSet, DownloadScope, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskArea, FaceMaskAreaSet, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorGender, FaceSelectorMode, FaceSelectorOrder, FaceSelectorRace, Gender, ImageEncoder, ImageFormat, ImageSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoSet, VoiceExtractorModel, WorkflowMode
|
||||
from facefusion.types import Angle, ApiSecurityStrategy, AudioEncoder, AudioFormat, AudioSet, BenchmarkMode, BenchmarkResolution, BenchmarkSet, DownloadProvider, DownloadProviderSet, DownloadScope, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskArea, FaceMaskAreaSet, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorGender, FaceSelectorMode, FaceSelectorOrder, FaceSelectorRace, Gender, ImageEncoder, ImageFormat, ImageSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, TempPixelFormat, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoSet, VoiceExtractorModel, WorkflowMode, WorkflowStrategy
|
||||
|
||||
face_detector_set : FaceDetectorSet =\
|
||||
{
|
||||
@@ -48,6 +48,7 @@ face_mask_regions : List[FaceMaskRegion] = list(get_args(FaceMaskRegion))
|
||||
voice_extractor_models : List[VoiceExtractorModel] = list(get_args(VoiceExtractorModel))
|
||||
|
||||
workflow_modes : List[WorkflowMode] = [ 'auto', 'audio-to-image:frames', 'audio-to-image:video', 'image-to-image', 'image-to-video', 'image-to-video:frames' ]
|
||||
workflow_strategies : List[WorkflowStrategy] = list(get_args(WorkflowStrategy))
|
||||
|
||||
audio_set : AudioSet =\
|
||||
{
|
||||
@@ -82,6 +83,7 @@ audio_formats : List[AudioFormat] = list(get_args(AudioFormat))
|
||||
image_formats : List[ImageFormat] = list(get_args(ImageFormat))
|
||||
video_formats : List[VideoFormat] = list(get_args(VideoFormat))
|
||||
temp_frame_formats : List[TempFrameFormat] = list(get_args(TempFrameFormat))
|
||||
temp_pixel_formats : List[TempPixelFormat] = list(get_args(TempPixelFormat))
|
||||
|
||||
audio_encoders : List[AudioEncoder] = list(get_args(AudioEncoder))
|
||||
image_encoders : List[ImageEncoder] = list(get_args(ImageEncoder))
|
||||
|
||||
@@ -4,12 +4,12 @@ from typing import Tuple
|
||||
import numpy
|
||||
from tqdm import tqdm
|
||||
|
||||
from facefusion import inference_manager, state_manager, translator
|
||||
from facefusion import inference_manager, state_manager, translator, video_manager
|
||||
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
|
||||
from facefusion.filesystem import resolve_relative_path
|
||||
from facefusion.thread_helper import conditional_thread_semaphore
|
||||
from facefusion.types import Detection, DownloadScope, DownloadSet, Fps, InferencePool, ModelSet, VisionFrame
|
||||
from facefusion.vision import detect_video_fps, fit_contain_frame, read_image, read_video_frame
|
||||
from facefusion.vision import detect_video_fps, fit_contain_frame, is_vision_frame, read_image
|
||||
|
||||
STREAM_COUNTER = 0
|
||||
|
||||
@@ -158,17 +158,21 @@ def analyse_image(image_path : str) -> bool:
|
||||
def analyse_video(video_path : str, trim_frame_start : int, trim_frame_end : int) -> bool:
|
||||
video_fps = detect_video_fps(video_path)
|
||||
frame_range = range(trim_frame_start, trim_frame_end)
|
||||
video_reader = video_manager.get_reader(video_path, 'analyse_video')
|
||||
rate = 0.0
|
||||
total = 0
|
||||
counter = 0
|
||||
|
||||
if trim_frame_start > 0:
|
||||
video_manager.seek_video_reader(video_reader, trim_frame_start)
|
||||
|
||||
with tqdm(total = len(frame_range), desc = translator.get('analysing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
|
||||
|
||||
for frame_number in frame_range:
|
||||
if frame_number % int(video_fps) == 0:
|
||||
vision_frame = read_video_frame(video_path, frame_number)
|
||||
vision_frame = video_manager.read_video_frame(video_reader)
|
||||
|
||||
if numpy.any(vision_frame):
|
||||
if frame_number % int(video_fps) == 0:
|
||||
if is_vision_frame(vision_frame):
|
||||
total += 1
|
||||
|
||||
if analyse_frame(vision_frame):
|
||||
|
||||
+5
-8
@@ -91,20 +91,17 @@ def pre_check() -> bool:
|
||||
logger.error(translator.get('python_not_supported').format(version = '3.10'), __name__)
|
||||
return False
|
||||
|
||||
if not shutil.which('curl'):
|
||||
logger.error(translator.get('curl_not_installed'), __name__)
|
||||
return False
|
||||
|
||||
if not shutil.which('ffmpeg'):
|
||||
logger.error(translator.get('ffmpeg_not_installed'), __name__)
|
||||
return False
|
||||
for dependency in [ 'curl', 'ffmpeg', 'ffprobe' ]:
|
||||
if not shutil.which(dependency):
|
||||
logger.error(translator.get('dependency_not_installed').format(dependency = dependency), __name__)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def common_pre_check() -> bool:
|
||||
content_analyser_content = inspect.getsource(content_analyser).encode()
|
||||
|
||||
return hash_helper.create_hash(content_analyser_content) == '975d67d6'
|
||||
return hash_helper.create_hash(content_analyser_content) == '3c6ce25e'
|
||||
|
||||
|
||||
def processors_pre_check() -> bool:
|
||||
|
||||
@@ -12,6 +12,16 @@ from facefusion.types import ExecutionProvider, InferenceOptionSet, InferencePro
|
||||
onnxruntime.set_default_logger_severity(3)
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def get_onnxruntime_version() -> Tuple[int, int, int]:
|
||||
version_split = onnxruntime.__version__.split('.')
|
||||
major_version = int(version_split[0])
|
||||
minor_version = int(version_split[1])
|
||||
patch_version = int(version_split[2].split('+')[0])
|
||||
|
||||
return major_version, minor_version, patch_version
|
||||
|
||||
|
||||
def has_execution_provider(execution_provider : ExecutionProvider) -> bool:
|
||||
return execution_provider in get_available_execution_providers()
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from facefusion.face_helper import apply_nms, average_points, convert_to_face_la
|
||||
from facefusion.face_landmarker import detect_face_landmark, estimate_face_landmark_68_5
|
||||
from facefusion.face_recognizer import calculate_face_embedding
|
||||
from facefusion.types import BoundingBox, Face, FaceLandmark5, FaceLandmarkSet, FaceScoreSet, Score, VisionFrame
|
||||
from facefusion.vision import is_vision_frame
|
||||
|
||||
|
||||
def create_faces(vision_frame : VisionFrame, bounding_boxes : List[BoundingBox], face_scores : List[Score], face_landmarks_5 : List[FaceLandmark5]) -> List[Face]:
|
||||
@@ -73,7 +74,7 @@ def get_many_faces(vision_frames : List[VisionFrame]) -> List[Face]:
|
||||
many_faces : List[Face] = []
|
||||
|
||||
for vision_frame in vision_frames:
|
||||
if numpy.any(vision_frame):
|
||||
if is_vision_frame(vision_frame):
|
||||
all_bounding_boxes = []
|
||||
all_face_scores = []
|
||||
all_face_landmarks_5 = []
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import threading
|
||||
from typing import List, Optional
|
||||
|
||||
import numpy
|
||||
|
||||
from facefusion.hash_helper import create_hash
|
||||
from facefusion.types import Face, FaceStore, VisionFrame
|
||||
from facefusion.vision import is_vision_frame
|
||||
|
||||
FACE_STORE : FaceStore = {}
|
||||
|
||||
|
||||
def get_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
|
||||
if numpy.any(vision_frame):
|
||||
if is_vision_frame(vision_frame):
|
||||
vision_hash = create_hash(vision_frame.tobytes())
|
||||
|
||||
if FACE_STORE.get(vision_hash):
|
||||
@@ -20,7 +19,7 @@ def get_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
|
||||
|
||||
|
||||
def set_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
|
||||
if numpy.any(vision_frame):
|
||||
if is_vision_frame(vision_frame):
|
||||
vision_hash = create_hash(vision_frame.tobytes())
|
||||
FACE_STORE.setdefault(vision_hash,
|
||||
{
|
||||
@@ -29,7 +28,7 @@ def set_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
|
||||
|
||||
|
||||
def resolve_lock(vision_frame : VisionFrame) -> threading.Lock:
|
||||
if numpy.any(vision_frame):
|
||||
if is_vision_frame(vision_frame):
|
||||
vision_hash = create_hash(vision_frame.tobytes())
|
||||
return FACE_STORE.setdefault(vision_hash,
|
||||
{
|
||||
|
||||
+65
-16
@@ -7,11 +7,10 @@ from typing import List, Optional, cast
|
||||
from tqdm import tqdm
|
||||
|
||||
import facefusion.choices
|
||||
from facefusion import ffmpeg_builder, logger, process_manager, state_manager, translator
|
||||
from facefusion import ffmpeg_builder, ffprobe, logger, process_manager, state_manager, translator, vision
|
||||
from facefusion.filesystem import get_file_format, remove_file
|
||||
from facefusion.temp_helper import get_temp_file_path, get_temp_frames_pattern
|
||||
from facefusion.types import ApiSecurityStrategy, AudioEncoder, Buffer, Command, EncoderSet, Fps, Resolution, SampleRate, UpdateProgress, VideoEncoder, VideoFormat
|
||||
from facefusion.vision import detect_video_duration, detect_video_fps, pack_resolution, predict_video_frame_total
|
||||
from facefusion.types import ApiSecurityStrategy, AudioEncoder, Buffer, Command, EncoderSet, Fps, Resolution, SampleRate, UpdateProgress, VideoEncoder, VideoFormat, VideoReaderMetadata
|
||||
|
||||
|
||||
def run_ffmpeg_with_progress(commands : List[Command], update_progress : UpdateProgress) -> subprocess.Popen[Buffer]:
|
||||
@@ -74,7 +73,52 @@ def run_ffmpeg(commands : List[Command]) -> subprocess.Popen[Buffer]:
|
||||
|
||||
def open_ffmpeg(commands : List[Command]) -> subprocess.Popen[Buffer]:
|
||||
commands = ffmpeg_builder.run(commands)
|
||||
return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
|
||||
return subprocess.Popen(commands, stdin = subprocess.PIPE, stderr = subprocess.DEVNULL, stdout = subprocess.PIPE)
|
||||
|
||||
|
||||
def create_video_reader(video_path : str, frame_number : int, video_metadata : VideoReaderMetadata) -> subprocess.Popen[Buffer]:
|
||||
commands = ffmpeg_builder.chain(
|
||||
ffmpeg_builder.seek_to(frame_number / video_metadata.get('fps')),
|
||||
ffmpeg_builder.set_input(video_path),
|
||||
ffmpeg_builder.restrict_color_transfer(video_metadata.get('color_transfer')),
|
||||
ffmpeg_builder.prevent_frame_drop(),
|
||||
ffmpeg_builder.enforce_pixel_format('bgr24'),
|
||||
ffmpeg_builder.set_output_format('rawvideo'),
|
||||
ffmpeg_builder.cast_stream()
|
||||
)
|
||||
|
||||
return open_ffmpeg(commands)
|
||||
|
||||
|
||||
def create_video_writer(target_path : str, temp_video_fps : Fps, temp_video_resolution : Resolution, output_video_resolution : Resolution, output_video_fps : Fps) -> subprocess.Popen[Buffer]:
|
||||
output_video_encoder = state_manager.get_item('output_video_encoder')
|
||||
output_video_quality = state_manager.get_item('output_video_quality')
|
||||
output_video_preset = state_manager.get_item('output_video_preset')
|
||||
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), target_path)
|
||||
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
|
||||
output_video_encoder = fix_video_encoder(temp_video_format, output_video_encoder)
|
||||
|
||||
commands = ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_output_format('rawvideo'),
|
||||
ffmpeg_builder.enforce_pixel_format(state_manager.get_item('temp_pixel_format')),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(temp_video_resolution)),
|
||||
ffmpeg_builder.set_input_fps(temp_video_fps),
|
||||
ffmpeg_builder.set_input('pipe:0'),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(output_video_resolution)),
|
||||
ffmpeg_builder.set_video_encoder(output_video_encoder),
|
||||
ffmpeg_builder.set_thread_count(16),
|
||||
ffmpeg_builder.set_video_tag(output_video_encoder, temp_video_format),
|
||||
ffmpeg_builder.set_video_quality(output_video_encoder, output_video_quality),
|
||||
ffmpeg_builder.set_video_preset(output_video_encoder, output_video_preset),
|
||||
ffmpeg_builder.concat(
|
||||
ffmpeg_builder.set_video_fps(output_video_fps),
|
||||
ffmpeg_builder.convert_color_space('bt709')
|
||||
),
|
||||
ffmpeg_builder.set_pixel_format(output_video_encoder),
|
||||
ffmpeg_builder.force_output(temp_video_path)
|
||||
)
|
||||
|
||||
return open_ffmpeg(commands)
|
||||
|
||||
|
||||
def log_debug(process : subprocess.Popen[Buffer]) -> None:
|
||||
@@ -123,14 +167,18 @@ def get_static_available_encoder_set() -> EncoderSet:
|
||||
|
||||
|
||||
def extract_frames(target_path : str, output_path : str, temp_video_resolution : Resolution, temp_video_fps : Fps, trim_frame_start : int, trim_frame_end : int) -> bool:
|
||||
extract_frame_total = predict_video_frame_total(target_path, temp_video_fps, trim_frame_start, trim_frame_end)
|
||||
color_transfer = ffprobe.extract_static_video_metadata(target_path).get('color_transfer')
|
||||
extract_frame_total = vision.predict_video_frame_total(target_path, temp_video_fps, trim_frame_start, trim_frame_end)
|
||||
temp_frames_pattern = get_temp_frames_pattern(state_manager.get_temp_path(), output_path, state_manager.get_item('temp_frame_format'), '%08d')
|
||||
commands = ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(target_path),
|
||||
ffmpeg_builder.set_media_resolution(pack_resolution(temp_video_resolution)),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(temp_video_resolution)),
|
||||
ffmpeg_builder.set_frame_quality(0),
|
||||
ffmpeg_builder.enforce_pixel_format('rgb24'),
|
||||
ffmpeg_builder.select_frame_range(trim_frame_start, trim_frame_end, temp_video_fps),
|
||||
ffmpeg_builder.concat(
|
||||
ffmpeg_builder.select_frame_range(trim_frame_start, trim_frame_end, temp_video_fps),
|
||||
ffmpeg_builder.restrict_color_transfer(color_transfer)
|
||||
),
|
||||
ffmpeg_builder.prevent_frame_drop(),
|
||||
ffmpeg_builder.set_start_number(trim_frame_start),
|
||||
ffmpeg_builder.set_output(temp_frames_pattern)
|
||||
@@ -150,7 +198,7 @@ def spawn_frames(target_path : str, output_path : str, temp_video_resolution : R
|
||||
ffmpeg_builder.set_input(target_path),
|
||||
ffmpeg_builder.set_video_duration(duration),
|
||||
ffmpeg_builder.set_video_fps(temp_video_fps),
|
||||
ffmpeg_builder.set_media_resolution(pack_resolution(temp_video_resolution)),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(temp_video_resolution)),
|
||||
ffmpeg_builder.set_output(temp_frames_pattern)
|
||||
)
|
||||
|
||||
@@ -163,7 +211,7 @@ def copy_image(target_path : str, output_path : str, temp_image_resolution : Res
|
||||
temp_image_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
|
||||
commands = ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(target_path),
|
||||
ffmpeg_builder.set_media_resolution(pack_resolution(temp_image_resolution)),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(temp_image_resolution)),
|
||||
ffmpeg_builder.set_image_quality(target_path, 100),
|
||||
ffmpeg_builder.force_output(temp_image_path)
|
||||
)
|
||||
@@ -175,7 +223,7 @@ def finalize_image(output_path : str, output_image_resolution : Resolution) -> b
|
||||
temp_image_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
|
||||
commands = ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(temp_image_path),
|
||||
ffmpeg_builder.set_media_resolution(pack_resolution(output_image_resolution)),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(output_image_resolution)),
|
||||
ffmpeg_builder.set_image_quality(output_path, output_image_quality),
|
||||
ffmpeg_builder.force_output(output_path)
|
||||
)
|
||||
@@ -204,10 +252,10 @@ def restore_audio(target_path : str, output_path : str, trim_frame_start : int,
|
||||
output_audio_encoder = state_manager.get_item('output_audio_encoder')
|
||||
output_audio_quality = state_manager.get_item('output_audio_quality')
|
||||
output_audio_volume = state_manager.get_item('output_audio_volume')
|
||||
target_video_fps = detect_video_fps(target_path)
|
||||
target_video_fps = vision.detect_video_fps(target_path)
|
||||
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
|
||||
temp_video_format = cast(VideoFormat, get_file_format(output_path))
|
||||
temp_video_duration = detect_video_duration(temp_video_path)
|
||||
temp_video_duration = vision.detect_video_duration(temp_video_path)
|
||||
output_video_format = cast(VideoFormat, get_file_format(output_path))
|
||||
|
||||
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
|
||||
@@ -234,7 +282,7 @@ def replace_audio(audio_path : str, output_path : str) -> bool:
|
||||
output_audio_volume = state_manager.get_item('output_audio_volume')
|
||||
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
|
||||
temp_video_format = cast(VideoFormat, get_file_format(output_path))
|
||||
temp_video_duration = detect_video_duration(temp_video_path)
|
||||
temp_video_duration = vision.detect_video_duration(temp_video_path)
|
||||
output_video_format = cast(VideoFormat, get_file_format(output_path))
|
||||
|
||||
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
|
||||
@@ -256,7 +304,7 @@ def merge_video(target_path : str, output_path : str, temp_video_fps : Fps, outp
|
||||
output_video_encoder = state_manager.get_item('output_video_encoder')
|
||||
output_video_quality = state_manager.get_item('output_video_quality')
|
||||
output_video_preset = state_manager.get_item('output_video_preset')
|
||||
merge_frame_total = predict_video_frame_total(target_path, output_video_fps, trim_frame_start, trim_frame_end)
|
||||
merge_frame_total = vision.predict_video_frame_total(target_path, output_video_fps, trim_frame_start, trim_frame_end)
|
||||
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
|
||||
temp_video_format = cast(VideoFormat, get_file_format(output_path))
|
||||
temp_frames_pattern = get_temp_frames_pattern(state_manager.get_temp_path(), output_path, state_manager.get_item('temp_frame_format'), '%08d')
|
||||
@@ -266,14 +314,15 @@ def merge_video(target_path : str, output_path : str, temp_video_fps : Fps, outp
|
||||
ffmpeg_builder.set_input_fps(temp_video_fps),
|
||||
ffmpeg_builder.set_start_number(trim_frame_start),
|
||||
ffmpeg_builder.set_input(temp_frames_pattern),
|
||||
ffmpeg_builder.set_media_resolution(pack_resolution(output_video_resolution)),
|
||||
ffmpeg_builder.set_media_resolution(vision.pack_resolution(output_video_resolution)),
|
||||
ffmpeg_builder.set_video_encoder(output_video_encoder),
|
||||
ffmpeg_builder.set_video_tag(output_video_encoder, temp_video_format),
|
||||
ffmpeg_builder.set_video_quality(output_video_encoder, output_video_quality),
|
||||
ffmpeg_builder.set_video_preset(output_video_encoder, output_video_preset),
|
||||
ffmpeg_builder.concat(
|
||||
ffmpeg_builder.set_video_fps(output_video_fps),
|
||||
ffmpeg_builder.keep_video_alpha(output_video_encoder)
|
||||
ffmpeg_builder.keep_video_alpha(output_video_encoder),
|
||||
ffmpeg_builder.convert_color_space('bt709')
|
||||
),
|
||||
ffmpeg_builder.set_pixel_format(output_video_encoder),
|
||||
ffmpeg_builder.force_output(temp_video_path)
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import List, Optional
|
||||
import numpy
|
||||
|
||||
from facefusion.filesystem import get_file_format
|
||||
from facefusion.types import AudioEncoder, Command, CommandSet, Duration, Fps, SampleRate, StreamMode, VideoEncoder, VideoFormat, VideoPreset
|
||||
from facefusion.types import AudioEncoder, ColorSpace, ColorTransfer, Command, CommandSet, Duration, Fps, SampleRate, StreamMode, VideoEncoder, VideoFormat, VideoPreset
|
||||
|
||||
|
||||
def run(commands : List[Command]) -> List[Command]:
|
||||
@@ -47,6 +47,10 @@ def set_input(input_path : str) -> List[Command]:
|
||||
return [ '-i', input_path ]
|
||||
|
||||
|
||||
def seek_to(time : float) -> List[Command]:
|
||||
return [ '-ss', str(time) ]
|
||||
|
||||
|
||||
def set_input_fps(input_fps : Fps) -> List[Command]:
|
||||
return [ '-r', str(input_fps) ]
|
||||
|
||||
@@ -63,6 +67,14 @@ def force_output(output_path : str) -> List[Command]:
|
||||
return [ '-y', output_path ]
|
||||
|
||||
|
||||
def set_output_format(output_format : str) -> List[Command]:
|
||||
return [ '-f', output_format ]
|
||||
|
||||
|
||||
def set_thread_count(thread_count : int) -> List[Command]:
|
||||
return [ '-threads', str(thread_count) ]
|
||||
|
||||
|
||||
def set_loop() -> List[Command]:
|
||||
return [ '-loop', '1' ]
|
||||
|
||||
@@ -103,6 +115,16 @@ def set_pixel_format(video_encoder : VideoEncoder) -> List[Command]:
|
||||
return [ '-pix_fmt', 'yuv420p' ]
|
||||
|
||||
|
||||
def restrict_color_transfer(color_transfer : ColorTransfer) -> List[Command]:
|
||||
if color_transfer in [ 'smpte2084', 'arib-std-b67' ]:
|
||||
return [ '-vf', 'scale=out_primaries=bt709:out_transfer=bt709:intent=perceptual' ]
|
||||
return []
|
||||
|
||||
|
||||
def convert_color_space(color_space : ColorSpace) -> List[Command]:
|
||||
return [ '-vf', 'scale=out_color_matrix=' + color_space + ':out_range=tv,setparams=colorspace=' + color_space + ':color_primaries=' + color_space + ':color_trc=' + color_space ]
|
||||
|
||||
|
||||
def set_frame_quality(frame_quality : int) -> List[Command]:
|
||||
return [ '-q:v', str(frame_quality) ]
|
||||
|
||||
|
||||
+65
-19
@@ -1,4 +1,5 @@
|
||||
import subprocess
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List
|
||||
|
||||
from facefusion import ffprobe_builder
|
||||
@@ -10,16 +11,9 @@ def run_ffprobe(commands : List[Command]) -> subprocess.Popen[Buffer]:
|
||||
return subprocess.Popen(commands, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
|
||||
|
||||
|
||||
def probe_entries(media_path : str, entries : List[str]) -> Dict[str, str]:
|
||||
def parse_entries(output : Buffer) -> Dict[str, str]:
|
||||
media_entries = {}
|
||||
|
||||
commands = ffprobe_builder.chain(
|
||||
ffprobe_builder.show_entries(entries),
|
||||
ffprobe_builder.format_to_key_value(),
|
||||
ffprobe_builder.set_input(media_path)
|
||||
)
|
||||
output, _ = run_ffprobe(commands).communicate()
|
||||
|
||||
if output:
|
||||
lines = output.decode().strip().splitlines()
|
||||
|
||||
@@ -31,14 +25,58 @@ def probe_entries(media_path : str, entries : List[str]) -> Dict[str, str]:
|
||||
return media_entries
|
||||
|
||||
|
||||
def extract_audio_metadata(audio_path : str) -> AudioMetadata:
|
||||
audio_entries = probe_entries(audio_path, [ 'duration', 'sample_rate', 'channels', 'bit_rate' ])
|
||||
def probe_audio_entries(audio_path : str, entries : List[str]) -> Dict[str, str]:
|
||||
commands = ffprobe_builder.chain(
|
||||
ffprobe_builder.select_stream('a:0'),
|
||||
ffprobe_builder.show_stream_entries(entries),
|
||||
ffprobe_builder.format_to_key_value(),
|
||||
ffprobe_builder.set_input(audio_path)
|
||||
)
|
||||
|
||||
duration = float(audio_entries.get('duration'))
|
||||
output, _ = run_ffprobe(commands).communicate()
|
||||
|
||||
return parse_entries(output)
|
||||
|
||||
|
||||
def probe_video_entries(video_path : str, entries : List[str]) -> Dict[str, str]:
|
||||
commands = ffprobe_builder.chain(
|
||||
ffprobe_builder.select_stream('v:0'),
|
||||
ffprobe_builder.show_stream_entries(entries),
|
||||
ffprobe_builder.format_to_key_value(),
|
||||
ffprobe_builder.set_input(video_path)
|
||||
)
|
||||
|
||||
output, _ = run_ffprobe(commands).communicate()
|
||||
|
||||
return parse_entries(output)
|
||||
|
||||
|
||||
def probe_format_entries(media_path : str, entries : List[str]) -> Dict[str, str]:
|
||||
commands = ffprobe_builder.chain(
|
||||
ffprobe_builder.show_format_entries(entries),
|
||||
ffprobe_builder.format_to_key_value(),
|
||||
ffprobe_builder.set_input(media_path)
|
||||
)
|
||||
|
||||
output, _ = run_ffprobe(commands).communicate()
|
||||
|
||||
return parse_entries(output)
|
||||
|
||||
|
||||
@lru_cache(maxsize = 128)
|
||||
def extract_static_audio_metadata(audio_path : str) -> AudioMetadata:
|
||||
return extract_audio_metadata(audio_path)
|
||||
|
||||
|
||||
def extract_audio_metadata(audio_path : str) -> AudioMetadata:
|
||||
audio_entries = probe_audio_entries(audio_path, [ 'sample_rate', 'channels' ])
|
||||
format_entries = probe_format_entries(audio_path, [ 'duration', 'bit_rate' ])
|
||||
|
||||
duration = float(format_entries.get('duration'))
|
||||
sample_rate = int(audio_entries.get('sample_rate'))
|
||||
frame_total = int(duration * sample_rate)
|
||||
frame_total = round(duration * sample_rate)
|
||||
channel_total = int(audio_entries.get('channels'))
|
||||
bit_rate = int(audio_entries.get('bit_rate'))
|
||||
bit_rate = int(format_entries.get('bit_rate'))
|
||||
|
||||
audio_metadata : AudioMetadata =\
|
||||
{
|
||||
@@ -52,15 +90,22 @@ def extract_audio_metadata(audio_path : str) -> AudioMetadata:
|
||||
return audio_metadata
|
||||
|
||||
|
||||
def extract_video_metadata(video_path : str) -> VideoMetadata:
|
||||
video_entries = probe_entries(video_path, [ 'duration', 'width', 'height', 'r_frame_rate', 'bit_rate' ])
|
||||
@lru_cache(maxsize = 128)
|
||||
def extract_static_video_metadata(video_path : str) -> VideoMetadata:
|
||||
return extract_video_metadata(video_path)
|
||||
|
||||
duration = float(video_entries.get('duration'))
|
||||
|
||||
def extract_video_metadata(video_path : str) -> VideoMetadata:
|
||||
video_entries = probe_video_entries(video_path, [ 'width', 'height', 'r_frame_rate', 'color_transfer' ])
|
||||
format_entries = probe_format_entries(video_path, [ 'duration', 'bit_rate' ])
|
||||
|
||||
duration = float(format_entries.get('duration'))
|
||||
fps = extract_video_fps(video_entries.get('r_frame_rate'))
|
||||
frame_total = int(duration * fps)
|
||||
frame_total = round(duration * fps)
|
||||
width = int(video_entries.get('width'))
|
||||
height = int(video_entries.get('height'))
|
||||
bit_rate = int(video_entries.get('bit_rate'))
|
||||
bit_rate = int(format_entries.get('bit_rate'))
|
||||
color_transfer = video_entries.get('color_transfer', 'unknown')
|
||||
|
||||
video_metadata : VideoMetadata =\
|
||||
{
|
||||
@@ -68,7 +113,8 @@ def extract_video_metadata(video_path : str) -> VideoMetadata:
|
||||
'frame_total' : frame_total,
|
||||
'fps' : fps,
|
||||
'resolution' : (width, height),
|
||||
'bit_rate' : bit_rate
|
||||
'bit_rate' : bit_rate,
|
||||
'color_transfer' : color_transfer
|
||||
}
|
||||
|
||||
return video_metadata
|
||||
|
||||
@@ -13,10 +13,18 @@ def chain(*commands : List[Command]) -> List[Command]:
|
||||
return list(itertools.chain(*commands))
|
||||
|
||||
|
||||
def show_entries(entries : List[str]) -> List[Command]:
|
||||
def select_stream(stream : str) -> List[Command]:
|
||||
return [ '-select_streams', stream ]
|
||||
|
||||
|
||||
def show_stream_entries(entries : List[str]) -> List[Command]:
|
||||
return [ '-show_entries', 'stream=' + ','.join(entries) ]
|
||||
|
||||
|
||||
def show_format_entries(entries : List[str]) -> List[Command]:
|
||||
return [ '-show_entries', 'format=' + ','.join(entries) ]
|
||||
|
||||
|
||||
def format_to_key_value() -> List[Command]:
|
||||
return [ '-of', 'default=noprint_wrappers=1' ]
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from facefusion.types import FrameStoreSet, VisionFrame, VisionFrameSet
|
||||
|
||||
FRAME_STORE_SET : FrameStoreSet = {}
|
||||
|
||||
|
||||
def get_frame_store(id : str) -> VisionFrameSet:
|
||||
if id not in FRAME_STORE_SET:
|
||||
FRAME_STORE_SET[id] = {}
|
||||
|
||||
return FRAME_STORE_SET.get(id)
|
||||
|
||||
|
||||
def set_frame(id : str, frame_number : int, vision_frame : VisionFrame) -> None:
|
||||
frame_store = get_frame_store(id)
|
||||
frame_store[frame_number] = vision_frame
|
||||
|
||||
|
||||
def select_frame_set(id : str, frame_start : int, frame_end : int) -> VisionFrameSet:
|
||||
frame_store = get_frame_store(id)
|
||||
frame_set = {}
|
||||
|
||||
for frame_number in range(frame_start, frame_end + 1):
|
||||
if frame_number in frame_store:
|
||||
frame_set[frame_number] = frame_store.get(frame_number)
|
||||
|
||||
return frame_set
|
||||
|
||||
|
||||
def reduce_frames(id : str, frame_min : int, frame_max : int) -> None:
|
||||
FRAME_STORE_SET[id] = select_frame_set(id, frame_min, frame_max)
|
||||
|
||||
|
||||
def clear_frames(id : str) -> None:
|
||||
if id in FRAME_STORE_SET:
|
||||
del FRAME_STORE_SET[id]
|
||||
@@ -9,7 +9,7 @@ from onnxruntime import InferenceSession
|
||||
from facefusion import logger, process_manager, state_manager, translator
|
||||
from facefusion.app_context import detect_app_context
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.execution import create_inference_providers, has_execution_provider
|
||||
from facefusion.execution import create_inference_providers, get_onnxruntime_version, has_execution_provider
|
||||
from facefusion.exit_helper import fatal_exit
|
||||
from facefusion.filesystem import get_file_name, is_file
|
||||
from facefusion.time_helper import calculate_end_time
|
||||
@@ -25,17 +25,21 @@ INFERENCE_POOL_SET : InferencePoolSet =\
|
||||
def get_inference_pool(module_name : str, model_names : List[str], model_source_set : DownloadSet) -> InferencePool:
|
||||
while process_manager.is_checking():
|
||||
sleep(0.5)
|
||||
|
||||
execution_device_ids = state_manager.get_item('execution_device_ids')
|
||||
execution_providers = state_manager.get_item('execution_providers')
|
||||
has_arena_leak = has_execution_provider('cuda') and get_onnxruntime_version() > (1, 24, 4)
|
||||
app_context = detect_app_context()
|
||||
|
||||
for execution_device_id in execution_device_ids:
|
||||
inference_context = get_inference_context(module_name, model_names, execution_device_id, execution_providers)
|
||||
|
||||
if app_context == 'cli' and INFERENCE_POOL_SET.get('api').get(inference_context):
|
||||
INFERENCE_POOL_SET['cli'][inference_context] = INFERENCE_POOL_SET.get('api').get(inference_context)
|
||||
if app_context == 'api' and INFERENCE_POOL_SET.get('cli').get(inference_context):
|
||||
INFERENCE_POOL_SET['api'][inference_context] = INFERENCE_POOL_SET.get('cli').get(inference_context)
|
||||
if not has_arena_leak:
|
||||
if app_context == 'cli' and INFERENCE_POOL_SET.get('api').get(inference_context):
|
||||
INFERENCE_POOL_SET['cli'][inference_context] = INFERENCE_POOL_SET.get('api').get(inference_context)
|
||||
if app_context == 'api' and INFERENCE_POOL_SET.get('cli').get(inference_context):
|
||||
INFERENCE_POOL_SET['api'][inference_context] = INFERENCE_POOL_SET.get('cli').get(inference_context)
|
||||
|
||||
if not INFERENCE_POOL_SET.get(app_context).get(inference_context):
|
||||
inference_providers = resolve_static_inference_providers(module_name, execution_device_id)
|
||||
INFERENCE_POOL_SET[app_context][inference_context] = create_inference_pool(model_source_set, inference_providers)
|
||||
@@ -49,6 +53,7 @@ def create_inference_pool(model_source_set : DownloadSet, inference_providers :
|
||||
|
||||
for model_name in model_source_set.keys():
|
||||
model_path = model_source_set.get(model_name).get('path')
|
||||
|
||||
if is_file(model_path):
|
||||
inference_pool[model_name] = create_inference_session(model_path, inference_providers)
|
||||
|
||||
@@ -65,6 +70,7 @@ def clear_inference_pool(module_name : str, model_names : List[str]) -> None:
|
||||
|
||||
for execution_device_id in execution_device_ids:
|
||||
inference_context = get_inference_context(module_name, model_names, execution_device_id, execution_providers)
|
||||
|
||||
if INFERENCE_POOL_SET.get(app_context).get(inference_context):
|
||||
del INFERENCE_POOL_SET[app_context][inference_context]
|
||||
|
||||
@@ -93,10 +99,23 @@ def resolve_static_inference_providers(module_name : str, execution_device_id :
|
||||
module = importlib.import_module(module_name)
|
||||
execution_providers = state_manager.get_item('execution_providers')
|
||||
|
||||
if hasattr(module, 'resolve_inference_providers'):
|
||||
inference_providers = getattr(module, 'resolve_inference_providers')()
|
||||
if hasattr(module, 'override_inference_providers'):
|
||||
override_inference_providers = getattr(module, 'override_inference_providers')()
|
||||
|
||||
if override_inference_providers:
|
||||
return override_inference_providers
|
||||
|
||||
if hasattr(module, 'adjust_inference_providers'):
|
||||
adjust_inference_providers = getattr(module, 'adjust_inference_providers')()
|
||||
|
||||
if adjust_inference_providers:
|
||||
inference_providers = create_inference_providers(execution_device_id, execution_providers)
|
||||
|
||||
for adjust_inference_provider in adjust_inference_providers:
|
||||
for inference_provider in inference_providers:
|
||||
if inference_provider[0] == adjust_inference_provider[0] and inference_provider[1]:
|
||||
inference_provider[1].update(adjust_inference_provider[1])
|
||||
|
||||
if inference_providers:
|
||||
return inference_providers
|
||||
|
||||
return create_inference_providers(execution_device_id, execution_providers)
|
||||
|
||||
+10
-8
@@ -19,17 +19,18 @@ LOCALES =\
|
||||
}
|
||||
ONNXRUNTIME_SET =\
|
||||
{
|
||||
'default': ('onnxruntime', '1.26.0')
|
||||
'default': ('onnxruntime', '1.28.0')
|
||||
}
|
||||
if is_windows() or is_linux():
|
||||
ONNXRUNTIME_SET['cuda'] = ('onnxruntime-gpu', '1.26.0')
|
||||
ONNXRUNTIME_SET['cuda@12'] = ('onnxruntime-gpu', '1.24.4')
|
||||
ONNXRUNTIME_SET['cuda@13'] = ('onnxruntime-gpu', '1.28.0')
|
||||
ONNXRUNTIME_SET['openvino'] = ('onnxruntime-openvino', '1.24.1')
|
||||
if is_windows():
|
||||
ONNXRUNTIME_SET['directml'] = ('onnxruntime-directml', '1.24.4')
|
||||
ONNXRUNTIME_SET['qnn'] = ('onnxruntime-qnn', '1.24.4')
|
||||
ONNXRUNTIME_SET['qnn'] = ('onnxruntime-qnn', '2.4.0')
|
||||
if is_linux():
|
||||
ONNXRUNTIME_SET['migraphx'] = ('onnxruntime-migraphx', '1.25.0')
|
||||
ONNXRUNTIME_SET['rocm'] = ('onnxruntime-rocm', '1.22.2.post1')
|
||||
ONNXRUNTIME_SET['migraphx'] = ('onnxruntime-migraphx', '1.27.1')
|
||||
ONNXRUNTIME_SET['rocm'] = ('onnxruntime-rocm', '1.22.2.post3')
|
||||
|
||||
|
||||
def cli() -> None:
|
||||
@@ -54,6 +55,9 @@ def run(program : ArgumentParser) -> None:
|
||||
sys.stdout.write(LOCALES.get('conda_not_activated') + os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
for onnxruntime_package, _ in ONNXRUNTIME_SET.values():
|
||||
subprocess.call([ shutil.which('pip'), 'uninstall', onnxruntime_package, '-y', '-q' ], stderr = subprocess.DEVNULL)
|
||||
|
||||
commands = [ shutil.which('pip'), 'install' ]
|
||||
|
||||
if args.force_reinstall:
|
||||
@@ -63,12 +67,10 @@ def run(program : ArgumentParser) -> None:
|
||||
|
||||
for line in file.readlines():
|
||||
__line__ = line.strip()
|
||||
|
||||
if not __line__.startswith('onnxruntime'):
|
||||
commands.append(__line__)
|
||||
|
||||
onnxruntime_name, onnxruntime_version = ONNXRUNTIME_SET.get(args.onnxruntime)
|
||||
commands.append(onnxruntime_name + '==' + onnxruntime_version)
|
||||
|
||||
subprocess.call([ shutil.which('pip'), 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ])
|
||||
|
||||
subprocess.call(commands)
|
||||
|
||||
@@ -6,8 +6,7 @@ LOCALES : Locales =\
|
||||
{
|
||||
'conda_not_activated': 'conda is not activated',
|
||||
'python_not_supported': 'python version is not supported, upgrade to {version} or higher',
|
||||
'curl_not_installed': 'curl is not installed',
|
||||
'ffmpeg_not_installed': 'ffmpeg is not installed',
|
||||
'dependency_not_installed': '{dependency} is not installed',
|
||||
'creating_temp': 'creating temporary resources',
|
||||
'extracting_frames': 'extracting frames with a resolution of {resolution} and {fps} frames per second',
|
||||
'extracting_frames_succeeded': 'extracting frames succeeded',
|
||||
@@ -104,6 +103,7 @@ LOCALES : Locales =\
|
||||
'install_dependency': 'choose the variant of {dependency} to install',
|
||||
'skip_conda': 'skip the conda environment check',
|
||||
'workflow_mode': 'choose the workflow mode',
|
||||
'workflow_strategy': 'process the temporary frames in memory or on disk',
|
||||
'config_path': 'choose the config file to override defaults',
|
||||
'temp_path': 'specify the directory for the temporary resources',
|
||||
'jobs_path': 'specify the directory to store jobs',
|
||||
@@ -141,6 +141,7 @@ LOCALES : Locales =\
|
||||
'trim_frame_start': 'specify the starting frame of the target video',
|
||||
'trim_frame_end': 'specify the ending frame of the target video',
|
||||
'temp_frame_format': 'specify the temporary resources format',
|
||||
'temp_pixel_format': 'specify the temporary pixel format',
|
||||
'target_frame_amount': 'specify the amount of target frames forwarded to the processor',
|
||||
'output_image_quality': 'specify the image quality which translates to the image compression',
|
||||
'output_image_scale': 'specify the image scale based on the target image',
|
||||
|
||||
@@ -23,7 +23,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import match_frame_color, read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import match_frame_color, read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -180,7 +180,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -22,7 +22,7 @@ from facefusion.program_helper import find_argument_group
|
||||
from facefusion.sanitizer import sanitize_int_range
|
||||
from facefusion.thread_helper import thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, InferencePool, InferenceProvider, Mask, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -479,7 +479,7 @@ def clear_inference_pool() -> None:
|
||||
inference_manager.clear_inference_pool(__name__, model_names)
|
||||
|
||||
|
||||
def resolve_inference_providers() -> List[InferenceProvider]:
|
||||
def override_inference_providers() -> List[InferenceProvider]:
|
||||
model_type = get_model_options().get('type')
|
||||
|
||||
if is_macos() and has_execution_provider('coreml') or is_windows() and has_execution_provider('directml') and model_type == 'corridor_key':
|
||||
@@ -558,7 +558,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -23,7 +23,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Face, InferencePool, Mask, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import conditional_match_frame_color, read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import conditional_match_frame_color, read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -334,7 +334,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -23,7 +23,7 @@ from facefusion.processors.types import ApplyStateItem, LivePortraitExpression,
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -167,7 +167,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -19,7 +19,7 @@ from facefusion.processors.modules.face_debugger.types import FaceDebuggerInputs
|
||||
from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.types import Args, Face, InferencePool, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
def get_inference_pool() -> InferencePool:
|
||||
@@ -77,7 +77,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') == 'strict':
|
||||
|
||||
@@ -23,7 +23,7 @@ from facefusion.processors.types import ApplyStateItem, LivePortraitExpression,
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Face, FaceLandmark68, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -302,7 +302,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -21,7 +21,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import blend_frame, read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import blend_frame, read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -358,7 +358,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -26,7 +26,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import conditional_thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, Embedding, Face, InferencePool, InferenceProvider, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_images, read_static_video_chunk, read_static_video_frame, unpack_resolution
|
||||
from facefusion.vision import read_static_image, read_static_images, read_static_video_frame, unpack_resolution
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -502,20 +502,19 @@ def clear_inference_pool() -> None:
|
||||
inference_manager.clear_inference_pool(__name__, model_names)
|
||||
|
||||
|
||||
def resolve_inference_providers() -> List[InferenceProvider]:
|
||||
def adjust_inference_providers() -> List[InferenceProvider]:
|
||||
model_precision = get_model_options().get('precision')
|
||||
model_type = get_model_options().get('type')
|
||||
workflow_mode = state_manager.get_item('workflow_mode')
|
||||
|
||||
if is_macos() and has_execution_provider('coreml'):
|
||||
if model_type in [ 'ghost', 'uniface' ] or model_precision == 'fp16':
|
||||
return\
|
||||
[
|
||||
(facefusion.choices.execution_provider_set.get('coreml'),
|
||||
{
|
||||
'ModelFormat': 'MLProgram',
|
||||
'SpecializationStrategy': 'FastPrediction'
|
||||
})
|
||||
]
|
||||
if is_macos() and has_execution_provider('coreml') and model_precision == 'fp16' and workflow_mode == 'image-to-video':
|
||||
return\
|
||||
[
|
||||
(facefusion.choices.execution_provider_set.get('coreml'),
|
||||
{
|
||||
'ModelFormat': 'MLProgram',
|
||||
'MLComputeUnits': 'CPUAndGPU'
|
||||
})
|
||||
]
|
||||
|
||||
return []
|
||||
|
||||
@@ -610,7 +609,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -20,7 +20,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, InferencePool, InferenceProvider, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import blend_frame, read_static_image, read_static_video_chunk, read_static_video_frame, unpack_resolution
|
||||
from facefusion.vision import blend_frame, read_static_image, read_static_video_frame, unpack_resolution
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -172,7 +172,7 @@ def clear_inference_pool() -> None:
|
||||
inference_manager.clear_inference_pool(__name__, model_names)
|
||||
|
||||
|
||||
def resolve_inference_providers() -> List[InferenceProvider]:
|
||||
def override_inference_providers() -> List[InferenceProvider]:
|
||||
if is_macos() and has_execution_provider('coreml'):
|
||||
return [ facefusion.choices.execution_provider_set.get('cpu') ]
|
||||
|
||||
@@ -250,7 +250,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -20,7 +20,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import conditional_thread_semaphore
|
||||
from facefusion.types import Args, DownloadScope, InferencePool, InferenceProvider, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import blend_frame, create_tile_frames, merge_tile_frames, read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import blend_frame, create_tile_frames, merge_tile_frames, read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -558,7 +558,7 @@ def clear_inference_pool() -> None:
|
||||
inference_manager.clear_inference_pool(__name__, model_names)
|
||||
|
||||
|
||||
def resolve_inference_providers() -> List[InferenceProvider]:
|
||||
def adjust_inference_providers() -> List[InferenceProvider]:
|
||||
model_precision = get_model_options().get('precision')
|
||||
|
||||
if is_macos() and has_execution_provider('coreml') and model_precision == 'fp16':
|
||||
@@ -566,8 +566,7 @@ def resolve_inference_providers() -> List[InferenceProvider]:
|
||||
[
|
||||
(facefusion.choices.execution_provider_set.get('coreml'),
|
||||
{
|
||||
'ModelFormat': 'MLProgram',
|
||||
'SpecializationStrategy': 'FastPrediction'
|
||||
'ModelFormat': 'MLProgram'
|
||||
})
|
||||
]
|
||||
|
||||
@@ -637,7 +636,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
|
||||
|
||||
@@ -9,4 +9,4 @@ FrameEnhancerInputs = TypedDict('FrameEnhancerInputs',
|
||||
'temp_vision_mask' : Mask
|
||||
})
|
||||
|
||||
FrameEnhancerModel = Literal['clear_reality_x4', 'face_dat_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'tghq_face_x8', 'ultra_sharp_x4', 'ultra_sharp_2_x4']
|
||||
FrameEnhancerModel = Literal['clear_reality_x4', 'face_dat_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_esrgan_x8', 'real_esrgan_x8_fp16', 'real_hatgan_x4', 'real_web_photo_x4', 'realistic_rescaler_x4', 'remacri_x4', 'siax_x4', 'span_kendata_x4', 'swin2_sr_x4', 'tghq_face_x8', 'ultra_sharp_x4', 'ultra_sharp_2_x4']
|
||||
|
||||
@@ -23,7 +23,7 @@ from facefusion.processors.types import ApplyStateItem, ProcessorOutputs
|
||||
from facefusion.program_helper import find_argument_group
|
||||
from facefusion.thread_helper import conditional_thread_semaphore
|
||||
from facefusion.types import Args, AudioFrame, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
|
||||
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_image, read_static_video_frame
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -185,7 +185,6 @@ def pre_process(mode : ProcessMode) -> bool:
|
||||
def post_process() -> None:
|
||||
read_static_image.cache_clear()
|
||||
read_static_video_frame.cache_clear()
|
||||
read_static_video_chunk.cache_clear()
|
||||
read_static_voice.cache_clear()
|
||||
video_manager.clear_video_pool()
|
||||
|
||||
|
||||
+25
-6
@@ -41,11 +41,11 @@ def create_config_path_program() -> ArgumentParser:
|
||||
|
||||
def create_workflow_program() -> ArgumentParser:
|
||||
program = ArgumentParser(add_help = False)
|
||||
group_paths = program.add_argument_group('paths')
|
||||
group_workflow = program.add_argument_group('workflow')
|
||||
|
||||
capability_store.register_capability_set(
|
||||
[
|
||||
group_paths.add_argument(
|
||||
group_workflow.add_argument(
|
||||
'--workflow-mode',
|
||||
help = translator.get('help.workflow_mode'),
|
||||
default = config.get_str_value('workflow', 'workflow_mode', 'auto'),
|
||||
@@ -54,6 +54,17 @@ def create_workflow_program() -> ArgumentParser:
|
||||
],
|
||||
scopes = [ 'api', 'cli' ]
|
||||
)
|
||||
capability_store.register_capability_set(
|
||||
[
|
||||
group_workflow.add_argument(
|
||||
'--workflow-strategy',
|
||||
help = translator.get('help.workflow_strategy'),
|
||||
default = config.get_str_value('workflow', 'workflow_strategy', 'memory'),
|
||||
choices = facefusion.choices.workflow_strategies
|
||||
)
|
||||
],
|
||||
scopes = [ 'api', 'cli' ]
|
||||
)
|
||||
|
||||
return program
|
||||
|
||||
@@ -592,6 +603,17 @@ def create_frame_extraction_program() -> ArgumentParser:
|
||||
],
|
||||
scopes = [ 'api', 'cli' ]
|
||||
)
|
||||
capability_store.register_capability_set(
|
||||
[
|
||||
group_frame_extraction.add_argument(
|
||||
'--temp-pixel-format',
|
||||
help = translator.get('help.temp_pixel_format'),
|
||||
default = config.get_str_value('frame_extraction', 'temp_pixel_format', 'bgr24'),
|
||||
choices = facefusion.choices.temp_pixel_formats
|
||||
)
|
||||
],
|
||||
scopes = [ 'api', 'cli' ]
|
||||
)
|
||||
|
||||
return program
|
||||
|
||||
@@ -1033,6 +1055,7 @@ def collect_step_program() -> ArgumentParser:
|
||||
create_frame_extraction_program(),
|
||||
create_frame_distribution_program(),
|
||||
create_output_creation_program(),
|
||||
create_workflow_program(),
|
||||
create_processors_program()
|
||||
],
|
||||
add_help = False
|
||||
@@ -1043,7 +1066,6 @@ def collect_job_program() -> ArgumentParser:
|
||||
return ArgumentParser(
|
||||
parents =
|
||||
[
|
||||
create_workflow_program(),
|
||||
create_execution_program(),
|
||||
create_download_providers_program(),
|
||||
create_memory_program(),
|
||||
@@ -1205,7 +1227,6 @@ def create_program() -> ArgumentParser:
|
||||
parents =
|
||||
[
|
||||
create_job_id_program(),
|
||||
create_workflow_program(),
|
||||
create_config_path_program(),
|
||||
create_jobs_path_program(),
|
||||
create_source_paths_program(),
|
||||
@@ -1222,7 +1243,6 @@ def create_program() -> ArgumentParser:
|
||||
parents =
|
||||
[
|
||||
create_job_id_program(),
|
||||
create_workflow_program(),
|
||||
create_step_index_program(),
|
||||
create_config_path_program(),
|
||||
create_jobs_path_program(),
|
||||
@@ -1239,7 +1259,6 @@ def create_program() -> ArgumentParser:
|
||||
parents =
|
||||
[
|
||||
create_job_id_program(),
|
||||
create_workflow_program(),
|
||||
create_step_index_program(),
|
||||
create_config_path_program(),
|
||||
create_jobs_path_program(),
|
||||
|
||||
@@ -2,10 +2,9 @@ import os
|
||||
import subprocess
|
||||
from collections import deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Deque, Iterator
|
||||
from typing import Deque, Iterator, List
|
||||
|
||||
import cv2
|
||||
import numpy
|
||||
from tqdm import tqdm
|
||||
|
||||
from facefusion import ffmpeg_builder, logger, state_manager, translator
|
||||
@@ -15,11 +14,12 @@ from facefusion.ffmpeg import open_ffmpeg
|
||||
from facefusion.filesystem import is_directory
|
||||
from facefusion.processors.core import get_processors_modules
|
||||
from facefusion.types import Buffer, Fps, StreamMode, VisionFrame
|
||||
from facefusion.vision import extract_vision_mask, read_static_images
|
||||
from facefusion.vision import extract_vision_mask, is_vision_frame, read_static_images
|
||||
|
||||
|
||||
def multi_process_capture(camera_capture : cv2.VideoCapture, camera_fps : Fps) -> Iterator[VisionFrame]:
|
||||
capture_deque : Deque[VisionFrame] = deque()
|
||||
source_vision_frames = read_static_images(state_manager.get_item('source_paths'))
|
||||
|
||||
with tqdm(desc = translator.get('streaming'), unit = 'frame', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
|
||||
with ThreadPoolExecutor(max_workers = state_manager.get_item('execution_thread_count')) as executor:
|
||||
@@ -30,8 +30,8 @@ def multi_process_capture(camera_capture : cv2.VideoCapture, camera_fps : Fps) -
|
||||
if analyse_stream(capture_vision_frame, camera_fps):
|
||||
camera_capture.release()
|
||||
|
||||
if numpy.any(capture_vision_frame):
|
||||
future = executor.submit(process_stream_frame, capture_vision_frame)
|
||||
if is_vision_frame(capture_vision_frame):
|
||||
future = executor.submit(process_stream_frame, source_vision_frames, capture_vision_frame)
|
||||
futures.append(future)
|
||||
|
||||
for future_done in [ future for future in futures if future.done() ]:
|
||||
@@ -44,8 +44,7 @@ def multi_process_capture(camera_capture : cv2.VideoCapture, camera_fps : Fps) -
|
||||
yield capture_deque.popleft()
|
||||
|
||||
|
||||
def process_stream_frame(target_vision_frame : VisionFrame) -> VisionFrame:
|
||||
source_vision_frames = read_static_images(state_manager.get_item('source_paths'))
|
||||
def process_stream_frame(source_vision_frames : List[VisionFrame], target_vision_frame : VisionFrame) -> VisionFrame:
|
||||
source_audio_frame = create_empty_audio_frame()
|
||||
source_voice_frame = create_empty_audio_frame()
|
||||
temp_vision_frame = target_vision_frame.copy()
|
||||
|
||||
@@ -2,6 +2,7 @@ import os
|
||||
from typing import List
|
||||
|
||||
from facefusion.filesystem import create_directory, get_file_extension, get_file_name, move_file, remove_directory, resolve_file_pattern
|
||||
from facefusion.types import FrameSet
|
||||
|
||||
|
||||
def get_temp_file_path(temp_path : str, output_path : str) -> str:
|
||||
@@ -20,6 +21,17 @@ def resolve_temp_frame_paths(temp_path : str, output_path : str, temp_frame_form
|
||||
return resolve_file_pattern(temp_frames_pattern)
|
||||
|
||||
|
||||
def resolve_temp_frame_set(temp_path : str, output_path : str, temp_frame_format : str) -> FrameSet:
|
||||
temp_frames_pattern = get_temp_frames_pattern(temp_path, output_path, temp_frame_format, '*')
|
||||
temp_frame_set = {}
|
||||
|
||||
for temp_frame_path in resolve_file_pattern(temp_frames_pattern):
|
||||
frame_number = int(get_file_name(temp_frame_path))
|
||||
temp_frame_set[frame_number] = temp_frame_path
|
||||
|
||||
return temp_frame_set
|
||||
|
||||
|
||||
def get_temp_frames_pattern(temp_path : str, output_path : str, temp_frame_format : str, temp_frame_prefix : str) -> str:
|
||||
temp_directory_path = get_temp_directory_path(temp_path, output_path)
|
||||
return os.path.join(temp_directory_path, temp_frame_prefix + '.' + temp_frame_format)
|
||||
|
||||
+43
-10
@@ -1,4 +1,5 @@
|
||||
import ctypes
|
||||
import subprocess
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
@@ -66,22 +67,19 @@ Locales : TypeAlias = Dict[Language, Dict[str, Any]]
|
||||
LocalePoolSet : TypeAlias = Dict[str, Locales]
|
||||
|
||||
WorkflowMode = Literal['auto', 'audio-to-image:frames', 'audio-to-image:video', 'image-to-image', 'image-to-video', 'image-to-video:frames']
|
||||
WorkflowStrategy = Literal['disk', 'memory']
|
||||
|
||||
VideoCaptureSet : TypeAlias = Dict[str, cv2.VideoCapture]
|
||||
VideoWriterSet : TypeAlias = Dict[str, cv2.VideoWriter]
|
||||
CameraCaptureSet : TypeAlias = Dict[str, cv2.VideoCapture]
|
||||
VideoPoolSet = TypedDict('VideoPoolSet',
|
||||
{
|
||||
'capture' : VideoCaptureSet,
|
||||
'writer' : VideoWriterSet
|
||||
})
|
||||
CameraPoolSet = TypedDict('CameraPoolSet',
|
||||
{
|
||||
'capture' : CameraCaptureSet
|
||||
})
|
||||
|
||||
ColorMode = Literal['rgb', 'rgba']
|
||||
ColorSpace = Literal['bt601', 'bt709', 'bt2020']
|
||||
ColorTransfer : TypeAlias = str
|
||||
VisionFrame : TypeAlias = NDArray[Any]
|
||||
VisionFrameSet : TypeAlias = Dict[int, VisionFrame]
|
||||
Mask : TypeAlias = NDArray[Any]
|
||||
Points : TypeAlias = NDArray[Any]
|
||||
Distance : TypeAlias = NDArray[Any]
|
||||
@@ -197,6 +195,7 @@ AudioFormat = Literal['flac', 'm4a', 'mp3', 'ogg', 'opus', 'wav']
|
||||
ImageFormat = Literal['bmp', 'jpeg', 'png', 'tiff', 'webp']
|
||||
VideoFormat = Literal['avi', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mxf', 'webm', 'wmv']
|
||||
TempFrameFormat = Literal['bmp', 'jpeg', 'png', 'tiff']
|
||||
TempPixelFormat = Literal['bgr24', 'bgra']
|
||||
|
||||
FrameSet : TypeAlias = Dict[int, str]
|
||||
|
||||
@@ -235,8 +234,38 @@ VideoMetadata = TypedDict('VideoMetadata',
|
||||
'frame_total' : int,
|
||||
'fps' : Fps,
|
||||
'resolution' : Resolution,
|
||||
'bit_rate' : BitRate
|
||||
'bit_rate' : BitRate,
|
||||
'color_transfer' : ColorTransfer
|
||||
})
|
||||
VideoReaderMetadata : TypeAlias = VideoMetadata
|
||||
VideoWriterMetadata = TypedDict('VideoWriterMetadata',
|
||||
{
|
||||
'fps' : Fps,
|
||||
'resolution' : Resolution
|
||||
})
|
||||
VideoReader = TypedDict('VideoReader',
|
||||
{
|
||||
'id' : str,
|
||||
'file_path' : str,
|
||||
'process' : subprocess.Popen[bytes],
|
||||
'metadata' : VideoReaderMetadata,
|
||||
'frame_number' : int
|
||||
})
|
||||
VideoReaderSet : TypeAlias = Dict[str, VideoReader]
|
||||
VideoWriter = TypedDict('VideoWriter',
|
||||
{
|
||||
'id' : str,
|
||||
'file_path' : str,
|
||||
'process' : subprocess.Popen[bytes],
|
||||
'metadata' : VideoWriterMetadata
|
||||
})
|
||||
VideoWriterSet : TypeAlias = Dict[str, VideoWriter]
|
||||
VideoPoolSet = TypedDict('VideoPoolSet',
|
||||
{
|
||||
'reader' : VideoReaderSet,
|
||||
'writer' : VideoWriterSet
|
||||
})
|
||||
FrameStoreSet : TypeAlias = Dict[str, VisionFrameSet]
|
||||
AudioAsset = TypedDict('AudioAsset',
|
||||
{
|
||||
'id' : AssetId,
|
||||
@@ -475,7 +504,6 @@ StateValue : TypeAlias = Any
|
||||
StateKey = Literal\
|
||||
[
|
||||
'command',
|
||||
'workflow_mode',
|
||||
'config_path',
|
||||
'temp_path',
|
||||
'jobs_path',
|
||||
@@ -518,6 +546,7 @@ StateKey = Literal\
|
||||
'trim_frame_start',
|
||||
'trim_frame_end',
|
||||
'temp_frame_format',
|
||||
'temp_pixel_format',
|
||||
'keep_temp',
|
||||
'target_frame_amount',
|
||||
'output_image_quality',
|
||||
@@ -531,6 +560,8 @@ StateKey = Literal\
|
||||
'output_video_quality',
|
||||
'output_video_scale',
|
||||
'output_video_fps',
|
||||
'workflow_mode',
|
||||
'workflow_strategy',
|
||||
'processors',
|
||||
'execution_device_ids',
|
||||
'execution_providers',
|
||||
@@ -549,7 +580,6 @@ StateKey = Literal\
|
||||
State = TypedDict('State',
|
||||
{
|
||||
'command' : str,
|
||||
'workflow_mode' : WorkflowMode,
|
||||
'config_path' : str,
|
||||
'temp_path' : str,
|
||||
'jobs_path' : str,
|
||||
@@ -592,6 +622,7 @@ State = TypedDict('State',
|
||||
'trim_frame_start' : int,
|
||||
'trim_frame_end' : int,
|
||||
'temp_frame_format' : TempFrameFormat,
|
||||
'temp_pixel_format' : TempPixelFormat,
|
||||
'keep_temp' : bool,
|
||||
'target_frame_amount' : int,
|
||||
'output_image_quality' : int,
|
||||
@@ -605,6 +636,8 @@ State = TypedDict('State',
|
||||
'output_video_scale' : Scale,
|
||||
'output_video_fps' : float,
|
||||
'output_audio_fps' : float,
|
||||
'workflow_mode' : WorkflowMode,
|
||||
'workflow_strategy' : WorkflowStrategy,
|
||||
'processors' : List[str],
|
||||
'execution_device_ids' : List[int],
|
||||
'execution_providers' : List[ExecutionProvider],
|
||||
|
||||
+131
-22
@@ -1,46 +1,155 @@
|
||||
import cv2
|
||||
import hashlib
|
||||
import uuid
|
||||
from io import BufferedReader
|
||||
from typing import Optional, cast
|
||||
|
||||
from facefusion.types import VideoPoolSet
|
||||
import numpy
|
||||
|
||||
from facefusion import ffmpeg, ffprobe, frame_store, vision
|
||||
from facefusion.common_helper import get_first, get_last
|
||||
from facefusion.types import Fps, Resolution, VideoPoolSet, VideoReader, VideoWriter, VisionFrame, VisionFrameSet
|
||||
|
||||
VIDEO_POOL_SET : VideoPoolSet =\
|
||||
{
|
||||
'capture': {},
|
||||
'reader': {},
|
||||
'writer': {}
|
||||
}
|
||||
|
||||
|
||||
def get_video_capture(video_path : str) -> cv2.VideoCapture:
|
||||
if video_path not in VIDEO_POOL_SET.get('capture'):
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
def get_reader(video_path : str, context : str) -> VideoReader:
|
||||
reader_id = hashlib.sha1((video_path + '_' + context).encode()).hexdigest()
|
||||
|
||||
if video_capture.isOpened():
|
||||
VIDEO_POOL_SET['capture'][video_path] = video_capture
|
||||
if reader_id not in VIDEO_POOL_SET.get('reader'):
|
||||
video_metadata = ffprobe.extract_static_video_metadata(video_path)
|
||||
|
||||
return VIDEO_POOL_SET.get('capture').get(video_path)
|
||||
VIDEO_POOL_SET['reader'][reader_id] =\
|
||||
{
|
||||
'id': reader_id,
|
||||
'file_path': video_path,
|
||||
'process': ffmpeg.create_video_reader(video_path, 0, video_metadata),
|
||||
'metadata': video_metadata,
|
||||
'frame_number': 0
|
||||
}
|
||||
|
||||
return VIDEO_POOL_SET.get('reader').get(reader_id)
|
||||
|
||||
|
||||
def conditional_set_video_frame_position(video_capture : cv2.VideoCapture, frame_position : int) -> bool:
|
||||
if not video_capture.get(cv2.CAP_PROP_POS_FRAMES) == frame_position:
|
||||
return video_capture.set(cv2.CAP_PROP_POS_FRAMES, frame_position)
|
||||
return True
|
||||
def conditional_seek_video_reader(video_reader : VideoReader, frame_number : int = 0) -> None:
|
||||
frame_total = video_reader.get('metadata').get('frame_total')
|
||||
frame_number = min(frame_total - 1, frame_number)
|
||||
skip_total = frame_number - video_reader.get('frame_number')
|
||||
skip_margin = 128
|
||||
|
||||
if 0 < skip_total <= skip_margin:
|
||||
drain_video_reader(video_reader, skip_total)
|
||||
|
||||
if not video_reader.get('frame_number') == frame_number:
|
||||
seek_video_reader(video_reader, frame_number)
|
||||
|
||||
|
||||
def get_video_writer(video_path : str) -> cv2.VideoWriter:
|
||||
def seek_video_reader(video_reader : VideoReader, frame_number : int = 0) -> None:
|
||||
close_video_reader(video_reader)
|
||||
|
||||
video_reader['process'] = ffmpeg.create_video_reader(video_reader.get('file_path'), frame_number, video_reader.get('metadata'))
|
||||
video_reader['frame_number'] = frame_number
|
||||
|
||||
|
||||
def drain_video_reader(video_reader : VideoReader, skip_total : int) -> None:
|
||||
width, height = video_reader.get('metadata').get('resolution')
|
||||
channel_total = 3
|
||||
frame_size = width * height * channel_total
|
||||
|
||||
for _ in range(skip_total):
|
||||
video_reader.get('process').stdout.read(frame_size)
|
||||
|
||||
video_reader['frame_number'] = video_reader.get('frame_number') + skip_total
|
||||
|
||||
|
||||
def read_video_frame(video_reader : VideoReader) -> Optional[VisionFrame]:
|
||||
width, height = video_reader.get('metadata').get('resolution')
|
||||
channel_total = 3
|
||||
video_stream = cast(BufferedReader, video_reader.get('process').stdout)
|
||||
vision_frame = numpy.empty(width * height * channel_total, numpy.uint8)
|
||||
|
||||
if video_stream.readinto(vision_frame) == vision_frame.size:
|
||||
video_reader['frame_number'] = video_reader.get('frame_number') + 1
|
||||
return vision_frame.reshape(height, width, channel_total)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def read_video_frames(video_reader : VideoReader, frame_start : int, frame_end : int) -> VisionFrameSet:
|
||||
reader_id = video_reader.get('id')
|
||||
frame_set = frame_store.get_frame_store(reader_id)
|
||||
keep_margin = 4
|
||||
frame_gaps = []
|
||||
|
||||
for frame_number in range(frame_start, frame_end + 1):
|
||||
if frame_number not in frame_set:
|
||||
frame_gaps.append(frame_number)
|
||||
|
||||
if frame_gaps:
|
||||
collect_video_frames(video_reader, get_first(frame_gaps), get_last(frame_gaps))
|
||||
|
||||
frame_store.reduce_frames(reader_id, frame_start - keep_margin, frame_end + keep_margin)
|
||||
return frame_store.select_frame_set(reader_id, frame_start, frame_end)
|
||||
|
||||
|
||||
def collect_video_frames(video_reader : VideoReader, frame_start : int, frame_end : int) -> None:
|
||||
reader_id = video_reader.get('id')
|
||||
skip_total = frame_start - video_reader.get('frame_number')
|
||||
skip_margin = 16
|
||||
|
||||
if skip_total < 0 or skip_total > skip_margin:
|
||||
seek_video_reader(video_reader, frame_start)
|
||||
|
||||
for frame_number in range(video_reader.get('frame_number'), frame_end + 1):
|
||||
vision_frame = read_video_frame(video_reader)
|
||||
|
||||
if vision.is_vision_frame(vision_frame):
|
||||
frame_store.set_frame(reader_id, frame_number, vision_frame)
|
||||
|
||||
|
||||
def close_video_reader(video_reader : VideoReader) -> None:
|
||||
video_reader.get('process').kill()
|
||||
video_reader.get('process').wait()
|
||||
|
||||
|
||||
def get_writer(video_path : str, temp_video_fps : Fps, temp_video_resolution : Resolution, output_video_resolution : Resolution, output_video_fps : Fps) -> VideoWriter:
|
||||
if video_path not in VIDEO_POOL_SET.get('writer'):
|
||||
video_writer = cv2.VideoWriter()
|
||||
|
||||
if video_writer.isOpened():
|
||||
VIDEO_POOL_SET['writer'][video_path] = video_writer
|
||||
VIDEO_POOL_SET['writer'][video_path] =\
|
||||
{
|
||||
'id': uuid.uuid4().hex,
|
||||
'file_path': video_path,
|
||||
'process': ffmpeg.create_video_writer(video_path, temp_video_fps, temp_video_resolution, output_video_resolution, output_video_fps),
|
||||
'metadata':
|
||||
{
|
||||
'fps': output_video_fps,
|
||||
'resolution': output_video_resolution
|
||||
}
|
||||
}
|
||||
|
||||
return VIDEO_POOL_SET.get('writer').get(video_path)
|
||||
|
||||
|
||||
def write_video_frame(video_writer : VideoWriter, vision_frame : VisionFrame) -> None:
|
||||
video_writer.get('process').stdin.write(vision_frame.data)
|
||||
|
||||
|
||||
def close_video_writer(video_writer : VideoWriter) -> bool:
|
||||
video_writer.get('process').stdin.close()
|
||||
video_writer.get('process').wait()
|
||||
|
||||
return video_writer.get('process').returncode == 0
|
||||
|
||||
|
||||
def clear_video_pool() -> None:
|
||||
for video_capture in VIDEO_POOL_SET.get('capture').values():
|
||||
video_capture.release()
|
||||
for video_reader in VIDEO_POOL_SET.get('reader').values():
|
||||
close_video_reader(video_reader)
|
||||
frame_store.clear_frames(video_reader.get('id'))
|
||||
|
||||
for video_writer in VIDEO_POOL_SET.get('writer').values():
|
||||
video_writer.release()
|
||||
close_video_writer(video_writer)
|
||||
|
||||
VIDEO_POOL_SET['capture'].clear()
|
||||
VIDEO_POOL_SET['reader'].clear()
|
||||
VIDEO_POOL_SET['writer'].clear()
|
||||
|
||||
+21
-64
@@ -1,17 +1,17 @@
|
||||
import math
|
||||
from functools import lru_cache
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy
|
||||
from cv2.typing import Size
|
||||
|
||||
from facefusion import ffprobe, video_manager
|
||||
from facefusion.common_helper import is_windows
|
||||
from facefusion.filesystem import get_file_extension, is_image, is_video
|
||||
from facefusion.media_helper import restrict_trim_frame
|
||||
from facefusion.thread_helper import thread_lock, thread_semaphore
|
||||
from facefusion.types import ColorMode, Duration, Fps, Mask, Orientation, Resolution, Scale, VisionFrame
|
||||
from facefusion.video_manager import conditional_set_video_frame_position, get_video_capture
|
||||
|
||||
|
||||
def read_static_images(image_paths : List[str], color_mode : ColorMode = 'rgb') -> List[VisionFrame]:
|
||||
@@ -78,61 +78,30 @@ def read_static_video_frame(video_path : str, frame_number : int = 0) -> Optiona
|
||||
|
||||
def read_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
video_reader = video_manager.get_reader(video_path, 'read_video_frame')
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video_frame_position = min(video_frame_total, frame_number)
|
||||
|
||||
with thread_semaphore():
|
||||
conditional_set_video_frame_position(video_capture, video_frame_position)
|
||||
has_vision_frame, vision_frame = video_capture.read()
|
||||
|
||||
if has_vision_frame:
|
||||
return vision_frame
|
||||
with thread_semaphore():
|
||||
video_manager.conditional_seek_video_reader(video_reader, frame_number)
|
||||
return video_manager.read_video_frame(video_reader)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize = 2)
|
||||
def read_static_video_chunk(video_path : str, chunk_number : int, chunk_size : int) -> Dict[int, VisionFrame]:
|
||||
return read_video_chunk(video_path, chunk_number, chunk_size)
|
||||
|
||||
|
||||
def read_video_chunk(video_path : str, chunk_number : int, chunk_size : int) -> Dict[int, VisionFrame]:
|
||||
video_frame_chunk = {}
|
||||
|
||||
if is_video(video_path) and chunk_number > -1:
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
video_frame_position = chunk_number * chunk_size
|
||||
|
||||
with thread_semaphore():
|
||||
conditional_set_video_frame_position(video_capture, video_frame_position)
|
||||
|
||||
for frame_number in range(video_frame_position, min(video_frame_position + chunk_size, video_frame_total)):
|
||||
has_vision_frame, vision_frame = video_capture.read()
|
||||
|
||||
if has_vision_frame:
|
||||
video_frame_chunk[frame_number] = vision_frame
|
||||
|
||||
return video_frame_chunk
|
||||
|
||||
|
||||
def select_video_frames(video_path : str, frame_number : int = 0, frame_offset : int = 2) -> List[VisionFrame]:
|
||||
vision_frames = []
|
||||
chunk_size = frame_offset * 2 + 1
|
||||
frame_start = frame_number - frame_offset
|
||||
frame_end = frame_number + frame_offset
|
||||
|
||||
if is_video(video_path):
|
||||
with thread_lock():
|
||||
for current_number in range(frame_number - frame_offset, frame_number + frame_offset + 1):
|
||||
video_frame_chunk = read_static_video_chunk(video_path, current_number // chunk_size, chunk_size)
|
||||
video_reader = video_manager.get_reader(video_path, 'select_video_frames')
|
||||
frame_set = video_manager.read_video_frames(video_reader, max(frame_start, 0), frame_end)
|
||||
|
||||
for frame_number in range(frame_start, frame_end + 1):
|
||||
vision_frame = create_empty_vision_frame()
|
||||
|
||||
if current_number in video_frame_chunk:
|
||||
vision_frame = video_frame_chunk.get(current_number)
|
||||
if frame_number in frame_set:
|
||||
vision_frame = frame_set.get(frame_number)
|
||||
|
||||
vision_frames.append(vision_frame)
|
||||
|
||||
@@ -141,12 +110,7 @@ def select_video_frames(video_path : str, frame_number : int = 0, frame_offset :
|
||||
|
||||
def count_video_frame_total(video_path : str) -> int:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
return video_frame_total
|
||||
return ffprobe.extract_static_video_metadata(video_path).get('frame_total')
|
||||
|
||||
return 0
|
||||
|
||||
@@ -162,12 +126,7 @@ def predict_video_frame_total(video_path : str, fps : Fps, trim_frame_start : in
|
||||
|
||||
def detect_video_fps(video_path : str) -> Optional[Fps]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
video_fps = video_capture.get(cv2.CAP_PROP_FPS)
|
||||
return video_fps
|
||||
return ffprobe.extract_static_video_metadata(video_path).get('fps')
|
||||
|
||||
return None
|
||||
|
||||
@@ -196,13 +155,7 @@ def restrict_trim_video_frame(video_path : str, trim_frame_start : Optional[int]
|
||||
|
||||
def detect_video_resolution(video_path : str) -> Optional[Resolution]:
|
||||
if is_video(video_path):
|
||||
video_capture = get_video_capture(video_path)
|
||||
|
||||
if video_capture and video_capture.isOpened():
|
||||
with thread_semaphore():
|
||||
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
return int(width), int(height)
|
||||
return ffprobe.extract_static_video_metadata(video_path).get('resolution')
|
||||
|
||||
return None
|
||||
|
||||
@@ -340,6 +293,10 @@ def create_empty_vision_frame() -> VisionFrame:
|
||||
return numpy.zeros((1, 1, 3)).astype(numpy.uint8)
|
||||
|
||||
|
||||
def is_vision_frame(vision_frame : VisionFrame) -> bool:
|
||||
return numpy.ndim(vision_frame) == 3
|
||||
|
||||
|
||||
def create_tile_frames(vision_frame : VisionFrame, size : Size) -> Tuple[List[VisionFrame], int, int]:
|
||||
tile_width = size[0] - 2 * size[2]
|
||||
pad_size_top = size[1] + size[2]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import List
|
||||
from collections import deque
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from typing import Deque, List
|
||||
|
||||
import numpy
|
||||
from tqdm import tqdm
|
||||
@@ -9,9 +10,9 @@ from facefusion.audio import create_empty_audio_frame, get_audio_frame, get_voic
|
||||
from facefusion.common_helper import get_first
|
||||
from facefusion.filesystem import filter_audio_paths
|
||||
from facefusion.processors.core import get_processors_modules
|
||||
from facefusion.temp_helper import clear_temp_directory, create_temp_directory, resolve_temp_frame_paths
|
||||
from facefusion.temp_helper import clear_temp_directory, create_temp_directory, resolve_temp_frame_set
|
||||
from facefusion.types import AudioFrame, ErrorCode, VisionFrame
|
||||
from facefusion.vision import conditional_merge_vision_mask, extract_vision_mask, read_static_image, read_static_images, read_static_video_frame, restrict_video_fps, select_video_frames, write_image
|
||||
from facefusion.vision import conditional_merge_vision_mask, extract_vision_mask, read_static_image, read_static_images, read_static_video_frame, restrict_trim_video_frame, restrict_video_fps, select_video_frames, write_image
|
||||
|
||||
|
||||
def is_process_stopping() -> bool:
|
||||
@@ -39,7 +40,9 @@ def conditional_get_source_audio_frame(frame_number : int) -> AudioFrame:
|
||||
fps = state_manager.get_item('output_audio_fps')
|
||||
|
||||
if state_manager.get_item('workflow_mode') == 'image-to-video':
|
||||
trim_frame_start, _ = restrict_trim_video_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end'))
|
||||
fps = restrict_video_fps(state_manager.get_item('target_path'), state_manager.get_item('output_video_fps'))
|
||||
frame_number = frame_number - trim_frame_start
|
||||
|
||||
source_audio_frame = get_audio_frame(source_audio_path, fps, frame_number)
|
||||
|
||||
@@ -55,7 +58,9 @@ def conditional_get_source_voice_frame(frame_number : int) -> AudioFrame:
|
||||
temp_fps = state_manager.get_item('output_audio_fps')
|
||||
|
||||
if state_manager.get_item('workflow_mode') == 'image-to-video':
|
||||
trim_frame_start, _ = restrict_trim_video_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end'))
|
||||
temp_fps = restrict_video_fps(state_manager.get_item('target_path'), state_manager.get_item('output_video_fps'))
|
||||
frame_number = frame_number - trim_frame_start
|
||||
|
||||
source_voice_frame = get_voice_frame(source_audio_path, temp_fps, frame_number)
|
||||
|
||||
@@ -102,26 +107,53 @@ def process_temp_frame(temp_frame_path : str, frame_number : int) -> bool:
|
||||
return write_image(temp_frame_path, temp_vision_frame)
|
||||
|
||||
|
||||
def process_frames() -> ErrorCode:
|
||||
temp_frame_paths = resolve_temp_frame_paths(state_manager.get_temp_path(), state_manager.get_item('output_path'), state_manager.get_item('temp_frame_format'))
|
||||
def process_temp_vision_frame(target_vision_frames : List[VisionFrame], temp_vision_frame : VisionFrame, frame_number : int) -> VisionFrame:
|
||||
reference_vision_frame = conditional_get_reference_vision_frame()
|
||||
source_vision_frames = read_static_images(state_manager.get_item('source_paths'))
|
||||
source_audio_frame = conditional_get_source_audio_frame(frame_number)
|
||||
source_voice_frame = conditional_get_source_voice_frame(frame_number)
|
||||
temp_vision_mask = extract_vision_mask(temp_vision_frame)
|
||||
|
||||
if temp_frame_paths:
|
||||
with tqdm(total = len(temp_frame_paths), desc = translator.get('processing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
|
||||
for processor_module in get_processors_modules(state_manager.get_item('processors')):
|
||||
temp_vision_frame, temp_vision_mask = processor_module.process_frame(
|
||||
{
|
||||
'reference_vision_frame': reference_vision_frame,
|
||||
'source_vision_frames': source_vision_frames,
|
||||
'source_audio_frame': source_audio_frame,
|
||||
'source_voice_frame': source_voice_frame,
|
||||
'target_vision_frames': target_vision_frames,
|
||||
'temp_vision_frame': temp_vision_frame[:, :, :3],
|
||||
'temp_vision_mask': temp_vision_mask
|
||||
})
|
||||
|
||||
return conditional_merge_vision_mask(temp_vision_frame, temp_vision_mask)
|
||||
|
||||
|
||||
def process_frames() -> ErrorCode:
|
||||
temp_frame_set = resolve_temp_frame_set(state_manager.get_temp_path(), state_manager.get_item('output_path'), state_manager.get_item('temp_frame_format'))
|
||||
|
||||
if temp_frame_set:
|
||||
with tqdm(total = len(temp_frame_set), desc = translator.get('processing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
|
||||
progress.set_postfix(execution_providers = state_manager.get_item('execution_providers'))
|
||||
|
||||
with ThreadPoolExecutor(max_workers = state_manager.get_item('execution_thread_count')) as executor:
|
||||
futures = []
|
||||
futures : Deque[Future[bool]] = deque()
|
||||
|
||||
for frame_number, temp_frame_path in enumerate(temp_frame_paths):
|
||||
for frame_number, temp_frame_path in temp_frame_set.items():
|
||||
future = executor.submit(process_temp_frame, temp_frame_path, frame_number)
|
||||
futures.append(future)
|
||||
|
||||
for future in as_completed(futures):
|
||||
if is_process_stopping():
|
||||
for __future__ in futures:
|
||||
__future__.cancel()
|
||||
while futures:
|
||||
future = futures.popleft()
|
||||
|
||||
if not future.cancelled():
|
||||
if is_process_stopping():
|
||||
|
||||
for pending_future in futures:
|
||||
pending_future.cancel()
|
||||
|
||||
futures.clear()
|
||||
|
||||
else:
|
||||
future.result()
|
||||
progress.update()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from functools import partial
|
||||
|
||||
from facefusion import process_manager
|
||||
from facefusion import process_manager, state_manager
|
||||
from facefusion.types import ErrorCode
|
||||
from facefusion.workflows.core import clear, process_frames, setup
|
||||
from facefusion.workflows.to_video import analyse_video, create_temp_frames, finalize_video, merge_frames, restore_audio
|
||||
from facefusion.workflows.to_video import analyse_video, create_temp_frames, finalize_video, merge_frames, process_memory_frames, restore_audio
|
||||
|
||||
|
||||
def process(start_time : float) -> ErrorCode:
|
||||
@@ -11,14 +11,26 @@ def process(start_time : float) -> ErrorCode:
|
||||
[
|
||||
analyse_video,
|
||||
clear,
|
||||
setup,
|
||||
create_temp_frames,
|
||||
process_frames,
|
||||
merge_frames,
|
||||
setup
|
||||
]
|
||||
|
||||
if state_manager.get_item('workflow_strategy') == 'disk':
|
||||
tasks.extend(
|
||||
[
|
||||
create_temp_frames,
|
||||
process_frames,
|
||||
merge_frames
|
||||
])
|
||||
|
||||
if state_manager.get_item('workflow_strategy') == 'memory':
|
||||
tasks.append(process_memory_frames)
|
||||
|
||||
tasks.extend(
|
||||
[
|
||||
restore_audio,
|
||||
partial(finalize_video, start_time),
|
||||
clear
|
||||
]
|
||||
])
|
||||
|
||||
process_manager.start()
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from facefusion import content_analyser, ffmpeg, logger, process_manager, state_manager, translator
|
||||
from facefusion.filesystem import is_image
|
||||
from facefusion.processors.core import get_processors_modules
|
||||
from facefusion.temp_helper import get_temp_file_path
|
||||
from facefusion.time_helper import calculate_end_time
|
||||
from facefusion.types import ErrorCode
|
||||
@@ -31,6 +32,9 @@ def process_image() -> ErrorCode:
|
||||
temp_image_path = get_temp_file_path(state_manager.get_temp_path(), state_manager.get_item('output_path'))
|
||||
process_temp_frame(temp_image_path, 0)
|
||||
|
||||
for processor_module in get_processors_modules(state_manager.get_item('processors')):
|
||||
processor_module.post_process()
|
||||
|
||||
if is_process_stopping():
|
||||
return 4
|
||||
return 0
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
from facefusion import content_analyser, ffmpeg, logger, state_manager, translator, video_manager
|
||||
from facefusion.common_helper import get_first
|
||||
from collections import deque
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from typing import Deque
|
||||
|
||||
import cv2
|
||||
import numpy
|
||||
from tqdm import tqdm
|
||||
|
||||
from facefusion import content_analyser, ffmpeg, logger, process_manager, state_manager, translator, video_manager
|
||||
from facefusion.common_helper import get_first, get_middle
|
||||
from facefusion.filesystem import filter_audio_paths, is_video
|
||||
from facefusion.media_helper import restrict_trim_frame
|
||||
from facefusion.processors.core import get_processors_modules
|
||||
from facefusion.temp_helper import move_temp_file, resolve_temp_frame_paths
|
||||
from facefusion.time_helper import calculate_end_time
|
||||
from facefusion.types import ErrorCode, Fps, Resolution
|
||||
from facefusion.vision import detect_image_resolution, detect_video_resolution, pack_resolution, restrict_trim_video_frame, restrict_video_fps, restrict_video_resolution, scale_resolution
|
||||
from facefusion.workflows.core import is_process_stopping
|
||||
from facefusion.types import ErrorCode, Fps, Resolution, VisionFrame
|
||||
from facefusion.vision import detect_image_resolution, detect_video_resolution, pack_resolution, read_static_video_frame, restrict_trim_video_frame, restrict_video_fps, restrict_video_resolution, scale_resolution, select_video_frames
|
||||
from facefusion.workflows.core import is_process_stopping, process_temp_vision_frame
|
||||
|
||||
|
||||
def analyse_video() -> ErrorCode:
|
||||
@@ -34,6 +43,75 @@ def create_temp_frames() -> ErrorCode:
|
||||
return 0
|
||||
|
||||
|
||||
def process_memory_frame(frame_number : int, temp_video_resolution : Resolution) -> VisionFrame:
|
||||
target_vision_frames = select_video_frames(state_manager.get_item('target_path'), frame_number, state_manager.get_item('target_frame_amount'))
|
||||
target_vision_frame = get_middle(target_vision_frames)
|
||||
temp_vision_frame = target_vision_frame.copy()
|
||||
|
||||
if not (target_vision_frame.shape[1], target_vision_frame.shape[0]) == temp_video_resolution:
|
||||
temp_vision_frame = cv2.resize(target_vision_frame, temp_video_resolution)
|
||||
|
||||
temp_vision_frame = process_temp_vision_frame(target_vision_frames, temp_vision_frame, frame_number)
|
||||
|
||||
if state_manager.get_item('temp_pixel_format') == 'bgra':
|
||||
temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_BGR2BGRA)
|
||||
|
||||
if state_manager.get_item('temp_pixel_format') == 'bgr24':
|
||||
temp_vision_frame = temp_vision_frame[:, :, :3]
|
||||
|
||||
return numpy.ascontiguousarray(temp_vision_frame)
|
||||
|
||||
|
||||
def process_memory_frames() -> ErrorCode:
|
||||
trim_frame_start, trim_frame_end = restrict_trim_video_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end'))
|
||||
output_video_resolution = scale_resolution(detect_video_resolution(state_manager.get_item('target_path')), state_manager.get_item('output_video_scale'))
|
||||
temp_video_resolution = restrict_video_resolution(state_manager.get_item('target_path'), output_video_resolution)
|
||||
temp_video_fps = restrict_video_fps(state_manager.get_item('target_path'), state_manager.get_item('output_video_fps'))
|
||||
temp_frame_range = range(trim_frame_start, trim_frame_end)
|
||||
|
||||
if temp_frame_range:
|
||||
video_writer = video_manager.get_writer(state_manager.get_item('output_path'), temp_video_fps, temp_video_resolution, output_video_resolution, state_manager.get_item('output_video_fps'))
|
||||
|
||||
with tqdm(total = len(temp_frame_range), desc = translator.get('processing'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
|
||||
progress.set_postfix(execution_providers = state_manager.get_item('execution_providers'))
|
||||
|
||||
read_static_video_frame(state_manager.get_item('target_path'), state_manager.get_item('reference_frame_number'))
|
||||
|
||||
with ThreadPoolExecutor(max_workers = state_manager.get_item('execution_thread_count')) as executor:
|
||||
futures : Deque[Future[VisionFrame]] = deque()
|
||||
|
||||
for frame_number in temp_frame_range:
|
||||
future = executor.submit(process_memory_frame, frame_number, temp_video_resolution)
|
||||
futures.append(future)
|
||||
|
||||
while futures:
|
||||
future = futures.popleft()
|
||||
|
||||
if is_process_stopping():
|
||||
|
||||
for pending_future in futures:
|
||||
pending_future.cancel()
|
||||
|
||||
futures.clear()
|
||||
|
||||
else:
|
||||
video_manager.write_video_frame(video_writer, future.result())
|
||||
progress.update()
|
||||
|
||||
if not video_manager.close_video_writer(video_writer):
|
||||
process_manager.stop()
|
||||
|
||||
for processor_module in get_processors_modules(state_manager.get_item('processors')):
|
||||
processor_module.post_process()
|
||||
|
||||
if is_process_stopping():
|
||||
return 4
|
||||
else:
|
||||
logger.error(translator.get('temp_frames_not_found'), __name__)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def merge_frames() -> ErrorCode:
|
||||
temp_frame_paths = resolve_temp_frame_paths(state_manager.get_temp_path(), state_manager.get_item('output_path'), state_manager.get_item('temp_frame_format'))
|
||||
trim_frame_start, trim_frame_end = restrict_trim_frame(len(temp_frame_paths), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end'))
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
numpy==2.4.2
|
||||
onnx==1.20.1
|
||||
onnxruntime==1.26.0
|
||||
opencv-python==4.13.0.92
|
||||
numpy==2.4.6
|
||||
onnx==1.22.0
|
||||
onnxruntime==1.28.0
|
||||
opencv-python-headless==5.0.0.93
|
||||
psutil==7.2.2
|
||||
python-multipart==0.0.28
|
||||
tqdm==4.67.3
|
||||
scipy==1.16.3
|
||||
tqdm==4.70.0
|
||||
scipy==1.18.0
|
||||
starlette==0.52.1
|
||||
uvicorn==0.41.0
|
||||
websockets==16.0
|
||||
|
||||
@@ -79,7 +79,7 @@ def test_run_video_encode_loop(video_codec : VideoCodec, payload_type : int) ->
|
||||
video_queue : Queue[Tuple[Time, Future[BufferPack]]] = Queue(maxsize = 30)
|
||||
|
||||
with ThreadPoolExecutor(max_workers = 1) as executor:
|
||||
video_queue.put((0.1, executor.submit(process_video_frame, video_frame)))
|
||||
video_queue.put((0.1, executor.submit(process_video_frame, [], video_frame)))
|
||||
|
||||
with patch('facefusion.apis.stream_video.rtc.send_video') as send_video_mock:
|
||||
encode_loop_thread = threading.Thread(target = run_video_encode_loop, args = (rtc_peer, video_queue), daemon = True)
|
||||
@@ -267,7 +267,7 @@ def test_handle_video_frame(video_codec : VideoCodec) -> None:
|
||||
with ThreadPoolExecutor(max_workers = 1) as executor:
|
||||
with patch('facefusion.apis.stream_video.decode_video_frame', return_value = video_frame):
|
||||
with patch('facefusion.apis.stream_video.process_video_frame', return_value = BufferPack(buffer = video_frame.tobytes(), resolution = (426, 226))):
|
||||
handle_video_frame(video_codec, video_decoder, video_queue, executor, bytes(), 0)
|
||||
handle_video_frame([], video_codec, video_decoder, video_queue, executor, bytes(), 0)
|
||||
_, video_future = video_queue.get_nowait()
|
||||
|
||||
video_buffer = video_future.result().get('buffer')
|
||||
|
||||
@@ -7,7 +7,7 @@ from facefusion.download import conditional_download
|
||||
from facefusion.face_creator import get_many_faces, get_one_face
|
||||
from facefusion.face_store import clear_faces
|
||||
from facefusion.face_tracker import create_face_tracks, select_face_track, track_faces
|
||||
from facefusion.vision import read_static_video_chunk, read_static_video_frame
|
||||
from facefusion.vision import read_static_video_frame, select_video_frames
|
||||
from .assert_helper import get_test_example_file, get_test_examples_directory
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ def before_each() -> None:
|
||||
|
||||
def test_track_faces() -> None:
|
||||
target_path = get_test_example_file('target-240p.mp4')
|
||||
video_frame_chunk = read_static_video_chunk(target_path, 0, 7)
|
||||
target_vision_frames = [ video_frame_chunk.get(frame_number) for frame_number in sorted(video_frame_chunk) ]
|
||||
target_vision_frames = select_video_frames(target_path, 3, 3)
|
||||
empty_vision_frame = numpy.zeros_like(get_first(target_vision_frames))
|
||||
|
||||
target_vision_frames[2] = empty_vision_frame
|
||||
@@ -58,7 +57,7 @@ def test_track_faces() -> None:
|
||||
|
||||
assert len(track_faces(target_vision_frames, 0.3)) == 1
|
||||
|
||||
target_vision_frames = [ video_frame_chunk.get(frame_number) for frame_number in sorted(video_frame_chunk)[:5] ]
|
||||
target_vision_frames = select_video_frames(target_path, 3, 3)[:5]
|
||||
target_vision_frames[0] = empty_vision_frame
|
||||
target_vision_frames[1] = empty_vision_frame
|
||||
target_vision_frames[2] = empty_vision_frame
|
||||
|
||||
@@ -7,7 +7,7 @@ import facefusion.ffmpeg
|
||||
from facefusion import ffmpeg, ffmpeg_builder, process_manager, state_manager
|
||||
from facefusion.download import conditional_download
|
||||
from facefusion.ffmpeg import concat_video, extract_frames, merge_video, read_audio_buffer, replace_audio, restore_audio, sanitize_audio, sanitize_image, sanitize_video, spawn_frames
|
||||
from facefusion.ffprobe import probe_entries
|
||||
from facefusion.ffprobe import probe_audio_entries, probe_video_entries
|
||||
from facefusion.filesystem import copy_file, is_image
|
||||
from facefusion.temp_helper import clear_temp_directory, create_temp_directory, get_temp_file_path, resolve_temp_frame_paths
|
||||
from facefusion.types import EncoderSet
|
||||
@@ -253,10 +253,10 @@ def test_sanitize_audio() -> None:
|
||||
]
|
||||
|
||||
assert sanitize_audio(file_content, output_paths[0], 'strict') is True
|
||||
assert probe_entries(output_paths[0], [ 'codec_name' ]).get('codec_name') == 'mp3'
|
||||
assert probe_audio_entries(output_paths[0], [ 'codec_name' ]).get('codec_name') == 'mp3'
|
||||
|
||||
assert sanitize_audio(file_content, output_paths[1], 'moderate') is True
|
||||
assert probe_entries(output_paths[1], [ 'codec_name' ]).get('codec_name') == 'pcm_s16le'
|
||||
assert probe_audio_entries(output_paths[1], [ 'codec_name' ]).get('codec_name') == 'pcm_s16le'
|
||||
|
||||
|
||||
def test_sanitize_image() -> None:
|
||||
@@ -278,9 +278,9 @@ def test_sanitize_video() -> None:
|
||||
]
|
||||
|
||||
assert sanitize_video(file_content, output_paths[0], 'strict') is True
|
||||
assert probe_entries(output_paths[0], [ 'codec_name' ]).get('codec_name') == 'h264'
|
||||
assert probe_video_entries(output_paths[0], [ 'codec_name' ]).get('codec_name') == 'h264'
|
||||
|
||||
assert sanitize_video(file_content, output_paths[1], 'moderate') is True
|
||||
assert probe_entries(output_paths[1], [ 'codec_name' ]).get('codec_name') == 'hevc'
|
||||
assert probe_video_entries(output_paths[1], [ 'codec_name' ]).get('codec_name') == 'hevc'
|
||||
|
||||
|
||||
|
||||
+18
-9
@@ -26,13 +26,14 @@ def before_all() -> None:
|
||||
ffmpeg_builder.set_output(get_test_example_file('source-48000khz-2ch.wav'))
|
||||
)
|
||||
)
|
||||
ffmpeg.run_ffmpeg(
|
||||
ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(get_test_example_file('target-240p.mp4')),
|
||||
ffmpeg_builder.set_video_duration(1),
|
||||
ffmpeg_builder.set_output(get_test_example_file('target-240p-1s.mov'))
|
||||
for video_format in [ 'mkv', 'mov' ]:
|
||||
ffmpeg.run_ffmpeg(
|
||||
ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(get_test_example_file('target-240p.mp4')),
|
||||
ffmpeg_builder.set_video_duration(1),
|
||||
ffmpeg_builder.set_output(get_test_example_file('target-240p-1s.' + video_format))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_extract_audio_metadata() -> None:
|
||||
@@ -40,7 +41,7 @@ def test_extract_audio_metadata() -> None:
|
||||
|
||||
assert audio_metadata.get('sample_rate') == 44100
|
||||
assert audio_metadata.get('channel_total') == 1
|
||||
assert audio_metadata.get('frame_total') == 167039
|
||||
assert audio_metadata.get('frame_total') == 167040
|
||||
assert audio_metadata.get('bit_rate') == 128000
|
||||
|
||||
audio_metadata = extract_audio_metadata(get_test_example_file('source-48000khz-2ch.wav'))
|
||||
@@ -48,7 +49,7 @@ def test_extract_audio_metadata() -> None:
|
||||
assert audio_metadata.get('sample_rate') == 48000
|
||||
assert audio_metadata.get('channel_total') == 2
|
||||
assert audio_metadata.get('frame_total') == 91200
|
||||
assert audio_metadata.get('bit_rate') == 1536000
|
||||
assert audio_metadata.get('bit_rate') == 1536328
|
||||
|
||||
|
||||
def test_extract_video_metadata() -> None:
|
||||
@@ -57,7 +58,15 @@ def test_extract_video_metadata() -> None:
|
||||
assert video_metadata.get('fps') == 25.0
|
||||
assert video_metadata.get('duration') == 10.8
|
||||
assert video_metadata.get('resolution') == (426, 226)
|
||||
assert video_metadata.get('bit_rate') == 138754
|
||||
assert video_metadata.get('bit_rate') == 141981
|
||||
assert video_metadata.get('color_transfer') == 'smpte170m'
|
||||
|
||||
video_metadata = extract_video_metadata(get_test_example_file('target-240p-1s.mkv'))
|
||||
|
||||
assert video_metadata.get('fps') == 25.0
|
||||
assert video_metadata.get('duration') == 1.0
|
||||
assert video_metadata.get('frame_total') == 25
|
||||
assert video_metadata.get('resolution') == (426, 226)
|
||||
|
||||
video_metadata = extract_video_metadata(get_test_example_file('target-240p-1s.mov'))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from shutil import which
|
||||
|
||||
from facefusion import ffprobe_builder
|
||||
from facefusion.ffprobe_builder import chain, format_to_key_value, run, set_input, show_entries
|
||||
from facefusion.ffprobe_builder import chain, format_to_key_value, run, select_stream, set_input, show_stream_entries
|
||||
|
||||
|
||||
def test_run() -> None:
|
||||
@@ -10,15 +10,21 @@ def test_run() -> None:
|
||||
|
||||
def test_chain() -> None:
|
||||
assert chain(
|
||||
ffprobe_builder.show_entries([ 'sample_rate' ]),
|
||||
ffprobe_builder.select_stream('a:0'),
|
||||
ffprobe_builder.show_stream_entries([ 'sample_rate' ]),
|
||||
ffprobe_builder.format_to_key_value(),
|
||||
ffprobe_builder.set_input('audio.mp3')
|
||||
) == [ '-show_entries', 'stream=sample_rate', '-of', 'default=noprint_wrappers=1', '-i', 'audio.mp3' ]
|
||||
) == [ '-select_streams', 'a:0', '-show_entries', 'stream=sample_rate', '-of', 'default=noprint_wrappers=1', '-i', 'audio.mp3' ]
|
||||
|
||||
|
||||
def test_show_entries() -> None:
|
||||
assert show_entries([ 'duration' ]) == [ '-show_entries', 'stream=duration' ]
|
||||
assert show_entries([ 'duration', 'sample_rate']) == [ '-show_entries', 'stream=duration,sample_rate' ]
|
||||
def test_select_stream() -> None:
|
||||
assert select_stream('a:0') == [ '-select_streams', 'a:0' ]
|
||||
assert select_stream('v:0') == [ '-select_streams', 'v:0' ]
|
||||
|
||||
|
||||
def test_show_stream_entries() -> None:
|
||||
assert show_stream_entries([ 'duration' ]) == [ '-show_entries', 'stream=duration' ]
|
||||
assert show_stream_entries([ 'duration', 'sample_rate' ]) == [ '-show_entries', 'stream=duration,sample_rate' ]
|
||||
|
||||
|
||||
def test_format_to_key_value() -> None:
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import pytest
|
||||
|
||||
from facefusion import process_manager
|
||||
from facefusion.download import conditional_download
|
||||
from facefusion.frame_store import clear_frames, get_frame_store, reduce_frames, select_frame_set, set_frame
|
||||
from facefusion.vision import read_video_frame
|
||||
from .assert_helper import get_test_example_file, get_test_examples_directory
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'module', autouse = True)
|
||||
def before_all() -> None:
|
||||
process_manager.start()
|
||||
conditional_download(get_test_examples_directory(),
|
||||
[
|
||||
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4'
|
||||
])
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'function', autouse = True)
|
||||
def before_each() -> None:
|
||||
clear_frames('reader-1')
|
||||
clear_frames('reader-2')
|
||||
|
||||
|
||||
def test_get_frame_store() -> None:
|
||||
frame_store = get_frame_store('reader-1')
|
||||
|
||||
assert frame_store == {}
|
||||
assert get_frame_store('reader-1') is frame_store
|
||||
|
||||
|
||||
def test_set_frame() -> None:
|
||||
target_frame = read_video_frame(get_test_example_file('target-240p.mp4'), 0)
|
||||
|
||||
set_frame('reader-1', 5, target_frame)
|
||||
|
||||
assert get_frame_store('reader-1').get(5) is target_frame
|
||||
|
||||
|
||||
def test_select_frame_set() -> None:
|
||||
first_frame = read_video_frame(get_test_example_file('target-240p.mp4'), 0)
|
||||
fifth_frame = read_video_frame(get_test_example_file('target-240p.mp4'), 5)
|
||||
|
||||
set_frame('reader-1', 2, first_frame)
|
||||
set_frame('reader-1', 5, fifth_frame)
|
||||
|
||||
assert sorted(select_frame_set('reader-1', 0, 4)) == [ 2 ]
|
||||
assert select_frame_set('reader-1', 0, 4).get(2) is first_frame
|
||||
assert sorted(select_frame_set('reader-1', 2, 6)) == [ 2, 5 ]
|
||||
assert select_frame_set('reader-1', 2, 6).get(5) is fifth_frame
|
||||
assert select_frame_set('reader-1', 8, 12) == {}
|
||||
|
||||
|
||||
def test_reduce_frames() -> None:
|
||||
target_frame = read_video_frame(get_test_example_file('target-240p.mp4'), 0)
|
||||
|
||||
for frame_number in range(0, 10):
|
||||
set_frame('reader-1', frame_number, target_frame)
|
||||
|
||||
reduce_frames('reader-1', 4, 6)
|
||||
|
||||
assert sorted(get_frame_store('reader-1')) == [ 4, 5, 6 ]
|
||||
|
||||
|
||||
def test_clear_frames() -> None:
|
||||
target_frame = read_video_frame(get_test_example_file('target-240p.mp4'), 0)
|
||||
|
||||
set_frame('reader-1', 0, target_frame)
|
||||
set_frame('reader-2', 0, target_frame)
|
||||
clear_frames('reader-1')
|
||||
|
||||
assert get_frame_store('reader-1') == {}
|
||||
assert get_frame_store('reader-2').get(0) is target_frame
|
||||
@@ -0,0 +1,203 @@
|
||||
import tempfile
|
||||
|
||||
import numpy
|
||||
import pytest
|
||||
|
||||
from facefusion import ffmpeg, ffmpeg_builder, process_manager, state_manager
|
||||
from facefusion.common_helper import is_linux, is_macos, is_windows
|
||||
from facefusion.download import conditional_download
|
||||
from facefusion.ffprobe import extract_video_metadata
|
||||
from facefusion.frame_store import get_frame_store
|
||||
from facefusion.temp_helper import create_temp_directory, get_temp_file_path
|
||||
from facefusion.video_manager import clear_video_pool, close_video_reader, close_video_writer, collect_video_frames, conditional_seek_video_reader, drain_video_reader, get_reader, get_writer, read_video_frame, read_video_frames, seek_video_reader, write_video_frame
|
||||
from .assert_helper import get_test_example_file, get_test_examples_directory
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'module', autouse = True)
|
||||
def before_all() -> None:
|
||||
process_manager.start()
|
||||
conditional_download(get_test_examples_directory(),
|
||||
[
|
||||
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4'
|
||||
])
|
||||
|
||||
for video_fps in [ 25, 30 ]:
|
||||
ffmpeg.run_ffmpeg(
|
||||
ffmpeg_builder.chain(
|
||||
ffmpeg_builder.set_input(get_test_example_file('target-240p.mp4')),
|
||||
ffmpeg_builder.set_video_fps(video_fps),
|
||||
ffmpeg_builder.set_output(get_test_example_file('target-240p-' + str(video_fps) + 'fps.mp4'))
|
||||
)
|
||||
)
|
||||
|
||||
state_manager.init_item('temp_path', tempfile.gettempdir())
|
||||
state_manager.init_item('temp_frame_format', 'png')
|
||||
state_manager.init_item('temp_pixel_format', 'bgr24')
|
||||
state_manager.init_item('output_video_encoder', 'libx264')
|
||||
state_manager.init_item('output_video_quality', 80)
|
||||
state_manager.init_item('output_video_preset', 'veryfast')
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'function', autouse = True)
|
||||
def before_each() -> None:
|
||||
clear_video_pool()
|
||||
|
||||
|
||||
def test_get_reader() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
video_metadata = video_reader.get('metadata')
|
||||
|
||||
assert video_metadata.get('resolution') == (426, 226)
|
||||
assert video_metadata.get('fps') == 25.0
|
||||
assert video_metadata.get('frame_total') == 270
|
||||
|
||||
assert get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame') is video_reader
|
||||
assert not get_reader(get_test_example_file('target-240p-25fps.mp4'), 'select_video_frames').get('id') == video_reader.get('id')
|
||||
|
||||
|
||||
def test_conditional_seek_video_reader() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
video_frames = {}
|
||||
|
||||
for frame_number in range(30):
|
||||
video_frames[frame_number] = read_video_frame(video_reader)
|
||||
|
||||
for frame_number in [ 5, 17, 29 ]:
|
||||
conditional_seek_video_reader(video_reader, frame_number)
|
||||
|
||||
assert numpy.array_equal(read_video_frame(video_reader), video_frames.get(frame_number)) is True
|
||||
|
||||
|
||||
def test_seek_video_reader() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
video_frames = {}
|
||||
|
||||
for frame_number in range(30):
|
||||
video_frames[frame_number] = read_video_frame(video_reader)
|
||||
|
||||
for frame_number in [ 5, 17, 29 ]:
|
||||
seek_video_reader(video_reader, frame_number)
|
||||
|
||||
assert numpy.array_equal(read_video_frame(video_reader), video_frames.get(frame_number)) is True
|
||||
|
||||
|
||||
def test_drain_video_reader() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
|
||||
drain_video_reader(video_reader, 10)
|
||||
|
||||
assert video_reader.get('frame_number') == 10
|
||||
|
||||
vision_frame = read_video_frame(video_reader)
|
||||
seek_video_reader(video_reader, 10)
|
||||
|
||||
assert numpy.array_equal(vision_frame, read_video_frame(video_reader)) is True
|
||||
|
||||
|
||||
def test_read_video_frame() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
|
||||
assert read_video_frame(video_reader).shape == (226, 426, 3)
|
||||
assert video_reader.get('frame_number') == 1
|
||||
|
||||
seek_video_reader(video_reader, 269)
|
||||
|
||||
assert read_video_frame(video_reader).shape == (226, 426, 3)
|
||||
assert read_video_frame(video_reader) is None
|
||||
|
||||
|
||||
def test_read_video_frames() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'read_video_frame')
|
||||
|
||||
assert sorted(read_video_frames(video_reader, 0, 4)) == [ 0, 1, 2, 3, 4 ]
|
||||
|
||||
frame_number = video_reader.get('frame_number')
|
||||
|
||||
assert sorted(read_video_frames(video_reader, 1, 3)) == [ 1, 2, 3 ]
|
||||
assert video_reader.get('frame_number') == frame_number
|
||||
|
||||
read_video_frames(video_reader, 21, 25)
|
||||
|
||||
assert min(get_frame_store(video_reader.get('id'))) == 17
|
||||
assert max(get_frame_store(video_reader.get('id'))) == 25
|
||||
assert sorted(read_video_frames(video_reader, 268, 275)) == [ 268, 269 ]
|
||||
|
||||
|
||||
def test_collect_video_frames() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'select_video_frames')
|
||||
|
||||
collect_video_frames(video_reader, 20, 24)
|
||||
|
||||
assert sorted(get_frame_store(video_reader.get('id'))) == [ 20, 21, 22, 23, 24 ]
|
||||
assert video_reader.get('frame_number') == 25
|
||||
|
||||
|
||||
def test_close_video_reader() -> None:
|
||||
video_reader = get_reader(get_test_example_file('target-240p-25fps.mp4'), 'select_video_frames')
|
||||
|
||||
read_video_frames(video_reader, 0, 4)
|
||||
close_video_reader(video_reader)
|
||||
|
||||
if is_windows():
|
||||
assert video_reader.get('process').returncode == 1
|
||||
|
||||
if is_linux() or is_macos():
|
||||
assert video_reader.get('process').returncode == -9
|
||||
|
||||
|
||||
def test_get_writer() -> None:
|
||||
target_path = get_test_example_file('target-240p-25fps.mp4')
|
||||
create_temp_directory(state_manager.get_temp_path(), target_path)
|
||||
video_writer = get_writer(target_path, 25.0, (426, 226), (426, 226), 25.0)
|
||||
|
||||
assert get_writer(target_path, 25.0, (426, 226), (426, 226), 25.0) is video_writer
|
||||
|
||||
|
||||
def test_write_video_frame() -> None:
|
||||
target_path = get_test_example_file('target-240p-25fps.mp4')
|
||||
create_temp_directory(state_manager.get_temp_path(), target_path)
|
||||
video_reader = get_reader(target_path, 'read_video_frame')
|
||||
video_writer = get_writer(target_path, 25.0, (426, 226), (426, 226), 25.0)
|
||||
|
||||
for frame_number in range(25):
|
||||
write_video_frame(video_writer, read_video_frame(video_reader))
|
||||
|
||||
assert close_video_writer(video_writer) is True
|
||||
|
||||
video_metadata = extract_video_metadata(get_temp_file_path(state_manager.get_temp_path(), target_path))
|
||||
|
||||
assert video_metadata.get('duration') == 1.0
|
||||
assert video_metadata.get('frame_total') == 25
|
||||
assert video_metadata.get('fps') == 25.0
|
||||
assert video_metadata.get('resolution') == (426, 226)
|
||||
assert video_metadata.get('color_transfer') == 'bt709'
|
||||
|
||||
|
||||
def test_close_video_writer() -> None:
|
||||
target_path = get_test_example_file('target-240p-30fps.mp4')
|
||||
create_temp_directory(state_manager.get_temp_path(), target_path)
|
||||
video_reader = get_reader(target_path, 'read_video_frame')
|
||||
video_writer = get_writer(target_path, 30.0, (426, 226), (426, 226), 30.0)
|
||||
|
||||
write_video_frame(video_writer, read_video_frame(video_reader))
|
||||
|
||||
assert close_video_writer(video_writer) is True
|
||||
|
||||
|
||||
def test_clear_video_pool() -> None:
|
||||
target_path = get_test_example_file('target-240p-25fps.mp4')
|
||||
create_temp_directory(state_manager.get_temp_path(), target_path)
|
||||
video_reader = get_reader(target_path, 'select_video_frames')
|
||||
video_writer = get_writer(target_path, 25.0, (426, 226), (426, 226), 25.0)
|
||||
|
||||
read_video_frames(video_reader, 0, 4)
|
||||
write_video_frame(video_writer, read_video_frame(video_reader))
|
||||
clear_video_pool()
|
||||
|
||||
if is_windows():
|
||||
assert video_reader.get('process').returncode == 1
|
||||
|
||||
if is_linux() or is_macos():
|
||||
assert video_reader.get('process').returncode == -9
|
||||
|
||||
assert video_writer.get('process').returncode == 0
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
|
||||
from facefusion import ffmpeg, ffmpeg_builder, process_manager
|
||||
from facefusion.download import conditional_download
|
||||
from facefusion.vision import calculate_histogram_difference, count_video_frame_total, detect_image_resolution, detect_video_duration, detect_video_fps, detect_video_resolution, match_frame_color, normalize_resolution, pack_resolution, predict_video_frame_total, read_image, read_video_chunk, read_video_frame, restrict_image_resolution, restrict_trim_video_frame, restrict_video_fps, restrict_video_resolution, scale_resolution, select_video_frames, unpack_resolution, write_image
|
||||
from facefusion.vision import calculate_histogram_difference, count_video_frame_total, detect_image_resolution, detect_video_duration, detect_video_fps, detect_video_resolution, match_frame_color, normalize_resolution, pack_resolution, predict_video_frame_total, read_image, read_video_frame, restrict_image_resolution, restrict_trim_video_frame, restrict_video_fps, restrict_video_resolution, scale_resolution, select_video_frames, unpack_resolution, write_image
|
||||
from .assert_helper import get_test_example_file, get_test_examples_directory, get_test_output_path, prepare_test_output_directory
|
||||
|
||||
|
||||
@@ -136,11 +136,6 @@ def test_read_video_frame() -> None:
|
||||
assert read_video_frame('invalid') is None
|
||||
|
||||
|
||||
def test_read_video_chunk() -> None:
|
||||
assert len(read_video_chunk(get_test_example_file('target-240p-25fps.mp4'), 1, 40)) == 40
|
||||
assert read_video_chunk('invalid', 1, 40) == {}
|
||||
|
||||
|
||||
def test_select_video_frames() -> None:
|
||||
assert len(select_video_frames(get_test_example_file('target-240p-25fps.mp4'), 50, 5)) == 11
|
||||
assert len(select_video_frames(get_test_example_file('target-240p-25fps.mp4'), 1, 5)) == 11
|
||||
|
||||
Reference in New Issue
Block a user