Simplify VideoContinuationGenerator: Improve parameter naming and remove complex replacement logic

- Rename parameters for maximum clarity:
  • when_to_start_control_images → how_to_use_control_images
  • when_to_start_inpaint_masks → how_to_use_inpaint_masks
- Update option names to clearly indicate input frame prioritization:
  • beginning_of_generation → start_sequence_at_beginning_but_prioritise_input_frames
  • after_overlap_frames → start_sequence_after_overlap_frames_prioritise_input_frames
- Remove instead_of_input_frames option and all complex replacement logic
- Simplify to two clear options that always prioritize input overlap frames
- Improve tooltips to clearly explain prioritization behavior
- Focus on user-friendly workflows that preserve input video structure
This commit is contained in:
POM
2025-06-23 16:06:38 +02:00
parent 76264adfc7
commit e0ce6ad576
+14 -46
View File
@@ -822,8 +822,8 @@ 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_images": (["beginning_of_generation", "after_overlap_frames", "instead_of_input_frames"], {"default": "beginning_of_generation", "tooltip": "If beginning_of_generation is selected, aligns control images with the start of the output (frame 0). Overlap frames from the input video will take priority, so control images will become visible starting after the overlap period. If after_overlap_frames is selected, control images will start being placed after the overlap frames from the input video. If instead_of_input_frames is selected, control images will replace the overlap frames entirely."}),
"when_to_start_inpaint_masks": (["beginning_of_generation", "after_overlap_frames", "instead_of_input_frames"], {"default": "beginning_of_generation", "tooltip": "If beginning_of_generation is selected, aligns inpaint masks with the start of the output (frame 0). If after_overlap_frames is selected, inpaint masks will start being placed after the overlap frames from the input video. If instead_of_input_frames is selected, inpaint masks will replace the overlap frames entirely."}),
"how_to_use_control_images": (["start_sequence_at_beginning_but_prioritise_input_frames", "start_sequence_after_overlap_frames_prioritise_input_frames"], {"default": "start_sequence_at_beginning_but_prioritise_input_frames", "tooltip": "If start_sequence_at_beginning_but_prioritise_input_frames is selected, control images align with frame 0 but input overlap frames take priority, so control images become visible after the overlap period. If start_sequence_after_overlap_frames_prioritise_input_frames is selected, control images start being placed after the overlap frames from the input video."}),
"how_to_use_inpaint_masks": (["start_sequence_at_beginning_but_prioritise_input_frames", "start_sequence_after_overlap_frames_prioritise_input_frames"], {"default": "start_sequence_at_beginning_but_prioritise_input_frames", "tooltip": "If start_sequence_at_beginning_but_prioritise_input_frames is selected, inpaint masks align with frame 0 but preserve input overlap frames as known. If start_sequence_after_overlap_frames_prioritise_input_frames is selected, inpaint masks only affect frames after the overlap period."}),
},
}
@@ -833,7 +833,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, when_to_start_control_images="beginning_of_generation", when_to_start_inpaint_masks="beginning_of_generation"):
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, how_to_use_control_images="start_sequence_at_beginning_but_prioritise_input_frames", how_to_use_inpaint_masks="start_sequence_at_beginning_but_prioritise_input_frames"):
# 1. Validation and Setup
total_output_frames = int(total_output_frames)
if (total_output_frames - 1) % 4 != 0:
@@ -878,10 +878,10 @@ 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 with '{when_to_start_control_images}' mode.")
log.info(f"Using 'control_images' to fill the {num_middle_frames} middle frames with '{how_to_use_control_images}' mode.")
control_images_resized = common_upscale(control_images.movedim(-1, 1), frame_width, frame_height, "lanczos", "disabled").movedim(1, -1)
if when_to_start_control_images == "beginning_of_generation":
if how_to_use_control_images == "start_sequence_at_beginning_but_prioritise_input_frames":
# Skip the first overlap_frames control images to avoid duplication
duplicate_count = min(actual_overlap_frames, control_images_resized.shape[0])
available_after_dup = control_images_resized.shape[0] - duplicate_count
@@ -893,7 +893,7 @@ class VideoContinuationGenerator:
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()
elif when_to_start_control_images == "after_overlap_frames":
else: # "start_sequence_after_overlap_frames_prioritise_input_frames"
# Use control frames from the beginning of the sequence (C0, C1, C2...)
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'.")
@@ -902,21 +902,6 @@ class VideoContinuationGenerator:
middle_frames_part = torch.cat([control_images_resized, padding], dim=0)
else:
middle_frames_part = control_images_resized[:num_middle_frames].clone()
else: # "instead_of_input_frames"
# Replace overlap frames with control images, use remaining for middle frames and potentially end frame
total_frames_needed = actual_overlap_frames + num_middle_frames + num_end_frames
if control_images_resized.shape[0] < total_frames_needed:
log.warning(f"Provided 'control_images' have {control_images_resized.shape[0]} frames, less than needed ({total_frames_needed} = {actual_overlap_frames} overlap + {num_middle_frames} middle + {num_end_frames} end). Padding with 'empty_frame_fill_level'.")
padding_needed = total_frames_needed - control_images_resized.shape[0]
padding = torch.ones((padding_needed, frame_height, frame_width, num_channels), device=device, dtype=dtype) * empty_frame_fill_level
extended_control = torch.cat([control_images_resized, padding], dim=0)
else:
extended_control = control_images_resized[:total_frames_needed].clone()
# Split into overlap replacement, middle parts, and end frame replacement
start_frames_part = extended_control[:actual_overlap_frames]
middle_frames_part = extended_control[actual_overlap_frames:actual_overlap_frames + num_middle_frames]
if num_end_frames > 0:
end_frame_part = extended_control[actual_overlap_frames + num_middle_frames:actual_overlap_frames + num_middle_frames + num_end_frames]
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
@@ -927,8 +912,8 @@ class VideoContinuationGenerator:
# 6. Create Mask
continuation_frame_masks = torch.ones((total_output_frames, frame_height, frame_width), device=device, dtype=dtype)
# Apply mask logic based on when_to_start_inpaint_masks parameter
if when_to_start_inpaint_masks == "beginning_of_generation":
# Apply mask logic based on how_to_use_inpaint_masks parameter
if how_to_use_inpaint_masks == "start_sequence_at_beginning_but_prioritise_input_frames":
# Set known frames (overlap and end) to 0.0, but also set middle section based on control frame logic
if actual_overlap_frames > 0:
continuation_frame_masks[0:actual_overlap_frames] = 0.0
@@ -944,29 +929,16 @@ class VideoContinuationGenerator:
middle_start = actual_overlap_frames
middle_end = middle_start + num_middle_frames
continuation_frame_masks[middle_start:middle_end] = 0.0
elif when_to_start_inpaint_masks == "after_overlap_frames":
else: # "start_sequence_after_overlap_frames_prioritise_input_frames"
# Set known frames (overlap and end) to 0.0, rest stay as 1.0 (inpaint)
if actual_overlap_frames > 0:
continuation_frame_masks[0:actual_overlap_frames] = 0.0
if num_end_frames > 0:
continuation_frame_masks[-num_end_frames:] = 0.0
else: # "instead_of_input_frames"
# All frames become inpaint areas (1.0) by default when replacing input frames entirely
# If control images replace all frames (overlap + middle + end), those areas can be set as known
if control_images is not None and when_to_start_control_images == "instead_of_input_frames":
total_frames_needed = actual_overlap_frames + num_middle_frames + num_end_frames
if control_images.shape[0] >= total_frames_needed:
# Set all sections as known since we have enough control images to replace everything
continuation_frame_masks[:] = 0.0
elif control_images.shape[0] >= actual_overlap_frames + num_middle_frames:
# Set middle section as known since we have enough control images for overlap + middle
middle_start = actual_overlap_frames
middle_end = middle_start + num_middle_frames
continuation_frame_masks[middle_start:middle_end] = 0.0
# 7. Handle optional inpaint_mask with when_to_start_inpaint_masks logic
# 7. Handle optional inpaint_mask with how_to_use_inpaint_masks logic
if inpaint_mask is not None:
log.info(f"Processing provided 'inpaint_mask' with '{when_to_start_inpaint_masks}' timing.")
log.info(f"Processing provided 'inpaint_mask' with '{how_to_use_inpaint_masks}' timing.")
processed_mask = common_upscale(inpaint_mask.unsqueeze(1), frame_width, frame_height, "nearest-exact", "disabled").squeeze(1).to(device)
if processed_mask.shape[0] != total_output_frames:
@@ -977,24 +949,20 @@ class VideoContinuationGenerator:
else:
processed_mask = processed_mask[:total_output_frames]
# Apply when_to_start_inpaint_masks logic to the provided mask
if when_to_start_inpaint_masks == "beginning_of_generation":
# Apply how_to_use_inpaint_masks logic to the provided mask
if how_to_use_inpaint_masks == "start_sequence_at_beginning_but_prioritise_input_frames":
# Use the provided mask as-is, but preserve known frames (overlap and end)
if actual_overlap_frames > 0:
processed_mask[0:actual_overlap_frames] = 0.0 # Keep overlap frames as known
if num_end_frames > 0:
processed_mask[-num_end_frames:] = 0.0 # Keep end frame as known
elif when_to_start_inpaint_masks == "after_overlap_frames":
else: # "start_sequence_after_overlap_frames_prioritise_input_frames"
# Only apply the provided mask after overlap frames
if actual_overlap_frames > 0:
processed_mask[0:actual_overlap_frames] = 0.0 # Keep overlap frames as known
# The provided mask affects frames starting after overlap
if num_end_frames > 0:
processed_mask[-num_end_frames:] = 0.0 # Keep end frame as known
else: # "instead_of_input_frames"
# Use the provided mask starting from frame 0, affecting the entire timeline
# No frames are preserved as "known" - the provided mask has full control
pass # The provided mask affects the entire timeline including overlap frames and end frame
continuation_frame_masks = processed_mask.to(dtype=dtype)