mirror of
https://github.com/tusharbhutt/Endless-Nodes.git
synced 2026-03-21 20:42:12 -03:00
Fix Image Saver bug, add standalone image saver
This commit is contained in:
11
README.md
11
README.md
@@ -2,7 +2,7 @@
|
||||
Some basic custom nodes for the ComfyUI user interface for Stable Diffusion. Features:
|
||||
|
||||
+ An image saver for images and JSON files to base folder, custom folders for one, or custom folders for both. Also allows for Python timestamping
|
||||
+ Two aesthetic scoring models, one based on the same as AUTO1111, the other based on Image Reward
|
||||
+ (REMOVED FOR NOW) Two aesthetic scoring models, one based on the same as AUTO1111, the other based on Image Reward
|
||||
+ Converters for various numerical functions to the other (int, float, number) and to strings. Still in beta
|
||||
+ Switches for text and numbers
|
||||
+ Parameter collection nodes
|
||||
@@ -12,6 +12,11 @@ When using the [ComfyUI](https://github.com/comfyanonymous/ComfyUI) interface fo
|
||||
|
||||
Rightly or wrongly, I am pretending to teach myself a bit of Python to get some nodes up and running to do what I'd like. Yes, I am using ChatGPT, and yes I am a glutton for punishment. There are no promises that these nodes will work for you or that I will maintain them. Feel free to do with them as you wish, according to the license model.
|
||||
|
||||
**UPDATE: Oct 8, 2023**
|
||||
|
||||
+ ***Added a standalone saver node. Fixed bug in this node and main node where image files were overwritten***
|
||||
|
||||
|
||||
**UPDATE: Oct 7, 2023**
|
||||
|
||||
+ ***REMOVED THE AESTHETIC SCORERS, TOO MANY PEOPLE CAN'T GET CLIP LOADED. WILL REVISIT AFTER VACATION***
|
||||
@@ -25,7 +30,7 @@ Rightly or wrongly, I am pretending to teach myself a bit of Python to get some
|
||||
**UPDATE: Oct 3, 2023**
|
||||
|
||||
+ Added an Image Saver that can place JSON files ***in separate folders***
|
||||
+ Added nodes to convert from one numeric type to another, and to string. **Still in beta**
|
||||
+ Added nodes to convert from one numeric type to another, and to string.
|
||||
|
||||
**UPDATE: Sep 24, 2023**
|
||||
|
||||
@@ -207,4 +212,4 @@ These nodes may or may not be maintained. They work on my system but may not on
|
||||
+ Took some base code from the [WAS save image node](https://github.com/WASasquatch/was-node-suite-comfyui) to repurpose it
|
||||
|
||||
+ Thanks to [chrisgoringe](https://github.com/chrisgoringe) for some vital insgiht into correcting mesy commas in the tuples for the converter nodes, much appreciated!
|
||||
#
|
||||
#
|
||||
176
RevertSaver.py
Normal file
176
RevertSaver.py
Normal file
@@ -0,0 +1,176 @@
|
||||
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
|
||||
}
|
||||
@@ -51,9 +51,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ESS Combo Parameterizer": "♾️🌊✨ Combo Parameterizer",
|
||||
"ESS Combo Parameterizer & Prompts": "♾️🌊✨ Combo Parameterizer & Prompts",
|
||||
"ESS Image Saver with JSON": "♾️🌊✨ Image Saver with JSON",
|
||||
"ESS Aesthetic Scoring": "♾️🌊✨ Aesthetic Scoring",
|
||||
# "ESS Aesthetic Scoring": "♾️🌊✨ Aesthetic Scoring",
|
||||
# "ESS Aesthetic Scoring Auto": "♾️🌊✨ Aesthetic Scoring Auto",
|
||||
"ESS Image Reward": "♾️🌊✨ Image Reward",
|
||||
# "ESS Image Reward": "♾️🌊✨ Image Reward",
|
||||
# "ESS Image Reward Auto": "♾️🌊✨ Image Reward Auto",
|
||||
"ESS Float to Integer": "♾️🌊✨ Float to Integer",
|
||||
"ESS Float to Number": "♾️🌊✨ Float to Number",
|
||||
@@ -75,4 +75,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
print("\033[36m An Endless Sea of Stars Custom Nodes V0.31 \033[34m: \033[92mLoaded\033[0m")
|
||||
print("\033[36m An Endless Sea of Stars Custom Nodes V0.37 \033[34m: \033[92mLoaded\033[0m")
|
||||
@@ -10,6 +10,8 @@
|
||||
# Endless Sea of Stars Custom Node Collection
|
||||
#https://github.com/tusharbhutt/Endless-Nodes
|
||||
#----------------------------------------------
|
||||
# Oct 08/23, V0.37: Bug fix in Image Saver module that would overwrite files was corrected
|
||||
# Oct 07/23, V0.36: Killed the scorers until I figure out why CLIP won't load for some people
|
||||
# Oct 06/23, V0.35: Reverted the Image Saver module as I had inadvertently removed the ability to add date and time to the filenames
|
||||
# Oct 05/23, V0.34: Renamed nodes to make them shorter and easier to search for, breaks names of previous workflows though
|
||||
# Oct 05/23, V0.33: Added random text input choice for six and eight nodes inputs
|
||||
@@ -54,7 +56,7 @@ import json
|
||||
import math
|
||||
import numpy as np
|
||||
import os
|
||||
import pytorch_lightning as pl
|
||||
#import pytorch_lightning as pl
|
||||
import re
|
||||
import socket
|
||||
import statistics
|
||||
@@ -623,7 +625,6 @@ class EndlessNode_ComboXLParameterizer:
|
||||
# ----------------------------------------------
|
||||
# Saver type one: saves IMAGE and JSON files, can specify separate folders for each, or one, or none, and use Python timestamps
|
||||
|
||||
|
||||
class EndlessNode_ImageSaver:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
@@ -638,7 +639,7 @@ class EndlessNode_ImageSaver:
|
||||
"delimiter": ("STRING", {"default": "_"}),
|
||||
"filename_number_padding": ("INT", {"default": 4, "min": 1, "max": 9, "step": 1}),
|
||||
"filename_number_start": (["false", "true"],),
|
||||
"img_folder": ("STRING", {"default": None}),
|
||||
"image_folder": ("STRING", {"default": None}),
|
||||
"json_folder": ("STRING", {"default": None}),
|
||||
},
|
||||
"hidden": {
|
||||
@@ -653,7 +654,7 @@ class EndlessNode_ImageSaver:
|
||||
|
||||
def save_images(self, images, filename_prefix="ComfyUI", delimiter="_",
|
||||
filename_number_padding=4, filename_number_start='false',
|
||||
img_folder=None, json_folder=None, prompt=None, extra_pnginfo=None):
|
||||
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)
|
||||
@@ -678,7 +679,7 @@ class EndlessNode_ImageSaver:
|
||||
|
||||
img_file, json_file = self.generate_filenames(filename_prefix, delimiter, counter,
|
||||
filename_number_padding, filename_number_start,
|
||||
img_extension, img_folder, json_folder)
|
||||
img_extension, image_folder, json_folder)
|
||||
|
||||
try:
|
||||
if img_extension == '.png':
|
||||
@@ -696,7 +697,7 @@ class EndlessNode_ImageSaver:
|
||||
"image_filename": os.path.basename(img_file),
|
||||
"image_path": img_file,
|
||||
"json_filename": os.path.basename(json_file),
|
||||
"text_path": json_file,
|
||||
"json_path": json_file,
|
||||
"type": self.type
|
||||
})
|
||||
|
||||
@@ -713,8 +714,8 @@ class EndlessNode_ImageSaver:
|
||||
return {"ui": {"results": results}}
|
||||
|
||||
def generate_filenames(self, filename_prefix, delimiter, counter,
|
||||
filename_number_padding, filename_number_start, img_extension,
|
||||
img_folder, json_folder):
|
||||
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"
|
||||
@@ -722,27 +723,20 @@ class EndlessNode_ImageSaver:
|
||||
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"
|
||||
|
||||
# Construct full paths for image and text files based on folders provided
|
||||
# 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 img_folder:
|
||||
img_folder = self.replace_date_time_placeholders(img_folder)
|
||||
os.makedirs(img_folder, exist_ok=True) # Create the image folder if it doesn't exist
|
||||
img_file = os.path.join(img_folder, img_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_folder = self.replace_date_time_placeholders(json_folder)
|
||||
os.makedirs(json_folder, exist_ok=True) # Create the image folder if it doesn't exist
|
||||
json_file = os.path.join(json_folder, json_file)
|
||||
else:
|
||||
json_file = os.path.join(os.path.dirname(img_file), json_file)
|
||||
|
||||
# Apply placeholders for date and time in filenames and folder
|
||||
img_file = self.replace_date_time_placeholders(img_file)
|
||||
json_file = self.replace_date_time_placeholders(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):
|
||||
@@ -754,25 +748,19 @@ class EndlessNode_ImageSaver:
|
||||
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"
|
||||
|
||||
# Construct full paths for image and text files based on folders provided
|
||||
# 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 img_folder:
|
||||
img_folder = self.replace_date_time_placeholders(img_folder)
|
||||
os.makedirs(img_folder, exist_ok=True) # Create the image folder if it doesn't exist
|
||||
img_file = os.path.join(img_folder, img_file)
|
||||
else:
|
||||
img_file = os.path.join(self.output_dir, img_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_folder = self.replace_date_time_placeholders(json_folder)
|
||||
os.makedirs(json_folder, exist_ok=True) # Create the image folder if it doesn't exist
|
||||
json_file = os.path.join(json_folder, json_file)
|
||||
else:
|
||||
json_file = os.path.join(os.path.dirname(img_file), json_file)
|
||||
|
||||
# Apply placeholders for date and time in filenames and folder
|
||||
img_file = self.replace_date_time_placeholders(img_file)
|
||||
json_file = self.replace_date_time_placeholders(json_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
|
||||
|
||||
@@ -793,8 +781,6 @@ class EndlessNode_ImageSaver:
|
||||
filename = filename.replace(placeholder, replacement)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
# ______________________________________________________________________________________________________________________________________________________________
|
||||
# CONVERTER NODES BLOCK #
|
||||
#
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
# clip @ git+https://github.com/openai/CLIP.git
|
||||
pytorch-lightning
|
||||
# image-reward==1.4
|
||||
colorama
|
||||
# ftfy
|
||||
# regex
|
||||
# tqdm
|
||||
colorama
|
||||
Reference in New Issue
Block a user