From 7a8dcaf91b3c80c4466d2b0d3492809f8613d0c7 Mon Sep 17 00:00:00 2001 From: yp Date: Thu, 11 Sep 2025 23:22:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9B=BE=E7=89=87=E7=94=9F?= =?UTF-8?q?=E6=88=90=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ext/video_agent_deploy.py | 7 +- nodes/img_agent.py | 165 ++++++++++++++++++++++++-------------- 2 files changed, 110 insertions(+), 62 deletions(-) diff --git a/ext/video_agent_deploy.py b/ext/video_agent_deploy.py index f5e8c44..eab89b5 100644 --- a/ext/video_agent_deploy.py +++ b/ext/video_agent_deploy.py @@ -7,7 +7,6 @@ import os import subprocess - import modal image = ( @@ -56,9 +55,9 @@ vol = modal.Volume.from_name("comfy_model", environment_name="dev", create_if_mi @modal.concurrent( max_inputs=10 ) -@modal.web_server(8000, startup_timeout=120) +@modal.web_server(8000, startup_timeout=120, label='image-video-agent-1') def ui_1(): - process = subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True) + process = subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8001", shell=True) process.wait() @@ -75,6 +74,6 @@ def ui_1(): @modal.concurrent( max_inputs=10 ) -@modal.web_server(8000, startup_timeout=120) +@modal.web_server(8000, startup_timeout=120, label='image-video-agent-2') def ui_2(): subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True) diff --git a/nodes/img_agent.py b/nodes/img_agent.py index 266bdfb..6d2fbad 100644 --- a/nodes/img_agent.py +++ b/nodes/img_agent.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding: utf-8 -*- """ File img_agent.py Author silence @@ -22,7 +22,7 @@ except ImportError: import logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') - logger = logging.getLogger("ImgSubmitNode_Final") + logger = logging.getLogger("ImgSubmitNode") print("提示: loguru 未安装,使用内置logging。建议安装以获得更好的日志体验: pip install loguru") @@ -102,6 +102,12 @@ class ImgSubmitNode: @classmethod def INPUT_TYPES(cls): + # 从用户提供的截图中提取的尺寸选项 + size_options = [ + '2048x2048 (1:1)', '2394x1728 (4:3)', '1728x2394 (3:4)', '2560x1440 (16:9)', + '1440x2560 (9:16)', '2496x1664 (3:2)', '1664x2496 (2:3)', '3024x1296 (21:9)', + '1K', '2K', '4K' + ] return { "required": { "model_name_display": (cls.MODEL_DATA["full_display_list"],), @@ -112,6 +118,10 @@ class ImgSubmitNode: "optional": { "image": ("IMAGE",), "image_filename": ("STRING", {"multiline": False, "default": ""}), + "image_urls": ( + "STRING", {"multiline": True, "default": "", "placeholder": "单个或多个图片URL,用英文逗号隔开..."}), + "num_images": ("INT", {"default": 1, "min": 1, "max": 9, "step": 1}), + "image_size": (size_options, {"default": "2K"}), } } @@ -134,59 +144,97 @@ class ImgSubmitNode: logger.info(f"环境: [{environment}], 模型: '{model_name_display}' -> '{tech_name}'") return base_url, tech_name - def submit_task(self, model_name_display, prompt, aspect_ratio, environment, image_filename=None, image=None): + def submit_task(self, model_name_display, prompt, aspect_ratio, environment, + image_filename=None, image=None, image_urls="", num_images=1, image_size="2K"): + file_obj = None try: base_url, tech_name = self._get_base_url_and_tech_name(environment, model_name_display) - model_config = self.MODEL_DATA["configs"].get(tech_name) - if not model_config: - raise ValueError(f"无法找到模型 '{tech_name}' 的配置。") - - def validate_and_correct_parameter(param_name, user_value, supported_values): - if not supported_values: return user_value - if user_value in supported_values: return user_value - - default_value = supported_values[0] - logger.warning( - f"参数警告!模型 '{tech_name}' 不支持 '{param_name}': '{user_value}'。" - f"已自动替换为支持的默认值: '{default_value}'。支持的选项: {supported_values}" - ) - return default_value - - final_ar = validate_and_correct_parameter("宽高比", aspect_ratio, model_config.get("supported_ar", [])) - - headers = {'accept': 'application/json'} - payload = {'prompt': prompt, 'model_name': tech_name, 'aspect_ratio': final_ar, - 'mode': 'turbo', 'webhook_flag': 'false'} - files_to_send = {} - file_obj = None - - if image is not None: - logger.info(f"检测到 IMAGE (Tensor) 输入,优先处理。") - img_tensor = image[0] - img_np = np.clip(255. * img_tensor.cpu().numpy(), 0, 255).astype(np.uint8) - pil_image = Image.fromarray(img_np) - buffer = io.BytesIO() - pil_image.save(buffer, format="PNG") - buffer.seek(0) - files_to_send['img_file'] = ('image_from_workflow.png', buffer, 'image/png') - elif image_filename and image_filename.strip(): - logger.info(f"处理文件名: {image_filename}") - full_path = folder_paths.get_full_path("input", image_filename.strip()) - if not (full_path and os.path.exists(full_path)): - return (f"错误: 在ComfyUI的input文件夹中未找到文件 '{image_filename}'",) - filename = os.path.basename(full_path) - mime_type, _ = mimetypes.guess_type(full_path) or ('application/octet-stream', None) - file_obj = open(full_path, 'rb') - files_to_send['img_file'] = (filename, file_obj, mime_type) - else: - logger.info("未提供任何图像输入,以纯文本模式运行。") - api_endpoint = f'{base_url}/api/custom/image/submit/task' - logger.info(f"向端点 {api_endpoint} 发送请求...") + headers = {'accept': 'application/json'} + if "doubao-seedream-4-0" in tech_name: + logger.info(f"检测到豆包模型 '{tech_name}',启用特定提交逻辑。") - response = requests.post( - api_endpoint, headers=headers, data=payload, files=files_to_send, timeout=60 - ) + # 从尺寸选项中提取实际值(例如 '2048x2048 (1:1)' -> '2048x2048') + actual_size = image_size.split(' ')[0] + + extra_params = { + "sequential_image_generation": "auto", + "size": actual_size, + "max_images": num_images + } + + payload = { + 'prompt': prompt, + 'model_name': tech_name, + 'aspect_ratio': aspect_ratio, + 'mode': 'turbo', + 'webhook_flag': 'false', + 'watermark': 'false', + 'extra': json.dumps(extra_params) + } + + if image_urls and image_urls.strip(): + logger.info(f"使用提供的图片URL: {image_urls.strip()}") + image_urls = image_urls.strip() + image_urls = image_urls.strip().replace('\n', ',') + payload['img_list'] = image_urls + else: + logger.info("未提供图片URL,将以文生图模式运行。") + + logger.info(f"向豆包模型端点 {api_endpoint} 发送请求...") + response = requests.post( + api_endpoint, headers=headers, data=payload, timeout=60 + ) + + else: + # 原始逻辑,适用于其他所有模型 + logger.info(f"使用标准逻辑提交任务到模型 '{tech_name}'。") + model_config = self.MODEL_DATA["configs"].get(tech_name) + if not model_config: + raise ValueError(f"无法找到模型 '{tech_name}' 的配置。") + + def validate_and_correct_parameter(param_name, user_value, supported_values): + if not supported_values: return user_value + if user_value in supported_values: return user_value + + default_value = supported_values[0] + logger.warning( + f"参数警告!模型 '{tech_name}' 不支持 '{param_name}': '{user_value}'。" + f"已自动替换为支持的默认值: '{default_value}'。支持的选项: {supported_values}" + ) + return default_value + + final_ar = validate_and_correct_parameter("宽高比", aspect_ratio, model_config.get("supported_ar", [])) + + payload = {'prompt': prompt, 'model_name': tech_name, 'aspect_ratio': final_ar, + 'mode': 'turbo', 'webhook_flag': 'false'} + files_to_send = {} + + if image is not None: + logger.info(f"检测到 IMAGE (Tensor) 输入,优先处理。") + img_tensor = image[0] + img_np = np.clip(255. * img_tensor.cpu().numpy(), 0, 255).astype(np.uint8) + pil_image = Image.fromarray(img_np) + buffer = io.BytesIO() + pil_image.save(buffer, format="PNG") + buffer.seek(0) + files_to_send['img_file'] = ('image_from_workflow.png', buffer, 'image/png') + elif image_filename and image_filename.strip(): + logger.info(f"处理文件名: {image_filename}") + full_path = folder_paths.get_full_path("input", image_filename.strip()) + if not (full_path and os.path.exists(full_path)): + return (f"错误: 在ComfyUI的input文件夹中未找到文件 '{image_filename}'",) + filename = os.path.basename(full_path) + mime_type, _ = mimetypes.guess_type(full_path) or ('application/octet-stream', None) + file_obj = open(full_path, 'rb') + files_to_send['img_file'] = (filename, file_obj, mime_type) + else: + logger.info("未提供任何图像输入,以纯文本模式运行。") + + logger.info(f"向标准端点 {api_endpoint} 发送请求...") + response = requests.post( + api_endpoint, headers=headers, data=payload, files=files_to_send, timeout=60 + ) response.raise_for_status() response_json = response.json() logger.info(f"任务提交成功,完整响应: {json.dumps(response_json, indent=2, ensure_ascii=False)}") @@ -203,10 +251,11 @@ class ImgSubmitNode: if file_obj: file_obj.close() -# NODE_CLASS_MAPPINGS = { -# "ImgSubmitNode": ImgSubmitNode -# } -# -# NODE_DISPLAY_NAME_MAPPINGS = { -# "ImgSubmitNode": "统一生图任务节点" -# } + +NODE_CLASS_MAPPINGS = { + "ImgSubmitNode": ImgSubmitNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ImgSubmitNode": "提交图片生成" +} \ No newline at end of file