The old heuristic dropped entire metadata fields based on the raw string length hitting 64k, which is not the same as the serialized EXIF byte size Pillow actually enforces (MAX_BYTES_IN_MARKER = 65533). Replace it with: serialize with piexif, and if the resulting exif_bytes exceeds the hard limit, strip fields in order of least importance (a1111 -> xpkeywords -> UI workflow -> API prompt) and re-serialize after each strip. The API prompt (Make field) is the last to go since downstream tooling (watch scripts, config conversion) depends on it. Also embed the workflow JSON with compact separators to shrink the payload, and add a final try/except fallback that saves without EXIF if stripping still does not fit, so a large workflow can no longer raise an unhandled ValueError out of the save path.
🖼️ ULTools for ComfyUI
Collection of tools supporting txt2img generation in ComfyUI and other tasks.
Nodes Included
SaveImgAdv
Forked from Kaharos94 / https://github.com/Kaharos94/ComfyUI-Saveaswebp and heavily adapted
Custom node to save a picture as webp or jpeg file in ComfyUI.
Workflow saving and loading is supported by drag and drop or the standard load dialogue.
Basic support of automatic1111 compatible prompt embedding and CivitAI model hashes.
EXIF keywords can be stored in image meta data (goes nice with WD14 tagger).
📔 Description
This adds a custom node to save a picture as png, webp or jpeg file and also adds a script to Comfy to drag and drop generated images into the UI to load the workflow.
PNG images saved by default from the node shipped with ComfyUI are lossless, thus occupy more space compared to lossy formats.
⚙️ Options - Filename prefix: just the same as in the original Save Image node of ComfyUI. Supports creation of subfolders by adding slashes - Format: png / webp / jpeg - Compression: used to set the quality for webp/jpeg, does nothing for png - Lossy / lossless (lossless supported for webp and jpeg formats only) - Calc model hashes: whether to calculate hashes of models used in the workflow and append the in CivitAI compatible format (AutoV2), so images are mapped to their resources automatically when uploaded - Add automatic1111 meta: whether to add a1111 compatible meta information to the image exif data, so that CivitAI may parse the prompt etc. - Keywords: can be used to store keywords in EXIF field ImageIFD/XPKeywords, e.g. when using WD14 tagger (right click node, then click "convert keywords to input" to be able to plug them in)
The compression slider is a bit misleading: In lossless mode, it only affects the "effort" taken to compress where 100 is the smallest possible size and 1 is the biggest possible size, it's a tradeoff for saving speed.
In lossy mode, that's the other way around, where 100 is the biggest possible size with the least compression and 1 is the smallest possible size with maximum compression.
On default it's set to lossy with a compression of 90.
The workflow JSON is embedded in the EXIF metadata of the images, sections ImageIFD/Make for prompt and ImageIFD/ImageDescription for workflow meta. Automatic1111 prompt info is added at field ExifIFD/UserComment.
🚧 Known issues
Import of Webpfiles breaks if import a workflow that has }Prompt:{ in a Node that has dynamic wildcards disabled.
Images posted on CivitAI created with this node will only use the automatic1111 compatibility metadata, not the workflow as with original ComfyUI PNG files.
The filename index appending only works with an additional underscore being added.
Prompt compatibility with automatic1111 is basic and hacky and will surely break for more advanced workflows. It works reasonably well for basic txt2img and tries doing its best to get a full positive and negative prompt by traversing through the node graph.
CivitAI model hashes require the SHA256sum of the models used in the workflow which takes a while to calculate on the first execution. Hashes are cached in a simple JSON file.
CLIPTextEncodeWithStats
Alternative for the regular CLIPTextEncode node which gathers statistics of the number of tokens and batches created from the input text.
Due to limitations of the litegraph backend the node currently is not capable of a live calculation of the statistics. Instead, upon execution of the prompt, the node calculates the statistic and creates an image of the table which can be viewed in the PreviewImage node. It is an ugly solution but at least it works.
OpenPoseEditorAdv
Forked from space-nuko / https://github.com/space-nuko/ComfyUI-OpenPose-Editor with bugs fixed and heavily adapted
A port of the openpose-editor extension for stable-diffusion-webui
The editor automatically generates a mask for each pose. Useful for specific conditioning per pose.
Usage
Import the image > OpenPoseEditorAdv node, add a new pose and use it like you would a LoadImage node. Each change you make to poses will be saved to the input folder of ComfyUI.
Example Workflow
SolidMaskAdv
A simple addition to the regular solid mask node to support circular mask generation.
It has a switch for the type of the mask to be generated (rectangle or ellipsis).
KorniaFilters (Clahe / UnsharpMask / Histogram)
Three image post-processing nodes built on kornia, grouped in the image category.
Clahe — applies Contrast Limited Adaptive Histogram Equalization to the lightness channel (LAB space) to enhance local contrast without blowing out highlights. - clip_limit: higher values increase contrast but risk amplifying noise (default 1.0) - grid_size_wh: tile grid size for the adaptive equalization, must be a power-of-2-friendly value (default 8)
UnsharpMask — sharpens the image via unsharp masking. - kernel_size: Gaussian kernel size, odd values (default 9) - sigma: Gaussian blur radius controlling the amount of sharpening (default 2.5)
Histogram — renders the luminance or per-channel histogram of each image in a batch as a 1600×400 image (dark theme), useful for previewing exposure/distribution before and after grading.
- mode: lightness (single luminance curve) or rgb (three overlaid channel curves)
All three pass through unmodified when their effect would be a no-op (e.g. grid_size_wh=1 for Clahe, kernel_size=1 & sigma=0 for UnsharpMask).
TextFindAndReplaceAdv
Forked from WASasquatch / https://github.com/WASasquatch/was-node-suite-comfyui (WAS_Search_and_Replace) and adapted
A find/replace node for prompt strings with a few extra controls over the basic ComfyUI string replace.
- text: the input string (force-input, plug in from any string node)
- find / replace: each may contain multiple lines — one find/replace pair per line, applied in order. If the two lists differ in length the last entry of the shorter list is reused for the remaining lines.
- max_count: maximum number of replacements per find pair,
0means replace all (default 0)
Matching is literal (the find string is regex-escaped), so special characters need no escaping. In addition to the resulting result_text, the node also outputs the total number of replacements performed as NUMBER, FLOAT, and INT for use in downstream logic.
🖥️ Installation
In your terminal/console, navigate to your ComfyUI custom nodes directory and clone this repo:
git clone https://github.com/jkrauss82/ultools-comfyui.git
Change into the directory:
cd ultools-comfyui
Activate your python virtual environment and install the requirements (piexif, pandas, numpy):
pip install -r requirements.txt
Restart ComfyUI.
⚠️ Warning
Hobby project, if you encounter bugs or problems feel free to create an issue, but not guarantee that it will be taken care of right away or ever.
