remove SavedImageWebhookNode
This commit is contained in:
@@ -1,14 +1,12 @@
|
||||
# ComfyUI-WebhookNotifier
|
||||
|
||||
ComfyUI的Webhook通知插件,可以在工作流程执行完成时,将生成图片的信息发送到指定的Webhook URL。
|
||||
ComfyUI的Webhook通知插件,可以在工作流程执行完成时,发送任务完成的通知。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 支持通知生成图像的基本信息
|
||||
- 支持通知已保存图像的具体路径
|
||||
- **支持提供图片的下载链接**
|
||||
- **支持在通知前自动保存图片**
|
||||
- 支持自定义通知载荷(payload)
|
||||
- 在生成图像后发送完成通知
|
||||
- 通知中包含当前工作流的prompt ID
|
||||
- 支持自定义通知内容
|
||||
- 可配置默认Webhook URL
|
||||
|
||||
## 安装方法
|
||||
@@ -34,93 +32,31 @@ pip install requests
|
||||
|
||||
- `default_webhook_url`: 默认的Webhook URL
|
||||
- `enable_debug_logs`: 是否启用调试日志
|
||||
- `base_url`: ComfyUI服务器的基础URL,默认为`http://localhost:8188`
|
||||
- `output_dir`: 默认的图片输出目录,默认为`output`
|
||||
|
||||
## 使用方法
|
||||
|
||||
插件提供了两种节点:
|
||||
|
||||
### 1. Webhook Notifier
|
||||
|
||||
直接连接到图像输出,可以自动保存图片并发送图像信息和下载链接。
|
||||
插件提供了 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节点的输出,发送已保存图像的路径信息和下载链接。
|
||||
|
||||
参数说明:
|
||||
- `saved_images`: SaveImage节点的输出(必填)
|
||||
- `webhook_url`: Webhook URL地址(必填)
|
||||
- `custom_payload`: 自定义JSON载荷(可选)
|
||||
- `base_url`: ComfyUI服务器的基础URL(可选,默认使用配置文件中的值)
|
||||
|
||||
## 示例工作流
|
||||
|
||||
1. 基本使用示例(不需要SaveImage节点):
|
||||
- 将模型输出的图像连接到Webhook Notifier节点
|
||||
- 设置webhook_url为你的接收端点
|
||||
- 设置save_images为true
|
||||
- 运行工作流,图片会自动保存并发送通知
|
||||
|
||||
2. 与SaveImage节点配合使用:
|
||||
- 将SaveImage节点的输出连接到Saved Image Webhook节点
|
||||
- 设置webhook_url为你的接收端点
|
||||
- 运行工作流
|
||||
|
||||
3. 带自定义数据的示例:
|
||||
- 将自定义JSON设置为如下格式:
|
||||
```json
|
||||
{
|
||||
"workflow_name": "我的测试工作流",
|
||||
"user_id": "user_123",
|
||||
"additional_info": "任何你想要的附加信息"
|
||||
}
|
||||
```
|
||||
- `workflow_name`: 工作流名称(可选)
|
||||
- `additional_info`: 自定义JSON载荷(可选)
|
||||
|
||||
## Webhook响应格式
|
||||
|
||||
WebhookNotifier节点发送的数据格式示例(当save_images=true时):
|
||||
WebhookNotifier节点发送的数据格式示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "completed",
|
||||
"prompt_id": "12345678",
|
||||
"workflow_name": "我的测试工作流",
|
||||
"timestamp": "2023-04-03 15:30:45",
|
||||
"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",
|
||||
"custom_field2": "自定义值2"
|
||||
}
|
||||
|
||||
+2
-5
@@ -4,10 +4,7 @@ import os
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
from .webhook_notifier import WebhookNotifierNode, SavedImageWebhookNode
|
||||
from .webhook_notifier import WebhookNotifierNode
|
||||
|
||||
NODE_CLASS_MAPPINGS["WebhookNotifierNode"] = WebhookNotifierNode
|
||||
NODE_DISPLAY_NAME_MAPPINGS["WebhookNotifierNode"] = "Webhook Notifier"
|
||||
|
||||
NODE_CLASS_MAPPINGS["SavedImageWebhookNode"] = SavedImageWebhookNode
|
||||
NODE_DISPLAY_NAME_MAPPINGS["SavedImageWebhookNode"] = "Saved Image Webhook"
|
||||
NODE_DISPLAY_NAME_MAPPINGS["WebhookNotifierNode"] = "Webhook Notifier"
|
||||
+28
-189
@@ -1,21 +1,14 @@
|
||||
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,
|
||||
"base_url": "http://localhost:8188",
|
||||
"output_dir": "output" # 添加默认输出目录
|
||||
"enable_debug_logs": True
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -45,34 +38,28 @@ class WebhookNotifierNode:
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")}),
|
||||
"webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")})
|
||||
},
|
||||
"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")}),
|
||||
"workflow_name": ("STRING", {"default": "默认工作流"}),
|
||||
"additional_info": ("STRING", {"default": "{}", "multiline": True})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "STRING")
|
||||
RETURN_NAMES = ("images", "saved_paths")
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("images",)
|
||||
FUNCTION = "notify"
|
||||
CATEGORY = "utils"
|
||||
|
||||
def notify(self, images, webhook_url, include_metadata=True, custom_payload="{}",
|
||||
base_url=None, save_images=True, filename_prefix="webhook_", output_dir=None):
|
||||
def notify(self, images, webhook_url, workflow_name="默认工作流", additional_info="{}", prompt=None):
|
||||
try:
|
||||
# 如果未提供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('/')
|
||||
# 从ComfyUI获取prompt_id (execution_id)
|
||||
prompt_id = "unknown"
|
||||
if prompt is not None and "extra_pnginfo" in prompt and "workflow" in prompt["extra_pnginfo"]:
|
||||
if "execution_id" in prompt["extra_pnginfo"]["workflow"]:
|
||||
prompt_id = prompt["extra_pnginfo"]["workflow"]["execution_id"]
|
||||
elif "client_id" in prompt["extra_pnginfo"]["workflow"]:
|
||||
prompt_id = prompt["extra_pnginfo"]["workflow"]["client_id"]
|
||||
|
||||
# 准备基本信息
|
||||
image_info = {
|
||||
@@ -80,67 +67,22 @@ 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)}")
|
||||
# 尝试解析额外信息
|
||||
try:
|
||||
extra_info = json.loads(additional_info) if additional_info else {}
|
||||
except json.JSONDecodeError:
|
||||
extra_info = {}
|
||||
|
||||
# 构造payload
|
||||
try:
|
||||
base_payload = json.loads(custom_payload) if custom_payload else {}
|
||||
except json.JSONDecodeError:
|
||||
base_payload = {}
|
||||
|
||||
# 合并信息
|
||||
payload = {
|
||||
**base_payload,
|
||||
"images": image_info
|
||||
"status": "completed",
|
||||
"prompt_id": prompt_id,
|
||||
"workflow_name": workflow_name,
|
||||
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"images": image_info,
|
||||
**extra_info
|
||||
}
|
||||
|
||||
# 如果已保存图片,添加路径和下载链接
|
||||
if save_images and saved_paths:
|
||||
payload["image_paths"] = saved_paths
|
||||
payload["download_links"] = download_links
|
||||
|
||||
if include_metadata:
|
||||
# 添加更多可能的元数据
|
||||
payload["metadata"] = {
|
||||
"timestamp": import_time_module(),
|
||||
"shape": images.shape,
|
||||
}
|
||||
|
||||
# 发送webhook
|
||||
response = requests.post(
|
||||
webhook_url,
|
||||
@@ -151,113 +93,10 @@ class WebhookNotifierNode:
|
||||
if response.status_code >= 400:
|
||||
print(f"Webhook通知失败: {response.status_code} - {response.text}")
|
||||
else:
|
||||
print(f"Webhook通知成功: {response.status_code}")
|
||||
print(f"Webhook通知成功: {response.status_code}, prompt_id: {prompt_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送webhook时出错: {str(e)}")
|
||||
|
||||
# 返回原始图像和保存的路径(逗号分隔的字符串)
|
||||
return (images, ",".join(saved_paths))
|
||||
|
||||
|
||||
class SavedImageWebhookNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"saved_images": ("STRING", {"forceInput": True}), # 接收SaveImage节点的输出
|
||||
"webhook_url": ("STRING", {"default": CONFIG.get("default_webhook_url", "https://example.com/webhook")}),
|
||||
},
|
||||
"optional": {
|
||||
"custom_payload": ("STRING", {"default": "{}", "multiline": True}),
|
||||
"base_url": ("STRING", {"default": CONFIG.get("base_url", "http://localhost:8188")}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "notify_saved"
|
||||
CATEGORY = "utils"
|
||||
|
||||
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 = {
|
||||
**base_payload,
|
||||
"image_paths": image_paths,
|
||||
"download_links": download_links, # 添加下载链接
|
||||
"timestamp": import_time_module(),
|
||||
"total_images": len(image_paths)
|
||||
}
|
||||
|
||||
# 记录调试信息
|
||||
if CONFIG.get("enable_debug_logs", False):
|
||||
print(f"正在发送webhook通知,图片路径: {image_paths}")
|
||||
print(f"下载链接: {download_links}")
|
||||
|
||||
# 发送webhook
|
||||
response = requests.post(
|
||||
webhook_url,
|
||||
json=payload,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code >= 400:
|
||||
print(f"保存图片Webhook通知失败: {response.status_code} - {response.text}")
|
||||
else:
|
||||
print(f"保存图片Webhook通知成功: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送保存图片webhook时出错: {str(e)}")
|
||||
|
||||
# 返回原始的保存路径信息
|
||||
return (saved_images,)
|
||||
|
||||
|
||||
# 导入time模块的辅助函数 - 避免全局导入
|
||||
def import_time_module():
|
||||
import time
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
# 返回原始图像
|
||||
return (images,)
|
||||
Reference in New Issue
Block a user