Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25727455b | ||
|
|
47551e38dd | ||
|
|
cb72f372aa | ||
|
|
b34c0d2c2e | ||
|
|
7f1e6dbfd7 | ||
|
|
b08ec0c5e9 | ||
|
|
11058de872 | ||
|
|
992536ca33 | ||
|
|
bab9dba0d9 | ||
|
|
5886a538da | ||
|
|
f61e51864c | ||
|
|
7b3b6279c1 | ||
|
|
50b0d9ff96 | ||
|
|
c67cc3df84 | ||
|
|
bd8f669d71 | ||
|
|
4c8589be19 | ||
|
|
88a2c8a39f | ||
|
|
368be01cdc | ||
|
|
dfc8f7ddd5 |
@@ -9,9 +9,7 @@
|
||||
<a href="https://github.com/psf/black">
|
||||
<img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
|
||||
<br>
|
||||
<blockquote>
|
||||
This project is currently in beta status. If you encounter any issues or have any suggestions, please let me know.
|
||||
</blockquote>
|
||||
|
||||
This is a subproject of the
|
||||
<a href="https://github.com/receyuki/stable-diffusion-prompt-reader">SD Prompt Reader.</a>
|
||||
It helps you extract metadata from images in any format supported by the
|
||||
@@ -24,7 +22,7 @@ additional metadata to ensure compatibility with metadata detection on websites
|
||||
<a href="#usage">Usage</a> •
|
||||
<a href="#credits">Credits</a>
|
||||
</p>
|
||||
<img src="./images/screenshot_v100b2.png">
|
||||
<img src="./images/screenshot_v100.png">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -44,6 +42,7 @@ additional metadata to ensure compatibility with metadata detection on websites
|
||||
See [SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader#supported-formats) for details
|
||||
|
||||
## Installation
|
||||
>While ZIP package is available, it is strongly recommended not to use it for installation.
|
||||
### Install and update via [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) (Recommended)
|
||||
Search for `SD Prompt Reader` in the ComfyUI Manager and install it.
|
||||
### Install manually
|
||||
@@ -65,56 +64,87 @@ git pull --recurse-submodules
|
||||
```
|
||||
|
||||
## Usage
|
||||
>The following section may be outdated and could differ from the actual nodes.
|
||||
### Prompt Reader Node
|
||||
- The Prompt Reader Node works exactly the same as the
|
||||
- The `Prompt Reader` Node works exactly the same as the
|
||||
[standalone SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader).
|
||||
It uses the Image Data Reader from the
|
||||
[standalone SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader),
|
||||
allowing it to support the same formats and receive updates along with the
|
||||
[SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader).
|
||||
|
||||
***For images containing multiple sets of parameters, such as those processed through `hires-fix` or `refiner`,
|
||||
you will need to modify the `data_index` to select the parameters you need***
|
||||
|
||||
***For images generated by SDXL and containing multiple sets of prompts,
|
||||
the text_g will be combined with text_l into a single prompt***
|
||||
[SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader).
|
||||
- Due to custom nodes and complex workflows potentially causing issues with SD Prompt Reader's ability
|
||||
to read image metadata correctly, it is recommended to embed the `Prompt Saver` Node within the workflow
|
||||
to ensure maximum compatibility.
|
||||
- For images containing multiple sets of parameters, such as those processed through `hires-fix` or `refiner`,
|
||||
you will need to modify the `parameter_index` to select the parameters you need
|
||||
- For images generated by SDXL and containing multiple sets of prompts,
|
||||
the `text_g` will be combined with `text_l` into a single prompt
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/reader.png" width="25%" height="25%" alt="reader node">
|
||||
</div>
|
||||
|
||||
### Prompt Saver Node & Parameter Generator Node
|
||||
- The Prompt Saver Node and The Parameter Generator Node are designed to be used together.
|
||||
- The Prompt Saver Node will write additional metadata in the A1111 format to the output images
|
||||
### Prompt Saver Node
|
||||
- The `Prompt Saver` Node and The `Parameter Generator` Node are designed to be used together.
|
||||
- The `Prompt Saver` Node will write additional metadata in the A1111 format to the output images
|
||||
to be compatible with any tools that support the A1111 format,
|
||||
including SD Prompt Reader and Civitai.
|
||||
Due to custom nodes and complex workflows potentially causing issues with SD Prompt Reader's ability
|
||||
to read image metadata correctly, it is recommended to embed this node within the workflow
|
||||
to ensure maximum compatibility.
|
||||
- Since it's not possible to directly extract metadata from KSampler, it is necessary to
|
||||
use the Parameter Generator Node to generate parameters and simultaneously output them to both
|
||||
the Prompt Saver Node and KSampler.
|
||||
- Since it's not possible to directly extract metadata from `KSampler`, it is necessary to
|
||||
use the `Parameter Generator` Node to generate parameters and simultaneously output them to both
|
||||
the `Prompt Saver` Node and `KSampler`.
|
||||
- Please refer to the following table for placeholders supported by the `filename` and `path`.
|
||||
|
||||
| Placeholder |
|
||||
|-------------|
|
||||
| %date |
|
||||
| %time |
|
||||
| %counter |
|
||||
| %seed |
|
||||
| %steps |
|
||||
| %cfg |
|
||||
| %extension |
|
||||
| %model |
|
||||
| %sampler |
|
||||
| %scheduler |
|
||||
| %quality |
|
||||
|
||||
- For the `date_format` and `time_format`, please refer to
|
||||
[strftime.org](https://strftime.org/) or [www.strfti.me](https://www.strfti.me/).
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/generator_saver.png" width="50%" height="50%" alt="generator and saver node">
|
||||
<img src="./images/saver.png" width="25%" height="25%" alt="generator and saver node">
|
||||
</div>
|
||||
|
||||
## Parameter Generator Node
|
||||
- Since it's not possible to directly extract metadata from `KSampler`, it is necessary to
|
||||
use the `Parameter Generator` Node to generate parameters and simultaneously output them to both
|
||||
the `Prompt Saver` Node and `KSampler`.
|
||||
- The `model_version` and `aspect_ratio` are used only for calculating the optimal resolution of the selected model version
|
||||
under the chosen aspect ratio. The calculation method is based on the
|
||||
[Stability AI development documentation](https://platform.stability.ai/docs/features/api-parameters#about-dimensions)
|
||||
and the [StableSwarmUI source code](https://github.com/Stability-AI/StableSwarmUI) (developed by Stability AI).
|
||||
- `refiner_start` refers to the proportion of steps completed when the refiner starts running,
|
||||
i.e., the proportion of base steps to total steps. This is used to calculate the `start_at_step` (`REFINER_START_STEP`)
|
||||
required by the refiner `KSampler` under the selected step ratio.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/generator.png" width="25%" height="25%" alt="generator and saver node">
|
||||
</div>
|
||||
|
||||
### Prompt Merger Node & Type Converter Node
|
||||
- Since the A1111 format cannot store `text_g` and `text_l` separately, SDXL users need to use
|
||||
the Prompt Merger Node to combine `text_g` and `text_l` into a single prompt.
|
||||
- In some cases, inputs like `model_name`, `sampler_name`, and `scheduler` may conflict with other custom nodes.
|
||||
You can use the SD Type Converter to convert them into `STRING` type and then
|
||||
input them into `model_name_str`, `sampler_name_str`, and `scheduler_str`. Please note that inputs of type `STRING`
|
||||
have higher priority, so do not connect `STRING` inputs unless necessary, as the Prompt Saver will prioritize reading
|
||||
`STRING` inputs over regular ones.
|
||||
the `Prompt Merger` Node to combine `text_g` and `text_l` into a single prompt.
|
||||
- Since `model_name`, `sampler_name`, and `scheduler` are special types
|
||||
that cannot be directly used by some other nodes,
|
||||
You can use the `Type Converter` Node to convert them into `STRING` type.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/merger_converter.png" width="25%" height="25%" alt="merger and converter node">
|
||||
</div>
|
||||
|
||||
### [Example Workflow](./workflows/example_workflow.json)
|
||||
>The example workflow is outdated and will be updated in the stable release.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/example_workflow.png" width="100%" height="100%" alt="example workflow">
|
||||
@@ -123,3 +153,5 @@ have higher priority, so do not connect `STRING` inputs unless necessary, as the
|
||||
## Credits
|
||||
- The SD Prompt Reader node is based on [ComfyUI Load Image With Metadata](https://github.com/tkoenig89/ComfyUI_Load_Image_With_Metadata)
|
||||
- The SD Prompt Saver node is based on [Comfy Image Saver](https://github.com/giriss/comfy-image-saver) & [Stable Diffusion Webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
|
||||
- The seed generator in the SD Parameter Generator is modified from [rgthree's Comfy Nodes](https://github.com/rgthree/rgthree-comfy#rgthrees-comfy-nodes)
|
||||
- A special thanks to [@alessandroperilli](https://github.com/alessandroperilli) and his [AP Workflow](https://perilli.com/ai/comfyui/) for providing numerous suggestions
|
||||
@@ -10,8 +10,10 @@ comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
tk_nodes_path = os.path.join(os.path.dirname(__file__))
|
||||
|
||||
js_dest_path = os.path.join(comfy_path, "web", "extensions", "SDPromptReader")
|
||||
if not os.path.exists(js_dest_path):
|
||||
os.makedirs(js_dest_path)
|
||||
os.makedirs(js_dest_path, exist_ok=True)
|
||||
|
||||
js_src_path = os.path.join(tk_nodes_path, "js", "promptDisplay.js")
|
||||
shutil.copy(js_src_path, js_dest_path)
|
||||
files_to_copy = ["promptDisplay.js", "parameterDisplay.js", "seedGen.js"]
|
||||
|
||||
for file in files_to_copy:
|
||||
js_src_path = os.path.join(tk_nodes_path, "js", file)
|
||||
shutil.copy(js_src_path, js_dest_path)
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION = "1.0.0b3"
|
||||
VERSION = "1.0.0"
|
||||
|
||||
|
Before Width: | Height: | Size: 906 KiB After Width: | Height: | Size: 761 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 769 KiB |
@@ -41,13 +41,21 @@ app.registerExtension({
|
||||
} else {
|
||||
ar_message = `Optimal resolution for ${message.text[1]} model
|
||||
with aspect ratio ${message.text[0]}: ${message.text[2]} x ${message.text[3]}`;
|
||||
this.widgets.find(obj => obj.name === "width").value = message.text[2];
|
||||
this.widgets.find(obj => obj.name === "height").value = message.text[3];
|
||||
}
|
||||
|
||||
const start_at_float = parseFloat(message.text[5])
|
||||
const base_percentage = Math.round(start_at_float * 100) + "%";
|
||||
const refiner_percentage = Math.round((1 - start_at_float) * 100) + "%";
|
||||
const step_message = `Total steps: ${message.text[4]}, refiner start at ${base_percentage},
|
||||
Base steps: ${message.text[6]} (${base_percentage}), Refiner steps: ${message.text[7]} (${refiner_percentage})`;
|
||||
// const refiner_percentage = Math.round((1 - start_at_float) * 100) + "%";
|
||||
let step_message;
|
||||
if (start_at_float === 1) {
|
||||
step_message = `Total steps: ${message.text[4]},
|
||||
Refiner off`;
|
||||
} else {
|
||||
step_message = `Total steps: ${message.text[4]},
|
||||
Refiner start at step: ${message.text[6]} (${base_percentage})`;
|
||||
}
|
||||
|
||||
this.widgets.find(obj => obj.name === "steps_display").value = step_message;
|
||||
this.widgets.find(obj => obj.name === "aspect_ratio_display").value = ar_message;
|
||||
|
||||
@@ -65,7 +65,7 @@ class SeedControl {
|
||||
if (typeof this.lastSeed === "number" && !SPECIAL_SEEDS.includes(this.lastSeed)) {
|
||||
if (inputSeed === SPECIAL_SEED_INCREMENT) {
|
||||
this.serializedCtx.seedUsed = this.lastSeed + 1;
|
||||
} else if (inputSeed === SPECIAL_SEED_INCREMENT) {
|
||||
} else if (inputSeed === SPECIAL_SEED_DECREMENT) {
|
||||
this.serializedCtx.seedUsed = this.lastSeed - 1;
|
||||
}
|
||||
}
|
||||
@@ -142,11 +142,12 @@ app.registerExtension({
|
||||
if (nodeData.name === "SDParameterGenerator") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
const result = onNodeCreated?.apply(this, []);
|
||||
this.seedControl = new SeedControl(this);
|
||||
const nodeWidth = this.size[0];
|
||||
const nodeHeight = this.size[1];
|
||||
this.setSize([nodeWidth * 1.5, nodeHeight * 1.1]);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ class SDPromptReader:
|
||||
return {
|
||||
"required": {
|
||||
"image": (sorted(files), {"image_upload": True}),
|
||||
"data_index": (
|
||||
"parameter_index": (
|
||||
"INT",
|
||||
{"default": 0, "min": 0, "max": 255, "step": 1},
|
||||
),
|
||||
@@ -94,15 +94,15 @@ class SDPromptReader:
|
||||
"CFG",
|
||||
"WIDTH",
|
||||
"HEIGHT",
|
||||
"SETTING",
|
||||
"FILE_NAME",
|
||||
"SETTINGS",
|
||||
)
|
||||
|
||||
FUNCTION = "load_image"
|
||||
CATEGORY = "SD Prompt Reader"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def load_image(self, image, data_index):
|
||||
def load_image(self, image, parameter_index):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
i = Image.open(image_path)
|
||||
i = ImageOps.exif_transpose(i)
|
||||
@@ -128,13 +128,15 @@ class SDPromptReader:
|
||||
raise ValueError(MESSAGE["format_error"][1])
|
||||
|
||||
seed = int(
|
||||
self.param_parser(image_data.parameter.get("seed"), data_index) or 0
|
||||
self.param_parser(image_data.parameter.get("seed"), parameter_index)
|
||||
or 0
|
||||
)
|
||||
steps = int(
|
||||
self.param_parser(image_data.parameter.get("steps"), data_index) or 0
|
||||
self.param_parser(image_data.parameter.get("steps"), parameter_index)
|
||||
or 0
|
||||
)
|
||||
cfg = float(
|
||||
self.param_parser(image_data.parameter.get("cfg"), data_index) or 0
|
||||
self.param_parser(image_data.parameter.get("cfg"), parameter_index) or 0
|
||||
)
|
||||
width = int(image_data.width or 0)
|
||||
height = int(image_data.height or 0)
|
||||
@@ -156,8 +158,8 @@ class SDPromptReader:
|
||||
cfg,
|
||||
width,
|
||||
height,
|
||||
image_data.setting,
|
||||
file_path.stem,
|
||||
image_data.setting,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -167,14 +169,14 @@ class SDPromptReader:
|
||||
return data_list[0] if len(data_list) == 1 else data_list[index]
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, image, data_index):
|
||||
def IS_CHANGED(s, image, parameter_index):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
with open(Path(image_path), "rb") as f:
|
||||
image_data = ImageDataReader(f)
|
||||
return image_data.props
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(s, image, data_index):
|
||||
def VALIDATE_INPUTS(s, image, parameter_index):
|
||||
if not folder_paths.exists_annotated_filepath(image):
|
||||
return "Invalid image file: {}".format(image)
|
||||
return True
|
||||
@@ -199,7 +201,7 @@ class SDPromptSaver:
|
||||
),
|
||||
"path": ("STRING", {"default": "%date/", "multiline": False}),
|
||||
"model_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
"model_name_str": ("STRING", {"default": ""}),
|
||||
# "model_name_str": ("STRING", {"default": ""}),
|
||||
"seed": (
|
||||
"INT",
|
||||
{
|
||||
@@ -223,9 +225,9 @@ class SDPromptSaver:
|
||||
},
|
||||
),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"sampler_name_str": ("STRING", {"default": ""}),
|
||||
# "sampler_name_str": ("STRING", {"default": ""}),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"scheduler_str": ("STRING", {"default": ""}),
|
||||
# "scheduler_str": ("STRING", {"default": ""}),
|
||||
"width": (
|
||||
"INT",
|
||||
{"default": 1, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
@@ -295,32 +297,36 @@ class SDPromptSaver:
|
||||
subfolder_alt,
|
||||
filename_prefix,
|
||||
) = folder_paths.get_save_image_path(
|
||||
self.prefix_append, self.output_dir, images[0].shape[1], images[0].shape[0]
|
||||
self.prefix_append,
|
||||
self.output_dir,
|
||||
images[0].shape[1],
|
||||
images[0].shape[0],
|
||||
)
|
||||
|
||||
results = list()
|
||||
|
||||
model_name_real = model_name_str if model_name_str else model_name
|
||||
sampler_name_real = sampler_name_str if sampler_name_str else sampler_name
|
||||
scheduler_real = scheduler_str if scheduler_str else scheduler
|
||||
|
||||
extra_info_real = f", Extra info: {extra_info}" if extra_info else ""
|
||||
|
||||
variable_map = {
|
||||
"%date": self.get_time(date_format),
|
||||
"%time": self.get_time(time_format),
|
||||
"%counter": f"{counter:05}",
|
||||
"%seed": seed,
|
||||
"%steps": steps,
|
||||
"%cfg": cfg,
|
||||
"%extension": extension,
|
||||
"%model": model_name_real,
|
||||
"%sampler": sampler_name_real,
|
||||
"%scheduler": scheduler_real,
|
||||
"%quality": jpg_webp_quality,
|
||||
}
|
||||
|
||||
for image in images:
|
||||
# model_name_str, sampler_name_str, scheduler_str = None, None, None
|
||||
|
||||
model_name_real = model_name_str if model_name_str else model_name
|
||||
sampler_name_real = sampler_name_str if sampler_name_str else sampler_name
|
||||
scheduler_real = scheduler_str if scheduler_str else scheduler
|
||||
|
||||
extra_info_real = f", Extra info: {extra_info}" if extra_info else ""
|
||||
|
||||
variable_map = {
|
||||
"%date": self.get_time(date_format),
|
||||
"%time": self.get_time(time_format),
|
||||
"%counter": f"{counter:05}",
|
||||
"%seed": seed,
|
||||
"%steps": steps,
|
||||
"%cfg": cfg,
|
||||
"%extension": extension,
|
||||
"%model": model_name_real,
|
||||
"%sampler": sampler_name_real,
|
||||
"%scheduler": scheduler_real,
|
||||
"%quality": jpg_webp_quality,
|
||||
}
|
||||
|
||||
i = 255.0 * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
metadata = None
|
||||
@@ -454,7 +460,7 @@ class SDParameterGenerator:
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"refiner_start": (
|
||||
"FLOAT",
|
||||
{"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.1},
|
||||
{"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.01},
|
||||
),
|
||||
"cfg": (
|
||||
"FLOAT",
|
||||
@@ -507,7 +513,6 @@ class SDParameterGenerator:
|
||||
"INT",
|
||||
"INT",
|
||||
"INT",
|
||||
"INT",
|
||||
"FLOAT",
|
||||
comfy.samplers.KSampler.SAMPLERS,
|
||||
comfy.samplers.KSampler.SCHEDULERS,
|
||||
@@ -526,8 +531,7 @@ class SDParameterGenerator:
|
||||
"VAE",
|
||||
"SEED",
|
||||
"STEPS",
|
||||
"BASE_STEPS",
|
||||
"REFINER_STEPS",
|
||||
"REFINER_START_STEP",
|
||||
"CFG",
|
||||
"SAMPLER_NAME",
|
||||
"SCHEDULER",
|
||||
@@ -601,7 +605,7 @@ class SDParameterGenerator:
|
||||
f"Sampler: {sampler_name},\n"
|
||||
f"Scheduler: {scheduler},\n"
|
||||
f"Size: {str(width)}x{str(height)},\n"
|
||||
f"Batch size: {str(batch_size)},\n"
|
||||
f"Batch size: {str(batch_size)}\n"
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -624,7 +628,6 @@ class SDParameterGenerator:
|
||||
seed,
|
||||
steps,
|
||||
base_steps,
|
||||
refiner_steps,
|
||||
cfg,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
|
||||