Compare commits

...
23 Commits
Author SHA1 Message Date
receyuki fe864be43a Update README.md 2023-11-24 03:06:43 +08:00
receyuki 1da8db7d5c Bump version to 1.1.0 2023-11-24 03:04:40 +08:00
receyuki fa63e9a466 Update CHANGELOG.md 2023-11-24 03:04:18 +08:00
receyuki c025d474c5 Update example workflow 2023-11-24 03:04:09 +08:00
receyuki 570a59ea3d Update README.md 2023-11-24 03:03:46 +08:00
Rhys Yang 52dfd0456d Add MODEL_NAME output to the Prompt Reader node #23 2023-11-24 01:05:35 +08:00
Rhys Yang 49a9c4d041 Remove Show/Hide Last Seed Value from menu options 2023-11-24 01:00:05 +08:00
Rhys Yang c07548e3dc Add loaderDisplay.js to web extensions 2023-11-24 00:40:36 +08:00
Rhys Yang 8dffa89ae9 Code cleanup 2023-11-24 00:20:38 +08:00
Rhys Yang f56c1e0c48 Add Batch Loader node #13 2023-11-24 00:07:20 +08:00
Rhys Yang 4f42729494 Add FILENAME and METADATA output to the Prompt Saver node #16 2023-11-22 03:22:40 +08:00
Rhys Yang fc11266095 Add VAE selector to the Parameter Generator node #15 2023-11-22 02:15:33 +08:00
Rhys Yang d02265dbce Add pixel dimensions to the aspect_ratio #6 2023-11-22 00:53:39 +08:00
Rhys Yang 2950c76e9d Add Positive and Negative Aesthetic Score to the parameters #8 2023-11-22 00:51:02 +08:00
receyuki 07ecca0de4 Update example workflow 2023-11-14 23:40:13 +08:00
Rhys Yang 3ecceb0175 Update README.md 2023-11-01 04:53:36 +08:00
Rhys Yang 00aabca362 Bump version to 1.0.1 2023-11-01 04:42:21 +08:00
Rhys Yang aff17ccbf4 Update package_repo.yml 2023-11-01 04:32:42 +08:00
Rhys Yang 0a5bedf412 Fix %counter overwriting existing images #11 #14, and add a new file-naming mechanism to ensure naming uniqueness. 2023-11-01 04:22:07 +08:00
Rhys Yang b68ed7d7fa Fix the bug causing the Prompt Saver node fails to save images in jpg and webp formats #10 2023-11-01 02:38:15 +08:00
Rhys Yang 61c0487744 Merge pull request #12 from mcmonkey4eva/main
Use relative paths for js imports
2023-10-30 00:16:47 +08:00
Alex "mcmonkey" Goodwin b4df56f08a use relative paths for js imports 2023-10-29 09:07:29 -07:00
Rhys Yang c5a8fed1e6 Update README.md 2023-10-28 03:09:45 +08:00
17 changed files with 914 additions and 671 deletions
+4 -1
View File
@@ -36,7 +36,10 @@ jobs:
- name: Extract Section
id: extract_section
run: sed -n '/## Installation/,/## Usage/ {/## Usage/!p;}' README.md > installation.md
run: |
tag_name="${{ env.TAG_NAME }}"
sed -n "/## $tag_name/,/## /p" CHANGELOG.md | sed '$d' >> installation.md
sed -n '/## Installation/,/## Usage/ {/## Usage/!p;}' README.md >> installation.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
+17
View File
@@ -0,0 +1,17 @@
# Change Log
## v1.1.0
- Add `Batch Loader` node #13
- Add `MODEL_NAME` output to the `Prompt Reader` node #23
- Add VAE selector to the `Parameter Generator` node #15
- Add pixel dimensions display to the `aspect_ratio` in the `Parameter Generator` node #6
- Add Positive and Negative Aesthetic Score to the `PARAMETERS` in the `Parameter Generator` node #8
- Add `FILENAME` and `METADATA` output to the `Prompt Saver` node #16
## v1.0.1
- Add a new file-naming mechanism to ensure naming uniqueness
- Fix `%counter` overwriting existing images #11 #14
- Fix the bug causing the `Prompt Saver` node fails to save images in jpg and webp formats #10
- Use relative paths for js imports #12
## v1.0.0
- Initial release
+24 -6
View File
@@ -20,9 +20,10 @@ additional metadata to ensure compatibility with metadata detection on websites
<a href="#supported-formats">Supported Formats</a> •
<a href="#installation">Installation</a> •
<a href="#usage">Usage</a> •
<a href="./CHANGELOG.md">Change Log</a> •
<a href="#credits">Credits</a>
</p>
<img src="./images/screenshot_v100.png">
<img src="./images/screenshot_v110.png">
</div>
@@ -78,6 +79,7 @@ to ensure maximum compatibility.
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
- For batch processing, please use the `Batch Loader` node.
<div align="center">
<img src="./images/reader.png" width="25%" height="25%" alt="reader node">
@@ -109,18 +111,22 @@ the `Prompt Saver` Node and `KSampler`.
| %sampler |
| %scheduler |
| %quality |
- `%counter` cannot be used for the `path`. This `%counter` is slightly different from the `%counter`
in the built-in Saver node, it will count all image files in the `path`.
- When the filename already exists, an index will be added at the end of the filename,
e.g. `file.png, file_1.png, file_2.png`.
- 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/saver.png" width="25%" height="25%" alt="generator and saver node">
<img src="./images/saver.png" width="25%" height="25%" alt="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 `Parameter Generator` Node can also be used as a control panel for complex ComfyUI workflows, just like the [AP workflow](https://perilli.com/ai/comfyui/).
- 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)
@@ -130,7 +136,19 @@ i.e., the proportion of base steps to total steps. This is used to calculate the
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">
<img src="./images/generator.png" width="25%" height="25%" alt="generator node">
</div>
### Batch Loader Node
- The `Batch Loader` node is specifically designed for the `Prompt Reader` node to batch-read image files in a directory
and cannot be used with other custom nodes.
- For batch processing, please connect the `IMAGE` output of the `Batch Loader` node to the `image` input of
the `Prompt Reader` node.
- The `path` supports relative paths such as `./input/` or absolute paths like `C:/Users/receyuki/Pictures`.
- Both `\ ` and `/` are acceptable.
<div align="center">
<img src="./images/loader.png" width="25%" height="25%" alt="loader node">
</div>
### Prompt Merger Node & Type Converter Node
@@ -144,7 +162,7 @@ You can use the `Type Converter` Node to convert them into `STRING` type.
<img src="./images/merger_converter.png" width="25%" height="25%" alt="merger and converter node">
</div>
### [Example Workflow](./workflows/example_workflow.json)
### [Example Workflow](https://raw.githubusercontent.com/receyuki/comfyui-prompt-reader-node/main/workflows/example_workflow.json)
<div align="center">
<img src="./images/example_workflow.png" width="100%" height="100%" alt="example workflow">
@@ -154,4 +172,4 @@ You can use the `Type Converter` Node to convert them into `STRING` type.
- 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
- A special thanks to [@alessandroperilli](https://github.com/alessandroperilli) and his [AP Workflow](https://perilli.com/ai/comfyui/) for providing numerous suggestions
+6 -1
View File
@@ -12,7 +12,12 @@ tk_nodes_path = os.path.join(os.path.dirname(__file__))
js_dest_path = os.path.join(comfy_path, "web", "extensions", "SDPromptReader")
os.makedirs(js_dest_path, exist_ok=True)
files_to_copy = ["promptDisplay.js", "parameterDisplay.js", "seedGen.js"]
files_to_copy = [
"promptDisplay.js",
"parameterDisplay.js",
"seedGen.js",
"loaderDisplay.js",
]
for file in files_to_copy:
js_src_path = os.path.join(tk_nodes_path, "js", file)
+1 -1
View File
@@ -1 +1 @@
VERSION = "1.0.0"
VERSION = "1.1.0"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 761 KiB

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 112 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 KiB

After

Width:  |  Height:  |  Size: 552 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

+36
View File
@@ -0,0 +1,36 @@
import {app} from "../../scripts/app.js";
import {ComfyWidgets} from "../../scripts/widgets.js";
// Create a read-only string widget with opacity set
function createWidget(app, node, widgetName) {
const widget = ComfyWidgets["STRING"](node, widgetName, ["STRING", {multiline: true}], app).widget;
widget.inputEl.readOnly = true;
widget.inputEl.style.opacity = 0.7;
return widget;
}
// Displays file list on the node
app.registerExtension({
name: "sd_prompt_reader.loaderDisplay",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "SDBatchLoader") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const result = onNodeCreated?.apply(this, arguments);
// Create prompt and setting widgets
const fileList = createWidget(app, this, "fileList");
return result;
};
// Update widgets
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
this.widgets.find(obj => obj.name === "fileList").value = message.text[0];
};
}
},
});
+13 -2
View File
@@ -1,5 +1,5 @@
import {app} from "/scripts/app.js";
import {ComfyWidgets} from "/scripts/widgets.js";
import {app} from "../../scripts/app.js";
import {ComfyWidgets} from "../../scripts/widgets.js";
// Create a read-only string widget
function createWidget(app, node, widgetName, type) {
@@ -59,6 +59,17 @@ 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;
const scalingFactor = message.text[9][message.text[1]]
const aspectRatioArray = Object.entries(message.text[8]).map(([ratio, dimensions]) => {
const [width, height] = dimensions;
return `${ratio} - ${width*scalingFactor}x${height*scalingFactor}`;
});
aspectRatioArray.unshift("custom")
const aspectRatio = `${message.text[0]} - ${message.text[8][message.text[0]][0]*scalingFactor}x${message.text[8][message.text[0]][1]*scalingFactor}`
this.widgets.find(obj => obj.name === "aspect_ratio").options.values = aspectRatioArray
this.widgets.find(obj => obj.name === "aspect_ratio").value = aspectRatio
};
}
},
+5 -5
View File
@@ -1,5 +1,5 @@
import {app} from "/scripts/app.js";
import {ComfyWidgets} from "/scripts/widgets.js";
import {app} from "../../scripts/app.js";
import {ComfyWidgets} from "../../scripts/widgets.js";
// Create a read-only string widget with opacity set
function createWidget(app, node, widgetName) {
@@ -34,9 +34,9 @@ app.registerExtension({
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
this.widgets[3].value = message.text[0];
this.widgets[4].value = message.text[1];
this.widgets[5].value = message.text[2];
this.widgets.find(obj => obj.name === "positive").value = message.text[0];
this.widgets.find(obj => obj.name === "negative").value = message.text[1];
this.widgets.find(obj => obj.name === "setting").value = message.text[2];
};
}
},
+13 -13
View File
@@ -97,19 +97,19 @@ class SeedControl {
}
this.serializedCtx = {};
};
this.node.getExtraMenuOptions = (_, options) => {
options.splice(options.length - 1, 0, {
content: "Show/Hide Last Seed Value",
callback: (_value, _options, _event, _parentMenu, _node) => {
this.node.properties["showLastSeed"] = !this.node.properties["showLastSeed"];
if (this.node.properties["showLastSeed"]) {
this.addLastSeedValue();
} else {
this.removeLastSeedValue();
}
},
});
};
// this.node.getExtraMenuOptions = (_, options) => {
// options.splice(options.length - 1, 0, {
// content: "Show/Hide Last Seed Value",
// callback: (_value, _options, _event, _parentMenu, _node) => {
// this.node.properties["showLastSeed"] = !this.node.properties["showLastSeed"];
// if (this.node.properties["showLastSeed"]) {
// this.addLastSeedValue();
// } else {
// this.removeLastSeedValue();
// }
// },
// });
// };
}
addLastSeedValue() {
+194 -25
View File
@@ -8,6 +8,7 @@
import os
from datetime import datetime
from itertools import chain
import torch
import json
@@ -52,18 +53,34 @@ output_to_terminal("Node version: " + NODE_VERSION)
output_to_terminal("Core version: " + CORE_VERSION)
class AnyType(str):
"""A special type that can be connected to any other types. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
class SDPromptReader:
files = []
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [
f
for f in os.listdir(input_dir)
if os.path.isfile(os.path.join(input_dir, f))
]
SDPromptReader.files = sorted(
[
f
for f in os.listdir(input_dir)
if os.path.isfile(os.path.join(input_dir, f))
]
)
return {
"required": {
"image": (sorted(files), {"image_upload": True}),
"image": (SDPromptReader.files, {"image_upload": True}),
},
"optional": {
"parameter_index": (
"INT",
{"default": 0, "min": 0, "max": 255, "step": 1},
@@ -81,6 +98,7 @@ class SDPromptReader:
"FLOAT",
"INT",
"INT",
any_type,
"STRING",
"STRING",
)
@@ -94,7 +112,8 @@ class SDPromptReader:
"CFG",
"WIDTH",
"HEIGHT",
"FILE_NAME",
"MODEL_NAME",
"FILENAME",
"SETTINGS",
)
@@ -103,7 +122,10 @@ class SDPromptReader:
OUTPUT_NODE = True
def load_image(self, image, parameter_index):
image_path = folder_paths.get_annotated_filepath(image)
if image in SDPromptReader.files:
image_path = folder_paths.get_annotated_filepath(image)
else:
image_path = image
i = Image.open(image_path)
i = ImageOps.exif_transpose(i)
image = i.convert("RGB")
@@ -138,6 +160,10 @@ class SDPromptReader:
cfg = float(
self.param_parser(image_data.parameter.get("cfg"), parameter_index) or 0
)
model = str(
self.param_parser(image_data.parameter.get("model"), parameter_index)
or ""
)
width = int(image_data.width or 0)
height = int(image_data.height or 0)
@@ -158,6 +184,7 @@ class SDPromptReader:
cfg,
width,
height,
model,
file_path.stem,
image_data.setting,
),
@@ -255,7 +282,8 @@ class SDPromptSaver:
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("FILENAME", "METADATA")
FUNCTION = "save_images"
OUTPUT_NODE = True
@@ -293,7 +321,7 @@ class SDPromptSaver:
(
full_output_folder,
filename_alt,
counter,
counter_alt,
subfolder_alt,
filename_prefix,
) = folder_paths.get_save_image_path(
@@ -303,7 +331,9 @@ class SDPromptSaver:
images[0].shape[0],
)
results = list()
results = []
files = []
comments = []
for image in images:
# model_name_str, sampler_name_str, scheduler_str = None, None, None
@@ -316,7 +346,6 @@ class SDPromptSaver:
variable_map = {
"%date": self.get_time(date_format),
"%time": self.get_time(time_format),
"%counter": f"{counter:05}",
"%seed": seed,
"%steps": steps,
"%cfg": cfg,
@@ -327,6 +356,12 @@ class SDPromptSaver:
"%quality": jpg_webp_quality,
}
subfolder = self.get_path(path, variable_map)
output_folder = Path(full_output_folder) / subfolder
output_folder.mkdir(parents=True, exist_ok=True)
counter = self.get_counter(output_folder)
variable_map["%counter"] = f"{counter:05}"
i = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
@@ -348,10 +383,9 @@ class SDPromptSaver:
f"Version: ComfyUI"
f"{extra_info_real}"
)
subfolder = self.get_path(path, variable_map)
output_folder = Path(full_output_folder) / subfolder
output_folder.mkdir(parents=True, exist_ok=True)
file = self.get_path(filename, variable_map).with_suffix("." + extension)
stem = self.get_path(filename, variable_map)
file = self.get_unique_filename(stem, extension, output_folder)
if extension == "png":
if not args.disable_metadata:
@@ -383,13 +417,15 @@ class SDPromptSaver:
},
}
)
piexif.insert(metadata, str(file))
piexif.insert(metadata, str(output_folder / file))
results.append(
{"filename": file.name, "subfolder": str(subfolder), "type": self.type}
)
counter += 1
files.append(str(file))
output_to_terminal("Saved file: " + str(file))
comments.append(comment)
return {"ui": {"images": results}}
return {"ui": {"images": results}, "result": (files, comments)}
@staticmethod
def calculate_model_hash(model_name):
@@ -403,6 +439,13 @@ class SDPromptSaver:
return hash_sha256.hexdigest()[:10]
@staticmethod
def get_counter(directory: Path):
img_files = list(
chain(*(directory.rglob(f"*{suffix}") for suffix in SUPPORTED_FORMATS))
)
return len(img_files) + 1
@staticmethod
def get_path(name, variable_map):
for variable, value in variable_map.items():
@@ -418,6 +461,18 @@ class SDPromptSaver:
except:
return ""
@staticmethod
def get_unique_filename(stem: Path, extension: str, output_folder: Path):
file = stem.with_suffix(f".{extension}")
index = 0
while (output_folder / file).exists():
index += 1
new_stem = f"{stem}_{index}"
file = Path(new_stem).with_suffix(f".{extension}")
return file
class SDParameterGenerator:
ASPECT_RATIO_MAP = {
@@ -438,13 +493,35 @@ class SDParameterGenerator:
"SDXL 1024px": 2.0,
}
DEFAULT_ASPECT_RATIO_DISPLAY = list(
map(
lambda x, scaling_factor=MODEL_SCALING_FACTOR: (
f"{x[0]} - "
f"{int(x[1][0]*scaling_factor['SDv1 512px'])}x"
f"{int(x[1][1]*scaling_factor['SDv1 512px'])} | "
f"{int(x[1][0]*scaling_factor['SDv2 768px'])}x"
f"{int(x[1][1]*scaling_factor['SDv2 768px'])} | "
f"{int(x[1][0]*scaling_factor['SDXL 1024px'])}x"
f"{int(x[1][1]*scaling_factor['SDXL 1024px'])}"
),
ASPECT_RATIO_MAP.items(),
)
)
ckpt_list = []
@classmethod
def INPUT_TYPES(s):
SDParameterGenerator.ckpt_list = folder_paths.get_filename_list("checkpoints")
return {
"required": {
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
"ckpt_name": (SDParameterGenerator.ckpt_list,),
},
"optional": {
"vae_name": (
["baked VAE"] + folder_paths.get_filename_list("vae"),
{"default": "baked VAE"},
),
"model_version": (
list(SDParameterGenerator.MODEL_SCALING_FACTOR.keys()),
{"default": "SDv1 512px"},
@@ -483,7 +560,7 @@ class SDParameterGenerator:
{"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01},
),
"aspect_ratio": (
["custom"] + list(SDParameterGenerator.ASPECT_RATIO_MAP.keys()),
["custom"] + SDParameterGenerator.DEFAULT_ASPECT_RATIO_DISPLAY,
{"default": "custom"},
),
"width": (
@@ -548,8 +625,9 @@ class SDParameterGenerator:
def generate_parameter(
self,
model_version,
ckpt_name,
vae_name,
model_version,
config_name,
seed,
steps,
@@ -566,6 +644,9 @@ class SDParameterGenerator:
output_vae=True,
output_clip=True,
):
if ckpt_name not in SDParameterGenerator.ckpt_list:
raise FileNotFoundError(f"Invalid ckpt_name: {ckpt_name}")
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
if config_name != "none":
config_path = folder_paths.get_full_path("configs", config_name)
@@ -584,19 +665,34 @@ class SDParameterGenerator:
embedding_directory=folder_paths.get_folder_paths("embeddings"),
)[:3]
if vae_name != "baked VAE":
vae_path = folder_paths.get_full_path("vae", vae_name)
sd = comfy.utils.load_torch_file(vae_path)
vae = comfy.sd.VAE(sd=sd)
checkpoint = (*checkpoint[:2], vae)
if aspect_ratio != "custom":
aspect_ratio_value = aspect_ratio.split(" - ")[0]
width = int(
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio][0]
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio_value][0]
* SDParameterGenerator.MODEL_SCALING_FACTOR[model_version]
)
height = int(
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio][1]
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio_value][1]
* SDParameterGenerator.MODEL_SCALING_FACTOR[model_version]
)
base_steps = int(steps * refiner_start)
refiner_steps = steps - base_steps
if model_version == "SDXL 1024px":
ascore = (
f"Positive aesthetic score: {positive_ascore},\n"
f"Negative aesthetic score: {negative_ascore},\n"
)
else:
ascore = ""
parameters = (
f"Model: {ckpt_name},\n"
f"Seed: {str(seed)},\n"
@@ -604,6 +700,7 @@ class SDParameterGenerator:
f"CFG scale: {str(cfg)},\n"
f"Sampler: {sampler_name},\n"
f"Scheduler: {scheduler},\n"
f"{ascore}"
f"Size: {str(width)}x{str(height)},\n"
f"Batch size: {str(batch_size)}\n"
)
@@ -611,7 +708,7 @@ class SDParameterGenerator:
return {
"ui": {
"text": (
aspect_ratio,
aspect_ratio.split(" - ")[0],
model_version,
width,
height,
@@ -619,6 +716,8 @@ class SDParameterGenerator:
refiner_start,
base_steps,
refiner_steps,
SDParameterGenerator.ASPECT_RATIO_MAP,
SDParameterGenerator.MODEL_SCALING_FACTOR,
)
},
"result": (
@@ -711,12 +810,81 @@ class SDTypeConverter:
)
class SDBatchLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"path": ("STRING", {"default": "./input/"}),
},
"optional": {
"image_load_limit": ("INT", {"default": 0, "min": 0, "step": 1}),
"start_index": ("INT", {"default": 0, "min": 0, "step": 1}),
},
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("IMAGE",)
OUTPUT_IS_LIST = (True,)
FUNCTION = "load_path"
CATEGORY = "SD Prompt Reader"
def load_path(
self,
path: str = "./input/",
image_load_limit: int = 0,
start_index: int = 0,
):
if not Path(path).is_dir():
raise FileNotFoundError(f"Invalid directory: {path}")
files = list(
filter(lambda file: file.suffix in SUPPORTED_FORMATS, Path(path).iterdir())
)
files = (
sorted(files)[start_index : start_index + image_load_limit]
if image_load_limit > 0
else sorted(files)[start_index:]
)
files_str = list(map(str, files))
return {
"ui": {
"text": ("\n".join(files_str),),
},
"result": (files_str,),
}
@classmethod
def IS_CHANGED(
s,
path,
image_load_limit,
start_index,
):
return os.listdir(path)
@classmethod
def VALIDATE_INPUTS(
s,
path,
image_load_limit,
start_index,
):
if not Path(path).is_dir():
return f"Invalid directory: {path}"
return True
NODE_CLASS_MAPPINGS = {
"SDPromptReader": SDPromptReader,
"SDPromptSaver": SDPromptSaver,
"SDParameterGenerator": SDParameterGenerator,
"SDPromptMerger": SDPromptMerger,
"SDTypeConverter": SDTypeConverter,
"SDBatchLoader": SDBatchLoader,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -725,4 +893,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"SDParameterGenerator": "SD Parameter Generator",
"SDPromptMerger": "SD Prompt Merger",
"SDTypeConverter": "SD Type Converter",
"SDBatchLoader": "SD Batch Loader",
}
File diff suppressed because it is too large Load Diff