Save Image w/Metadata¶
Documentation¶
- Class name:
Save Image w_Metadata
- Category:
ImageSaverTools
- Output node:
True
This node specializes in saving images with additional metadata, allowing for enhanced traceability and context of the generated images. It supports various image formats and incorporates custom metadata, such as generation parameters and prompts, directly into the image files.
Input types¶
Required¶
images
- The images to be saved, potentially with added metadata for context.
- Comfy dtype:
IMAGE
- Python dtype:
torch.Tensor
filename
- The base filename for the saved images, which can include formatting for dynamic naming based on other parameters.
- Comfy dtype:
STRING
- Python dtype:
str
path
- The directory path where the images will be saved.
- Comfy dtype:
STRING
- Python dtype:
str
extension
- The file format extension for the saved images, supporting formats like PNG, JPEG, and WEBP.
- Comfy dtype:
COMBO[STRING]
- Python dtype:
str
steps
- The number of steps involved in the image generation process, potentially included in the metadata.
- Comfy dtype:
INT
- Python dtype:
int
cfg
- The CFG (classifier-free guidance) scale used during image generation, optionally included in the metadata.
- Comfy dtype:
FLOAT
- Python dtype:
float
modelname
- The name of the model used for generating the images, which can be included in the metadata.
- Comfy dtype:
COMBO[STRING]
- Python dtype:
str
sampler_name
- The name of the sampling method used in the generation process, optionally included in the metadata.
- Comfy dtype:
COMBO[STRING]
- Python dtype:
str
scheduler
- The scheduler used during the image generation, which can be included in the metadata.
- Comfy dtype:
COMBO[STRING]
- Python dtype:
str
Optional¶
positive
- Positive prompts or keywords that influenced the image generation, optionally included in the metadata.
- Comfy dtype:
STRING
- Python dtype:
str
negative
- Negative prompts or keywords that were avoided during the image generation, optionally included in the metadata.
- Comfy dtype:
STRING
- Python dtype:
str
seed_value
- The seed value used for random number generation during image creation, optionally included in the metadata.
- Comfy dtype:
INT
- Python dtype:
int
width
- The width of the generated images.
- Comfy dtype:
INT
- Python dtype:
int
height
- The height of the generated images.
- Comfy dtype:
INT
- Python dtype:
int
lossless_webp
- A flag indicating whether to save images in a lossless format when using WEBP.
- Comfy dtype:
BOOLEAN
- Python dtype:
bool
quality_jpeg_or_webp
- The quality setting for JPEG or WEBP images, affecting file size and image clarity.
- Comfy dtype:
INT
- Python dtype:
int
counter
- A counter that can be used for naming or ordering images, potentially included in the metadata.
- Comfy dtype:
INT
- Python dtype:
int
time_format
- The format for timestamps that can be included in the filename or metadata, providing temporal context for the image generation.
- Comfy dtype:
STRING
- Python dtype:
str
Output types¶
The node doesn't have output types
Usage tips¶
- Infra type:
CPU
- Common nodes: unknown
Source code¶
class ImageSaveWithMetadata:
def __init__(self):
self.output_dir = folder_paths.output_directory
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"filename": ("STRING", {"default": f'%time_%seed', "multiline": False}),
"path": ("STRING", {"default": '', "multiline": False}),
"extension": (['png', 'jpeg', 'webp'],),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
"modelname": (folder_paths.get_filename_list("checkpoints"),),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
},
"optional": {
"positive": ("STRING", {"default": 'unknown', "multiline": True}),
"negative": ("STRING", {"default": 'unknown', "multiline": True}),
"seed_value": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8}),
"height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8}),
"lossless_webp": ("BOOLEAN", {"default": True}),
"quality_jpeg_or_webp": ("INT", {"default": 100, "min": 1, "max": 100}),
"counter": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff }),
"time_format": ("STRING", {"default": "%Y-%m-%d-%H%M%S", "multiline": False}),
},
"hidden": {
"prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO"
},
}
RETURN_TYPES = ()
FUNCTION = "save_files"
OUTPUT_NODE = True
CATEGORY = "ImageSaverTools"
def save_files(self, images, seed_value, steps, cfg, sampler_name, scheduler, positive, negative, modelname, quality_jpeg_or_webp,
lossless_webp, width, height, counter, filename, path, extension, time_format, prompt=None, extra_pnginfo=None):
filename = make_filename(filename, seed_value, modelname, counter, time_format)
path = make_pathname(path, seed_value, modelname, counter, time_format)
ckpt_path = folder_paths.get_full_path("checkpoints", modelname)
basemodelname = parse_name(modelname)
modelhash = calculate_sha256(ckpt_path)[:10]
comment = f"{handle_whitespace(positive)}\nNegative prompt: {handle_whitespace(negative)}\nSteps: {steps}, Sampler: {sampler_name}{f'_{scheduler}' if scheduler != 'normal' else ''}, CFG Scale: {cfg}, Seed: {seed_value}, Size: {width}x{height}, Model hash: {modelhash}, Model: {basemodelname}, Version: ComfyUI"
output_path = os.path.join(self.output_dir, path)
if output_path.strip() != '':
if not os.path.exists(output_path.strip()):
print(f'The path `{output_path.strip()}` specified doesn\'t exist! Creating directory.')
os.makedirs(output_path, exist_ok=True)
filenames = self.save_images(images, output_path, filename, comment, extension, quality_jpeg_or_webp, lossless_webp, prompt, extra_pnginfo)
subfolder = os.path.normpath(path)
return {"ui": {"images": map(lambda filename: {"filename": filename, "subfolder": subfolder if subfolder != '.' else '', "type": 'output'}, filenames)}}
def save_images(self, images, output_path, filename_prefix, comment, extension, quality_jpeg_or_webp, lossless_webp, prompt=None, extra_pnginfo=None) -> list[str]:
img_count = 1
paths = list()
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
if images.size()[0] > 1:
filename_prefix += "_{:02d}".format(img_count)
if extension == 'png':
metadata = PngInfo()
metadata.add_text("parameters", comment)
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
filename = f"{filename_prefix}.png"
img.save(os.path.join(output_path, filename), pnginfo=metadata, optimize=True)
else:
filename = f"{filename_prefix}.{extension}"
file = os.path.join(output_path, filename)
img.save(file, optimize=True, quality=quality_jpeg_or_webp, lossless=lossless_webp)
exif_bytes = piexif.dump({
"Exif": {
piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(comment, encoding="unicode")
},
})
piexif.insert(exif_bytes, file)
paths.append(filename)
img_count += 1
return paths