Universal Image Unpack
The node that turns a dropped file into image, mask, and RGBA β in one shot
- media_data
- image
- mask
- rgba_image
- width
- height
- orig_width
- orig_height
- megapixels
- crop_x
- crop_y
- crop_w
- crop_h
- filename
You drop an image on UniversalMediaLoader, and this is the node that actually does something with it. UniversalImageUnpack reads the loader's MEDIA_DATA, decodes the file, applies everything you did visually - the crop box, the mask you painted, flips and rotation - and hands you a ready-to-use IMAGE tensor, a MASK, and an RGBA version, all in one go. It's the answer to the workflow where you'd otherwise chain a loader, a crop node, a mask node, and a resize node together and still fight about pixel alignment.
How it works
The node takes one optional input, media_data, and does three jobs server-side:
- Locate and decode. It reads the filename from the JSON, finds it in ComfyUI's
input/folder, and opens it with Pillow (auto-correcting EXIF orientation). - Apply your edits. Crop coordinates from the loader's visual crop box are applied - including outpaint mode, where the crop extends past the image edge: missing areas get filled black and the mask fills white, so an outpaint workflow gets its mask for free. Megapixel clamping and 32px grid snapping run here too.
- Composite the mask. Whatever you painted with the inpaint brush comes through as the
maskoutput. Thergba_imageoutput carries that mask inverted as its alpha channel - the painted region is transparent, the rest opaque - which is exactly the convention ComfyUI's inpaint nodes expect.
The surprise feature: video and GIF input
Connect this node to a loader that has a video or GIF dropped on it and you don't get a full frame dump. You get a batch of exactly three keyframes: [Trim Start, Current Playhead Position, Trim End]. That's the pack's answer to "show me the start, the bit I care about, and the end" for a video-to-image inpaint or style pass. Want every frame instead? That's what UniversalVideoUnpack is for - this node is deliberately sampling, not decoding.
Wire the wrong media in and you still get something safe: an audio file yields a minimal black 32Γ32 placeholder with zeroed metadata rather than a crash.
The outputs that matter
image- RGB tensor, normalized 0.0β1.0. Straight into your VAE, sampler, or ControlNet preprocessor.mask- your painted inpaint region (white = edit here). Feed it toSet Latent Noise Maskor any inpaint path.rgba_image- 4-channel version with the mask as alpha, handy for compositing.width/height- the output dimensions after crop/snap;orig_width/orig_heightare the source's.crop_x/crop_y/crop_w/crop_hgive you the bounding box so downstream nodes know what happened.filename- the source file name, handy for filenames and logging.
Installing
Same pack as the loader: ComfyUI Manager, search "Universal Media Loader", or:
cd ComfyUI/custom_nodes/
git clone https://github.com/Fictiverse/ComfyUI_UniversalMediaLoader.git
Restart ComfyUI. No extra Python packages, no model downloads.
Where people trip
The three-keyframe behavior is the one that surprises everyone - you drag a video in expecting frames and get a batch of 3. It's a feature, but if you were chasing "decode all frames," you're in the wrong unpack. Also remember: the mask is only as good as what you painted in the loader. Nothing painted, all black, and an inpaint will happily regenerate the whole frame - check your brush strokes before you queue.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| media_dataopt | MEDIA_DATA | β |
Outputs (13)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | β |
| mask | MASK | β |
| rgba_image | IMAGE | β |
| width | INT | β |
| height | INT | β |
| orig_width | INT | β |
| orig_height | INT | β |
| megapixels | FLOAT | β |
| crop_x | INT | β |
| crop_y | INT | β |
| crop_w | INT | β |
| crop_h | INT | β |
| filename | STRING | β |