Save as Animated WebP
The loop that survives a chat window without turning into soup
- video
- images
- images
- info
You made a loop, and now it has to play somewhere that isn't your output folder. An MP4 won't autoplay inline in most of those places. A GIF will, but it's 256 colours, so your gradients and skin tones come back looking like they were faxed. Animated WebP threads that needle: autoplays like a GIF, carries full 24-bit colour, has real alpha.
Save as Animated WebP is this pack's export node: the front half of its sibling Save as GIF (frame-dropping, box scaling, tone curve) with the palette-and-dither block swapped for libwebp's three knobs. No palette to pick, nothing to dither.
Why this and not the stock node
ComfyUI already ships a SaveAnimatedWEBP and it works fine - but it takes an IMAGE batch, not a VIDEO, so the common case means breaking your clip into components and remembering its rate. This one takes the VIDEO directly and reads the rate off it. It's also the pack's return leg: Load GIF as Video reads animated WebP back in as a VIDEO, so a loop can go out and come back as source.
How it works
Resolve the source: a connected video contributes its own frames and frame rate, or an images batch is timed by source_fps. Both is refused - two frame rates is an argument the node can't win.
Then the shared pipeline - drop frames down to fps (same wall-clock duration, fewer frames), scale into the width × height box keeping aspect ratio, apply gamma, round to 8-bit RGB. Only then does libwebp get involved, through Pillow's animated-WebP writer. The encode runs in memory, the bytes land in your output folder as {filename_prefix}_{counter:05}_.webp, and the node then reads that file back to build the images output. Lossless, that's bit-exact; lossy, it's what a viewer sees - which is what you tune quality against. Nothing hits disk until the encode finishes, so a cancel leaves no half-file.
Hard limits before you hit them: 10,000 frames, 8,192 pixels a side, fps capped at 60.
The inputs you'll actually touch
The two optional slots are the decision - video or images, one or the other. Everything else is a required widget with a usable default.
fps(12) - the rate the WebP is written at. Below the source rate it drops frames: same running time, fewer frames, smaller file. Roughly linear, and it costs nothing in the frames that survive. Above the source rate it does nothing; duplicating frames buys bytes and shows nothing new.lossless(on) - every pixel stored exactly. Off gives lossy VP8, dramatically smaller on rendered material. The author's own numbers on a 192×192 twelve-frame clip: GIF at 64 colours 161 KB, lossless 140 KB, lossy at 80 about 5.8 KB.quality/method- under lossy,qualityis the familiar dial. Under lossless both only change encoder effort; the pixels are exact either way, so don't chase it. The README's own note:quality100 withmethod6 took fifty times as long and came out 4% larger.
Beyond those: width/height, where 0 means keep the source or derive from the other axis, size_mode choosing pad (exactly that canvas, transparent bars) or fit (box as an upper bound, no bars), and resample, where nearest is the specialist that keeps pixel art and hard-edged animation crisp. loop_count 0 loops forever.
Two outputs. images is the frames as written - into Preview Image, or diffed against your input. info is a STRING with filename, real byte size, frame count, canvas, rate and runtime; wire it to Preview as Text (PreviewAny) and tune against that.
Install
ComfyUI Manager → search the pack title Load GIF as Video, or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/id-fa/ComfyUI-LoadGifAsVideo
Restart ComfyUI. There's genuinely nothing to install: the pack's pyproject.toml declares an empty dependency list, and Pillow, NumPy, PyTorch and PyAV already ship with ComfyUI. No requirements file exists here, whatever a guide tells you. You do need a ComfyUI recent enough to have the VIDEO type (comfy_api.input_impl); older builds load the node and raise when it runs.
Common issues
"Video failed to load / Invalid URL" on the node. Not your file. ComfyUI's frontend reads "has a VIDEO input slot" as "preview this node as video", and an unconnected slot counts - so it hands your .webp to a video player and the URL parse throws. The pack ships web/save_as_gif_preview.js to pin the preview back to image; if you see the error, that script didn't load. Update the pack, then hard-refresh the browser (Ctrl+Shift+R).
"Connect either video or images, not both." You wired both. Related: "Connect a video or an images batch to save" when neither is linked.
The images preview doesn't match the file. With pad, the bars are real alpha in the WebP, but images has no alpha channel, so they come back black. The picture itself is always opaque - neither VIDEO nor IMAGE carries alpha in ComfyUI, so composite upstream if you need transparency inside the frame.
Keep the MP4 or PNG sequence as the master, though. Once a clip has been through lossy VP8 there's no way back - let this node make the copy people look at, not the one you archive.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| filename_prefix | STRING | ComfyUI | Prefix for the file written into the ComfyUI output directory. |
| source_fps | FLOAT | 12.00.1–240 | The rate an `images` batch is meant to play at. Ignored when `video` is connected — a VIDEO carries its own rate. This is only the description of the input; `fps` is what gets written. |
| fps | FLOAT | 12.00.1–60 | Frame rate of the WebP. Set it below the source rate to drop frames: the animation runs for the same length of time on fewer frames, which is the cheapest way to shrink the file. Raising it above the source rate does nothing. |
| width | INT | 00–8192 | Output width in pixels. 0 keeps the source width, or derives it from `height`. With both set, the frame is scaled to fit inside the box keeping its aspect ratio; `size_mode` decides what happens to the room left over. |
| height | INT | 00–8192 | Output height in pixels. 0 keeps the source height, or derives it from `width`. |
| size_mode | COMBO | pad | What `width` x `height` means when the source has a different aspect ratio. pad: the box is the canvas — the frame is centred and the leftover bars are written transparent, so the WebP is exactly the requested size. fit: the frame is scaled to the largest size that fits inside the box and written at that size, with no bars. Same thing whenever only one of width/height is set. |
| resample | COMBO | lanczos | Filter used when scaling. lanczos is the sharpest downscale; box and hamming are softer and compress a little better; nearest keeps pixel art and hard-edged animation crisp instead of blurring it. |
| lossless | BOOLEAN | true | Lossless WebP stores every pixel exactly, so the file decodes back to the `images` output bit for bit; `quality` then only trades encode time for file size. Off writes lossy WebP (VP8), where `quality` is the usual quality/size trade and is much smaller on photographic material. |
| quality | INT | 800–100 | Lossy: picture quality, 0 (smallest, ugliest) to 100 (largest, nearly lossless). Lossless: compression effort, 0 (fastest) to 100 (smallest) — the pixels are exact either way. |
| method | INT | 40–6 | How hard libwebp tries, 0 (fastest) to 6 (slowest, smallest). Never changes the picture at a given quality, only the time and the size; 6 can take several times as long as 4 for a few percent off the file. |
| loop_count | INT | 00–1000 | How many times the animation plays. 0 loops forever. |
| gamma | FLOAT | 1.000.1–10 | Tone correction applied to the frames before they are encoded. 1.0 leaves them alone; above it brightens the midtones, below it darkens them, and black and white stay put. The `images` output shows the corrected result. |
| videoopt | VIDEO | The video to save. Its frame rate is used as-is. | |
| imagesopt | IMAGE | Frames to save, timed by the `source_fps` widget. Connect this or `video`, not both. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| info | STRING | — |