diff --git a/README.jp.md b/README.jp.md index 63e6fcf..b0bb00f 100644 --- a/README.jp.md +++ b/README.jp.md @@ -58,6 +58,10 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git ##### By node ID - ノードIDが `sampler_selection_node_id` であるKSamplerノードを選びます。 +### Create Extra MetaData +- 保存する画像に追加するメタデータを指定します。 +- 例: [extra_metadata.png](examples/extra_metadata.png)。 + ## 付与されるメタデータ - Positive prompt - Negative prompt diff --git a/README.md b/README.md index 5d97a4f..e9a20dc 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,10 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git ##### By node ID - Select the KSampler node whose node ID is `sampler_selection_node_id`. +### Create Extra MetaData +- Specifies metadata to be added to the image to be saved. +- Example: In [extra_metadata.png](examples/extra_metadata.png). + ## Metadata to be given - Positive prompt - Negative prompt diff --git a/__init__.py b/__init__.py index e81f8bb..dd3ce91 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,13 @@ -from .py.nodes.node import SaveImageWithMetaData +from .py.nodes.node import SaveImageWithMetaData, CreateExtraMetaData NODE_CLASS_MAPPINGS = { "SaveImageWithMetaData": SaveImageWithMetaData, + "CreateExtraMetaData": CreateExtraMetaData, } NODE_DISPLAY_NAME_MAPPINGS = { "SaveImageWithMetaData": "Save Image With Metadata", + "CreateExtraMetaData": "Create Extra MetaData", } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/examples/extra_metadata.json b/examples/extra_metadata.json new file mode 100644 index 0000000..30960c2 --- /dev/null +++ b/examples/extra_metadata.json @@ -0,0 +1,667 @@ +{ + "last_node_id": 21, + "last_link_id": 29, + "nodes": [ + { + "id": 10, + "type": "VAEDecode", + "pos": [ + 812, + 429 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 9, + "slot_index": 0 + }, + { + "name": "vae", + "type": "VAE", + "link": 18 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 19 + ], + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 16, + "type": "CreateExtraMetaData", + "pos": [ + 763, + 962 + ], + "size": [ + 245.1999969482422, + 226 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "extra_metadata", + "type": "EXTRA_METADATA", + "link": 20 + }, + { + "name": "value1", + "type": "STRING", + "link": 27, + "widget": { + "name": "value1" + } + }, + { + "name": "value2", + "type": "STRING", + "link": 26, + "widget": { + "name": "value2" + } + } + ], + "outputs": [ + { + "name": "EXTRA_METADATA", + "type": "EXTRA_METADATA", + "links": [ + 25 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CreateExtraMetaData" + }, + "widgets_values": [ + "custom_w", + "", + "custom_h", + "", + "", + "", + "", + "" + ] + }, + { + "id": 17, + "type": "CreateExtraMetaData", + "pos": [ + 440, + 960 + ], + "size": { + "0": 245.1999969482422, + "1": 226 + }, + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "extra_metadata", + "type": "EXTRA_METADATA", + "link": null + } + ], + "outputs": [ + { + "name": "EXTRA_METADATA", + "type": "EXTRA_METADATA", + "links": [ + 20 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CreateExtraMetaData" + }, + "widgets_values": [ + "custom_key", + "custom_value", + "hello", + "world", + "", + "", + "", + "" + ] + }, + { + "id": 1, + "type": "CheckpointLoaderSimple", + "pos": [ + -340, + 370 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 13, + 14 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 18 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "animagine-xl-3.1.safetensors" + ] + }, + { + "id": 13, + "type": "CLIPTextEncode", + "pos": [ + 40, + 600 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 14 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 15 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]", + true + ] + }, + { + "id": 14, + "type": "CLIPTextEncode", + "pos": [ + 40, + 430 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 13 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "1girl, hatsune miku, vocaloid, aqua hair, aqua eyes, twintails, aqua necktie, cyan shirt, sleeveless, detached sleeves, black sleeves, upper body, smile, looking at viewer, waving, masterpiece, best quality, very aesthetic, absurdres", + true + ] + }, + { + "id": 9, + "type": "KSampler", + "pos": [ + 445, + 430 + ], + "size": { + "0": 315, + "1": 262 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 17 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 16 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 15 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 8, + "slot_index": 3 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 9 + ], + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "fixed", + 20, + 8, + "euler_ancestral", + "karras", + 1 + ] + }, + { + "id": 20, + "type": "CR Integer To String", + "pos": [ + 457, + 743 + ], + "size": [ + 210, + 54 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "int_", + "type": "INT", + "link": 29, + "widget": { + "name": "int_" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 27 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "show_help", + "type": "STRING", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "CR Integer To String" + }, + "widgets_values": [ + 0 + ] + }, + { + "id": 21, + "type": "CR Integer To String", + "pos": [ + 460, + 847 + ], + "size": [ + 210, + 54 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "int_", + "type": "INT", + "link": 28, + "widget": { + "name": "int_" + } + } + ], + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 26 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "show_help", + "type": "STRING", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "CR Integer To String" + }, + "widgets_values": [ + 0 + ] + }, + { + "id": 8, + "type": "EmptyLatentImageFromPresetsSDXL", + "pos": [ + 40, + 800 + ], + "size": { + "0": 315, + "1": 122 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "latent", + "type": "LATENT", + "links": [ + 8 + ], + "shape": 3 + }, + { + "name": "w", + "type": "INT", + "links": [ + 29 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "h", + "type": "INT", + "links": [ + 28 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImageFromPresetsSDXL" + }, + "widgets_values": [ + " 832 x 1216", + 1 + ] + }, + { + "id": 15, + "type": "SaveImageWithMetaData", + "pos": [ + 1106, + 430 + ], + "size": [ + 315, + 482 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 19 + }, + { + "name": "extra_metadata", + "type": "EXTRA_METADATA", + "link": 25 + } + ], + "properties": { + "Node name for S&R": "SaveImageWithMetaData" + }, + "widgets_values": [ + "extra_metadata", + "Farthest", + 0, + "png", + true, + 100, + false, + true, + false + ] + } + ], + "links": [ + [ + 8, + 8, + 0, + 9, + 3, + "LATENT" + ], + [ + 9, + 9, + 0, + 10, + 0, + "LATENT" + ], + [ + 13, + 1, + 1, + 14, + 0, + "CLIP" + ], + [ + 14, + 1, + 1, + 13, + 0, + "CLIP" + ], + [ + 15, + 13, + 0, + 9, + 2, + "CONDITIONING" + ], + [ + 16, + 14, + 0, + 9, + 1, + "CONDITIONING" + ], + [ + 17, + 1, + 0, + 9, + 0, + "MODEL" + ], + [ + 18, + 1, + 2, + 10, + 1, + "VAE" + ], + [ + 19, + 10, + 0, + 15, + 0, + "IMAGE" + ], + [ + 20, + 17, + 0, + 16, + 0, + "EXTRA_METADATA" + ], + [ + 25, + 16, + 0, + 15, + 1, + "EXTRA_METADATA" + ], + [ + 26, + 21, + 0, + 16, + 2, + "STRING" + ], + [ + 27, + 20, + 0, + 16, + 1, + "STRING" + ], + [ + 28, + 8, + 2, + 21, + 0, + "INT" + ], + [ + 29, + 8, + 1, + 20, + 0, + "INT" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.7513148009015779, + "offset": [ + 653.6784224287038, + -213.77225271772178 + ] + } + }, + "version": 0.4 +} diff --git a/examples/extra_metadata.png b/examples/extra_metadata.png new file mode 100644 index 0000000..71a92af Binary files /dev/null and b/examples/extra_metadata.png differ diff --git a/py/nodes/node.py b/py/nodes/node.py index 790b69d..a00489a 100644 --- a/py/nodes/node.py +++ b/py/nodes/node.py @@ -51,7 +51,8 @@ class SaveImageWithMetaData(BaseNode): "quality": ("INT", {"default": 100, "min": 1, "max": 100}), "save_workflow_json": ("BOOLEAN", {"default": False}), "add_counter_to_filename": ("BOOLEAN", {"default": True}), - "civitai_sampler": ("BOOLEAN", {"default": False}) + "civitai_sampler": ("BOOLEAN", {"default": False}), + "extra_metadata": ("EXTRA_METADATA", {}), }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, } @@ -74,13 +75,18 @@ class SaveImageWithMetaData(BaseNode): quality=100, save_workflow_json=False, add_counter_to_filename=True, - civitai_sampler = False, + civitai_sampler=False, + extra_metadata={}, prompt=None, extra_pnginfo=None, ): pnginfo_dict_src = self.gen_pnginfo( sampler_selection_method, sampler_selection_node_id, civitai_sampler ) + for k, v in extra_metadata.items(): + if k and v: + pnginfo_dict_src[k] = v.replace(",", "/") + results = list() for index, image in enumerate(images): i = 255.0 * image.cpu().numpy() @@ -162,7 +168,9 @@ class SaveImageWithMetaData(BaseNode): return {"ui": {"images": results}} @classmethod - def gen_pnginfo(cls, sampler_selection_method, sampler_selection_node_id, save_civitai_sampler): + def gen_pnginfo( + cls, sampler_selection_method, sampler_selection_node_id, save_civitai_sampler + ): # get all node inputs inputs = Capture.get_inputs() @@ -246,3 +254,48 @@ class SaveImageWithMetaData(BaseNode): filename = filename.replace(segment, date_format) return filename + + +class CreateExtraMetaData(BaseNode): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "key1": ("STRING", {"default": "", "multiline": False}), + "value1": ("STRING", {"default": "", "multiline": False}), + }, + "optional": { + "key2": ("STRING", {"default": "", "multiline": False}), + "value2": ("STRING", {"default": "", "multiline": False}), + "key3": ("STRING", {"default": "", "multiline": False}), + "value3": ("STRING", {"default": "", "multiline": False}), + "key4": ("STRING", {"default": "", "multiline": False}), + "value4": ("STRING", {"default": "", "multiline": False}), + "extra_metadata": ("EXTRA_METADATA",), + }, + } + + RETURN_TYPES = ("EXTRA_METADATA",) + FUNCTION = "create_extra_metadata" + + def create_extra_metadata( + self, + extra_metadata={}, + key1="", + value1="", + key2="", + value2="", + key3="", + value3="", + key4="", + value4="", + ): + extra_metadata.update( + { + key1: value1, + key2: value2, + key3: value3, + key4: value4, + } + ) + return (extra_metadata,)