188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
# -*- coding:utf-8 -*-
|
||
"""
|
||
File webhook_forward.py
|
||
Author silence
|
||
Date 2025/6/13 13:42
|
||
"""
|
||
import base64
|
||
import io
|
||
import os
|
||
import random
|
||
import string
|
||
import time
|
||
|
||
import numpy as np
|
||
import requests
|
||
from PIL import Image
|
||
|
||
|
||
class PromptIDGenerator:
|
||
"""PromptID生成器:用户可传入或自动生成PromptID"""
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {},
|
||
"optional": {
|
||
"custom_prompt_id": ("STRING", {"default": "", "placeholder": "留空则自动生成"}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("STRING",)
|
||
RETURN_NAMES = ("prompt_id",)
|
||
FUNCTION = "generate_prompt_id"
|
||
OUTPUT_NODE = False
|
||
CATEGORY = "utils"
|
||
|
||
def generate_prompt_id(self, custom_prompt_id=""):
|
||
if custom_prompt_id and custom_prompt_id.strip():
|
||
# 用户输入了自定义ID
|
||
prompt_id = custom_prompt_id.strip()
|
||
print(f"📝 使用自定义PromptID: {prompt_id}")
|
||
else:
|
||
# 自动生成ID:时间戳 + 随机字符
|
||
timestamp = str(int(time.time()))
|
||
random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||
prompt_id = f"prompt_{timestamp}_{random_str}"
|
||
print(f"🎲 自动生成PromptID: {prompt_id}")
|
||
|
||
return (prompt_id,)
|
||
|
||
|
||
class PlugAndPlayWebhook:
|
||
"""即插即用Webhook节点:连上线就能转发数据"""
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"webhook_url": ("STRING", {"default": "http://127.0.0.1:8010/handler/webhook",
|
||
"placeholder": "https://your-api.com/webhook"}),
|
||
"image_url": ("STRING", {"default": "",
|
||
"placeholder": "图片的url"}),
|
||
},
|
||
"optional": {
|
||
"prompt_id": ("STRING", {"default": ""}),
|
||
},
|
||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"},
|
||
}
|
||
|
||
RETURN_TYPES = ()
|
||
FUNCTION = "send"
|
||
OUTPUT_NODE = True
|
||
CATEGORY = "utils"
|
||
|
||
def send(self, webhook_url, image_url, prompt_id="", prompt=None, extra_pnginfo=None, unique_id=None):
|
||
if not webhook_url:
|
||
raise ValueError("❌ 请填写Webhook URL!")
|
||
|
||
# 使用传入的prompt_id,如果没有则用unique_id
|
||
final_prompt_id = prompt_id or unique_id or "unknown"
|
||
|
||
# 准备发送的数据
|
||
data = {
|
||
"img_base64": image_url,
|
||
"format": "png",
|
||
"image_url": image_url,
|
||
"prompt_id": final_prompt_id,
|
||
"timestamp": time.time()
|
||
}
|
||
|
||
# 发送Webhook
|
||
try:
|
||
response = requests.post(webhook_url, json=data)
|
||
response.raise_for_status()
|
||
print(f'发送的数据:{data}')
|
||
except Exception as e:
|
||
print(f"❌ 发送失败: {str(e)}")
|
||
|
||
# 终端节点,无需返回
|
||
return ()
|
||
|
||
|
||
class SaveImageWithOutput:
|
||
"""保存图片并输出的节点:既保存图片又能继续传递数据"""
|
||
|
||
def __init__(self):
|
||
self.output_dir = "output"
|
||
self.type = "output"
|
||
self.prefix_append = ""
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"images": ("IMAGE",),
|
||
"filename_prefix": ("STRING", {"default": "ComfyUI"}),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE", "STRING")
|
||
RETURN_NAMES = ("images", "file_path")
|
||
FUNCTION = "save_image"
|
||
OUTPUT_NODE = False
|
||
CATEGORY = "image"
|
||
|
||
def save_image(self, images, filename_prefix="ComfyUI"):
|
||
filename_prefix += self.prefix_append
|
||
full_output_folder, filename, counter, subfolder, filename_prefix = self.get_save_image_path(filename_prefix,
|
||
self.output_dir)
|
||
|
||
file_paths = []
|
||
|
||
for i, image in enumerate(images):
|
||
# 转换图片数据
|
||
img_array = 255. * image.cpu().numpy()
|
||
img = Image.fromarray(np.clip(img_array, 0, 255).astype(np.uint8))
|
||
|
||
# 生成文件名
|
||
file = f"{filename_prefix}_{counter + i:05}_.png"
|
||
file_path = os.path.join(full_output_folder, file)
|
||
|
||
# 保存图片
|
||
img.save(file_path, compress_level=4)
|
||
file_paths.append(file_path)
|
||
|
||
print(f"✅ 图片已保存到: {file_paths[0] if file_paths else '未知路径'}")
|
||
|
||
# 直接返回元组:(原始图片数据, 第一个文件路径)
|
||
return (images, file_paths[0] if file_paths else "")
|
||
|
||
def get_save_image_path(self, filename_prefix, output_dir):
|
||
def map_filename(filename):
|
||
prefix_len = len(os.path.basename(filename_prefix))
|
||
prefix = filename[:prefix_len + 1]
|
||
try:
|
||
digits = int(filename[prefix_len + 1:].split('_')[0])
|
||
except:
|
||
digits = 0
|
||
return (digits, prefix)
|
||
|
||
subfolder = ""
|
||
full_output_folder = os.path.join(output_dir, subfolder)
|
||
|
||
if os.path.commonpath((output_dir, os.path.abspath(full_output_folder))) != output_dir:
|
||
print("Saving image outside the output folder is not allowed.")
|
||
return {}
|
||
|
||
try:
|
||
counter = max(filter(lambda a: a[1][:-1] == filename_prefix and a[1][-1] == "_",
|
||
map(map_filename, os.listdir(full_output_folder))))[0] + 1
|
||
except ValueError:
|
||
counter = 1
|
||
except FileNotFoundError:
|
||
os.makedirs(full_output_folder, exist_ok=True)
|
||
counter = 1
|
||
|
||
return full_output_folder, filename_prefix, counter, subfolder, filename_prefix
|
||
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"PlugAndPlayWebhook": PlugAndPlayWebhook,
|
||
"SaveImageWithOutput": SaveImageWithOutput
|
||
}
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"PlugAndPlayWebhook": "Webhook转发器",
|
||
"SaveImageWithOutput": "保存图片(带输出)"
|
||
}
|