Unified Prefix
The Unified Prefix Node
- portrait_image
- video_image
- portrait_fullprefix
- portrait_image
- video_fullprefix
- video_image
- frame_fullprefix
- frame_image
If you generate the same character across days of sessions - portraits one night, animation frames the next - your save folder is probably a disaster zone of 00001.png, copy_final_v3.png and existential dread. NoelUnifiedPrefix hands you a ready-made Save Image prefix like Risu/portrait_032 or Risu/animation_002/00007 and keeps the counters going between sessions, so you never overwrite an earlier take and never have to mentally reconstruct where the last run stopped. It's the "naming files so you don't have to" node.
It's a text/counter utility, not a generator - you wire its prefix strings into Save Image's filename_prefix and let it count for you. For character work specifically (keeping one character's output in its own folder tree), that's a surprisingly large quality-of-life win.
How it works
The node keeps a small JSON state file (character_counters.json inside the pack's _state folder) with a counter block per character_name: a portrait counter, a run counter, a frame counter, and a timestamp. Feed an image into portrait_image and it bumps the portrait counter and returns {character}/portrait_{###}. Feed a frame into video_image and it does the run/frame dance: if the gap since the last video frame is bigger than gap_timeout_sec, it starts a new animation run (run counter up, frame back to 1) - that's how it knows a fresh batch is a fresh animation_00N folder.
State is saved to disk under a thread lock on every run, which is why the counters survive restarts and even queue crashes.
Inputs that matter
character_name(defaulttest) - the folder name. Set this one, or everything piles undertest/.pad_portrait/pad_run/pad_frame(defaults 3/3/5) - zero-padding width per counter.gap_timeout_sec(default 1.5) - the time gap that triggers a new animation run. Keep it small for real-time frame streaming; bump it if you generate frames slowly and don't want every other frame to become a new run.portrait_image/video_image(optional) - the triggers that actually advance the counters. They're also passed straight through as theportrait_image/video_imageoutputs, so you can daisy-chain the node into the middle of a graph without extra wires.
Outputs: portrait_fullprefix, video_fullprefix, frame_fullprefix (the strings for Save Image), plus the two image pass-throughs. frame_fullprefix is nested - {character}/animation_{###}/{#####} - so frames land inside their run's folder.
Installing it
Same pack as its sibling, so this is a one-time setup:
cd ComfyUI/custom_nodes
git clone https://github.com/noelkim12/ComfyUI-NoelTextUtil
then restart ComfyUI, or search ComfyUI-ComfyUI-NoelTextUtil in ComfyUI Manager. No models, no dependencies, pure Python plus a bit of frontend.
Where people get burned
- The state file ships with the repo and is git-tracked. That means your clone arrives with a leftover
Risutest entry at portrait 32, and - the real trap - agit pullto update the pack can roll your carefully counted output back to that shipped state. If your counters matter, back up_state/character_counters.jsonbefore updating. - The counters only move when the image inputs fire. If you generate a batch without connecting
portrait_image, you'll get the same prefix every time and silently overwrite. The node counts what flows through it, not what you think you queued. - It's ComfyUI's own formatting you're feeding. The source comment is blunt: leave Save Image's
subfolderempty and put the prefix infilename_prefix, or the two conventions collide.
Is it essential? No - you could hand-roll these strings. But if you're running hundreds of portrait takes of one character, "never think about numbering again" is a real feature, and this is the cheapest version of it you'll find.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| character_name | STRING | test | — |
| pad_portrait | INT | 31–10 | — |
| pad_run | INT | 31–10 | — |
| pad_frame | INT | 51–10 | — |
| gap_timeout_sec | FLOAT | 1.50–60 | — |
| portrait_imageopt | IMAGE | — | |
| video_imageopt | IMAGE | — |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| portrait_fullprefix | STRING | — |
| portrait_image | IMAGE | — |
| video_fullprefix | STRING | — |
| video_image | IMAGE | — |
| frame_fullprefix | STRING | — |
| frame_image | IMAGE | — |