MCPcopy Create free account
hub / github.com/banodoco/Steerable-Motion / VideoContinuationGenerator

Class VideoContinuationGenerator

SteerableMotion.py:811–971  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

809 return (guidance_video_output.cpu().float(), final_frame_masks.cpu().float())
810
811class VideoContinuationGenerator:
812 @classmethod
813 def INPUT_TYPES(s):
814 return {
815 "required": {
816 "input_video_frames": ("IMAGE", {"tooltip": "Input video frames to create continuation from."}),
817 "total_output_frames": ("INT", {"default": 81, "min": 1, "max": 10000, "step": 4, "tooltip": "Total number of frames for the output continuation video. Must satisfy: (frames - 1) divisible by 4."}),
818 "overlap_frames": ("INT", {"default": 3, "min": 1, "max": 50, "step": 1, "tooltip": "Number of frames from the end of input video to use as overlap at the start."}),
819 "empty_frame_fill_level": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Grayscale level (0.0 black, 1.0 white) for empty continuation frames."}),
820 },
821 "optional": {
822 "end_frame": ("IMAGE", {"tooltip": "Optional single frame to place at the end of the continuation video."}),
823 "control_images": ("IMAGE", {"tooltip": "Optional control images to fill the empty frames."}),
824 "inpaint_mask": ("MASK", {"tooltip": "Optional inpaint mask to use for the empty frames, overriding the default mask."}),
825 "how_to_use_control_images": (["start_sequence_at_beginning_and_prioritise_input_frames", "start_sequence_after_overlap_frames_and_prioritise_input_frames"], {"default": "start_sequence_at_beginning_and_prioritise_input_frames", "tooltip": "If start_sequence_at_beginning_and_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_and_prioritise_input_frames is selected, control images start being placed after the overlap frames from the input video."}),
826 "how_to_use_inpaint_masks": (["start_sequence_at_beginning_and_prioritise_input_frames", "start_sequence_after_overlap_frames_and_prioritise_input_frames"], {"default": "start_sequence_at_beginning_and_prioritise_input_frames", "tooltip": "If start_sequence_at_beginning_and_prioritise_input_frames is selected, inpaint masks align with frame 0 but preserve input overlap frames as known. If start_sequence_after_overlap_frames_and_prioritise_input_frames is selected, inpaint masks only affect frames after the overlap period."}),
827 },
828 }
829
830 RETURN_TYPES = ("IMAGE", "MASK",)
831 RETURN_NAMES = ("continuation_video_frames", "continuation_frame_masks",)
832 FUNCTION = "generate_continuation_video"
833 CATEGORY = "Steerable-Motion"
834 DESCRIPTION = "Creates a continuation video by placing overlap frames from the end of input video at the start, with optional end frame."
835
836 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_and_prioritise_input_frames", how_to_use_inpaint_masks="start_sequence_at_beginning_and_prioritise_input_frames"):
837 # 1. Validation and Setup
838 total_output_frames = int(total_output_frames)
839 if (total_output_frames - 1) % 4 != 0:
840 raise ValueError("total_output_frames must satisfy (frames - 1) divisible by 4")
841
842 if input_video_frames is None or input_video_frames.shape[0] == 0:
843 log.error("Input video_frames is empty. Cannot proceed.")
844 dummy_height, dummy_width, dummy_channels = 64, 64, 3
845 return (torch.zeros((total_output_frames, dummy_height, dummy_width, dummy_channels), dtype=torch.float32),
846 torch.ones((total_output_frames, dummy_height, dummy_width), dtype=torch.float32))
847
848 device = input_video_frames.device
849 dtype = input_video_frames.dtype
850 batch_size_input, frame_height, frame_width, num_channels = input_video_frames.shape
851
852 # 2. Prepare Start Frames (from overlap)
853 actual_overlap_frames = min(overlap_frames, batch_size_input, total_output_frames)
854 if actual_overlap_frames < overlap_frames:
855 log.warning(f"Requested {overlap_frames} overlap frames but input video only has {batch_size_input} frames or total output is smaller. Using {actual_overlap_frames} instead.")
856
857 overlap_start_idx = batch_size_input - actual_overlap_frames
858 start_frames_part = input_video_frames[overlap_start_idx : overlap_start_idx + actual_overlap_frames].clone()
859
860 # 3. Prepare End Frame
861 end_frame_part = torch.empty((0, frame_height, frame_width, num_channels), device=device, dtype=dtype)
862 num_end_frames = 0
863 if end_frame is not None and end_frame.shape[0] > 0 and total_output_frames > actual_overlap_frames:
864 num_end_frames = 1
865 end_frame_processed = end_frame[0].clone().to(device=device, dtype=dtype)
866
867 if end_frame_processed.shape != (frame_height, frame_width, num_channels):
868 log.info(f"Resizing end_frame from {end_frame_processed.shape} to {(frame_height, frame_width, num_channels)}.")

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected