Save as GIF
The node that finally gets animated GIFs out of ComfyUI looking right
- video
- images
- images
- info
ComfyUI will happily hand you an MP4 or an animated WEBP, but for an actual .gif the stock graph has no answer - which is why the standard advice is "just pipe it through ffmpeg afterwards." This node is the answer that lives inside the workflow instead. Feed it the VIDEO output of a generation (or any Load Video, or even your SaveAnimatedWEBP path), and it writes an animated GIF with real control over how the 256-color limit is cheated. It's the export half of the ComfyUI-LoadGifAsVideo pack - its siblings turn looped GIFs into video, this one turns video back into GIF - but it stands perfectly well on its own on top of any stock video.
What you're actually tuning
GIF can hold at most 256 colors, so writing a good one is two problems: which colors the palette gets, and how everything else pretends to be those colors. The first is quantizer, the second dither, and both are where this node is several rungs above the -coalesce+ffmpeg pipeline most people fall back to.
Reality check: the defaults (median_cut_aforge + floyd-steinberg) look fine on 90% of photographic frames, and the wall of 21 dither options is mostly there for animation purists. Don't let it intimidate you. You'll touch a handful of inputs in practice, and the README is honest about which.
The knobs that actually matter
fps- the cheapest lever in the node. Set it below the source rate and it drops frames rather than slowing the animation down: 60 frames of 24fps material atfps = 8becomes 20 frames that still run for 2.5 seconds. File size drops roughly linearly and the frames that survive lose nothing. Set it above the source rate and nothing happens - there's nothing new to show. The widget caps at 50fps because that's the format's real ceiling: GIF stores delays in hundredths of a second and every browser re-times anything under 2cs to 10cs.colors(2–256) - fewer colors, smaller file, stronger dither pattern. Leavepalette_scopeonglobalunless your animation changes content a lot; per-frame palettes are more accurate there but flicker in flat areas and pay 768 bytes per frame on a 256-color table.quantizer- the default median-cut is even and predictable.diversityandblend_diversityare the ones that hold onto small bright accents that median cut averages away. Pick one if the defaults band; otherwise, don't lose sleep.halftone-poster- the secret weapon for tiny files. It ignores the palette machinery entirely and rounds each channel onto an even RGB grid, ImageMagick-style, throwing away most of the tone on purpose. Measured by the author, an 8-color square halftone cut a 164 KB baseline down to 20 KB. Want a print-looking meme, or a file small enough to email? This is the mode. Note it ignoresquantizerandpalette_scope- it dictates its own palette.
width and height (0 = keep source) letterbox with transparent bars if your aspect ratio disagrees with the canvas, which is a nice touch - a 4:3 clip on a square canvas doesn't get squashed.
What comes out
Two outputs. The images one is the dithered frames as an IMAGE batch - byte-for-byte what went into the file - handy for comparing settings without opening anything. The info STRING is the one you'll actually want: filename, size in KB, frame count, canvas, fps, running time, palette size and dither. Wire it to the stock Preview as Text node and you can watch the file size move as you tune. The ui result also previews correctly in the graph as an animated image - the author deliberately fought the frontend's video-preview heuristic to make that work.
Install
Via ComfyUI Manager (search "LoadGifAsVideo"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/id-fa/ComfyUI-LoadGifAsVideo
Then restart ComfyUI. There are no extra Python dependencies - the source's pyproject.toml is empty, because Pillow, NumPy and PyTorch all ship with ComfyUI already. Files land in your output directory with a counter suffix (ComfyUI_00042_.gif). One real requirement: because this node has a VIDEO input, you need a ComfyUI build with the VIDEO type. On older builds the node loads but raises a clear error when you run it.
Gotchas worth knowing
Connect video or images, never both - it raises rather than guessing which frame rate you meant. If you're feeding an image batch, source_fps tells it how fast those frames play; with a video connected it's ignored because the video carries its own rate. And if you letterbox, the transparent slot costs one palette entry, so you're quantizing to 255 colors, not 256 - negligible, just don't chase a file that "should" have all 256.
One honest caveat: this is a niche, low-traffic pack from a solo author, so don't expect a huge support community around it. But for the exact moment where you need a good GIF out of the graph - not a banded ffmpeg afterthought - it's the cleanest path in the ecosystem.
Inputs (17)
| 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–50 | Frame rate of the GIF. 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. GIF cannot store a delay shorter than 1/50s. |
| 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 canvas keeping its aspect ratio, and the leftover bars are written transparent. |
| height | INT | 00–8192 | Output height in pixels. 0 keeps the source height, or derives it from `width`. |
| resample | COMBO | lanczos | Filter used when scaling. lanczos is the sharpest downscale; box and hamming are softer and quieter to dither; nearest keeps pixel art and hard-edged animation crisp instead of blurring it. |
| colors | INT | 2562–256 | Palette size. GIF allows at most 256 colors; fewer makes a smaller file and a stronger dither pattern. The -poster dithers round this down to a cube (8, 27, 64, 125, 216) because they quantize each channel on its own. |
| palette_scope | COMBO | global | global: one palette for the whole animation — no color flicker, smaller file. per_frame: a fresh palette per frame — better color on animations whose content changes a lot. Ignored by the -poster dithers, whose palette does not depend on the frames at all. |
| quantizer | COMBO | median_cut_aforge | How the palette is chosen. median_cut_* split the color cube; diversity picks popular and far-apart colors in turn, which holds onto small bright accents that median cut averages away. Ignored by the -poster dithers, which dictate their own even RGB grid. |
| dither | COMBO | floyd-steinberg | How colors the palette does not hold are approximated. Error diffusion looks cleanest on photographic frames; the ordered and halftone screens are stable frame to frame, so they do not crawl on animation. halftone/halftone-square cap a cell at two colors for a printed-ink look; their -ordered variants lift that cap for smoother tone. The -mask pair lays a single flat ink dot (the halftone_ink color) over the picture and leaves the rest of the frame alone; its lattice is the same in every frame, so it never crawls. The -poster pair is ImageMagick's -ordered-dither: it ignores the palette entirely and rounds each channel to an even RGB grid, throwing away most of the tone for a much smaller file. |
| dither_strength | FLOAT | 1.000–1 | How much of the quantization error is dithered away. 0 disables the dither entirely; lower values trade banding back for less noise. For the -mask dithers this is the screen's coverage instead: how much of each cell the dot fills, reaching half at 1.0. |
| halftone_size | INT | 62–64 | Width of one halftone cell in pixels (any of the halftone dithers). For the palette-searching screens, larger dots carry less of the image and compress far better. For the -poster pair the cell also sets the number of threshold steps, so the file peaks around size 8 and only shrinks again past 16 — there, small cells give the smallest file and large cells the boldest dots. |
| halftone_steps | INT | 00–255 | How many tonal steps one halftone cell resolves. 0 gives one per cell, which is what the cell size implies on its own. Setting it lower separates the dot size from the tone: big dots with coarse tone, which is the smallest a halftone gets. Ignored by the -mask dithers, whose dot is one fixed size. |
| halftone_ink | COMBO | black | Which end of the tonal range the halftone dot grows from. black: dark dots on a light ground, the way ink sits on paper. white: light dots out of a dark ground. For the -mask dithers this names the ink itself — real black or real white — and it is reserved in the palette. |
| loop_count | INT | 00–1000 | How many extra times the GIF replays. 0 loops forever. |
| videoopt | VIDEO | The video to save. Its frame rate is used as-is. | |
| imagesopt | IMAGE | Frames to save, timed by the `fps` widget. Connect this or `video`, not both. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| info | STRING | — |