first commit
This commit is contained in:
+220
@@ -0,0 +1,220 @@
|
||||
{
|
||||
"last_node_id": 15,
|
||||
"last_link_id": 14,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 15,
|
||||
"type": "CleanFolders",
|
||||
"pos": [
|
||||
1179.648193359375,
|
||||
803.1049194335938
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
54
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "output_path",
|
||||
"type": "STRING",
|
||||
"link": 13,
|
||||
"widget": {
|
||||
"name": "output_path"
|
||||
},
|
||||
"label": "output_path"
|
||||
},
|
||||
{
|
||||
"name": "input_path",
|
||||
"type": "STRING",
|
||||
"link": 14,
|
||||
"widget": {
|
||||
"name": "input_path"
|
||||
},
|
||||
"label": "input_path"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "CleanFolders"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "CompressImages",
|
||||
"pos": [
|
||||
894.0198974609375,
|
||||
745.897705078125
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 9,
|
||||
"label": "images"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "temp_dir",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "temp_dir"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CompressImages"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "ImageInvert",
|
||||
"pos": [
|
||||
698.370849609375,
|
||||
745.5828247070312
|
||||
],
|
||||
"size": [
|
||||
140,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 8,
|
||||
"label": "image"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageInvert"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "UnzipToInput",
|
||||
"pos": [
|
||||
430.0738220214844,
|
||||
798.1468505859375
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
78
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 6,
|
||||
"label": "images"
|
||||
},
|
||||
{
|
||||
"name": "folder",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"slot_index": 1,
|
||||
"label": "folder"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UnzipToInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
"input/file.zip"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
13,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
11,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
14,
|
||||
9,
|
||||
1,
|
||||
15,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8769226950000008,
|
||||
"offset": [
|
||||
-24.799331551459204,
|
||||
-332.5597278000348
|
||||
]
|
||||
},
|
||||
"groupNodes": {},
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.10"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
from .unzip import UnzipToInput
|
||||
from .compress import CompressImages
|
||||
from .clean import CleanFolders
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"UnzipToInput": UnzipToInput,
|
||||
"CompressImages": CompressImages,
|
||||
"CleanFolders": CleanFolders
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"UnzipToInput": "Unzip To Input",
|
||||
"CompressImages": "Zip Compress",
|
||||
"CleanFolders": "Clean Folders"
|
||||
}
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import shutil
|
||||
import folder_paths
|
||||
|
||||
class CleanFolders:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"input_path": ("STRING", {"default": ""}),
|
||||
"output_path": ("STRING", {"default": ""})
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "clean"
|
||||
CATEGORY = "comfyui_zip"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def clean(self, input_path, output_path):
|
||||
result_msg = []
|
||||
|
||||
# 获取基础目录
|
||||
input_base = folder_paths.get_input_directory()
|
||||
output_base = folder_paths.get_output_directory()
|
||||
|
||||
# 确保路径是字符串类型,如果是元组则取第一个元素
|
||||
input_path = input_path[0] if isinstance(input_path, tuple) else str(input_path) if input_path else ""
|
||||
output_path = output_path[0] if isinstance(output_path, tuple) else str(output_path) if output_path else ""
|
||||
|
||||
# 只有当提供了路径时才进行删除操作
|
||||
if input_path:
|
||||
input_full_path = os.path.join(input_base, input_path)
|
||||
if os.path.exists(input_full_path):
|
||||
try:
|
||||
shutil.rmtree(input_full_path)
|
||||
result_msg.append(f"已清理输入目录: {input_full_path}")
|
||||
except Exception as e:
|
||||
result_msg.append(f"清理输入目录失败: {str(e)}")
|
||||
else:
|
||||
result_msg.append(f"输入目录不存在: {input_full_path}")
|
||||
else:
|
||||
result_msg.append("未提供输入路径,跳过清理")
|
||||
|
||||
# 只有当提供了路径时才进行删除操作
|
||||
if output_path:
|
||||
output_full_path = os.path.join(output_base, output_path)
|
||||
if os.path.exists(output_full_path):
|
||||
try:
|
||||
shutil.rmtree(output_full_path)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return ("",)
|
||||
|
||||
# 注册节点
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CleanFolders": CleanFolders
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"CleanFolders": "清理文件夹"
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import os
|
||||
import zipfile
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import folder_paths
|
||||
import tempfile
|
||||
import shutil
|
||||
import matplotlib.pyplot as plt
|
||||
import time
|
||||
import random
|
||||
|
||||
class CompressImages:
|
||||
_current_id = None
|
||||
_temp_dirs = {}
|
||||
_image_counts = {}
|
||||
_zip_filename = {} # 新增:记录每个工作流的zip文件名
|
||||
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI"})
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
"unique_id": "UNIQUE_ID",
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("temp_dir",)
|
||||
FUNCTION = "compress_images"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "comfyui_zip"
|
||||
|
||||
def compress_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None, unique_id=None):
|
||||
try:
|
||||
# 确保临时目录存在
|
||||
if unique_id not in CompressImages._temp_dirs or not os.path.exists(CompressImages._temp_dirs[unique_id]):
|
||||
folder_name = f"{filename_prefix}{self.prefix_append}_{unique_id}"
|
||||
CompressImages._temp_dirs[unique_id] = os.path.join(self.output_dir, folder_name)
|
||||
CompressImages._image_counts[unique_id] = 0
|
||||
timestamp = int(time.time())
|
||||
CompressImages._zip_filename[unique_id] = f'{filename_prefix}_{timestamp}.zip'
|
||||
|
||||
# 确保目录存在
|
||||
os.makedirs(CompressImages._temp_dirs[unique_id], exist_ok=True)
|
||||
|
||||
temp_dir = CompressImages._temp_dirs[unique_id]
|
||||
zip_path = os.path.abspath(os.path.join(self.output_dir, CompressImages._zip_filename[unique_id]))
|
||||
|
||||
# 追加模式打开zip文件
|
||||
zip_mode = 'a' if os.path.exists(zip_path) else 'w'
|
||||
with zipfile.ZipFile(zip_path, zip_mode) as zipf:
|
||||
for image in images:
|
||||
if isinstance(image, torch.Tensor):
|
||||
img_array = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8))
|
||||
elif isinstance(image, Image.Image):
|
||||
img = image
|
||||
else:
|
||||
raise ValueError(f"不支持的图片格式: {type(image)}")
|
||||
|
||||
temp_image_path = os.path.join(temp_dir, f'temp_image_{CompressImages._image_counts[unique_id]:05d}.png')
|
||||
img.save(temp_image_path)
|
||||
arcname = f'image_{CompressImages._image_counts[unique_id]:05d}.png'
|
||||
zipf.write(temp_image_path, arcname)
|
||||
os.remove(temp_image_path)
|
||||
CompressImages._image_counts[unique_id] += 1
|
||||
|
||||
folder_name = os.path.basename(CompressImages._temp_dirs[unique_id])
|
||||
|
||||
# 正确的返回格式
|
||||
results = (folder_name,) # 这是 RETURN_TYPES 定义的返回值
|
||||
|
||||
return {"ui": {"file": [CompressImages._zip_filename[unique_id]]}, "result": (results,)}
|
||||
|
||||
except Exception as e:
|
||||
print(f"发生错误: {str(e)}")
|
||||
self._cleanup(unique_id)
|
||||
raise e
|
||||
|
||||
class ZipFiles:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"images": ("IMAGE",),
|
||||
"zip_name": ("STRING", {"default": "images.zip"}),
|
||||
"save_path": ("STRING", {"default": "ComfyUI/output"}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING",)
|
||||
RETURN_NAMES = ("zip_path", "temp_dir",)
|
||||
FUNCTION = "zip_files"
|
||||
|
||||
def zip_files(self, images, zip_name, save_path, unique_id):
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
|
||||
return (output_path, temp_dir)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CompressImages": CompressImages
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"CompressImages": "Zip Compress"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
## ComfyUI 压缩包图片处理
|
||||
## ComfyUI zip package image processing
|
||||
|
||||
### 节点介绍
|
||||
### Node Introduction
|
||||
|
||||
#### 1. Zip Compress (压缩图片)
|
||||
- 功能:将生成的图片打包成zip压缩文件
|
||||
- Function: Package generated images into a zip file
|
||||
- 输入参数 (Input Parameters):
|
||||
- images: 图片输入 (Image input)
|
||||
- filename_prefix: 文件名前缀,默认为"ComfyUI" (Filename prefix, default is "ComfyUI")
|
||||
- 输出 (Output):
|
||||
- temp_dir: 临时目录路径 (Temporary directory path)
|
||||
- 生成的zip文件将保存在ComfyUI的输出目录中 (Generated zip file will be saved in ComfyUI's output directory)
|
||||
|
||||
#### 2. UnzipToInput (解压到输入)
|
||||
- 功能:解压zip文件并将图片加载为可处理的格式
|
||||
- Function: Extract zip file and load images into processable format
|
||||
- 输入参数 (Input Parameters):
|
||||
- zip_path: zip文件路径,支持本地路径或URL (Zip file path, supports local path or URL)
|
||||
- 输出 (Output):
|
||||
- images: 解压出的图片列表 (List of extracted images)
|
||||
- folder: 解压目录名称 (Extraction directory name)
|
||||
|
||||
> **注意**: 压缩包仅支持图片文件的zip,不支持其他类型文件和文件夹
|
||||
> **Note**: Only supports zip files containing images, other file types and folders are not supported
|
||||
|
||||
#### 3. 清理文件夹 (Clean Folders)
|
||||
- 功能:清理临时文件夹和输出文件夹
|
||||
- Function: Clean temporary and output folders
|
||||
- 输入参数 (Input Parameters):
|
||||
- input_path: 需要清理的输入路径 (Input path to clean)
|
||||
- output_path: 需要清理的输出路径 (Output path to clean)
|
||||
- 输出 (Output):
|
||||
- 无返回值,直接执行清理操作 (No return value, directly performs cleaning operation)
|
||||
|
||||
### 使用说明
|
||||
### Usage Instructions
|
||||
|
||||
1. 压缩图片 (Compress Images):
|
||||
- 将图片节点连接到Zip Compress节点
|
||||
- 设置文件名前缀
|
||||
- 运行后将在输出目录生成zip文件
|
||||
|
||||
2. 解压图片 (Extract Images):
|
||||
- 在UnzipToInput节点中输入zip文件路径
|
||||
- 支持本地zip文件或在线zip文件URL
|
||||
- 解压后的图片可直接用于后续处理
|
||||
|
||||
3. 清理文件夹 (Clean Folders):
|
||||
- 用于清理工作流程中产生的临时文件
|
||||
- 可选择性地清理输入或输出目录
|
||||
- 建议在工作流程结束时使用
|
||||
|
||||
### 测试JSON文件
|
||||
项目中有一个Example.json文件,可以作为使用的参考
|
||||
There is an Example.json file in the project that you can refer to for usage.
|
||||
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import time
|
||||
import torch
|
||||
import urllib.request
|
||||
from urllib.parse import urlparse
|
||||
import folder_paths
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import shutil
|
||||
|
||||
class UnzipToInput:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_input_directory()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"zip_path": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING",)
|
||||
RETURN_NAMES = ("images", "folder",)
|
||||
OUTPUT_IS_LIST = (True, False,)
|
||||
FUNCTION = "unzip_process"
|
||||
CATEGORY = "comfyui_zip"
|
||||
|
||||
def download_file(self, url, save_path):
|
||||
urllib.request.urlretrieve(url, save_path)
|
||||
|
||||
def is_image_file(self, filename):
|
||||
image_extensions = {'.png', '.jpg', '.jpeg', '.webp', '.bmp'}
|
||||
return os.path.splitext(filename.lower())[1] in image_extensions
|
||||
|
||||
def unzip_process(self, zip_path):
|
||||
timestamp = str(int(time.time()))
|
||||
extract_dir = os.path.join(self.output_dir, f"unzip_{timestamp}")
|
||||
os.makedirs(extract_dir, exist_ok=True)
|
||||
|
||||
# 处理URL或本地路径
|
||||
if zip_path.startswith(('http://', 'https://')):
|
||||
parsed_url = urlparse(zip_path)
|
||||
zip_filename = os.path.basename(parsed_url.path)
|
||||
temp_zip = os.path.join(extract_dir, zip_filename)
|
||||
self.download_file(zip_path, temp_zip)
|
||||
zip_path = temp_zip
|
||||
|
||||
# 解压文件
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
# 获取所有文件列表
|
||||
name_list = zip_ref.namelist()
|
||||
|
||||
# 过滤掉 __MACOSX 文件夹
|
||||
filtered_names = [name for name in name_list
|
||||
if not name.startswith('__MACOSX/')
|
||||
and not name.startswith('._')]
|
||||
|
||||
# 解压文件,处理中文编码
|
||||
for name in filtered_names:
|
||||
try:
|
||||
# 尝试使用 cp437 解码,然后用 utf-8 编码
|
||||
unicode_name = name.encode('cp437').decode('utf-8')
|
||||
except:
|
||||
# 如果失败,直接使用原名称
|
||||
unicode_name = name
|
||||
|
||||
# 获取解压后的完整路径
|
||||
extracted_path = os.path.join(extract_dir, unicode_name)
|
||||
|
||||
# 确保目标目录存在
|
||||
os.makedirs(os.path.dirname(extracted_path), exist_ok=True)
|
||||
|
||||
# 提取文件
|
||||
with zip_ref.open(name) as source, open(extracted_path, 'wb') as target:
|
||||
shutil.copyfileobj(source, target)
|
||||
|
||||
# 收集所有图片文件
|
||||
image_files = []
|
||||
for root, _, files in os.walk(extract_dir):
|
||||
for file in files:
|
||||
if self.is_image_file(file):
|
||||
image_files.append(os.path.join(root, file))
|
||||
|
||||
if not image_files:
|
||||
raise ValueError("No image files found in ZIP archive")
|
||||
|
||||
# 读取所有图片并转换为tensor
|
||||
images_list = []
|
||||
for img_path in image_files:
|
||||
img = Image.open(img_path)
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGB')
|
||||
img_tensor = np.array(img).astype(np.float32) / 255.0
|
||||
img_tensor = torch.from_numpy(img_tensor)
|
||||
img_tensor = img_tensor.unsqueeze(0)
|
||||
images_list.append(img_tensor)
|
||||
|
||||
# 返回图片列表和文件夹名称
|
||||
folder_name = os.path.basename(extract_dir)
|
||||
|
||||
return (images_list, folder_name)
|
||||
Reference in New Issue
Block a user