Extensions/ComfyUi-MpiNodes
ComfyUI Extension

ComfyUi-MpiNodes

Multiple utilities for ComfyUI

By MadPonyInteractive·Created 11 months ago·Updated 5 days ago· 3
MadPonyInteractive/ComfyUi-MpiNodes
Nodes123
On cloudLocal install
CategoryMpiNodes/ImgOps, MpiNodes/Logic
Stars3
Updated5 days ago

Nodes (123)

Mpi Add Image to List

Grow an image list one frame at a time, inside the graph

MpiNodes/ImgOps
Mpi Any Blocker

Kill a ComfyUI branch the second it comes up empty

MpiNodes/Logic
Mpi Any Checker

Ask ComfyUI 'does this wire actually hold something?'

MpiNodes/Logic
Mpi Any Inverted Switch

One input, pick where it lands

MpiNodes/Logic
Mpi Any Switch

One of five inputs, picked at runtime — the switch that doesn't care about types

MpiNodes/Logic
Mpi Any Switch 10

Ten slots, lazy-evaluated — for the workflow that outgrew five

MpiNodes/Logic
Mpi Get Aspect Ratio

9?' — the node that stops you doing mental math

MpiNodes/ImgOps
Mpi Audio Range

Cut video frames without silently desyncing the soundtrack

MpiNodes/Video
Mpi Audio Splice

Sound stops early after an H3 edit? This node sews the audio window back in

MpiNodes/Video
Mpi Batch Text Replace

Find-and-replace across every prompt file in a folder, in one run

MpiNodes/TextOps
Mpi Bbox To Mask

Detector boxes to usable masks, in either format you were handed

MpiNodes/ImgOps
Mpi Bernini Conditioning

One graph for every Bernini combo instead of one per combo

MpiNodes/Utils
Mpi Bernini Length

The 15-second node that stops Bernini eating your frames

MpiNodes/Utils
Mpi Blocker

A manual kill switch for a whole branch — even the expensive upstream part

MpiNodes/Logic
Mpi Any Blocker

Mpi Block If Empty is just Mpi Any Blocker's old name

MpiNodes/Logic
Mpi Block If Empty List

Stop an IndexError before it happens, with the whole list in view

MpiNodes/Logic
Mpi Boolean

One boolean, three types — the adapter that unblocks stubborn wires

MpiNodes/Logic
Mpi Boolean Compare

The eight-way boolean comparison you'll actually use once you see it

MpiNodes/Logic
Mpi Boolean Invert

One wire in, the opposite wire out

MpiNodes/Logic
Mpi Box

Package a rectangle as a real object

MpiNodes/ImgOps
Mpi Box Crop

Crop to a rectangle that's allowed to hang off the edge

MpiNodes/ImgOps
Mpi Box Mask

Turn a rectangle into a mask without touching a brush

MpiNodes/ImgOps
Mpi Brush Train

Train a real Gaussian splat in ComfyUI without touching a CUDA compiler

MpiNodes/Splat
Mpi Clamp

Keep a number inside bounds without a wall of if/else nodes

MpiNodes/Math
Mpi Clear Vram

The 'I've been running video models all day' button

MpiNodes/Utils
Mpi Clear Vram End

The Node That Hands Your VRAM Back — Once the Branch Is Actually Done

MpiNodes/Utils
Mpi Compare

The little logic node that makes workflows branch

MpiNodes/Logic
Mpi Compare Packs

Check that a whole bundle of values didn't change

MpiNodes/Logic
Mpi Conditioning Reroute

Keep positive and negative wires from becoming spaghetti

MpiNodes/Logic
Mpi Convert

One value in, every type out

MpiNodes/Logic
Mpi Crop

The anchor-aware crop that respects your model's divisibility

MpiNodes/ImgOps
Mpi Prompt Display

Actually see what your prompt list contains

MpiNodes/Debug
Mpi Double Lora Loader

One LoRA, two models, both clips

MpiNodes/ModelOps
Mpi Exec Logger

Inline progress prints without breaking your wire

MpiNodes/Logic
Mpi Expo Float

Reshape a 0–1 value with a power curve

MpiNodes/Math
Mpi Float

The pass-through node that keeps your numbers honest

MpiNodes/Logic
Mpi From Box

Unpack an MPI_BOX back into plain integers

MpiNodes/ImgOps
Mpi From Checkpoint

Un-bundle a checkpoint back into model, clip, VAE

MpiNodes/Logic
Mpi Get Image At Index

Pull one frame out of a batch without the IndexError dance

MpiNodes/ImgOps
Mpi Grid Dimensions

Tile sizes that actually line up for tiled upscaling

MpiNodes/ImgOps
Mpi H3 Decode AV

Finish a MiniMax H3 inpaint without smearing the rest of the frame

MpiNodes/Utils
Mpi H3 Encode AV

One latent that carries picture and sound together

MpiNodes/Utils
Mpi H3 Image To Video

One MiniMax H3 conditioning node for every mode — t2v, first-frame, last-frame, or both

MpiNodes/Utils
Mpi H3 Length

The frame-count calculator MiniMax H3 forces you to need

MpiNodes/Utils
Mpi H3 Masked Prefix

Extend a MiniMax H3 clip without re-rolling the part you already like

MpiNodes/Utils
Mpi H3 References

Every MiniMax H3 reference slot, on one node

MpiNodes/Utils
Mpi Has Audio

Know whether a video file actually has a soundtrack

MpiNodes/Utils
Mpi Has Options

Turn a prompt-list pick into a workflow decision

MpiNodes/Logic
Mpi If Else

The node that actually skips the branch you don't take

MpiNodes/Logic
Mpi If Else Inverted

One input, two conditional destinations

MpiNodes/Logic
Mpi If Else Probability

Roll a weighted die to decide which branch of your graph runs

MpiNodes/Logic
Mpi If Else Probability Inverted

Route a value down a random branch

MpiNodes/Logic
Mpi Image Splice

Stitch processed frames back into a clip

MpiNodes/ImgOps
Mpi Inpaint Heal

Your inpaint fill looks off? Heal the colour drift after the fact

MpiNodes/ImgOps
Mpi Int

One integer, every socket that needs it

MpiNodes/Logic
Mpi Is List Empty

Check a list before the graph throws an IndexError on it

MpiNodes/Logic
Mpi Json Load

Pull a JSON file into your graph by absolute path

MpiNodes/JSON
Mpi Json Save

Write a dictionary to disk at a path you choose

MpiNodes/JSON
Mpi Latent Upscale

Upscale a latent in pixels, not latent cells — and don't crash on H3

MpiNodes/Latent
Mpi List Count

How long is that list? Ask, and get a boolean too

MpiNodes/Logic
Mpi List Range

Slice a list with inclusive, negative-index friendly bounds

MpiNodes/Logic
Mpi Load Audio

Load audio by path — even pull a soundtrack out of a video file

MpiNodes/Video
Mpi Load Image From Path

Load an image from anywhere on disk — with a preview and a mask

MpiNodes/ImgOps
Mpi Load Latent

Pick up a stage-1 latent and keep the run going

MpiNodes/Latent
Mpi Load Video

A no-frills video loader that's faster than the fancy one

MpiNodes/Video
Mpi Logger

Print what's flowing through your graph — to the console, not the canvas

MpiNodes/Logic
Mpi Lora Model

Apply a LoRA to the model only — skip the text encoder

MpiNodes/ModelOps
Mpi Lora Model Clip

Model and clip tuned independently

MpiNodes/ModelOps
Mpi Lora Switch

Pick one of five LoRAs by number — or by seed

MpiNodes/Logic
Mpi Mask Bbox

Where the mask is, how big it is, and the mask itself, on one node

MpiNodes/ImgOps
Mpi Mask Debug Info

What's in that mask, really? Shape, dtype, device — printed and wired

MpiNodes/Debug
Mpi Mask Fill Holes

SAM3 keeps punching the teeth out of your face mask

MpiNodes/ImgOps
Mpi Mask Preview

The three-node mask check you keep rebuilding, in one node

MpiNodes/Debug
Mpi Mask Square Bbox

Turn any mask into a centered square crop — with x, y and size

MpiNodes/ImgOps
Mpi Math

Type the arithmetic instead of chaining seven math nodes

MpiNodes/Math
Mpi Multi Text x2

Two text boxes, one prompt, zero separator pain

MpiNodes/Conditioning
Mpi Multi Text x3

Join three prompt pieces without the string gymnastics

MpiNodes/Conditioning
Mpi Multi Text x4

Four prompt chunks, one string, each still editable

MpiNodes/Conditioning
Mpi Multi Text x5

The biggest box in the join family, and probably the last one you'll need

MpiNodes/Conditioning
Mpi Normalize Value

Map any range to any range, without the spreadsheet

MpiNodes/Math
Mpi Packer

Cram five values of any type onto one wire and stop the spaghetti

MpiNodes/Logic
Mpi Packer 10

When five slots on one wire just isn't enough

MpiNodes/Logic
Mpi Packer 2

Two values, one wire, no empty sockets

MpiNodes/Logic
Mpi Prompt Bridge

A labelled pass-through for prompt_list plus seed

MpiNodes/PromptGen
Mpi Prompt Chain Selector

Pick one list out of a chain, deterministically

MpiNodes/PromptGen
Mpi Prompt List

The wildcard engine behind ComfyUi-MpiNodes' prompt system

MpiNodes/PromptGen
Mpi Prompt List Join

The glue that builds prompt chains

MpiNodes/PromptGen
Mpi Prompt List Selector

A 50/50 coin flip between two entire prompt lists

MpiNodes/PromptGen
Mpi Prompt Override

Force custom options into a prompt list without editing it

MpiNodes/PromptGen
Mpi Prompt Override Seed

Re-roll one list's selection without touching the seed

MpiNodes/PromptGen
Mpi Prompt Processor

Where _title_ tokens finally become real prompt text

MpiNodes/PromptGen
Mpi RandPromptGen

A random prompt factory that actually thinks in sections

MpiNodes/PromptGen
Mpi RandPromptGen Order

Take control of the sentence your random prompts make

MpiNodes/PromptGen
Mpi RandPromptGen Override List

Swap a preset's contents without editing JSON

MpiNodes/PromptGen
Mpi RandPromptGen Override Section

Pin one section of a random prompt

MpiNodes/PromptGen
Mpi RandPromptGen Save

Freeze your random prompt generator into a reusable preset

MpiNodes/PromptGen
Mpi Reroute

An any-type reroute that does exactly one thing, quietly

MpiNodes/Logic
Mpi Round To Multiple

Snap a stray number to a resolution your model accepts

MpiNodes/Logic
Mpi Round To Multiple Res

Snap a full resolution to a valid size in one node

MpiNodes/Logic
Mpi Save Latent

Save a latent without losing your sampler (or your sanity)

MpiNodes/Latent
Mpi Save Video

The fastest way to get a generated video out of ComfyUI

MpiNodes/Video
Mpi Scaled Dimensions

Scale to a target size without breaking the aspect ratio

MpiNodes/ImgOps
Mpi Seed Passthrough

A seed node that refuses to let ComfyUI cache you into a wall

MpiNodes/Logic
Mpi Simple Bool

The most boring node in the pack, and that's the compliment

MpiNodes/Logic
Mpi Stage Latents

The two-stage latent handshake, collapsed into one node

MpiNodes/Latent
Mpi String

A single-line string you can wire, instead of retype

MpiNodes/Logic
Mpi String Inverted Switch

Send one string down exactly one of five wires

MpiNodes/Logic
Mpi Style Loras

Five LoRA slots that only fire on their matching style

MpiNodes/ModelOps
Mpi Style Selector

One integer picks your style, its LoRA, and its trigger words

MpiNodes/ModelOps
Mpi Text

A multiline text box that lives on a wire

MpiNodes/Logic
Mpi Text Contains

A prompt whitelist check that actually understands plurals

MpiNodes/Logic
Mpi Text List Join

Bulk-edit every item of a prompt list in one go

MpiNodes/TextOps
Mpi Text List Replace

Find-and-replace across an entire prompt list

MpiNodes/TextOps
Mpi Tiny Vae Loader

Load the tiny VAE decoder ComfyUI's own loader refuses to

MpiNodes/Sampling
Mpi To ChekPoint

Model, clip, and VAE — bundled onto one wire

MpiNodes/Logic
Mpi Unpacker

The other half of the wire that carries five values at once

MpiNodes/Logic
Mpi Unpacker 10

When five sockets just aren't enough

MpiNodes/Logic
Mpi Unpacker 2

Get your two values back, in the order you packed them

MpiNodes/Logic
Mpi Upscale Model Scale

Stop trusting filenames for the upscale factor

MpiNodes/ImgOps
Mpi Video Sampling Preview

Real previews for video models ComfyUI can't show

MpiNodes/Sampling
Mpi Wan Frames

The frames-to-seconds math Wan pretends you already know

MpiNodes/Wan
Mpi Wan Seconds

The node that adds Wan's '+1' frame so you don't have to

MpiNodes/Wan
Mpi Windowed Sampler

Sample the Video That Won't Fit — One Window at a Time (Refine Only)

MpiNodes/Sampling
Readme

ComfyUi-MpiNodes

A ComfyUI custom node pack by Mad Pony Interactive with over 100 utilities for logic, math, prompt generation, image operations, model management, and workflow automation.

Registry

These nodes power Cubric Vision — a free, open-source desktop app for AI image and video generation. Vision installs and manages ComfyUI, the models and this pack for you, and wraps them in a real editing UI: gallery, masking, inpainting, painting, cropping and project management. If you found this pack through one of our workflows, the app is where those workflows come from.


Nodes

Prompt Generation

Build, process, randomize, and route prompts with fine-grained control.

| Node | Description | |---|---| | MpiPromptList | Create a prompt list with options, probability, and blocking rules. Supports prefix/suffix, amount selection, specific item picking, probabilistic shuffling, and chaining. | | MpiPromptProcessor | Process prompt lists into positive and negative prompts by replacing _title_ tokens with selected options. Handles multipliers (e.g. _title_x2_) and cleanup. | | MpiPromptListSelector | Probabilistically select between two prompt lists based on seed and probability. | | MpiPromptChainSelector | Select one list from a prompt chain using a seed. | | MpiPromptListJoin | Join two prompt lists or chains. | | MpiPromptBridge | Pass-through utility that forwards a prompt list and seed values. | | MpiPromptOverride | Override prompt list options with custom text and optional seed override. | | MpiHasOptions | Check if selected options contain any of the specified options. Returns a boolean. | | MpiDisplayPrompt | Display prompt list contents for debugging — outputs titles, blocklists, and processed prompts as strings. | | MpiRandPromptGen | Generate random prompts from preset lists with customizable sections (shot type, hair, clothes, pose, expression, location, lighting). Supports indoor/outdoor probability, pronoun selection, and bright/dark lighting. | | MpiRandPromptGenOrder | Set custom section order and transitions for random prompt generation. | | MpiRandPromptGenOverrideSection | Override specific section options and seed for random prompt generation. | | MpiRandPromptGenOverrideList | Override preset list options for random prompt generation. | | MpiRandPromptGenSave | Save a random prompt preset to disk. |


Logic & Control Flow

Primitive operations for comparisons, type conversions, rounding, and boolean logic.

| Node | Description | |---|---| | MpiCompare | General logic operator comparing two values with ==, !=, >, <, >=, <=. b can be wired or typed into the b_value widget. | | MpiBooleanCompare | Compare two booleans with modes: equal, not_equal, both_false, both_true, one_is_true, not_both_true, etc. | | MpiBooleanInvert | Invert a boolean — true in, false out. A pass-through NOT gate; wire-only input, no widget. | | MpiConvert | Convert any value to STRING, INT, FLOAT, and BOOLEAN outputs simultaneously. | | MpiRoundToMultiple | Round a single integer to the nearest multiple (up or down). | | MpiRoundToMultipleRes | Round width and height to the nearest multiple simultaneously. | | MpiBoolean | Pass through a boolean → outputs boolean, int, and float. | | MpiSimpleBoolean | Pass through a boolean value only. | | MpiFloat / MpiInt / MpiString / MpiText | Pass-through nodes for float, int, string (single-line), and text (multiline) values. | | MpiListCount | Count entries in a list of any type. Outputs count (INT) and has_items (BOOLEAN, true if non-empty). | | MpiIsListEmpty | Check whether a list of any type is empty. Outputs is_empty (BOOLEAN, true if zero items) and count (INT). Route is_empty into an if/else to skip nodes that would IndexError on an empty list. | | MpiBlockIfEmptyList | Pass a list through, but block downstream execution if it is empty. Place before nodes that index into a list so an empty list halts the branch instead of throwing IndexError. Unlike MpiAnyBlocker, it receives the whole list, so a truly empty list still reaches it. | | MpiListRange | Output a sub-range of any list using inclusive start/end indices. Negative indices count from the end. Outputs sliced list and its count. | | MpiTextContains | Check whether any of several words or phrases (multiline box, separated by commas or new lines) appears in an input text as a whole word. Case-insensitive and plural-aware, so cat matches a CAT sits and two cats but not catalogue. Multi-word entries ignore spacing, so bird in a tree still matches across a line break. Irregular plurals (child/children) need their own entry. Outputs a BOOLEAN — use as a whitelist/blacklist check on a prompt. | | MpiReroute | Pass any value through unchanged. Rename the node title for a labelled reroute. | | MpiConditioningReroute | Pass positive and negative conditioning through unchanged — a labelled conditioning reroute. | | MpiAnyBlocker | Pass any value through, but block downstream execution if it is empty (empty string/list/dict/None/zero-element tensor/empty audio). 0, 0.0 and False pass through. | | MpiBlocker | Manual gate: pass the input through when the switch is on (continue), block the branch when off (block). The input is lazy, so blocking also skips everything feeding this node — not just what comes after it. | | MpiAnyChecker | Pass any value through unchanged and output a has_value boolean — true if non-empty, false if empty. Same emptiness rules as MpiAnyBlocker. | | MpiSeedPassthrough | Pass any value through and emit a seed. Forces the workflow to re-run every time (via IS_CHANGED) so seed-less workflows don't get stuck on cached outputs. Leave any unconnected to use as a pure seed source. | | MpiLogger | End-of-chain logger with no output — prints when its input arrives. mode="value" logs the input value with a prefix; mode="message" logs only the message text, marking that the point was reached without dumping the input. | | MpiExecLogger | Pass any input through to its output while logging a message to the console — wire inline to print workflow progress. |


If / Else Routing

Conditional and probabilistic routing of values and inputs.

| Node | Description | |---|---| | MpiIfElse | Output true or false value based on a boolean condition (lazy evaluation). | | MpiIfElseInverted | Route input to output A or output B based on a boolean condition. | | MpiIfElseProbability | Probabilistically choose between true and false using seed and probability. | | MpiIfElseProbabilityInverted | Probabilistically route input to true or false output. |


Switches

Route any type of input to a selected output by index.

| Node | Description | |---|---| | MpiAnySwitch | Select one of up to 5 inputs of any type based on selection index. | | MpiAnySwitch10 | Select one of up to 10 inputs of any type based on selection index. | | MpiLoraSwitch | Select one of up to 5 LoRA files by index. | | MpiAnyInvSwitch | Route an input of any type to one of up to 5 outputs (inverted switch). | | MpiPacker | Bundle up to 5 values of any type (image + float + int + boolean + …) into one MPI_PACK wire. Unconnected slots stay empty. A pack is just a list, so a pack can go into another packer's slot — 5 packers into a packer carries 25 values on one wire. | | MpiUnpacker | Unpack an MPI_PACK back into 5 outputs, in the same slot order they were packed. Slots that were empty block execution downstream, so nothing runs on a missing value. | | MpiPacker10 | Same as MpiPacker with 10 slots instead of 5. | | MpiUnpacker10 | Same as MpiUnpacker with 10 outputs instead of 5. Feeding a 10-slot pack into the 5-slot MpiUnpacker drops the tail and logs a warning. | | MpiPacker2 | Same as MpiPacker with 2 slots instead of 5 — the pair case (width + height, image + mask) without three empty sockets. | | MpiUnpacker2 | Same as MpiUnpacker with 2 outputs instead of 5. A longer pack drops its tail and logs a warning naming how many outputs are needed. | | MpiComparePacks | Compare two packs slot by slot. Outputs equal (true only if every slot matches) and first_diff, the 1-based slot of the first mismatch (0 when equal). Images and other tensors compare by content, and nested packs compare all the way down — this is the pack-safe alternative to MpiCompare, which raises on tensors. | | MpiStringInvSwitch | Route a string input to one of up to 5 outputs. |


Image Operations

Dimension math, aspect ratio, bounding box conversion, and grid tiling.

| Node | Description | |---|---| | MpiScaledDimensions | Scale image dimensions proportionally to a target size (use_max or use_min side). Returns width, height, is_portrait boolean, and the image resized to those dimensions (upscale_method selects the interpolation). | | MpiAspectRatio | Calculate aspect ratio from width/height. Outputs string (1:1, 4:3, 3:2, 16:9, 9:16, 2:3, 3:4) and pack, a 2-slot MPI_PACK carrying width then height on one wire — MpiUnpacker2 gives them back in that order. | | MpiGetImageAtIndex | Return the image at a specified index from a batch. Supports negative indexing (-1 = last). | | MpiBboxToMask | Convert bounding boxes (xyxy or xywh format) to mask tensors. | | MpiGridDimensions | Calculate grid dimensions and corrected source size for perfect tiling — avoids repeated tiles when fed to UltimateSDUpscale. Has auto mode. | | MpiUpscaleModelScale | Takes a Load Upscale Model node (or any UPSCALE_MODEL input) and reads its native scale (1x/2x/4x/8x) from the model's descriptor metadata. Outputs INT and FLOAT. fallback_scale used only if metadata is absent. | | MpiLoadImageFromPath | Load an image from a file path with an in-graph preview. Outputs image, mask, width, height. A channel combo (alpha/red/green/blue) selects the mask source. Blocks downstream execution if the path is empty, unless block_if_empty is off (then outputs a blank 1x1 image so the graph continues). | | MpiCrop | Crop an image to width/height at a chosen anchor (center/left/right/top/bottom). width/height of 0 keep that dimension full; the crop is floored to a multiple of divisible_by. | | MpiMaskSquareBbox | Square bounding box around a mask, centered on the mask and clamped (shrunk if needed) to stay fully inside the image. Outputs a filled square MASK plus x, y, and side length. Optional padding around the tight box. On a batch — a video mask — the box is the union of every frame and the square is returned for every frame, so the region holds still for the whole clip instead of crawling, and a video consumer gets as many mask frames as it was given. | | MpiMaskBbox | Tight rectangular bounding box around a mask, as an MPI_BOX plus width and height — and the mask passed through untouched. That is the difference from MpiMaskSquareBbox, which squares the box and returns a filled block where the mask was: one wire here crops the plate by the box and composites back through the real mask shape. padding widens the tight box; divisible_by rounds width and height up to a multiple and re-centres, for a consumer that needs its dimensions on a grid. On a batch — a video mask — the box is the union of every frame, so the crop holds still for the whole clip instead of crawling. An empty mask gives a zero box rather than raising. | | MpiMaskFillHoles | Fill the enclosed holes in a mask — SAM3 routinely drops the lips and teeth out of a face or head mask, and a composite then paints straight through the gap. Only touches pixels the mask already surrounds, so unlike a grow/shrink close the outer silhouette is untouched and no gap gets welded shut. max_hole_size caps it by area in pixels; 0 fills every hole. A hole is not a bite — an open mouth reaching the jaw line breaks the silhouette rather than being enclosed, and no fill can reach it, so add mouth to the SAM3 vocabulary for that. | | MpiBox | Build an MPI_BOX rectangle from width/height/x/y, where x/y are the top-left corner. One wire carries a region into any box-aware node instead of four loose INT sockets. Consumers clamp the box to the image, so out-of-bounds values are safe — and x/y may be negative, so a box can start outside the image to sit tight on a subject at the frame edge. | | MpiFromBox | Unpack an MPI_BOX back into width, height, x, y integers — the escape hatch for feeding raw INTs to other nodes. | | MpiBoxCrop | Crop an image to an MPI_BOX region. Outputs the cropped image plus the clamped box actually used. A box fully outside the image passes the image through unchanged. pad (optional, off by default) pads an overhanging box back out to its requested size by replicating the edge pixels, so the crop keeps the aspect that was asked for — what a square reference crop needs. | | MpiBoxMask | Build a mask the size of the image — black with a white rectangle at the MPI_BOX region. Outputs the mask plus the clamped box actually drawn. | | MpiInpaintHeal | Heal an inpainted region against the real pixels around it — corrects colour drift and restores fine grain by matching a ring of untouched pixels hugging the mask. Unlike a colour-match on a rectangular crop, the ring never contains the removed object, so it cannot pull the fill toward it. Run after stitching. | | MpiMaskDebugInfo | Print mask shape, dtype, and device info to the console for debugging. | | MpiMaskPreview | See where a MASK lands on an image, in one node — paints a flat color through the mask onto destination and previews it in-graph, instead of wiring Empty Image + Image Composite Masked + Preview Image every time. invert_mask swaps which side is painted; alpha fades the paint so you can see the plate under it, which is the whole point of looking. No source, no x/y and no resize_source — a debug view, not a compositor. index picks one frame out of a batch (negative counts from the end, out of range clamps), so a 124-frame clip does not render 124 thumbnails; a mask batch of 1 is treated as one mask for the whole clip. A mask at a different resolution is resampled nearest, so a blocky mask still looks blocky here rather than being smoothed into looking correct. | | MpiAddImageToList | Append an image to a list of images. |


Math

| Node | Description | |---|---| | MpiMath | Evaluate a Python expression on inputs a, b, c (b and c optional). All math module functions are in scope. | | MpiExpoFloat | Apply a power curve to a 0–1 float: value ^ exponent, clamped to [0, 1]. | | MpiClamp | Clamp an int or float between min_value and max_value. With min 200 / max 1000: 400 stays 400, 2000 becomes 1000, 100 becomes 200. Preserves int inputs. | | MpiNormalizeValue | Normalize a value from an input range to an output range with optional transforms (linear, inverse, log, exp) and clamping. |


LoRA & Checkpoint

| Node | Description | |---|---| | MpiLoraModel | Apply a LoRA to a model only, with strength control. | | MpiLoraModelClip | Apply a LoRA to both model and clip with independent strength controls. | | MpiDoubleLora | Apply the same LoRA to two models and clips simultaneously with strength controls. | | MpiStyleSelector | Style router: one selector integer plus a list of trigger words, feeding a chain of MpiStyleLoras banks. Outputs an MPI_STYLE bundle. | | MpiStyleLoras | A bank of 5 LoRA slots for an MpiStyleSelector — applies only the LoRA matching the selector. Chainable (bank 1 = selector 1-5, bank 2 = 6-10, …), outputs model, clip and prompt. | | MpiFromCheckpoint | Extract model, clip, and VAE from a checkpoint. | | MpiToCheckpoint | Combine model, clip, and VAE into a checkpoint. |


Conditioning

| Node | Description | |---|---| | MpiMultiTextX2 / X3 / X4 / X5 | Combine 2, 3, 4, or 5 text inputs into one. |


Text & String Operations

| Node | Description | |---|---| | MpiBatchTextReplace | Batch find and replace text in all .txt files within a folder. Handles UTF-8 and cp1252 encoding. | | MpiTextListReplace | Find and replace text in each item of a text list. | | MpiTextListJoin | Prepend or append text to each item in a text list. |


Animation Timing (Wan)

| Node | Description | |---|---| | MpiWanFrames | Calculate duration from frame count and FPS. Returns frames, seconds, and fps. | | MpiWanSeconds | Calculate frame count from duration (seconds) and FPS. Returns frames, seconds, and fps. |


JSON

| Node | Description | |---|---| | MpiJsonLoad | Load a JSON file from a full path and output as a dictionary. | | MpiJsonSave | Save a dictionary to a JSON file at a specified path. |


Utilities

| Node | Description | |---|---| | MpiClearVram | Offload all models from VRAM to RAM, clear GPU cache, and run garbage collection. Passes through a value to chain in workflows. | | MpiClearVramEnd | The same clear, as a terminal node with no output. Clearing VRAM properly needs one per terminal branch, because the node only runs on branches that actually execute — a single clear hung off the preview branch leaves the models resident for every other branch, and nothing reports it; the symptom is an OOM later in the graph with 0 models unloaded in the log. The pass-through version has to be spliced into a branch, which means moving an existing link, so covering three sinks (a latent, a decoded image, a decoded audio) means three rewires. This one hangs off the end of a branch and changes nothing downstream, because there is no downstream. The trigger input is required and exists only to place the node in execution order — an unconnected sink would run at an arbitrary point, which is the one thing a VRAM clear must not do. | | MpiAudioRange | Cut a soundtrack to a frame range, using the same inclusive start/end that MpiListRange takes — so one pair of numbers windows the picture and the sound together. Exists for windowed work on an audio-video model: cutting the frames and leaving the audio whole hands the model a soundtrack that does not line up with the picture, and nothing downstream can notice — every tensor is valid, the result is just conditioned on the wrong moment. Wire the loader's own fps; a guessed rate slides the audio rather than failing. | | MpiImageSplice | Write a run of frames back into a longer clip at a given frame — the return leg of windowed work: cut a window out, run the expensive thing on it alone, splice the result back. Pairs with MpiH3DecodeAV, whose output outside the mask is already the original pixel, so the window boundary is exact and needs no blending. A mismatched canvas, or a patch running past the end, raises — either one otherwise lands as a clip that plays but is wrong. | | MpiAudioSplice | Write a run of audio back into a longer soundtrack at a frame offset — the sound half of what MpiImageSplice does for picture, and the inverse MpiAudioRange shipped without. Takes the same inclusive start, so the number that cut a window writes it back. Windowed work on an audio-video model cuts both streams and has to splice both back; without this the regenerated soundtrack stays window-length while the picture comes back full-length, which reads as a video whose sound stops early rather than as a wiring mistake — VHS writes the short track without complaint. The crossfade is MpiH3DecodeAV's own splice_audio, so there is one implementation of the equal-power fade rather than two that can drift. A patch running past the end raises and says by how many samples, naming the two things that actually cause it: a start that names a different window, or a patch from a different clip. A mono patch is expanded to a stereo track instead of broadcasting into a crash, and a cut spliced straight back is a bit-exact no-op. | | MpiHasAudio | Check if a video file (by path) contains an audio stream via ffmpeg. Outputs a boolean to gate an audio wire into CreateVideo. | | MpiH3Length | Convert a wanted duration into a valid MiniMax H3 frame count. H3 only generates n % 17 == 5 frames at 24 fps, so whole seconds are mostly unreachable — asking for 2 s gives 2.33 s, and 8 s is the shortest exact one. Snaps to the nearest valid count (core snaps up, which is never closer), and outputs the true seconds plus in_trained_range (false outside 124–362 frames, where the model runs but was not trained). | | MpiH3ImageToVideo | MiniMax H3 image-to-video conditioning that tolerates EMPTY frame inputs, so one graph covers t2va, first-frame, last-frame and first+last instead of a pre-authored branch per combination. Core's MiniMaxH3ImageToVideo already skips a keyframe that arrives as None, but a graph cannot send None down a connected link, so a host app compiling a saved workflow had to carry four copies of the node behind a lattice of booleans. Empty means nothing connected or an Mpi loader with block_if_empty off (a 1×1 image) — a genuinely black first frame reports its true size and passes through untouched. The conditioning and the AV latent are built by core's own node, so the tensor maths cannot drift. Both frames are cover-cropped to the canvas first, because core is asymmetric about it — it stretches first_frame (crop='disabled') and cover-crops last_frame ('center'), so an off-aspect source came back squashed and the two keyframes disagreed with each other. Crop, never pad: letterbox bars baked into frame 0 get animated as scenery. Also what makes a SECOND conditioning at upscaled dimensions practical, which is what an H3 latent-upscale refine pass needs: the keyframe latent is encoded at the first stage's width/height and core sizes its cond rows off the target grid, so an upscaled latent otherwise raises a shape mismatch in SamplerCustomAdvanced. | | MpiH3References | MiniMax H3 reference conditioning with every slot exposed at once — 9 images, 3 videos with paired soundtracks, 3 standalone audio. Core's node grows its slots one at a time, so a host app compiling a saved workflow would need a pre-authored branch per combination (2¹⁸ of them); this one takes them all and drops the empty ones itself, renumbering the survivors so core's index-based soundtrack pairing still lines up. Empty means nothing connected or an Mpi loader with block_if_empty off (a 1×1 image / 1-sample waveform) — real black images and real silence pass through. Conditioning is built by core's own MiniMaxH3ReferenceToVideo, so it cannot drift. Write the prompt against slot numbers — <Picture 1> means ref_image_1 — and the node rewrites them to the ordinals core presents, dropping any tag whose slot is empty. That is what keeps a tag pointing at the same input when a slot ahead of it is left empty, and it is the only way to be right about audio: a reference video's soundtrack consumes an <Audio j>, and whether the file has one is unknown until it is decoded. Outputs ref_tags with the resulting map. | | MpiH3EncodeAV | Encode a clip and its soundtrack into one MiniMax H3 AV latent. Core encodes the two streams separately (VAEEncode + VAEEncodeAudio) and offers no way to join them, so a joint latent — the thing MpiH3MaskedPrefix takes as its context — was previously only reachable through a third-party fork that monkey-patches two ComfyUI internals at import time. Resamples the soundtrack to the audio VAE's own rate when it differs. Batch 1 only, matching H3 itself: a second item would pair the wrong soundtrack to the picture rather than fail. With the optional mask it is also the inpainting encode: a per-frame mask (from SAM3, or any per-frame source) is packed onto the video latent's own grid by union — not interpolated, which on H3's period-5 packing snaps each latent step to a single picked frame and makes a short mask vanish silently — and the audio half is masked all-keep, sized off the encoded audio latent so no frame count or frame rate has to be supplied. The mask may cover the whole clip or just a range of it, which is what a segmenter run over part of the clip returns: mask_start says which clip frame the mask's first frame lands on, and a mask that runs past the end raises rather than inpainting the wrong moment. This is what lets a host app reach H3 inpainting from IMAGE + AUDIO + MASK, with no VIDEO type and no file loader in the graph. When a mask is connected, a clip whose frame count is off H3's 17k+5 grid raises at encode, naming the nearest valid count, instead of surfacing as a mask overhang once a full sample has already been paid for — a 6-frame clip packs to 5 and the VAE drops the sixth without complaint. audio_start / audio_end open a window in the soundtrack the way the mask opens one in the picture, and audio_ranges (0-10, 40-50, 90-100) opens several disjoint ones in a single pass — the capability LanPaint's interactive editor has and no core node exposes, spelled as text because nothing emits the 1-D [F] MASK LanPaint consumes. This is context-aware foley, and it is the thing a standalone foley model cannot do: that generates in isolation from a silent clip, while H3 hears the rest of the track as unmasked context, so the window matches the room, the mic and the ambience, and is in sync by construction because one model produces both halves. Ranges are given in frames and snapped outwards to a multiple of 3 — audio runs on a 40 Hz clock against 24 fps picture, so a frame boundary is a whole audio step only every third frame, and an off-grid edge raises nowhere, it just drifts the sound against the picture — then merged where they touch, since two ranges three frames apart can collide once snapped. An audio window with no picture mask regenerates sound over untouched video, which is the pure foley case. The limitation is structural: an audio mask selects a time range, not a source, because audio sums every source into the same samples, so the window regenerates dialogue, room tone and footsteps together — MpiH3DecodeAV's audio_mode is the layering answer. A second info output reports what was actually masked on both halves, including how many audio steps were left unmasked, and says so explicitly when the answer is none: a clip whose audio is entirely masked has nothing to match and returns the model's own score at its own level. Pair with MpiH3DecodeAV. | | MpiH3DecodeAV | Decode the video half of an H3 AV latent and composite it back into the original frames through the same mask, which is what finishes an MpiH3EncodeAV inpaint. Outside the mask the result is the original pixel, not a VAE round trip of it, so an inpaint does not quietly soften the whole frame; feather ramps the mask edge so the seam does not show — dilate then Gaussian, matching LanPaint's blend_overlap to float noise in the interior, so the ramp sits outside the mask you drew and the inpainted content keeps full strength at its own edge. It diverges from LanPaint in one place on purpose: replicate padding, so a mask touching the frame border (a subject walking out of shot) is not faded away there — LanPaint's zero padding takes it to 0.58. It now finishes the audio half of the job too, through a second AUDIO output: connect audio_vae + audio and the same audio_start / audio_end / audio_ranges the encode was given. It shipped without them deliberately — nothing was spliced into the soundtrack, so there was no audio seam to hide — and that held exactly as long as the encode masked audio all-keep, which an audio window ends. Outside the window the sample is the original, matching what the picture half already promised. The decode is taken raw rather than through core's vae_decode_audio, which divides by its own standard deviation and would land the window in a different loudness domain from the track it is being spliced into. audio_crossfade is feather's counterpart, and its curve follows the mode because the two are not the same operation: replace crosses between two uncorrelated takes of one moment, where equal power holds the level, while mix fades a layer in over an original that keeps playing, where equal power would start the layer at 71% on the very first sample — a click of exactly the kind the crossfade exists to remove. audio_mode mix sums the window under the original instead of replacing it, which is the only way to layer a sound onto a track that already carries a performance: the mask selects a time range and not a source, so replace throws the original dialogue away along with the silence it was meant to fill. audio_gain (default 1.0) is the trim for the level the model decided on, which is rarely the level of the track it has to sit in; around 0.3 is where a new layer sits under an existing track rather than fighting it. With no audio_vae, or audio_end at 0, the audio input passes straight through, so a picture-only inpaint keeps its old wiring and its old result. mask_start / mask_end must match the encode's. A latent whose frame count does not match the originals raises, naming the 17k+5 grid as the cause. mask_mode chooses which mask the composite runs through: per-frame is the exact mask you drew, and as sampled rebuilds the coarser one the model was actually given — the latent grid, the union of the frames each latent step packs, and the DiT's own patch pooling. The painted region is always wider than the drawn one (measured +27% on a 124-frame 1536x640 clip, where the model sees 48x20 mask cells), so compositing per-frame keeps a thin slice of a wide repaint and puts the seam INSIDE the region the model worked to. as sampled moves the boundary out to where the model stopped, which is blockier but lets feather come right down. | | MpiH3MaskedPrefix | Continue a MiniMax H3 clip without regenerating its tail. Writes the prior clip's encoded frames into the front of the target latent and masks that region out of sampling, so the head is preserved exactly (measured at PSNR 38 dB against the source — a VAE round trip) and there is nothing to trim afterwards. context_frames snaps down to 39 / 90 / 141 …, the only lengths that sit on H3's 17k+5 video grid and divide by 3 so audio's 40 Hz clock also lands on a whole step; an off-grid value does not raise, it drifts the audio against the picture and starts the continuation from a moment that never happened, so the snap is reported rather than assumed. The nested video+audio noise mask is built at the latent's own resolution deliberately — core's reshape_mask interpolates a mask that does not match, smearing the one boundary that has to stay hard. A clean prefix is not an anchor: on its own the model preserves those frames and then renders an unrelated scene, so pair it with a single frame-0 MiniMaxH3AddGuide. | | MpiBerniniConditioning | Bernini-R in-context conditioning with every reference slot exposed at once - a source video, a reference video and 4 reference images. Core grows its reference slots one at a time, so a host app compiling a saved workflow cannot add one at inject time and would need a pre-authored branch per combination; this node takes them all as flat optional inputs and drops the empty ones itself. Empty means nothing connected or an Mpi loader with block_if_empty off (a 1x1 image) - a real black reference passes through. The task is inferred from what survives, as core documents it: nothing = t2v, source = v2v, source + refs = rv2v, refs alone = r2v, source + reference video = ads2v. Unlike MpiH3References there are no prompt tags to rewrite - Bernini tells its streams apart with a source_id rotation in the spatial RoPE, never by text - so dropping empties is purely about not handing the model a garbage stream. Conditioning is built by core own BerniniConditioning, so it cannot drift. Feed length from MpiBerniniLength — core returns 4n+1 frames and says nothing when it snaps, and the plate must be trimmed to the same number. ref_max_size caps the long edge of the references only (never upscales, so setting it at or above their own size does nothing) and does not touch source_video, which uses width/height. | | MpiBerniniLength | Snap a frame count to the one Bernini can actually return, before anything is sampled. Core builds ((length-1)//4)+1 latent frames and the VAE decodes 4 apiece, so a request off the 4n+1 grid comes back short and nothing says so — 48 frames in, 45 out — which surfaces downstream as an inpaint stitch refusing two different frame counts, pointing nowhere near the conditioning. Feed this one number to both MpiBerniniConditioning.length and whatever trims the plate, and the two cannot disagree. Deliberately a separate node rather than an output on the conditioning node: the plate is upstream of that node's source_video, so reading the count off it closes a dependency cycle ComfyUI refuses to execute — the same reason MpiH3Length is its own node. on_grid is false when the input had to be snapped, so a graph can surface the lost frames rather than quietly dropping them. |

Video

| Node | Description | |---|---| | MpiSaveVideo | Fast save-video node with no in-graph preview and optional audio. Encodes an IMAGE frame batch (+ optional AUDIO) to a single .mp4 in one libx264 pass, on the engine — much faster than CreateVideo+SaveVideo for video export, and remote gens transfer only the final mp4. Toggle audio with the use_audio boolean; output length is pinned to the video (short audio padded, long audio trimmed), or flip truncate_to_audio to cut the clip to the audio instead. GPU-agnostic (no nvenc). Outputs video_path, the absolute path of the written mp4, so work can be ordered after the file exists — hang an MpiClearVramEnd off it and the VRAM clear stops costing render time; it also feeds MpiHasAudio / MpiLoadVideo directly. | | MpiLoadVideo | Fast, no-frills video loader by path. Decodes frames + audio and outputs source info (fps, frame_count, duration, width, height, has_audio) in one ffmpeg pass — no in-graph preview, no VHS param surface, so it loads much faster than Load Video (Path). Input named string to match MpiString / MpiAnyChecker; empty/missing path blocks downstream, unless block_if_empty is off (then outputs a blank 1x1 image + silent audio). Optional force_rate resamples to a target frame rate inside that same decode pass (0 = source rate), so fps, frame_count and duration all come out at the forced rate. | | MpiLoadAudio | Load audio from a file path into a ComfyUI AUDIO object, like the built-in Load Audio but driven by a string path (matches MpiString / MpiAnyChecker). Works on anything ffmpeg reads, including the audio track of a video. Empty/missing/audio-less path blocks downstream, unless block_if_empty is off (then outputs silent audio). |

Latent

| Node | Description | |---|---| | MpiSaveLatent | Save a latent to <output>/latents/<filename>.latent (or an absolute path), overwriting, then either stop the branch there or carry on — the boolean widget is the same continue/block gate as MpiBlocker. Unlike the core Save Latent node it handles packed audio+video latents (MiniMax H3's NestedTensor pair, which has no .contiguous() and crashes core). Single-tensor files stay byte-compatible with core Load Latent. The BOOLEAN output is never blocked, so it can drive a second branch. This is an output node, so ComfyUI runs it on every submit — set enabled false on runs that must not save (a stage-2 continue), or it drags the sampler feeding it along too. | | MpiLoadLatent | Load a latent written by MpiSaveLatent and continue the run — the second half of a two-stage sample. Rebuilds a packed audio+video pair (H3) or a plain tensor. Filename is a plain text field, not a dropdown, so a file written this session needs no UI refresh. A missing file blocks the latent output and reports loaded false, so the other branch can generate it instead. | | MpiStageLatents | The two-stage latent handshake in ONE node: saves stage 1, gates the preview, and loads the latent back on a continue. is_continue / is_preview are widgets, so a host app drives both stages of a single workflow file instead of shipping a _stage2 twin — a continue requests no latent inputs, so the stage-1 sampler is genuinely skipped rather than run and discarded. A missing latent raises instead of blocking every branch, which would otherwise leave a silent no-output run. Handles the packed audio+video latents (MiniMax H3) that crash core Save/Load Latent. | | MpiLatentUpscale | Upscale a latent to a target given in pixels, dividing by the model's own VAE stride instead of core's hardcoded // 8. Two fixes in one node. Core's Upscale Latent crashes on a packed audio+video latent (AttributeError: 'NestedTensor' object has no attribute 'reshape', because common_upscale reshapes) — this unbinds the pair, scales only the half with spatial dims (picked by dim() >= 5, not by index) and passes the audio half [B,32,2,T] through untouched. And core assumes the SD VAE factor, so an H3 target typed in pixels comes out at DOUBLE with no error at all — set stride 16 for MiniMax H3 and Krea2, 8 for the SD/SDXL family, and the number you type is the number you get. Built for the hi-res fix: small stage 1, upscale here, let stage 2's low sigmas finish at the target so only half the denoise pays for the resolution. |

Sampling

| Node | Description | |---|---| | MpiVideoSamplingPreview | Live RGB sampling previews for video models ComfyUI cannot preview. A format that names no taesd_decoder_name — MiniMax H3's MiniMaxH3Video / MiniMaxH3AV — falls back to Latent2RGB, the blocky colour blobs, and it does so silently: previews still appear, they are just bad. Wire a tiny TAEHV decoder (taeh3 for H3) into vae via MpiTinyVaeLoader — a plain VAELoader cannot build taeh3 — and this decodes the in-progress latent for real. Frames stream on the standard binary preview channel, preceded by one VHS_latentpreview marker, so a host app driving ComfyUI over the websocket receives them with no extra plumbing (unlike node-widget previewers, which base64 onto a private event only the ComfyUI web UI can see). Every sampler step decodes the whole clip from frame 0 and bursts it, announced at preview_rate fps, so the preview plays at the video's own speed instead of jumping one frame per step. Decoding the whole thing is not a cost choice: a TAEHV is temporal, its MemBlocks chain state forward, so a window taken out of the middle decodes with cold state and comes out as garbage. H3's own chunking is honoured as well — it codes 17 pixel frames per 5 latent tokens, so each chunk's prefix is trimmed instead of one global trim, and the encoder's 3-token tail pad is dropped, which is what makes the frame count and the timing exact (bit-exact against KJNodes' TAEHVDecoder on the same weight). Handles H3's packed audio+video NestedTensor by previewing the video half. On LTX, LanPaint-style guide latents (LTXVAddGuide's keyframe_idxs) are trimmed off the tail before decoding, so the preview shows the clip and not the conditioning frames pasted after it; H3 has no guide node, which is why the previewer shipped without it. A preview failure is caught, reported once and the generation continues. Sits anywhere on the model wire before the sampler and passes the model through untouched. | | MpiWindowedSampler | SamplerCustomAdvanced, run over overlapping temporal windows and cross-faded, so a clip too long to sample in one pass still fits. A video DiT's attention cost scales with T x H x W, so the same settings that succeed at one duration OOM at another with nothing else changed — measured on MiniMax H3 at 2K (latent 92x160) on a 16 GB card, T=27 samples fine and T=32 and T=37 both die inside comfy_kitchen.prequantize_int8_attention. That is not a tuning problem: H3's own trained minimum is 124 frames, which is T=37, so the model's shortest trained clip is the one the card cannot refine, and lowering the upscale factor does not help because stage-2 peak is set by the output latent size, not the factor. Intended for a refine pass, and unsound for first-pass generation: windowing works here only because the latent arrives globally coherent — motion, identity and framing were decided upstream — so a short sigma schedule adds local detail and two windows have nothing to disagree about. Noise is generated once for the whole clip and sliced per window, so both sides of a seam get identical noise over identical input and the cross-fade blends two near-identical denoisings rather than two different ones. Audio in a joint AV latent (H3, LTXV) is sliced to its window for conditioning and restored whole on output. Both halves matter: the model pairs audio tokens with video frames by index (MiniMaxH3AV sizes them round(frame_count * 5/3)), so handing a window the entire track tells it that a window starting at latent frame 15 begins at t=0 — a ~2 s offset that shows up as a mouth which opens but does not track, worst at the seam. No amount of overlap fixes that; widening the fade only smears the discontinuity. The refined audio is still discarded and the original stream kept, so nothing stitches a seam into the soundtrack. The model stays loaded across windows on purpose: unloading between them would re-stream the whole DiT over PCIe once per window, costing far more than the sampling it protects. A clip that already fits (T <= window) takes the plain single-pass path and is unchanged. Windows are cut on the model's temporal grid (frame_grid, default 5). H3 patchifies time as a 2-frame causal head plus blocks of 5, so a legal latent length is 5k+2 and a legal cut point is a multiple of 5 — a window of 21 ends four frames into a block, and the padded remainder comes back with flashing artefacts and a black final frame, which reads as a seam problem and is not one (the blend reconstructs a clip to under 1e-5 with an identity sampler). Both the window length and the stride are snapped, so windows start on the grid as well as end on it; snapping the stride can only ever hand out more overlap than was asked for, never less. window is a ceiling, not a target: the pass count is what costs time, so once it is fixed the window shrinks to the smallest legal size that still covers — at T=37 a ceiling of 27 plans two 22-frame windows rather than two 27s, which is 44 latent-frames of sampling instead of 54. Where grid and ceiling disagree the grid wins, because an over-ceiling window risks an OOM while an off-grid one guarantees a corrupt one; the log line reports both numbers. frame_grid=1 means the model has no such constraint and gives plain fixed-size windows. Both frame widgets are in video frames, not latent frames, because that is the unit an OOM is discovered in — "it died at 124 and survived at 90". The conversion needs no extra widget: H3 packs (1, 4, 4, 4, 4) video frames per latent frame indexed by absolute position mod 5, so the model's own temporal_downscale_ratio and the grid reproduce ComfyUI's own frame count exactly. The crossfade ramps over the frames the windows actually share, not the number you typed — grid snapping hands out more overlap than was asked for, and ramping over the smaller number crossfades part of the shared region and hard-cuts the rest. Width is the lever that matters: measured on H3 at T=37, one seam went from a face visibly becoming two, to a trace of distortion at 7 shared frames, to clean at 17. The windows denoise those frames independently, so the fade has to be wide enough to hide two valid but different answers. An info output reports the plan in both units. | | MpiTinyVaeLoader | Loads a tiny TAEHV preview decoder that ComfyUI's own VAELoader cannot build. Core sizes a TAEHV's edge convs as image_channels * patch_size**2 and only selects patch_size = 2 for latent_channels in [48, 32]. MiniMax H3's taeh3 is a 24-channel latent with a 12-wide decoder (3 RGB x 4 temporal frames), so core builds it 3 wide and VAELoader raises size mismatch for decoder.22.bias: [12] vs [3]. There is no branch for that shape and no argument that reaches it. This rebuilds the two edge convs at the right width — a strict state-dict load then matches all 128 tensors exactly — and corrects the two spatial ratios and the latent scaling core's fallback branch guessed wrong (taeh3 is 16x spatial and needs no scaling). Any decoder core already handles is passed straight through to VAELoader's own code path. Feed the output to MpiVideoSamplingPreview. |

Gaussian Splats

| Node | Description | |---|---| | MpiBrushTrain | Trains a Gaussian splat from a COLMAP dataset with Brush (Apache-2.0) and returns the exported .ply path. Brush is a native binary: it is downloaded per-platform on first use and SHA-256 verified against a pinned checksum — pinned rather than fetched, because a checksum served from the same host as the payload proves nothing about that host. Set brush_path to point at a copy you manage yourself and the node never touches the network. Two measured quirks shape the node. Brush writes zero bytes to stdout when it is not on a TTY, so there is no step line to parse; progress is read from the export directory instead, where Brush drops export_{iter}.ply every --export-every steps — a silent run is a working run, not a hung one. And a SplatKit dataset carries four COLMAP models, two of them under _spheresfm_work/ on camera model 11 (SPHERE), which Brush picks between nondeterministically and then dies on with Invalid camera model; so the node stages a root holding exactly one model, hardlinking the images rather than copying tens of GB. Cancellable — a 30000-step bake takes tens of minutes, and interrupting the prompt kills the trainer. max_resolution (default 2048) is the third quirk and the one that costs RAM: Brush caches one decoded u8 RGB copy of every training view in HOST memory, so the bake needs N_views x min(face_size, max_resolution)^2 x 3 bytes — ~11.5 GB for 984 faces at 2048, 4.5 GB at 1280 — and its own default of 1920 quietly discards the top of anything rendered larger. |


License

GNU AGPL-3.0 — the same licence as Cubric Vision, and compatible with ComfyUI's GPL-3.0 (GPLv3 §13).

Free to use, modify and share. If you build on this pack, your work carries the same licence — including when it is offered to others over a network.

Versions up to and including 1.2.6 were published under MIT. That grant stands for those versions; 1.2.7 onward is AGPL-3.0.