From f4d4dfaa66ffa4bce0b00ebbb3eb57d648f02f95 Mon Sep 17 00:00:00 2001 From: octo-patch Date: Thu, 9 Apr 2026 12:33:21 +0800 Subject: [PATCH 1/2] fix: change SaveAnimatedWEBP default method from 'default' to 'fastest' (fixes #13300) The 'default' compression method maps to WebP method=4, which is significantly slower than method=0 ('fastest'). For animated WebP with many frames (e.g. 120 frames of video), this resulted in encoding times of 2+ minutes. Changing the node default to 'fastest' (method=0) reduces encoding time by ~3x while still allowing users to select higher compression methods when needed. --- comfy_extras/nodes_images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py index a77f0641f..3d47f44a0 100644 --- a/comfy_extras/nodes_images.py +++ b/comfy_extras/nodes_images.py @@ -196,7 +196,7 @@ class SaveAnimatedWEBP(IO.ComfyNode): IO.Float.Input("fps", default=6.0, min=0.01, max=1000.0, step=0.01), IO.Boolean.Input("lossless", default=True), IO.Int.Input("quality", default=80, min=0, max=100), - IO.Combo.Input("method", options=list(cls.COMPRESS_METHODS.keys())), + IO.Combo.Input("method", options=list(cls.COMPRESS_METHODS.keys()), default="fastest"), # "num_frames": ("INT", {"default": 0, "min": 0, "max": 8192}), ], hidden=[IO.Hidden.prompt, IO.Hidden.extra_pnginfo], From 66c4fde38cd0fdc1e4f6980cdc912504fa2fd64a Mon Sep 17 00:00:00 2001 From: octo-patch Date: Sat, 11 Apr 2026 13:25:41 +0800 Subject: [PATCH 2/2] fix: ensure SaveWEBM container is always closed on error (fixes #9115) Wrap the av.Container operations in SaveWEBM.execute with try/finally to guarantee container.close() is called even when an exception occurs during encoding (e.g. OOM, codec error). Without this, the output file handle could remain open, causing 'file is open in Python' errors on Windows when trying to delete or overwrite the output file. Consistent with the pattern already used in encode_single_frame() and decode_single_frame() in comfy_extras/nodes_lt.py. --- comfy_extras/nodes_video.py | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/comfy_extras/nodes_video.py b/comfy_extras/nodes_video.py index 5c096c232..d4d0dadff 100644 --- a/comfy_extras/nodes_video.py +++ b/comfy_extras/nodes_video.py @@ -39,29 +39,31 @@ class SaveWEBM(io.ComfyNode): file = f"{filename}_{counter:05}_.webm" container = av.open(os.path.join(full_output_folder, file), mode="w") - if cls.hidden.prompt is not None: - container.metadata["prompt"] = json.dumps(cls.hidden.prompt) + try: + if cls.hidden.prompt is not None: + container.metadata["prompt"] = json.dumps(cls.hidden.prompt) - if cls.hidden.extra_pnginfo is not None: - for x in cls.hidden.extra_pnginfo: - container.metadata[x] = json.dumps(cls.hidden.extra_pnginfo[x]) + if cls.hidden.extra_pnginfo is not None: + for x in cls.hidden.extra_pnginfo: + container.metadata[x] = json.dumps(cls.hidden.extra_pnginfo[x]) - codec_map = {"vp9": "libvpx-vp9", "av1": "libsvtav1"} - stream = container.add_stream(codec_map[codec], rate=Fraction(round(fps * 1000), 1000)) - stream.width = images.shape[-2] - stream.height = images.shape[-3] - stream.pix_fmt = "yuv420p10le" if codec == "av1" else "yuv420p" - stream.bit_rate = 0 - stream.options = {'crf': str(crf)} - if codec == "av1": - stream.options["preset"] = "6" + codec_map = {"vp9": "libvpx-vp9", "av1": "libsvtav1"} + stream = container.add_stream(codec_map[codec], rate=Fraction(round(fps * 1000), 1000)) + stream.width = images.shape[-2] + stream.height = images.shape[-3] + stream.pix_fmt = "yuv420p10le" if codec == "av1" else "yuv420p" + stream.bit_rate = 0 + stream.options = {'crf': str(crf)} + if codec == "av1": + stream.options["preset"] = "6" - for frame in images: - frame = av.VideoFrame.from_ndarray(torch.clamp(frame[..., :3] * 255, min=0, max=255).to(device=torch.device("cpu"), dtype=torch.uint8).numpy(), format="rgb24") - for packet in stream.encode(frame): - container.mux(packet) - container.mux(stream.encode()) - container.close() + for frame in images: + frame = av.VideoFrame.from_ndarray(torch.clamp(frame[..., :3] * 255, min=0, max=255).to(device=torch.device("cpu"), dtype=torch.uint8).numpy(), format="rgb24") + for packet in stream.encode(frame): + container.mux(packet) + container.mux(stream.encode()) + finally: + container.close() return io.NodeOutput(ui=ui.PreviewVideo([ui.SavedResult(file, subfolder, io.FolderType.output)]))