Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
659d35b9b8 | ||
|
|
ee54281e45 | ||
|
|
f94e130bef | ||
|
|
6ab1534e85 | ||
|
|
d960b6dbf1 | ||
|
|
0d0afd7f45 | ||
|
|
2069d0c4fc | ||
|
|
55a57a3b2e | ||
|
|
a2e6aff81c | ||
|
|
2af4a879b5 | ||
|
|
12b5178915 | ||
|
|
a4f5891079 | ||
|
|
9042867e97 | ||
|
|
5527096b53 | ||
|
|
9a84eb186b | ||
|
|
faeea1f73c | ||
|
|
e6cca92351 | ||
|
|
05c3553688 | ||
|
|
23b5afbc4f | ||
|
|
c2ac789c44 | ||
|
|
bc62d2be03 | ||
|
|
74beb22df4 | ||
|
|
0d43174e14 | ||
|
|
91336e987d | ||
|
|
cbf771ff03 | ||
|
|
867f5eb6f1 | ||
|
|
88ed9fa966 | ||
|
|
d6f8598946 | ||
|
|
63919f44a4 | ||
|
|
2321ce5ae4 | ||
|
|
a8e217d9f1 | ||
|
|
e32a514adc | ||
|
|
27bcc7273e | ||
|
|
d1e6940491 | ||
|
|
3457877e2c | ||
|
|
4d85411aee | ||
|
|
508bc56ca8 | ||
|
|
bdcc5c7461 | ||
|
|
f86625d71b | ||
|
|
cf4b85a0a0 | ||
|
|
5241cf50b9 | ||
|
|
6cd2b3414d | ||
|
|
9194663f09 | ||
|
|
103a5c826a | ||
|
|
cdd38cb3cd | ||
|
|
8f3c760bad | ||
|
|
7ab1628c2a | ||
|
|
c4630346e6 | ||
|
|
202b1fdb06 | ||
|
|
a7fd3f11bd | ||
|
|
50e0456eca | ||
|
|
b5572bba48 | ||
|
|
d44b01659e | ||
|
|
97c6aa988d | ||
|
|
52882ee594 | ||
|
|
a6107989fb | ||
|
|
1a5850865f | ||
|
|
6ce83b3828 | ||
|
|
e67c358e2d | ||
|
|
111bc489d5 | ||
|
|
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,36 @@
|
||||
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
|
||||
@@ -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,20 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -153,3 +153,4 @@ dmypy.json
|
||||
cython_debug/
|
||||
|
||||
.idea/
|
||||
preview/
|
||||
@@ -0,0 +1,87 @@
|
||||
# Change Log
|
||||
## v1.3.3
|
||||
> [!TIP]
|
||||
> Starting from v1.3, to support auto-detection on Civitai, `calculate_model_hash` will be renamed as
|
||||
> `calculate_hash` and it will be enabled by default. Due to the addition of temporary storage of the model hash values,
|
||||
> the first image generated after switching to the new model will take more time to calculate the hash value,
|
||||
> but it will not affect the generation speed afterwards until the server is restarted.
|
||||
|
||||
> [!NOTE]
|
||||
> comfyui-prompt-reader-node is now published on the [Comfy Registry](https://www.comfyregistry.org/).
|
||||
|
||||
- Add pyproject.toml for Custom Node Registry #75 @haohaocreates
|
||||
|
||||
## v1.3.2
|
||||
> Starting from v1.3, to support auto-detection on Civitai, `calculate_model_hash` will be renamed as
|
||||
> `calculate_hash` and it will be enabled by default. Due to the addition of temporary storage of the model hash values,
|
||||
> the first image generated after switching to the new model will take more time to calculate the hash value,
|
||||
> but it will not affect the generation speed afterwards until the server is restarted.
|
||||
|
||||
- Add support for pasted images to the `Prompt Reader` node #64
|
||||
- Update core to [1.3.5](https://github.com/receyuki/stable-diffusion-prompt-reader/releases/tag/v1.3.5)
|
||||
- Update nodes.py save_images to have %width and %height #66 @COvert96
|
||||
- Update the `Prompt Reader` node to the new exception handling method
|
||||
|
||||
## v1.3.1
|
||||
> Starting from v1.3, to support auto-detection on Civitai, `calculate_model_hash` will be renamed as
|
||||
> `calculate_hash` and it will be enabled by default. Due to the addition of temporary storage of the model hash values,
|
||||
> the first image generated after switching to the new model will take more time to calculate the hash value,
|
||||
> but it will not affect the generation speed afterwards until the server is restarted.
|
||||
|
||||
- Add user-friendly warnings and remove the exception raised during the reading of metadata #58 #63
|
||||
- Add JPEG to the image extension list
|
||||
- Fix the issue where the `Prompt Saver` node cannot consume filename from another `Prompt Saver` node #62
|
||||
- Fix the `Prompt Saver` node freezes workflow when name includes `%model` #61
|
||||
- Remove ckpt validation of the `Prompt Reader` node #59
|
||||
|
||||
## v1.3.0
|
||||
> Starting from this version, to support auto-detection on Civitai, `calculate_model_hash` will be renamed as
|
||||
> `calculate_hash` and it will be enabled by default. Due to the addition of temporary storage of the model hash values,
|
||||
> the first image generated after switching to the new model will take more time to calculate the hash value,
|
||||
> but it will not affect the generation speed afterwards until the server is restarted.
|
||||
|
||||
- Add `Lora Loader` node and `Lora Selector` node
|
||||
- Add `VAE_NAME` output to the `Parameter Generator` node and add `vae_name` input to the `Prompt Saver` node #39
|
||||
- Add `lora_name` input to the `Prompt Saver` node
|
||||
- Add `resource_hash` switch to the `Prompt Saver` node
|
||||
- Add resources hashes to metadata for auto-detection on Civitai #35
|
||||
- Add temporary storage for model hashes
|
||||
- Add `WEB_DIRECTORY` and remove old js directory
|
||||
- Fix adding the `Parameter Generator` node to a workflow disables the queue button #45
|
||||
- Fix the `Parameter Generator` node doesn't load seed value from generated images #44
|
||||
- Fix the input validation of the `Batch Loader` node #37 #38
|
||||
- Fix the parsing error caused by the loss of model data #43
|
||||
- Fix the error message caused by aspect_ratio
|
||||
- Rename `calculate_model_hash` as `calculate_hash`
|
||||
- Update seedGen.js to rgthree's latest code
|
||||
- Update core to cc3c8b2
|
||||
|
||||
## v1.2.1
|
||||
- Fix the input validation of the `Prompt Reader` node #37
|
||||
- Fix the `Batch Loader` node not working when not connected to any node
|
||||
|
||||
## v1.2.0
|
||||
- Add `Parameter Extractor` node #24
|
||||
- Add a model matching feature to the `Prompt Reader` node #24
|
||||
- Add `save_metadata_file` option to the `Prompt Saver` node #30
|
||||
- Add `FILE_PATH` output to the `Prompt Saver` node #26
|
||||
- Fix the issue where the `Prompt Merger` node threw an error when merging empty strings #29
|
||||
- Enhance the `Batch Loader` node to support processing either a single file or a list of files #26
|
||||
- Update core to 1.3.4.post1
|
||||
|
||||
## 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
|
||||
@@ -1,15 +1,15 @@
|
||||
<div align="center">
|
||||
<img alt="icon" src="https://github.com/receyuki/stable-diffusion-prompt-reader/raw/master/sd_prompt_reader/resources/icon.png" width=20% height=20%>
|
||||
<img alt="icon" src="https://github.com/receyuki/stable-diffusion-prompt-reader/raw/master/sd_prompt_reader/resources/icon-cube.png" width=20% height=20%>
|
||||
<h1>SD Prompt Reader Node</h1>
|
||||
<a href="https://github.com/receyuki/comfyui-prompt-reader-node/blob/master/LICENSE">
|
||||
<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,36 +20,43 @@ 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_v130.png">
|
||||
</div>
|
||||
|
||||
|
||||
## Supported Formats
|
||||
| | PNG | JPEG | WEBP | TXT* |
|
||||
|--------------------------------------------------------------------------|:---:|:----:|:----:|:----:|
|
||||
| [A1111's webUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) | ✅ | ✅ | ✅ | ✅ |
|
||||
| [Easy Diffusion](https://github.com/easydiffusion/easydiffusion) | ✅ | ✅ | ✅ | |
|
||||
| [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI)* | ✅ | ✅ | | |
|
||||
| [Fooocus-MRE](https://github.com/MoonRide303/Fooocus-MRE)* | ✅ | ✅ | | |
|
||||
| [InvokeAI](https://github.com/invoke-ai/InvokeAI) | ✅ | | | |
|
||||
| [ComfyUI](https://github.com/comfyanonymous/ComfyUI)* | ✅ | | | |
|
||||
| [NovelAI](https://novelai.net/) | ✅ | | | |
|
||||
| [Draw Things](https://drawthings.ai/) | ✅ | | | |
|
||||
| Naifu(4chan) | ✅ | | | |
|
||||
| | PNG | JPEG | WEBP | TXT* |
|
||||
|----------------------------------------------------------------------------------------|:---:|:----:|:----:|:----:|
|
||||
| [A1111's webUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) | ✅ | ✅ | ✅ | ✅ |
|
||||
| [Easy Diffusion](https://github.com/easydiffusion/easydiffusion) | ✅ | ✅ | ✅ | |
|
||||
| [StableSwarmUI](https://github.com/Stability-AI/StableSwarmUI)* | ✅ | ✅ | | |
|
||||
| [StableSwarmUI (prior to 0.5.8-alpha)](https://github.com/Stability-AI/StableSwarmUI)* | ✅ | ✅ | | |
|
||||
| [Fooocus-MRE](https://github.com/MoonRide303/Fooocus-MRE)* | ✅ | ✅ | | |
|
||||
| [NovelAI (stealth pnginfo)](https://novelai.net/) | ✅ | | ✅ | |
|
||||
| [NovelAI (legacy)](https://novelai.net/) | ✅ | | | |
|
||||
| [InvokeAI](https://github.com/invoke-ai/InvokeAI) | ✅ | | | |
|
||||
| [InvokeAI (prior to 2.3.5-post.2)](https://github.com/invoke-ai/InvokeAI) | ✅ | | | |
|
||||
| [InvokeAI (prior to 1.15)](https://github.com/invoke-ai/InvokeAI) | ✅ | | | |
|
||||
| [ComfyUI](https://github.com/comfyanonymous/ComfyUI)* | ✅ | | | |
|
||||
| [Draw Things](https://drawthings.ai/) | ✅ | | | |
|
||||
| Naifu(4chan) | ✅ | | | |
|
||||
|
||||
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.
|
||||
> [!NOTE]
|
||||
> 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 +64,259 @@ 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
|
||||
<div align="center">
|
||||
<img src="./images/reader.png" width="25%" height="25%" alt="reader node">
|
||||
</div>
|
||||
|
||||
- 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).
|
||||
> [!IMPORTANT]
|
||||
> 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.
|
||||
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
#### `parameter_index`
|
||||
- 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
|
||||
#### SDXL
|
||||
- For images generated by SDXL and containing multiple sets of prompts,
|
||||
the `text_g` will be combined with `text_l` into a single prompt
|
||||
#### Batch Read
|
||||
- For batch processing, please use the `Batch Loader` node. When using the `Batch Loader` node for bulk reading,
|
||||
the preview image will not update, and the text box will only display the metadata of the last image.
|
||||
<div align="center">
|
||||
<img src="./images/loader2reader.png" width="50%" height="50%" alt="connect batch loader to prompt reader">
|
||||
</div>
|
||||
|
||||
#### Additional Parameters
|
||||
- To read parameters other than the existing output, please connect the `settings` to the `Parameter Extractor` node.
|
||||
<div align="center">
|
||||
<img src="./images/reader2extractor.png" width="50%" height="50%" alt="connect prompt reader to parameter extractor">
|
||||
</div>
|
||||
|
||||
#### `MODEL_NAME`
|
||||
- `MODEL_NAME` is a special output that matches the model name in the metadata with the existing models on the server
|
||||
according to the following priority:
|
||||
1. Identical path, filename, and extension.
|
||||
2. Identical filename, and extension.
|
||||
e.g. `sd_xl_base.safetensors` will be matched with `SDXL\sd_xl_base.safetensors`, and vice versa.
|
||||
3. Identical filename.
|
||||
e.g. `sd_xl_base` will be matched with `SDXL\sd_xl_base.safetensors`, and vice versa.
|
||||
4. If a matching model cannot be found, the original name will be outputted.
|
||||
|
||||
</details>
|
||||
|
||||
### Prompt Saver Node
|
||||
<div align="center">
|
||||
<img src="./images/saver.png" width="25%" height="25%" alt="saver node">
|
||||
</div>
|
||||
|
||||
- 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's recommended to embed this node within the workflow
|
||||
to ensure maximum compatibility.
|
||||
> [!TIP]
|
||||
> Since it's not possible to directly extract metadata from `KSampler`, it's necessary to
|
||||
> use the `Parameter Generator` Node to generate parameters and simultaneously output them to
|
||||
> the `Prompt Saver` Node and `KSampler` Node.
|
||||
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
#### Image Format
|
||||
- Only PNG format supports embedding both metadata and workflow at the same time. Other formats can only embed metadata.
|
||||
#### Duplicate Filename
|
||||
- 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`.
|
||||
#### Hashes & Auto-Detection on Civitai
|
||||
- When `calculate_hash` is enabled, the node will compute the hash values of checkpoint, VAE, Lora,
|
||||
and embedding/Textual Inversion, and write them into the metadata. After the server restarts, or a new checkpoint,
|
||||
VAE, Lora, or embedding/Textual Inversion is loaded, the first image generation may take a longer time for hash calculation.
|
||||
The hash value will be stored in temporary storage without the need for repeated calculation,
|
||||
until the server is restarted.
|
||||
- When `resource_hash` is enabled, the resource hashes will be written into the metadata to support auto-detection
|
||||
on Civitai. This function will only run when `calculate_hash` is enabled.
|
||||
- If you need to calculate the hash of Lora, please use the `Lora Loader` Node or the `Lora Selector` Node.
|
||||
The hash value of the embedding/Textual Inversion will be automatically detected from the prompt.
|
||||
#### `save_metadata_file`
|
||||
- When the `save_metadata_file` is turned on, the metadata will be saved as a TXT file with the same name
|
||||
alongside the image.
|
||||
#### `date_format` & `time_format`
|
||||
- For the `date_format` and `time_format`, please refer to
|
||||
[strftime.org](https://strftime.org/) or [www.strfti.me](https://www.strfti.me/).
|
||||
#### `filename` & `path`
|
||||
- `%counter` cannot be used for `path`, it can only be used for `filename`. This `%counter` is slightly different
|
||||
from the `%counter` in the built-in `Saver` node, it will count all image files in the `path`.
|
||||
- Please refer to the following table for placeholders supported by the `filename` and `path`.
|
||||
|
||||
| | |
|
||||
|------------|------------|
|
||||
| %seed | %date |
|
||||
| %steps | %time |
|
||||
| %cfg | %counter |
|
||||
| %model | %extension |
|
||||
| %sampler | %quality |
|
||||
| %scheduler | |
|
||||
|
||||
</details>
|
||||
|
||||
### Parameter Generator Node
|
||||
<div align="center">
|
||||
<img src="./images/generator.png" width="25%" height="25%" alt="generator node">
|
||||
</div>
|
||||
|
||||
- Since it's not possible to directly extract metadata from `KSampler`, it's necessary to
|
||||
use the `Parameter Generator` Node to generate parameters and simultaneously output them to both
|
||||
the `Prompt Saver` Node and `KSampler` Node.
|
||||
> [!TIP]
|
||||
> 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/).
|
||||
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
#### Optimal Resolution
|
||||
- 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`
|
||||
- `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.
|
||||
|
||||
</details>
|
||||
|
||||
### Batch Loader Node
|
||||
<div align="center">
|
||||
<img src="./images/loader.png" width="25%" height="25%" alt="loader node">
|
||||
</div>
|
||||
|
||||
- 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.
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
- For batch processing, please connect the `IMAGE` output of the `Batch Loader` Node to the `image` input of
|
||||
the `Prompt Reader` Node.
|
||||
<div align="center">
|
||||
<img src="./images/loader2reader.png" width="50%" height="50%" alt="connect prompt reader to parameter extractor">
|
||||
</div>
|
||||
|
||||
#### `path`
|
||||
- The `path` supports relative paths such as `./input/` or absolute paths like `C:/Users/receyuki/Pictures`.
|
||||
- Both `\ ` and `/` are acceptable.
|
||||
- You can also input a single file or a list of files into the `path`,
|
||||
in which case the `image_load_limit` and `start_index` will not function.
|
||||
|
||||
</details>
|
||||
|
||||
### Parameter Extractor Node
|
||||
<div align="center">
|
||||
<img src="./images/extractor.png" width="25%" height="25%" alt="extractor node">
|
||||
</div>
|
||||
|
||||
- The `Parameter Extractor` Node is an extension of the `Prompt Reader` Node, designed to retrieve the values
|
||||
of all parameters in the settings (including those parameters that the `Prompt Reader` Node cannot output).
|
||||
e.g. `Hires upscaler`
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
- Connect the `SETTINGS` of the `Prompt Reader` Node to the `settings` of the `Parameter Extractor` Node.
|
||||
After the first run, the parameter list will be loaded.
|
||||
<div align="center">
|
||||
<img src="./images/reader2extractor.png" width="50%" height="50%" alt="connect prompt reader to parameter extractor">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
### Prompt Merger Node & Type Converter Node
|
||||
<div align="center">
|
||||
<img src="./images/merger_converter.png" width="25%" height="25%" alt="merger and converter node">
|
||||
</div>
|
||||
|
||||
- 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.
|
||||
|
||||
### Lora Loader Node & Lora Selector Node
|
||||
<div align="center">
|
||||
<img src="./images/lora.png" width="50%" height="50%" alt="lora loader and selector node">
|
||||
</div>
|
||||
|
||||
- The `Lora Loader` Node and `Lora Selector` Node are used to write Lora data into metadata and support auto-detection
|
||||
on Civitai.
|
||||
<details>
|
||||
<summary><b>More Information</b></summary>
|
||||
|
||||
- Replace the original loader with the `Lora Loader` Node, or connect the `LORA_NAME` output of the `Lora Selector` Node
|
||||
to the `lora_name` input of other lora loaders (built-in or custom), and link the `NEXT_LORA` output to the `lora_name`
|
||||
input of the `Prompt Saver` Node. Both of these nodes have the same function, please choose according to your needs.
|
||||
#### Multiple Loras
|
||||
- If you need to load multiple Loras, please connect the `Lora Loader` Node or `Lora Selector` Node head to tail
|
||||
through `last_lora` and `NEXT_LORA`, and connect the `NEXT_LORA` at the end of the Lora chain to the `lora_name` input
|
||||
of the `Prompt Saver` Node.
|
||||
1. Lora Loader chain
|
||||
<div align="center">
|
||||
<img src="./images/lora_loader_chain.png" width="100%" height="100%" alt="lora loader chain">
|
||||
</div>
|
||||
|
||||
2. Lora Selector chain
|
||||
<div align="center">
|
||||
<img src="./images/lora_selector_chain.png" width="100%" height="100%" alt="lora loader chain">
|
||||
</div>
|
||||
|
||||
</details>
|
||||
|
||||
### Example Workflow
|
||||
<details>
|
||||
<summary><b>Simple Example</b></summary>
|
||||
<div align="center">
|
||||
<img src="./workflows/simple_example.png" width="100%" height="100%" alt="example workflow">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Lora Example</b></summary>
|
||||
<div align="center">
|
||||
<img src="./workflows/lora_example.png" width="100%" height="100%" alt="example workflow">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Hires-fix Example</b></summary>
|
||||
<div align="center">
|
||||
<img src="./workflows/hires_fix_example.png" width="100%" height="100%" alt="example workflow">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>SDXL Example</b></summary>
|
||||
<div align="center">
|
||||
<img src="./workflows/sdxl_example.png" width="100%" height="100%" alt="example workflow">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
@@ -6,12 +6,10 @@ import shutil
|
||||
import folder_paths
|
||||
import os
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
# remove old directory
|
||||
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)
|
||||
|
||||
js_src_path = os.path.join(tk_nodes_path, "js", "promptDisplay.js")
|
||||
shutil.copy(js_src_path, js_dest_path)
|
||||
old_dir = os.path.join(comfy_path, "web", "extensions", "SDPromptReader")
|
||||
if os.path.exists(old_dir):
|
||||
shutil.rmtree(old_dir)
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION = "1.0.0b1"
|
||||
VERSION = "1.3.3"
|
||||
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 552 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 66 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 |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 978 KiB |
@@ -0,0 +1,31 @@
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
import {createTextWidget} from "./utils.js"
|
||||
|
||||
app.registerExtension({
|
||||
name: "sd_prompt_reader.extractorDisplay",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "SDParameterExtractor") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// Create widgets
|
||||
const styles = {textAlign: "center", fontSize: "0.75rem"}
|
||||
const value_display = createTextWidget(app, this, "value_display", styles);
|
||||
};
|
||||
|
||||
// Update widgets
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
this.widgets.find(obj => obj.name === "value_display").value = message.text[1]
|
||||
this.widgets.find(obj => obj.name === "parameter").options.values = message.text[0]
|
||||
if (this.widgets.find(obj => obj.name === "parameter").value === "parameters not loaded") {
|
||||
this.widgets.find(obj => obj.name === "parameter").value = message.text[0][0]
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
import {createTextWidget} from "./utils.js"
|
||||
|
||||
// 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 styles = {opacity: 0.7}
|
||||
const fileList =createTextWidget(app, this, "fileList", styles);
|
||||
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,70 @@
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
import {createTextWidget} from "./utils.js"
|
||||
|
||||
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 styles = {textAlign: "center", fontSize: "0.75rem"}
|
||||
const steps_display = createTextWidget(app, this, "steps_display", styles);
|
||||
const aspect_ratio_display = createTextWidget(app, this, "aspect_ratio_display", styles);
|
||||
|
||||
// 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")
|
||||
if (message.text[0] !== "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").value = aspectRatio
|
||||
}
|
||||
this.widgets.find(obj => obj.name === "aspect_ratio").options.values = aspectRatioArray
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,13 +1,6 @@
|
||||
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;
|
||||
}
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
import {createTextWidget} from "./utils.js"
|
||||
|
||||
// Displays prompt and setting on the node
|
||||
app.registerExtension({
|
||||
@@ -20,9 +13,10 @@ app.registerExtension({
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
// Create prompt and setting widgets
|
||||
const positive = createWidget(app, this, "positive");
|
||||
const negative = createWidget(app, this, "negative");
|
||||
const setting = createWidget(app, this, "setting");
|
||||
const styles = {opacity: 0.7}
|
||||
const positive = createTextWidget(app, this, "positive", styles);
|
||||
const negative = createTextWidget(app, this, "negative", styles);
|
||||
const setting = createTextWidget(app, this, "setting", styles);
|
||||
// Resize the node
|
||||
const nodeWidth = this.size[0];
|
||||
const nodeHeight = this.size[1];
|
||||
@@ -34,9 +28,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,223 @@
|
||||
/**
|
||||
* Modified from: https://github.com/rgthree/rgthree-comfy/blob/main/web/comfyui/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];
|
||||
|
||||
function getResolver(timeout = 5000) {
|
||||
const resolver = {};
|
||||
resolver.id = generateId(8);
|
||||
resolver.completed = false;
|
||||
resolver.resolved = false;
|
||||
resolver.rejected = false;
|
||||
resolver.promise = new Promise((resolve, reject) => {
|
||||
resolver.reject = () => {
|
||||
resolver.completed = true;
|
||||
resolver.rejected = true;
|
||||
reject();
|
||||
};
|
||||
resolver.resolve = (data) => {
|
||||
resolver.completed = true;
|
||||
resolver.resolved = true;
|
||||
resolve(data);
|
||||
};
|
||||
});
|
||||
resolver.timeout = setTimeout(() => {
|
||||
if (!resolver.completed) {
|
||||
resolver.reject();
|
||||
}
|
||||
}, timeout);
|
||||
return resolver;
|
||||
}
|
||||
|
||||
function generateId(length) {
|
||||
const arr = new Uint8Array(length / 2);
|
||||
crypto.getRandomValues(arr);
|
||||
return Array.from(arr, dec2hex).join('');
|
||||
}
|
||||
|
||||
function dec2hex(dec) {
|
||||
return dec.toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
let graphResolver = null;
|
||||
|
||||
function waitForGraph() {
|
||||
if (graphResolver === null) {
|
||||
graphResolver = getResolver();
|
||||
|
||||
function _wait() {
|
||||
if (!graphResolver.completed) {
|
||||
if (app === null || app === void 0 ? void 0 : app.graph) {
|
||||
graphResolver.resolve(app.graph);
|
||||
} else {
|
||||
requestAnimationFrame(_wait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_wait();
|
||||
}
|
||||
return graphResolver.promise;
|
||||
}
|
||||
|
||||
class Rgthree extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this.processingQueue = false;
|
||||
this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = null;
|
||||
this.initializeGraphAndCanvasHooks();
|
||||
this.initializeComfyUIHooks();
|
||||
}
|
||||
|
||||
async initializeGraphAndCanvasHooks() {
|
||||
const rgthree = this;
|
||||
const [graph] = await Promise.all([waitForGraph()]);
|
||||
const onSerialize = graph.onSerialize;
|
||||
graph.onSerialize = (data) => {
|
||||
this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff = data;
|
||||
onSerialize === null || onSerialize === void 0 ? void 0 : onSerialize.call(graph, data);
|
||||
};
|
||||
}
|
||||
|
||||
initializeComfyUIHooks() {
|
||||
const rgthree = this;
|
||||
const queuePrompt = app.queuePrompt;
|
||||
app.queuePrompt = async function () {
|
||||
rgthree.dispatchEvent(new CustomEvent("queue"));
|
||||
rgthree.processingQueue = true;
|
||||
try {
|
||||
await queuePrompt.apply(app, [...arguments]);
|
||||
} finally {
|
||||
rgthree.processingQueue = false;
|
||||
rgthree.dispatchEvent(new CustomEvent("queue-end"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node) {
|
||||
var _a, _b, _c;
|
||||
return ((_c = (_b = (_a = this.initialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.find((n) => n.id === node.id)) !== null && _c !== void 0 ? _c : null);
|
||||
}
|
||||
}
|
||||
|
||||
const rgthree = new Rgthree();
|
||||
|
||||
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;
|
||||
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
||||
} 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;
|
||||
if (!rgthree.processingQueue) {
|
||||
return inputSeed;
|
||||
}
|
||||
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;
|
||||
}
|
||||
const n = rgthree.getNodeFromInitialGraphToPromptSerializedWorkflowBecauseComfyUIBrokeStuff(node);
|
||||
if (n) {
|
||||
n.widgets_values[index] = this.serializedCtx.seedUsed;
|
||||
} else {
|
||||
console.warn('No serialized node found in workflow. May be attributed to '
|
||||
+ 'https://github.com/comfyanonymous/ComfyUI/issues/2193');
|
||||
}
|
||||
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 = {};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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 () {
|
||||
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
|
||||
this.seedControl = new SeedControl(this);
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import {app} from "../../scripts/app.js";
|
||||
import {ComfyWidgets} from "../../scripts/widgets.js";
|
||||
|
||||
// Create a read-only string widget
|
||||
export function createTextWidget(app, node, widgetName, styles = {}) {
|
||||
const widget = ComfyWidgets["STRING"](node, widgetName, ["STRING", {multiline: true}], app).widget;
|
||||
widget.inputEl.readOnly = true;
|
||||
Object.assign(widget.inputEl.style, styles);
|
||||
return widget;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "comfyui-prompt-reader-node"
|
||||
description = "The ultimate solution for managing image metadata and multi-tool compatibility. ComfyUI node version of the SD Prompt Reader."
|
||||
version = "1.3.3"
|
||||
license = "MIT"
|
||||
dependencies = ["piexif"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/receyuki/comfyui-prompt-reader-node"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "receyuki"
|
||||
DisplayName = "comfyui-prompt-reader-node"
|
||||
Icon = "https://raw.githubusercontent.com/receyuki/comfyui-prompt-reader-node/main/images/icon-400.png"
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 719 KiB |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 632 KiB |