Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,45 @@
|
||||
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: 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
|
||||
@@ -5,11 +5,13 @@
|
||||
<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>
|
||||
<blockquote>
|
||||
This project is currently in beta status. If you encounter any issues or have any suggestions, please let me know.
|
||||
</blockquote>
|
||||
This is a subproject of the
|
||||
<a href="https://github.com/receyuki/stable-diffusion-prompt-reader">SD Prompt Reader.</a>
|
||||
It helps you extract metadata from images in any format supported by the
|
||||
@@ -22,7 +24,7 @@ additional metadata to ensure compatibility with metadata detection on websites
|
||||
<a href="#usage">Usage</a> •
|
||||
<a href="#credits">Credits</a>
|
||||
</p>
|
||||
<img src="images/screenshot_v100b1.png">
|
||||
<img src="./images/screenshot_v100b2.png">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -42,14 +44,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 +60,69 @@ 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.
|
||||
>The following section may be outdated and could differ from the actual nodes.
|
||||
### Prompt Reader Node
|
||||
- The Prompt Reader Node works exactly the same as the
|
||||
[standalone SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader).
|
||||
It uses the Image Data Reader from the
|
||||
[standalone SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader),
|
||||
allowing it to support the same formats and receive updates along with the
|
||||
[SD Prompt Reader](https://github.com/receyuki/stable-diffusion-prompt-reader).
|
||||
|
||||
***For images containing multiple sets of parameters, such as those processed through `hires-fix` or `refiner`,
|
||||
you will need to modify the `data_index` to select the parameters you need***
|
||||
|
||||
***For images generated by SDXL and containing multiple sets of prompts,
|
||||
the text_g will be combined with text_l into a single prompt***
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/reader.png" width="25%" height="25%" alt="reader node">
|
||||
</div>
|
||||
|
||||
### Prompt Saver Node & Parameter Generator Node
|
||||
- The Prompt Saver Node and The Parameter Generator Node are designed to be used together.
|
||||
- The Prompt Saver Node will write additional metadata in the A1111 format to the output images
|
||||
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.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/generator_saver.png" width="50%" height="50%" alt="generator and saver node">
|
||||
</div>
|
||||
|
||||
### Prompt Merger Node & Type Converter Node
|
||||
- Since the A1111 format cannot store `text_g` and `text_l` separately, SDXL users need to use
|
||||
the Prompt Merger Node to combine `text_g` and `text_l` into a single prompt.
|
||||
- In some cases, inputs like `model_name`, `sampler_name`, and `scheduler` may conflict with other custom nodes.
|
||||
You can use the SD Type Converter to convert them into `STRING` type and then
|
||||
input them into `model_name_str`, `sampler_name_str`, and `scheduler_str`. Please note that inputs of type `STRING`
|
||||
have higher priority, so do not connect `STRING` inputs unless necessary, as the Prompt Saver will prioritize reading
|
||||
`STRING` inputs over regular ones.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/merger_converter.png" width="25%" height="25%" alt="merger and converter node">
|
||||
</div>
|
||||
|
||||
### [Example Workflow](./workflows/example_workflow.json)
|
||||
>The example workflow is outdated and will be updated in the stable release.
|
||||
|
||||
<div align="center">
|
||||
<img src="./images/example_workflow.png" width="100%" height="100%" alt="example workflow">
|
||||
</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,10 @@ comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
tk_nodes_path = os.path.join(os.path.dirname(__file__))
|
||||
|
||||
js_dest_path = os.path.join(comfy_path, "web", "extensions", "SDPromptReader")
|
||||
if not os.path.exists(js_dest_path):
|
||||
os.makedirs(js_dest_path)
|
||||
os.makedirs(js_dest_path, exist_ok=True)
|
||||
|
||||
js_src_path = os.path.join(tk_nodes_path, "js", "promptDisplay.js")
|
||||
shutil.copy(js_src_path, js_dest_path)
|
||||
files_to_copy = ["promptDisplay.js", "parameterDisplay.js", "seedGen.js"]
|
||||
|
||||
for file in files_to_copy:
|
||||
js_src_path = os.path.join(tk_nodes_path, "js", file)
|
||||
shutil.copy(js_src_path, js_dest_path)
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION = "1.0.0b1"
|
||||
VERSION = "1.0.0b3"
|
||||
|
||||
|
After Width: | Height: | Size: 906 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
Before Width: | Height: | Size: 738 KiB After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 787 KiB |
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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,8 @@
|
||||
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import torch
|
||||
import json
|
||||
import numpy as np
|
||||
@@ -62,7 +64,7 @@ class SDPromptReader:
|
||||
return {
|
||||
"required": {
|
||||
"image": (sorted(files), {"image_upload": True}),
|
||||
"data_index": (
|
||||
"parameter_index": (
|
||||
"INT",
|
||||
{"default": 0, "min": 0, "max": 255, "step": 1},
|
||||
),
|
||||
@@ -80,6 +82,7 @@ class SDPromptReader:
|
||||
"INT",
|
||||
"INT",
|
||||
"STRING",
|
||||
"STRING",
|
||||
)
|
||||
RETURN_NAMES = (
|
||||
"IMAGE",
|
||||
@@ -91,14 +94,15 @@ class SDPromptReader:
|
||||
"CFG",
|
||||
"WIDTH",
|
||||
"HEIGHT",
|
||||
"SETTING",
|
||||
"SETTINGS",
|
||||
"FILE_NAME",
|
||||
)
|
||||
|
||||
FUNCTION = "load_image"
|
||||
CATEGORY = "SD Prompt Reader"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def load_image(self, image, data_index):
|
||||
def load_image(self, image, parameter_index):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
i = Image.open(image_path)
|
||||
i = ImageOps.exif_transpose(i)
|
||||
@@ -111,24 +115,28 @@ 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
|
||||
)
|
||||
width = int(image_data.width or 0)
|
||||
height = int(image_data.height or 0)
|
||||
@@ -151,6 +159,7 @@ class SDPromptReader:
|
||||
width,
|
||||
height,
|
||||
image_data.setting,
|
||||
file_path.stem,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -184,8 +193,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,23 +225,32 @@ 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"},
|
||||
}
|
||||
@@ -240,41 +265,69 @@ 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,
|
||||
filename_alt,
|
||||
counter,
|
||||
subfolder,
|
||||
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()
|
||||
|
||||
model_name_real = model_name_str if model_name_str else model_name
|
||||
sampler_name_real = sampler_name_str if sampler_name_str else sampler_name
|
||||
scheduler_real = scheduler_str if scheduler_str else scheduler
|
||||
|
||||
extra_info_real = f", Extra info: {extra_info}" if extra_info else ""
|
||||
|
||||
variable_map = {
|
||||
"%date": self.get_time(date_format),
|
||||
"%time": self.get_time(time_format),
|
||||
"%counter": f"{counter:05}",
|
||||
"%seed": seed,
|
||||
"%steps": steps,
|
||||
"%cfg": cfg,
|
||||
"%extension": extension,
|
||||
"%model": model_name_real,
|
||||
"%sampler": sampler_name_real,
|
||||
"%scheduler": scheduler_real,
|
||||
"%quality": jpg_webp_quality,
|
||||
}
|
||||
|
||||
for image in images:
|
||||
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 +335,20 @@ 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}"
|
||||
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)
|
||||
|
||||
if extension == "png":
|
||||
if not args.disable_metadata:
|
||||
metadata = PngInfo()
|
||||
@@ -301,12 +359,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(
|
||||
{
|
||||
@@ -319,7 +381,7 @@ class SDPromptSaver:
|
||||
)
|
||||
piexif.insert(metadata, str(file))
|
||||
results.append(
|
||||
{"filename": file.name, "subfolder": subfolder, "type": self.type}
|
||||
{"filename": file.name, "subfolder": str(subfolder), "type": self.type}
|
||||
)
|
||||
counter += 1
|
||||
|
||||
@@ -337,6 +399,244 @@ class SDPromptSaver:
|
||||
|
||||
return hash_sha256.hexdigest()[:10]
|
||||
|
||||
@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 ""
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
},
|
||||
"optional": {
|
||||
"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"] + list(SDParameterGenerator.ASPECT_RATIO_MAP.keys()),
|
||||
{"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,
|
||||
model_version,
|
||||
ckpt_name,
|
||||
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,
|
||||
):
|
||||
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 aspect_ratio != "custom":
|
||||
width = int(
|
||||
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio][0]
|
||||
* SDParameterGenerator.MODEL_SCALING_FACTOR[model_version]
|
||||
)
|
||||
height = int(
|
||||
SDParameterGenerator.ASPECT_RATIO_MAP[aspect_ratio][1]
|
||||
* SDParameterGenerator.MODEL_SCALING_FACTOR[model_version]
|
||||
)
|
||||
|
||||
base_steps = int(steps * refiner_start)
|
||||
refiner_steps = steps - base_steps
|
||||
|
||||
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"Size: {str(width)}x{str(height)},\n"
|
||||
f"Batch size: {str(batch_size)}\n"
|
||||
)
|
||||
|
||||
return {
|
||||
"ui": {
|
||||
"text": (
|
||||
aspect_ratio,
|
||||
model_version,
|
||||
width,
|
||||
height,
|
||||
steps,
|
||||
refiner_start,
|
||||
base_steps,
|
||||
refiner_steps,
|
||||
)
|
||||
},
|
||||
"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 +664,61 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SDPromptReader": SDPromptReader,
|
||||
"SDPromptSaver": SDPromptSaver,
|
||||
"SDPromptMerger": SDPromptMerger,
|
||||
"SDParameterGenerator": SDParameterGenerator,
|
||||
"SDPromptMerger": SDPromptMerger,
|
||||
"SDTypeConverter": SDTypeConverter,
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||