Files
LAOGOU-666-Comfyui-LG_Group…/py/trans.py
T

751 lines
28 KiB
Python

from server import PromptServer
import os
import sys
import time
import torch
import numpy as np
from PIL import Image
import folder_paths
import random
from nodes import SaveImage
import json
from comfy.cli_args import args
from PIL.PngImagePlugin import PngInfo
import psutil
import ctypes
from ctypes import wintypes
class AnyType(str):
"""用于表示任意类型的特殊类,在类型比较时总是返回相等"""
def __eq__(self, _) -> bool:
return True
def __ne__(self, __value: object) -> bool:
return False
any = AnyType("*")
class MemoryCleanup:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"anything": (any, {}),
"offload_model": ("BOOLEAN", {"default": True}),
"offload_cache": ("BOOLEAN", {"default": True}),
},
"optional": {},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
}
}
RETURN_TYPES = (any,)
RETURN_NAMES = ("output",)
OUTPUT_NODE = True
FUNCTION = "empty_cache"
CATEGORY = "Memory Management"
def empty_cache(self, anything, offload_model, offload_cache, unique_id=None, extra_pnginfo=None):
try:
if offload_model:
import comfy.model_management
comfy.model_management.unload_all_models()
if offload_cache:
import torch
import gc
torch.cuda.empty_cache()
gc.collect()
except Exception as e:
print(f"内存清理出错: {str(e)}")
import traceback
print(traceback.format_exc())
return (anything,)
class RAMCleanup:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"anything": (any, {}),
"clean_file_cache": ("BOOLEAN", {"default": True, "label": "清理文件缓存"}),
"clean_processes": ("BOOLEAN", {"default": True, "label": "清理进程内存"}),
"clean_dlls": ("BOOLEAN", {"default": True, "label": "清理未使用DLL"}),
"retry_times": ("INT", {
"default": 3,
"min": 1,
"max": 10,
"step": 1,
"label": "重试次数"
}),
},
"optional": {},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
}
}
RETURN_TYPES = (any,)
RETURN_NAMES = ("output",)
OUTPUT_NODE = True
FUNCTION = "clean_ram"
CATEGORY = "Memory Management"
def get_ram_usage(self):
memory = psutil.virtual_memory()
return memory.percent, memory.available / (1024 * 1024)
def clean_ram(self, anything, clean_file_cache, clean_processes, clean_dlls, retry_times, unique_id=None, extra_pnginfo=None):
try:
current_usage, available_mb = self.get_ram_usage()
print(f"开始清理RAM - 当前使用率: {current_usage:.1f}%, 可用: {available_mb:.1f}MB")
for attempt in range(retry_times):
if clean_file_cache:
try:
ctypes.windll.kernel32.SetSystemFileCacheSize(-1, -1, 0)
except Exception as e:
print(f"清理文件缓存失败: {str(e)}")
if clean_processes:
cleaned_processes = 0
for process in psutil.process_iter(['pid', 'name']):
try:
handle = ctypes.windll.kernel32.OpenProcess(
wintypes.DWORD(0x001F0FFF),
wintypes.BOOL(False),
wintypes.DWORD(process.info['pid'])
)
ctypes.windll.psapi.EmptyWorkingSet(handle)
ctypes.windll.kernel32.CloseHandle(handle)
cleaned_processes += 1
except:
continue
if clean_dlls:
try:
ctypes.windll.kernel32.SetProcessWorkingSetSize(-1, -1, -1)
except Exception as e:
print(f"释放DLL失败: {str(e)}")
time.sleep(1)
current_usage, available_mb = self.get_ram_usage()
print(f"清理后内存使用率: {current_usage:.1f}%, 可用: {available_mb:.1f}MB")
print(f"清理完成 - 最终内存使用率: {current_usage:.1f}%, 可用: {available_mb:.1f}MB")
except Exception as e:
print(f"RAM清理过程出错: {str(e)}")
return (anything,)
class LG_ImageSender:
def __init__(self):
self.output_dir = folder_paths.get_temp_directory()
self.type = "temp"
self.compress_level = 1
self.accumulated_results = [] # 添加积累结果列表
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"tooltip": "要发送的图像"}),
"filename_prefix": ("STRING", {"default": "lg_send"}),
"link_id": ("INT", {"default": 1, "min": 0, "max": sys.maxsize, "step": 1, "tooltip": "发送端连接ID"}),
"accumulate": ("BOOLEAN", {"default": False, "tooltip": "开启后将累积所有图像一起发送"}) # 改名为accumulate
},
"optional": {
"masks": ("MASK", {"tooltip": "要发送的遮罩"})
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "save_images"
CATEGORY = "🎈LAOGOU"
INPUT_IS_LIST = True
@classmethod
def IS_CHANGED(s, images, filename_prefix, link_id, accumulate, masks=None, prompt=None, extra_pnginfo=None):
if isinstance(accumulate, list):
accumulate = accumulate[0]
if accumulate:
return float("NaN")
# 非积累模式下计算hash
hash_value = hash(str(images) + str(masks))
return hash_value
def save_images(self, images, filename_prefix, link_id, accumulate, masks=None, prompt=None, extra_pnginfo=None):
timestamp = int(time.time() * 1000)
results = list()
filename_prefix = filename_prefix[0] if isinstance(filename_prefix, list) else filename_prefix
link_id = link_id[0] if isinstance(link_id, list) else link_id
accumulate = accumulate[0] if isinstance(accumulate, list) else accumulate
for idx, image_batch in enumerate(images):
try:
image = image_batch.squeeze()
rgb_image = Image.fromarray(np.clip(255. * image.cpu().numpy(), 0, 255).astype(np.uint8))
if masks is not None and idx < len(masks):
mask = masks[idx].squeeze()
mask_img = Image.fromarray(np.clip(255. * (1 - mask.cpu().numpy()), 0, 255).astype(np.uint8))
else:
mask_img = Image.new('L', rgb_image.size, 255)
r, g, b = rgb_image.convert('RGB').split()
rgba_image = Image.merge('RGBA', (r, g, b, mask_img))
filename = f"{filename_prefix}_{link_id}_{timestamp}_{idx}.png"
file_path = os.path.join(self.output_dir, filename)
rgba_image.save(file_path, compress_level=self.compress_level)
result = {
"filename": filename,
"subfolder": "",
"type": self.type
}
results.append(result)
if accumulate:
self.accumulated_results.append(result)
except Exception as e:
print(f"[ImageSender] 处理图像 {idx+1} 时出错: {str(e)}")
import traceback
traceback.print_exc()
continue
send_results = self.accumulated_results if accumulate else results
if send_results:
print(f"[ImageSender] 发送 {len(send_results)} 张图像")
PromptServer.instance.send_sync("img-send", {
"link_id": link_id,
"images": send_results
})
if not accumulate:
self.accumulated_results = []
return { "ui": { "images": send_results } }
class LG_ImageReceiver:
@classmethod
def INPUT_TYPES(s):
temp_dir = folder_paths.get_temp_directory()
files = [f for f in os.listdir(temp_dir) if os.path.isfile(os.path.join(temp_dir, f))]
return {
"required": {
"image": ("STRING", {"default": "", "multiline": False, "tooltip": "多个文件名用逗号分隔"}),
"link_id": ("INT", {"default": 1, "min": 0, "max": sys.maxsize, "step": 1, "tooltip": "发送端连接ID"}),
}
}
CATEGORY = "🎈LAOGOU"
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("images", "masks")
OUTPUT_IS_LIST = (True, True)
FUNCTION = "load_image"
def load_image(self, image, link_id):
image_files = [x.strip() for x in image.split(',') if x.strip()]
print(f"[ImageReceiver] 加载图像: {image_files}")
output_images = []
output_masks = []
if not image_files:
empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32)
empty_mask = torch.zeros((1, 64, 64), dtype=torch.float32)
return ([empty_image], [empty_mask])
temp_dir = folder_paths.get_temp_directory()
for img_file in image_files:
try:
img_path = os.path.join(temp_dir, img_file)
if not os.path.exists(img_path):
continue
# 加载RGBA图像
img = Image.open(img_path)
if img.mode == 'RGBA':
# 分离RGB和Alpha通道
r, g, b, a = img.split()
# 合并RGB通道
rgb_image = Image.merge('RGB', (r, g, b))
# 转换为tensor
image = np.array(rgb_image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
# 转换alpha为mask,并确保维度是 (B, H, W)
mask = np.array(a).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)[None,] # 添加batch维度
# 反转遮罩值
mask = 1.0 - mask
else:
# 如果不是RGBA,按原来的方式处理
image = np.array(img.convert('RGB')).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
mask = torch.zeros((1, image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
output_images.append(image)
output_masks.append(mask)
except Exception as e:
print(f"[ImageReceiver] 处理文件 {img_file} 时出错: {str(e)}")
import traceback
traceback.print_exc()
continue
return (output_images, output_masks)
class ImageListSplitter:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"indices": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "输入要提取的图片索引,用逗号分隔,如:0,1,3,4"
}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "split_images"
CATEGORY = "🎈LAOGOU"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,) # (images,)
def split_images(self, images, indices):
try:
# 解析索引字符串
try:
if isinstance(indices, list):
indices = indices[0] if indices else ""
indices = [int(idx.strip()) for idx in indices.split(',') if idx.strip()]
except ValueError:
print("[ImageSplitter] 索引格式错误,请使用逗号分隔的数字")
return ([],)
# 确保images是列表
if not isinstance(images, list):
images = [images]
# 处理批量图片的情况
if len(images) == 1 and len(images[0].shape) == 4: # [B, H, W, C]
batch_images = images[0]
total_images = batch_images.shape[0]
print(f"[ImageSplitter] 检测到批量图片,总数: {total_images}")
selected_images = []
for idx in indices:
if 0 <= idx < total_images:
# 保持批次维度,使用unsqueeze确保维度为 [1, H, W, C]
img = batch_images[idx].unsqueeze(0)
selected_images.append(img)
print(f"[ImageSplitter] 从批量中选择第 {idx} 张图片")
else:
print(f"[ImageSplitter] 索引 {idx} 超出批量范围 0-{total_images-1}")
if not selected_images:
return ([],)
return (selected_images,)
# 处理图片列表的情况
total_images = len(images)
print(f"[ImageSplitter] 检测到图片列表,总数: {total_images}")
if total_images == 0:
print("[ImageSplitter] 没有输入图片")
return ([],)
selected_images = []
for idx in indices:
if 0 <= idx < total_images:
selected_image = images[idx]
# 确保输出维度为 [1, H, W, C]
if len(selected_image.shape) == 3: # [H, W, C]
selected_image = selected_image.unsqueeze(0)
selected_images.append(selected_image)
print(f"[ImageSplitter] 从列表中选择第 {idx} 张图片")
else:
print(f"[ImageSplitter] 索引 {idx} 超出列表范围 0-{total_images-1}")
if not selected_images:
return ([],)
return (selected_images,)
except Exception as e:
print(f"[ImageSplitter] 处理出错: {str(e)}")
return ([],)
class MaskListSplitter:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"masks": ("MASK",),
"indices": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "输入要提取的遮罩索引,用逗号分隔,如:0,1,3,4"
}),
},
}
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("masks",)
FUNCTION = "split_masks"
CATEGORY = "🎈LAOGOU"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,) # (masks,)
def split_masks(self, masks, indices):
try:
# 解析索引字符串
try:
if isinstance(indices, list):
indices = indices[0] if indices else ""
indices = [int(idx.strip()) for idx in indices.split(',') if idx.strip()]
except ValueError:
print("[MaskSplitter] 索引格式错误,请使用逗号分隔的数字")
return ([],)
# 确保masks是列表
if not isinstance(masks, list):
masks = [masks]
# 处理批量遮罩的情况
if len(masks) == 1 and len(masks[0].shape) == 3: # [B, H, W]
batch_masks = masks[0]
total_masks = batch_masks.shape[0]
print(f"[MaskSplitter] 检测到批量遮罩,总数: {total_masks}")
selected_masks = []
for idx in indices:
if 0 <= idx < total_masks:
selected_masks.append(batch_masks[idx].unsqueeze(0))
print(f"[MaskSplitter] 从批量中选择第 {idx} 个遮罩")
else:
print(f"[MaskSplitter] 索引 {idx} 超出批量范围 0-{total_masks-1}")
if not selected_masks:
return ([],)
return (selected_masks,)
# 处理遮罩列表的情况
total_masks = len(masks)
print(f"[MaskSplitter] 检测到遮罩列表,总数: {total_masks}")
if total_masks == 0:
print("[MaskSplitter] 没有输入遮罩")
return ([],)
selected_masks = []
for idx in indices:
if 0 <= idx < total_masks:
selected_mask = masks[idx]
if len(selected_mask.shape) == 2: # [H, W]
selected_mask = selected_mask.unsqueeze(0)
elif len(selected_mask.shape) != 3: # 不是 [B, H, W]
print(f"[MaskSplitter] 不支持的遮罩维度: {selected_mask.shape}")
continue
selected_masks.append(selected_mask)
print(f"[MaskSplitter] 从列表中选择第 {idx} 个遮罩")
else:
print(f"[MaskSplitter] 索引 {idx} 超出列表范围 0-{total_masks-1}")
if not selected_masks:
return ([],)
return (selected_masks,)
except Exception as e:
print(f"[MaskSplitter] 处理出错: {str(e)}")
return ([],)
class ImageListRepeater:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE",),
"repeat_times": ("INT", {
"default": 1,
"min": 1,
"max": 100,
"step": 1,
"tooltip": "每张图片重复的次数"
}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "repeat_images"
CATEGORY = "🎈LAOGOU"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def repeat_images(self, images, repeat_times):
try:
# 处理 repeat_times 参数
if isinstance(repeat_times, list):
repeat_times = repeat_times[0] if repeat_times else 1
# 确保images是列表
if not isinstance(images, list):
images = [images]
if len(images) == 0:
print("[ImageRepeater] 没有输入图片")
return ([],)
# 创建重复后的图片列表
repeated_images = []
for idx, img in enumerate(images):
for _ in range(int(repeat_times)): # 确保 repeat_times 是整数
repeated_images.append(img)
print(f"[ImageRepeater] 图片 {idx} 重复 {repeat_times} 次")
print(f"[ImageRepeater] 输入 {len(images)} 张图片,输出 {len(repeated_images)} 张图片")
return (repeated_images,)
except Exception as e:
print(f"[ImageRepeater] 处理出错: {str(e)}")
return ([],)
class MaskListRepeater:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"masks": ("MASK",),
"repeat_times": ("INT", {
"default": 1,
"min": 1,
"max": 100,
"step": 1,
"tooltip": "每张遮罩重复的次数"
}),
},
}
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("masks",)
FUNCTION = "repeat_masks"
CATEGORY = "🎈LAOGOU"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def repeat_masks(self, masks, repeat_times):
try:
# 处理 repeat_times 参数
if isinstance(repeat_times, list):
repeat_times = repeat_times[0] if repeat_times else 1
# 确保masks是列表
if not isinstance(masks, list):
masks = [masks]
if len(masks) == 0:
print("[MaskRepeater] 没有输入遮罩")
return ([],)
# 创建重复后的遮罩列表
repeated_masks = []
for idx, mask in enumerate(masks):
for _ in range(int(repeat_times)): # 确保 repeat_times 是整数
repeated_masks.append(mask)
print(f"[MaskRepeater] 遮罩 {idx} 重复 {repeat_times} 次")
print(f"[MaskRepeater] 输入 {len(masks)} 个遮罩,输出 {len(repeated_masks)} 个遮罩")
return (repeated_masks,)
except Exception as e:
print(f"[MaskRepeater] 处理出错: {str(e)}")
return ([],)
class LG_FastPreview(SaveImage):
def __init__(self):
self.output_dir = folder_paths.get_temp_directory()
self.type = "temp"
self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
@classmethod
def INPUT_TYPES(s):
return {"required": {
"images": ("IMAGE", ),
"format": (["PNG", "JPEG", "WEBP"], {"default": "JPEG"}),
"quality": ("INT", {"default": 95, "min": 1, "max": 100, "step": 1}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "save_images"
CATEGORY = "image"
DESCRIPTION = "快速预览图像,支持多种格式和质量设置"
def save_images(self, images, format="JPEG", quality=95, prompt=None, extra_pnginfo=None):
filename_prefix = "preview"
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
for (batch_number, image) in enumerate(images):
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
save_kwargs = {}
if format == "PNG":
file_extension = ".png"
compress_level = int(9 * (1 - quality/100))
save_kwargs["compress_level"] = compress_level
if not args.disable_metadata:
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
save_kwargs["pnginfo"] = metadata
elif format == "JPEG":
file_extension = ".jpg"
save_kwargs["quality"] = quality
save_kwargs["optimize"] = True
else:
file_extension = ".webp"
save_kwargs["quality"] = quality
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
file = f"{filename_with_batch_num}_{counter:05}_{file_extension}"
img.save(os.path.join(full_output_folder, file), format=format, **save_kwargs)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
counter += 1
return { "ui": { "images": results } }
class LG_AccumulatePreview(SaveImage):
def __init__(self):
self.output_dir = folder_paths.get_temp_directory()
self.type = "temp"
self.prefix_append = "_acc_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
self.accumulated_images = []
self.accumulated_masks = []
self.counter = 0
@classmethod
def INPUT_TYPES(s):
return {"required": {
"images": ("IMAGE", ),
},
"optional": {
"mask": ("MASK",),
},
"hidden": {
"prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO",
"unique_id": "UNIQUE_ID"
},
}
RETURN_TYPES = ("IMAGE", "MASK", "INT")
RETURN_NAMES = ("images", "masks", "image_count")
FUNCTION = "accumulate_images"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True, True, False)
CATEGORY = "🎈LAOGOU"
DESCRIPTION = "累计图像预览"
def accumulate_images(self, images, mask=None, prompt=None, extra_pnginfo=None, unique_id=None):
# 添加调试信息
print(f"[AccumulatePreview] accumulate_images - 当前累积图片数量: {len(self.accumulated_images)}")
print(f"[AccumulatePreview] accumulate_images - 新输入图片数量: {len(images)}")
print(f"[AccumulatePreview] accumulate_images - unique_id: {unique_id}")
filename_prefix = "accumulate"
filename_prefix += self.prefix_append
full_output_folder, filename, _, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
file = f"{filename}_{self.counter:05}.png"
img.save(os.path.join(full_output_folder, file), format="PNG")
if len(image.shape) == 3:
image = image.unsqueeze(0)
self.accumulated_images.append({
"image": image,
"info": {
"filename": file,
"subfolder": subfolder,
"type": self.type
}
})
if mask is not None:
if len(mask.shape) == 2:
mask = mask.unsqueeze(0)
self.accumulated_masks.append(mask)
else:
self.accumulated_masks.append(None)
self.counter += 1
if not self.accumulated_images:
return {"ui": {"images": []}, "result": ([], [], 0)}
accumulated_tensors = []
for item in self.accumulated_images:
img = item["image"]
if len(img.shape) == 3: # [H, W, C]
img = img.unsqueeze(0) # 变成 [1, H, W, C]
accumulated_tensors.append(img)
accumulated_masks = [m for m in self.accumulated_masks if m is not None]
ui_images = [item["info"] for item in self.accumulated_images]
return {
"ui": {"images": ui_images},
"result": (accumulated_tensors, accumulated_masks, len(self.accumulated_images))
}