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 <noreply@anthropic.com>
This commit is contained in:
henryruhs
2026-07-22 10:44:49 +02:00
parent 6e91bddfb0
commit 8d2d7df040
+13 -11
View File
@@ -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)