Compare commits

..
9 Commits
Author SHA1 Message Date
wangbo 8dd0f05145 增加文档说明 2025-12-02 13:00:41 +08:00
wangbo 7358ddc2f9 增加一个图像上传节点,支持多图上传,适合新出的Qwen多图编辑 2025-12-02 12:55:13 +08:00
wangbo e9f43126a9 fix any 2025-05-15 21:13:02 +08:00
wangbo 4b891f103c fix any 2025-05-15 21:11:07 +08:00
wangbo 405607f616 fix any 2025-05-15 21:03:44 +08:00
wangbo 761f1bb03b fix any 2025-05-15 21:01:16 +08:00
wangbo 7bfaa76190 fix any 2025-05-15 20:58:54 +08:00
wangbo f93b880d3f fix audio 维度bug 2025-05-15 20:42:29 +08:00
wangbo cbe1fc951d 更新节点 2025-05-15 20:23:06 +08:00
5 changed files with 119 additions and 41 deletions
+3 -2
View File
@@ -2,10 +2,13 @@
ComfyUI-Easyai is a powerful extension for ComfyUI that enables users to share workflows and models to easyai.
## ChangeLog
- 20251102: add LoadImagesMulti,使用EasyAI平台多图上传(单任务)节点与该组件对接,可实现批量任务或者任意数量的多图上传和快速选择
![LoadImage](./docs/loadImageMulti.png)
- 202401027: init
## Features
- share workflows to easyai
- LoadImagesMulti
## Installation
### [method1] From Source
@@ -21,8 +24,6 @@ If you have a comfy-cli, you can simply execute `comfy node registry-install com
## Usage
![use](./docs/use.gif)
- After installation, you can use the Easyai nodes in your ComfyUI workflows.
- For more detailed usage, please refer to the [金华岩石](https://jinhuayanshi.cn) or [三景AI](https://easyai.jinhuayanshi.cn) websites.
- Follow me: <img src="./docs/douyin.jpg" width="200" />
## License
This project is licensed under the MIT License. See the LICENSE file for details.
+2 -2
View File
@@ -1,6 +1,6 @@
WEB_DIRECTORY = "js"
from .nodes import NODE_CLASS_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS']
from .nodes import NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
from aiohttp import ClientSession, web
from server import PromptServer
Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

+112 -37
View File
@@ -1,51 +1,126 @@
import soundfile as sf
import requests
import io
import os
from PIL import Image, ImageOps, ImageSequence
import numpy as np
import torch
import folder_paths
import node_helpers
import re
class AudioLoadPath:
class LoadImagesMulti:
@classmethod
def INPUT_TYPES(s):
return {"required": { "path": ("STRING", {"default": "X://insert/path/here.mp4"}),
"sample_rate": ("INT", {"default": 22050, "min": 6000, "max": 192000, "step": 1}),
"offset": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1e6, "step": 0.001}),
"duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1e6, "step": 0.001})}}
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
files = folder_paths.filter_files_content_types(files, ["image"])
RETURN_TYPES = ("AUDIO", )
CATEGORY = "Audio Reactor"
FUNCTION = "load"
return {
"required": {
"filenames": ("STRING", {
"default": "filename1.png\nfilename2.png",
"tooltip": "输入多个文件名,用逗号或者换行分隔,例如: 1.png, 2.jpg, dir/sub.png",
"multiline": True # 多行文本域
}),
}
}
def load(self, path: str, sample_rate: int, offset: float, duration: float|None):
if duration == 0.0:
duration = None
CATEGORY = "EasyAI"
RETURN_TYPES = ("IMAGE", "MASK", "STRING",
"IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
RETURN_NAMES = ("images", "masks", "filepaths",
"image1", "image2", "image3", "image4", "image5", "image6")
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,True,False,
False,False,False,False,False,False)
FUNCTION = "load_images"
if path.startswith(('http://', 'https://')):
# 对于网络路径,直接从内存加载
try:
response = requests.get(path)
response.raise_for_status()
audio_data = io.BytesIO(response.content)
def load_images(self, filenames):
# 解析用户输入的多个文件名
# 使用 soundfile 从内存中读取音频数据
audio, file_sr = sf.read(audio_data)
filenames = re.split(r'[, \r\n]+', filenames) # 按逗号、空格或任何换行符分割
filenames = [f.strip() for f in filenames if f.strip()] # 去掉首尾空格和空字符串
# 如果需要重采样
if file_sr != sample_rate:
# 这里需要添加重采样逻辑
# 可以使用 librosa.resample 或其他方法
pass
if len(filenames) == 0:
raise ValueError("未提供有效的文件名,请至少输入一个文件名。")
except Exception as e:
raise Exception(f"加载网络音频失败: {str(e)}")
else:
# 本地文件使用原有的 librosa 方式加载
audio, _ = librosa.load(path, sr=sample_rate, offset=offset, duration=duration)
output_images = []
output_masks = []
output_paths = []
# 转换为 torch tensor 并调整维度
audio = torch.from_numpy(audio)[None,:,None]
return (audio,)
excluded_formats = ["MPO"]
for fname in filenames:
# 支持子目录,如 "sub/my.png"
img_path = folder_paths.get_annotated_filepath(fname)
if not folder_paths.exists_annotated_filepath(fname):
raise FileNotFoundError(f"文件不存在: {fname}")
img = node_helpers.pillow(Image.open, img_path)
# frames_img = []
# frames_mask = []
w, h = None, None
for i in ImageSequence.Iterator(img):
i = node_helpers.pillow(ImageOps.exif_transpose, i)
if i.mode == "I":
i = i.point(lambda x: x * (1 / 255))
rgb = i.convert("RGB")
# 统一尺寸
if w is None:
w, h = rgb.size
elif rgb.size != (w, h):
continue
# 转 tensor
rgb_tensor = torch.from_numpy(
np.array(rgb).astype(np.float32) / 255.0
)[None,]
# Mask
if "A" in i.getbands():
alpha = i.getchannel("A")
mask_np = np.array(alpha).astype(np.float32) / 255.0
mask_tensor = 1. - torch.from_numpy(mask_np)
elif i.mode == "P" and "transparency" in i.info:
alpha = i.convert("RGBA").getchannel("A")
mask_np = np.array(alpha).astype(np.float32) / 255.0
mask_tensor = 1. - torch.from_numpy(mask_np)
else:
mask_tensor = torch.zeros((64, 64), dtype=torch.float32)
output_images.append(rgb_tensor)
output_masks.append(mask_tensor.unsqueeze(0))
# if len(frames_img) > 1 and img.format not in excluded_formats:
# image_tensor = torch.cat(frames_img, dim=0)
# mask_tensor = torch.cat(frames_mask, dim=0)
# else:
# image_tensor = frames_img[0]
# mask_tensor = frames_mask[0]
# output_images.append(frames_img)
# output_masks.append(frames_mask)
output_paths.append(img_path)
# 合并为 batchN, H, W, C
# batch_images = output_images # 保持 list,每个元素是不同尺寸的 tensor
# batch_masks = output_masks
# 前6张单图输出(如果不够就用None占位)
single_images = [output_images[i] if i < len(output_images) else None for i in range(6)]
return (output_images, output_masks, "\n".join(output_paths),
*single_images)
# 节点导出
NODE_CLASS_MAPPINGS = {
"AudioLoadPath": AudioLoadPath,
"LoadImagesMulti": LoadImagesMulti
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadImagesMulti": "Load Images(input filenames)"
}
+2
View File
@@ -0,0 +1,2 @@
numpy
librosa