# ComfyUI模板--Base 停止维护!! import json import os import shutil import subprocess import uuid from typing import Dict import loguru import modal image = ( # build up a Modal Image to run ComfyUI, step by step modal.Image.debian_slim( # start from basic Linux with Python python_version="3.10" ) .apt_install("git") # install git to clone ComfyUI .apt_install("gcc") .apt_install("libportaudio2") .pip_install("fastapi[standard]==0.115.4") # install web dependencies .pip_install("comfy-cli==1.3.5") # install comfy-cli .pip_install("cos-python-sdk-v5") .pip_install("sqlalchemy") .pip_install("ultralytics") .pip_install("tencentcloud-sdk-python") .pip_install("pymysql") .pip_install("Pillow") .pip_install("ffmpy") .pip_install("opencv-python") .pip_install("av") .pip_install("imageio") .pip_install("loguru") .pip_install("conformer==0.3.2",extra_options="--no-dependencies") .pip_install("einops>0.6.1",extra_options="--no-dependencies") .pip_install("openai-whisper") .run_commands( # use comfy-cli to install ComfyUI and its dependencies "comfy --skip-prompt install --nvidia --version 0.3.10" ) ) image = ( image.run_commands("comfy node install https://github.com/M1kep/ComfyLiterals") .run_commands("comfy node install https://github.com/evanspearman/ComfyMath") .run_commands("comfy node install https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved") .run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-Bowong.git") .run_commands("comfy node install https://github.com/crystian/ComfyUI-Crystools") .run_commands("comfy node install https://github.com/pythongosssss/ComfyUI-Custom-Scripts") .run_commands("comfy node install https://github.com/BennyKok/comfyui-deploy") .run_commands("comfy node install https://github.com/yolain/ComfyUI-Easy-Use") .run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-VideoHelperSuite.git") .run_commands("comfy node install https://github.com/jags111/efficiency-nodes-comfyui") .run_commands("comfy node install https://github.com/WASasquatch/was-node-suite-comfyui") .run_commands("comfy node install https://github.com/rgthree/rgthree-comfy") .run_commands("comfy node install https://github.com/cubiq/ComfyUI_essentials") .run_commands("comfy node install https://github.com/melMass/comfy_mtb") .run_commands("echo 3 && comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI_SparkTTS.git") .run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git") .run_commands("echo 04111 && comfy node install https://e.coding.net/g-ldyi2063/dev/cosyvoice_comfyui.git") .run_commands("echo 4 && comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-LatentSync-Node.git") .run_commands( "mkdir -p /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model && rm -rf /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model && ln -s /root/comfy/ComfyUI/models/ComfyUI-CustomNode/model /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/model" ).run_commands( "mkdir -p /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Node/checkpoints && rm -rf /root/comfy/ComfyUI/custom_nodes/ComfyUI-LatentSync-Node/checkpoints && ln -s /root/comfy/ComfyUI/models/ComfyUI-LatentSync-Node/checkpoints /root/comfy/ComfyUI/custom_nodes/ComfyUI-LatentSync-Node/checkpoints" ).run_commands( "mkdir -p /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints && mkdir -p /root/.cache/torch/hub/checkpoints && rm -rf /root/.cache/torch/hub/checkpoints && ln -s /root/comfy/ComfyUI/models/.cache/torch/hub/checkpoints /root/.cache/torch/hub/checkpoints" ).run_commands( "mkdir -p /root/comfy/ComfyUI/models/stabilityai && ln -s /root/comfy/ComfyUI/models/stabilityai /root/comfy/ComfyUI/stabilityai" ).run_commands( "mkdir -p /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M && mkdir -p /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && rm -rf /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M && ln -s /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M /root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/CosyVoice-300M" ).run_commands( "rm -rf /root/comfy/ComfyUI/models" ).run_commands( "apt update && apt install -y ffmpeg && ffmpeg -version" ).add_local_file("config.yaml", "/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml", copy=True ).add_local_file("config.py", "/root/comfy/ComfyUI/custom_nodes/cosyvoice_comfyui/pretrained_models/tools/config.py", copy=True ).workdir("/root/comfy") # Add .run_commands(...) calls for any other custom nodes you want to download ) app = modal.App(name="highlight-comfyui-s3", image=image) vol = modal.Volume.from_name("comfyui-model", create_if_missing=True) bucket_dict = modal.Dict.from_name("aws_s3_bucket", create_if_missing=False) bucket_input = str(bucket_dict.get("INPUT")) bucket_output = str(bucket_dict.get("OUTPUT")) secret = modal.Secret.from_name("aws-s3-secret") # completed workflows write output images to this directory output_dir = "/root/comfy/ComfyUI/output" @app.cls( allow_concurrent_inputs=1, # allow 10 concurrent API calls max_containers=200, min_containers=0, buffer_containers=0, scaledown_window=120, # 5 minute container keep alive after it processes an input; increasing this value is a great way to reduce ComfyUI cold start times timeout=900, gpu=["L4", "T4"], cpu=(2,16), # memory=(32768, 32768), # (内存预留量, 内存使用上限) memory=(20480,40960), enable_memory_snapshot=False, secrets=[secret], volumes={ "/root/comfy/ComfyUI/models": vol, "/root/comfy/ComfyUI/input_s3": modal.CloudBucketMount( bucket_name=bucket_input, # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], secret=secret, key_prefix="/" ), "/root/comfy/ComfyUI/output_s3": modal.CloudBucketMount( bucket_name=bucket_output, # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], secret=secret, key_prefix="/" ), }, ) class ComfyUI: @modal.enter() def launch_comfy_background(self): # starts the ComfyUI server in the background exactly once when the first input is received self.session_id = str(uuid.uuid4()) cmd = "echo client_uuid: {}&&rm -rf /root/comfy/ComfyUI/user&&mkdir -p /root/comfy/ComfyUI/output_s3/logs/{}&&ln -s /root/comfy/ComfyUI/output_s3/logs/{} /root/comfy/ComfyUI/user && comfy launch --background".format(self.session_id,self.session_id, self.session_id) subprocess.run(cmd, shell=True, check=True) @modal.method() def infer(self, workflow_json: str = ""): self.poll_server_health() self.prompt_uuid = str(uuid.uuid4()) # runs the comfy run --workflow command as a subprocess workflow = json.loads(workflow_json) print("Workflow JSON:") print(json.dumps(workflow, indent=4, ensure_ascii=False)) for node in workflow.values(): if node.get("class_type") == "ComfyUIDeployExternalText": if node.get("inputs").get("input_id") == "file_path": file_to_move = node.get("inputs").get("default_value") if file_to_move: os.makedirs(os.path.dirname(file_to_move.replace("input_s3", "input")), exist_ok=True) try: shutil.copy(file_to_move, file_to_move.replace("input_s3", "input")) except: try: print("Try download file to S3 manually") # S3 Fallback import boto3 import yaml with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml", encoding="utf-8", mode="r+") as config: yaml_config = yaml.load(config, Loader=yaml.FullLoader) awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"], aws_secret_access_key=yaml_config["aws_access_key"]) awss3.meta.client.download_file(bucket_input, file_to_move.split("input_s3/")[1], file_to_move.replace("input_s3", "input")) except: raise Exception("Failed to download file from S3 manually") node["inputs"]["default_value"] = node["inputs"]["default_value"].replace("input_s3", "input") with open(f"/root/{self.prompt_uuid}.json", "w", encoding="utf-8") as f: f.write(json.dumps(workflow, ensure_ascii=False)) cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 890 --verbose" subprocess.run(cmd, shell=True, check=True, timeout=895) # looks up the name of the output image file based on the workflow file_prefix = [ node.get("inputs") for node in workflow.values() if node.get("class_type") == "VHS_VideoCombine" ][0]["filename_prefix"] # returns the image as bytes file_list = os.listdir(output_dir) # 获取按照文件时间创建排序的列表,默认是按时间升序 new_file_list = sorted(file_list, key=lambda file: os.path.getctime(os.path.join(output_dir, file)), reverse=True) # print("file_list", new_file_list) for f in new_file_list: if f.startswith(file_prefix): os.makedirs(os.path.dirname(os.path.join(output_dir.replace("output", "output_s3"), f)), exist_ok=True) try: shutil.copy(os.path.join(output_dir, f), os.path.join(output_dir.replace("output", "output_s3"), f)) except: try: print("Try move file to S3 manually") # S3 Fallback import boto3 import yaml with open("/root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml", encoding="utf-8", mode="r+") as config: yaml_config = yaml.load(config, Loader=yaml.FullLoader) awss3 = boto3.resource('s3', aws_access_key_id=yaml_config["aws_key_id"], aws_secret_access_key=yaml_config["aws_access_key"]) awss3.meta.client.upload_file(os.path.join(output_dir, f), bucket_output, f) except: raise Exception("Failed to move file to S3 manually") return f @modal.fastapi_endpoint(method="POST") def api(self, item: Dict): try: # save this updated workflow to a new file new_workflow_file = item["prompt"] # run inference on the currently running container fname = self.infer.local(new_workflow_file) if fname is None: raise RuntimeError("Output File not found") j = {"file_name": fname} loguru.logger.success(j) return j except Exception as e: raise RuntimeError(str(e)) finally: print("Purge Garbage By Restart Comfy") cmd = "comfy stop" try: subprocess.run(cmd, shell=True, check=True) except: pass cmd = "comfy launch --background" try: subprocess.run(cmd, shell=True, check=True) except: pass def poll_server_health(self): import socket import urllib try: # dummy request to check if the server is healthy req = urllib.request.Request("http://127.0.0.1:8188/system_stats") urllib.request.urlopen(req, timeout=5) print("ComfyUI server is healthy") except (socket.timeout, urllib.error.URLError) as e: # if no response in 5 seconds, stop the container; Modal will schedule queued inputs on a new container print(f"Server health check failed: {str(e)} restarting ComfyUI...") try: cmd = "comfy stop" try: subprocess.run(cmd, shell=True, check=True) except: pass cmd = "comfy launch --background" try: subprocess.run(cmd, shell=True, check=True) except: raise Exception("Failed to launch ComfyUI") except: modal.experimental.stop_fetching_inputs() raise Exception("ComfyUI server is not healthy, restart failed, stopping container") # @modal.web_endpoint(method="POST", label="tk") # def tk_api(self, item: Dict): # pass #