fix: robust trim/crop for edge-case videos and pass-through previews

This commit is contained in:
Terry Jia
2026-08-09 18:10:55 -04:00
parent e567f78876
commit 39374ea8eb
2 changed files with 35 additions and 8 deletions

View File

@@ -606,8 +606,13 @@ class VideoFromFile(VideoInput):
duration_cap = math.ceil(duration * sample_rate)
import comfy.utils
raw_duration = self._get_raw_duration()
window_seconds = duration if duration else max(raw_duration - start_time, 0.0)
if duration:
window_seconds = duration
else:
try:
window_seconds = max(self._get_raw_duration() - start_time, 0.0)
except ValueError:
window_seconds = 0.0
progress_total = max(1, int(round(window_seconds * float(rate))))
pbar = comfy.utils.ProgressBar(progress_total)
@@ -707,6 +712,12 @@ class VideoFromFile(VideoInput):
crop_rect = normalize_crop_rect(*self.__crop, out_width, out_height)
if crop_rect is not None:
out_width, out_height = crop_rect[2], crop_rect[3]
if (out_width % 2 or out_height % 2) and crop_rect is None:
even_width = out_width - out_width % 2
even_height = out_height - out_height % 2
if even_width > 0 and even_height > 0:
crop_rect = (0, 0, even_width, even_height)
out_width, out_height = even_width, even_height
if out_width % 2 or out_height % 2:
raise ValueError(f"H.264 output requires even dimensions, got {out_width}x{out_height}")
source_size = (frame.width, frame.height)
@@ -873,7 +884,7 @@ class VideoFromFile(VideoInput):
duration=duration,
crop=self.__crop,
)
if trimmed.get_duration() < duration and strict_duration:
if strict_duration and duration and trimmed.get_duration() < duration:
return None
return trimmed

View File

@@ -250,7 +250,7 @@ class LoadVideo(io.ComfyNode):
video = apply_video_trim(source, (edit or {}).get("trim"))
video = apply_video_crop(video, (edit or {}).get("crop"))
if video is source:
return io.NodeOutput(video, ui=preview_input_video(file))
return io.NodeOutput(video, ui=preview_input_video(file, source))
return io.NodeOutput(video, ui=save_video_preview(video))
@classmethod
@@ -268,25 +268,41 @@ class LoadVideo(io.ComfyNode):
return True
def preview_input_video(file: str) -> ui.PreviewVideo:
def preview_input_video(file: str, video: Input.Video | None = None) -> ui.PreviewVideo:
name, _ = folder_paths.annotated_filepath(file)
subfolder, _, filename = name.replace("\\", "/").rpartition("/")
return ui.PreviewVideo([ui.SavedResult(filename, subfolder, io.FolderType.input)])
result = ui.SavedResult(filename, subfolder, io.FolderType.input)
if video is not None:
try:
video._preview_result = (folder_paths.get_annotated_filepath(file), result)
except AttributeError:
pass
return ui.PreviewVideo([result])
def save_video_preview(video: Input.Video) -> ui.PreviewVideo:
cached = getattr(video, "_preview_result", None)
if cached is not None and os.path.isfile(cached[0]):
return ui.PreviewVideo([cached[1]])
width, height = video.get_dimensions()
full_output_folder, filename, counter, subfolder, _ = folder_paths.get_save_image_path(
"ComfyUI_temp_video", folder_paths.get_temp_directory(), width, height
)
preview_format = Types.VideoContainer.MP4
file = f"{filename}_{counter:05}_.{Types.VideoContainer.get_extension(preview_format)}"
full_path = os.path.join(full_output_folder, file)
video.save_to(
os.path.join(full_output_folder, file),
full_path,
format=preview_format,
codec="auto",
)
return ui.PreviewVideo([ui.SavedResult(file, subfolder, io.FolderType.temp)])
result = ui.SavedResult(file, subfolder, io.FolderType.temp)
try:
video._preview_result = (full_path, result)
except AttributeError:
pass
return ui.PreviewVideo([result])
def apply_video_trim(video: Input.Video, trim, strict_duration: bool = False) -> Input.Video: