From 94139118ed5b0ec732122be9d5ba0d7067d9e06c Mon Sep 17 00:00:00 2001 From: henryruhs Date: Wed, 22 Jul 2026 21:25:24 +0200 Subject: [PATCH] fix order of workflow --- facefusion/video_manager.py | 12 ++++++++++++ facefusion/workflows/image_to_video.py | 22 +++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/facefusion/video_manager.py b/facefusion/video_manager.py index 7cb42d12..7a661f46 100644 --- a/facefusion/video_manager.py +++ b/facefusion/video_manager.py @@ -18,6 +18,7 @@ VIDEO_POOL_SET : VideoPoolSet =\ } +#todo: needs review - [process] [critical: medium] new ffmpeg raw pipe reader with seek based start, stderr devnull hides decode errors def create_video_reader_process(video_path : str, frame_position : int, video_fps : Fps) -> subprocess.Popen[bytes]: color_transfer = extract_video_metadata(video_path).get('color_transfer') commands = ffmpeg_builder.chain( @@ -33,6 +34,7 @@ def create_video_reader_process(video_path : str, frame_position : int, video_fp return subprocess.Popen(commands, stdout = subprocess.PIPE, stderr = subprocess.DEVNULL) +#todo: needs review - [perf] [critical: medium] select filter decodes the entire range to sample frames, runs at lowered process priority def create_video_sampler_process(video_path : str, frame_start : int, frame_end : int, frame_stride : int) -> subprocess.Popen[bytes]: color_transfer = extract_video_metadata(video_path).get('color_transfer') sample_total = len([ frame_number for frame_number in range(frame_start, frame_end) if frame_number % frame_stride == 0 ]) @@ -57,10 +59,12 @@ def create_video_sampler_process(video_path : str, frame_start : int, frame_end return subprocess.Popen(commands, stdout = subprocess.PIPE, stderr = subprocess.DEVNULL, preexec_fn = demote_process_priority) +#todo: needs review - [process] [critical: low] posix only nice fallback for the sampler priority def demote_process_priority() -> None: os.nice(10) +#todo: needs review - [memory] [critical: medium] pooled reader per path is created without lock and only reaped via clear_video_pool def get_video_reader(video_path : str) -> VideoReader: if video_path not in VIDEO_POOL_SET.get('reader'): video_metadata = extract_video_metadata(video_path) @@ -81,6 +85,7 @@ def get_video_reader(video_path : str) -> VideoReader: return VIDEO_POOL_SET.get('reader').get(video_path) +#todo: needs review - [perf] [critical: high] forward skip up to 128 frames by draining the pipe, everything else restarts the ffmpeg process def conditional_set_video_reader_position(video_reader : VideoReader, frame_position : int) -> None: skip_margin = 128 skipping = video_reader.get('position') < frame_position and frame_position - video_reader.get('position') <= skip_margin @@ -93,6 +98,7 @@ def conditional_set_video_reader_position(video_reader : VideoReader, frame_posi restart_video_reader(video_reader, frame_position) +#todo: needs review - [process] [critical: high] kill and respawn per out of order seek, frequent restarts stack up on random access def restart_video_reader(video_reader : VideoReader, frame_position : int) -> None: video_reader.get('process').kill() video_reader.get('process').wait() @@ -101,6 +107,7 @@ def restart_video_reader(video_reader : VideoReader, frame_position : int) -> No video_reader['frame_buffer'].clear() +#todo: needs review - [correctness] [critical: high] partial pipe read returns none and desyncs position from the actual stream def read_video_reader_frame(video_reader : VideoReader) -> Tuple[bool, Optional[VisionFrame]]: frame_size = video_reader.get('width') * video_reader.get('height') * 3 frame_buffer = video_reader.get('process').stdout.read(frame_size) @@ -111,6 +118,7 @@ def read_video_reader_frame(video_reader : VideoReader) -> Tuple[bool, Optional[ return False, None +#todo: needs review - [memory] [critical: medium] eviction only trims below frame_start, look ahead growth is bounded by the window callers def evict_video_reader_buffer(video_reader : VideoReader, frame_start : int, buffer_margin : int) -> None: frame_buffer = video_reader.get('frame_buffer') @@ -118,6 +126,7 @@ def evict_video_reader_buffer(video_reader : VideoReader, frame_start : int, buf del frame_buffer[frame_number] +#todo: needs review - [memory] [critical: high] frame_buffer keeps decoded frames in ram, restart heuristic depends on buffer_margin 16 def read_video_reader_window(video_reader : VideoReader, frame_start : int, frame_end : int) -> VideoReaderBuffer: frame_buffer = video_reader.get('frame_buffer') buffer_margin = 16 @@ -143,11 +152,13 @@ def read_video_reader_window(video_reader : VideoReader, frame_start : int, fram return frame_buffer +#todo: needs review - [correctness] [critical: low] rounds odd dimensions to even for yuv420p compatibility def pack_video_resolution(resolution : Resolution) -> str: width, height = resolution return str(round(width / 2) * 2) + 'x' + str(round(height / 2) * 2) +#todo: needs review - [color] [critical: high] output is always converted and tagged bt709, encoder_thread_count hardcoded to 16 def create_video_writer_process(target_path : str, temp_video_fps : Fps, temp_video_resolution : Resolution, output_video_resolution : Resolution, output_video_fps : Fps) -> subprocess.Popen[bytes]: output_video_encoder = state_manager.get_item('output_video_encoder') output_video_quality = state_manager.get_item('output_video_quality') @@ -179,6 +190,7 @@ def create_video_writer_process(target_path : str, temp_video_fps : Fps, temp_vi return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.DEVNULL) +#todo: needs review - [process] [critical: low] pooled writer keyed by target_path def get_video_writer(target_path : str, temp_video_fps : Fps, temp_video_resolution : Resolution, output_video_resolution : Resolution, output_video_fps : Fps) -> subprocess.Popen[bytes]: if target_path not in VIDEO_POOL_SET.get('writer'): VIDEO_POOL_SET['writer'][target_path] = create_video_writer_process(target_path, temp_video_fps, temp_video_resolution, output_video_resolution, output_video_fps) diff --git a/facefusion/workflows/image_to_video.py b/facefusion/workflows/image_to_video.py index 2bebdd09..a8aae891 100644 --- a/facefusion/workflows/image_to_video.py +++ b/facefusion/workflows/image_to_video.py @@ -24,10 +24,10 @@ def process(start_time : float) -> ErrorCode: tasks =\ [ setup, + enforce_analysis, extract_frames, process_disk_frames, merge_frames, - enforce_analysis, restore_audio, partial(finalize_video, start_time) ] @@ -36,8 +36,8 @@ def process(start_time : float) -> ErrorCode: tasks =\ [ setup, - process_stream_frames, enforce_analysis, + process_stream_frames, restore_audio, partial(finalize_video, start_time) ] @@ -54,15 +54,6 @@ def process(start_time : float) -> ErrorCode: return 0 -def enforce_analysis() -> ErrorCode: - trim_frame_start, trim_frame_end = restrict_trim_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end')) - - if analyse_video(state_manager.get_item('target_path'), trim_frame_start, trim_frame_end): - clear_temp_directory(state_manager.get_item('target_path')) - return 3 - return 0 - - def setup() -> ErrorCode: if clear_temp_directory(state_manager.get_item('target_path')): logger.debug(translator.get('clearing_temp'), __name__) @@ -73,6 +64,15 @@ def setup() -> ErrorCode: return 0 +def enforce_analysis() -> ErrorCode: + trim_frame_start, trim_frame_end = restrict_trim_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end')) + + if analyse_video(state_manager.get_item('target_path'), trim_frame_start, trim_frame_end): + clear_temp_directory(state_manager.get_item('target_path')) + return 3 + return 0 + + def extract_frames() -> ErrorCode: trim_frame_start, trim_frame_end = restrict_trim_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'))