Nodes/🌐 Universal Media Loader/Universal Video Unpack
ComfyUI Node

Universal Video Unpack

Frames, sync audio, and mask β€” in one wire

By FictiverseΒ·Created 21 days agoΒ·Updated 19 days agoΒ· 13
Universal Video Unpack
  • media_data
  • frames
  • mask
  • audio
  • frame_count
  • fps
  • orig_fps
  • duration
  • orig_duration
  • width
  • height
  • megapixels
  • trim_start
  • trim_end
  • filename

Where UniversalImageUnpack samples a video for three keyframes, this node does the full job: it decodes a video or animated GIF into every frame as a batched IMAGE tensor, pulls the audio track along with it, and applies your trim, target framerate, crop, and mask. It's the pack's answer to the role VHS LoadVideo plays in most ComfyUI video workflows - with the difference that the trimming, cropping, and masking were already done visually in the loader instead of by typing numbers into widgets.

How it works

All the heavy lifting is Python-side, in this pack's video loader:

  • Decoding runs through PyAV (av), the same library ComfyUI core bundles. GIFs take a different path - Pillow's ImageSequence - and the node figures out the GIF's native fps from its frame durations, which is something a lot of naive loaders get wrong.
  • Trimming uses the trim_start / trim_end seconds you set on the timeline in the loader, and it applies to both the frames and the audio, so they stay in sync.
  • Framerate resampling: toggle custom FPS in the loader and the decode subsamples to your target. Want 60fps footage at 24? It hands you frames at 24.
  • Post-processing matches the other unpacks: crop from the visual box, optional megapixel clamping, and 32px grid snapping.
  • If the file has no audio track, you get a silent placeholder AUDIO dict instead of a None - a small courtesy that keeps downstream audio nodes from exploding.

The outputs that matter

  • frames - batched tensor [B, H, W, C], normalized 0.0–1.0. This is your whole video, ready for the next stage.
  • mask - the mask you painted, repeated across every frame so it matches the frame count.
  • audio - the synchronized, trimmed audio as a ComfyUI AUDIO dict.
  • frame_count / fps - feed these into anything that needs to know how long the video is, like conditioning schedulers or frame-interpolation math. orig_fps is the source's native rate; fps is what came out.
  • duration / orig_duration - trimmed versus original length, in seconds.
  • width / height / megapixels, trim_start / trim_end, filename - the usual metadata breadcrumbs.

In a video-gen workflow (LTXV, Wan, AnimateDiff style), this is the front door: decode β†’ extract frames β†’ encode them as latent for image-to-video or ControlNet conditioning, and use fps and frame_count to keep the scheduling honest.

Installing

ComfyUI Manager: search "Universal Media Loader", or:

cd ComfyUI/custom_nodes/
git clone https://github.com/Fictiverse/ComfyUI_UniversalMediaLoader.git

Restart ComfyUI. Zero extra dependencies and no models - it uses what ComfyUI already has.

The honest warnings

  • It decodes the whole video into one tensor in memory. A 30-second, 60fps 1080p clip is ~1800 frames. That's a lot of VRAM, and it will OOM on consumer cards. This is the biggest trap in the pack: trim aggressively and set the megapixel clamp in the loader before you queue. The resample isn't magic either - "upsampling" 24fps to 60fps just duplicates frames, it doesn't invent motion.
  • Decode is CPU-bound PyAV, not hardware-accelerated. Big files take a real few seconds before generation even starts. Annoying, but the trade for getting crop/mask/trim in one place.
  • Files must live in ComfyUI's input/ folder for a saved workflow to re-run - standard ComfyUI behavior, easy to forget when you shared the .json but not the .mp4.
Categoryloaders/universal_media

Inputs (1)

NameTypeDefaultDescription
media_dataoptMEDIA_DATAβ€”

Outputs (14)

NameTypeDescription
framesIMAGEβ€”
maskMASKβ€”
audioAUDIOβ€”
frame_countINTβ€”
fpsFLOATβ€”
orig_fpsFLOATβ€”
durationFLOATβ€”
orig_durationFLOATβ€”
widthINTβ€”
heightINTβ€”
megapixelsFLOATβ€”
trim_startFLOATβ€”
trim_endFLOATβ€”
filenameSTRINGβ€”