From 888cdd1eca371a50fc3ae668879848ddcd5ba2f2 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 30 Jun 2026 22:32:10 +0100 Subject: [PATCH] feat(video): support first-last-frame for Wan 2.2 I2V The I2V path forwards a last-frame image to the pipeline when one is supplied and the loaded pipeline accepts it, turning the run into first-last-frame interpolation. supports_last_frame() gates on the pipeline taking a last_image argument and not running expand_timesteps, which conditions on the first frame only, so a model that cannot use a last frame logs a warning instead of silently ignoring it. --- modules/video_models/video_run.py | 8 +++++++- modules/video_models/video_utils.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/modules/video_models/video_run.py b/modules/video_models/video_run.py index 569220a45..93a666480 100644 --- a/modules/video_models/video_run.py +++ b/modules/video_models/video_run.py @@ -67,7 +67,13 @@ def generate(*args, **kwargs): if init_image is None: return video_utils.queue_err('No input image provided. Please upload or select an image.') p.task_args['image'] = images.resize_image(resize_mode=2, im=init_image, width=p.width, height=p.height, upscaler_name=None, output_type='pil') - log.debug(f'Video: op=I2V init={init_image} resized={p.task_args["image"]}') + if last_image is not None and video_utils.supports_last_frame(shared.sd_model): + p.task_args['last_image'] = images.resize_image(resize_mode=2, im=last_image, width=p.width, height=p.height, upscaler_name=None, output_type='pil') + log.debug(f'Video: op=FLF2V init={init_image} last={last_image} resized={p.task_args["image"]}') + elif last_image is not None: + log.warning(f'Video: op=I2V model="{model}" last frame not supported, ignoring') + else: + log.debug(f'Video: op=I2V init={init_image} resized={p.task_args["image"]}') elif 'FLF2V' in model: if init_image is None: return video_utils.queue_err('No input image provided. Please upload or select an image.') diff --git a/modules/video_models/video_utils.py b/modules/video_models/video_utils.py index 18ae1249d..090b850e8 100644 --- a/modules/video_models/video_utils.py +++ b/modules/video_models/video_utils.py @@ -1,6 +1,7 @@ import os import sys import time +import inspect from PIL import Image from installer import install from modules import shared, sd_models, timer, errors, devices @@ -19,6 +20,18 @@ def get_url(url): return f'{url}

' if url else '

' +def supports_last_frame(model): + # last-frame (FLF2V) conditioning needs a pipeline whose __call__ accepts `last_image`. + # wan 2.2 5b accepts the arg but masks timesteps from the first frame only, so it drops the last frame. + try: + params = list(inspect.signature(type(model).__call__, follow_wrapped=True).parameters) + except (ValueError, TypeError): + return False + if 'last_image' not in params: + return False + return not getattr(getattr(model, 'config', None), 'expand_timesteps', False) + + def check_av(): install('av') try: