FIX
This commit is contained in:
parent
d7c49f25c2
commit
6ba51fe59f
|
|
@ -15,54 +15,78 @@ image = (
|
||||||
"comfy --skip-prompt install --fast-deps --nvidia --version 0.3.40"
|
"comfy --skip-prompt install --fast-deps --nvidia --version 0.3.40"
|
||||||
)
|
)
|
||||||
.pip_install_from_pyproject(os.path.join(os.path.dirname(__file__), "pyproject.toml"))
|
.pip_install_from_pyproject(os.path.join(os.path.dirname(__file__), "pyproject.toml"))
|
||||||
.run_commands("comfy node install https://e.coding.net/g-ldyi2063/dev/ComfyUI-CustomNode.git", force_build=True)
|
.run_commands("comfy node install https://gitea.bowongai.com/Polaris/ComfyUI-CustomNode.git")
|
||||||
.run_commands("comfy node install https://github.com/yolain/ComfyUI-Easy-Use.git")
|
.run_commands("comfy node install https://github.com/yolain/ComfyUI-Easy-Use.git")
|
||||||
.run_commands("cp -f /root/comfy/ComfyUI/custom_nodes/ComfyUI-CustomNode/ext/nodes_bfl.py /root/comfy/ComfyUI/comfy_api_nodes/nodes_bfl.py")
|
.run_commands("comfy node install https://github.com/kijai/ComfyUI-WanVideoWrapper.git")
|
||||||
|
.run_commands("comfy node install https://github.com/christian-byrne/audio-separation-nodes-comfyui.git")
|
||||||
|
.run_commands("comfy node install https://github.com/crystian/ComfyUI-Crystools.git")
|
||||||
|
.run_commands("comfy node install https://github.com/pythongosssss/ComfyUI-Custom-Scripts.git")
|
||||||
|
.run_commands("comfy node install https://github.com/kijai/ComfyUI-KJNodes.git")
|
||||||
|
.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")
|
||||||
|
.run_commands("comfy node install https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes.git")
|
||||||
|
.run_commands("comfy node install https://github.com/jamesWalker55/comfyui-various.git")
|
||||||
|
.pip_install("sageattention")
|
||||||
|
.run_commands("rm -rf /root/comfy/ComfyUI/models&&ln -s /models /root/comfy/ComfyUI/models")
|
||||||
|
.run_commands("rm -rf /root/comfy/ComfyUI/input&&ln -s /models/input /root/comfy/ComfyUI/input")
|
||||||
|
.run_commands("rm -rf /root/comfy/ComfyUI/outputs&&ln -s /models/output /root/comfy/ComfyUI/output")
|
||||||
)
|
)
|
||||||
app = modal.App(image=image)
|
app = modal.App(image=image)
|
||||||
custom_secret = modal.Secret.from_name("comfyui-custom-secret", environment_name="dev")
|
custom_secret = modal.Secret.from_name("comfyui-custom-secret", environment_name="dev")
|
||||||
|
vol = modal.Volume.from_name("comfy_model", environment_name="dev", create_if_missing=True)
|
||||||
|
|
||||||
|
|
||||||
@app.function(
|
@app.function(
|
||||||
|
cpu=(4, 64),
|
||||||
|
memory=(2048, 131072),
|
||||||
|
min_containers=0,
|
||||||
|
buffer_containers=0,
|
||||||
max_containers=1,
|
max_containers=1,
|
||||||
secrets=[custom_secret]
|
gpu="L40S",
|
||||||
|
scaledown_window=600,
|
||||||
|
secrets=[custom_secret],
|
||||||
|
region="us",
|
||||||
|
volumes={
|
||||||
|
"/models": vol
|
||||||
|
}
|
||||||
)
|
)
|
||||||
@modal.concurrent(
|
@modal.concurrent(
|
||||||
max_inputs=10
|
max_inputs=20
|
||||||
)
|
)
|
||||||
@modal.web_server(8000, startup_timeout=60)
|
@modal.web_server(8000, startup_timeout=120)
|
||||||
def ui_1():
|
def ui_1():
|
||||||
subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
subprocess.Popen("comfy launch -- --listen 0.0.0.0 --port 8000", shell=True)
|
||||||
|
|
||||||
@app.function(
|
# @app.function(
|
||||||
max_containers=1,
|
# max_containers=1,
|
||||||
secrets=[custom_secret]
|
# secrets=[custom_secret]
|
||||||
)
|
# )
|
||||||
@modal.concurrent(
|
# @modal.concurrent(
|
||||||
max_inputs=10
|
# max_inputs=10
|
||||||
)
|
# )
|
||||||
@modal.web_server(8000, startup_timeout=60)
|
# @modal.web_server(8000, startup_timeout=60)
|
||||||
def ui_2():
|
# def ui_2():
|
||||||
subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
# subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
||||||
|
#
|
||||||
@app.function(
|
# @app.function(
|
||||||
max_containers=1,
|
# max_containers=1,
|
||||||
secrets=[custom_secret]
|
# secrets=[custom_secret]
|
||||||
)
|
# )
|
||||||
@modal.concurrent(
|
# @modal.concurrent(
|
||||||
max_inputs=10
|
# max_inputs=10
|
||||||
)
|
# )
|
||||||
@modal.web_server(8000, startup_timeout=60)
|
# @modal.web_server(8000, startup_timeout=60)
|
||||||
def ui_3():
|
# def ui_3():
|
||||||
subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
# subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
||||||
|
#
|
||||||
@app.function(
|
# @app.function(
|
||||||
max_containers=1,
|
# max_containers=1,
|
||||||
secrets=[custom_secret]
|
# secrets=[custom_secret]
|
||||||
)
|
# )
|
||||||
@modal.concurrent(
|
# @modal.concurrent(
|
||||||
max_inputs=10
|
# max_inputs=10
|
||||||
)
|
# )
|
||||||
@modal.web_server(8000, startup_timeout=60)
|
# @modal.web_server(8000, startup_timeout=60)
|
||||||
def ui_4():
|
# def ui_4():
|
||||||
subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
# subprocess.Popen("comfy launch -- --cpu --listen 0.0.0.0 --port 8000", shell=True)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
import httpx
|
||||||
|
import modal
|
||||||
|
from fastapi import FastAPI, HTTPException, UploadFile
|
||||||
|
from pydantic import BaseModel, HttpUrl
|
||||||
|
|
||||||
|
image = (
|
||||||
|
modal.Image.debian_slim(
|
||||||
|
python_version="3.10"
|
||||||
|
).pip_install(
|
||||||
|
["fastapi[standard]", "httpx", "aiofiles"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
app = modal.App(image=image)
|
||||||
|
vol = modal.Volume.from_name("comfy_model", create_if_missing=True)
|
||||||
|
DOWNLOAD_BASE_DIR = Path("/models")
|
||||||
|
|
||||||
|
|
||||||
|
@app.function(
|
||||||
|
cpu=(0.125, 8),
|
||||||
|
memory=(128, 4096),
|
||||||
|
scaledown_window=360,
|
||||||
|
timeout=600,
|
||||||
|
max_containers=500,
|
||||||
|
min_containers=0,
|
||||||
|
region="ap",
|
||||||
|
volumes={
|
||||||
|
"/models": vol
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@modal.concurrent(max_inputs=20)
|
||||||
|
@modal.asgi_app()
|
||||||
|
def fastapi_webapp():
|
||||||
|
fastapi_app = FastAPI(
|
||||||
|
title="文件下载服务",
|
||||||
|
description="一个通过URL下载文件到指定目录的API端点",
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Pydantic 模型 ---
|
||||||
|
# 定义请求体的数据结构和验证规则
|
||||||
|
class DownloadRequest(BaseModel):
|
||||||
|
url: HttpUrl # Pydantic 会自动验证这是否是一个有效的URL
|
||||||
|
save_path: str # 用户指定的相对保存路径(例如 "videos/my_video.mp4" 或 "my_document.pdf")
|
||||||
|
|
||||||
|
# --- FastAPI 端点 ---
|
||||||
|
@fastapi_app.post("/download-file/", summary="从URL下载模型")
|
||||||
|
async def download_file_from_url(request: DownloadRequest):
|
||||||
|
if request.save_path.endswith("/"):
|
||||||
|
request.save_path += str(request.url).split("/")[-1].split("?")[0]
|
||||||
|
fn_call = await do_download.spawn.aio(request.url, request.save_path)
|
||||||
|
return {"task_id": fn_call.object_id}
|
||||||
|
|
||||||
|
@fastapi_app.post("/upload-file/", summary="上传模型")
|
||||||
|
async def upload_file(file: UploadFile, save_path: str):
|
||||||
|
if save_path.endswith("/"):
|
||||||
|
save_path += str(file.filename)
|
||||||
|
destination_path = DOWNLOAD_BASE_DIR.joinpath(save_path).resolve()
|
||||||
|
if os.path.exists(destination_path):
|
||||||
|
os.remove(destination_path)
|
||||||
|
print("删除成功")
|
||||||
|
with open(destination_path, "wb") as f:
|
||||||
|
f.write(await file.read())
|
||||||
|
return {"msg": "上传成功"}
|
||||||
|
|
||||||
|
return fastapi_app
|
||||||
|
|
||||||
|
|
||||||
|
@app.function(
|
||||||
|
cpu=(0.125, 8),
|
||||||
|
memory=(128, 4096),
|
||||||
|
scaledown_window=300,
|
||||||
|
timeout=3600,
|
||||||
|
max_containers=500,
|
||||||
|
min_containers=0,
|
||||||
|
region="ap",
|
||||||
|
volumes={
|
||||||
|
"/models": vol
|
||||||
|
}
|
||||||
|
)
|
||||||
|
async def do_download(url, save_path):
|
||||||
|
print(f"Downloading {url} to {save_path}")
|
||||||
|
file_name = os.path.basename(save_path)
|
||||||
|
if not file_name:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="无效的保存路径:无法提取文件名。"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- 安全性检查 ---
|
||||||
|
# 构建绝对目标路径
|
||||||
|
temp_path = "/tmp/" + str(file_name)
|
||||||
|
destination_path = DOWNLOAD_BASE_DIR.joinpath(save_path).resolve()
|
||||||
|
|
||||||
|
# 确保解析后的路径仍然在我们的基础下载目录内,防止目录遍历攻击
|
||||||
|
if not destination_path.is_relative_to(DOWNLOAD_BASE_DIR.resolve()):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="不安全的路径:禁止在指定的下载目录之外写入文件。"
|
||||||
|
)
|
||||||
|
# --- 文件下载与保存 ---
|
||||||
|
try:
|
||||||
|
# 创建目标文件夹(如果不存在)
|
||||||
|
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 使用 httpx 进行异步网络请求
|
||||||
|
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||||
|
# 使用 stream=True 进行流式下载,适合大文件
|
||||||
|
async with client.stream("GET", str(url)) as response:
|
||||||
|
# 检查请求是否成功
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 使用 aiofiles 进行异步文件写入
|
||||||
|
async with aiofiles.open(temp_path, 'wb') as f:
|
||||||
|
async for chunk in response.aiter_bytes():
|
||||||
|
await f.write(chunk)
|
||||||
|
if os.path.exists(destination_path):
|
||||||
|
os.remove(destination_path)
|
||||||
|
print("删除成功")
|
||||||
|
os.system("cp -f {} {}".format(temp_path, destination_path))
|
||||||
|
print("移动成功")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=502,
|
||||||
|
detail=f"下载文件时网络请求失败: {e}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"处理文件时发生内部错误: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print({
|
||||||
|
"message": "文件下载成功",
|
||||||
|
"url": url,
|
||||||
|
"saved_at": str(destination_path)
|
||||||
|
})
|
||||||
|
|
@ -40,7 +40,7 @@ class JMUtils:
|
||||||
self.cos_secret_key = yaml_config["cos_secret_key"]
|
self.cos_secret_key = yaml_config["cos_secret_key"]
|
||||||
self.cos_bucket_name = yaml_config["cos_sucai_bucket_name"]
|
self.cos_bucket_name = yaml_config["cos_sucai_bucket_name"]
|
||||||
|
|
||||||
def submit_task(self, prompt: str, img_url: str, duration: str = "10"):
|
def submit_task(self, prompt: str, img_url: str, duration: str = "10", resolution:str="720p"):
|
||||||
try:
|
try:
|
||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -52,7 +52,7 @@ class JMUtils:
|
||||||
"content": [
|
"content": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": f"{prompt} --resolution 1080p --dur {duration} --camerafixed false",
|
"text": f"{prompt} --resolution {resolution} --dur {duration} --camerafixed false",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "image_url",
|
"type": "image_url",
|
||||||
|
|
@ -253,7 +253,7 @@ class JMUtils:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 下载视频
|
# 下载视频
|
||||||
video_path = self.download_video(video_url)
|
video_path, _ = self.download_video(video_url)
|
||||||
|
|
||||||
# 获取视频总帧数
|
# 获取视频总帧数
|
||||||
cmd_frames = [
|
cmd_frames = [
|
||||||
|
|
@ -334,7 +334,8 @@ class JMGestureCorrect:
|
||||||
def INPUT_TYPES(s):
|
def INPUT_TYPES(s):
|
||||||
return {
|
return {
|
||||||
"required": {
|
"required": {
|
||||||
"image": ("IMAGE",)
|
"image": ("IMAGE",),
|
||||||
|
"resolution":(["720p","1080p"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,7 +344,7 @@ class JMGestureCorrect:
|
||||||
FUNCTION = "gen"
|
FUNCTION = "gen"
|
||||||
CATEGORY = "不忘科技-自定义节点🚩/图片/姿态"
|
CATEGORY = "不忘科技-自定义节点🚩/图片/姿态"
|
||||||
|
|
||||||
def gen(self, image: torch.Tensor):
|
def gen(self, image: torch.Tensor, resolution: str):
|
||||||
wait_time = 240
|
wait_time = 240
|
||||||
interval = 2
|
interval = 2
|
||||||
client = JMUtils()
|
client = JMUtils()
|
||||||
|
|
@ -354,7 +355,7 @@ class JMGestureCorrect:
|
||||||
else:
|
else:
|
||||||
raise Exception("上传失败")
|
raise Exception("上传失败")
|
||||||
prompt = "Stand straight ahead, facing the camera, showing your full body, maintaining a proper posture, keeping the camera still, and ensuring that your head and feet are all within the frame"
|
prompt = "Stand straight ahead, facing the camera, showing your full body, maintaining a proper posture, keeping the camera still, and ensuring that your head and feet are all within the frame"
|
||||||
submit_data = client.submit_task(prompt, image_url)
|
submit_data = client.submit_task(prompt, image_url, duration="5", resolution=resolution)
|
||||||
if submit_data["status"]:
|
if submit_data["status"]:
|
||||||
job_id = submit_data["data"]
|
job_id = submit_data["data"]
|
||||||
else:
|
else:
|
||||||
|
|
@ -385,6 +386,7 @@ class JMCustom:
|
||||||
"default": "Stand straight ahead, facing the camera, showing your full body, maintaining a proper posture, keeping the camera still, and ensuring that your head and feet are all within the frame",
|
"default": "Stand straight ahead, facing the camera, showing your full body, maintaining a proper posture, keeping the camera still, and ensuring that your head and feet are all within the frame",
|
||||||
"multiline": True}),
|
"multiline": True}),
|
||||||
"duration": ("INT", {"default": 5, "min": 2, "max": 10}),
|
"duration": ("INT", {"default": 5, "min": 2, "max": 10}),
|
||||||
|
"resolution": (["720p", "1080p"]),
|
||||||
"wait_time": ("INT", {"default": 180, "min": 60, "max": 600}),
|
"wait_time": ("INT", {"default": 180, "min": 60, "max": 600}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +396,7 @@ class JMCustom:
|
||||||
FUNCTION = "gen"
|
FUNCTION = "gen"
|
||||||
CATEGORY = "不忘科技-自定义节点🚩/视频/即梦"
|
CATEGORY = "不忘科技-自定义节点🚩/视频/即梦"
|
||||||
|
|
||||||
def gen(self, image: torch.Tensor, prompt: str, duration: int, wait_time: int):
|
def gen(self, image: torch.Tensor, prompt: str, duration: int, resolution: str, wait_time: int):
|
||||||
interval = 2
|
interval = 2
|
||||||
client = JMUtils()
|
client = JMUtils()
|
||||||
image_io = client.tensor_to_io(image)
|
image_io = client.tensor_to_io(image)
|
||||||
|
|
@ -403,7 +405,7 @@ class JMCustom:
|
||||||
image_url = upload_data["data"]
|
image_url = upload_data["data"]
|
||||||
else:
|
else:
|
||||||
raise Exception("上传失败")
|
raise Exception("上传失败")
|
||||||
submit_data = client.submit_task(prompt, image_url, str(duration))
|
submit_data = client.submit_task(prompt, image_url, str(duration), resolution=resolution)
|
||||||
if submit_data["status"]:
|
if submit_data["status"]:
|
||||||
job_id = submit_data["data"]
|
job_id = submit_data["data"]
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -278,7 +278,7 @@ class ModalMidJourneyDescribeImage:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("TEXT",)
|
RETURN_TYPES = ("STRING",)
|
||||||
RETURN_NAMES = ("描述内容",)
|
RETURN_NAMES = ("描述内容",)
|
||||||
FUNCTION = "process"
|
FUNCTION = "process"
|
||||||
OUTPUT_NODE = False
|
OUTPUT_NODE = False
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue