BD Bulk Save
The batch-save node that ends the spaghetti
- alpha_mask
- input_1
- input_2
- input_3
- input_4
- input_5
- input_6
- input_7
- input_8
- input_9
- input_10
- input_11
- input_12
- input_13
- input_14
- input_15
- input_16
- saved_count
- filepaths
- status
Here's the workflow that used to be ugly: a PBR derivation spits out albedo, normal, roughness, metallic, AO, ORM, ARM - seven images that all need saving with proper names. The old way was seven parallel save nodes, each with its own filename logic, each one an opportunity for a typo and a graph so wide it needs two monitors. BD Bulk Save kills that. It takes up to 16 inputs and writes them all in a single run, naming each file from a one-line-per-slot label list. The node description literally tells you the intended use: "Wire BD_DerivePBR's 7 outputs to input_1..input_7, set labels accordingly, and all 7 files save in a single Run. No queueing."
The inputs that matter
- input_1 … input_16 - the data to save. They're typed
*(ANY), so images, masks, strings, whatever. Empty slots are skipped - you can wire three inputs today and nine tomorrow without touching anything else. - labels - one label per line, aligned with the wired inputs after skipping empty slots.
albedoon line one names input_1's file. If your label list is shorter than the wired inputs, the extra files save with no suffix. - label_prefix - what gets prepended to each label to form the file suffix. Default is
_, soalbedobecomes_albedoand the file ends..._albedo_001.png. Set it to""if you want no separator. - context_id - for the pack's Save Context system. Empty + exactly one BD Save Context registered = auto-pick. Otherwise specify which context's path template to resolve against.
- custom_vars - extra
key=valuelines layered on top of the context's variables, so one batch can dump intosubfolder=PBRwhile another usessubfolder=textures. - format - PNG by default, JPG available with jpg_quality (95 default).
- skip_if_exists - True and it won't overwrite existing files, though it still reports their paths.
- alpha_mask / invert_alpha / save_alpha_separately - the alpha pipeline.
alpha_maskbakes a mask into the saved file's alpha (white = opaque),invert_alphaflips polarity, andsave_alpha_separatelyalso writes a greyscale_alpha.pngalongside.
Outputs: saved_count (INT), filepaths (STRING), and status (STRING) - so you can hand the paths off to the next node if something downstream needs to know where the files landed.
The workflow shape
The pack's recommended PBR pipeline is the perfect illustration: BD Derive PBR Maps outputs its seven maps, all of them feed input_1..input_7, the labels field reads albedo\nnormal\nroughness\nmetallic\nao\norm\narm, and one run writes all seven files into the context-resolved folder. One node doing the job of seven. If you're not on the PBR pipeline, it's just as good for "save this character's head, hands, clothes and shoes in one shot."
Installing it
Part of BizaNator/ComfyUI-BrainDead (BrainDeadGuild). ComfyUI Manager → search "BrainDead" → install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/BizaNator/ComfyUI-BrainDead
cd ComfyUI-BrainDead
pip install -r requirements.txt
Restart; it's under 🧠BrainDead/Cache.
Gotchas
The label alignment is the thing that bites. Labels align with wired inputs after empty slots are skipped - so if you wire input_1 and input_3 but leave input_2 empty, the labels line up with input_1 and input_3, not with the slot numbers. It's sensible once you know it, infuriating until you do. Second: this node saves in one run - it's the right tool when the downstream is just saving. If the downstream is more complex (per-part upscaling before save), the pack's BD For Each Run iterator is the one that emits per-item labels across runs. And remember it's an output node: it runs on every queue, so keep skip_if_exists in mind if you're re-running the same workflow repeatedly.
Inputs (26)
| Name | Type | Default | Description |
|---|---|---|---|
| labels | STRING | albedo normal roughness metallic ao | One label per line, aligned with WIRED inputs (after skipping empty slots). If labels list is shorter than wired inputs, missing labels save with empty suffix. |
| label_prefixopt | STRING | _ | Prepended to each label before becoming the save suffix. Default '_' so label='albedo' → suffix='_albedo' → file ends in '_albedo'. |
| context_idopt | STRING | Match a BD_SaveContext id. Empty + exactly one context registered = auto-pick. | |
| formatopt | COMBO | png | 3 options: png, jpg, webp |
| jpg_qualityopt | INT | 951–100 | — |
| skip_if_existsopt | BOOLEAN | false | If True, don't overwrite existing files (still reports their path). |
| custom_varsopt | STRING | Extra variables applied to ALL bulk-saved files in this batch. One per line as key=value. Layered ON TOP of the context's custom_vars. Examples: subfolder=PBR materials=metal pass=4k Become %subfolder%, %materials%, %pass% in the template. Empty values OK. | |
| save_alpha_separatelyopt | BOOLEAN | false | Also write the alpha channel as a standalone greyscale PNG alongside the main file, named with the same suffix + '_alpha'. Source of the alpha pixels: • alpha_mask wired → uses the mask (after invert_alpha) • no mask wired, image has 4 channels (RGBA) → extracts the embedded A channel • no mask wired, image is RGB (3ch) → no alpha to extract; this output is skipped NOTE: the _alpha.png is a RAW greyscale representation — white pixel = opaque area, black pixel = transparent area. It is NOT a composited preview; open the main RGBA PNG in a viewer that supports transparency to see the actual cut-out. |
| alpha_maskopt | MASK | Bake this mask into the saved file's alpha channel before writing. Convention: WHITE (1.0) = OPAQUE, BLACK (0.0) = TRANSPARENT. Face mask (white face, black background) → face is opaque, background is cut out. Background mask (white background, black face) → use invert_alpha=True. If you already have an RGBA image (4-channel), you do NOT need to wire alpha_mask — the embedded alpha is preserved automatically when saving. The upstream image tensor is NOT modified — transparency is baked only in the saved file. Accepts batched masks (B, H, W); single (H,W) or (1,H,W) applies to all frames. | |
| invert_alphaopt | BOOLEAN | false | Flip the mask polarity before baking as alpha. Use when your mask has white=background / black=subject and you want the subject opaque. |
| input_1opt | * | Input slot #1. Wire any data type. Empty slots are skipped. | |
| input_2opt | * | Input slot #2. Wire any data type. Empty slots are skipped. | |
| input_3opt | * | Input slot #3. Wire any data type. Empty slots are skipped. | |
| input_4opt | * | Input slot #4. Wire any data type. Empty slots are skipped. | |
| input_5opt | * | Input slot #5. Wire any data type. Empty slots are skipped. | |
| input_6opt | * | Input slot #6. Wire any data type. Empty slots are skipped. | |
| input_7opt | * | Input slot #7. Wire any data type. Empty slots are skipped. | |
| input_8opt | * | Input slot #8. Wire any data type. Empty slots are skipped. | |
| input_9opt | * | Input slot #9. Wire any data type. Empty slots are skipped. | |
| input_10opt | * | Input slot #10. Wire any data type. Empty slots are skipped. | |
| input_11opt | * | Input slot #11. Wire any data type. Empty slots are skipped. | |
| input_12opt | * | Input slot #12. Wire any data type. Empty slots are skipped. | |
| input_13opt | * | Input slot #13. Wire any data type. Empty slots are skipped. | |
| input_14opt | * | Input slot #14. Wire any data type. Empty slots are skipped. | |
| input_15opt | * | Input slot #15. Wire any data type. Empty slots are skipped. | |
| input_16opt | * | Input slot #16. Wire any data type. Empty slots are skipped. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| saved_count | INT | — |
| filepaths | STRING | — |
| status | STRING | — |