Add Output To Text Converter utility node for flexible data conversion

This commit is contained in:
jammyfu
2025-01-26 21:41:08 +08:00
parent 9dec65abf1
commit 44bee9415f
4 changed files with 223 additions and 28 deletions
+3
View File
@@ -29,6 +29,7 @@ from .modules.switch.switch_node import MaskSwitch
from .modules.switch.switch_node import LatentSwitch
from .modules.web.web_image_loader import WebImageLoader
from .modules.images.image_base64_converter import ImageToBase64
from .modules.utils.output_converter import OutputToTextConverter
@@ -54,6 +55,7 @@ NODE_CLASS_MAPPINGS = {
"PaintingCoder::MaskSwitch": MaskSwitch,
"PaintingCoder::LatentSwitch": LatentSwitch,
"PaintingCoder::WebImageLoader": WebImageLoader,
"PaintingCoder::OutputToTextConverter": OutputToTextConverter,
}
# 节点显示名称映射
@@ -77,6 +79,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"PaintingCoder::MaskSwitch": "Mask Switch 🔄",
"PaintingCoder::LatentSwitch": "Latent Switch 🔄",
"PaintingCoder::WebImageLoader": "Web Image Loader 🌐(URL Or Base64)",
"PaintingCoder::OutputToTextConverter": "Output To Text Converter 📝",
}
# Web 目录配置
+100 -26
View File
@@ -11,19 +11,62 @@ class ImageSizeCreatorPlus(ImageSizeCreator):
@classmethod
def INPUT_TYPES(s):
"""动态获取输入类型"""
return {
"required": {
"mode": (["Landscape", "Portrait", "Square"],),
"style": (["SDXL", "Midjourney"],),
"resolution": (s.get_resolution_options(),),
"resolution": (s.get_resolution_options(),), # 使用动态方法获取选项
"scale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}),
},
}
RETURN_TYPES = ("STRING", "INT", "INT")
RETURN_NAMES = ("resolution", "width", "height")
FUNCTION = "create_size"
CATEGORY = "🎨Painting👓Coder/🖼️Image"
@classmethod
def get_sdxl_resolution_options(cls):
"""获取SDXL的分辨率选项"""
sdxl_resolutions = [
(1024, 1024), # 1:1
(1152, 896), # 9:7
(896, 1152), # 7:9
(1216, 832), # 3:2
(832, 1216), # 2:3
(1344, 768), # 7:4
(768, 1344), # 4:7
(1536, 640), # 12:5
(640, 1536), # 5:12
]
return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in sdxl_resolutions]
@classmethod
def get_midjourney_resolution_options(cls):
"""获取Midjourney的分辨率选项"""
mj_resolutions = [
(768, 1536), # 1:2
(816, 1456), # 9:16
(896, 1344), # 2:3
(928, 1232), # 3:4
(992, 1200), # 5:6
(1024, 1024), # 1:1
(1200, 992), # 6:5
(1232, 928), # 4:3
(1344, 896), # 3:2
(1456, 816), # 16:9
(1536, 768), # 2:1
]
return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in mj_resolutions]
@classmethod
def get_resolution_options(cls):
"""根据选择的风格返回对应的分辨率选项"""
return cls.get_sdxl_resolutions() # 默认返回SDXL的分辨率
"""获取所有可用的分辨率选项"""
# 合并SDXL和Midjourney的所有选项
sdxl_options = cls.get_sdxl_resolution_options()
mj_options = cls.get_midjourney_resolution_options()
return list(set(sdxl_options + mj_options)) # 使用set去重
@staticmethod
def get_sdxl_resolutions():
@@ -57,7 +100,7 @@ class ImageSizeCreatorPlus(ImageSizeCreator):
(1456, 816), # 16:9
(1536, 768), # 2:1
]
return [(w, h) for w, h in mj_resolutions]
return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in mj_resolutions]
@staticmethod
def get_aspect_ratio_string(width, height):
@@ -94,44 +137,75 @@ class ImageSizeCreatorPlus(ImageSizeCreator):
all_ratios = {**sdxl_ratios, **mj_ratios}
return all_ratios.get((width, height), "1:1")
def get_valid_resolutions(self, style):
"""根据风格获取有效的分辨率选项"""
if style == "Midjourney":
return self.get_midjourney_resolution_options()
return self.get_sdxl_resolution_options()
def create_size(self, mode, style, resolution, scale_factor=1.0):
"""创建图像尺寸,支持不同风格"""
try:
print(f"[ImageSizeCreatorPlus] Creating size with style: {style}")
return super().create_size(mode, resolution, scale_factor)
# 获取当前风格的有效分辨率选项
valid_resolutions = self.get_valid_resolutions(style)
# 检查分辨率是否在有效选项中
if resolution not in valid_resolutions:
# 如果不在有效选项中,使用默认分辨率
resolution = valid_resolutions[0]
print(f"[ImageSizeCreatorPlus] Invalid resolution for {style}, using default: {resolution}")
# 从分辨率字符串中提取宽度和高度
import re
match = re.search(r'\((\d+)x(\d+)\)', resolution)
if not match:
raise ValueError(f"Invalid resolution format: {resolution}")
width = int(match.group(1))
height = int(match.group(2))
# 应用缩放因子
width = int(width * scale_factor)
height = int(height * scale_factor)
# 确保宽高为8的倍数
width = (width // 8) * 8
height = (height // 8) * 8
return (str(resolution), width, height)
except Exception as e:
print(f"Error in ImageSizeCreatorPlus: {str(e)}")
return ("1:1 (1024x1024)", 1024, 1024)
def validate_inputs(self, **kwargs):
"""验证输入参数"""
style = kwargs.get("style", "SDXL")
resolution = kwargs.get("resolution", "")
valid_resolutions = self.get_valid_resolutions(style)
if resolution not in valid_resolutions:
return {"resolution": valid_resolutions[0]}
return {}
class ImageLatentCreatorPlus(ImageLatentCreator, ImageSizeCreatorPlus):
"""增强版图像潜空间创建器"""
@classmethod
def INPUT_TYPES(s):
base_inputs = super().INPUT_TYPES()
base_inputs = ImageLatentCreator.INPUT_TYPES()
base_inputs["required"]["style"] = (["SDXL", "Midjourney"],)
base_inputs["required"]["resolution"] = (s.get_resolution_options(),) # 使用动态方法
return base_inputs
def create_latent(self, mode, style, resolution, batch_size=1, scale_factor=1.0, extra_pnginfo=None):
"""创建潜空间,支持不同风格"""
try:
print(f"[ImageLatentCreatorPlus] Creating latent with style: {style}")
resolution_list, width, height = self.create_size(mode, style, resolution, scale_factor)
batch_size = max(1, int(batch_size))
samples = torch.zeros([batch_size, 4, height // 8, width // 8])
latent = {
"samples": samples,
"batch_size": batch_size,
"batch_index": list(range(batch_size))
}
return (latent, resolution_list, width, height, batch_size)
except Exception as e:
print(f"Error in ImageLatentCreatorPlus: {str(e)}")
raise e
RETURN_TYPES = ("LATENT", "STRING", "INT", "INT", "INT")
RETURN_NAMES = ("latent", "resolution", "width", "height", "batch_size")
def validate_inputs(self, **kwargs):
"""验证输入参数"""
return super().validate_inputs(**kwargs)
# 注册节点
NODE_CLASS_MAPPINGS = {
+117
View File
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
# Filename: output_converter.py
# Developer: jammyfu
# Category: 🎨Painting👓Coder/🛠️Utils
import json
import torch
import numpy as np
from PIL import Image
class AnyType(str):
"""自定义任意类型"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
class OutputToTextConverter:
"""通用输出转文本转换器"""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"any_input": (any_type,), # 使用自定义的any_type
"format": (["Auto", "JSON", "Plain Text", "Raw"],),
"indent": ("INT", {"default": 2, "min": 0, "max": 8, "step": 1}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "convert"
CATEGORY = "🎨Painting👓Coder/🛠️Utils"
def convert(self, any_input, format="Auto", indent=2):
"""将任何输入转换为文本"""
try:
# 如果输入为None
if any_input is None:
return ("None",)
# 如果输入已经是字符串
if isinstance(any_input, str):
return (any_input,)
# 根据不同类型进行转换
if format == "Auto":
text = self._auto_convert(any_input, indent)
elif format == "JSON":
text = self._to_json(any_input, indent)
elif format == "Plain Text":
text = str(any_input)
else: # Raw
text = repr(any_input)
print(f"[OutputConverter] Converted {type(any_input).__name__} to text")
return (text,)
except Exception as e:
print(f"[OutputConverter] Error converting output: {str(e)}")
return (f"Error: {str(e)}",)
def _auto_convert(self, obj, indent):
"""自动选择最合适的转换方式"""
# 处理常见的数据类型
if isinstance(obj, (dict, list, tuple, set)):
return self._to_json(obj, indent)
elif isinstance(obj, (int, float, bool)):
return str(obj)
elif isinstance(obj, (torch.Tensor, np.ndarray)):
return self._tensor_to_text(obj)
elif isinstance(obj, Image.Image):
return self._image_to_text(obj)
else:
# 尝试JSON序列化,如果失败则使用str
try:
return self._to_json(obj, indent)
except:
return str(obj)
def _to_json(self, obj, indent):
"""转换为JSON格式"""
def default_converter(o):
if isinstance(o, (torch.Tensor, np.ndarray)):
return self._tensor_to_text(o)
elif isinstance(o, Image.Image):
return self._image_to_text(o)
elif hasattr(o, '__dict__'):
return o.__dict__
return str(o)
return json.dumps(obj, indent=indent, default=default_converter)
def _tensor_to_text(self, tensor):
"""转换张量为文本描述"""
if isinstance(tensor, np.ndarray):
shape = tensor.shape
dtype = str(tensor.dtype)
else: # torch.Tensor
shape = tuple(tensor.size())
dtype = str(tensor.dtype)
return f"Tensor(shape={shape}, dtype={dtype})"
def _image_to_text(self, image):
"""转换图像为文本描述"""
return f"Image(size={image.size}, mode={image.mode})"
# 注册节点
NODE_CLASS_MAPPINGS = {
"PaintingCoder::OutputToTextConverter": OutputToTextConverter,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PaintingCoder::OutputToTextConverter": "Output To Text 📝",
}
+3 -2
View File
@@ -41,10 +41,11 @@ const midjourneyResolutionOptions = {
]
};
// 更新分辨率选项的函数
// 更新分辨率选项
function updatePlusResolutionOptions(node, mode, style, triggerUpdate = true) {
console.log("[ImageSizeCreatorPlus] Updating resolution options for mode:", mode, "style:", style);
// 找到分辨率widget
const resolutionWidget = node.widgets?.find(w => w.name === "resolution");
if (!resolutionWidget) {
console.warn("[ImageSizeCreatorPlus] Resolution widget not found");
@@ -80,7 +81,7 @@ function updatePlusResolutionOptions(node, mode, style, triggerUpdate = true) {
}
}
// 设置Plus节点的函数
// 设置Plus节点
function setupPlusSizeNode(nodeType, nodeData, app) {
// 创建节点时的处理
const onNodeCreated = nodeType.prototype.onNodeCreated;