From 423fdec4fe16852f0fa0da2021f22c63ebd141c7 Mon Sep 17 00:00:00 2001 From: KLL535 Date: Mon, 3 Feb 2025 02:20:37 +0300 Subject: [PATCH] v1.0.4_alfa --- CHANGELOG.md | 3 +- nodes/nodes.py | 234 ++++++++++++++++++++++++++++++------------------- 2 files changed, 148 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 194ee3d..1991fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # nightly +- update `Simple Load Image With Metadata` and `Simple Image Saver (as Forge)` # v1.0.3 - fix random -- add node Simple Load Image With Metadata +- add node `Simple Load Image With Metadata` # v1.0.2 - fastest load LoRA hash - add button **Update LoRA dictionary** in `Simple Lora Loader` for update **lora_name.json** diff --git a/nodes/nodes.py b/nodes/nodes.py index 0ffaf0a..210ee28 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -482,12 +482,12 @@ class SimpleImageSaver: return { "required": { "images": ("IMAGE", { "tooltip": "image(s) to save", "forceInput": True}), - "prompt": ("STRING", {"default": "", "multiline": False, "tooltip": "STRING", "forceInput": True}), "output_path": ("STRING", {"default": "", "multiline": False, "tooltip": "Path where images will be saved"}), - "SEED": ("INT", {"default": '0', "tooltip": "INT", "forceInput": True}), - "modelname": ("STRING", {"default": '', "multiline": False, "tooltip": "STRING, use Checkpoint Loader with Name (Image Saver) node", "forceInput": True}), }, "optional": { + "prompt_text": ("STRING", {"default": "", "multiline": False, "tooltip": "STRING", "forceInput": True}), + "SEED": ("INT", {"default": None, "tooltip": "INT", "forceInput": True}), + "modelname": ("STRING", {"default": '', "multiline": False, "tooltip": "STRING, use Checkpoint Loader with Name (Image Saver) node", "forceInput": True}), "steps": ("INT", {"default": 0, "tooltip": "INT", "forceInput": True}), "sampler": (any, {"default": None, "tooltip": "ANY, use Sampler Selector (Image Saver) node", "forceInput": True}), "schedule": (any, {"default": None, "tooltip": "ANY, use Scheduler Selector (Image Saver) node", "forceInput": True}), @@ -500,10 +500,15 @@ class SimpleImageSaver: "civitai_lora": ("STRING", {"default": '', "multiline": False, "tooltip": "STRING, Loras name from Simple Lora Loader node", "forceInput": True}), "civitai_lora_hash": ("STRING", {"default": '', "multiline": False, "tooltip": "STRING, Loras hash from Simple Lora Loader node", "forceInput": True}), "negative": ("STRING", {"default": '', "multiline": False, "tooltip": "STRING, Negative prompt", "forceInput": True}), - "save_workflow": ("BOOLEAN", {"default": False, "tooltip": "if True, saves workflow in the image"}), + "save_comfy_workflow": ("BOOLEAN", {"default": True, "tooltip": "if True, saves workflow in the image"}), + "save_comfy_prompt": ("BOOLEAN", {"default": False, "tooltip": "if True, saves workflow in the image"}), + "override_parameters": ("STRING", {"default": None, "multiline": False, "tooltip": "If present, this text will be written to image metadata (parameters)", "forceInput": True}), + "override_workflow": ("STRING", {"default": None, "multiline": False, "tooltip": "If present, this text will be written to image metadata (workflow)", "forceInput": True}), + "override_prompt": ("STRING", {"default": None, "multiline": False, "tooltip": "If present, this text will be written to image metadata (prompt)", "forceInput": True}), }, "hidden": { - "extra_pnginfo": "EXTRA_PNGINFO", + "prompt": "PROMPT", + "extra_pnginfo": "EXTRA_PNGINFO", }, } @@ -528,22 +533,123 @@ class SimpleImageSaver: i = new return i+1 - def image_saver(self, images, prompt, output_path, SEED, modelname, steps=0, sampler=None, schedule=None, CFG_scale=0.0, distilled_CFG_scale=0.0, width=0, height=0, beta_schedule_alpha=0.0, beta_schedule_beta=0.0, civitai_lora="", civitai_lora_hash="",negative="",save_workflow=False,extra_pnginfo=None): + def parse_parameters(self, text): + pattern = r"\s*,\s*(?![^\"]*\"\,)" + items = re.split(pattern, text) + params = {} + for item in items: + if ":" in item: + key, value = item.split(":", 1) + params[key.strip()] = value.strip() + return params - metadata_text = "" + def image_saver(self, images, output_path, prompt_text = "", SEED = None, modelname = "", steps=0, sampler=None, schedule=None, CFG_scale=0.0, distilled_CFG_scale=0.0, width=0, height=0, beta_schedule_alpha=0.0, beta_schedule_beta=0.0, civitai_lora="", civitai_lora_hash="", negative="", save_comfy_workflow=True, save_comfy_prompt=False, override_parameters=None, override_workflow=None, override_prompt=None, prompt=None, extra_pnginfo=None): now = datetime.now() date = now.strftime("%Y-%m-%d") output_path = output_path.strip() if output_path == '': - return () + return ("",) output_path = os.path.join(output_path, date) if not os.path.exists(output_path): os.makedirs(output_path, exist_ok=True) + seed_text = "" + if SEED != None: + seed_text = str(SEED) + +##### metadata parameters (Forge style) ##### + + forge = "" + if override_parameters == None: + + prompt_and_lora = prompt_text + if civitai_lora != "": + prompt_and_lora = prompt_and_lora + ' ' + civitai_lora + + forge = prompt_and_lora + '\n' + + if negative != "": + forge = forge + "Negative prompt: "+ negative + '\n' + + if steps != 0: + forge = forge + "Steps: "+str(steps)+", " + if sampler != None: + forge = forge + "Sampler: "+str(sampler)+", " + if schedule != None: + forge = forge + "Schedule type: "+str(schedule)+", " + if CFG_scale != 0.0: + forge = forge + "CFG scale: "+str(CFG_scale)+", " + if distilled_CFG_scale != 0.0: + forge = forge + "Distilled CFG Scale: "+str(distilled_CFG_scale)+", " + forge = forge + "Seed: "+seed_text+", " + if width != 0: + if height != 0: + forge = forge + "Size: " + str(width) + "x" + str(height) + ", " + + if modelname != "": + ckpt_path = folder_paths.get_full_path("checkpoints", modelname) + if not ckpt_path: + ckpt_path = folder_paths.get_full_path("diffusion_models", modelname) + if ckpt_path: + modelhash = get_sha256(self,ckpt_path)[:10] + else: + modelhash = "" + if modelhash != "": + forge = forge + "Model hash: "+modelhash+", " + modelname = os.path.basename(modelname) + modelname = os.path.splitext(modelname)[0] + if modelname != "": + forge = forge + "Model: "+modelname+", " + + if beta_schedule_alpha != 0.0: + forge = forge + "Beta schedule alpha: "+str(beta_schedule_alpha)+", " + if beta_schedule_beta != 0.0: + forge = forge + "Beta schedule beta: "+str(beta_schedule_beta)+", " + + if civitai_lora_hash != "": + forge = forge + "Lora hashes: \"" + civitai_lora_hash + "\", " + + forge = forge + "Version: ComfyUI" + else: + forge = override_parameters + params = self.parse_parameters(forge) + seed = params.get("Seed") + if seed is not None: + seed_text = seed + +##### metadata prompt (Сomfy-ui style) ##### + + prompt_json = "" + if save_comfy_prompt: + if override_prompt == None: + if prompt is not None: + try: + prompt_json = json.dumps(prompt) + except Exception as e: + print(f"\033[91mJSON coding error (prompt)\033[0m") + else: + prompt_json = override_prompt + +##### metadata workflow (Сomfy-ui style) ##### + + workflow_json = "" + if save_comfy_workflow: + if override_workflow == None: + if extra_pnginfo is not None: + if "workflow" in extra_pnginfo: + try: + workflow_json = json.dumps(extra_pnginfo["workflow"]) + except Exception as e: + print(f"\033[91mJSON coding error (workflow)\033[0m") + else: + workflow_json = override_workflow + +##### images ##### + for image in images: i = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) @@ -551,79 +657,25 @@ class SimpleImageSaver: free_number = self.find_free_number(output_path) if free_number > 99999: print(f"\033[91mToo many files\033[0m") - return () - - seed_text = str(SEED) - -#################### -##### metadata ##### - - prompt_and_lora = prompt - if civitai_lora != "": - prompt_and_lora = prompt_and_lora + ' ' + civitai_lora - - metadata_text = prompt_and_lora + '\n' - - if negative != "": - metadata_text = metadata_text + "Negative prompt: "+ negative + '\n' - - if steps != 0: - metadata_text = metadata_text + "Steps: "+str(steps)+", " - if sampler != None: - metadata_text = metadata_text + "Sampler: "+str(sampler)+", " - if schedule != None: - metadata_text = metadata_text + "Schedule type: "+str(schedule)+", " - if CFG_scale != 0.0: - metadata_text = metadata_text + "CFG scale: "+str(CFG_scale)+", " - if distilled_CFG_scale != 0.0: - metadata_text = metadata_text + "Distilled CFG Scale: "+str(distilled_CFG_scale)+", " - metadata_text = metadata_text + "Seed: "+seed_text+", " - if width != 0: - if height != 0: - metadata_text = metadata_text + "Size: " + str(width) + "x" + str(height) + ", " - - ckpt_path = folder_paths.get_full_path("checkpoints", modelname) - if not ckpt_path: - ckpt_path = folder_paths.get_full_path("diffusion_models", modelname) - if ckpt_path: - modelhash = get_sha256(self,ckpt_path)[:10] - else: - modelhash = "" - if modelhash != "": - metadata_text = metadata_text + "Model hash: "+modelhash+", " - modelname = os.path.basename(modelname) - modelname = os.path.splitext(modelname)[0] - if modelname != "": - metadata_text = metadata_text + "Model: "+modelname+", " - - if beta_schedule_alpha != 0.0: - metadata_text = metadata_text + "Beta schedule alpha: "+str(beta_schedule_alpha)+", " - if beta_schedule_beta != 0.0: - metadata_text = metadata_text + "Beta schedule beta: "+str(beta_schedule_beta)+", " - - if civitai_lora_hash != "": - metadata_text = metadata_text + "Lora hashes: \"" + civitai_lora_hash + "\", " - - metadata_text = metadata_text + "Version: ComfyUI" + return ("",) metadata = PngInfo() - metadata.add_text("parameters", metadata_text) + metadata.add_text("parameters", forge) + if prompt_json != "": + metadata.add_text("prompt", prompt_json) + if workflow_json != "": + metadata.add_text("workflow", workflow_json) - if save_workflow: - if prompt_and_lora != "": - metadata.add_text("prompt", prompt_and_lora) - if extra_pnginfo is not None: - for x in extra_pnginfo: - print(f"Write {x}") - metadata.add_text(x, json.dumps(extra_pnginfo[x])) - -##### metadata ##### -#################### - - filename = f"{free_number:05d}-{seed_text}.png" + if seed_text != "": + filename = f"{free_number:05d}-{seed_text}.png" + else: + filename = f"{free_number:05d}.png" + img.save(os.path.join(output_path, filename), pnginfo=metadata, optimize=False) - return (metadata_text,) + print(f"\033[92mImage save\033[0m") + + return (forge,) ################################ @@ -637,8 +689,8 @@ class SimpleLoadImageWithMetadataString: {"image": (sorted(files), )}, } CATEGORY = "📚 SimpleButcher" - RETURN_TYPES = ("IMAGE", "MASK", "STRING", "STRING") - RETURN_NAMES = ("image", "mask", "metadata_forge", "prompt") + RETURN_TYPES = ("IMAGE", "MASK", "STRING", "STRING", "STRING") + RETURN_NAMES = ("image", "mask", "metadata_parameters (forge)", "metadata_workflow", "metadata_prompt") FUNCTION = "read_image" DESCRIPTION = "Load Image with metadata in simple string!" @@ -658,19 +710,25 @@ class SimpleLoadImageWithMetadataString: format = i.format metadata = i.info - #exif_workflow = "" - #if "workflow" in metadata: - # exif_workflow = metadata["workflow"] +##### metadata parameters (Forge style) ##### - exif_prompt = "" - if "prompt" in metadata: - exif_prompt = metadata["prompt"] - - exif_metadata = "" + forge = "" if "parameters" in metadata: - exif_metadata = metadata["parameters"] + forge = metadata["parameters"] - return (image, mask, exif_metadata, exif_prompt) +##### metadata prompt (Сomfy-ui style) ##### + + prompt_json = "" + if "prompt" in metadata: + prompt_json = metadata["prompt"] + +##### metadata workflow (Сomfy-ui style) ##### + + workflow_json = "" + if "workflow" in metadata: + workflow_json = metadata["workflow"] + + return (image, mask, forge, workflow_json, prompt_json) @classmethod def IS_CHANGED(s, image):