Add Output To Text Converter utility node for flexible data conversion
This commit is contained in:
@@ -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 目录配置
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 📝",
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user