From 8678b7bb7d66a7e3728b9123c9dce7c45e264645 Mon Sep 17 00:00:00 2001 From: "kyj@bowong.ai" Date: Wed, 9 Apr 2025 18:55:08 +0800 Subject: [PATCH] =?UTF-8?q?ADD=20=E5=A2=9E=E5=8A=A0HeyGem=20APP=20ADD=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8C=85=E5=90=ABHeyGem=E7=9A=84ComfyUI=20AP?= =?UTF-8?q?P?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yaml | 22 --- dockerfile | 134 ---------------- heygem.py | 2 +- server.py | 149 ------------------ server_pure_heygem.py | 284 ++++++++++++++++++++++++++++++++++ server_with_s3.py | 4 +- server_with_s3_auth.py | 4 +- server_with_s3_auth_heygem.py | 122 ++++++++++----- 8 files changed, 370 insertions(+), 351 deletions(-) delete mode 100644 docker-compose.yaml delete mode 100644 dockerfile delete mode 100644 server.py create mode 100644 server_pure_heygem.py diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 0d78dc4..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,22 +0,0 @@ -services: - comfyui-service: - image: comfyui-image:v1.2 - container_name: comfyui - ports: - - 8188:8188 - ipc: host - deploy: - resources: - limits: - memory: 64000M - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: [ gpu ] - volumes: - #- ./custom_nodes:/app/ComfyUI/custom_nodes - - E://models:/root/comfy/ComfyUI/models - - E://comfy//ComfyUI//input:/root/comfy/ComfyUI/input - # - s3:/app/ComfyUI/input_s3 - #- s3:/app/ComfyUI/output_s3 \ No newline at end of file diff --git a/dockerfile b/dockerfile deleted file mode 100644 index d5f1134..0000000 --- a/dockerfile +++ /dev/null @@ -1,134 +0,0 @@ -# 使用 Debian Slim 作为基础镜像 -FROM python:3.10-slim - -# 创建并配置腾讯云的 apt 源 -RUN apt update -RUN apt install -y wget -RUN wget https://linuxmirrors.cn/main.sh -RUN bash main.sh --source mirrors.cloud.tencent.com - -RUN apt update - -# 替换 pip 源为阿里云 -RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ - -# 安装 git -RUN apt-get update && apt-get install -y git - -# 安装 Python 依赖 -RUN pip install fastapi[standard]==0.115.4 \ - comfy-cli==1.3.5 \ - cos-python-sdk-v5 \ - sqlalchemy \ - ultralytics \ - tencentcloud-sdk-python \ - pymysql \ - Pillow \ - ffmpy \ - opencv-python \ - av \ - imageio \ - loguru - -# 安装 ComfyUI 及其依赖 -RUN comfy --skip-prompt install --nvidia --version 0.3.10 - -# 克隆并安装自定义节点 -RUN git clone https://github.com/M1kep/ComfyLiterals /root/comfy/ComfyUI/custom_nodes/ComfyLiterals && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyLiterals/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyLiterals/requirements.txt; fi - -RUN git clone https://github.com/evanspearman/ComfyMath /root/comfy/ComfyUI/custom_nodes/ComfyMath && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyMath/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyMath/requirements.txt; fi - -RUN git clone https://github.com/Kosinkadink/ComfyUI-AnimateDiff-Evolved /root/comfy/ComfyUI/custom_nodes/ComfyUI-AnimateDiff-Evolved && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-AnimateDiff-Evolved/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-AnimateDiff-Evolved/requirements.txt; fi - -RUN git clone https://e.coding.net/g-ldyi2063/dev/ComfyUI-Bowong.git /root/comfy/ComfyUI/custom_nodes/ComfyUI-Bowong && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-Bowong/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-Bowong/requirements.txt; fi - -RUN git clone https://github.com/crystian/ComfyUI-Crystools /root/comfy/ComfyUI/custom_nodes/ComfyUI-Crystools && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-Crystools/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-Crystools/requirements.txt; fi - -RUN git clone https://github.com/pythongosssss/ComfyUI-Custom-Scripts /root/comfy/ComfyUI/custom_nodes/ComfyUI-Custom-Scripts && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-Custom-Scripts/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-Custom-Scripts/requirements.txt; fi - -RUN git clone https://github.com/BennyKok/comfyui-deploy /root/comfy/ComfyUI/custom_nodes/comfyui-deploy && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/comfyui-deploy/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/comfyui-deploy/requirements.txt; fi - -RUN git clone https://github.com/yolain/ComfyUI-Easy-Use /root/comfy/ComfyUI/custom_nodes/ComfyUI-Easy-Use && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-Easy-Use/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-Easy-Use/requirements.txt; fi - -RUN git clone https://e.coding.net/g-ldyi2063/dev/ComfyUI-VideoHelperSuite.git /root/comfy/ComfyUI/custom_nodes/ComfyUI-VideoHelperSuite && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-VideoHelperSuite/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-VideoHelperSuite/requirements.txt; fi - -#防止依赖安装问题 -RUN pip install conformer==0.3.2 einops>0.6.1 --no-dependencies - -RUN pip install -U openai-whisper - -RUN apt install --reinstall -y gcc - -RUN git clone https://e.coding.net/g-ldyi2063/dev/CosyVoice-ComfyUI.git /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI/requirements.txt; fi - - -RUN git clone https://github.com/jags111/efficiency-nodes-comfyui /root/comfy/ComfyUI/custom_nodes/efficiency-nodes-comfyui && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/efficiency-nodes-comfyui/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/efficiency-nodes-comfyui/requirements.txt; fi - -RUN git clone https://github.com/WASasquatch/was-node-suite-comfyui /root/comfy/ComfyUI/custom_nodes/was-node-suite-comfyui && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/was-node-suite-comfyui/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/was-node-suite-comfyui/requirements.txt; fi - -RUN git clone https://github.com/rgthree/rgthree-comfy /root/comfy/ComfyUI/custom_nodes/rgthree-comfy && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/rgthree-comfy/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/rgthree-comfy/requirements.txt; fi - -RUN git clone https://github.com/cubiq/ComfyUI_essentials /root/comfy/ComfyUI/custom_nodes/ComfyUI_essentials && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI_essentials/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI_essentials/requirements.txt; fi - -RUN git clone https://github.com/melMass/comfy_mtb /root/comfy/ComfyUI/custom_nodes/comfy_mtb && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/comfy_mtb/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/comfy_mtb/requirements.txt; fi - -RUN git clone https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/requirements.txt; fi - -RUN git clone https://e.coding.net/g-ldyi2063/dev/ComfyUI-LatentSync-Node.git /root/comfy/ComfyUI/custom_nodes/ComfyUI-LatentSync-Node && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-LatentSync-Node/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI-LatentSync-Node/requirements.txt; fi - -#解决PortAudio library not found问题 -RUN apt-get install -y libportaudio2 - -RUN git clone https://e.coding.net/g-ldyi2063/dev/ComfyUI_SparkTTS.git /root/comfy/ComfyUI/custom_nodes/ComfyUI_SparkTTS && \ - if [ -f /root/comfy/ComfyUI/custom_nodes/ComfyUI_SparkTTS/requirements.txt ]; then pip install -r /root/comfy/ComfyUI/custom_nodes/ComfyUI_SparkTTS/requirements.txt; fi - -# 创建符号链接 -RUN 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 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 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 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 mkdir -p /root/comfy/ComfyUI/models/stabilityai && \ - ln -s /root/comfy/ComfyUI/models/stabilityai /root/comfy/ComfyUI/stabilityai - -RUN rm -rf /root/comfy/ComfyUI/models - -# 安装 ffmpeg -RUN apt-get update && apt-get install -y ffmpeg && ffmpeg -version - -# 添加本地配置文件 -COPY config.yaml /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/config.yaml - -WORKDIR /root/comfy - -CMD ["comfy", "--here", "launch", "--", "--listen", "0.0.0.0", "--port", "8188"] \ No newline at end of file diff --git a/heygem.py b/heygem.py index 6198fc8..a4a850b 100644 --- a/heygem.py +++ b/heygem.py @@ -5,7 +5,7 @@ pkg_path = site.getsitepackages()[0] sys.path.append(f"{pkg_path}/heygem/code") p = subprocess.Popen(f"cd {pkg_path}/heygem/code && " - "python3 app_local.py", shell=True) + "python3.8 app_local.py", shell=True) p.communicate() diff --git a/server.py b/server.py deleted file mode 100644 index 5aebc1e..0000000 --- a/server.py +++ /dev/null @@ -1,149 +0,0 @@ -import json -import os -import subprocess -import uuid -from pathlib import Path -from typing import Dict - -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 - .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") - .run_commands( # use comfy-cli to install ComfyUI and its dependencies - "comfy --skip-prompt install --nvidia --version 0.3.10" - ).add_local_file("snapshot.json","/root/snapshot.json", copy=True) -) - -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://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git") - .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-LatentSync-Node.git") - .run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-VideoHelperSuite.git") - .run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/CosyVoice-ComfyUI.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( - "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/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M-SFT && mkdir -p /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M-SFT && rm -rf /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M-SFT && ln -s /root/comfy/ComfyUI/models/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M-SFT /root/comfy/ComfyUI/custom_nodes/CosyVoice-ComfyUI/pretrained_models/CosyVoice-300M-SFT" - ).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( - "rm -rf /root/comfy/ComfyUI/models" - ).run_commands( - "apt update && apt install -y ffmpeg && ffmpeg -version" - ).pip_install("av") - # Add .run_commands(...) calls for any other custom nodes you want to download -) - -image = image.add_local_file( - "highlight_v0.113_api.json", "/root/workflow_api.json" -) - -app = modal.App(name="highlight-comfyui", image=image) -vol = modal.Volume.from_name("comfyui-model", create_if_missing=True) -secret = modal.Secret.from_name("aws-s3-secret") - -@app.cls( - allow_concurrent_inputs=1, # allow 10 concurrent API calls - concurrency_limit=50, - container_idle_timeout=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=800, - gpu="T4", - secrets=[secret], - volumes={ - "/root/comfy/ComfyUI/models": vol, - # "/root/comfy/ComfyUI/input": modal.CloudBucketMount( - # bucket_name=os.environ["BUCKET_INPUT"], - # # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], - # secret=secret, - # key_prefix="comfyui" - # ), - # "/root/comfy/ComfyUI/output": modal.CloudBucketMount( - # bucket_name=os.environ["BUCKET_OUTPUT"], - # # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], - # secret=secret, - # key_prefix="comfyui" - # ), - }, -) -class ComfyUI: - @modal.enter() - def launch_comfy_background(self): - # starts the ComfyUI server in the background exactly once when the first input is received - cmd = "comfy launch --background" - subprocess.run(cmd, shell=True, check=True) - - @modal.method() - def infer(self, workflow_json: str = ""): - # runs the comfy run --workflow command as a subprocess - session_id = str(uuid.uuid4()) - with open(f"/root/{session_id}.json", "w", encoding="utf-8") as f: - f.write(workflow_json) - cmd = f"comfy run --workflow /root/{session_id}.json --wait --timeout 1200" - # TODO 接收返回值 - subprocess.run(cmd, shell=True, check=True) - - # completed workflows write output images to this directory - output_dir = "/root/comfy/ComfyUI/output" - - # looks up the name of the output image file based on the workflow - workflow = json.loads(workflow_json) - 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): - return f - - @modal.web_endpoint(method="POST") - def api(self, item: Dict): - from fastapi import Response - - # 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) - j = { - "file_name": fname - } - print("json", j) - - return Response(content=json.dumps(j), media_type="application/json") diff --git a/server_pure_heygem.py b/server_pure_heygem.py new file mode 100644 index 0000000..fc8dcb9 --- /dev/null +++ b/server_pure_heygem.py @@ -0,0 +1,284 @@ +# HeyGem模板 +import json +import os +import shutil +import socket +import subprocess +import time +import traceback +import uuid +from pathlib import Path +from typing import Any + +import httpx +import loguru +import modal +from fastapi import Depends, HTTPException, status, UploadFile +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi.responses import Response, FileResponse + +image = ( + modal.Image.debian_slim( # start from basic Linux with Python + python_version="3.10" + ).apt_install("git") + .apt_install("gcc") + .pip_install("loguru") + .pip_install("fastapi[standard]") + .run_commands( + "apt update && apt install -y ffmpeg && ffmpeg -version" + ) + # 添加Python3.8 HeyGem + .run_commands("apt update && apt install -y curl build-essential libssl-dev zlib1g-dev libncurses5-dev libncursesw5-dev libreadline-dev libsqlite3-dev libgdbm-dev libdb5.3-dev libbz2-dev libexpat1-dev lzma liblzma-dev tk-dev libffi-dev") + .run_commands("curl -O https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tar.xz&&tar -xf Python-3.8.12.tar.xz") + .run_commands("cd Python-3.8.12 && ./configure --enable-optimizations && make -j 10 && make altinstall") + .add_local_file("heygem-1.0-py3-none-any.whl","/root/heygem-1.0-py3-none-any.whl", copy=True) + .shell(["/bin/bash", "-c"]) + .run_commands("python3.8 -m pip install /root/heygem-1.0-py3-none-any.whl") + .env({"LD_LIBRARY_PATH":"/usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib"}) + .run_commands("ln -s /usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib/libnvrtc.so.11.2 /usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib/libnvrtc.so") + .run_commands("python3.8 -m pip install https://github.com/pydata/numexpr/releases/download/v2.8.6/numexpr-2.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl") + .add_local_file("heygem.py", "/root/heygem.py", copy=True) + .add_local_file("config.yaml", "/root/config.yaml", copy=True) + .workdir("/root").pip_install("boto3").pip_install("PyYAML") +) + +app = modal.App(name="heygem", image=image) +secret = modal.Secret.from_name("aws-s3-secret") +auth_scheme = HTTPBearer() +bucket_output = "bw-heygem-output" + +@app.cls( + allow_concurrent_inputs=1, # required for UI startup process which runs several API calls concurrently + max_containers=25, # limit interactive session to 1 container + gpu="T4", # good starter GPU for inference + cpu=(2,32), + memory=(6144, 32768), + timeout=2160, + scaledown_window=240, + secrets=[secret, modal.Secret.from_name("web_auth_token")], + volumes={ + "/code/data/final": modal.CloudBucketMount( + bucket_name=bucket_output, + # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], + secret=secret, + key_prefix="/" + ), + "/root/logs": modal.CloudBucketMount( + bucket_name=bucket_output, + # bucket_endpoint_url="https://s3.%s.amazonaws.com" % os.environ["AWS_REGION"], + secret=secret, + key_prefix="/logs/" + ) + }, # mounts our cached models +) +class HeyGem: + @modal.enter() + def start(self): + def check_port_in_use(port, host='127.0.0.1'): + s = None + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect((host, int(port))) + return True + except socket.error: + return False + finally: + if s: + s.close() + uid = str(uuid.uuid4()) + cmd = "echo client_id: %s && nohup python3.8 /root/heygem.py >> /root/logs/%s.log 2>&1 &" % (uid, uid) + subprocess.run(cmd, shell=True, check=True) + timeout = 30 + while timeout > 0: + check = check_port_in_use(8383) + if check: + loguru.logger.success("HeyGem Server started on port 8383") + break + else: + timeout -= 1 + time.sleep(2) + if timeout == 0: + raise TimeoutError("HeyGem Server Start timed out") + + + @modal.method() + def infer(self, code, video_path, audio_path) -> str: + def task_submit(uid, video, audio, heygem_url) -> dict[str, Any]: + """Submit a task to the API""" + task_submit_api = f'{heygem_url}/easy/submit' + result_json = { + 'status': False, 'data': {}, 'msg': '' + } + try: + data = { + "code": uid, + "video_url": video, + "audio_url": audio, + "chaofen": 1, + "watermark_switch": 0, + "pn": 1 + } + loguru.logger.info(f'data={data}') + with httpx.Client() as client: + resp = client.post(task_submit_api, json=data) + resp_dict = resp.json() + loguru.logger.info(f'submit data: {resp_dict}') + if resp_dict['code'] != 10000: + result_json['status'] = False + result_json['msg'] = result_json['msg'] + else: + result_json['status'] = True + result_json['data'] = uid + result_json['msg'] = '任务提交成功' + return result_json + except Exception as e: + loguru.logger.info(f'submit task fail case by:{str(e)}') + raise RuntimeError(str(e)) + + def query_task_progress(heygem_url, heygem_temp_path, task_id: str, interval: int = 10, timeout: int = 60 * 35) -> dict[str, Any]: + """Query task progress and wait for completion""" + result_json = {'status': False, 'data': {}, 'msg': ''} + + def query_result(t_id: str): + tmp_dict = {'status': True, 'data': dict(), 'msg': ''} + try: + query_task_url = f'{heygem_url}/easy/query' + params = { + 'code': t_id + } + with httpx.Client() as client: + resp = client.get(query_task_url, params=params) + resp_dict = resp.json() + loguru.logger.info(f'query task data: {json.dumps(resp_dict)}') + status_code = resp_dict['code'] + if status_code in (9999, 10002, 10003, 10001): + tmp_dict['status'] = False + tmp_dict['msg'] = resp_dict['msg'] + elif status_code == 10000: + status_code = resp_dict['data'].get('status', 1) + if status_code == 3: + tmp_dict['status'] = False + tmp_dict['msg'] = resp_dict['data']['msg'] + else: + process = resp_dict['data'].get('progress', 20) + if status_code == 2: + process = 100 + else: + process = process + result = resp_dict['data'].get('result', '') + tmp_dict['data'] = {'progress': process, + 'path': result, + } + else: + pass + except Exception as e: + loguru.logger.info(f'query task fail case by:{str(e)}') + raise RuntimeError(str(e)) + return tmp_dict + + end = time.time() + timeout + while time.time() < end: + tmp_result = query_result(task_id) + if not tmp_result['status'] or tmp_result['data'].__eq__({}): + result_json['status'] = False + result_json['msg'] = tmp_result['msg'] + break + else: + process = tmp_result['data']['progress'] + loguru.logger.info(f'query task progress :{process}') + if tmp_result['data']['progress'] < 100: + time.sleep(interval) + loguru.logger.info(f'wait next interval:{interval}') + else: + p = tmp_result['data']['path'] + p = p.replace('/', '').replace('\\', '') + result_json['data'] = "%s/%s" % (heygem_temp_path, p) + result_json['status'] = True + break + return result_json + + try: + heygem_url = "http://127.0.0.1:8383" + heygem_temp_path = "/code/data/temp" + submit_result = task_submit(code, video_path, audio_path, heygem_url) + if not submit_result['status']: + raise Exception(f"Task submission failed: {submit_result['msg']}") + + task_id = submit_result['data'] + loguru.logger.info(f'Submitted task: {task_id}') + + # Query task progress + progress_result = query_task_progress(heygem_url, heygem_temp_path, task_id, interval=5) + + if not progress_result['status']: + raise RuntimeError(f"Task processing failed: {progress_result['msg']}") + + # Return the file for download + file_path = progress_result['data'] + if not os.path.exists(file_path): + raise FileNotFoundError(f"Output file not found at {file_path}") + return file_path + except Exception as e: + loguru.logger.error(f"Error processing request || {str(e)}") + raise Exception(str(e)) + finally: + try: + os.remove(video_path) + os.remove(audio_path) + except: + pass + + + @modal.fastapi_endpoint(method="POST") + async def api(self, video_file: UploadFile, audio_file: UploadFile, token: HTTPAuthorizationCredentials = Depends(auth_scheme)) -> Response: + if token.credentials != os.environ["AUTH_TOKEN"]: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect bearer token", + headers={"WWW-Authenticate": "Bearer"}, + ) + code = str(uuid.uuid4()) + try: + video_path = "/root/%s" % video_file.filename + with open(video_path, "wb") as f: + data = await video_file.read() + f.write(data) + + audio_path = "/root/%s" % audio_file.filename + with open(audio_path, "wb") as f: + data = await audio_file.read() + f.write(data) + except Exception as e: + return Response({"status": "fail", "msg": f"video or audio file save failed: {str(e)}"}) + + loguru.logger.info(f"Enter Inference Module") + try: + file_path = self.infer.local(code, video_path, audio_path) + try: + shutil.copy(file_path, os.path.join("/code/data/final",file_path.split(os.path.sep)[-1])) + except Exception as e: + loguru.logger.info(f"Moving {file_path} to S3 Failed: {str(e)}") + try: + print("Try move file to S3 manually") + # S3 Fallback + import boto3 + import yaml + with open("/root/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(file_path, bucket_output, file_path.split(os.path.sep)[-1]) + except: + raise Exception("Failed to move file to S3 manually") + resp = FileResponse(path=file_path, filename=Path(file_path).name, status_code=200, headers={ + "Content-Type": "application/octet-stream", + "Content-Disposition": f"attachment; filename={Path(file_path).name}", + }) + return resp + except Exception as e: + traceback.print_exc() + return Response({"status": "fail", "msg": "Inference module failed: "+str(e)}) + + diff --git a/server_with_s3.py b/server_with_s3.py index 70a98f1..a14d051 100644 --- a/server_with_s3.py +++ b/server_with_s3.py @@ -1,11 +1,9 @@ +# ComfyUI模板--Base 停止维护!! import json import os import shutil import subprocess -import sys -import traceback import uuid -from pathlib import Path from typing import Dict import modal diff --git a/server_with_s3_auth.py b/server_with_s3_auth.py index 74cc2ad..bab2ef9 100644 --- a/server_with_s3_auth.py +++ b/server_with_s3_auth.py @@ -1,11 +1,9 @@ +# ComfyUI模板--Base Auth import json import os import shutil import subprocess -import sys -import traceback import uuid -from pathlib import Path from typing import Dict import modal diff --git a/server_with_s3_auth_heygem.py b/server_with_s3_auth_heygem.py index 00b0b89..56f7c59 100644 --- a/server_with_s3_auth_heygem.py +++ b/server_with_s3_auth_heygem.py @@ -1,11 +1,9 @@ +# ComfyUI模板--Base Auth Heygem import json import os import shutil import subprocess -import sys -import traceback import uuid -from pathlib import Path from typing import Dict import modal @@ -13,14 +11,14 @@ from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials image = ( # build up a Modal Image to run ComfyUI, step by step - modal.Image.from_registry( # start from basic Linux with Python - "ubuntu/python:3.12-24.04_stable" - ).apt_install("python3.8-dev") + 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.8") # install comfy-cli + .pip_install("comfy-cli==1.3.5") # install comfy-cli .pip_install("cos-python-sdk-v5") .pip_install("sqlalchemy") .pip_install("ultralytics") @@ -56,7 +54,7 @@ image = ( .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("echo 1 && comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git") + .run_commands("echo 4 && comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git") .run_commands("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( @@ -75,9 +73,17 @@ image = ( "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 - ).add_local_file("heygem-1.0-py3-none-any.whl","/root/comfy/heygem-1.0-py3-none-any.whl", copy=True - ).add_local_file("heygem.py", "/root/comfy/heygem.py", copy=True - ).pip_install("/root/comfy/heygem-1.0-py3-none-any.whl") + ) # 添加Python3.8 HeyGem + .run_commands("apt update && apt install -y curl build-essential libssl-dev zlib1g-dev libncurses5-dev libncursesw5-dev libreadline-dev libsqlite3-dev libgdbm-dev libdb5.3-dev libbz2-dev libexpat1-dev lzma liblzma-dev tk-dev libffi-dev") + .run_commands("curl -O https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tar.xz&&tar -xf Python-3.8.12.tar.xz") + .run_commands("cd Python-3.8.12 && ./configure --enable-optimizations && make -j 10 && make altinstall") + .add_local_file("heygem-1.0-py3-none-any.whl","/root/comfy/heygem-1.0-py3-none-any.whl", copy=True) + .shell(["/bin/bash", "-c"]) + .run_commands("python3.8 -m pip install /root/comfy/heygem-1.0-py3-none-any.whl") + .env({"LD_LIBRARY_PATH":"/usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib"}) + .run_commands("ln -s /usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib/libnvrtc.so.11.2 /usr/local/lib/python3.8/site-packages/nvidia/cuda_nvrtc/lib/libnvrtc.so") + .run_commands("python3.8 -m pip install https://github.com/pydata/numexpr/releases/download/v2.8.6/numexpr-2.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl") + .add_local_file("heygem.py", "/root/comfy/heygem.py", copy=True) .workdir("/root/comfy") # Add .run_commands(...) calls for any other custom nodes you want to download @@ -101,13 +107,12 @@ auth_scheme = HTTPBearer() 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, + timeout=1200, gpu=["L4", "T4"], cpu=(2,16), # memory=(32768, 32768), # (内存预留量, 内存使用上限) - memory=(20480,40960), - retries=1, - enable_memory_snapshot=True, + memory=(20480,81920), + enable_memory_snapshot=False, secrets=[secret, modal.Secret.from_name("web_auth_token")], volumes={ "/root/comfy/ComfyUI/models": vol, @@ -134,8 +139,10 @@ class ComfyUI: " && 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" - " && nohup python heygem.py >> /root/comfy/ComfyUI/output_s3/logs/{}/heygem.log 2>&1 &").format(self.session_id,self.session_id, self.session_id, self.session_id) + " && nohup python3.8 /root/comfy/heygem.py >> /root/comfy/ComfyUI/output_s3/logs/{}/heygem.log 2>&1 &" + ).format(self.session_id,self.session_id, self.session_id, self.session_id) + subprocess.run(cmd, shell=True, check=True) + cmd = "comfy launch --background" subprocess.run(cmd, shell=True, check=True) @modal.method() @@ -170,41 +177,78 @@ class ComfyUI: 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 fi: fi.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) + cmd = f"comfy run --workflow /root/{self.prompt_uuid}.json --wait --timeout 1190 --verbose" + subprocess.run(cmd, shell=True, check=True, timeout=1195) # 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"] + if "VHS_VideoCombine" in workflow_json: + 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) + # 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 + elif "HeyGemF2F" in workflow_json or "HeyGemF2FFromFile" in workflow_json: + heygem_dir = [ + node.get("inputs") + for node in workflow.values() + if node.get("class_type") == "HeyGemF2F" or node.get("class_type") == "HeyGemF2FFromFile" + ][0]["heygem_temp_path"] + # returns the image as bytes + file_list = os.listdir(heygem_dir) + # 获取按照文件时间创建排序的列表,默认是按时间升序 + new_file_list = sorted(file_list, key=lambda file: os.path.getctime(os.path.join(heygem_dir, file)), + reverse=True) + if len(new_file_list) > 0: try: - shutil.copy(os.path.join(output_dir, f), os.path.join(output_dir.replace("output", "output_s3"), f)) + shutil.copy(os.path.join(heygem_dir, new_file_list[0]), os.path.join(output_dir.replace("output", "output_s3"), new_file_list[0])) 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: + 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) + aws_secret_access_key=yaml_config["aws_access_key"]) + awss3.meta.client.upload_file(os.path.join(heygem_dir, new_file_list[0]), bucket_output, new_file_list[0]) except: - raise Exception("Failed to move file to S3 manually") - return f + raise Exception("Failed to move HeyGem file to S3 manually") + return new_file_list[0] + else: + raise FileNotFoundError("HeyGem generation file not found") + else: + raise Exception("Workflow does not contain VHS_VideoCombine/HeygemF2F node, cannot find output file") + + + @modal.fastapi_endpoint(method="POST") def api(self, item: Dict, token: HTTPAuthorizationCredentials = Depends(auth_scheme)):