From 9ed5f0b528ab75ba6e2281502fa8bf75040f002c Mon Sep 17 00:00:00 2001 From: POM Date: Mon, 23 Jun 2025 11:05:39 +0200 Subject: [PATCH] Add when_to_start_control_frames parameter to VideoContinuationGenerator - Adds dropdown with 'beginning' and 'after overlap_frames' options - Controls when control frames become active in the continuation video - 'beginning' mode starts from first control frame regardless of overlap - 'after overlap_frames' mode skips overlap count to avoid duplication - Includes proper logic and tooltip explanation --- SteerableMotion.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/SteerableMotion.py b/SteerableMotion.py index 2a30100..ed6ed57 100644 --- a/SteerableMotion.py +++ b/SteerableMotion.py @@ -822,6 +822,7 @@ class VideoContinuationGenerator: "end_frame": ("IMAGE", {"tooltip": "Optional single frame to place at the end of the continuation video."}), "control_images": ("IMAGE", {"tooltip": "Optional control images to fill the empty frames."}), "inpaint_mask": ("MASK", {"tooltip": "Optional inpaint mask to use for the empty frames, overriding the default mask."}), + "when_to_start_control_frames": (["beginning", "after overlap_frames"], {"default": "after overlap_frames", "tooltip": "If at beginning, control frames won't actually be active until after the context, but after that they'll continue on from after the overlap_frames number. If after overlap_frames, the first control frame will be placed after the context is over."}), }, } @@ -831,7 +832,7 @@ class VideoContinuationGenerator: CATEGORY = "Steerable-Motion" DESCRIPTION = "Creates a continuation video by placing overlap frames from the end of input video at the start, with optional end frame." - def generate_continuation_video(self, input_video_frames, total_output_frames, overlap_frames, empty_frame_fill_level, end_frame=None, control_images=None, inpaint_mask=None): + def generate_continuation_video(self, input_video_frames, total_output_frames, overlap_frames, empty_frame_fill_level, end_frame=None, control_images=None, inpaint_mask=None, when_to_start_control_frames="after overlap_frames"): # 1. Validation and Setup total_output_frames = int(total_output_frames) if (total_output_frames - 1) % 4 != 0: @@ -876,18 +877,30 @@ class VideoContinuationGenerator: if num_middle_frames > 0: if control_images is not None: - log.info(f"Using 'control_images' to fill the {num_middle_frames} middle frames.") + log.info(f"Using 'control_images' to fill the {num_middle_frames} middle frames with '{when_to_start_control_frames}' mode.") control_images_resized = common_upscale(control_images.movedim(-1, 1), frame_width, frame_height, "lanczos", "disabled").movedim(1, -1) - if control_images_resized.shape[0] < num_middle_frames: - log.warning(f"Provided 'control_images' have {control_images_resized.shape[0]} frames, less than needed ({num_middle_frames}). Padding with 'empty_frame_fill_level'.") - padding_needed = num_middle_frames - control_images_resized.shape[0] - padding = torch.ones((padding_needed, frame_height, frame_width, num_channels), device=device, dtype=dtype) * empty_frame_fill_level - middle_frames_part = torch.cat([control_images_resized, padding], dim=0) - else: - if control_images_resized.shape[0] > num_middle_frames: - log.info(f"Provided 'control_images' have {control_images_resized.shape[0]} frames, more than needed ({num_middle_frames}). Using the first {num_middle_frames}.") - middle_frames_part = control_images_resized[:num_middle_frames].clone() + if when_to_start_control_frames == "beginning": + # Start from the beginning of control_images, regardless of overlap + if control_images_resized.shape[0] < num_middle_frames: + log.warning(f"Provided 'control_images' have {control_images_resized.shape[0]} frames, less than needed ({num_middle_frames}). Padding with 'empty_frame_fill_level'.") + padding_needed = num_middle_frames - control_images_resized.shape[0] + padding = torch.ones((padding_needed, frame_height, frame_width, num_channels), device=device, dtype=dtype) * empty_frame_fill_level + middle_frames_part = torch.cat([control_images_resized, padding], dim=0) + else: + middle_frames_part = control_images_resized[:num_middle_frames].clone() + else: # "after overlap_frames" + # Skip potential duplicate frames that overlap with the start section + duplicate_count = min(actual_overlap_frames, control_images_resized.shape[0]) + available_after_dup = control_images_resized.shape[0] - duplicate_count + if available_after_dup < num_middle_frames: + log.info(f"After removing {duplicate_count} overlapping frames, only {available_after_dup} control frames remain; padding {num_middle_frames - available_after_dup} frames with 'empty_frame_fill_level'.") + selected_control = control_images_resized[duplicate_count:] + padding_needed = num_middle_frames - selected_control.shape[0] + padding = torch.ones((padding_needed, frame_height, frame_width, num_channels), device=device, dtype=dtype) * empty_frame_fill_level + middle_frames_part = torch.cat([selected_control, padding], dim=0) + else: + middle_frames_part = control_images_resized[duplicate_count:duplicate_count + num_middle_frames].clone() else: log.info(f"No 'control_images', filling {num_middle_frames} middle frames with level {empty_frame_fill_level}.") middle_frames_part = torch.ones((num_middle_frames, frame_height, frame_width, num_channels), device=device, dtype=dtype) * empty_frame_fill_level