diff --git a/README.md b/README.md index c7712fb..f588dca 100644 --- a/README.md +++ b/README.md @@ -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", diff --git a/config.json b/config.json index d5a4c80..b49a157 100644 --- a/config.json +++ b/config.json @@ -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" } \ No newline at end of file diff --git a/webhook_notifier.py b/webhook_notifier.py index 9b6376e..672aa17 100644 --- a/webhook_notifier.py +++ b/webhook_notifier.py @@ -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(