30 Commits
Author SHA1 Message Date
da2el-ai 6083981758 update version number 2025-03-10 23:30:46 +09:00
da2el-ai 2498771a3f fix: Fixed a bug where negative prompts couldn't be recorded when passing prompts through the d2_pipe. 2025-03-10 23:30:13 +09:00
coeracanth ed0e3e3c82 exifに保存されたnegativeにpositiveの内容が入っていたのを修正 (#8) 2024-12-31 21:13:46 +09:00
da2el-ai f709533cd9 Update documents 2024-12-29 03:14:32 +09:00
da2el-ai dea07a10d7 feat: Add support for d2_pipe in d2-node-comfyui 2024-12-29 02:21:38 +09:00
da2el-ai 49e0b66089 update version number 2024-11-24 01:59:48 +09:00
coeracanth aed718825e Added encoding specification when saving as webp (#6)
- webpで保存する際にエンコードの指定を追加
2024-11-24 01:54:46 +09:00
da2el-ai 725552ab7a feat: Compatible with JPEG format 2024-11-13 23:06:41 +09:00
da2el-ai 5fb2cc1bfd feat: Add image preview toggle button 2024-10-06 17:59:56 +09:00
da2el-ai 01a4b14137 feat: Add version number 2024-10-03 19:26:31 +09:00
da2el-ai e0b0e13172 fix: Add piexif install 2024-10-03 19:25:54 +09:00
da2el-ai e2060a548d feat: Add D2 KSampler into config 2024-10-03 01:35:02 +09:00
da2el-ai 64655aa8eb update version number 2024-10-02 02:17:58 +09:00
da2el-ai 421771e1a5 feat: add negative /positive to output 2024-10-02 02:17:11 +09:00
da2el-ai 18f91f9715 add english & 繁体中文 README 2024-09-29 01:10:00 +09:00
da2el-ai d4e254578c ver 1.1.0 2024-09-29 00:16:53 +09:00
da2el-ai 6db61b4bb8 fix: Unet Loaderを使った時にモデル名を取得できないのを修正 2024-08-19 18:19:15 +09:00
da2el-ai d53a19118f fix: webp形式で保存できないのを修正 2024-08-05 13:11:44 +09:00
da2el-ai 49a784c023 fix: インストール方法を修正 2024-08-05 02:48:12 +09:00
da2el-ai df99ba7ffa feat: add ComfyUI Registry support 2024-08-05 01:59:45 +09:00
haohaocreatesandsnomiao 664cd77564 chore(publish): Add Github Action for Publishing to Comfy Registry (#1)
Co-authored-by: snomiao <snomiao+comfy-pr@gmail.com>
2024-08-05 01:38:28 +09:00
da2el-ai 3fded06dbe feat: save_tags の説明を追加 2024-08-05 01:07:37 +09:00
da2el-ai 9040eb4b92 feat: Checkpoint名もタグとして保存可能にした 2024-08-05 01:04:05 +09:00
だにえる 17b141d66c Dev (#3)
* eagle_folder を追加。まだ未使用

* feat: エラーハンドリング追加。フォルダー一覧の取得を追加

* feat: Eagleフォルダ検索、作成を追加

* feat: Eagleフォルダ指定に対応

* feat: eagle_folder の説明を追加
2024-08-05 00:09:10 +09:00
da2el-ai a85f5b6b96 feat: eagle_folder の説明を追加 2024-08-04 23:42:56 +09:00
da2el-ai 42e5d50aa6 feat: Eagleフォルダ指定に対応 2024-08-04 23:42:56 +09:00
da2el-ai 6115649c29 feat: Eagleフォルダ検索、作成を追加 2024-08-04 23:42:56 +09:00
da2el-ai f1f28c6c93 feat: エラーハンドリング追加。フォルダー一覧の取得を追加 2024-08-04 23:42:56 +09:00
da2el-ai 6be8081f57 eagle_folder を追加。まだ未使用 2024-08-04 23:42:56 +09:00
da2el-ai 299888fdf8 fix: インストール方法を修正 2024-08-04 17:01:52 +09:00
17 changed files with 959 additions and 412 deletions
+24
View File
@@ -0,0 +1,24 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
# if this is a forked repository. Skipping the workflow.
if: github.event.repository.fork == false
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+1
View File
@@ -158,3 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
config.yaml
+2
View File
@@ -24,4 +24,6 @@
"files.associations": {
"*.vue": "vue"
},
"python.analysis.extraPaths": ["."],
"python.autoComplete.extraPaths": ["."]
}
+147 -89
View File
@@ -1,7 +1,8 @@
import json
import os
import numpy as np
import json
from typing import Dict, Optional, TypedDict, Union, List
from typing import Dict, Optional, Literal
from PIL import Image
from PIL.PngImagePlugin import PngInfo
@@ -11,22 +12,12 @@ import folder_paths
from .modules.util import util
from .modules.eagle_api import EagleAPI
from .modules.prompt_info_extractor import PromptInfoExtractor
from .modules.params_extractor import ParamsExtractor
from .my_types import TNodeParams, TGenInfo, D2_TD2Pipe
FORCE_WRITE_PROMPT = False
class ParamsInfo(TypedDict):
format: str
lossless_webp: bool
save_tags: bool
filename_template: str
eagle_folder: str
compression: int
positive: str
negative: str
memo_text: str
prompt: Optional[Dict]
extra_pnginfo: Optional[Dict]
class D2_SendEagle:
@@ -35,41 +26,49 @@ class D2_SendEagle:
self.type = "output"
self.output_folder = ""
self.subfolder_name = ""
self.eagle_api = None
self.eagle_api:EagleAPI = EagleAPI()
@classmethod
def INPUT_TYPES(s):
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"format": (["webp", "png"],),
"format": (["webp", "png", "jpeg"],),
# webpの時に可逆(lossless)不可逆(lossy)どちらにするか
"lossless_webp": (
"BOOLEAN",
{"default": True, "label_on": "lossless", "label_off": "lossy"},
),
# lossy の時の圧縮率
# webp lossy または jpeg の時の圧縮率
"compression": (
"INT",
{"default": 80, "min": 1, "max": 100, "step": 1},
),
# プロンプトをEagleタグに保存するか
"save_tags": (
"BOOLEAN",
{"default": False, "label_on": "save", "label_off": "none"},
{"default": 90, "min": 1, "max": 100, "step": 1},
),
# プロンプトやモデルをEagleタグに保存するか
"save_tags": ([
"None",
"Prompt + Checkpoint",
"Prompt",
"Checkpoint",
],),
# 保存するファイル名
"filename_template": (
"STRING",
{"multiline": False, "default":"{model}-{width}-{height}-{seed}"},
{"multiline": False, "default":"{model}-{seed}"},
),
# Eagleフォルダ
"eagle_folder": (
"STRING",
{"default": ""}
),
# プレビュー表示するか
"preview": (
"BOOLEAN",
{"default": True, "label_on": "ON", "label_off": "OFF"},
),
},
"optional":{
# ポジティブプロンプト
"positive": (
"STRING",
@@ -85,76 +84,107 @@ class D2_SendEagle:
"STRING",
{"multiline": True},
),
"d2_pipe": ("D2_TD2Pipe",),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
RETURN_TYPES = ("STRING", "STRING", "IMAGE")
RETURN_NAMES = ("positive", "negative", "IMAGE", )
FUNCTION = "add_item"
OUTPUT_NODE = True
CATEGORY = "D2"
# ######################
# ノード実行でEagleに画像を送る
# ノード実行で呼ばれる関数
# Eagleに画像を送る
def add_item(
self,
images,
format = "webp",
lossless_webp = False,
save_tags = True,
save_tags = "None",
filename_template = "{model}-{width}-{height}-{seed}",
eagle_folder = "",
compression = 80,
positive = "",
negative = "",
preview = True,
memo_text = "",
d2_pipe: Optional[D2_TD2Pipe] = None,
prompt: Optional[Dict] = None,
extra_pnginfo: Optional[Dict] = None,
):
self.output_folder, self.subfolder_name = self.get_output_folder()
self.eagle_api = EagleAPI()
results = list()
params: ParamsInfo = {
"format": format,
"lossless_webp": lossless_webp,
"save_tags": save_tags,
"filename_template": filename_template,
"eagle_folder": eagle_folder,
"compression": compression,
"positive": positive,
"negative": negative,
"memo_text": memo_text,
"prompt": prompt,
"extra_pnginfo": extra_pnginfo,
}
params = TNodeParams(
format = format,
lossless_webp = lossless_webp,
save_tags = save_tags,
filename_template = filename_template,
eagle_folder = eagle_folder,
compression = compression,
positive = self.__class__.get_prompt_value("positive", positive, d2_pipe),
negative = self.__class__.get_prompt_value("negative", negative, d2_pipe),
memo_text = memo_text,
prompt = prompt,
extra_pnginfo = extra_pnginfo,
)
for image in images:
results.append(self.create_image_object(image, params))
results.append(self.create_image_object(image, params, d2_pipe))
return {"ui": {"images": results}}
if(preview):
return {
"ui": {"images": results},
"result": (params["positive"], params["negative"], images,)
}
return {
"result": (params["positive"], params["negative"], images,)
}
@classmethod
def get_prompt_value(cls, mode: Literal["positive","negative"], value: Optional[str], d2_pipe: Optional[D2_TD2Pipe]) -> str:
if value:
return value
if d2_pipe is not None and getattr(d2_pipe, mode) is not None:
return getattr(d2_pipe, mode)
return ""
# ######################
# イメージオブジェクトを作成
def create_image_object(self, image, params:ParamsInfo) -> dict:
def create_image_object(self, image, params:TNodeParams, d2_pipe:D2_TD2Pipe | None) -> dict:
normalized_pixels = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(normalized_pixels, 0, 255).astype(np.uint8))
# 生成パラメータ整理
paramsExtractor = self.create_generate_params(img, params, d2_pipe)
# 必要な生成パラメーターをまとめたもの
gen_info = paramsExtractor.gen_info
# EagleやPNGInfo用に整形したもの
formated_info = paramsExtractor.format_info(params["memo_text"])
# print("generate_params", gen_info)
# print("format_info", formated_info)
# 画像をローカルに保存
file_name, file_full_path = self.save_image(img, params)
file_name, file_full_path = self.save_image(img, params, gen_info, formated_info)
# Eagleフォルダが指定されているならフォルダIDを取得
folder_id = self.eagle_api.find_or_create_folder(params["eagle_folder"])
# Send image to Eagle
item = {"path": file_full_path, "name": file_name}
item["annotation"] = util.make_annotation_text(
params["positive"], params["negative"], params["memo_text"]
)
# Eagleに送る情報を作成
item = {
"path": file_full_path,
"name": file_name,
"annotation": formated_info,
"tags": [],
}
if(params["save_tags"]):
item["tags"] = util.get_prompt_tags(params["positive"])
# タグを取得
item["tags"] = self.get_tags(params, gen_info)
_ret = self.eagle_api.add_item_from_path(data=item, folder_id=folder_id)
@@ -162,32 +192,58 @@ class D2_SendEagle:
"filename": file_name, "subfolder": self.subfolder_name, "type": self.type
}
# ######################
# 登録タグを取得
def get_tags(self, params:TNodeParams, gen_info:TGenInfo) -> list:
if(params["save_tags"] == "Prompt + Checkpoint"):
return [*util.get_prompt_tags(gen_info["positive"]), gen_info["model_name"]]
elif(params["save_tags"] == "Prompt"):
return util.get_prompt_tags(gen_info["positive"])
elif(params["save_tags"] == "Checkpoint"):
return [gen_info["model_name"]]
return []
# ######################
# 画像をローカルに保存
def save_image(self, img, params):
def save_image(self, img, params:TNodeParams, gen_info:TGenInfo, formated_info:str):
file_name = ""
file_full_path = ""
filename_params = self.get_filename_params(img, params)
if params["format"] == "webp":
# Save webp image file
file_name = self.get_filename(params["filename_template"], 'webp', filename_params)
file_name = self.get_filename(params["filename_template"], 'webp', gen_info)
file_full_path = os.path.join(self.output_folder, file_name)
exif_data = util.get_exif_from_prompt(
img.getexif(), params["prompt"], params["extra_pnginfo"]
)
exif = util.get_exif_from_prompt(img, formated_info, params["extra_pnginfo"], params["prompt"])
img.save(
file_full_path,
quality = params["compression"],
exif = exif_data,
exif = exif,
lossless = params["lossless_webp"],
)
elif params["format"] == "jpeg":
# Save jpeg image file
file_name = self.get_filename(params["filename_template"], 'jpeg', gen_info)
file_full_path = os.path.join(self.output_folder, file_name)
exif = util.get_exif_from_prompt(img, formated_info, params["extra_pnginfo"], params["prompt"])
img.save(
file_full_path,
quality = params["compression"],
optimize = True,
exif = exif,
)
else:
# Save png image file
file_name = self.get_filename(params["filename_template"], 'png', filename_params)
file_name = self.get_filename(params["filename_template"], 'png', gen_info)
file_full_path = os.path.join(self.output_folder, file_name)
metadata = PngInfo()
@@ -202,6 +258,7 @@ class D2_SendEagle:
return file_name, file_full_path
# ######################
# 画像保存パスを取得
def get_output_folder(self):
@@ -217,40 +274,41 @@ class D2_SendEagle:
# ######################
# ファイルネーム用パラメーターを取得
def get_filename_params(self, img, params) -> dict:
width, height = img.size
gen_data = PromptInfoExtractor(params["prompt"])
# 生成パラメーターを取得
def create_generate_params(self, img, params:TNodeParams, d2_pipe:D2_TD2Pipe | None) -> ParamsExtractor:
# print("[SendEagle] create_generate_params - ", params )
paramsExtractor = ParamsExtractor(params)
paramsExtractor.gen_info["width"] = img.width
paramsExtractor.gen_info["height"] = img.height
model = ""
steps = ""
seed = ""
# pipe が指定されていればその値を入力する
if d2_pipe != None:
if d2_pipe.steps != None:
paramsExtractor.gen_info["steps"] = d2_pipe.steps
if d2_pipe.sampler_name:
paramsExtractor.gen_info["sampler_name"] = d2_pipe.sampler_name
if d2_pipe.scheduler:
paramsExtractor.gen_info["scheduler"] = d2_pipe.scheduler
if d2_pipe.cfg != None:
paramsExtractor.gen_info["cfg"] = d2_pipe.cfg
if d2_pipe.seed != None:
paramsExtractor.gen_info["seed"] = d2_pipe.seed
if d2_pipe.ckpt_name:
paramsExtractor.gen_info["model_name"] = d2_pipe.ckpt_name.replace("\\", "__")
if gen_data and hasattr(gen_data, 'info') and gen_data.info:
model = os.path.splitext(gen_data.info.get("model_name", ""))[0]
steps = gen_data.info.get("steps", "")
seed = gen_data.info.get("seed", "")
return {
"width": width,
"height": height,
"model": model,
"steps": steps,
"seed": seed,
}
return paramsExtractor
# ######################
# ファイルネームを取得
def get_filename(self, template:str, ext:str, filename_params) -> str:
def get_filename(self, template:str, ext:str, gen_info:TGenInfo) -> str:
base = template.format(
width = filename_params["width"],
height = filename_params["height"],
model = filename_params["model"],
steps = filename_params["steps"],
seed = filename_params["seed"],
width = gen_info["width"],
height = gen_info["height"],
model = gen_info["model_name"],
steps = gen_info["steps"],
seed = gen_info["seed"],
)
return f"{util.get_datetime_str_msec()}-{base}.{ext}"
+133 -36
View File
@@ -1,59 +1,156 @@
# D2 Send Eagle
ComfyUIで生成した画像を画像管理ソフト [Eagle](https://en.eagle.cool/) に送るカスタムノードです。
[English][<a href="README_ja.md">日本語</a>][<a href="README_zh.md">繁体中文</a>]
A custom node for sending images generated in ComfyUI to the image management software [Eagle](https://en.eagle.cool/).
Please also try [D2 Nodes ComfyUI](https://github.com/da2el-ai/d2-nodes-ComfyUI/), which contains a collection of useful custom nodes.
[ComfyUI-send-eagle-slim](https://github.com/shingo1228/ComfyUI-send-eagle-slim) という既存のカスタムノードを自分の好みに拡張したものです。素晴らしいカスタムノードを制作してくださった Shingo.T 様に感謝です。
<img src="img/image.png">
## サンプルワークフロー
## Sample Workflows
<img src="img/sample_workflow.png">
### Basic Configuration Workflow
<a href="workflow/workflow_basic.png" target="_blkank">
<img src="workflow/workflow_basic.png">
</a>
### Workflow Using D2 Nodes ComfyUI
<a href="workflow/workflow_d2-node.png" target="_blkank">
<img src="workflow/workflow_d2-node.png">
</a>
- Gets checkpoint name from `D2 Checkpoint Loader` and removes the ".safetensors" extension using `D2 Filename Template`
- Retrieves generation parameters from `d2_pipe`
## 主な機能
---
- `image` で受け取った画像を Eagle に送信する
- `positive`、`negative` で受け取ったテキストを Eagle のメモとして記録する
- 画像の上の入力欄に記入したテキストも Eagle のメモとして記録する
- `format`: 保存形式を Webp / PNG から選択
- `lossless_webp`: Webp は可逆(lossless)か不可逆(lossy)から選択
- `compression`: lossyの時の圧縮率を指定
- `save_tags`: プロンプトを Eagle のタグとして保存するか選択
- `filename_template`: 保存ファイル名のテンプレート。`{}` 内に該当パラメータが入る
- 初期設定:`{model}-{width}-{height}-{seed}`
- `{model}`: checkpoint名
- `{width}` `{height}`: 画像の幅、高さ
- `{steps}`: 生成ステップ数
- `{seed}`: 生成シード
- `eagle_folder`: Eagleのフォルダ名、またはフォルダIDを指定。フォルダが存在しなければ新規作成する
## Main Features
- Sends images received through `image` to Eagle
- Records text received through `positive` and `negative` as Eagle memos
- Supports both png and webp formats
### Generation Parameters Recorded as Image and Eagle Comments
- Parameters such as positive, negative, CFG, steps are retrieved from the KSampler connected to D2 Send Eagle
- May not be retrievable depending on the workflow
- Parameters can also be specified through d2_pipe
## その他の機能
#### Supported KSamplers
Eagleに送信する他に、ローカル環境の下記フォルダにも画像を保存します。
このフォルダ名は変更できません。
The following KSampler types are supported.
You can add more by editing `config.yaml`.
- KSampler
- KSamplerAdvanced
- KSampler With Refiner (Fooocus)
- BNK_TiledKSampler
- KSampler (Efficient)
- GenerateNAID
### Differences Between png and webp Formats
- png format
- **Can save** ComfyUI workflows
- **Cannot display** in StableDiffusion webui A1111's PNGInfo
- webp format
- **Cannot save** ComfyUI workflows
- **Can display** in StableDiffusion webui A1111's PNGInfo
---
## Installation
### Using ComfyUI Manager
1. Open ComfyUI Manager
2. Click `Custom Nodes Manager`
3. Search for `D2 Send Eagle`
4. Click `Install`
5. Restart ComfyUI
### Using Command Prompt
1. Open Command Prompt
1. Navigate to `{ComfyUI installation folder}/custom_nodes`
2. `git clone https://github.com/da2el-ai/ComfyUI-d2-send-eagle`
---
## Inputs / Options
- `images`
- Images to save
- `d2_pipe`
- Collection of generation parameters
- `positive`
- Positive prompt
- `negative`
- Negative prompt
- `format`
- Choose from webp / png / jpeg formats
- `lossless_webp`
- Choose between lossless and lossy. Active when `webp` is selected.
- `compression`
- Specify compression rate. Active when selecting `lossy` for webp or when using `jpeg`.
- `save_tags`
- Choose whether to save as Eagle tags
- `None`: Don't save
- `Prompt + Checkpoint`: Save prompts and model name
- `Prompt`: Save prompts
- `Checkpoint`: Save model name
- `filename_template`
- Specify filename format
- Default is `{model}-{width}-{height}-{seed}`
- Available parameters: `width`, `height`, `model_name`, `steps`, `seed`
- `eagle_folder`
- Specify Eagle folder name or folder ID. Creates new folder if it doesn't exist
---
## Other Features
### Local Image Saving
In addition to sending to Eagle, images are also saved locally in the following folder.
This folder name cannot be changed.
`./ComfyUI/output/YYYY-MM-DD/YYYYMMDD_HHMMss_SSSSSS-{FinalImage_width}-{FinalImage_height}.webp`
## インストール
1. Navigaate to ComfyUI custom nodes directory
2. `git clone https://github.com/shingo1228/ComfyUI-send-eagle`
---
## Change Log
## 制限事項
PNGInfo の保存は下記のノードに対応しています。それ以外のノードでは情報を取得できません。
- 2024/12/28
- Added support for d2_pipe in d2-node-comfyui
- 2024/11/13
- Added JPEG format support
- 2024/10/04
- Added toggle button to hide image preview
- Because enlarging preview also enlarged `memo_text` which was inconvenient
- Added IMAGE to output
- Just a passthrough. Not necessary but added anyway
- 2024/09/29
- Added support for NovelAI generation node GenerateNAID
- Made settings configurable through `config.yaml`
- Added Exif prompt information recording for webp format
- 2024/08/19
- Fixed issue with model name not being retrieved when using Unet Loader
- 2024/08/04
- Initial release
- Sampler:`KSampler`,`KSamplerAdvanced`
- Latentイメージ:`Empty Latent Image`
- CLIP:`ClIP TextEncoder(Prompt)`,`CLIPTextEncodeSDXL`
- Latentイメージ: [ComfyUI-SDXL-EmptyLatentImage](https://github.com/shingo1228/ComfyUI-SDXL-EmptyLatentImage)
- Sampler: `KSampler With Refiner (Fooocus)`
- Sampler: [BNK_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler)
- Prompt: `SDXL Prompt Styler`
---
## Acknowledgements
## 変更履歴
- 2024/08/04 とりあえず公開
D2 Send Eagle is an extension of an existing custom node [ComfyUI-send-eagle-slim](https://github.com/shingo1228/ComfyUI-send-eagle-slim), modified to my preferences. Thanks to Shingo.T for creating such an excellent custom node.
+156
View File
@@ -0,0 +1,156 @@
# D2 Send Eagle
[<a href="README.md">English</a>][日本語][<a href="README_zh.md">繁体中文</a>]
ComfyUIで生成した画像を画像管理ソフト [Eagle](https://en.eagle.cool/) に送るカスタムノードです。
便利なカスタムノードをまとめた [D2 Nodes ComfyUI](https://github.com/da2el-ai/d2-nodes-ComfyUI/)も是非お試しください。
<img src="img/image.png">
## サンプルワークフロー
### 基本的な構成のワークフロー
<a href="workflow/workflow_basic.png" target="_blkank">
<img src="workflow/workflow_basic.png">
</a>
### D2 Nodes ComfyUI を使ったワークフロー
<a href="workflow/workflow_d2-node.png" target="_blkank">
<img src="workflow/workflow_d2-node.png">
</a>
- チェックポイント名を `D2 Checkpoint Loader` から取得し、`D2 Filename Template` を使って拡張子「.safetensors」を削除している
- `d2_pipe` から生成パラメータ—を取得している
---
## 主な機能
- `image` で受け取った画像を Eagle に送信する
- `positive`、`negative` で受け取ったテキストを Eagle のメモとして記録する
- png形式、webp形式が選択可能
### 画像、Eagleのコメントとして記録する生成パラメーター
- positive、negtive、CFG、steps などのパラメーターは D2 Send Eagle に接続している KSampler から取得しています
- ワークフローによっては取得できないことがあります
- d2_pipe からパラメータを指定することもできます
#### 対応 KSampler
下記の KSampler系に対応しています。
`config.yaml` を編集すると自分で増やすことができます。
- KSampler
- KSamplerAdvanced
- KSampler With Refiner (Fooocus)
- BNK_TiledKSampler
- KSampler (Efficient)
- GenerateNAID
### png形式と webp形式の違い
- png形式
- ComfyUIワークフローを**保存できる**
- StableDiffusion webui A1111 の PNGInfoには**表示できない**
- webp形式
- ComfyUIワークフローを**保存できない**
- StableDiffusion webui A1111 の PNGInfoには**表示できる**
---
## インストール
### ComfyUI Manager を使う場合
1. ComfyUI Manager を開く
2. `Custom Nodes Manager` をクリック
3. `D2 Send Eagle` を検索
4. `Install` をクリック
5. ComfyUI を再起動
### コマンドプロンプトを使う場合
1. コマンドプロンプトを開く
1. `{ComfyUIインストールフォルダ}/custom_nodes` に移動
2. `git clone https://github.com/da2el-ai/ComfyUI-d2-send-eagle`
---
## 入力 / オプション
- `images`
- 保存する画像
- `d2_pipe`
- 生成パラメータをまとめたもの
- `positive`
- ポジティブプロンプト
- `negative`
- ネガティブプロンプト
- `format`
- 保存形式を webp / png / jpeg から選択
- `lossless_webp`
- 可逆(lossless)か不可逆(lossy)から選択。`webp` を選択した時に有効。
- `compression`
- 圧縮率を指定。webp で `lossy` を選択するか、または `jpeg` を選択したときに有効。
- `save_tags`
- Eagle のタグとして保存するか選択
- `None`: 保存しない
- `Prompt + Checkpoint`: プロンプトとモデル名を保存
- `Prompt`: プロンプトを保存
- `Checkpoint`: モデル名を保存
- `filename_template`
- ファイル名の書式を指定
- 初期設定では `{model}-{width}-{height}-{seed}`
- 使用可能なパラメーター: `width`、`height`、`model_name`、`steps`、`seed`
- `eagle_folder`
- Eagleのフォルダ名、またはフォルダIDを指定。フォルダが存在しなければ新規作成する
---
## その他の機能
### ローカルにも画像を保存
Eagleに送信する他に、ローカル環境の下記フォルダにも画像を保存します。
このフォルダ名は変更できません。
`./ComfyUI/output/YYYY-MM-DD/YYYYMMDD_HHMMss_SSSSSS-{FinalImage_width}-{FinalImage_height}.webp`
---
## 変更履歴
- 2024/12/28
- d2-node-comfyui の d2_pipe に対応
- 2024/11/13
- JPEG方式に対応
- 2024/10/04
- イメージプレビューを非表示にするトグルボタンを追加
- プレビューを大きくすると `memo_text` も一緒に大きくなって邪魔だったので
- output に IMAGE を追加
- ただのパススルーです。必要ではないけど追加しました
- 2024/09/29
- NovelAI生成ノード GenerateNAID に対応
- 設定項目を `config.yaml` で指定できるようにした
- webp形式の時にプロンプト情報を Exif に記録するようにした
- 2024/08/19
- Unet Loaderを使った時にモデル名を取得できないのを修正
- 2024/08/04
- とりあえず公開
---
## 謝辞
D2 Send Eagle は [ComfyUI-send-eagle-slim](https://github.com/shingo1228/ComfyUI-send-eagle-slim) という既存のカスタムノードを自分の好みに拡張したものです。素晴らしいカスタムノードを制作してくださった Shingo.T 様に感謝です。
+156
View File
@@ -0,0 +1,156 @@
# D2 Send Eagle
[<a href="README.md">English</a>][<a href="README_ja.md">日本語</a>][繁体中文]
這是一個自定義節點,用於將 ComfyUI 生成的圖片發送到圖片管理軟件 [Eagle](https://en.eagle.cool/)。
也請嘗試使用 [D2 Nodes ComfyUI](https://github.com/da2el-ai/d2-nodes-ComfyUI/),其中包含了許多實用的自定義節點。
<img src="img/image.png">
## 範例工作流程
### 基礎配置工作流程
<a href="workflow/workflow_basic.png" target="_blkank">
<img src="workflow/workflow_basic.png">
</a>
### 使用 D2 Nodes ComfyUI 的工作流程
<a href="workflow/workflow_d2-node.png" target="_blkank">
<img src="workflow/workflow_d2-node.png">
</a>
- 從 `D2 Checkpoint Loader` 獲取檢查點名稱,並使用 `D2 Filename Template` 刪除 ".safetensors" 副檔名
- 從 `d2_pipe` 獲取生成參數
---
## 主要功能
- 將通過 `image` 接收的圖片發送到 Eagle
- 將通過 `positive` 和 `negative` 接收的文字記錄為 Eagle 備註
- 支持 png 和 webp 格式
### 作為圖片和 Eagle 註釋記錄的生成參數
- 參數如 positive、negative、CFG、steps 等是從連接到 D2 Send Eagle 的 KSampler 獲取的
- 根據工作流程的不同,可能無法獲取
- 也可以通過 d2_pipe 指定參數
#### 支持的 KSampler
支持以下 KSampler 類型。
可以通過編輯 `config.yaml` 來添加更多。
- KSampler
- KSamplerAdvanced
- KSampler With Refiner (Fooocus)
- BNK_TiledKSampler
- KSampler (Efficient)
- GenerateNAID
### png 和 webp 格式的區別
- png 格式
- **可以**保存 ComfyUI 工作流程
- **無法**顯示在 StableDiffusion webui A1111 的 PNGInfo 中
- webp 格式
- **無法**保存 ComfyUI 工作流程
- **可以**顯示在 StableDiffusion webui A1111 的 PNGInfo 中
---
## 安裝
### 使用 ComfyUI Manager
1. 打開 ComfyUI Manager
2. 點擊 `Custom Nodes Manager`
3. 搜索 `D2 Send Eagle`
4. 點擊 `Install`
5. 重啟 ComfyUI
### 使用命令提示符
1. 打開命令提示符
1. 進入 `{ComfyUI 安裝資料夾}/custom_nodes`
2. `git clone https://github.com/da2el-ai/ComfyUI-d2-send-eagle`
---
## 輸入 / 選項
- `images`
- 要保存的圖片
- `d2_pipe`
- 生成參數的集合
- `positive`
- 正向提示詞
- `negative`
- 負向提示詞
- `format`
- 從 webp / png / jpeg 中選擇保存格式
- `lossless_webp`
- 選擇無損(lossless)或有損(lossy)。選擇 `webp` 時有效。
- `compression`
- 指定壓縮率。選擇 webp 的 `lossy` 或使用 `jpeg` 時有效。
- `save_tags`
- 選擇是否保存為 Eagle 標籤
- `None`: 不保存
- `Prompt + Checkpoint`: 保存提示詞和模型名稱
- `Prompt`: 保存提示詞
- `Checkpoint`: 保存模型名稱
- `filename_template`
- 指定檔案名格式
- 預設為 `{model}-{width}-{height}-{seed}`
- 可用參數:`width`、`height`、`model_name`、`steps`、`seed`
- `eagle_folder`
- 指定 Eagle 資料夾名稱或資料夾 ID。如果不存在則創建新資料夾
---
## 其他功能
### 本地圖片保存
除了發送到 Eagle,圖片也會保存在本地的以下資料夾中。
此資料夾名稱無法更改。
`./ComfyUI/output/YYYY-MM-DD/YYYYMMDD_HHMMss_SSSSSS-{FinalImage_width}-{FinalImage_height}.webp`
---
## 更新日誌
- 2024/12/28
- 添加對 d2-node-comfyui 的 d2_pipe 支持
- 2024/11/13
- 添加 JPEG 格式支持
- 2024/10/04
- 添加隱藏圖片預覽的切換按鈕
- 因為放大預覽時 `memo_text` 也會一起放大很不方便
- 添加 IMAGE 到輸出
- 僅作為傳遞。不是必要的但還是添加了
- 2024/09/29
- 添加對 NovelAI 生成節點 GenerateNAID 的支持
- 使設置可通過 `config.yaml` 配置
- 為 webp 格式添加 Exif 提示詞信息記錄
- 2024/08/19
- 修復使用 Unet Loader 時無法獲取模型名稱的問題
- 2024/08/04
- 初始發布
---
## 致謝
D2 Send Eagle 是對現有自定義節點 [ComfyUI-send-eagle-slim](https://github.com/shingo1228/ComfyUI-send-eagle-slim) 的擴展,根據個人喜好進行了修改。感謝 Shingo.T 製作了如此優秀的自定義節點。
+11
View File
@@ -0,0 +1,11 @@
ksamplers:
- "D2 KSampler"
- "KSampler"
- "KSamplerAdvanced"
- "KSampler With Refiner (Fooocus)"
- "BNK_TiledKSampler"
- "KSampler (Efficient)"
- "GenerateNAID"
output_format: "{positive}\n\nNegative prompt:{negative}\nSteps: {steps}, Sampler: {sampler_name} {scheduler}, CFG scale: {cfg}, Seed: {seed}, Size: {width}x{height}, Model: {model_name}\n\nMemo: {memo}"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

+207
View File
@@ -0,0 +1,207 @@
import json
import re
import yaml
import os
import shutil
from pathlib import Path
from ..my_types import TGenInfo, TConfig
DEBUG = False
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_FILE = BASE_DIR / 'config.yaml'
CONFIG_ORG = BASE_DIR / 'config.org.yaml'
def dprint(str, dict=[]):
if DEBUG:
print(f"debug:{str}")
if dict:
print(json.dumps(dict, indent=2))
class ParamsExtractor:
def __init__(self, params, ):
"""constructor
Args:
prompt (_type_): ComfyUI hidden object "prompt".
"""
self._prompt = params["prompt"]
if DEBUG:
self._show_data()
self._load_config()
self.gen_info:TGenInfo = self._gather_info(params["positive"], params["negative"])
# ##################
# プロンプトからEagleタグを取得
def get_prompt_tags(self):
cleaned_string = re.sub(r":\d+\.\d+", "", self.gen_info["positive"])
items = cleaned_string.split(",")
return [
re.sub(r"[\(\)]", "", item).strip()
for item in items
if re.sub(r"[\(\)]", "", item).strip()
]
# ##################
# 設定ファイルを読み込む
def _load_config(self):
# ユーザー設定がなければオリジナル設定をコピーする
if not os.path.exists(CONFIG_FILE):
shutil.copy2(CONFIG_ORG, CONFIG_FILE)
with open(CONFIG_FILE, "r", encoding="utf-8") as file:
self.config:TConfig = yaml.safe_load(file)
# ##################
# 受け取ったパラメターを表示
def _show_data(self):
dprint("prompt(hidden object)", self._prompt)
# ##################
# 必要なパラメーターをまとめる
def _gather_info(self, positive, negative) -> TGenInfo:
gen_info:TGenInfo = {
"steps": 0,
"sampler_name": "",
"scheduler": "",
"cfg": 0,
"seed": 0,
"model_name": "",
"width": 0,
"height": 0,
"positive": positive,
"negative": negative,
}
ksamplers = self._get_ksamplers()
if not ksamplers:
return gen_info
# 最初のKsamplerのみ対象とする
key, ksampler = ksamplers[0]
gen_info["model_name"] = self._extract_model_name(ksampler)
width, height = self._extract_image_size(ksampler)
gen_info["width"] = width
gen_info["height"] = height
gen_info["steps"] = ksampler["inputs"]["steps"]
gen_info["sampler_name"] = self._extract_sampler_name(ksampler)
gen_info["scheduler"] = ksampler["inputs"]["scheduler"]
gen_info["cfg"] = ksampler["inputs"]["cfg"]
gen_info["seed"] = ksampler["inputs"].get(
"seed", ksampler["inputs"].get("noise_seed", None)
)
return gen_info
def _get_ksamplers(self):
ksamplers = [
(k, v)
for k, v in self._prompt.items()
if v["class_type"] in self.config["ksamplers"]
]
return sorted(ksamplers, key=lambda x: int(x[0]))
# ######################
# モデル名を取得
def _extract_model_name(self, node):
try:
model_input = node.get("inputs", {}).get("model")
if model_input and isinstance(model_input, list) and model_input:
ckpt_name = self._get_ckpt_name(model_input[0])
if ckpt_name:
return ckpt_name.replace("/", "_").replace("\\", "_")
except Exception:
pass
return ""
# モデル名読み込んだノードIDからモデル名を取得
def _get_ckpt_name(self, node_number):
"""Recursively search for the 'ckpt_name' key starting from the specified node."""
node = self._prompt[node_number]
if "ckpt_name" in node["inputs"]:
return node["inputs"]["ckpt_name"]
if "model" in node["inputs"]:
return self._get_ckpt_name(node["inputs"]["model"][0])
if "unet_name" in node["inputs"]:
return node["inputs"]["unet_name"]
return None
# ######################
# サンプラー名を取得
def _extract_sampler_name(self, node):
sampler = node["inputs"].get("sampler") or node["inputs"].get("sampler_name")
return sampler if sampler is not None else ""
# ######################
# 画像サイズを取得
def _extract_image_size(self, node):
# サイズ情報を持ってるならそのまま返す
if "width" in node["inputs"]:
return node["inputs"]["width"], node["inputs"]["height"]
width, height = self._get_image_size_from_latent_image(node["inputs"]["latent_image"][0])
return width, height
def _get_image_size_from_latent_image(self, node_number):
target_node = self._prompt[node_number]
if "width" in target_node["inputs"]:
return target_node["inputs"]["width"], target_node["inputs"]["height"]
if "clip_width" in target_node.get("outputs", {}):
return target_node["outputs"]["clip_width"], target_node["outputs"]["clip_height"]
elif target_node["class_type"] == "SDXL Empty Latent Image":
resolusion_str = target_node["inputs"]["resolution"]
pattern = r"(\d+) x (\d+)"
match = re.search(pattern, resolusion_str)
return int(match.group(1)), int(match.group(2))
else:
return 0, 0
# ######################
# PngInfoやEagleメモ用のテキストに整形
def format_info(self, memo:str):
formatted_str = self.config["output_format"].format(**self.gen_info, memo=memo)
return formatted_str
# def extract_and_format(self):
# """Extract and format the required information from the loaded JSON data."""
# info = self._gather_info()
# if not info:
# return "No suitable data found."
# return self.format_info(info)
# def formatted_annotation(self):
# annotation = ""
# if len(self.info["prompt"]) > 0:
# annotation += self.info["prompt"]
# if len(self.info["negative"]) > 0:
# if len(annotation) > 0:
# annotation += "\n"
# annotation += "Negative prompt: "
# annotation += self.info["negative"]
# if len(annotation) > 0:
# annotation += "\n"
# annotation += self.extract_and_format()
# return annotation
-235
View File
@@ -1,235 +0,0 @@
import json
import re
DEBUG = False
def dprint(str):
if DEBUG:
print(f"debug:{str}")
class PromptInfoExtractor:
def __init__(self, prompt, config_filepath=None):
"""constructor
Args:
prompt (_type_): ComfyUI hidden object "prompt".
config_filepath (_type_, optional): Configration file path. Defaults to None.
"""
# self.load_data(prompt)
self._prompt = prompt
if DEBUG:
self._show_data()
if config_filepath:
self.load_config(config_filepath)
else:
self.config = {
"search_class_types": ["KSampler", "KSamplerAdvanced", "KSampler With Refiner (Fooocus)", "BNK_TiledKSampler"],
"output_format": "Steps: {steps}, Sampler: {sampler_name} {scheduler}, CFG scale: {cfg}, Seed: {seed}, Size: {width}x{height}, Model: {model_name}",
}
self.info = self.gather_info()
def load_data(self, json_filepath):
"""Load JSON data from the provided filepath."""
with open(json_filepath, "r") as file:
self._prompt = json.load(file)
def load_config(self, config_filepath):
"""Load configuration from the provided filepath."""
with open(config_filepath, "r") as config_file:
self.config = json.load(config_file)
def _show_data(self):
"""For debug. show information of ComfyUI hidden object "prompt" and "extra_onginfo"."""
dprint(f"prompt(hidden object){self._prompt}")
def gather_info(self):
ksampler_items = self.get_ksampler_items()
if not ksampler_items:
return None
key, ksampler_item = ksampler_items[0]
model_name = self.extract_model_name(ksampler_item)
latent_image_info = self.extract_latent_image_info(ksampler_item)
prompt_text = self.extract_prompt_info()
info_dict = {
"steps": ksampler_item["inputs"]["steps"],
"sampler_name": ksampler_item["inputs"]["sampler_name"],
"scheduler": ksampler_item["inputs"]["scheduler"],
"cfg": ksampler_item["inputs"]["cfg"],
"seed": ksampler_item["inputs"].get(
"seed", ksampler_item["inputs"].get("noise_seed", None)
),
"model_name": model_name,
"width": latent_image_info["inputs"]["width"],
"height": latent_image_info["inputs"]["height"],
}
if "prompt" in prompt_text:
info_dict["prompt"] = prompt_text["prompt"]
else:
info_dict["prompt"] = ""
if "negative" in prompt_text:
info_dict["negative"] = prompt_text["negative"]
else:
info_dict["negative"] = ""
return info_dict
def get_ksampler_items(self):
ksampler_items = [
(k, v)
for k, v in self._prompt.items()
if v["class_type"] in self.config["search_class_types"]
]
return sorted(ksampler_items, key=lambda x: int(x[0]))
def extract_model_name(self, item):
return (
self.get_ckpt_name(item["inputs"]["model"][0])
.replace("/", "_")
.replace("\\", "_")
)
def get_ckpt_name(self, node_number):
"""Recursively search for the 'ckpt_name' key starting from the specified node."""
node = self._prompt[node_number]
if "ckpt_name" in node["inputs"]:
return node["inputs"]["ckpt_name"]
if "model" in node["inputs"]:
return self.get_ckpt_name(node["inputs"]["model"][0])
return None
def extract_latent_image_info(self, item):
latent_image_node_number = item["inputs"]["latent_image"][0]
target_item = self._prompt[latent_image_node_number]
if target_item["class_type"] == "EmptyLatentImage":
return target_item
elif target_item["class_type"] == "SDXL Empty Latent Image":
resolusion_str = target_item["inputs"]["resolution"]
pattern = r"(\d+) x (\d+)"
match = re.search(pattern, resolusion_str)
latent_image_info = {
"inputs": {"width": int(match.group(1)), "height": int(match.group(2))}
}
return latent_image_info
else:
latent_image_info = {"inputs": {"width": 0, "height": 0}}
return latent_image_info
def extract_prompt_info(self):
positive_text = self.extract_text_by_key("positive")
negative_text = self.extract_text_by_key("negative")
info_dict = {}
if positive_text:
info_dict["prompt"] = positive_text
if negative_text:
info_dict["negative"] = negative_text
return info_dict
def extract_text_by_key(self, key):
"""Extract text by the given key, either 'positive' or 'negative'."""
ksampler_items = self.get_ksampler_items()
if not ksampler_items:
return None
ksampler_item = ksampler_items[0][1]
if key not in ksampler_item["inputs"]:
return None
target_node_number = ksampler_item["inputs"][key][0]
target_node = self._prompt.get(str(target_node_number), {})
extracted_text = self.extract_text_from_node_v2(target_node, key)
return extracted_text
def extract_text_from_node_v2(self, node, key):
# For CLIPTextEncoder
direct_text = node.get("inputs", {}).get("text")
if isinstance(direct_text, str):
return direct_text
# For SDXLCLIPTextEncoder
text_g = node.get("inputs", {}).get("text_g")
text_l = node.get("inputs", {}).get("text_l")
if text_g and text_l:
if text_g == text_l:
return text_g
else:
return f"text_g:{text_g} text_l:{text_l}"
elif text_g:
return text_g
elif text_l:
return text_l
# For SDXLPromptStyler
try:
node_number = node.get("inputs", {}).get("text")[0]
if key == "positive":
text_positive = self._prompt[node_number]["inputs"]["text_positive"]
if text_positive:
return text_positive
else:
text_negative = self._prompt[node_number]["inputs"]["text_negative"]
if text_negative:
return text_negative
except(Exception) as e:
return None
if DEBUG:
return "Prompt parse error"
else:
return None
def format_info(self, info_dict):
"""Format the gathered information based on the configuration."""
formatted_str = self.config["output_format"].format(**info_dict)
return formatted_str
def extract_and_format(self):
"""Extract and format the required information from the loaded JSON data."""
info = self.gather_info()
if not info:
return "No suitable data found."
return self.format_info(info)
def formatted_annotation(self):
annotation = ""
if len(self.info["prompt"]) > 0:
annotation += self.info["prompt"]
if len(self.info["negative"]) > 0:
if len(annotation) > 0:
annotation += "\n"
annotation += "Negative prompt: "
annotation += self.info["negative"]
if len(annotation) > 0:
annotation += "\n"
annotation += self.extract_and_format()
return annotation
def get_prompt_tags(self):
cleaned_string = re.sub(r":\d+\.\d+", "", self.info["prompt"])
items = cleaned_string.split(",")
return [
re.sub(r"[\(\)]", "", item).strip()
for item in items
if re.sub(r"[\(\)]", "", item).strip()
]
+59 -52
View File
@@ -3,84 +3,91 @@ import json
import traceback
import re
from PIL import Image
import piexif
import piexif.helper
from datetime import datetime
from typing import Dict, Optional, TypedDict, Union, List
from ..my_types import TGenInfo
class util:
@staticmethod
def initialize_defaults(prompt, extra_pnginfo):
util.write_prompt(prompt, extra_pnginfo)
print("check prompt_decode_err.log")
traceback.print_exc()
return "", [], "unknown", "00", "000000"
# @staticmethod
# def initialize_defaults(prompt, extra_pnginfo):
# util.write_prompt(prompt, extra_pnginfo)
# print("check prompt_decode_err.log")
# traceback.print_exc()
# return "", [], "unknown", "00", "000000"
# @staticmethod
# def write_prompt(prompt, extra_pnginfo):
# log_file_name = os.path.join(os.path.dirname(__file__), "prompt_decode_err.log")
# with open(log_file_name, "w", encoding="utf-8") as f:
# f.write('"prompt:"\n')
# json.dump(prompt, f, indent=4, ensure_ascii=False)
# if extra_pnginfo is not None:
# f.write('\n\n"extra_pnginfo:"\n')
# json.dump(extra_pnginfo, f, indent=4, ensure_ascii=False)
@staticmethod
def write_prompt(prompt, extra_pnginfo):
log_file_name = os.path.join(os.path.dirname(__file__), "prompt_decode_err.log")
with open(log_file_name, "w", encoding="utf-8") as f:
f.write('"prompt:"\n')
json.dump(prompt, f, indent=4, ensure_ascii=False)
if extra_pnginfo is not None:
f.write('\n\n"extra_pnginfo:"\n')
json.dump(extra_pnginfo, f, indent=4, ensure_ascii=False)
def _is_valid_text(text):
return isinstance(text, str) and text.strip() and text != "undefined"
@staticmethod
def make_annotation_text(
prompt_text: str, negative_text: str, memo_text: str
) -> str:
"""
Generates annotation text.
# # #############################
# # Eagle保存用コメントを作成
# @staticmethod
# def make_annotation_text(gen_info:TGenInfo) -> str:
# positive = gen_info["prompt_text"]["prompt"]
# negative = gen_info["prompt_text"]["negative"]
# tmp_annotation = ""
This method creates text for annotations by combining prompt text, negative text, and memo text.
"""
# if util._is_valid_text(positive):
# tmp_annotation += positive
# if util._is_valid_text(negative):
# tmp_annotation += "\n" if tmp_annotation.strip() else ""
# tmp_annotation += "Negative prompt:" + negative
# if util._is_valid_text(memo_text):
# tmp_annotation += "\n" if tmp_annotation.strip() else ""
# tmp_annotation += "Memo:" + memo_text
# return tmp_annotation
def is_valid_text(text):
return isinstance(text, str) and text.strip() and text != "undefined"
tmp_annotation = ""
if is_valid_text(prompt_text):
tmp_annotation += prompt_text
if is_valid_text(negative_text):
tmp_annotation += "\n" if tmp_annotation.strip() else ""
tmp_annotation += "Negative prompt:" + negative_text
if is_valid_text(memo_text):
tmp_annotation += "\n" if tmp_annotation.strip() else ""
tmp_annotation += "Memo:" + memo_text
return tmp_annotation
# ####################
# 現在時刻のテキストを取得
@staticmethod
def get_datetime_str_msec() -> str:
"""
Gets the current datetime as a string with millisecond precision.
This method generates and returns a string representing the current datetime with millisecond precision.
"""
now = datetime.now()
date_time_str = now.strftime("%Y%m%d_%H%M%S")
return f"{date_time_str}_{now.microsecond:06}"
# #########################
# webp用にExifデータを作成する
# ただしa1111のPNGInfoに表示するが、ワークフローは再現しない
@staticmethod
def get_exif_from_prompt(
emptyExifData: Image.Exif, prompt, extra_pnginfo
) -> Image.Exif:
"""
Generate exif information for webp format from hidden items "prompt" and "extra_pnginfo
"""
imgexif = emptyExifData
def get_exif_from_prompt(img, formated_info:str, extra_pnginfo, prompt):
metadata = img.getexif()
if len(metadata) == 0:
metadata = {"0th": {}, "Exif": {}, "GPS": {}, "Interop": {}, "1st": {}}
# Add PromptString to EXIF position 0x010f (Exif.Image.Make)
if prompt is not None:
promptstr = "".join(json.dumps(prompt))
imgexif[0x010F] = "Prompt:" + promptstr
# Add Workflowstring to EXIF position 0x010e (Exif.Image.ImageDescription)
metadata["0th"][0x0110] = "prompt:{}".format(json.dumps(prompt))
if extra_pnginfo is not None:
workflowmetadata = str()
inital_exif = 0x010f
for x in extra_pnginfo:
workflowmetadata += "".join(json.dumps(extra_pnginfo[x]))
imgexif[0x010E] = "Workflow:" + workflowmetadata
metadata["0th"][inital_exif] = "{}:{}".format(x, json.dumps(extra_pnginfo[x]))
inital_exif -= 1
metadata["Exif"][piexif.ExifIFD.UserComment] = piexif.helper.UserComment.dump(formated_info, "unicode")
return piexif.dump(metadata)
return imgexif
@staticmethod
def get_prompt_tags(prompt_text: str) -> list:
+48
View File
@@ -0,0 +1,48 @@
from dataclasses import dataclass
from typing import Dict, Optional, TypedDict, Union, List
# SendEagleノードの入力パラメーター
class TNodeParams(TypedDict):
format: str
lossless_webp: bool
save_tags: str
filename_template: str
eagle_folder: str
compression: int
positive: Optional[str]
negative: Optional[str]
memo_text: str
prompt: Optional[Dict]
extra_pnginfo: Optional[Dict]
# 生成情報をまとめた辞書
class TGenInfo(TypedDict):
steps: int
sampler_name: str
scheduler: str
cfg: float
seed: int
model_name: str
width: int
height: int
positive: str
negative: str
class TConfig(TypedDict):
ksamplers: list
output_format: str
@dataclass
class D2_TD2Pipe:
ckpt_name: Optional[str] = None
positive: Optional[str] = None
negative: Optional[str] = None
seed: Optional[int] = None
steps: Optional[int] = None
cfg: Optional[float] = None
sampler_name: Optional[str] = None
scheduler: Optional[str] = None
denoise: Optional[float] = None
width: Optional[int] = None
height: Optional[int] = None
+14
View File
@@ -0,0 +1,14 @@
[project]
name = "comfyui-d2-send-eagle"
description = "Send generated images to Eagle"
version = "1.6.1"
license = { file = "LICENSE" }
[project.urls]
Repository = "https://github.com/da2el-ai/ComfyUI-d2-send-eagle"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "da2el"
DisplayName = "ComfyUI-d2-send-eagle"
Icon = ""
+1
View File
@@ -0,0 +1 @@
piexif
Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB