download image and return downloadable url

This commit is contained in:
iSuneast
2025-04-03 22:05:53 +08:00
parent 2e1836b337
commit 0593059be5
3 changed files with 158 additions and 15 deletions
+44 -5
View File
@@ -6,6 +6,8 @@ ComfyUI的Webhook通知插件,可以在工作流程执行完成时,将生成
- 支持通知生成图像的基本信息
- 支持通知已保存图像的具体路径
- **支持提供图片的下载链接**
- **支持在通知前自动保存图片**
- 支持自定义通知载荷(payload)
- 可配置默认Webhook URL
@@ -32,6 +34,8 @@ pip install requests
- `default_webhook_url`: 默认的Webhook URL
- `enable_debug_logs`: 是否启用调试日志
- `base_url`: ComfyUI服务器的基础URL,默认为`http://localhost:8188`
- `output_dir`: 默认的图片输出目录,默认为`output`
## 使用方法
@@ -39,31 +43,46 @@ pip install requests
### 1. Webhook Notifier
直接连接到图像输出,发送图像的基本信息。
直接连接到图像输出,可以自动保存图片并发送图像信息和下载链接。
参数说明:
- `images`: 输入图像(必填)
- `webhook_url`: Webhook URL地址(必填)
- `include_metadata`: 是否包含元数据(可选)
- `custom_payload`: 自定义JSON载荷(可选)
- `base_url`: ComfyUI服务器的基础URL(可选,默认使用配置文件中的值)
- `save_images`: 是否保存图片(可选,默认为true)
- `filename_prefix`: 保存文件名前缀(可选,默认为"webhook_")
- `output_dir`: 输出目录(可选,默认使用配置文件中的值)
此节点有两个输出:
1. `images`: 原始图像数据,可以连接到其他节点
2. `saved_paths`: 保存的图片路径,可以连接到其他需要文件路径的节点
### 2. Saved Image Webhook
连接到SaveImage节点的输出,发送已保存图像的路径信息。
连接到SaveImage节点的输出,发送已保存图像的路径信息和下载链接。
参数说明:
- `saved_images`: SaveImage节点的输出(必填)
- `webhook_url`: Webhook URL地址(必填)
- `custom_payload`: 自定义JSON载荷(可选)
- `base_url`: ComfyUI服务器的基础URL(可选,默认使用配置文件中的值)
## 示例工作流
1. 基本使用示例:
- 将SaveImage节点连接到Saved Image Webhook节点
1. 基本使用示例(不需要SaveImage节点):
- 将模型输出的图像连接到Webhook Notifier节点
- 设置webhook_url为你的接收端点
- 设置save_images为true
- 运行工作流,图片会自动保存并发送通知
2. 与SaveImage节点配合使用:
- 将SaveImage节点的输出连接到Saved Image Webhook节点
- 设置webhook_url为你的接收端点
- 运行工作流
2. 带自定义数据的示例:
3. 带自定义数据的示例:
- 将自定义JSON设置为如下格式:
```json
{
@@ -75,11 +94,31 @@ pip install requests
## Webhook响应格式
WebhookNotifier节点发送的数据格式示例(当save_images=true时):
```json
{
"images": {
"image_count": 1,
"dimensions": "512x512"
},
"image_paths": ["/path/to/output/webhook_20230403-153045_abcd1234.png"],
"download_links": ["http://localhost:8188/view?filename=output/webhook_20230403-153045_abcd1234.png"],
"metadata": {
"timestamp": "2023-04-03 15:30:45",
"shape": [1, 512, 512, 3]
},
"custom_field1": "自定义值1",
"custom_field2": "自定义值2"
}
```
SavedImageWebhook节点发送的数据格式示例:
```json
{
"image_paths": ["/path/to/image1.png", "/path/to/image2.png"],
"download_links": ["http://localhost:8188/view?filename=image1.png", "http://localhost:8188/view?filename=image2.png"],
"timestamp": "2023-04-03 15:30:45",
"total_images": 2,
"custom_field1": "自定义值1",
+3 -1
View File
@@ -1,4 +1,6 @@
{
"default_webhook_url": "https://example.com/webhook",
"enable_debug_logs": true
"enable_debug_logs": true,
"base_url": "http://localhost:8188",
"output_dir": "output"
}
+111 -9
View File
@@ -2,19 +2,30 @@ import requests
import json
import os
from pathlib import Path
import urllib.parse
import uuid
import time
import numpy as np
from PIL import Image
# 加载配置文件
def load_config():
config_path = os.path.join(os.path.dirname(__file__), "config.json")
default_config = {
"default_webhook_url": "https://example.com/webhook",
"enable_debug_logs": True
"enable_debug_logs": True,
"base_url": "http://localhost:8188",
"output_dir": "output" # 添加默认输出目录
}
try:
if os.path.exists(config_path):
with open(config_path, "r") as f:
config = json.load(f)
# 确保config中包含必要的字段
for key, value in default_config.items():
if key not in config:
config[key] = value
return config
else:
# 如果配置文件不存在,创建一个默认配置
@@ -39,17 +50,29 @@ class WebhookNotifierNode:
"optional": {
"include_metadata": ("BOOLEAN", {"default": True}),
"custom_payload": ("STRING", {"default": "{}", "multiline": True}),
"base_url": ("STRING", {"default": CONFIG.get("base_url", "http://localhost:8188")}),
"save_images": ("BOOLEAN", {"default": True}),
"filename_prefix": ("STRING", {"default": "webhook_"}),
"output_dir": ("STRING", {"default": CONFIG.get("output_dir", "output")}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("images", "saved_paths")
FUNCTION = "notify"
CATEGORY = "utils"
def notify(self, images, webhook_url, include_metadata=True, custom_payload="{}"):
def notify(self, images, webhook_url, include_metadata=True, custom_payload="{}",
base_url=None, save_images=True, filename_prefix="webhook_", output_dir=None):
try:
# 这里我们需要获取生成的图片文件路径
# 由于ComfyUI在这个阶段可能还没有保存图片,我们只能传递基本信息
# 如果未提供base_url和output_dir,则使用配置中的默认值
if not base_url:
base_url = CONFIG.get("base_url", "http://localhost:8188")
if not output_dir:
output_dir = CONFIG.get("output_dir", "output")
# 确保base_url没有尾部斜杠
base_url = base_url.rstrip('/')
# 准备基本信息
image_info = {
@@ -57,6 +80,43 @@ class WebhookNotifierNode:
"dimensions": f"{images.shape[1]}x{images.shape[2]}"
}
saved_paths = []
download_links = []
# 如果需要保存图片
if save_images:
try:
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)
# 为每张图片生成唯一文件名并保存
timestamp = time.strftime("%Y%m%d-%H%M%S")
for i in range(len(images)):
# 生成文件名
unique_id = str(uuid.uuid4())[:8]
filename = f"{filename_prefix}{timestamp}_{unique_id}.png"
filepath = os.path.join(output_dir, filename)
# 将numpy数组转换为PIL图像并保存
img = Image.fromarray((images[i] * 255).astype(np.uint8))
img.save(filepath)
# 记录保存的路径
saved_paths.append(filepath)
# 生成下载链接
encoded_path = urllib.parse.quote(os.path.join(os.path.basename(output_dir), filename))
download_url = f"{base_url}/view?filename={encoded_path}"
download_links.append(download_url)
if CONFIG.get("enable_debug_logs", False):
print(f"图片已保存到: {saved_paths}")
print(f"下载链接: {download_links}")
except Exception as e:
print(f"保存图片时出错: {str(e)}")
# 构造payload
try:
base_payload = json.loads(custom_payload) if custom_payload else {}
@@ -69,6 +129,11 @@ class WebhookNotifierNode:
"images": image_info
}
# 如果已保存图片,添加路径和下载链接
if save_images and saved_paths:
payload["image_paths"] = saved_paths
payload["download_links"] = download_links
if include_metadata:
# 添加更多可能的元数据
payload["metadata"] = {
@@ -91,8 +156,8 @@ class WebhookNotifierNode:
except Exception as e:
print(f"发送webhook时出错: {str(e)}")
# 返回原始图像,不做任何修改
return (images,)
# 返回原始图像和保存的路径(逗号分隔的字符串)
return (images, ",".join(saved_paths))
class SavedImageWebhookNode:
@@ -105,6 +170,7 @@ class SavedImageWebhookNode:
},
"optional": {
"custom_payload": ("STRING", {"default": "{}", "multiline": True}),
"base_url": ("STRING", {"default": CONFIG.get("base_url", "http://localhost:8188")}),
}
}
@@ -112,22 +178,57 @@ class SavedImageWebhookNode:
FUNCTION = "notify_saved"
CATEGORY = "utils"
def notify_saved(self, saved_images, webhook_url, custom_payload="{}"):
def notify_saved(self, saved_images, webhook_url, custom_payload="{}", base_url=None):
try:
# 如果未提供base_url,则使用配置中的默认值
if not base_url:
base_url = CONFIG.get("base_url", "http://localhost:8188")
# 确保base_url没有尾部斜杠
base_url = base_url.rstrip('/')
# 解析保存的图片路径
image_paths = saved_images.split(",") if isinstance(saved_images, str) else []
image_paths = [p.strip() for p in image_paths if p.strip()]
# 准备下载链接
download_links = []
for path in image_paths:
# 获取相对于ComfyUI输出文件夹的路径
try:
# 假设路径格式为绝对路径,需要提取相对于output目录的部分
relative_path = path
if os.path.isabs(path):
# 尝试找到output目录的位置
output_dir = None
parts = Path(path).parts
for i, part in enumerate(parts):
if part == "output":
output_dir = os.path.join(*parts[:i+1])
relative_path = os.path.join(*parts[i+1:])
break
# 使用URL编码处理文件路径
encoded_path = urllib.parse.quote(relative_path)
# 构建完整的下载URL
download_url = f"{base_url}/view?filename={encoded_path}"
download_links.append(download_url)
except Exception as e:
print(f"处理图片路径时出错: {str(e)}")
# 如果出错,至少提供原始路径
download_links.append(path)
# 准备发送的信息
try:
base_payload = json.loads(custom_payload) if custom_payload else {}
except json.JSONDecodeError:
base_payload = {}
# 构建包含图片路径的payload
# 构建包含图片路径和下载链接的payload
payload = {
**base_payload,
"image_paths": image_paths,
"download_links": download_links, # 添加下载链接
"timestamp": import_time_module(),
"total_images": len(image_paths)
}
@@ -135,6 +236,7 @@ class SavedImageWebhookNode:
# 记录调试信息
if CONFIG.get("enable_debug_logs", False):
print(f"正在发送webhook通知,图片路径: {image_paths}")
print(f"下载链接: {download_links}")
# 发送webhook
response = requests.post(