import numpy as np from PIL import Image from PIL.PngImagePlugin import PngInfo import colorama from colorama import init, Fore, Back, Style import os import json import time import socket import re import folder_paths import datetime # Added to support date and time placeholders # Initialize colorama colorama.init(autoreset=True) class EndlessNode_ImageSaver: def __init__(self): self.output_dir = folder_paths.get_output_directory() self.type = "output" @classmethod def INPUT_TYPES(cls): return { "required": { "images": ("IMAGE",), "filename_prefix": ("STRING", {"default": "ComfyUI"}), "delimiter": ("STRING", {"default": "_"}), "filename_number_padding": ("INT", {"default": 4, "min": 1, "max": 9, "step": 1}), "filename_number_start": (["false", "true"],), "image_folder": ("STRING", {"default": None}), "json_folder": ("STRING", {"default": None}), }, "hidden": { "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO" }, } RETURN_TYPES = () FUNCTION = "save_images" OUTPUT_NODE = True CATEGORY = "Endless 🌊✨/IO" def save_images(self, images, filename_prefix="ComfyUI", delimiter="_", filename_number_padding=4, filename_number_start='false', image_folder=None, json_folder=None, prompt=None, extra_pnginfo=None): # Replace illegal characters in the filename prefix with dashes filename_prefix = re.sub(r'[<>:"\/\\|?*]', '-', filename_prefix) # Set IMG Extension img_extension = '.png' counter = 1 results = list() for image in images: i = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) metadata = PngInfo() 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])) img_file, json_file = self.generate_filenames(filename_prefix, delimiter, counter, filename_number_padding, filename_number_start, img_extension, image_folder, json_folder) try: if img_extension == '.png': img.save(img_file, pnginfo=metadata, compress_level=4) elif img_extension == '.jpeg': img.save(img_file, quality=100, optimize=True) with open(json_file, 'w', encoding='utf-8', newline='\n') as f: if prompt is not None: f.write(json.dumps(prompt, indent=4)) print(Fore.GREEN + f"+ File(s) saved to: {img_file}") results.append({ "image_filename": os.path.basename(img_file), "image_path": img_file, "json_filename": os.path.basename(json_file), "json_path": json_file, "type": self.type }) except OSError as e: print(Fore.RED + " + Unable to save file: ", end='') print({img_file}) print(e) except Exception as e: print(Fore.RED + " + Unable to save file due to the following error: ", end='') print(e) counter += 1 return {"ui": {"results": results}} def generate_filenames(self, filename_prefix, delimiter, counter, filename_number_padding, filename_number_start, img_extension, image_folder, json_folder): if filename_number_start == 'true': img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" else: img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" # Apply placeholders for date and time in filenames img_file = self.replace_date_time_placeholders(img_file) json_file = self.replace_date_time_placeholders(json_file) # Construct full paths for image and text files based on folders provided if image_folder: img_file = os.path.join(image_folder, img_file) else: img_file = os.path.join(self.output_dir, img_file) if json_folder: json_file = os.path.join(json_folder, json_file) else: json_file = os.path.join(os.path.dirname(img_file), json_file) # Check if files with the same name exist, increment counter if necessary while os.path.exists(img_file) or os.path.exists(json_file): counter += 1 if filename_number_start == 'true': img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" else: img_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}{img_extension}" json_file = f"{filename_prefix}{delimiter}{counter:0{filename_number_padding}}.json" # Apply placeholders for date and time in filenames img_file = self.replace_date_time_placeholders(img_file) json_file = self.replace_date_time_placeholders(json_file) if image_folder: img_file = os.path.join(image_folder, img_file) else: img_file = os.path.join(self.output_dir, img_file) if json_folder: json_file = os.path.join(json_folder, json_file) else: json_file = os.path.join(os.path.dirname(img_file), json_file) return img_file, json_file def replace_date_time_placeholders(self, filename): # Replace date and time placeholders with actual date and time strings now = datetime.datetime.now() placeholders = { '%Y': now.strftime('%Y'), # Year with century as a decimal number '%y': now.strftime('%y'), # Year without century as a zero-padded decimal number '%m': now.strftime('%m'), # Month as a zero-padded decimal number '%d': now.strftime('%d'), # Day of the month as a zero-padded decimal number '%H': now.strftime('%H'), # Hour (24-hour clock) as a zero-padded decimal number '%M': now.strftime('%M'), # Minute as a zero-padded decimal number '%S': now.strftime('%S'), # Second as a zero-padded decimal number } for placeholder, replacement in placeholders.items(): filename = filename.replace(placeholder, replacement) return filename NODE_CLASS_MAPPINGS = { "β™ΎοΈπŸŒŠβœ¨ Image Saver with JSON": EndlessNode_ImageSaver }