Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe864be43a | ||
|
|
1da8db7d5c | ||
|
|
fa63e9a466 | ||
|
|
c025d474c5 | ||
|
|
570a59ea3d | ||
|
|
52dfd0456d | ||
|
|
49a9c4d041 | ||
|
|
c07548e3dc | ||
|
|
8dffa89ae9 | ||
|
|
f56c1e0c48 | ||
|
|
4f42729494 | ||
|
|
fc11266095 | ||
|
|
d02265dbce | ||
|
|
2950c76e9d | ||
|
|
07ecca0de4 | ||
|
|
3ecceb0175 | ||
|
|
00aabca362 | ||
|
|
aff17ccbf4 | ||
|
|
0a5bedf412 | ||
|
|
b68ed7d7fa | ||
|
|
61c0487744 | ||
|
|
b4df56f08a | ||
|
|
c5a8fed1e6 | ||
|
|
d25727455b | ||
|
|
47551e38dd | ||
|
|
cb72f372aa | ||
|
|
b34c0d2c2e | ||
|
|
7f1e6dbfd7 | ||
|
|
b08ec0c5e9 | ||
|
|
11058de872 | ||
|
|
992536ca33 | ||
|
|
bab9dba0d9 | ||
|
|
5886a538da | ||
|
|
f61e51864c | ||
|
|
7b3b6279c1 | ||
|
|
50b0d9ff96 | ||
|
|
c67cc3df84 | ||
|
|
bd8f669d71 | ||
|
|
4c8589be19 | ||
|
|
88a2c8a39f | ||
|
|
368be01cdc | ||
|
|
dfc8f7ddd5 | ||
|
|
a5d16cabd2 | ||
|
|
3ef54b705f | ||
|
|
3ccf6c6bd9 | ||
|
|
72aef6526c | ||
|
|
ce145417bd | ||
|
|
ff2cc91bc2 | ||
|
|
a1f415d5be | ||
|
|
44d03539e7 | ||
|
|
00fb6c7528 | ||
|
|
bb069f599d | ||
|
|
d25c5e8bac | ||
|
|
e4dca4d391 | ||
|
|
7daa149bda | ||
|
|
4180d56074 | ||
|
|
baceed2a01 | ||
|
|
f67ae16743 | ||
|
|
31cd4f6f41 | ||
|
|
a276df0aec | ||
|
|
289d918ad2 | ||
|
|
6a2df87b1b | ||
|
|
df6a43ab6a | ||
|
|
300669de66 | ||
|
|
6ab02a2311 | ||
|
|
faa29e83c8 | ||
|
|
8d6d871d59 | ||
|
|
2027b48457 | ||
|
|
b0c2163298 | ||
|
|
a1360bafbb | ||
|
|
d4e50ff463 | ||
|
|
e1c948ec4f | ||
|
|
832098367b | ||
|
|
f73ff88921 | ||
|
|
c94cf4ce40 | ||
|
|
67e59b9ceb | ||
|
|
a187932d84 | ||
|
|
f9aa0f108e | ||
|
|
3b795c9c42 | ||
|
|
e5fd46b07e | ||
|
|
96ded420e1 | ||
|
|
426d6710e0 | ||
|
|
30aaa3ef9c | ||
|
|
4d33bfff0a | ||
|
|
5afc486fea | ||
|
|
97bb1e7f61 |
@@ -0,0 +1,47 @@
|
||||
name: "Bug Report"
|
||||
description: Create a new ticket for a bug.
|
||||
title: "[BUG] - title"
|
||||
labels: [
|
||||
"bug"
|
||||
]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: "Description"
|
||||
description: Please enter an explicit description of your issue.
|
||||
placeholder: Short and explicit description of your incident...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reprod
|
||||
attributes:
|
||||
label: "Reproduction steps"
|
||||
description: Please enter an explicit description of your issue.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: image
|
||||
attributes:
|
||||
label: "Image file"
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
placeholder: |
|
||||

|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: "Version"
|
||||
description: What version are you running?
|
||||
multiple: false
|
||||
options:
|
||||
- 1.0.0
|
||||
default: 0
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,31 @@
|
||||
name: "Feature Request"
|
||||
description: Create a new ticket for a new feature request.
|
||||
title: "[FEATURE REQUEST] - title"
|
||||
labels: [
|
||||
"question"
|
||||
]
|
||||
body:
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: "Summary"
|
||||
description: Provide a brief explanation of the feature.
|
||||
placeholder: Describe in a few lines your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: basic_example
|
||||
attributes:
|
||||
label: "Basic Example"
|
||||
description: Indicate here some basic examples of your feature.
|
||||
placeholder: A few specific words about your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reference_issues
|
||||
attributes:
|
||||
label: "Reference Issues."
|
||||
description: Common issues
|
||||
placeholder: "#Issues IDs"
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Package Repository with Submodules
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Update Submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Get Tag Name
|
||||
id: get_tag
|
||||
run: echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Create ZIP Archive
|
||||
run: |
|
||||
tag_name="${{ env.TAG_NAME }}"
|
||||
mkdir "comfyui-prompt-reader-node"
|
||||
rsync -av --exclude='.github' \
|
||||
--exclude="comfyui-prompt-reader-node/" \
|
||||
--exclude="stable_diffusion_prompt_reader/images/" \
|
||||
--exclude="stable_diffusion_prompt_reader/whl/" \
|
||||
* comfyui-prompt-reader-node
|
||||
zip -r "${tag_name}.zip" "comfyui-prompt-reader-node"
|
||||
|
||||
- name: Extract Section
|
||||
id: extract_section
|
||||
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
|
||||
with:
|
||||
files: ${{ env.TAG_NAME }}.zip
|
||||
body_path: installation.md
|
||||
@@ -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
|
||||
@@ -5,11 +5,11 @@
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/receyuki/comfyui-prompt-reader-node"></a>
|
||||
<img alt="GitHub tag (with filter)" src="https://img.shields.io/github/v/tag/receyuki/comfyui-prompt-reader-node?label=node">
|
||||
<a href="https://github.com/receyuki/stable-diffusion-prompt-reader">
|
||||
<img alt="GitHub release (with filter)" src="https://img.shields.io/github/v/release/receyuki/stable-diffusion-prompt-reader?label=core"></a>
|
||||
<img alt="GitHub tag (with filter)" src="https://img.shields.io/github/v/tag/receyuki/stable-diffusion-prompt-reader?label=core"></a>
|
||||
<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><br>
|
||||
<h3>This project is currently in beta status. If you encounter any issues or have any suggestions, please let me know.</h3>
|
||||
<br>
|
||||
|
||||
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
|
||||
@@ -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_v100b1.png">
|
||||
<img src="./images/screenshot_v110.png">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -42,14 +43,15 @@ 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
|
||||
### Install via [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) (Recommended)
|
||||
~~Search for `SD Prompt Reader` in the ComfyUI Manager and install it.~~
|
||||
Waiting for approval.
|
||||
>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
|
||||
Please make sure to install the submodules along with the main repository.
|
||||
1. `cd` to the `custom_node` folder
|
||||
2. Clone this repo
|
||||
```shell
|
||||
git clone https://github.com/receyuki/comfyui-prompt-reader-node.git
|
||||
git clone --recursive https://github.com/receyuki/comfyui-prompt-reader-node.git
|
||||
```
|
||||
3. Install dependencies
|
||||
```shell
|
||||
@@ -57,14 +59,117 @@ Waiting for approval.
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
#### Update
|
||||
Please ensure that you update the submodules along with the main repo.
|
||||
When updating, don't forget to include the submodules along with the main repository.
|
||||
```shell
|
||||
git pull --recurse-submodules
|
||||
```
|
||||
|
||||
## Usage
|
||||
Example workflow coming soon.
|
||||
### Prompt Reader Node
|
||||
- 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).
|
||||
- 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
|
||||
- For batch processing, please use the `Batch Loader` node.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/reader.png" width="25%" height="25%" alt="reader node">
|
||||
</div>
|
||||
|
||||
### 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`.
|
||||
- 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 |
|
||||
- `%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="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)
|
||||
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 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
|
||||
- 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.
|
||||
- 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](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">
|
||||
</div>
|
||||
|
||||
## 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 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,15 @@ 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",
|
||||
"loaderDisplay.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.0b1"
|
||||
VERSION = "1.1.0"
|
||||
|
||||
|
After Width: | Height: | Size: 436 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 552 KiB |
|
After Width: | Height: | Size: 507 KiB |
|
After Width: | Height: | Size: 769 KiB |
|
Before Width: | Height: | Size: 738 KiB After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 787 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
@@ -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];
|
||||
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
|
||||
// Create a read-only string widget
|
||||
function createWidget(app, node, widgetName, type) {
|
||||
const widget = ComfyWidgets[type](node, widgetName, ["STRING", {multiline: true}], app).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
widget.inputEl.style.textAlign = "center";
|
||||
widget.inputEl.style.fontSize = "0.75rem";
|
||||
return widget;
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "sd_prompt_reader.parameterDisplay",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "SDParameterGenerator") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// Create widgets
|
||||
const steps_display = createWidget(app, this, "steps_display", "STRING");
|
||||
const aspect_ratio_display = createWidget(app, this, "aspect_ratio_display", "STRING");
|
||||
|
||||
// Resize the node
|
||||
const nodeWidth = this.size[0];
|
||||
const nodeHeight = this.size[1];
|
||||
this.setSize([nodeWidth * 2, nodeHeight * 1.2]);
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
// Update widgets
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
let ar_message;
|
||||
if (message.text[0] === "custom") {
|
||||
ar_message = "Custom aspect ratio: " + message.text[2] + " x " + message.text[3];
|
||||
} 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) + "%";
|
||||
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;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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];
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Modified from: https://github.com/rgthree/rgthree-comfy/blob/main/web/seed.js
|
||||
* Modified by: receyuki
|
||||
*/
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
|
||||
const LAST_SEED_BUTTON_LABEL = "(Use Last Queued Seed)";
|
||||
const SPECIAL_SEED_RANDOM = -1;
|
||||
const SPECIAL_SEED_INCREMENT = -2;
|
||||
const SPECIAL_SEED_DECREMENT = -3;
|
||||
const SPECIAL_SEEDS = [SPECIAL_SEED_RANDOM, SPECIAL_SEED_INCREMENT, SPECIAL_SEED_DECREMENT];
|
||||
|
||||
class SeedControl {
|
||||
constructor(node) {
|
||||
this.lastSeed = undefined;
|
||||
this.serializedCtx = {};
|
||||
this.lastSeedValue = null;
|
||||
this.node = node;
|
||||
this.node.constructor.exposedActions = ["Randomize seed each time", "Use last queued seed"];
|
||||
const handleAction = this.node.handleAction;
|
||||
this.node.handleAction = async (action) => {
|
||||
handleAction && handleAction.call(this.node, action);
|
||||
if (action === "Randomize each time") {
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
} else if (action === "Use last queued seed") {
|
||||
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}
|
||||
};
|
||||
this.node.properties = this.node.properties || {};
|
||||
for (const [i, w] of this.node.widgets.entries()) {
|
||||
if (w.name === "seed") {
|
||||
this.seedWidget = w;
|
||||
} else if (w.name === "control_after_generate") {
|
||||
this.node.widgets.splice(i, 1);
|
||||
}
|
||||
}
|
||||
if (!this.seedWidget) {
|
||||
throw new Error("Something's wrong; expected seed widget");
|
||||
}
|
||||
const randMax = Math.min(1125899906842624, this.seedWidget.options.max);
|
||||
const randMin = Math.max(0, this.seedWidget.options.min);
|
||||
const randomRange = (randMax - Math.max(0, randMin)) / (this.seedWidget.options.step / 10);
|
||||
this.node.addWidget("button", "Randomize seed each time", null, () => {
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
}, {serialize: false});
|
||||
this.node.addWidget("button", "New fixed random seed", null, () => {
|
||||
this.seedWidget.value =
|
||||
Math.floor(Math.random() * randomRange) * (this.seedWidget.options.step / 10) + randMin;
|
||||
}, {serialize: false});
|
||||
this.lastSeedButton = this.node.addWidget("button", LAST_SEED_BUTTON_LABEL, null, () => {
|
||||
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}, {serialize: false});
|
||||
this.lastSeedButton.disabled = true;
|
||||
this.seedWidget.serializeValue = async (node, index) => {
|
||||
const inputSeed = this.seedWidget.value;
|
||||
this.serializedCtx = {
|
||||
inputSeed: this.seedWidget.value,
|
||||
};
|
||||
if (SPECIAL_SEEDS.includes(this.serializedCtx.inputSeed)) {
|
||||
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_DECREMENT) {
|
||||
this.serializedCtx.seedUsed = this.lastSeed - 1;
|
||||
}
|
||||
}
|
||||
if (!this.serializedCtx.seedUsed || SPECIAL_SEEDS.includes(this.serializedCtx.seedUsed)) {
|
||||
this.serializedCtx.seedUsed =
|
||||
Math.floor(Math.random() * randomRange) * (this.seedWidget.options.step / 10) + randMin;
|
||||
}
|
||||
} else {
|
||||
this.serializedCtx.seedUsed = this.seedWidget.value;
|
||||
}
|
||||
node.widgets_values[index] = this.serializedCtx.seedUsed;
|
||||
this.seedWidget.value = this.serializedCtx.seedUsed;
|
||||
this.lastSeed = this.serializedCtx.seedUsed;
|
||||
if (SPECIAL_SEEDS.includes(this.serializedCtx.inputSeed)) {
|
||||
this.lastSeedButton.name = `${this.serializedCtx.seedUsed}`;
|
||||
this.lastSeedButton.disabled = false;
|
||||
if (this.lastSeedValue) {
|
||||
this.lastSeedValue.value = `Last Seed: ${this.serializedCtx.seedUsed}`;
|
||||
}
|
||||
} else {
|
||||
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
||||
this.lastSeedButton.disabled = true;
|
||||
}
|
||||
return this.serializedCtx.seedUsed;
|
||||
};
|
||||
this.seedWidget.afterQueued = () => {
|
||||
if (this.serializedCtx.inputSeed) {
|
||||
this.seedWidget.value = this.serializedCtx.inputSeed;
|
||||
}
|
||||
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();
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
}
|
||||
|
||||
addLastSeedValue() {
|
||||
if (this.lastSeedValue)
|
||||
return;
|
||||
this.lastSeedValue = ComfyWidgets["STRING"](this.node, "last_seed", ["STRING", {multiline: true}], app).widget;
|
||||
this.lastSeedValue.inputEl.readOnly = true;
|
||||
this.lastSeedValue.inputEl.style.fontSize = "0.75rem";
|
||||
this.lastSeedValue.inputEl.style.textAlign = "center";
|
||||
this.lastSeedValue.serializeValue = async (node, index) => {
|
||||
node.widgets_values[index] = "";
|
||||
return "";
|
||||
};
|
||||
this.node.computeSize();
|
||||
}
|
||||
|
||||
removeLastSeedValue() {
|
||||
if (!this.lastSeedValue)
|
||||
return;
|
||||
this.lastSeedValue.inputEl.remove();
|
||||
this.node.widgets.splice(this.node.widgets.indexOf(this.lastSeedValue), 1);
|
||||
this.lastSeedValue = null;
|
||||
this.node.computeSize();
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "sd_prompt_reader.seedGen",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
|
||||
if (nodeData.name === "SDParameterGenerator") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
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;
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
|
||||
import torch
|
||||
import json
|
||||
import numpy as np
|
||||
@@ -50,19 +53,35 @@ 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}),
|
||||
"data_index": (
|
||||
"image": (SDPromptReader.files, {"image_upload": True}),
|
||||
},
|
||||
"optional": {
|
||||
"parameter_index": (
|
||||
"INT",
|
||||
{"default": 0, "min": 0, "max": 255, "step": 1},
|
||||
),
|
||||
@@ -79,6 +98,8 @@ class SDPromptReader:
|
||||
"FLOAT",
|
||||
"INT",
|
||||
"INT",
|
||||
any_type,
|
||||
"STRING",
|
||||
"STRING",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
@@ -91,15 +112,20 @@ class SDPromptReader:
|
||||
"CFG",
|
||||
"WIDTH",
|
||||
"HEIGHT",
|
||||
"SETTING",
|
||||
"MODEL_NAME",
|
||||
"FILENAME",
|
||||
"SETTINGS",
|
||||
)
|
||||
|
||||
FUNCTION = "load_image"
|
||||
CATEGORY = "SD Prompt Reader"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def load_image(self, image, data_index):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
def load_image(self, image, parameter_index):
|
||||
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")
|
||||
@@ -111,24 +137,32 @@ class SDPromptReader:
|
||||
else:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
|
||||
if Path(image_path).suffix not in SUPPORTED_FORMATS:
|
||||
file_path = Path(image_path)
|
||||
|
||||
if file_path.suffix not in SUPPORTED_FORMATS:
|
||||
output_to_terminal(MESSAGE["suffix_error"][1])
|
||||
raise ValueError(MESSAGE["suffix_error"][1])
|
||||
|
||||
with open(Path(image_path), "rb") as f:
|
||||
with open(file_path, "rb") as f:
|
||||
image_data = ImageDataReader(f)
|
||||
if not image_data.tool:
|
||||
output_to_terminal(MESSAGE["format_error"][1])
|
||||
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
|
||||
)
|
||||
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)
|
||||
@@ -150,6 +184,8 @@ class SDPromptReader:
|
||||
cfg,
|
||||
width,
|
||||
height,
|
||||
model,
|
||||
file_path.stem,
|
||||
image_data.setting,
|
||||
),
|
||||
}
|
||||
@@ -160,14 +196,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
|
||||
@@ -184,8 +220,15 @@ class SDPromptSaver:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"}),
|
||||
},
|
||||
"optional": {
|
||||
"filename": (
|
||||
"STRING",
|
||||
{"default": "ComfyUI_%time_%seed_%counter", "multiline": False},
|
||||
),
|
||||
"path": ("STRING", {"default": "%date/", "multiline": False}),
|
||||
"model_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
# "model_name_str": ("STRING", {"default": ""}),
|
||||
"seed": (
|
||||
"INT",
|
||||
{
|
||||
@@ -209,28 +252,38 @@ class SDPromptSaver:
|
||||
},
|
||||
),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
# "sampler_name_str": ("STRING", {"default": ""}),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"negative": ("STRING", {"default": "", "multiline": True}),
|
||||
"extension": (["png", "jpg", "webp"],),
|
||||
},
|
||||
"optional": {
|
||||
# "scheduler_str": ("STRING", {"default": ""}),
|
||||
"width": (
|
||||
"INT",
|
||||
{"default": 0, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
{"default": 1, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
),
|
||||
"height": (
|
||||
"INT",
|
||||
{"default": 0, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
{"default": 1, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
),
|
||||
"positive": ("STRING", {"default": "", "multiline": True}),
|
||||
"negative": ("STRING", {"default": "", "multiline": True}),
|
||||
"extension": (["png", "jpg", "webp"],),
|
||||
"calculate_model_hash": ("BOOLEAN", {"default": False}),
|
||||
"lossless_webp": ("BOOLEAN", {"default": True}),
|
||||
"jpg_webp_quality": ("INT", {"default": 100, "min": 1, "max": 100}),
|
||||
"date_format": (
|
||||
"STRING",
|
||||
{"default": "%Y-%m-%d", "multiline": False},
|
||||
),
|
||||
"time_format": (
|
||||
"STRING",
|
||||
{"default": "%H%M%S", "multiline": False},
|
||||
),
|
||||
"extra_info": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("FILENAME", "METADATA")
|
||||
FUNCTION = "save_images"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
@@ -240,41 +293,80 @@ class SDPromptSaver:
|
||||
def save_images(
|
||||
self,
|
||||
images,
|
||||
filename_prefix,
|
||||
filename: str = "ComfyUI_%time_%seed_%counter",
|
||||
path: str = "%date/",
|
||||
model_name: str = "",
|
||||
model_name_str: str = "",
|
||||
seed: int = 0,
|
||||
steps: int = 0,
|
||||
cfg: float = 0.0,
|
||||
sampler_name: str = "",
|
||||
sampler_name_str: str = "",
|
||||
scheduler: str = "",
|
||||
scheduler_str: str = "",
|
||||
width: int = 1,
|
||||
height: int = 1,
|
||||
positive: str = "",
|
||||
negative: str = "",
|
||||
extension: str = "png",
|
||||
width: int = 0,
|
||||
height: int = 0,
|
||||
calculate_model_hash: bool = False,
|
||||
lossless_webp: bool = True,
|
||||
jpg_webp_quality: int = 100,
|
||||
date_format: str = "%Y-%m-%d",
|
||||
time_format: str = "%H%M%S",
|
||||
extra_info: str = "",
|
||||
prompt=None,
|
||||
extra_pnginfo=None,
|
||||
):
|
||||
filename_prefix += self.prefix_append
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
filename_alt,
|
||||
counter_alt,
|
||||
subfolder_alt,
|
||||
filename_prefix,
|
||||
) = folder_paths.get_save_image_path(
|
||||
filename_prefix, 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()
|
||||
|
||||
results = []
|
||||
files = []
|
||||
comments = []
|
||||
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),
|
||||
"%seed": seed,
|
||||
"%steps": steps,
|
||||
"%cfg": cfg,
|
||||
"%extension": extension,
|
||||
"%model": model_name_real,
|
||||
"%sampler": sampler_name_real,
|
||||
"%scheduler": scheduler_real,
|
||||
"%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
|
||||
model_hash = (
|
||||
f"Model hash: {self.calculate_model_hash(model_name)}, "
|
||||
f"Model hash: {self.calculate_model_hash(model_name_real)}, "
|
||||
if calculate_model_hash
|
||||
else ""
|
||||
)
|
||||
@@ -282,15 +374,19 @@ class SDPromptSaver:
|
||||
f"{positive}\n"
|
||||
f"Negative prompt: {negative}\n"
|
||||
f"Steps: {steps}, "
|
||||
f"Sampler: {sampler_name}{''if scheduler == 'normal' else '_'+scheduler}, "
|
||||
f"Sampler: {sampler_name_real}{''if scheduler_real == 'normal' else '_'+scheduler_real}, "
|
||||
f"CFG scale: {cfg}, "
|
||||
f"Seed: {seed}, "
|
||||
f"Size: {img.width if width==0 else width}x{img.height if height==0 else height}, "
|
||||
f"{model_hash}"
|
||||
f"Model: {Path(model_name).stem}, "
|
||||
f"Model: {Path(model_name_real).stem}, "
|
||||
f"Version: ComfyUI"
|
||||
f"{extra_info_real}"
|
||||
)
|
||||
file = Path(full_output_folder) / f"{filename}_{counter:05}_.{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:
|
||||
metadata = PngInfo()
|
||||
@@ -301,12 +397,16 @@ class SDPromptSaver:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||
img.save(
|
||||
file,
|
||||
output_folder / file,
|
||||
pnginfo=metadata,
|
||||
compress_level=4,
|
||||
)
|
||||
else:
|
||||
img.save(file, quality=jpg_webp_quality, lossless=lossless_webp)
|
||||
img.save(
|
||||
output_folder / file,
|
||||
quality=jpg_webp_quality,
|
||||
lossless=lossless_webp,
|
||||
)
|
||||
if not args.disable_metadata:
|
||||
metadata = piexif.dump(
|
||||
{
|
||||
@@ -317,13 +417,15 @@ class SDPromptSaver:
|
||||
},
|
||||
}
|
||||
)
|
||||
piexif.insert(metadata, str(file))
|
||||
piexif.insert(metadata, str(output_folder / file))
|
||||
results.append(
|
||||
{"filename": file.name, "subfolder": subfolder, "type": self.type}
|
||||
{"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):
|
||||
@@ -337,6 +439,307 @@ 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():
|
||||
name = name.replace(variable, str(value))
|
||||
return Path(name)
|
||||
|
||||
@staticmethod
|
||||
def get_time(time_format):
|
||||
now = datetime.now()
|
||||
try:
|
||||
time_str = now.strftime(time_format)
|
||||
return time_str
|
||||
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 = {
|
||||
"1:1": (512, 512),
|
||||
"4:3": (576, 448),
|
||||
"3:4": (448, 576),
|
||||
"3:2": (608, 416),
|
||||
"2:3": (416, 608),
|
||||
"16:9": (672, 384),
|
||||
"9:16": (384, 672),
|
||||
"21:9": (768, 320),
|
||||
"9:21": (320, 768),
|
||||
}
|
||||
|
||||
MODEL_SCALING_FACTOR = {
|
||||
"SDv1 512px": 1.0,
|
||||
"SDv2 768px": 1.5,
|
||||
"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": (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"},
|
||||
),
|
||||
"config_name": (
|
||||
["none"] + folder_paths.get_filename_list("configs"),
|
||||
{"default": "none"},
|
||||
),
|
||||
"seed": (
|
||||
"INT",
|
||||
{"default": -1, "min": -3, "max": 0xFFFFFFFFFFFFFFFF},
|
||||
),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"refiner_start": (
|
||||
"FLOAT",
|
||||
{"default": 0.8, "min": 0.0, "max": 1.0, "step": 0.01},
|
||||
),
|
||||
"cfg": (
|
||||
"FLOAT",
|
||||
{
|
||||
"default": 8.0,
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"step": 0.5,
|
||||
"round": 0.01,
|
||||
},
|
||||
),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"positive_ascore": (
|
||||
"FLOAT",
|
||||
{"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01},
|
||||
),
|
||||
"negative_ascore": (
|
||||
"FLOAT",
|
||||
{"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01},
|
||||
),
|
||||
"aspect_ratio": (
|
||||
["custom"] + SDParameterGenerator.DEFAULT_ASPECT_RATIO_DISPLAY,
|
||||
{"default": "custom"},
|
||||
),
|
||||
"width": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
),
|
||||
"height": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
),
|
||||
"batch_size": (
|
||||
"INT",
|
||||
{
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 4096,
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
folder_paths.get_filename_list("checkpoints"),
|
||||
"MODEL",
|
||||
"CLIP",
|
||||
"VAE",
|
||||
"INT",
|
||||
"INT",
|
||||
"INT",
|
||||
"FLOAT",
|
||||
comfy.samplers.KSampler.SAMPLERS,
|
||||
comfy.samplers.KSampler.SCHEDULERS,
|
||||
"FLOAT",
|
||||
"FLOAT",
|
||||
"INT",
|
||||
"INT",
|
||||
"INT",
|
||||
"STRING",
|
||||
)
|
||||
|
||||
RETURN_NAMES = (
|
||||
"MODEL_NAME",
|
||||
"MODEL",
|
||||
"CLIP",
|
||||
"VAE",
|
||||
"SEED",
|
||||
"STEPS",
|
||||
"REFINER_START_STEP",
|
||||
"CFG",
|
||||
"SAMPLER_NAME",
|
||||
"SCHEDULER",
|
||||
"POSITIVE_ASCORE",
|
||||
"NEGATIVE_ASCORE",
|
||||
"WIDTH",
|
||||
"HEIGHT",
|
||||
"BATCH_SIZE",
|
||||
"PARAMETERS",
|
||||
)
|
||||
FUNCTION = "generate_parameter"
|
||||
|
||||
CATEGORY = "SD Prompt Reader"
|
||||
|
||||
def generate_parameter(
|
||||
self,
|
||||
ckpt_name,
|
||||
vae_name,
|
||||
model_version,
|
||||
config_name,
|
||||
seed,
|
||||
steps,
|
||||
refiner_start,
|
||||
cfg,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
positive_ascore,
|
||||
negative_ascore,
|
||||
aspect_ratio,
|
||||
width,
|
||||
height,
|
||||
batch_size,
|
||||
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)
|
||||
checkpoint = comfy.sd.load_checkpoint(
|
||||
config_path,
|
||||
ckpt_path,
|
||||
output_vae=True,
|
||||
output_clip=True,
|
||||
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
||||
)
|
||||
else:
|
||||
checkpoint = comfy.sd.load_checkpoint_guess_config(
|
||||
ckpt_path,
|
||||
output_vae=True,
|
||||
output_clip=True,
|
||||
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_value][0]
|
||||
* SDParameterGenerator.MODEL_SCALING_FACTOR[model_version]
|
||||
)
|
||||
height = int(
|
||||
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"
|
||||
f"Steps: {str(steps)},\n"
|
||||
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"
|
||||
)
|
||||
|
||||
return {
|
||||
"ui": {
|
||||
"text": (
|
||||
aspect_ratio.split(" - ")[0],
|
||||
model_version,
|
||||
width,
|
||||
height,
|
||||
steps,
|
||||
refiner_start,
|
||||
base_steps,
|
||||
refiner_steps,
|
||||
SDParameterGenerator.ASPECT_RATIO_MAP,
|
||||
SDParameterGenerator.MODEL_SCALING_FACTOR,
|
||||
)
|
||||
},
|
||||
"result": (
|
||||
(ckpt_name,)
|
||||
+ checkpoint
|
||||
+ (
|
||||
seed,
|
||||
steps,
|
||||
base_steps,
|
||||
cfg,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
positive_ascore,
|
||||
negative_ascore,
|
||||
width,
|
||||
height,
|
||||
batch_size,
|
||||
parameters,
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class SDPromptMerger:
|
||||
@classmethod
|
||||
@@ -364,129 +767,131 @@ class SDPromptMerger:
|
||||
return (text_g + "\n" + text_l,)
|
||||
|
||||
|
||||
class SDParameterGenerator:
|
||||
class SDTypeConverter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
"with_config": ("BOOLEAN", {"default": False}),
|
||||
"config_name": (folder_paths.get_filename_list("configs"),),
|
||||
},
|
||||
"required": {},
|
||||
"optional": {
|
||||
"seed": (
|
||||
"INT",
|
||||
{"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF},
|
||||
"model_name": (
|
||||
folder_paths.get_filename_list("checkpoints"),
|
||||
{"forceInput": True},
|
||||
),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": (
|
||||
"FLOAT",
|
||||
{
|
||||
"default": 8.0,
|
||||
"min": 0.0,
|
||||
"max": 100.0,
|
||||
"step": 0.5,
|
||||
"round": 0.01,
|
||||
},
|
||||
),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
|
||||
"width": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
),
|
||||
"height": (
|
||||
"INT",
|
||||
{"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 8},
|
||||
"sampler_name": (
|
||||
comfy.samplers.KSampler.SAMPLERS,
|
||||
{"forceInput": True},
|
||||
),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"forceInput": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
"MODEL",
|
||||
"CLIP",
|
||||
"VAE",
|
||||
folder_paths.get_filename_list("checkpoints"),
|
||||
"INT",
|
||||
"INT",
|
||||
"FLOAT",
|
||||
comfy.samplers.KSampler.SAMPLERS,
|
||||
comfy.samplers.KSampler.SCHEDULERS,
|
||||
"INT",
|
||||
"INT",
|
||||
"STRING",
|
||||
"STRING",
|
||||
"STRING",
|
||||
)
|
||||
|
||||
RETURN_NAMES = (
|
||||
"MODEL",
|
||||
"CLIP",
|
||||
"VAE",
|
||||
"MODEL_NAME",
|
||||
"SEED",
|
||||
"STEPS",
|
||||
"CFG",
|
||||
"SAMPLER_NAME",
|
||||
"SCHEDULER",
|
||||
"WIDTH",
|
||||
"HEIGHT",
|
||||
"MODEL_NAME_STR",
|
||||
"SAMPLER_NAME_STR",
|
||||
"SCHEDULER_STR",
|
||||
)
|
||||
FUNCTION = "generate_parameter"
|
||||
|
||||
FUNCTION = "convert_string"
|
||||
CATEGORY = "SD Prompt Reader"
|
||||
|
||||
def generate_parameter(
|
||||
self,
|
||||
ckpt_name,
|
||||
with_config,
|
||||
config_name,
|
||||
seed,
|
||||
steps,
|
||||
cfg,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
width,
|
||||
height,
|
||||
output_vae=True,
|
||||
output_clip=True,
|
||||
def convert_string(
|
||||
self, model_name: str = "", sampler_name: str = "", scheduler: str = ""
|
||||
):
|
||||
config_path = folder_paths.get_full_path("configs", config_name)
|
||||
ckpt_path = folder_paths.get_full_path("checkpoints", ckpt_name)
|
||||
if with_config:
|
||||
checkpoint = comfy.sd.load_checkpoint(
|
||||
config_path,
|
||||
ckpt_path,
|
||||
output_vae=True,
|
||||
output_clip=True,
|
||||
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
||||
)
|
||||
else:
|
||||
checkpoint = comfy.sd.load_checkpoint_guess_config(
|
||||
ckpt_path,
|
||||
output_vae=True,
|
||||
output_clip=True,
|
||||
embedding_directory=folder_paths.get_folder_paths("embeddings"),
|
||||
)[:3]
|
||||
return checkpoint + (
|
||||
ckpt_name,
|
||||
seed,
|
||||
steps,
|
||||
cfg,
|
||||
return (
|
||||
model_name,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
"SDPromptMerger": SDPromptMerger,
|
||||
"SDParameterGenerator": SDParameterGenerator,
|
||||
"SDPromptMerger": SDPromptMerger,
|
||||
"SDTypeConverter": SDTypeConverter,
|
||||
"SDBatchLoader": SDBatchLoader,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SDPromptReader": "SD Prompt Reader",
|
||||
"SDPromptSaver": "SD Prompt Saver",
|
||||
"SDPromptMerger": "SD Prompt Merger",
|
||||
"SDParameterGenerator": "SD Parameter Generator",
|
||||
"SDPromptMerger": "SD Prompt Merger",
|
||||
"SDTypeConverter": "SD Type Converter",
|
||||
"SDBatchLoader": "SD Batch Loader",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,942 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 39,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
840,
|
||||
500
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 74
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 10,
|
||||
"widget": {
|
||||
"name": "width"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 11,
|
||||
"widget": {
|
||||
"name": "height"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "batch_size",
|
||||
"type": "INT",
|
||||
"link": 9,
|
||||
"widget": {
|
||||
"name": "batch_size"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
840,
|
||||
650
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 54
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 37,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
840,
|
||||
910
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 50
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 22,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 21
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
34
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
840,
|
||||
780
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 54
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 20,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 38,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
39
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "SDPromptSaver",
|
||||
"pos": [
|
||||
1210,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 500
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 34
|
||||
},
|
||||
{
|
||||
"name": "model_name",
|
||||
"type": "COMBO",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "model_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"link": 26,
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "steps",
|
||||
"type": "INT",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cfg",
|
||||
"type": "FLOAT",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "cfg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sampler_name",
|
||||
"type": "COMBO",
|
||||
"link": 14,
|
||||
"widget": {
|
||||
"name": "sampler_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "scheduler",
|
||||
"type": "COMBO",
|
||||
"link": 13,
|
||||
"widget": {
|
||||
"name": "scheduler"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 29,
|
||||
"widget": {
|
||||
"name": "width"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 28,
|
||||
"widget": {
|
||||
"name": "height"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "positive"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "negative"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "FILENAME",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "METADATA",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SDPromptSaver"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI_%time_%seed_%counter",
|
||||
"%date/",
|
||||
"CheckpointYesmix_v20.safetensors",
|
||||
822116345397686,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1,
|
||||
1,
|
||||
"",
|
||||
"",
|
||||
"png",
|
||||
false,
|
||||
true,
|
||||
100,
|
||||
"%Y-%m-%d",
|
||||
"%H%M%S",
|
||||
"ComfyUI Example Image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "KSamplerAdvanced",
|
||||
"pos": [
|
||||
840,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 290
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 17
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 18
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 39
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 23
|
||||
},
|
||||
{
|
||||
"name": "noise_seed",
|
||||
"type": "INT",
|
||||
"link": 24,
|
||||
"widget": {
|
||||
"name": "noise_seed"
|
||||
},
|
||||
"slot_index": 4
|
||||
},
|
||||
{
|
||||
"name": "steps",
|
||||
"type": "INT",
|
||||
"link": 35,
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cfg",
|
||||
"type": "FLOAT",
|
||||
"link": 36,
|
||||
"widget": {
|
||||
"name": "cfg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sampler_name",
|
||||
"type": "COMBO",
|
||||
"link": 15,
|
||||
"widget": {
|
||||
"name": "sampler_name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "scheduler",
|
||||
"type": "COMBO",
|
||||
"link": 12,
|
||||
"widget": {
|
||||
"name": "scheduler"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSamplerAdvanced"
|
||||
},
|
||||
"widgets_values": [
|
||||
"enable",
|
||||
1033713264376921,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
0,
|
||||
10000,
|
||||
"disable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SDPromptReader",
|
||||
"pos": [
|
||||
100,
|
||||
130
|
||||
],
|
||||
"size": [
|
||||
320,
|
||||
900
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "POSITIVE",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
37
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "NEGATIVE",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
38
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 3
|
||||
},
|
||||
{
|
||||
"name": "SEED",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "STEPS",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 5
|
||||
},
|
||||
{
|
||||
"name": "CFG",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
36
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 6
|
||||
},
|
||||
{
|
||||
"name": "WIDTH",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3,
|
||||
"slot_index": 7
|
||||
},
|
||||
{
|
||||
"name": "HEIGHT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MODEL_NAME",
|
||||
"type": "*",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "FILENAME",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "SETTINGS",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SDPromptReader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"00067-2302238556.png",
|
||||
"image",
|
||||
0,
|
||||
"ultra realistic 8k cg, HDR, epic, perfect artwork, masterpiece, bokeh, beautiful face, perfect female body, alluring, perfect female figure, ultra high res, beach, pink hair, seductive posture, seductive gaze, skindentation, 1girl, kpop idol, blush, street, earring, choker, close up, strap dress, half-length portrait, <lora:koreandolllikenessV20_v20:0.3>, <lora:fashigirl-v6-nai-5ep-resize:0.3>",
|
||||
"grayscale, monochrome, low quality, worst quality, lowres, acnes, bad anatomy, bad hands, deformed, error, logo, mosaic censoring, sketches, skin blemishes, skin spots, text, watermark, FastNegativeV2",
|
||||
"Steps: 35, Sampler: DPM++ 2M Karras, CFG scale: 7.5, Seed: 2302238556, Size: 512x512, Model hash: 18ed2b6c48, Model: xxmix9realistic_v40, Denoising strength: 0.45, Hires upscale: 2, Hires steps: 15, Hires upscaler: R-ESRGAN 4x+, Lora hashes: \"koreandolllikenessV20_v20: 8f3d16e6eada, fashigirl-v6-nai-5ep-resize: 4e0173ae8cef\", Version: 1.6.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "SDParameterGenerator",
|
||||
"pos": [
|
||||
470,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 320,
|
||||
"1": 900
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL_NAME",
|
||||
"type": "COMBO",
|
||||
"links": [
|
||||
16
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
17
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
20,
|
||||
32
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 3
|
||||
},
|
||||
{
|
||||
"name": "SEED",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
24,
|
||||
26
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 4
|
||||
},
|
||||
{
|
||||
"name": "STEPS",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "REFINER_START_STEP",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "CFG",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "SAMPLER_NAME",
|
||||
"type": "COMBO",
|
||||
"links": [
|
||||
14,
|
||||
15
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 8
|
||||
},
|
||||
{
|
||||
"name": "SCHEDULER",
|
||||
"type": "COMBO",
|
||||
"links": [
|
||||
12,
|
||||
13
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 9
|
||||
},
|
||||
{
|
||||
"name": "POSITIVE_ASCORE",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "NEGATIVE_ASCORE",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "WIDTH",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
10,
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 12
|
||||
},
|
||||
{
|
||||
"name": "HEIGHT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
11,
|
||||
28
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 13
|
||||
},
|
||||
{
|
||||
"name": "BATCH_SIZE",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 14
|
||||
},
|
||||
{
|
||||
"name": "PARAMETERS",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SDParameterGenerator"
|
||||
},
|
||||
"widgets_values": [
|
||||
"xxmix9realistic_v40.safetensors",
|
||||
"baked VAE",
|
||||
"SDv1 512px",
|
||||
"none",
|
||||
546889506427388,
|
||||
30,
|
||||
1,
|
||||
8,
|
||||
"dpmpp_2m",
|
||||
"karras",
|
||||
6,
|
||||
6,
|
||||
"1:1 - 512x512",
|
||||
512,
|
||||
512,
|
||||
1,
|
||||
"Total steps: 30,\nRefiner off",
|
||||
"Optimal resolution for SDv1 512px model\nwith aspect ratio 1:1: 512 x 512",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
9,
|
||||
2,
|
||||
14,
|
||||
4,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
10,
|
||||
2,
|
||||
12,
|
||||
4,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
11,
|
||||
2,
|
||||
13,
|
||||
4,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
12,
|
||||
2,
|
||||
9,
|
||||
8,
|
||||
8,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
13,
|
||||
2,
|
||||
9,
|
||||
3,
|
||||
6,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
14,
|
||||
2,
|
||||
8,
|
||||
3,
|
||||
5,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
15,
|
||||
2,
|
||||
8,
|
||||
8,
|
||||
7,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
16,
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
17,
|
||||
2,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
18,
|
||||
7,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
2,
|
||||
2,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
21,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
22,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
23,
|
||||
4,
|
||||
0,
|
||||
8,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
24,
|
||||
2,
|
||||
4,
|
||||
8,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
26,
|
||||
2,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
28,
|
||||
2,
|
||||
13,
|
||||
3,
|
||||
8,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
29,
|
||||
2,
|
||||
12,
|
||||
3,
|
||||
7,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
32,
|
||||
2,
|
||||
2,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
35,
|
||||
9,
|
||||
5,
|
||||
8,
|
||||
5,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
36,
|
||||
9,
|
||||
6,
|
||||
8,
|
||||
6,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
37,
|
||||
9,
|
||||
2,
|
||||
7,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
38,
|
||||
9,
|
||||
3,
|
||||
6,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
39,
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||