This commit is contained in:
MeeeyoAI
2025-04-13 22:16:27 +08:00
parent 5fb93d2ba8
commit a19a582904
5 changed files with 309 additions and 59 deletions
+14 -6
View File
@@ -1,19 +1,20 @@
from .meyo_node_Computational import StandardSize, CompareInt, FloatToInteger, GenerateNumbers, GetRandomIntegerInRange
from .meyo_node_String import AddPrefixSuffix, ExtractSubstring, ExtractSubstringByIndices, SplitStringByDelimiter, ProcessString, ExtractBeforeAfter, ReplaceNthOccurrence, BatchReplaceStrings, ReplaceMultiple, RandomLineFromText, CheckSubstringPresence, AddPrefixSuffixToLines, ExtractAndCombineLines, FilterLinesBySubstrings, FilterLinesByWordCount, SplitAndExtractText, CountOccurrences, ExtractLinesByIndex, ExtractSpecificLines, RemoveContentBetweenChars, ShuffleTextLines, ConditionalTextOutput, TextConditionCheck, TextConcatenation, ExtractSpecificData, FindFirstLineContent
from .meyo_node_File import FileListAndSuffix, ReadExcelData, WriteExcelData, FindExcelData, ReadExcelRowOrColumnDiff
from .meyo_node_Computational import CompareInt, FloatToInteger, GenerateNumbers, GetRandomIntegerInRange
from .meyo_node_String import SingleTextInput, MultiParamInputNode, AddPrefixSuffix, ExtractSubstring, ExtractSubstringByIndices, SplitStringByDelimiter, ProcessString, ExtractBeforeAfter, ReplaceNthOccurrence, BatchReplaceStrings, ReplaceMultiple, RandomLineFromText, CheckSubstringPresence, AddPrefixSuffixToLines, ExtractAndCombineLines, FilterLinesBySubstrings, FilterLinesByWordCount, SplitAndExtractText, CountOccurrences, ExtractLinesByIndex, ExtractSpecificLines, RemoveContentBetweenChars, ShuffleTextLines, ConditionalTextOutput, TextConditionCheck, TextConcatenation, ExtractSpecificData, FindFirstLineContent
from .meyo_node_File import LoadAndAdjustImage, ImageAdjuster, SaveImagEX, FileListAndSuffix, ReadExcelData, WriteExcelData, FindExcelData, ReadExcelRowOrColumnDiff
from .meyo_node_Functional import GetCurrentTime, SelectionParameter
NODE_CLASS_MAPPINGS = {
#运算型节点:meyo_node_Computational
"StandardSize": StandardSize,
"CompareInt": CompareInt,
"FloatToInteger": FloatToInteger,
"GenerateNumbers": GenerateNumbers,
"GetRandomIntegerInRange": GetRandomIntegerInRange,
#字符串处理:meyo_node_String
"SingleTextInput": SingleTextInput,
"MultiParamInputNode": MultiParamInputNode,
"AddPrefixSuffix": AddPrefixSuffix,
"ExtractSubstring": ExtractSubstring,
"ExtractSubstringByIndices": ExtractSubstringByIndices,
@@ -42,6 +43,9 @@ NODE_CLASS_MAPPINGS = {
"FindFirstLineContent": FindFirstLineContent,
#文件处理:meyo_node_File
"LoadAndAdjustImage": LoadAndAdjustImage,
"ImageAdjuster": ImageAdjuster,
"SaveImagEX": SaveImagEX,
"FileListAndSuffix": FileListAndSuffix,
"ReadExcelData": ReadExcelData,
"WriteExcelData": WriteExcelData,
@@ -58,13 +62,14 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
#运算型节点:meyo_node_Computational
"StandardSize": "重置尺寸(meeeyo.com)",
"CompareInt": "比较数值(meeeyo.com)",
"FloatToInteger": "规范数值(meeeyo.com)",
"GenerateNumbers": "生成范围数组(meeeyo.com)",
"GetRandomIntegerInRange": "范围内随机数(meeeyo.com)",
#字符串处理:meyo_node_String
"SingleTextInput": "文本输入(meeeyo.com)",
"MultiParamInputNode": "多参数输入(meeeyo.com)",
"AddPrefixSuffix": "添加前后缀(meeeyo.com)",
"ExtractSubstring": "提取标签之间(meeeyo.com)",
"ExtractSubstringByIndices": "按数字范围提取(meeeyo.com)",
@@ -93,6 +98,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"FindFirstLineContent": "指定字符行参数(meeeyo.com)",
#文件处理:meyo_node_File
"LoadAndAdjustImage": "加载重置图像(meeeyo.com)",
"ImageAdjuster": "重置图像(meeeyo.com)",
"SaveImagEX": "保存图像(meeeyo.com)",
"FileListAndSuffix": "从路径加载(meeeyo.com)",
"ReadExcelData": "读取表格数据(meeeyo.com)",
"WriteExcelData": "写入表格数据(meeeyo.com)",
@@ -100,7 +108,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ReadExcelRowOrColumnDiff": "读取表格数量差(meeeyo.com)",
#功能型节点:meyo_node_Functional
"GetCurrentTime": "当前时间",
"GetCurrentTime": "当前时间(戳)",
"SelectionParameter": "选择参数",
}
-46
View File
@@ -2,52 +2,6 @@ import math
import random
#======宽度高度调整
class StandardSize:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"width": ("INT",),
"height": ("INT",),
"size_option": (["小尺寸", "中尺寸", "大尺寸"], {"default": "中尺寸"}), # 下拉选择框
},
"optional": {},
}
RETURN_TYPES = ("INT", "INT")
FUNCTION = "resize_image"
CATEGORY = "Meeeyo/Image"
def resize_image(self, width, height, size_option):
# 根据选择的选项调整尺寸像尺寸
if size_option == "小尺寸":
if width / height >= 1.23:
return (768, 512)
elif 0.82 < width / height < 1.23:
return (768, 768)
else:
return (512, 768)
elif size_option == "中尺寸":
if width / height >= 1.23:
return (1216, 832)
elif 0.82 < width / height < 1.23:
return (1216, 1216)
else:
return (832, 1216)
elif size_option == "大尺寸":
if width / height >= 1.23:
return (1600, 1120)
elif 0.82 < width / height < 1.23:
return (1600, 1600)
else:
return (1120, 1600)
#======比较数值
class CompareInt:
def __init__(self):
+246 -1
View File
@@ -1,6 +1,251 @@
import os
import csv
import openpyxl
import os
import pathlib
import folder_paths
import torch
import node_helpers
import numpy as np
from PIL import Image, ImageOps, ImageSequence
#======加载重置图像
class LoadAndAdjustImage:
_color_channels = ["alpha", "red", "green", "blue"]
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f.name for f in pathlib.Path(input_dir).iterdir() if f.is_file()]
return {
"required": {
"image": (sorted(files), {"image_upload": True}),
"max_dimension": ("INT", {"default": 1024, "min": 0, "max": 4096, "step": 8}),
"size_option": (["Custom", "Small", "Medium", "Large"], {"default": "Custom"}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "INT", "INT")
RETURN_NAMES = ("image", "mask", "width", "height")
FUNCTION = "load_image"
CATEGORY = "Meeeyo/File"
def load_image(self, image, max_dimension, size_option):
image_path = folder_paths.get_annotated_filepath(image)
img = node_helpers.pillow(Image.open, image_path)
W, H = img.size
output_images = []
output_masks = []
for frame in ImageSequence.Iterator(img):
frame = node_helpers.pillow(ImageOps.exif_transpose, frame)
if frame.mode == 'P':
frame = frame.convert("RGBA")
elif 'A' in frame.getbands():
frame = frame.convert("RGBA")
if size_option != "Custom":
aspect_ratio = W / H
if size_option == "Small":
if aspect_ratio >= 1.23:
target_width, target_height = 768, 512
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 768, 768
else:
target_width, target_height = 512, 768
elif size_option == "Medium":
if aspect_ratio >= 1.23:
target_width, target_height = 1216, 832
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 1216, 1216
else:
target_width, target_height = 832, 1216
elif size_option == "Large":
if aspect_ratio >= 1.23:
target_width, target_height = 1600, 1120
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 1600, 1600
else:
target_width, target_height = 1120, 1600
image = frame.convert("RGB")
image = ImageOps.fit(image, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5))
if 'A' in frame.getbands():
mask = np.array(frame.getchannel('A')).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)
mask_image = Image.fromarray((mask.numpy() * 255).astype(np.uint8))
mask_image = ImageOps.fit(mask_image, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5))
mask = np.array(mask_image).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)
else:
mask = torch.ones((target_height, target_width), dtype=torch.float32, device="cpu")
else:
ratio = min(max_dimension / W, max_dimension / H)
adjusted_width = round(W * ratio)
adjusted_height = round(H * ratio)
image = frame.convert("RGB")
image = image.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR)
if 'A' in frame.getbands():
mask = np.array(frame.getchannel('A')).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)
mask_image = Image.fromarray((mask.numpy() * 255).astype(np.uint8))
mask_image = mask_image.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR)
mask = np.array(mask_image).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)
else:
mask = torch.ones((adjusted_height, adjusted_width), dtype=torch.float32, device="cpu")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
output_images.append(image)
output_masks.append(mask.unsqueeze(0))
if len(output_images) > 1:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]
if size_option != "Custom":
return (output_image, output_mask, target_width, target_height)
else:
return (output_image, output_mask, adjusted_width, adjusted_height)
@classmethod
def VALIDATE_INPUTS(s, image):
if not folder_paths.exists_annotated_filepath(image):
return f"Invalid image file: {image}"
return True
#======重置图像尺寸
class ImageAdjuster:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"max_dimension": ("INT", {"default": 1024, "min": 0, "max": 4096, "step": 8}),
"size_option": (["Custom", "Small", "Medium", "Large"], {"default": "Custom"}),
}
}
RETURN_TYPES = ("IMAGE", "INT", "INT")
RETURN_NAMES = ("image", "width", "height")
FUNCTION = "process_image"
CATEGORY = "Meeeyo/File"
def process_image(self, image, max_dimension=1024, size_option="Custom"):
input_image = Image.fromarray((image.squeeze(0).numpy() * 255).astype(np.uint8))
W, H = input_image.size
processed_images = []
for frame in [input_image]:
frame = ImageOps.exif_transpose(frame)
if frame.mode == 'P':
frame = frame.convert("RGBA")
elif 'A' in frame.getbands():
frame = frame.convert("RGBA")
if size_option != "Custom":
aspect_ratio = W / H
if size_option == "Small":
if aspect_ratio >= 1.23:
target_width, target_height = 768, 512
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 768, 768
else:
target_width, target_height = 512, 768
elif size_option == "Medium":
if aspect_ratio >= 1.23:
target_width, target_height = 1216, 832
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 1216, 1216
else:
target_width, target_height = 832, 1216
elif size_option == "Large":
if aspect_ratio >= 1.23:
target_width, target_height = 1600, 1120
elif 0.82 < aspect_ratio < 1.23:
target_width, target_height = 1600, 1600
else:
target_width, target_height = 1120, 1600
processed_image = frame.convert("RGB")
processed_image = ImageOps.fit(processed_image, (target_width, target_height), method=Image.Resampling.BILINEAR, centering=(0.5, 0.5))
else:
ratio = min(max_dimension / W, max_dimension / H)
adjusted_width = round(W * ratio)
adjusted_height = round(H * ratio)
processed_image = frame.convert("RGB")
processed_image = processed_image.resize((adjusted_width, adjusted_height), Image.Resampling.BILINEAR)
processed_image = np.array(processed_image).astype(np.float32) / 255.0
processed_image = torch.from_numpy(processed_image)[None,]
processed_images.append(processed_image)
output_image = torch.cat(processed_images, dim=0) if len(processed_images) > 1 else processed_images[0]
if size_option != "Custom":
return (output_image, target_width, target_height)
else:
return (output_image, adjusted_width, adjusted_height)
#======保存图像
class SaveImagEX:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"save_path": ("STRING", {"default": "./output"}),
"image_name": ("STRING", {"default": "ComfyUI"}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "save_image"
OUTPUT_NODE = True
CATEGORY = "Meeeyo/File"
def save_image(self, image, save_path, image_name):
if not isinstance(image, torch.Tensor):
raise ValueError("Invalid image tensor format")
if save_path == "./output":
save_path = self.output_dir
elif not os.path.isabs(save_path):
save_path = os.path.join(self.output_dir, save_path)
os.makedirs(save_path, exist_ok=True)
base_name = os.path.splitext(image_name)[0]
batch_size = image.shape[0]
channel_to_mode = {1: "L", 3: "RGB", 4: "RGBA"}
for i in range(batch_size):
filename = f"{base_name}.png" if batch_size == 1 else f"{base_name}_{i:05d}.png"
full_path = os.path.join(save_path, filename)
single_image = image[i].cpu().numpy()
single_image = (single_image * 255).astype('uint8')
channels = single_image.shape[2]
if channels not in channel_to_mode:
raise ValueError(f"Unsupported channel number: {channels}")
mode = channel_to_mode[channels]
if channels == 1:
single_image = single_image[:, :, 0]
pil_image = Image.fromarray(single_image, mode)
pil_image.save(full_path, format="PNG", compress_level=0)
return (image,)
#======文件路径和后缀统计
+8 -6
View File
@@ -1,5 +1,5 @@
from datetime import datetime
import ntplib
import datetime
import time
#======获取当前时间
class GetCurrentTime:
@@ -17,7 +17,7 @@ class GetCurrentTime:
},
}
RETURN_TYPES = ("STRING",)
RETURN_TYPES = ("STRING", "INT") # 新增 INTEGER 类型的返回值
FUNCTION = "get_current_time"
CATEGORY = "Meeeyo/Functional"
@@ -26,13 +26,15 @@ class GetCurrentTime:
def get_current_time(self, prefix="", any=None):
# Get the current system time
current_time = datetime.now()
current_time = datetime.datetime.now()
# Format the time as a string
formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
# Include the prefix in the output
result = f"{prefix}{formatted_time}"
# Return the formatted time with prefix as a string
return (result,)
# Get the current timestamp as an integer with milliseconds precision
timestamp = int(round(time.time() * 1000)) # 获取当前时间戳的整数部分并精确到毫秒
# Return both the formatted time with prefix and the timestamp
return (result, timestamp) # 返回两个值:字符串和整数
#======选择性别和模板
+41
View File
@@ -7,6 +7,47 @@ import math
import re
#======多参数输入
class SingleTextInput:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": "", "multiline": True}),
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "process_input"
CATEGORY = "Meeeyo/Text"
OUTPUT_NODE = False
def process_input(self, text):
return (text,)
#======多参数输入
class MultiParamInputNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text1": ("STRING", {"default": "", "multiline": True}), # 第一个多行文本输入框
"text2": ("STRING", {"default": "", "multiline": True}), # 第二个多行文本输入框
"int1": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第一个整数输入框
"int2": ("INT", {"default": 0, "min": -1000000, "max": 1000000}), # 第二个整数输入框
}
}
RETURN_TYPES = ("STRING", "STRING", "INT", "INT") # 返回两个字符串和两个整数
FUNCTION = "process_inputs"
CATEGORY = "Meeeyo/String"
OUTPUT_NODE = False
def process_inputs(self, text1, text2, int1, int2):
return (text1, text2, int1, int2)
#======添加前后缀
class AddPrefixSuffix:
def __init__(self):