Nodes/BrainDead Nodes/BD Bulk Save
ComfyUI Node

BD Bulk Save

The batch-save node that ends the spaghetti

By BizaNator·Created 8 months ago·Updated 3 days ago· 15
BD Bulk Save
  • 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
labelsalbedo normal roughness metallic ao
label_prefix_
context_id
formatpng
jpg_quality95
skip_if_existsfalse
custom_vars
save_alpha_separatelyfalse
invert_alphafalse

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. albedo on 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 _, so albedo becomes _albedo and 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=value lines layered on top of the context's variables, so one batch can dump into subfolder=PBR while another uses subfolder=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_mask bakes a mask into the saved file's alpha (white = opaque), invert_alpha flips polarity, and save_alpha_separately also writes a greyscale _alpha.png alongside.

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.

Category🧠BrainDead/Cache

Inputs (26)

NameTypeDefaultDescription
labelsSTRINGalbedo normal roughness metallic aoOne 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_prefixoptSTRING_Prepended to each label before becoming the save suffix. Default '_' so label='albedo' → suffix='_albedo' → file ends in '_albedo'.
context_idoptSTRINGMatch a BD_SaveContext id. Empty + exactly one context registered = auto-pick.
formatoptCOMBOpng3 options: png, jpg, webp
jpg_qualityoptINT951–100
skip_if_existsoptBOOLEANfalseIf True, don't overwrite existing files (still reports their path).
custom_varsoptSTRINGExtra 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_separatelyoptBOOLEANfalseAlso 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_maskoptMASKBake 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_alphaoptBOOLEANfalseFlip 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)

NameTypeDescription
saved_countINT
filepathsSTRING
statusSTRING