From d5ad329da3fffd098e0781803a70809aecd4e922 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 22 Dec 2024 06:50:11 +0800 Subject: [PATCH] aaa --- RH_ExecuteNode.py | 232 +++++++++++++++++++++++++++++++++++++++++ RH_NodeInfoListNode.py | 47 +++++++++ RH_SettingsNode.py | 24 +++++ __init__.py | 15 +++ 4 files changed, 318 insertions(+) create mode 100644 RH_ExecuteNode.py create mode 100644 RH_NodeInfoListNode.py create mode 100644 RH_SettingsNode.py create mode 100644 __init__.py diff --git a/RH_ExecuteNode.py b/RH_ExecuteNode.py new file mode 100644 index 0000000..8b64549 --- /dev/null +++ b/RH_ExecuteNode.py @@ -0,0 +1,232 @@ +import requests +import time +import json +from PIL import Image +from io import BytesIO +import numpy as np +import torch + +class ExecuteNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "apiConfig": ("STRUCT",), # 设置节点的输入 + "nodeInfoList": ("ARRAY", {"default": []}), # NodeInfoList节点的输出 + }, + } + + RETURN_TYPES = ("IMAGE", ) # 仅定义返回类型为 IMAGE + CATEGORY = "RunningHub" + FUNCTION = "process" # 指向 process 方法 + + def process(self, apiConfig, nodeInfoList): + """ + 该节点通过调用 RunningHub API 创建任务并返回生成的图片链接。 + """ + # 打印请求数据,方便调试 + print(f"API request data: {apiConfig}") + print(f"Node info list: {nodeInfoList}") + + # 1. 查询账户状态,检查是否可以提交任务 + account_status = self.check_account_status(apiConfig["apiKey"]) + if int(account_status["currentTaskCounts"]) > 0: + print("There are tasks running, waiting for them to finish.") + # 等待最多 10 分钟,如果任务未完成,则超时 + start_time = time.time() + while account_status["currentTaskCounts"] > 0 and time.time() - start_time < 600: + time.sleep(2) # 每 2 秒查询一次 + account_status = self.check_account_status(apiConfig["apiKey"]) + if int(account_status["currentTaskCounts"]) > 0: + raise Exception("Timeout: There are still running tasks after 10 minutes.") + + # 2. 创建任务 + task_creation_result = self.create_task(apiConfig, nodeInfoList) + if task_creation_result["code"] != 0: + raise Exception(f"Task creation failed: {task_creation_result['msg']}") + + task_id = task_creation_result["data"]["taskId"] + task_status = task_creation_result["data"]["taskStatus"] + print(f"Task created successfully, taskId: {task_id}, status: {task_status}") + + # 3. 查询任务状态直到任务完成 + while task_status != "success": + print(f"Task still running, checking again in 2 seconds...") + time.sleep(2) # 每 2 秒检查一次任务状态 + task_status_result = self.check_task_status(task_id, apiConfig["apiKey"]) + print(f"Task info, taskId: {task_id}, status: {task_status_result}") + task_status = task_status_result.get("taskStatus", "unknown") # 从结果中获取任务状态 + if task_status != "RUNNING": + print(f"Task failed or completed with status: {task_status}") + break + + # 4. 任务完成,处理输出 + return self.process_task_output(task_id, apiConfig["apiKey"]) + + def check_account_status(self, api_key): + """ + 查询账户状态,检查是否可以提交新任务 + """ + url = "https://www.runninghub.cn/uc/openapi/accountStatus" + headers = { + "User-Agent": "Apifox/1.0.0 (https://apifox.com)", + "Content-Type": "application/json", + } + data = { + "apikey": api_key + } + + response = requests.post(url, json=data, headers=headers) + result = response.json() + if result["code"] != 0: + raise Exception(f"Failed to get account status: {result['msg']}") + # 检查并确保 currentTaskCounts 是整数 + try: + current_task_counts = int(result["data"]["currentTaskCounts"]) + except ValueError: + raise Exception("Invalid value for currentTaskCounts. It should be an integer.") + + result["data"]["currentTaskCounts"] = current_task_counts + return result["data"] + + def create_task(self, apiConfig, nodeInfoList): + """ + 创建任务 + """ + url = "https://www.runninghub.cn/task/openapi/create" + headers = { + "Content-Type": "application/json", + "User-Agent": "Apifox/1.0.0 (https://apifox.com)", + } + data = { + "workflowId": apiConfig["workflowId"], + "apiKey": apiConfig["apiKey"], + "nodeInfoList": [ + { + "nodeId": int(nodeInfo["nodeId"]), # 确保 nodeId 为整数类型 + "fieldName": nodeInfo["fieldName"], + "fieldValue": nodeInfo["fieldValue"], + } + for nodeInfo in nodeInfoList + ], + } + + response = requests.post(url, json=data, headers=headers) + return response.json() + + def check_task_status(self, task_id, api_key): + """ + 查询任务状态 + """ + url = "https://www.runninghub.cn/task/openapi/outputs" + headers = { + "User-Agent": "Apifox/1.0.0 (https://apifox.com)", + "Content-Type": "application/json", + } + data = { + "taskId": task_id, + "apiKey": api_key + } + + response = requests.post(url, json=data, headers=headers) + + # 打印响应以便调试 + print("Response Status Code:", response.status_code) + try: + response_json = response.json() + print("Response JSON:", json.dumps(response_json, indent=4, ensure_ascii=False)) + except ValueError: + print("Response Text:", response.text) + + if response.status_code != 200: + raise Exception(f"HTTP request failed with status code: {response.status_code}") + + result = response.json() + + # 检查 'code' 和 'msg' 字段 + if result.get("code") != 0: + # 如果任务正在运行,返回一个特定的状态 + if result.get("msg") == "APIKEY_TASK_IS_RUNNING": + return {"taskStatus": "RUNNING"} + # 检查 'data' 是否存在并且是列表类型 + if result.get("data") and isinstance(result["data"], list): + if len(result["data"]) > 0: + return result["data"][0] # 假设列表中的第一个元素 + else: + return {"taskStatus": "RUNNING"} # 如果 data 是空列表,任务仍在运行 + else: + return {"taskStatus": "RUNNING"} # 如果 data 是 None,任务仍在运行 + + def process_task_output(self, task_id, api_key): + """ + 处理任务输出,返回文件链接。 + """ + task_status_result = self.check_task_status(task_id, api_key) + + # 记录任务状态结果以了解其结构 + print("Task Status Result:", json.dumps(task_status_result, indent=4, ensure_ascii=False)) + + image_urls = [] + + # 确保 task_status_result 是字典类型 + if isinstance(task_status_result, dict): + # 检查 fileUrl 和 fileType + file_url = task_status_result.get("fileUrl") + file_type = task_status_result.get("fileType") + if file_url and file_type.lower() in ["png", "jpg", "jpeg"]: + image_urls.append(file_url) # 添加到 images 列表 + elif isinstance(task_status_result, list): + for output in task_status_result: + if isinstance(output, dict): + file_url = output.get("fileUrl") + file_type = output.get("fileType") + if file_url and file_type.lower() in ["png", "jpg", "jpeg"]: + image_urls.append(file_url) # 添加到 images 列表 + + if not image_urls: + raise Exception("No valid image output found.") + + # 假设只有一张图,可以根据需要扩展 + image_data = None + if image_urls: + print("Downloading image from URL:", image_urls[0]) # 记录图像 URL + image_data = self.download_image(image_urls[0]) # 下载并处理图像 + print("Image downloaded and processed successfully.") + + return (image_data, ) # 返回一个元组,匹配 RETURN_TYPES + + def download_image(self, image_url): + """ + 从 URL 下载图像并转换为适合预览或保存的 torch.Tensor 格式。 + """ + response = requests.get(image_url) + if response.status_code == 200: + img = Image.open(BytesIO(response.content)).convert("RGB") + img_array = np.array(img).astype(np.float32) / 255.0 # 归一化到 [0, 1] + img_tensor = torch.from_numpy(img_array).unsqueeze(0) # 形状 (1, H, W, C) + img_tensor = img_tensor.contiguous() + + # 打印图像尺寸 + print(f"Downloaded image dimensions: {img_tensor.shape}") # 打印图像形状 + + return img_tensor + else: + raise Exception(f"Failed to download image: {image_url}") + + def download_video(self, video_url): + """ + 从 URL 下��视频。 + 根据 ComfyUI 的要求实现此方法。 + """ + response = requests.get(video_url, stream=True) + if response.status_code == 200: + # 示例:将视频保存到临时位置并返回路径或数据 + video_content = response.content + # 您可能需要根据 ComfyUI 的要求处理视频数据 + # 目前,返回原始字节 + return video_content + else: + raise Exception(f"Failed to download video: {video_url}") diff --git a/RH_NodeInfoListNode.py b/RH_NodeInfoListNode.py new file mode 100644 index 0000000..52c881d --- /dev/null +++ b/RH_NodeInfoListNode.py @@ -0,0 +1,47 @@ +class NodeInfoListNode: + def __init__(self): + # 初始化一个空的 node_info_list,用于存储所有的 nodeInfo + self.node_info_list = [] + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "nodeId": ("INT", {"default": 0}), + "fieldName": ("STRING", {"default": ""}), + "fieldValue": ("STRING", {"default": ""}), + }, + "optional": { + "previousNodeInfoList": ("ARRAY", {"default": []}), # 使其为可选,默认值为空列表 + } + } + + RETURN_TYPES = ("ARRAY",) # 输出类型改为 ARRAY + CATEGORY = "RunningHub" + FUNCTION = "process" + + def process(self, nodeId, fieldName, fieldValue, previousNodeInfoList=[]): + """ + 该节点允许用户配置多个 nodeId、fieldName 和 fieldValue 参数, + 并将多个 nodeInfoList 输出为数组。支持串联,多个节点将合并成一个数组。 + """ + + # 输出调试信息,查看 previousNodeInfoList + print(f"Processing nodeId: {nodeId}, fieldName: {fieldName}, fieldValue: {fieldValue}") + print(f"previousNodeInfoList: {previousNodeInfoList}") + + # 当前的 node_info + node_info = {"nodeId": nodeId, "fieldName": fieldName, "fieldValue": fieldValue} + + # 如果前一个节点有输出(previousNodeInfoList),则将其添加到当前 node_info_list 中 + if previousNodeInfoList: + self.node_info_list.extend(previousNodeInfoList) # 将前一个节点的输出合并进来 + + # 将当前的 node_info 加入 node_info_list 中 + self.node_info_list.append(node_info) + + # 输出调试信息,查看当前的 node_info_list + print(f"Updated node_info_list: {self.node_info_list}") + + # 返回整个 node_info_list 数组,包含当前节点和之前节点的输出 + return [self.node_info_list] diff --git a/RH_SettingsNode.py b/RH_SettingsNode.py new file mode 100644 index 0000000..347f4cc --- /dev/null +++ b/RH_SettingsNode.py @@ -0,0 +1,24 @@ +class SettingsNode: + def __init__(self): + # 初始化节点的任何必要参数 + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "apiKey": ("STRING", {"default": ""}), + "workflowId": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = ("STRUCT",) + CATEGORY = "RunningHub" + FUNCTION = "process" # 添加 FUNCTION 属性并指向 process 方法 + + def process(self, apiKey, workflowId): + """ + 该节点接收 apiKey 和 workflowId,返回结构化数据供后续节点使用 + """ + # 返回一个结构体,包含 apiKey 和 workflowId + return [{"apiKey": apiKey, "workflowId": workflowId}] diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..cbbd987 --- /dev/null +++ b/__init__.py @@ -0,0 +1,15 @@ +from .RH_SettingsNode import SettingsNode +from .RH_NodeInfoListNode import NodeInfoListNode +from .RH_ExecuteNode import ExecuteNode + +NODE_CLASS_MAPPINGS = { + "RH_SettingsNode": SettingsNode, + "RH_NodeInfoListNode": NodeInfoListNode, + "RH_ExecuteNode": ExecuteNode, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "RH_SettingsNode": "RH Settings Node", + "RH_NodeInfoListNode": "RH Node Info List Node", + "RH_ExecuteNode": "RH Execute Node", +}