257 lines
8.0 KiB
Python
257 lines
8.0 KiB
Python
import os
|
|
import numpy as np
|
|
import json
|
|
from typing import Dict, Optional, TypedDict, Union, List
|
|
|
|
from PIL import Image
|
|
from PIL.PngImagePlugin import PngInfo
|
|
from datetime import datetime
|
|
|
|
import folder_paths
|
|
|
|
from .modules.util import util
|
|
from .modules.eagle_api import EagleAPI
|
|
from .modules.prompt_info_extractor import PromptInfoExtractor
|
|
|
|
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:
|
|
def __init__(self):
|
|
self.output_dir = folder_paths.get_output_directory()
|
|
self.type = "output"
|
|
self.output_folder = ""
|
|
self.subfolder_name = ""
|
|
self.eagle_api = None
|
|
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"images": ("IMAGE",),
|
|
"format": (["webp", "png"],),
|
|
# webpの時に可逆(lossless)不可逆(lossy)どちらにするか
|
|
"lossless_webp": (
|
|
"BOOLEAN",
|
|
{"default": True, "label_on": "lossless", "label_off": "lossy"},
|
|
),
|
|
# lossy の時の圧縮率
|
|
"compression": (
|
|
"INT",
|
|
{"default": 80, "min": 1, "max": 100, "step": 1},
|
|
),
|
|
# プロンプトをEagleタグに保存するか
|
|
"save_tags": (
|
|
"BOOLEAN",
|
|
{"default": False, "label_on": "save", "label_off": "none"},
|
|
),
|
|
# 保存するファイル名
|
|
"filename_template": (
|
|
"STRING",
|
|
{"multiline": False, "default":"{model}-{width}-{height}-{seed}"},
|
|
),
|
|
# Eagleフォルダ
|
|
"eagle_folder": (
|
|
"STRING",
|
|
{"default": ""}
|
|
),
|
|
|
|
# ポジティブプロンプト
|
|
"positive": (
|
|
"STRING",
|
|
{"forceInput": True, "multiline": True},
|
|
),
|
|
# ネガティブプロンプト
|
|
"negative": (
|
|
"STRING",
|
|
{"forceInput": True, "multiline": True},
|
|
),
|
|
# その他メモ
|
|
"memo_text": (
|
|
"STRING",
|
|
{"multiline": True},
|
|
),
|
|
},
|
|
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
|
}
|
|
|
|
RETURN_TYPES = ()
|
|
FUNCTION = "add_item"
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "D2"
|
|
|
|
# ######################
|
|
# ノード実行でEagleに画像を送る
|
|
def add_item(
|
|
self,
|
|
images,
|
|
format = "webp",
|
|
lossless_webp = False,
|
|
save_tags = True,
|
|
filename_template = "{model}-{width}-{height}-{seed}",
|
|
eagle_folder = "",
|
|
compression = 80,
|
|
positive = "",
|
|
negative = "",
|
|
memo_text = "",
|
|
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,
|
|
}
|
|
|
|
for image in images:
|
|
results.append(self.create_image_object(image, params))
|
|
|
|
return {"ui": {"images": results}}
|
|
|
|
|
|
# ######################
|
|
# イメージオブジェクトを作成
|
|
def create_image_object(self, image, params:ParamsInfo) -> dict:
|
|
normalized_pixels = 255.0 * image.cpu().numpy()
|
|
img = Image.fromarray(np.clip(normalized_pixels, 0, 255).astype(np.uint8))
|
|
|
|
# 画像をローカルに保存
|
|
file_name, file_full_path = self.save_image(img, params)
|
|
|
|
# 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"]
|
|
)
|
|
|
|
if(params["save_tags"]):
|
|
item["tags"] = util.get_prompt_tags(params["positive"])
|
|
|
|
_ret = self.eagle_api.add_item_from_path(data=item, folder_id=folder_id)
|
|
|
|
return {
|
|
"filename": file_name, "subfolder": self.subfolder_name, "type": self.type
|
|
}
|
|
|
|
# ######################
|
|
# 画像をローカルに保存
|
|
def save_image(self, img, params):
|
|
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_full_path = os.path.join(self.output_folder, file_name)
|
|
|
|
exif_data = util.get_exif_from_prompt(
|
|
img.getexif(), params["prompt"], params["extra_pnginfo"]
|
|
)
|
|
|
|
img.save(
|
|
file_full_path,
|
|
quality = params["compression"],
|
|
exif = exif_data,
|
|
lossless = params["lossless_webp"],
|
|
)
|
|
|
|
else:
|
|
# Save png image file
|
|
file_name = self.get_filename(params["filename_template"], 'png', filename_params)
|
|
file_full_path = os.path.join(self.output_folder, file_name)
|
|
|
|
metadata = PngInfo()
|
|
|
|
if params["prompt"] is not None:
|
|
metadata.add_text("prompt", json.dumps(params["prompt"]))
|
|
if params["extra_pnginfo"] is not None:
|
|
for x in params["extra_pnginfo"]:
|
|
metadata.add_text(x, json.dumps(params["extra_pnginfo"][x]))
|
|
|
|
img.save(file_full_path, pnginfo=metadata, compress_level=4)
|
|
|
|
return file_name, file_full_path
|
|
|
|
# ######################
|
|
# 画像保存パスを取得
|
|
def get_output_folder(self):
|
|
subfolder_name = datetime.now().strftime("%Y-%m-%d")
|
|
|
|
# 画像保存用フォルダが無ければ作成
|
|
output_folder = os.path.join(self.output_dir, subfolder_name)
|
|
|
|
if not os.path.exists(output_folder):
|
|
os.makedirs(output_folder)
|
|
|
|
return output_folder, subfolder_name
|
|
|
|
|
|
# ######################
|
|
# ファイルネーム用パラメーターを取得
|
|
def get_filename_params(self, img, params) -> dict:
|
|
width, height = img.size
|
|
gen_data = PromptInfoExtractor(params["prompt"])
|
|
|
|
model = ""
|
|
steps = ""
|
|
seed = ""
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
# ######################
|
|
# ファイルネームを取得
|
|
def get_filename(self, template:str, ext:str, filename_params) -> str:
|
|
base = template.format(
|
|
width = filename_params["width"],
|
|
height = filename_params["height"],
|
|
model = filename_params["model"],
|
|
steps = filename_params["steps"],
|
|
seed = filename_params["seed"],
|
|
)
|
|
|
|
return f"{util.get_datetime_str_msec()}-{base}.{ext}"
|
|
|
|
|