diff --git a/__init__.py b/__init__.py index 2d6f81e..0e78541 100644 --- a/__init__.py +++ b/__init__.py @@ -18,6 +18,7 @@ from .nodes.video_agent import VideoSubmitNode from .nodes.save_node import ExtSaveNode from .nodes.video_preview import VideoDownloaderNode from .nodes.fetch_task_result import FetchTaskResult +from .nodes.file_upload import FileUploadNode NODE_CLASS_MAPPINGS = { "FaceOccDetect": FaceDetect, @@ -63,7 +64,8 @@ NODE_CLASS_MAPPINGS = { "VideoSubmitNode": VideoSubmitNode, "ExtSaveNode": ExtSaveNode, "VideoDownloaderNode": VideoDownloaderNode, - "FetchTaskResult": FetchTaskResult + "FetchTaskResult": FetchTaskResult, + "FileUploadNode": FileUploadNode } NODE_DISPLAY_NAME_MAPPINGS = { @@ -110,5 +112,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "VideoSubmitNode": "提交视频生成", "ExtSaveNode": "通用文件保存", "VideoDownloaderNode": "视频下载", - "FetchTaskResult": "获取生成结果 (图片/视频链接)" + "FetchTaskResult": "获取生成结果 (图片/视频链接)", + "FileUploadNode": "文件上传" } diff --git a/ext/video_agent_deploy.py b/ext/video_agent_deploy.py index 44809c9..08a009d 100644 --- a/ext/video_agent_deploy.py +++ b/ext/video_agent_deploy.py @@ -27,7 +27,8 @@ image = ( .run_commands("comfy node install https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite.git") .run_commands("comfy node install https://github.com/WASasquatch/was-node-suite-comfyui.git") .run_commands("comfy node install https://github.com/cubiq/ComfyUI_essentials.git") - .add_local_dir(local_path='/Users/charon/Desktop/ComfyUI-CustomNode', remote_path='/root/comfy/ComfyUI/custom_nodes', + .add_local_dir(local_path='/Users/charon/Desktop/ComfyUI-CustomNode', + remote_path='/root/comfy/ComfyUI/custom_nodes', copy=True ) .run_commands("comfy node install https://github.com/jamesWalker55/comfyui-various.git") @@ -59,6 +60,7 @@ vol = modal.Volume.from_name("comfy_model", environment_name="dev", create_if_mi def ui_1(): subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True) + @app.function( min_containers=0, buffer_containers=0, diff --git a/nodes/file_upload.py b/nodes/file_upload.py new file mode 100644 index 0000000..3fe4558 --- /dev/null +++ b/nodes/file_upload.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" + Author charon + Date 2025/9/7 13:12 + """ +import os +import requests +import mimetypes +import folder_paths + + +class FileUploadNode: + ENVIRONMENT_URL_MAP = { + "prod": "https://bowongai-prod--text-video-agent-fastapi-app.modal.run", + "test": "https://bowongai-test--text-video-agent-fastapi-app.modal.run", + "dev": "https://bowongai-dev--text-video-agent-fastapi-app.modal.run" + } + API_ENDPOINT = "/api/file/upload/s3" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "file_to_upload": ("*", {"file_upload": True}), + "environment": (list(s.ENVIRONMENT_URL_MAP.keys()), {"default": "dev"}), + "form_field_name": ("STRING", {"multiline": False, "default": "file"}), + }, + } + + CATEGORY = "不忘科技-自定义节点🚩/utils/文件上传" + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("url",) + FUNCTION = "handler_file_upload" + + def handler_file_upload(self, file_to_upload, environment, form_field_name): + if not file_to_upload or not isinstance(file_to_upload, str): + return (f"Error: Invalid file input. Please upload a file.",) + + base_url = self.ENVIRONMENT_URL_MAP.get(environment) + if not base_url: + error_message = f"Error: Invalid environment '{environment}' selected." + return (error_message,) + full_api_url = base_url + self.API_ENDPOINT + + local_filepath = folder_paths.get_annotated_filepath(file_to_upload) + + if not os.path.exists(local_filepath): + return (f"Error: File not found at path: {local_filepath}",) + + headers = {'accept': 'application/json'} + filename = os.path.basename(local_filepath) + mime_type, _ = mimetypes.guess_type(local_filepath) + if mime_type is None: + mime_type = 'application/octet-stream' + + try: + with open(local_filepath, 'rb') as f: + files = {form_field_name: (filename, f, mime_type)} + print(f"Uploading '{filename}' to '{full_api_url}'...") + response = requests.post(full_api_url, headers=headers, files=files) + response.raise_for_status() + json_response = response.json() + + if json_response.get("status"): + result_url = json_response.get("data") + print(f"Success! URL: {result_url}") + return (result_url,) + else: + api_msg = json_response.get("msg", "Unknown API error") + error_message = f"Error from API: '{api_msg}'" + return (error_message,) + except Exception as e: + raise ValueError(f'File upload process failed: {e}') diff --git a/nodes/union_llm_node.py b/nodes/union_llm_node.py index 8a48188..7172f7b 100644 --- a/nodes/union_llm_node.py +++ b/nodes/union_llm_node.py @@ -28,7 +28,7 @@ def handler_google_analytics(prompt: str, model_id: str, media_file_path: str, b if media_file_path and os.path.exists(media_file_path): files['img_file'] = (os.path.basename(media_file_path), open(media_file_path, 'rb'), mimetypes.guess_type(media_file_path)[0] or 'application/octet-stream') - if media_file_path.startswith("gs:"): + if bool(media_file_path) and media_file_path.startswith("gs:"): files['img_url'] = (None, media_file_path) try: response = requests.post(f'{base_url}/api/llm/google/analysis', headers=headers, files=files, @@ -62,7 +62,7 @@ class LLMUionNode: "video": ("*",), "image": ("IMAGE",), "audio": ("AUDIO",), - "url": ("STRING", {"multiline": True, "default": "", "placeholder": "【可选】输入要分析的链接"}), + "url": ("STRING", {"multiline": True, "default": None, "placeholder": "【可选】输入要分析的链接"}), "environment": (s.ENVIRONMENTS,), "timeout": ("INT", {"default": 300, "min": 10, "max": 1200}), } @@ -155,7 +155,7 @@ class LLMUionNode: timeout=300): base_url = self.ENV_URLS.get(environment, self.ENV_URLS["prod"]) media_path = None - + url = url.strip() if video is not None: if 'gemini' not in model_name: raise ValueError(f'{model_name}暂不支持视频分析,\n请使用gemini-2.5-flash或者gemini-2.5-pro') @@ -180,7 +180,6 @@ class LLMUionNode: media_path = full_path else: return (f"错误: 无法在 'input' 文件夹中找到文件 '{unwrapped_input}'。",) - elif image is not None: print('多模态处理图片输出...') pil_image = self.tensor_to_pil(image) @@ -218,7 +217,7 @@ class LLMUionNode: else: return (f"错误: 不支持的音频输入格式或结构。收到类型: {type(audio_info)}",) - elif url is not None: + elif url: url = url.strip() model_name = model_name.strip() is_google = model_name.startswith('gemini')