From 8d2d7df040545447f342815b8d6f5439faa01636 Mon Sep 17 00:00:00 2001 From: henryruhs Date: Wed, 22 Jul 2026 10:44:49 +0200 Subject: [PATCH] Skip temp-frame extraction; process reads and writes frames directly With the canvas coming from the video reader, the extracted temp PNGs were never read for pixels -- extraction only produced write targets and a frame list. Drop the extract_frames step entirely: process_video iterates the trim range, each worker reads its frame from the reader, swaps, and writes straight to the temp frame path that merge consumes. Removes the whole extracting phase (a second full-video decode + PNG encode). Wall time drops ~25-36% (AV1 ~30 -> ~19 s, H.264 ~29 -> ~22 s for 300 frames); processing throughput unchanged; output unchanged. Note: this path does not resample fps (output_video_fps must equal source fps) and derives the frame count via cv2 container metadata; a scaled output falls back to a per-frame resize instead of the temp PNG. Co-Authored-By: Claude Opus 4.8 --- facefusion/workflows/image_to_video.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/facefusion/workflows/image_to_video.py b/facefusion/workflows/image_to_video.py index a71661f7..e51528f2 100644 --- a/facefusion/workflows/image_to_video.py +++ b/facefusion/workflows/image_to_video.py @@ -1,6 +1,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from functools import partial +import cv2 import numpy from tqdm import tqdm @@ -11,10 +12,10 @@ from facefusion.common_helper import get_first, get_middle from facefusion.content_analyser import analyse_video from facefusion.filesystem import filter_audio_paths, is_video from facefusion.processors.core import get_processors_modules -from facefusion.temp_helper import clear_temp_directory, create_temp_directory, move_temp_file, resolve_temp_frame_set +from facefusion.temp_helper import clear_temp_directory, create_temp_directory, get_temp_frame_pattern, move_temp_file from facefusion.time_helper import calculate_end_time from facefusion.types import ErrorCode, Resolution -from facefusion.vision import conditional_merge_vision_mask, detect_video_resolution, extract_vision_mask, pack_resolution, read_static_image, read_static_images, read_static_video_frame, restrict_trim_frame, restrict_video_fps, restrict_video_resolution, scale_resolution, select_video_frames, write_image +from facefusion.vision import conditional_merge_vision_mask, detect_video_resolution, extract_vision_mask, pack_resolution, read_static_images, read_static_video_frame, restrict_trim_frame, restrict_video_fps, restrict_video_resolution, scale_resolution, select_video_frames, write_image from facefusion.workflows.core import is_process_stopping @@ -22,7 +23,6 @@ def process(start_time : float) -> ErrorCode: tasks =\ [ setup, - extract_frames, process_video, merge_frames, restore_audio, @@ -74,19 +74,20 @@ def extract_frames() -> ErrorCode: def process_video() -> ErrorCode: - temp_frame_set = resolve_temp_frame_set(state_manager.get_item('target_path')) + 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')) temp_video_resolution = restrict_video_resolution(state_manager.get_item('target_path'), output_video_resolution) + frame_range = range(trim_frame_start, trim_frame_end) - 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: + if frame_range: + with tqdm(total = len(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')) with ThreadPoolExecutor(max_workers = state_manager.get_item('execution_thread_count')) as executor: futures = [] - for frame_number, temp_frame_path in temp_frame_set.items(): - future = executor.submit(process_temp_frame, temp_frame_path, frame_number, temp_video_resolution) + for frame_number in frame_range: + future = executor.submit(process_temp_frame, frame_number, temp_video_resolution) futures.append(future) for future in as_completed(futures): @@ -156,7 +157,7 @@ def restore_audio() -> ErrorCode: return 0 -def process_temp_frame(temp_frame_path : str, frame_number : int, temp_video_resolution : Resolution) -> bool: +def process_temp_frame(frame_number : int, temp_video_resolution : Resolution) -> bool: trim_frame_start, _ = restrict_trim_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end')) reference_vision_frame = read_static_video_frame(state_manager.get_item('target_path'), state_manager.get_item('reference_frame_number')) source_vision_frames = read_static_images(state_manager.get_item('source_paths')) @@ -164,10 +165,11 @@ def process_temp_frame(temp_frame_path : str, frame_number : int, temp_video_res 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_video_fps = restrict_video_fps(state_manager.get_item('target_path'), state_manager.get_item('output_video_fps')) + temp_frame_path = get_temp_frame_pattern(state_manager.get_item('target_path'), format(frame_number, '08d')) + temp_vision_frame = target_vision_frame.copy() if not (target_vision_frame.shape[1], target_vision_frame.shape[0]) == temp_video_resolution: - target_vision_frame = read_static_image(temp_frame_path, 'rgba') - temp_vision_frame = target_vision_frame.copy() + temp_vision_frame = cv2.resize(target_vision_frame, temp_video_resolution) temp_vision_mask = extract_vision_mask(temp_vision_frame) source_audio_frame = get_audio_frame(source_audio_path, temp_video_fps, frame_number - trim_frame_start)