Merge pull request #9 from nkchocoai/8-can-not-use-date-format

8 can not use date format
This commit is contained in:
nkchocoai
2024-03-16 13:23:32 +09:00
committed by GitHub
5 changed files with 770 additions and 13 deletions
+28 -1
View File
@@ -16,6 +16,33 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git
- メタデータは `sampler_selection_method` で見つけたKSamplerノードの入力と以前に実行されたノードの入力から取得します。
- 対象となるKSamplerノードは[py/defs/samplers.py](py/defs/samplers.py)と[py/defs/ext/](py/defs/ext/)配下のファイルの`SAMPLERS`のキーです。
#### filename_prefix
- `filename_prefix` で指定した文字列(Key)は取得した情報に置換されます。
| Key | 置換先の情報 |
| --------------------- | -------------------------- |
| %seed% | シード値 |
| %width% | 画像の幅 |
| %height% | 画像の高さ |
| %pprompt% | Positive Prompt |
| %pprompt:<文字数n>% | Positive Promptの先頭n文字 |
| %nprompt% | Negative Prompt |
| %nprompt:<文字数n>% | Negative Promptの先頭n文字 |
| %model% | Checkpoint名 |
| %model:<文字数n>% | Checkpoint名の先頭n文字 |
| %date:<フォーマット>% | 生成日時 |
- `%date:<フォーマット>%` の `<フォーマット>` で指定する識別子は以下の表を参照ください。
| 識別子 | 説明 |
| ------ | ---- |
| yyyy | 年 |
| MM | 月 |
| dd | 日 |
| hh | 時 |
| mm | 分 |
| ss | 秒 |
#### sampler_selection_method
- このノードよりも前に実行されたKSamplerノードを選ぶ方法を指定します。
@@ -65,4 +92,4 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git
- [py/defs/captures.py](py/defs/captures.py)
- [py/defs/samplers.py](py/defs/samplers.py)
- 対応している拡張機能は以下のディレクトリをご確認ください。
- [py/defs/ext/](py/defs/ext/)
- [py/defs/ext/](py/defs/ext/)
+28 -1
View File
@@ -18,6 +18,33 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git
- Metadata is extracted from the input of the KSampler node found by `sampler_selection_method` and the input of the previously executed node.
- Target KSampler nodes are the key of `SAMPLERS` in the file [py/defs/samplers.py](py/defs/samplers.py) and the file in [py/defs/ext/](py/defs/ext/).
#### filename_prefix
- The string (Key) specified in `filename_prefix` will be replaced with the retrieved information.
| Key | Information to be replaced |
| --------------- | ------------------------------------- |
| %seed% | seed value |
| %width% | Image width |
| %height% | Image height |
| %pprompt% | Positive Prompt |
| %pprompt:[n]% | first n characters of Positive Prompt |
| %nprompt% | Negative Prompt |
| %nprompt:[n]% | First n characters of Negative Prompt |
| %model% | Checkpoint name |
| %model:[n]% | First n characters of Checkpoint name |
| %date:[format]% | Date of generation |
- See the following table for the identifier specified by `[format]` in `%date:[format]%`.
| Identifier | Description |
| ---------- | ----------- |
| yyyy | year |
| MM | month |
| dd | day |
| hh | hour |
| mm | minute |
| ss | second |
#### sampler_selection_method
- Specifies how to select a KSampler node that has been executed before this node.
@@ -67,4 +94,4 @@ git clone https://github.com/nkchocoai/ComfyUI-SaveImageWithMetaData.git
- [py/defs/captures.py](py/defs/captures.py)
- [py/defs/samplers.py](py/defs/samplers.py)
- Please check the following directories for supported extensions.
- [py/defs/ext/](py/defs/ext/)
- [py/defs/ext/](py/defs/ext/)
+643
View File
@@ -0,0 +1,643 @@
{
"last_node_id": 20,
"last_link_id": 29,
"nodes": [
{
"id": 7,
"type": "PromptUtilitiesConstStringMultiLine",
"pos": [
21,
378
],
"size": {
"0": 287.9851379394531,
"1": 99.69133758544922
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
7
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "PromptUtilitiesConstStringMultiLine"
},
"widgets_values": [
"(low quality, worst quality:1.4), embedding:EasyNegative"
]
},
{
"id": 9,
"type": "CLIPTextEncode",
"pos": [
394,
382
],
"size": {
"0": 210,
"1": 54
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null,
"slot_index": 0
},
{
"name": "text",
"type": "STRING",
"link": 7,
"widget": {
"name": "text"
},
"slot_index": 1
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
10
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
390,
257
],
"size": {
"0": 210,
"1": 54
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null
},
{
"name": "text",
"type": "STRING",
"link": 8,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
9
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
""
]
},
{
"id": 10,
"type": "Prompts Everywhere",
"pos": [
674,
302
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "CONDITIONING",
"type": "*",
"link": 9,
"color_on": "#FFA931"
},
{
"name": "CONDITIONING",
"type": "*",
"link": 10,
"slot_index": 1,
"color_on": "#FFA931"
}
],
"properties": {
"Node name for S&R": "Prompts Everywhere",
"group_restricted": false,
"color_restricted": false
},
"widgets_values": []
},
{
"id": 6,
"type": "PromptUtilitiesConstStringMultiLine",
"pos": [
18,
225
],
"size": {
"0": 287.9851379394531,
"1": 99.69133758544922
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
8
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "PromptUtilitiesConstStringMultiLine"
},
"widgets_values": [
"masterpiece, best quality, hatsune miku, upper body"
]
},
{
"id": 1,
"type": "CheckpointLoaderSimple",
"pos": [
24,
54
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
24
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
25
],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
26
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"CounterfeitV30_v30.safetensors"
]
},
{
"id": 5,
"type": "Anything Everywhere3",
"pos": [
395,
54
],
"size": {
"0": 210,
"1": 66
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "MODEL",
"type": "*",
"link": 24,
"slot_index": 0,
"color_on": "#B39DDB"
},
{
"name": "CLIP",
"type": "*",
"link": 25,
"color_on": "#FFD500"
},
{
"name": "VAE",
"type": "*",
"link": 26,
"color_on": "#FF6E6E",
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "Anything Everywhere3",
"group_restricted": false,
"color_restricted": false
},
"widgets_values": []
},
{
"id": 2,
"type": "VAELoader",
"pos": [
402,
858
],
"size": {
"0": 323.17578125,
"1": 58
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
13
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"clearvae_nanslesstest.safetensors"
]
},
{
"id": 13,
"type": "VAEDecode",
"pos": [
790,
531
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 28,
"slot_index": 0
},
{
"name": "vae",
"type": "VAE",
"link": 13
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
15
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 12,
"type": "EmptyLatentImageFromPresetsSD15",
"pos": [
23,
550
],
"size": {
"0": 327.6000061035156,
"1": 122
},
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "latent",
"type": "LATENT",
"links": [
11
],
"shape": 3,
"slot_index": 0
},
{
"name": "w",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "h",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmptyLatentImageFromPresetsSD15"
},
"widgets_values": [
"512 x 768",
4
]
},
{
"id": 11,
"type": "KSampler",
"pos": [
402,
532
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": 11
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
28
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
123,
"fixed",
20,
8,
"dpmpp_2m",
"karras",
1
]
},
{
"id": 20,
"type": "PromptUtilitiesConstStringMultiLine",
"pos": [
800,
645
],
"size": [
255.52172323794503,
110.68967742638506
],
"flags": {},
"order": 5,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
29
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "PromptUtilitiesConstStringMultiLine"
},
"widgets_values": [
"siwm-%model:10%/%pprompt:20%-%nprompt:20%/%seed%-%width%x%height%_%date:yyyyMMdd-hhmmss%"
]
},
{
"id": 15,
"type": "SaveImageWithMetaData",
"pos": [
1132,
495
],
"size": [
277.5890906467905,
411.8033565804974
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 15,
"slot_index": 0
},
{
"name": "filename_prefix",
"type": "STRING",
"link": 29,
"widget": {
"name": "filename_prefix"
}
}
],
"properties": {
"Node name for S&R": "SaveImageWithMetaData"
},
"widgets_values": [
"siwm-%model:10%/%pprompt:20%-%nprompt:20%/%seed%-%width%x%height%_%date:yyyyMMdd-hhmmss%",
"Farthest",
0
]
}
],
"links": [
[
7,
7,
0,
9,
1,
"STRING"
],
[
8,
6,
0,
8,
1,
"STRING"
],
[
9,
8,
0,
10,
0,
"*"
],
[
10,
9,
0,
10,
1,
"*"
],
[
11,
12,
0,
11,
3,
"LATENT"
],
[
13,
2,
0,
13,
1,
"VAE"
],
[
15,
13,
0,
15,
0,
"IMAGE"
],
[
24,
1,
0,
5,
0,
"*"
],
[
25,
1,
1,
5,
1,
"*"
],
[
26,
1,
2,
5,
2,
"*"
],
[
28,
11,
0,
13,
0,
"LATENT"
],
[
29,
20,
0,
15,
1,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

+71 -11
View File
@@ -1,5 +1,8 @@
import json
import os
import re
from datetime import datetime
from PIL import Image
from PIL.PngImagePlugin import PngInfo
@@ -45,6 +48,8 @@ class SaveImageWithMetaData(BaseNode):
OUTPUT_NODE = True
pattern_format = re.compile(r"(%[^%]+%)")
def save_images(
self,
images,
@@ -54,23 +59,15 @@ class SaveImageWithMetaData(BaseNode):
prompt=None,
extra_pnginfo=None,
):
(
full_output_folder,
filename,
counter,
subfolder,
filename_prefix,
) = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
pnginfo_dict_src = self.gen_pnginfo(
sampler_selection_method, sampler_selection_node_id
)
results = list()
for index, image in enumerate(images):
i = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
pnginfo_dict = self.gen_pnginfo(
sampler_selection_method, sampler_selection_node_id
)
pnginfo_dict = pnginfo_dict_src.copy()
if len(images) >= 2:
pnginfo_dict["Batch index"] = index
pnginfo_dict["Batch size"] = len(images)
@@ -88,7 +85,21 @@ class SaveImageWithMetaData(BaseNode):
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
filename_prefix = self.format_filename(filename_prefix, pnginfo_dict)
output_path = os.path.join(self.output_dir, filename_prefix)
if not os.path.exists(os.path.dirname(output_path)):
os.makedirs(os.path.dirname(output_path), exist_ok=True)
(
full_output_folder,
filename,
counter,
subfolder,
filename_prefix,
) = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
file = f"{filename}_{counter:05}_.png"
img.save(
os.path.join(full_output_folder, file),
pnginfo=metadata,
@@ -130,3 +141,52 @@ class SaveImageWithMetaData(BaseNode):
inputs_before_sampler_node, inputs_before_this_node
)
return pnginfo_dict
@classmethod
def format_filename(cls, filename, pnginfo_dict):
result = re.findall(cls.pattern_format, filename)
for segment in result:
parts = segment.replace("%", "").split(":")
key = parts[0]
if key == "seed":
filename = filename.replace(segment, str(pnginfo_dict.get("Seed", "")))
elif key == "width":
w = pnginfo_dict.get("Size", "x").split("x")[0]
filename = filename.replace(segment, str(w))
elif key == "height":
w = pnginfo_dict.get("Size", "x").split("x")[1]
filename = filename.replace(segment, str(w))
elif key == "pprompt":
prompt = pnginfo_dict.get("Positive prompt", "").replace("\n", " ")
if len(parts) >= 2:
length = int(parts[1])
prompt = prompt[:length]
filename = filename.replace(segment, prompt.strip())
elif key == "nprompt":
prompt = pnginfo_dict.get("Negative prompt", "").replace("\n", " ")
if len(parts) >= 2:
length = int(parts[1])
prompt = prompt[:length]
filename = filename.replace(segment, prompt.strip())
elif key == "model":
model = pnginfo_dict.get("Model", "")
model = os.path.splitext(os.path.basename(model))[0]
if len(parts) >= 2:
length = int(parts[1])
model = model[:length]
filename = filename.replace(segment, model)
elif key == "date" and len(parts) >= 2:
date_format = parts[1]
now = datetime.now()
date_table = {
"yyyy": now.year,
"MM": now.hour,
"dd": now.day,
"hh": now.hour,
"mm": now.minute,
"ss": now.second,
}
for k, v in date_table.items():
date_format = date_format.replace(k, str(v).zfill(len(k)))
filename = filename.replace(segment, date_format)
return filename