Reuse the decoded window frame as the swap canvas

process_temp_frame read the target frame twice per output frame: once from
the video (the detection window) and once from the extracted temp PNG (the
swap canvas). They are the same frame N. The window centre is already decoded
in the reader, so use it as the canvas and only fall back to the temp PNG when
the reader resolution differs from the temp resolution (scaled output).

Removes one full-frame decode per output frame. AV1 ~22 -> ~26 fps and
H.264 ~20 -> ~23 fps -- matching and slightly beating 3.6.1, because a
frame copy is cheaper than a PNG decode. The canvas now comes from the raw
pipe decode instead of the PNG decode of the same frame (sub-visual ~1 LSB
difference in passthrough regions); detection is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
henryruhs
2026-07-22 10:31:11 +02:00
parent 7df613985d
commit 6e91bddfb0
+11 -5
View File
@@ -7,13 +7,13 @@ from tqdm import tqdm
from facefusion import ffmpeg
from facefusion import logger, process_manager, state_manager, translator, video_manager
from facefusion.audio import create_empty_audio_frame, get_audio_frame, get_voice_frame
from facefusion.common_helper import get_first
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.time_helper import calculate_end_time
from facefusion.types import ErrorCode
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.workflows.core import is_process_stopping
@@ -75,6 +75,8 @@ def extract_frames() -> ErrorCode:
def process_video() -> ErrorCode:
temp_frame_set = resolve_temp_frame_set(state_manager.get_item('target_path'))
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)
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:
@@ -84,7 +86,7 @@ def process_video() -> ErrorCode:
futures = []
for frame_number, temp_frame_path in temp_frame_set.items():
future = executor.submit(process_temp_frame, temp_frame_path, frame_number)
future = executor.submit(process_temp_frame, temp_frame_path, frame_number, temp_video_resolution)
futures.append(future)
for future in as_completed(futures):
@@ -154,14 +156,18 @@ def restore_audio() -> ErrorCode:
return 0
def process_temp_frame(temp_frame_path : str, frame_number : int) -> bool:
def process_temp_frame(temp_frame_path : str, 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'))
source_audio_path = get_first(filter_audio_paths(state_manager.get_item('source_paths')))
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_vision_frame = read_static_image(temp_frame_path, 'rgba')
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_mask = extract_vision_mask(temp_vision_frame)
source_audio_frame = get_audio_frame(source_audio_path, temp_video_fps, frame_number - trim_frame_start)