ComfyUI Node

Save Video (C2C)

Save Video (C2C) does ProRes, EXR sequences and stream-decode

By Code2Collapse·Created 9 months ago·Updated a day ago· 60
Save Video (C2C)
  • images
  • audio
  • video_info
  • latent
  • vae
  • filenames
  • preview
◄formatcustom (container / codec)►
◄fps24.000►
◄filename_prefixC2C/video►
◄quality80►
◄colorspace_insRGB - Display►
◄colorspace_outsame as input►
◄namingdefault►
◄naming_resolved►
◄subfolder►
◄save_outputtrue►
◄tile_modeauto►
◄tile_size512►
◄save_metadatatrue►
◄containerMP4►
◄codecH.264►
◄profileHigh►
◄bit_depth8►
◄chroma4:2:0►
◄color_primariesauto►
◄color_transferauto►
◄color_rangeauto►
◄handles_trimauto►
◄trim_frames8►
◄trim_sidehead►
◄range_in0►
◄range_out0►
◄write_modewrite►
◄write_token►
◄format_familyVideo►
◄subformatfrom the controls►
◄encoder_presetdefault►
◄gop0►
◄bitrate_kbps0►
◄channelsauto►
◄png_compression-1►
◄follow_loadauto►
◄review_copyfalse►
◄burn_instandard►
◄burn_in_template{shot} {version}|{frame} {tc}|{colour}|{date}►
◄burn_in_artist►

Two separate problems, one node. The first: the default way to save a clip in ComfyUI is VAE Decode → Video Combine, which materialises the whole frame batch in RAM before anything gets written. The second is delivery - video_00003.mp4 is fine to post and useless the moment the shot is going into Resolve.

Save Video (C2C) is an output node that writes an IMAGE batch into sixteen formats, muxes audio, optionally applies an OCIO colour transform, and shows a browser preview with a timeline scrubber after each run.

What you get

The format list is the point: MP4 H.264, MP4 H.265 10-bit, four ProRes profiles (422 up to 4444 XQ), three DNxHR, FFV1 lossless in MKV, WebM VP9, GIF, animated WebP, 16-bit PNG sequences and half/full-float EXR sequences. Delivery-grade formats sit beside the social-media ones - no second pack for the "pro" half.

The bit that isn't in other save nodes

Stream save. Wire latent and vae instead of images and the latent is decoded straight into the file, in order, keeping the VAE's causal state - the full frame batch never exists in RAM. For long clips on a card that barely fits, that's the difference between finishing and OOMing.

With a Wan VAE there's tile_mode. On auto it tiles only when the frame wouldn't fit the GPU (the tooltip's own example is 2K on 8 GB landing on 512 px tiles), and each tile is decoded over the whole clip in one causal pass and blended on disk - so the tile is never held as a batch either. off disables it, manual takes your tile_size.

The encoder underneath is PyAV, imported lazily, and PyAV bundles FFmpeg - no separate ffmpeg binary to install.

Inputs you actually set

  • format - start here, everything else follows.
  • images or latent + vae - the normal path versus the streaming path.
  • audio - muxed in for formats that carry it.
  • video_info - a VHS_VIDEOINFO, and the trick worth knowing: leave fps at its default 24, wire video_info, and the loader's loaded_fps is used instead. Load a 30 fps source, process, save, and it stays 30 fps without you remembering the number. Set fps to anything else and yours wins.
  • quality - CRF / qscale / ProRes profile depending on format, and hidden for lossless formats. If the widget won't move, you picked FFV1 or an EXR sequence.
  • filename_prefix (default C2C/video), subfolder, save_output - normal output plumbing.
  • naming - default, ComfyUI counter, or Folder Version. That third one is the pack's own FolderIncrementer: output lands in v001/, v002/, and a cancelled run doesn't burn a version because the version is resolved off the filesystem rather than a counter file.
  • colorspace_in / colorspace_out - sRGB - Display, Linear Rec.709 (sRGB) or ACEScg, with colorspace_out defaulting to "same as input". It uses OCIO when a config is available, built-in matrices otherwise.

Outputs

filenames (STRING) is where the file went. preview (IMAGE) is what the in-node scrubber plays. It's an output node, so it always runs, with its own change-hash so an unchanged graph stays cached.

Installing it

Manager → search "CustomNodePacks", or:

cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks

Restart and look for [C2C] CustomNodePacks: 112 nodes loaded ... 0 failed. No line means the pack didn't import, and nothing else matters yet.

Don't run pip install -r requirements.txt on a working ComfyUI - the README says so outright and it's right: it overwrites bundled torch, torchvision, numpy and Pillow. The repo's requirements.txt is also longer than the README's table (timm, einops, huggingface_hub, ftfy, regex, cryptography, for the SAM 3 and Vault nodes). PyAV, numpy and torch are the whole bill for this node.

Where it bites

ModuleNotFoundError: av - install PyAV. Mainstream ComfyUI installs already have it for the video nodes; a stripped-down or old environment won't.

Your export looks washed out or flat. Almost always a colour-space mismatch: many graphs run linear or ACEScg internals, and saving that as display-referred sRGB gives you a milky file. Set colorspace_in to what the graph is holding and colorspace_out to sRGB - Display for delivery.

Alpha and odd sizes. Only ProRes 4444, FFV1 and VP9 carry alpha; H.264 and H.265 are 4:2:0 and can't - the same constraint pads odd width/height by a pixel with edge replicate. Animated WebP goes through Pillow rather than PyAV on purpose: PyAV's libwebp encoder segfaulted the process on an RGBA batch (PyAV 17.0.1), and inside ComfyUI a segfault kills the server.

The scrubber and non-browser codecs. The preview runs in your browser, so H.264 and VP9 play happily while ProRes and DNxHR may not. That's the browser, not a bad write - check those in your NLE.

RAM still spiking. You're on the images path. Streaming only happens when the latent goes in and the frames come out one at a time.

For a quick MP4 to post, the node you already have is fine. Add this one for the two things it really sells: long clips on limited VRAM, and a delivery-grade file when the shot is going somewhere else.

Category🐺 C2C/🧰 Core/Video

Inputs (45)

NameTypeDefaultDescription
formatCOMBOcustom (container / codec)custom: choose container, codec, profile, bit depth and chroma below. The other entries are the old single list, kept so saved workflows encode as before.
fpsFLOAT24.0000.001–240Frame rate for the written file. When video_info is wired and fps is left at 24, loaded_fps from the loader is used.
filename_prefixSTRINGC2C/video—
qualityINT800–100Encoder quality where the format supports it (CRF / qscale / ProRes profile). Hidden for lossless formats.
imagesoptIMAGEThe frames to save. Or wire latent + vae instead to decode and save as one stream.
audiooptAUDIO—
video_infooptVHS_VIDEOINFO—
colorspace_inoptCOMBOsRGB - Display58 options: auto (from Load), ACES2065-1, ACEScc, ACEScct, ACEScg, ADX10, +52
colorspace_outoptCOMBOsame as input59 options: same as input, auto (from Load), ACES2065-1, ACEScc, ACEScct, ACEScg, +53
namingoptCOMBOdefaultFolder Version Incrementer: the version folder and name come from the Folder Version Incrementer node in this graph (no wire needed), or from the incrementer's defaults when there is none; a wired subfolder still wins. ComfyUI counter: prefix_00001 like Save Image. default: the C2C setting (Folder Version Incrementer unless you changed it).
naming_resolvedoptSTRINGSet by the UI from c2c.saveVideo.naming.
subfolderoptSTRING—
save_outputoptBOOLEANtrue—
latentoptLATENTStream save: a video latent decoded straight into the file, in order, keeping the VAE's causal state - the full frame batch is never held in RAM. Wire this and vae instead of images.
vaeoptVAEThe VAE for latent (stream save).
tile_modeoptCOMBOautoStream save, Wan VAEs: auto tiles only when the frame would not fit the GPU (2K on 8 GB -> 512 px). Each tile is decoded over the whole clip in one causal pass and blended on disk, so the clip never sits in RAM. off: no tiles. manual: tile_size.
tile_sizeoptINT51264–2048Tile size in pixels when tile_mode is manual.
save_metadataoptBOOLEANtrueWrite '<name>.png' next to the saved file: its first frame with the workflow and prompt inside - drag it into ComfyUI to rebuild the graph. The video itself never carries the workflow.
containeroptCOMBOMP4The file type. Used when format is custom.
codecoptCOMBOH.264The video codec; the list follows the container.
profileoptCOMBOHighThe codec's profile (ProRes 422 HQ, DNxHR HQX, H.265 Main 10, EXR ZIP ...).
bit_depthoptCOMBO8Bits per channel the profile allows. EXR: 16 = half float, 32 = float.
chromaoptCOMBO4:2:0Chroma subsampling: 4:2:0 (delivery), 4:2:2 (edit), 4:4:4 / RGB (full).
color_primariesoptCOMBOautoColour primaries written into the file. auto: from colorspace_out (sRGB / Rec.709 named, others untagged). For YUV codecs the conversion matrix follows (BT.2020 -> BT.2020 NCL).
color_transferoptCOMBOautoTransfer function written into the file (it labels; the pixels come from colorspace_out).
color_rangeoptCOMBOautoYUV codecs: limited (16-235, broadcast, the default) or full (0-255). Changes the conversion and the tag together. RGB codecs are always full range.
handles_trimoptCOMBOautoauto: trim exactly the handles Load Video (C2C) added (read from video_info; fit's target length wins). manual: trim_frames on trim_side. off: write every frame that arrives.
trim_framesoptINT80–100000manual trim: frames to cut (side both: on each end).
trim_sideoptCOMBOheadmanual trim: which end.
range_inoptINT00–10000000Export range, first frame (of the trimmed clip). Set it with the player's In handle. 0 / 0 = the whole clip.
range_outoptINT00–10000000Export range, last frame (inclusive). 0 = to the end.
write_modeoptCOMBOwritepreview first: a run shows a quick preview of exactly what would be written (handles trimmed, range applied) and writes nothing; the player's Write file button then writes it - only the encode runs again, the graph above is cached.
write_tokenoptSTRINGSet by the player's Write file button.
format_familyoptCOMBOVideoVideo, Image Sequence, or Custom (container -> codec -> profile -> bit depth -> chroma).
subformatoptCOMBOfrom the controlsThe output format. It fixes the container and codec; only its own options show. 'from the controls' = the five controls exactly (saved workflows).
encoder_presetoptCOMBOdefaultH.264 / H.265 speed vs size (NVENC and AV1 get the matching step). default = the encoder's own.
gopoptINT00–10000Keyframe interval in frames (0 = the encoder's own). 1 = every frame a keyframe (edit-friendly).
bitrate_kbpsoptINT00–2000000Average bitrate in kbit/s instead of constant quality (0 = constant quality from quality).
channelsoptCOMBOautoauto: alpha when the frames have it. RGB: never alpha. RGBA: alpha always (opaque when the frames have none) - where the format can store it.
png_compressionoptINT-1-1–9PNG zlib level 0 (fast, big) - 9 (slow, small); -1 = the writer's default. Lossless either way.
follow_loadoptSTRINGautoWhich Load Video this Save takes handles, frame rate, colour and name from. auto: the one reached through each node's first image input; a node id (the picker on the node) pins that one. A wired video_info always wins.
review_copyoptBOOLEANfalseAlso write '<name>_review.mp4' next to the file: H.264, half size, in the OCIO display (sRGB - Display), with the burn-in below. For review only - the master gets no burn-in.
burn_inoptCOMBOstandardstandard: name + version (top left), frame + timecode (top right), colour spaces (bottom left), date (bottom right). custom: burn_in_template. off: none.
burn_in_templateoptSTRING{shot} {version}|{frame} {tc}|{colour}|{date}Four corners, top-left | top-right | bottom-left | bottom-right, with {shot} {version} {frame} {tc} {date} {artist} {colour}. The frame and timecode are the source's when Save carried them.
burn_in_artistoptSTRINGWhat {artist} writes in the custom template.

Outputs (2)

NameTypeDescription
filenamesSTRING—
previewIMAGE—