بنقرة واحدة
comfyui-api
ComfyUI API 调用、工作流构建、模型管理和提示词编写 — 针对实验室 4×A10 服务器
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
ComfyUI API 调用、工作流构建、模型管理和提示词编写 — 针对实验室 4×A10 服务器
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Set up Alembic database migrations for FastAPI projects using async SQLAlchemy (asyncpg). Covers env.py configuration with async engine, autogenerate handling, greenfield vs brownfield migration strategies, and env var integration.
使用 CloakBrowser 绕过抖音反爬,获取真实视频 MP4 下载链接
Wan2.2 FLF2V (First/Last Frame to Video) 管线 — 生成首尾帧之间的插值视频,支持电影级镜头过渡
Use CloakBrowser (anti-detection Chromium) to access protected Chinese web platforms (Douyin/抖音, etc.) and extract structured data — intercepting API responses, extracting video/media URLs, and downloading content from sites that block standard Playwright/browser automation.
Install and diagnose Playwright-based packages (Playwright, CloakBrowser, etc.) on Linux/WSL — handles externally-managed Python, system deps, proxy-based binary downloads, and runtime troubleshooting
Exploratory QA of web apps: find bugs, evidence, reports.
| name | comfyui-api |
| description | ComfyUI API 调用、工作流构建、模型管理和提示词编写 — 针对实验室 4×A10 服务器 |
现象: GUI 上点 Queue Prompt/Add Queue 后,没有任何反馈,进度条不动。
排查步骤(按优先级):
# 1. 检查队列——很可能已经在跑了(GUI 前端线程卡了,后端正常)
curl -s http://localhost:8188/queue | python3 -m json.tool
# 2. 检查 GPU 内存——如果 GPU 满载,任务在排队
nvidia-smi --query-gpu=index,memory.used,memory.total --format=csv,noheader,nounits
# 3. 检查 API 是否响应
curl -s -o /dev/null -w '%{http_code}' http://localhost:8188/
常见原因:
| 原因 | 表现 | 解决 |
|---|---|---|
| GPU 满载(22+/23GB) | queue_running 有任务 + GPU memory.used 接近 total | 等当前任务跑完,queue_pending 会自动进入 |
| GUI 前端线程卡 | 浏览器无反馈但后端已在处理 | 刷新页面(F5),看队列增加任务 |
| 工作流特定节点报错 | queue 中任务状态异常 | 查 history API 看 error |
注意: ComfyUI 在加载大模型(Wan2.2 14B ~10GB+)时 GUI 线程可能短暂无响应,这不影响后端队列的正常运行。
ssh -p 35043 po@3722d01e5a6f.ofalias.com (OpenFRP 隧道, 带宽有限, 少传文件)bash -c 包装或等 30s+ 后再连。详见 lab-server-deploy skilllocalhost:18188 → server:8188(SSH 转发,非持久,可能需重建)lab-server-deploy skill# 远端先用 base64 编码,本地解码
ssh -p PORT user@host 'base64 -w0 /path/to/file' | base64 -d > local_file
sed -i 's/^fastfetch/#fastfetch/' ~/.config/fish/config.fish)/data/comfy/comfy-env//data/comfy/ComfyUI/user/default/workflows//data/comfy/ComfyUI/output/服务器上已有 /data/comfy/comfy_api.py,支持:
source /data/comfy/comfy-env/bin/activate
python3 /data/comfy/comfy_api.py queue # 查看队列
python3 /data/comfy/comfy_api.py submit WF.json # 提交
python3 /data/comfy/comfy_api.py status PROMPT_ID # 状态
python3 /data/comfy/comfy_api.py image [关键词] # 输出文件
python3 /data/comfy/comfy_api.py list-workflows # 已有工作流
python3 /data/comfy/comfy_api.py list-models # 模型列表
当需要在服务器上学习一个未知的 ComfyUI 模型或工作流时,按以下顺序操作:
https://docs.comfy.org/built-in-nodes/{node_name}https://docs.comfy.org/tutorials/video/wan/https://blog.comfy.org/ — 新功能发布和最佳实践/data/comfy/ComfyUI/user/default/workflows/{"nodes": [...], "links": [...], "widgets_values": [...]}widgets_values 按序对应无 link 的 inputsmode: 4 = bypassed(跨掉的子管线)mode: 0 = 激活links 数组: [id, src_id, src_slot, tgt_id, tgt_slot, type]curl http://localhost:8188/object_info/{class_type} 返回该节点的所有输入、输出、参数范围、默认值curl http://localhost:8188/queuecurl http://localhost:8188/history/{prompt_id}curl http://localhost:8188/historyAstrBot astrbot_plugin_ComfyUI_promax 的 comfyui_txt2img 路径(LLM tool 调)原本走 _build_comfyui_prompt 标准 T2I 路径,用 CheckpointLoaderSimple 直接加载 Anima ckpt。Anima 不内嵌 CLIP/VAE,这条路径会立即报 clip input is invalid: None。
修法: comfyui_txt2img 必须先检测 eng.workflow_prefixes["anima"],有就走 anima_t2i workflow(anima_t2i.json),由 workflow 自带的独立 CLIPLoader(qwen_3_06b_base.safetensors, type=stable_diffusion) + VAELoader + CheckpointLoaderSimple 三节点解决;无 anima workflow 才回落到标准路径(只对内嵌 CLIP 的 SD/SDXL ckpt 工作)。
坑 — 容易改错的地方:
type 从 stable_diffusion 改成 qwen_image 看似更"准确",但 8 次跑成功已验证 stable_diffusion 工作——Anima ckpt 内部 checkpoint loader 会覆盖 type 字段,改 type 反而有引入新 bug 的风险enable_translation: "已弃用",但代码里 _translate_cn_prompt 仍自动调,LLM 翻译失败 fallback 原文 → 中文 prompt 进 Qwen3 出乱码。不要靠 promax 翻译,LLM tool 路径直接传英文 Danbooru 标签/home/po/astrbot/data/plugins/astrbot_plugin_ComfyUI_promax/workflow/anima_t2i.json 的 node 11 硬编码 "best quality, score_7, ..." 仍是 workflow 兜底 starter,LLM tool 路径会覆盖它。
→ AstrBot 端 LLM 调 tool 失败的更多 pitfall 见 astrbot-operations 的"LLM/Provider 端故障排查"段。
Wan2.2 系列使用双模型级联架构(高噪 + 低噪),不同任务共享同一组模型:
| 任务 | 使用的模型 | 区别节点 | 有无独立 checkpoint |
|---|---|---|---|
| T2V (文生视频) | high_noise + low_noise | EmptyHunyuanLatentVideo | 有独立 T2V 模型 |
| I2V (图生视频) | high_noise + low_noise | WanImageToVideoLatent | 与 FLF2V 共用 |
| FLF2V (首尾帧) | high_noise + low_noise | WanFirstLastFrameToVideo | 无独立模型!复用 I2V 模型 |
| T2V Fun Camera | high_noise + low_noise | WanFunControl | 有独立模型 |
注意:Wan2.1 的 FLF2V 有独立 checkpoint (wan2.1_flf2v_720p_14B_fp16),但 Wan2.2 FLF2V 没有,用同一组 I2V 模型文件。
框架:Cast & Count + Setting & Time + Camera & Framing + Action Timeline + Motion Boundaries + Visual Style
| 要素 | 说明 | 示例 |
|---|---|---|
| Cast & Count | 明确指出角色数量和身份 | "Exactly two people: one man and one woman in their 30s" |
| Setting & Time | 锚定环境和时间 | "Indoor restaurant, warm candlelit evening, wooden table" |
| Camera & Framing | 镜头语言 | "Static camera, eye-level, medium shot. No zoom, no pan." |
| Action Timeline | 按时间顺序描述动作演进 | "The man keeps his arm around her. They lean toward each other. They talk and smile." |
| Motion Boundaries | 正面约束限制动作 | "They remain seated. They do not stand up. Nobody else enters the frame." |
| Visual Style | 风格和氛围 | "Cinematic, naturalistic lighting, soft shallow depth of field" |
关键陷阱: CFG=1 时负面 prompt 基本无效。使用 WanVideoNAG 节点(NAG scale=11, alpha=0.25, tau=2.5)在 CFG=1 下恢复负面控制。
生成后必须验证输出:
file output.mp4 # 确认是有效 MP4
ffprobe -v quiet -show_streams output.mp4 | grep -E "codec_name|width|height|duration|nb_frames|r_frame_rate"
常见问题: 只有首帧后灰屏 → KSampler 参数错误(scheduler/step 分割/leftover_noise)
flux-2-klein-base-9b-fp8.safetensors — Flux 2 Klein 文生图z_image_turbo_bf16.safetensors — Z Image Turbo (SD3架构, 快速)qwen_image_edit_2509_fp8_e4m3fn.safetensors — Qwen 图像编辑wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors — Wan2.2 图生视频 (I2V/FLF2V共用)wan2.2_i2v_low_noise_14B_fp8_scaled.safetensorsWan21_SkyReelsV3-R2V_fp8_scaled_mixed.safetensors — SkyReels V3 参考图生视频 (R2V)ltx-2-19b-dev-fp8_transformer_only.safetensors — LTX VideoMelBandRoformer_fp32.safetensors — 音频分离SkyReelsV3 系列 — 视频生成wan2.2_i2v_lightx2v_4steps_lora_v1_high_noise.safetensors — Wan2.2 I2V/FLF2V 4步 LoRA (高噪)wan2.2_i2v_lightx2v_4steps_lora_v1_low_noise.safetensors — Wan2.2 I2V/FLF2V 4步 LoRA (低噪)wan2.1_i2v_lora_rank64_lightx2v_4step.safetensors — SkyReels-V3/Wan2.1 4步 LoRAhunyuan_3d_v2.1.safetensors — 3D生成ltx-2-19b-dev-fp8.safetensors / ltx-2-19b-distilled.safetensorsmiaomiao-harem.safetensors — SDXL Illustrious 动漫模型 (6.5GB, Civitai ⭐⭐⭐⭐⭐)。通过 CheckpointLoaderSimple 加载(自带 CLIP/VAE),不需要额外 SDXL CLIP 模型。已验证 1024×1024 完整生成 ✅。参数:30步, cfg=4, euler, scheduler=normal。路线评估参考 ai-drama-pipeline-route-planning skillAnima/miaomiaoHarem_anima14.safetensors — Anima 动漫模型 (4GB, 2B 参数, flow matching 架构)。不自带 CLIP/VAE,需三个独立节点加载:CLIPLoader(qwen_3_06b_base.safetensors, type=stable_diffusion) + VAELoader(ae.safetensors) + CheckpointLoaderSimple(只加载扩散模型部分)。Qwen 0.6B 文本编码器不支持中文 prompt,中文会生成随机动漫少女而非预期内容。必须用英文 Danbooru 标签格式写 prompt(如 1girl, cat ears, smoking)。已给 AstrBot ComfyUI promax 插件 handle_workflow 加 _translate_cn_prompt 方法(通过 self.context.get_using_provider().text_chat() 调 DeepSeek 翻译),含 CJK 检测 + try/except fallback。参数:32步, cfg=4, euler_ancestral, scheduler=simple。WAN/wan2.2-rapid-mega-aio-v1.safetensors — Wan2.2 Rapid All-in-One (24.3GB, 放在 checkpoints/WAN/ 子目录内,使用 CheckpointLoaderSimple 加载)。单文件合并了 WAN 2.2 + CLIP + VAE + 加速器,无需分开加载多个模型。qwen_3_8b_fp8mixed.safetensors — Flux2用的CLIP, type=flux2qwen_3_4b.safetensors — SD3/ZIT用的CLIP, type=sd3umt5_xxl_fp8_e4m3fn_scaled.safetensors — T5系列gemma_3_12B_it_fp4_mixed.safetensors — Gemmaflux2-vae.safetensors — Flux VAEae.safetensors — SD3/ZIT VAEwan_2.1_vae.safetensors — Wan2.2 I2V/FLF2V VAE (fp8)Wan2_1_VAE_fp32.safetensors — Wan2.1/SkyReels-V3 VAE (fp32, 精度更高)关键节点: UNETLoader + CLIPLoader(type=flux2) + CLIPTextEncode + EmptyFlux2LatentImage + Flux2Scheduler + KSamplerSelect + CFGGuider + SamplerCustomAdvanced + RandomNoise + VAEDecode + VAELoader + SaveImage
已有预置工作流: _hermes_flux_t2i.json
关键节点: UNETLoader + LoraLoaderModelOnly + ModelSamplingAuraFlow + CFGNorm + CLIPLoader(type=qwen_image) + VAELoader(qwen_image_vae) + LoadImage + ImageScaleToTotalPixels + VAEEncode + TextEncodeQwenImageEditPlus + KSampler + VAEDecode + SaveImage
使用步骤:
_hermes_qwen_edit.json,指定图片文件名和编辑指令提示词示例: "改为安全帽反光马甲背心", "angry expression, furrowed brows" (表情包编辑)
经验总结(Qwen Image Edit 表情包批量生成):
关键细节:
TextEncodeQwenImageEditPlus 的输入字段名是 prompt (不是 text)!用错名会报 400image2/image3 等可选 IMAGE 输入,如果不使用必须从 inputs 中删除(不能设 null/None)/data/comfy/ComfyUI/input/,用 LoadImage 节点加载api/object_info/{class_type} 检查节点输入字段名关键节点: UNETLoader + CLIPLoader(type=sd3) + CLIPTextEncode + ConditioningZeroOut + EmptySD3LatentImage + KSampler(cfg=1) + VAEDecode + VAELoader + SaveImage
已有预置工作流: _hermes_zit_t2i.json
已验证 ✅ 可工作。使用 CheckpointLoaderSimple(自动提取 MODEL + CLIP + VAE),无需单独加载 CLIP。
WF = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "miaomiao-harem.safetensors"}},
"2": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["1", 1], "text": ""}}, # 负prompt (空)
"3": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["1", 1], "text": "prompt"}}, # 正prompt
"4": {"class_type": "EmptyLatentImage", "inputs": {"width": 1024, "height": 1024, "batch_size": 1}},
"5": {"class_type": "KSampler", "inputs": {
"model": ["1", 0], "seed": 42, "steps": 30, "cfg": 4.0,
"sampler_name": "euler", "scheduler": "normal",
"positive": ["3", 0], "negative": ["2", 0],
"latent_image": ["4", 0], "denoise": 1.0}},
"6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["1", 2]}},
"7": {"class_type": "SaveImage", "inputs": {"images": ["6", 0], "filename_prefix": "miao"}},
}
参数速查:
| 参数 | 值 | 说明 |
|---|---|---|
| Steps | 30 | Illustrious 模型推荐值 |
| CFG | 4.0 | 动漫风格推荐 3-5 |
| Sampler | euler | 稳定 |
| Scheduler | normal | SDXL 标准 |
| Resolution | 1024×1024 | SDXL 原生分辨率 |
| 耗时 | ~123s/张 | A10 实测 |
Prompt 风格(SDXL/Illustrious 标签式,非 Flux 自然语言):
masterpiece, best quality, anime style, [场景描述], [角色描述], [风格描述], highly detailed
masterpiece, best quality, anime style)/data/comfy/pipeline_flux2wan.py这两个节点调用 api.comfy.org 的云端 API,不是本地推理。当该服务不可达时会报:
The API server at https://api.comfy.org is currently unreachable
不要依赖这两个节点。需要本地生图时用 UNETLoader + CLIPLoader(type=flux2)。
当 ComfyUI Flux 工作流出问题时,mmx image generate 是更快的替代方案:
mmx image generate "prompt text" --output output.png
输出是标准图片文件,无需经过工作流构建、队列等待、轮询的复杂流程。适合快速生成单张图片(如数字人肖像)。
| 变体 | 生成方式 | 步骤 | 结果 |
|---|---|---|---|
| Base FP8 | SamplerCustomAdvanced | 20步, cfg=5 | ❌ 底部 90-95% 灰色 |
| Base FP8 | KSampler | 20步, cfg=3.5 | ❌ 底部 90-95% 灰色 |
| Distilled GGUF Q4_K_S | UnetLoaderGGUF + KSampler | 4步, cfg=1 | ❌ 底部 90% 灰色 |
| Distilled GGUF Q4_K_S | 同上方但 640×640 | 4步, cfg=1 | ⚠️ 底部 45-50% 灰色(部分改善) |
根因猜测: 可能是 VAE 解码阶段显存不足导致 latent 只部分解码,或模型本身训练数据比例导致正方形分辨率下渲染不全。非 prompt/工作流参数问题(官方工作流和简化版都复现)。
对策: 如需完整 1024×1024 图片,使用 MiaoMiao Harem (SDXL) 替代。Flux 2 Klein 仅适用于可接受底部裁剪的场景。
| 步骤 | 耗时 | 输出 | 说明 |
|---|---|---|---|
| Flux 2 Klein 关键帧 | ~70s | 1024×1024 PNG (~1.4MB) | cfg=3-5, 20步 |
| 图片上传到 ComfyUI | ~1s | — | POST /upload/image |
| Wan2.2 I2V 动画化 | ~170s | 720×720 MP4 (~340KB) | 41帧@16fps=2.6秒 |
| 单镜头总计 | ~4min | 2.6秒视频 | 81帧≈5秒, 耗时~5min |
视频参数与时长关系:
| 帧数 | 16fps时长 | 预计耗时 |
|---|---|---|
| 41 | 2.6秒 | ~3min |
| 81 | 5秒 | ~5min |
Wan2.2 内存: 24GB A10 可跑 720×720, fp8 模型 + 4步 LoRA。VRAM 余量约 172MB (torch_vram_free),说明模型动态加载。A10 上 41 帧约 3 分钟。
服务器上的工作流 JSON 使用 ComfyUI 0.4 subgraph 格式 (非 API 格式):
{"definitions": {"subgraphs": [{"nodes": [...], "links": [...], ...}]}}
Links 结构 (dict 格式):
{"id": N, "origin_id": src, "origin_slot": 0, "target_id": dst, "target_slot": 0, "type": "IMAGE"}
origin_id=-10 → 外部 widget 输入 (PrimitiveStringMultiline 等顶层节点)target_id=-20 → 输出到顶层 (SaveImage 等)每个子图节点结构:
{"id": N, "type": "CLIPLoader", "inputs": [{"name": "clip_name", "type": "COMBO", "link": null, "widget": {"name": "clip_name"}}],
"widgets_values": ["qwen_3_8b_fp8mixed.safetensors", "flux2", "default"]}
widgets_values 按序对应无 link 的 inputs 中的 widget 输入。
| 模型存放目录 | 对应的加载节点 |
|---|---|
checkpoints/ | CheckpointLoaderSimple, easy fluxLoader(仅扫描此目录) |
diffusion_models/ | UNETLoader, DiffusionModelLoaderKJ |
loras/ | 有多种 LoRA 加载节点 |
vae/ | VAELoader |
text_encoders/ / clip/ | CLIPLoader / DualCLIPLoader |
Flux 2 Klein 模型在 diffusion_models/ 下,不能用 easy fluxLoader(它只扫描 checkpoints/)。必须用 UNETLoader 加载模型,配合 CLIPLoader(type="flux2")。
远端 shell 是 fish,不支持 bash 风格的 heredoc。huggingface.co 被墙,需用 hf-mirror.com 镜像下载大模型。
# ❌ 这样不行 (fish 报错)
ssh host 'cat > file << "EOF" ... EOF'
# ✅ 方法1: 用 base64 管道绕过 (最通用,兼容 fish 有杂音的情况)
cat local_file.py | base64 -w0 | ssh host 'bash -c "base64 -d > /path/to/file && chmod +x /path/to/file"'
# ✅ 方法2: bash -c "cat > file" < local_file (需要 fish greeting 已禁用)
ssh -o StrictHostKeyChecking=no -p PORT user@host 'bash -c "cat > /remote/file"' < /local/file
~/.config/fish/config.fish 末尾有 fastfetch -c examples/10.jsonc,每次 SSH 登录都会先输出系统信息 banner。这会破坏 scp/sftp/sftp 协议握手(报 Received message too long)。
修复:
# 注释掉 fastfetch 行
ssh server "sed -i 's/^fastfetch/#fastfetch/' ~/.config/fish/config.fish"
替代方案(不修改远端配置):
# 用 base64 中转(绕开 shell 输出干扰)
ssh -o StrictHostKeyChecking=no -p PORT user@host 'cat /remote/path/file.mp4 | base64 -w0' 2>/dev/null | base64 -d > /local/path/file.mp4
关键: 2>/dev/null 丢弃 stderr 上的任何残留输出,base64 -d 从 stdin 解码。
项目根目录: /data/comfy/manhua/
manhua/
├── workflows/ # API 格式工作流 JSON
│ ├── flux_t2i.json # Flux 2 Klein 文生图
│ ├── zit_t2i.json # Z Image Turbo 文生图
│ ├── qwen_edit.json # Qwen 图像编辑
│ ├── wan_i2v.json # Wan2.2 图生视频 (双模型级联 I2V)
│ └── index_tts.json # IndexTTS2 TTS
│ # (GUI 格式工作流在 ComfyUI/user/default/workflows/ 下)
│ # video_wan2_2_14B_flf2v.json — 首尾帧 FLF2V
│ # SkyReels-V3双图参考.json — SkyReels V3 R2V
├── scripts/
│ ├── orchestrator.py # 主编排器
│ ├── character_manager.py # 角色管理
│ ├── storyboard_gen.py # 分镜格式文档
│ └── cron_runner.py # cron 定时执行
├── series/ # 生成结果
├── characters/ # 角色资产
├── pending/ # 待处理 storyboard
└── series_config.json # 系列配置
模型架构:
Subgraph 扁平化:
原始工作流 (03_video_wan2_2_14B_i2v_subgraphed.json) 是 ComfyUI 0.4 subgraph 格式。节点 116 是子图包装器(98e3bef8-...),内部包含 16 个节点(84-104)。API 格式需要展开成 16 个独立节点 + 额外 SaveVideo 节点。
API 工作流节点清单(已扁平化的 wan_i2v.json):
┌─ 95 UNETLoader(high_noise) ─┐
│ └→ 101 LoraLoaderModelOnly(high) │
│ └→ 104 ModelSamplingSD3(shift) │
│ └───┐ │
├─ 96 UNETLoader(low_noise) ─┤│
│ └→ 102 LoraLoaderModelOnly(low) ││
│ └→ 103 ModelSamplingSD3(shift) ││
│ └───┐ ││
│ ├──┐ ││
│ 84 CLIPLoader ─→ 93 CLIPTextEncode(+p) │
│ └→ 89 CLIPTextEncode(-p) │
│ └──┐ │
│ 90 VAELoader ─────────────┤ │
│ 97 LoadImage ─────────────┤ │
│ ↓ │ │
│ 98 WanImageToVideo ───────┤ │
│ ├→[0]positive ─→ 86 KSampler(high, ena) │
│ ├→[1]negative ─→ 86 │
│ └→[2]latent ─→ 86 │
│ ↓ high latent │
│ 85 KSampler(low, dis) │
│ ↓ low latent │
│ 87 VAEDecode │
│ ↓ frames │
│ 94 CreateVideo(fps=16) │
│ ↓ VIDEO │
│ [+id] SaveVideo(codec=h264) │
KSampler 步数设置(关键!参数错了视频只出首帧后灰屏):\n| 节点 | add_noise | start_at_step | end_at_step | return_with_leftover_noise | 用途 |\n|------|-----------|---------------|-------------|---------------------------|------|\n| 86 (high) | enable | 0 | 2 | enable | 高噪: 第1-2步(共4步), 加噪, 保留残噪供低噪精修 |\n| 85 (low) | disable | 2 | 4 | disable | 低噪: 第3-4步(共4步), 不加噪, 从残噪精修到清晰 |\n注意: API JSON 中这些值是绝对整数(end_at_step=2 不是 2/4)。scheduler 必须用 simple 非 normal。cfg=1.0, sampler_name=euler。
视频输出节点(2026-04-28 更新):使用 VHS_VideoCombine
SaveVideo 需要 VIDEO 类型输入,但工作流中只有 CreateVideo 输出 VIDEO。改用 VHS_VideoCombine(VideoHelperSuite),它接受 IMAGE 类型,直接连接到 VAE Decode(87)。
# VHS_VideoCombine 节点配置(ID=105,已永久内置)
{
"class_type": "VHS_VideoCombine",
"inputs": {
"images": ["87", 0], # ← 直接连 VAE Decode!CreateVideo 已移除
"frame_rate": 16,
"loop_count": 0,
"filename_prefix": "manhua-video",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 19,
"save_metadata": true,
"pingpong": false,
"save_output": true,
}
}
输出位置:outputs["gifs"] — 每个条目含 filename、subfolder、fullpath、format、frame_rate 等字段。
现在工作流结构: VAE Decode(87) → VHS_VideoCombine(105) → MP4
API 提交步骤:
api/object_info/WanFirstLastFrameToVideo 确认节点可用/promptoutputs["images"] 提取视频文件由社区创作者 Phr00t 开发的单文件合并模型,将 WAN 2.2 + CLIP + VAE + 加速器合并为一个 24.3GB 的 checkpoint。支持 T2V、I2V、首尾帧(FLF)全部模式。比分开加载的双模型级联快 ~4 倍。
# ❌ huggingface.co 被墙,必须用国内镜像
cd /data/comfy/ComfyUI/models/checkpoints
mkdir -p WAN && cd WAN
wget -c -O wan2.2-rapid-mega-aio-v1.safetensors \
'https://hf-mirror.com/Phr00t/WAN2.2-14B-Rapid-AllInOne/resolve/main/Mega-v1/wan2.2-rapid-mega-aio-v1.safetensors'
下载后 ComfyUI 需要重启或等待自动扫描。验证:CheckpointLoaderSimple 的 ckpt_name 列表中会出现 WAN/wan2.2-rapid-mega-aio-v1.safetensors。
HF 仓库中有官方工作流 JSON:Rapid-AIO-Mega.json(加载后调整 prompt 和输入图片)
| 节点 | 用途 |
|---|---|
CheckpointLoaderSimple | 加载 checkpoint(路径 WAN/wan2.2-rapid-mega-aio-v1.safetensors) |
WanVaceToVideo | 核心生成节点,输出 positive/negative conditioning + latent |
WanVideoVACEStartToEndFrame | 首尾帧控制节点,生成首尾帧间的插值序列 |
ModelSamplingSD3 | 设置 shift(T2V=8.0) |
KSampler | 4步采样,CFG=1,ipndm,sgm_uniform |
| 模式 | strength | 需要的图像 | 说明 |
|---|---|---|---|
| T2V | 0.0 | 无 | 纯文本生成,无需加载图片和 VACE 节点 |
| I2V | 1.0 | start_image | 参考图生成视频,需 VACE 节点控制 |
| FLF | 1.0 | start_image + end_image | 首尾帧插值,需 VACE 节点+两图 |
| 混合 | 0.0~1.0 | 可选 | 介于之间的控制强度 |
wf = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "WAN/wan2.2-rapid-mega-aio-v1.safetensors"}},
# 注意:CheckpointLoaderSimple 输出索引: [0]=MODEL, [1]=CLIP, [2]=VAE
"2": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["1", 1]}},
"3": {"class_type": "CLIPTextEncode", "inputs": {"text": "", "clip": ["1", 1]}}, # CFG=1 时负prompt留空
"4": {"class_type": "WanVaceToVideo", "inputs": {
"positive": ["2", 0], "negative": ["3", 0],
"vae": ["1", 2],
"width": 512, "height": 512, "length": 81, "batch_size": 1,
"strength": 0.0 # T2V模式
}},
"5": {"class_type": "ModelSamplingSD3", "inputs": {"model": ["1", 0], "shift": 8.0}},
"6": {"class_type": "KSampler", "inputs": {
"model": ["5", 0], "positive": ["4", 0], "negative": ["4", 1],
"latent_image": ["4", 2],
"seed": 42001, "steps": 4, "cfg": 1.0,
"sampler_name": "ipndm", "scheduler": "sgm_uniform", "denoise": 1.0
}},
"7": {"class_type": "VAEDecode", "inputs": {"samples": ["6", 0], "vae": ["1", 2]}},
"8": {"class_type": "VHS_VideoCombine", "inputs": {
"images": ["7", 0], "frame_rate": 16, "loop_count": 0,
"filename_prefix": "rapid_out", "format": "video/h264-mp4",
"pingpong": False, "save_output": True
}}
}
相比 T2V 增加 LoadImage + VACE 节点,strength=1.0:
# 先上传图片
start_name = upload_image_via_api("input.png")
int_node = "3" # INTConstant(81)
wf = {
# ... 同 T2V 的 CheckpointLoaderSimple + CLIPTextEncode ...
"2": {"class_type": "LoadImage", "inputs": {"image": start_name, "upload": "image"}},
"3": {"class_type": "INTConstant", "inputs": {"int": 81}},
"4": {"class_type": "WanVideoVACEStartToEndFrame", "inputs": {
"num_frames": [int_node, 0], "empty_frame_level": 0.5,
"start_image": ["2", 0]
}},
# WanVaceToVideo: strength=1.0, control_video=["4", 0], control_masks=["4", 1]
# ... 同 T2V 的 KSampler + VAEDecode + VHS_VideoCombine ...
}
| 参数 | 值 | 说明 |
|---|---|---|
| 分辨率 | 512×512 | 可用更高,但会显著增加显存和耗时 |
| 帧数 | 81(~5s @ 16fps) | 通过 INTConstant 节点设置 |
| 步数 | 4 | 这是加速后的最优值 |
| CFG | 1.0 | 留空负prompt |
| Sampler | ipndm | 配合 sgm_uniform scheduler |
| VACE strength | 0.5 | controls首尾帧的控制强度 |
| 模型 shift | 8.0 | 视频用的标准值 |
| 模式 | 分辨率 | 帧数 | 耗时 |
|---|---|---|---|
| T2V, 4步 | 512×512 | 81 | ~5min |
| I2V, 4步 | 512×512 | 81 | ~5-6min(含图片上传) |
| FLF, 4步 | 512×512 | 81 | ~6-7min(含两张图) |
对比 FLF2V Default 20步 640×640: ~20min。Rapid AIO 快约 4 倍。
使用 VHS_VideoCombine(而非 SaveVideo/CreateVideo),outputs["gifs"] 中获取 mp4 路径。
输出文件名示例: rapid_out_00001_.mp4
工作流文件: video_wan2_2_14B_flf2v.json(ComfyUI GUI 格式,含两个子管线)
首尾帧必须不同:FLF2V 的核心价值是插值两张不同的图像。如果用同一张图做首尾帧,模型没有变化可生成,输出基本静止(同 loop 1 张图)。首帧和尾帧至少要有构图、角度、或场景元素的差异,FLF2V 才能产生有意义的运动。
两帧差异要可过渡:首尾帧差距不宜过大。Wan2.2 能处理风格转换(如油画→写实)和运镜变化(广角→近景),但不能凭空生成完全不存在的内容。设计时应确保两帧之间有视觉连续性。
FLF2V 做镜头间过渡:这是 FLF2V 的优势用法——用相邻镜头建立过渡:
镜头N尾帧 ──FLF2V 1s──→ 镜头N+1首帧
过渡手法包括:
当用 FLF2V 构建完整的视频故事(而非单段特效)时,需要重新设计分镜结构,不能套用传统 T2I 分镜逻辑。
核心结构:
每镜 = [首帧图A] ──FLF2V 5s──→ [尾帧图B] (主体内容)
│
▼ 过渡 1s (FLF2V)
│
每镜 = [首帧图C] ──FLF2V 5s──→ [尾帧图D] (下一个镜头)
设计流程:
生成顺序:
参数速查:
| 类型 | 模式 | 帧数 | 时长 | 耗时估算 |
|---|---|---|---|---|
| 主体(24镜) | Lightning 4步 | 81帧 | 5s | ~3min/镜 |
| 动效镜头(如建筑溶解) | Default 20步 | 81帧 | 5s | ~10min/镜 |
| 过渡(23次) | Lightning 4步 | 17帧 | 1s | ~1min/过渡 |
| 总耗时(24镜+23过渡) | - | - | ~2min23s | ~2-3h |
过渡手法详解:
搭配 Flux 生成首尾帧的技巧:
核心节点: WanFirstLastFrameToVideo(ComfyUI 0.3.48+ 内置,comfy_extras.nodes_wan)
用途: 给定首帧(start_image)和尾帧(end_image),生成两帧之间的平滑过渡视频
输入输出:
Inputs: positive(CONDITIONING), negative(CONDITIONING), vae(VAE),
start_image(IMAGE, optional), end_image(IMAGE, optional),
width(INT, 16-16384, step=16), height(INT, 16-16384, step=16),
length(INT, 1-16384, step=4, default=81), batch_size(INT, 1-4096)
Outputs: positive(CONDITIONING, slot=0), negative(CONDITIONING, slot=1),
latent(LATENT, slot=2)
双管线架构(工作流默认 Default 模式激活,Lightning 模式 bypassed):
| 模式 | 激活方式 | steps | cfg | shift | sampler | scheduler | 高噪→低噪 | LoRA |
|---|---|---|---|---|---|---|---|---|
| Default (20步) | 默认激活 ✅ | 20 | 4 | 8.0 | euler | simple | 0→10 / 10→10000 | 无 |
| Lightning (4步) | Ctrl+B 启用 | 4 | 1 | 5.0 | euler | simple | 0→2 / 2→10000 | lightx2v |
API 格式关键节点清单(Default 模式,已验证 ✅ 输出 640×640, 81帧, 5s, H.264):
wf = {
"1": LoadImage(start_image), # 如 "wan22_14B_flf2v_start_image.png"
"2": LoadImage(end_image), # 如 "wan22_14B_flf2v_end_image.png"
"3": CLIPLoader("umt5_xxl_fp8_e4m3fn_scaled.safetensors", "wan", "default"),
"4": CLIPTextEncode(clip="3", text=""), # 正prompt
"5": CLIPTextEncode(clip="3", text=neg_prompt), # 负prompt
"6": UNETLoader("wan2.2_i2v_high_noise_14B_fp8_scaled...", "default"),
"7": ModelSamplingSD3(model="6", shift=8.0),
"8": UNETLoader("wan2.2_i2v_low_noise_14B_fp8_scaled...", "default"),
"9": ModelSamplingSD3(model="8", shift=8.0),
"10": VAELoader("wan_2.1_vae.safetensors"),
"11": WanFirstLastFrameToVideo(
positive="4", negative="5", vae="10",
start_image="1", end_image="2",
width=640, height=640, length=81, batch_size=1),
"12": KSamplerAdvanced(
model="7", positive="11", negative="11", latent_image="11",
add_noise="enable", steps=20, cfg=4,
sampler_name="euler", scheduler="simple",
start_at_step=0, end_at_step=10,
return_with_leftover_noise="enable"),
"13": KSamplerAdvanced(
model="9", positive="11", negative="11", latent_image="12",
add_noise="disable", steps=20, cfg=4,
sampler_name="euler", scheduler="simple",
start_at_step=10, end_at_step=10000,
return_with_leftover_noise="disable"),
"14": VAEDecode(samples="13", vae="10"),
"15": CreateVideo(images="14", fps=16),
"16": SaveVideo(video="15", filename_prefix="flf2v", format="mp4", codec="h264"),
}
参数坑(和 I2V 一致):
"simple" 非 "normal"start_at_step / end_at_step 是绝对值(不是 2/4 这种分数)return_with_leftover_noise="enable", 低噪段 ="disable"Lightning 模式(4步)API 差异:
video_wan2_2_14B_flf2v.json 中 bypassed 组已知参考图:
wan22_14B_flf2v_start_image.png (2.1MB)wan22_14B_flf2v_end_image.png (1.7MB)a1a42643e0d1f6f93b423952bac4be4b.jpg (1.1MB, 旧默认首帧)d2d66521a31cdbf7c4c1c2dd12680cda.jpg (109KB, 旧默认尾帧)工作流文件: SkyReels-V3双图参考.json(ComfyUI GUI 格式)
核心节点: WanPhantomSubjectToVideo(comfy_extras.nodes_wan)
模型: Wan21_SkyReelsV3-R2V_fp8_scaled_mixed.safetensors (13.5GB)
LoRA: wan2.1_i2v_lora_rank64_lightx2v_4step.safetensors (705MB)
VAE: Wan2_1_VAE_fp32.safetensors (484MB)
用途: 参考图到视频(Reference to Video, R2V)。接受两张图像(ImageBatchMulti 合并),生成基于参考图的视频
输入输出(WanPhantomSubjectToVideo):
Inputs: positive(CONDITIONING, required), negative(CONDITIONING, required),
vae(VAE, required),
images(IMAGE, optional — 多帧参考图 batch),
width(INT, 16-16384, step=16), height(INT, 16-16384, step=16),
length(INT, 1-16384, step=4, default=81), batch_size(INT, 1-4096)
Outputs: positive(CONDITIONING, slot=0), negative_text(CONDITIONING, slot=1),
negative_img_text(CONDITIONING, slot=2), latent(LATENT, slot=3)
管线架构:
Image 1 ──┐
├→ ImageBatchMulti(inputcount=2) → ImageScaleByAspectRatio V2(4:3, crop, lanczos) → images
Image 2 ──┘
WanPhantomSubjectToVideo
UNETLoader(SkyReelsV3) → PathchSageAttentionKJ(auto) → ModelPatchTorchSettings(enable_tf32)
→ LoraLoaderModelOnly(strength=1.0) → ModelSamplingSD3(shift=8)
→ [模型进入 WanPhantomSubjectToVideo]
→ KSampler(steps=8, cfg=1, euler, simple, denoise=1)
→ VAEDecode
→ easy cleanGpuUsed
→ VHS_VideoCombine(fps=24, h264-mp4, save_output=true)
默认参数: 832×480, 81帧, 8步, cfg=1, shift=8
已验证 ✅: 历史记录显示已成功生成 sky-v3_00001.mp4 ~ sky-v3_00003.mp4 (通过 GUI 运行)
⚠️ 注意: 原始工作流中 VHS_VideoCombine 的 save_output=false(预览模式)。API 提交时需改为 true,否则视频存为 temp 会丢失。
API 格式关键节点清单:
wf = {
"1": LoadImage("z-image-turbo_00017_.png"), # 参考图1
"2": LoadImage("z-image-turbo_00018_.png"), # 参考图2
"3": ImageBatchMulti(image_1="1", image_2="2", inputcount=2),
"4": LayerUtility: ImageScaleByAspectRatio V2(image="3", aspect="4:3", scale=1, ...),
"5": CLIPLoader("umt5_xxl_fp8_e4m3fn_scaled.safetensors", "wan", "default"),
"6": CLIPTextEncode(clip="5", text=positive_prompt),
"7": CLIPTextEncode(clip="5", text=negative_prompt),
"8": UNETLoader("Wan21_SkyReelsV3-R2V_fp8_scaled_mixed.safetensors", "default"),
"9": PathchSageAttentionKJ(model="8", attention="auto", patch=False),
"10": ModelPatchTorchSettings(model="9", enable_tf32=True),
"11": LoraLoaderModelOnly(model="10", lora_name="wan2.1_i2v_lora_rank64_lightx2v_4step.safetensors", strength_model=1.0),
"12": ModelSamplingSD3(model="11", shift=8.0),
"13": VAELoader("Wan2_1_VAE_fp32.safetensors"),
"14": WanPhantomSubjectToVideo(
positive="6", negative="7", vae="13", images="4",
width=832, height=480, length=81, batch_size=1),
"15": KSampler(model="12", positive="14:0", negative="14:1", latent_image="14:3",
seed=..., steps=8, cfg=1, sampler_name="euler", scheduler="simple", denoise=1),
"16": VAEDecode(samples="15", vae="13"),
"17": easy cleanGpuUsed(anything="16"),
"18": VHS_VideoCombine(images="17", frame_rate=24, format="video/h264-mp4",
filename_prefix="skyv3", save_output=True),
}
注意: WanPhantomSubjectToVideo 输出有 4 个 slot:
已知参考图:
z-image-turbo_00017_.png (1.0MB)z-image-turbo_00018_.png (1.4MB)/prompt/history/{prompt_id} 直到完成outputs["gifs"] 提取视频路径,fullpath 字段可直接使用模型位置: /data/comfy/ComfyUI/models/IndexTTS-2/ (3.5GB gpt.pth + 1.2GB s2mel.pth + campplus_cn_common.bin 声纹提取)
节点清单:
easy downloadIndexTTSAndLoadModel — 加载模型easy indexTTSGenerate / easy indexTTSGenerateSimple — 生成 TTSeasy indexTTSEmotionVector — 8维情感向量(Happy/Angry/Sad/Fear/Hate/Low/Surprise/Neutral)easy indexTTSEmotionText — 文本描述情感easy indexTTSAudioMerge — 合并多段音频ReferenceTimbreAudio — 声纹参考音频输入(5~15秒干净录音即可克隆)IndexTTSEmotionOptionsNode — 情感选项声音克隆 API 工作流(已验证 ✅):
wf = {
"1": {
"class_type": "easy downloadIndexTTSAndLoadModel",
"inputs": {"model": "IndexTTS-2", "download_from": "modelscope"}
},
"2": {
"class_type": "LoadAudio",
"inputs": {"audio": "reference.wav"} # ⚠️ 必须 WAV!MP3 会报 Header missing
},
"3": {
"class_type": "easy indexTTSGenerateSimple",
"inputs": {
"indextts_model": ["1", 0],
"text": "目标文本...",
"unload_model": False,
"seed": 42,
"reference_audio": ["2", 0]
}
},
"4": {
"class_type": "SaveAudioMP3",
"inputs": {
"audio": ["3", 0],
"filename_prefix": "output_name",
"quality": "320k"
}
}
}
# 提交: POST /prompt {"prompt": wf, "client_id": "..."}
# 轮询: GET /history/{prompt_id}
# 输出: /data/comfy/ComfyUI/output/output_name_00001_.mp3
节点输入速查:
| 节点 | required inputs | optional inputs |
|---|---|---|
easy downloadIndexTTSAndLoadModel | model (COMBO), download_from (COMBO) | start (*) |
easy indexTTSGenerateSimple | indextts_model, text, unload_model, seed | reference_audio, reference_audios, emotions |
LoadAudio | audio (COMBO, 从 input/ 目录选) | — |
SaveAudioMP3 | audio, filename_prefix, quality | — |
坑点:
LoadAudio 节点会报 Header missing / InvalidDataError。用 ffmpeg -i ref.mp3 -ar 16000 -ac 1 ref.wav 转换{"1": {...}, "2": {...}} 不是 {"nodes": [...]} GUI 格式SaveAudioMP3 有 quality 参数:可选 V0/128k/320k,是 required input(容易漏)len(text) / 104 * 60 估算秒数再精简单词数性能参考(A10):
| 指标 | 值 |
|---|---|
| 朗读速度 | ~104 wpm (265s / 460词) |
| 4000 字符生成耗时 | ~5 min |
| 输出质量 | 320k MP3, 5MB/4.5min |
声音克隆工作流:
LoadAudio 加载参考音频(WAV 格式) → 提取声纹easy indexTTSGenerateSimple 输入目标文本 + 声纹 → 输出克隆语音API 调用: 通过 SSH 隧道 localhost:18188 直接调。用 execute_code + terminal 提交工作流。
服务器端轮询: 对长任务(>5min),用 systemd-run --pipe --collect --wait 在服务器后台执行轮询脚本,避免 SSH 超时。
⚠️ execute_code 的 terminal() 不支持 input 参数——不要写 terminal("scp ...", input=payload),会报 TypeError。正确做法:先 write_file 写临时文件,再 scp 上传。
模型位置: /data/comfy/ComfyUI/models/sonic/(全部就绪 ✅)
unet.pth — 主模型yoloface_v5m.pt — 人脸检测audio2bucket.pth + audio2token.pth — 音频特征提取whisper-tiny/ — 语音转特征RIFE/ — 帧插值(流畅度提升)核心节点:
SONICTLoader — 加载 Sonic 模型 + 人脸检测器SONIC_PreData — 预处理:输入图片 + 音频 → 驱动参数SONICSampler — 采样生成说话视频帧用途: 给定一张肖像照 + 音频文件 → 生成说话数字人视频。适合口播、讲解类内容。
⚠️ 前置依赖: Sonic 需要 SD/SDXL 系列 checkpoint 作为基模型(通过 SONICTLoader.model 输入)。服务器上目前只有 3D 和视频模型(hunyuan3d, LTX, Wan),没有 SD/SDXL 基模型,因此 Sonic 无法直接使用。需要先下载 SD1.5 或 SDXL 模型(~7GB)。
参考工作流: /data/comfy/ComfyUI/custom_nodes/ComfyUI_Sonic/examples/
| 节点组 | 关键节点 | 状态 |
|---|---|---|
| ElevenLabs | ElevenLabsTextToSpeech, ElevenLabsInstantVoiceClone, ElevenLabsSpeechToSpeech, ElevenLabsVoiceSelector | 节点已安装,需 API key |
| ChatterBox | ChatterBoxVoiceCapture, ChatterBoxAudioAnalyzer, ChatterBoxF5TTSEditOptions | zip 存在但未解压安装 |
| Qwen3 TTS | Qwen3TTSVoiceDesignerNode | 节点已安装 |
| 通用 TTS | SpeechSynthesis, SpeechRecognition, SenseVoiceNode | 节点已安装 |
| MelBand RoFormer | MelBandRoformer_fp32 模型已就绪 | 音频分离/增强 |
| 节点/方案 | 模型状态 | 用途 |
|---|---|---|
| ComfyUI_Sonic | ✅ 模型全 | 音频驱动面部动画(最推荐) |
| SadTalker | ✅ 已安装+模型全 | 单图+音频→说话视频,3D 头部运动+口型。已通过 ComfyUI API 验证 ✅ |
| FantasyTalking | ❌ 模型未下载 | 数字人说话 |
| MultiTalk | ❌ 模型未下载 | 多人数字人 |
| WanInfiniteTalk | ⚠️ patch 有 (Wan2_1-InfiniTetalk-Single_fp16.safetensors) | Wan 视频模型扩展 |
| WanVideoImageToVideoMultiTalk | ⚠️ 节点存在 | Wan 多说话人 I2V |
| ChatterBox | ❌ zip 未解压 | 声音克隆 + 表情编辑 |
通过 SSH 隧道 localhost:18188 直接调用 API。用 execute_code + terminal 提交工作流。
也可以在本地用 curl 直接调 localhost:18188。
ComfyUI 自定义节点: haomole/Comfyui-SadTalker(GitHub)
安装步骤:
cd /data/comfy/ComfyUI/custom_nodes
git clone https://github.com/haomole/Comfyui-SadTalker.git
# ⚠️ 不要 touch nodes/__init__.py!空 __init__.py 会遮蔽父包的 NODE_CLASS_MAPPINGS
# nodes/ 目录应该作为 namespace package(无 __init__.py),让 ComfyUI 直接导入子模块
Python 3.12 兼容性修复(必须!否则节点无法加载):
SadTalker 依赖 gfpgan → basicsr,而 basicsr 使用了 Python 3.12 中移除的 API:
# 1. 修复 basicsr 的 distutils 导入(Python 3.12 已移除 distutils)
sed -i 's/from distutils.version import LooseVersion/from packaging.version import Version as LooseVersion/' \
/data/comfy/comfy-env/lib/python3.12/site-packages/basicsr/archs/arch_util.py
# 2. 修复 basicsr 的 torchvision API 变更
sed -i 's/from torchvision.transforms.functional_tensor import rgb_to_grayscale/from torchvision.transforms.functional import rgb_to_grayscale/' \
/data/comfy/comfy-env/lib/python3.12/site-packages/basicsr/data/degradations.py
# 3. 安装 gfpgan(需走代理,huggingface 被墙)
HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 \
/data/comfy/comfy-env/bin/pip install gfpgan facexlib
节点注册修复(关键!否则 ComfyUI 不加载 SadTalkerNode):
SadTalker 的 __init__.py 使用相对导入(from .nodes.SadTalkerNode import ...),但 ComfyUI 通过 spec_from_file_location 加载时 __package__ 未正确设置,导致 ImportError: attempted relative import with no known parent package。
同时 nodes/SadTalkerNode.py 内部有 from ..SadTalker.src.Pb import SadTalker 也会失败。
修复方案:全部改为 sys.path.insert 绝对导入:
# __init__.py(放在 Comfyui-SadTalker/ 根目录)
_base = os.path.dirname(__file__)
sys.path.insert(0, _base)
sys.path.insert(0, os.path.join(_base, "nodes"))
sys.path.append(os.path.join(_base, "SadTalker"))
from ShowText import ShowText
from ShowVideo import ShowVideo
# ...
# nodes/SadTalkerNode.py(修改第 12 行附近)
# 将: from ..SadTalker.src.Pb import SadTalker
# 改为:
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from SadTalker.src.Pb import SadTalker
模型下载(~2.4GB, 8 个文件):
# SadTalker 主模型 → Comfyui-SadTalker/SadTalker/checkpoints/
# huggingface.co 被墙,用 hf-mirror.com 镜像
SD_CHECKPOINTS=/data/comfy/ComfyUI/custom_nodes/Comfyui-SadTalker/SadTalker/checkpoints
# 4 个 SadTalker 模型 (~1.7GB)
wget -O $SD_CHECKPOINTS/SadTalker_V0.0.2_256.safetensors \
https://hf-mirror.com/vinthony/SadTalker-V002rc/resolve/main/SadTalker_V0.0.2_256.safetensors
wget -O $SD_CHECKPOINTS/SadTalker_V0.0.2_512.safetensors \
https://hf-mirror.com/camenduru/SadTalker/resolve/main/new/checkpoints/SadTalker_V0.0.2_512.safetensors
wget -O $SD_CHECKPOINTS/mapping_00109-model.pth.tar \
https://github.com/OpenTalker/SadTalker/releases/download/v0.0.2-rc/mapping_00109-model.pth.tar
wget -O $SD_CHECKPOINTS/mapping_00229-model.pth.tar \
https://github.com/Winfredy/SadTalker/releases/download/v0.0.2/mapping_00229-model.pth.tar
# 4 个 GFPGAN 模型 → ComfyUI/gfpgan/weights/ (~700MB)
GFP_WEIGHTS=/data/comfy/ComfyUI/gfpgan/weights
wget -O $GFP_WEIGHTS/alignment_WFLW_4HG.pth \
https://github.com/xinntao/facexlib/releases/download/v0.1.0/alignment_WFLW_4HG.pth
wget -O $GFP_WEIGHTS/detection_Resnet50_Final.pth \
https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth
wget -O $GFP_WEIGHTS/GFPGANv1.4.pth \
https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth
wget -O $GFP_WEIGHTS/parsing_parsenet.pth \
https://github.com/xinntao/facexlib/releases/download/v0.2.2/parsing_parsenet.pth
节点: SadTalkerNode(类别: "SadTalker")
输入: image(IMAGE), audio(AUDIO), poseStyle(INT 0-46), faceModelResolution("256"/"512"), preprocess("crop"/"resize"/"full"/"extcrop"/"extfull"), stillMode(BOOL), gfpganAsFaceEnhancer(BOOL)
输出: video_path(STRING), show_video_path(STRING) — 视频文件路径
工作流 (API 格式):
wf = {
"1": {"class_type": "LoadImage", "inputs": {"image": "portrait.jpg"}},
"2": {"class_type": "LoadAudio", "inputs": {"audio": "narration.wav"}},
"3": {
"class_type": "SadTalkerNode",
"inputs": {
"image": ["1", 0],
"audio": ["2", 0],
"poseStyle": 0,
"faceModelResolution": "256", # 256 更快,512 更清晰
"preprocess": "crop",
"stillMode": True,
"batchSizeInGeneration": 2,
"gfpganAsFaceEnhancer": False, # 设为 False(GFPGAN 不稳定)
"useIdleMode": False,
"idleModeTime": 5,
"useRefVideo": False,
"refInfo": "pose", # ⚠️ 必填!pose/blink/pose+blink/all
"useRefAudio": False,
}
},
"4": {"class_type": "ShowVideo", "inputs": {"show_video_path": ["3", 1]}},
# ⚠️ 必须加 ShowVideo 作为输出节点!SadTalkerNode 输出 STRING 不被 ComfyUI 识别为 output
}
⚠️ 关键坑点:
refInfo 是 required input(容易漏),值:pose / blink / pose+blink / all,默认 pose。漏了会报 prompt_outputs_failed_validation: required_input_missing: refInfoShowVideo 必须作为最终输出节点,否则提交报 prompt_no_outputsgfpganAsFaceEnhancer 设 False——GFPGAN 的 basicsr 依赖与 Python 3.12 不兼容Allocation on device OOM。运行前用 nvidia-smi 检查目标 GPU 空闲 ≥21GB。如果 VRAM 紧张:重启 ComfyUI 清空缓存、加 --lowvram、换 CUDA_VISIBLE_DEVICES=N 切换 GPUecho '...' > /tmp/st.json && scp && ssh 'curl -d @/tmp/st.json' 避免 shell 转义地狱。不要用 execute_code 的 terminal(input=...)——terminal() 不支持 input 参数GPU VRAM 僵尸进程排查:
# 查看 GPU 上所有进程(含已死但未释放 VRAM 的僵尸)
nvidia-smi --query-compute-apps=pid,name,used_memory,gpu_bus_id --format=csv
# [Not Found] = 进程已死但 VRAM 未释放
# fuser -v /dev/nvidia0 查看哪些 PID 持有 GPU 文件句柄
# 如果 GPU 重置被拒:先停掉所有使用该 GPU 的进程,再 sudo nvidia-smi --gpu-reset -i N
# 注意:僵尸 VRAM 通常无法通过 nvidia-smi --gpu-reset 清理(需要完全释放 CUDA context)
# 最可靠方案:换一张干净的 GPU(CUDA_VISIBLE_DEVICES=N)
**⚠️ 安装后需重启 ComfyUI** 才能加载节点。重启方式见下。
### ComfyUI 进程管理 (systemd-run 模式)
**⚠️ SSH 后台进程绝对不可靠**——`terminal(background=true)` 起 `ssh ... &` 会在 SSH 断开时杀死所有子进程。**唯一可靠的方式是 `systemd-run --user`**,它创建独立的 systemd 单元,不受 SSH 会话生命周期影响。
```bash
# 启动 ComfyUI(长期运行服务,用 -d detach)
ssh server 'systemd-run --user --unit=comfyui -d --working-directory=/data/comfy/ComfyUI \
/data/comfy/comfy-env/bin/python3 main.py --listen 0.0.0.0 --port 8188'
# 切换 GPU(VRAM 紧张时换卡)
ssh server 'systemctl --user stop comfyui; CUDA_VISIBLE_DEVICES=1 systemd-run --user --unit=comfyui \
--setenv=CUDA_VISIBLE_DEVICES=1 -d --working-directory=/data/comfy/ComfyUI \
/data/comfy/comfy-env/bin/python3 main.py --listen 0.0.0.0 --port 8188 --lowvram'
# 重启(如安装新 custom node 后)
ssh server 'systemctl --user restart comfyui'
# 查看状态 / 日志
ssh server 'systemctl --user status comfyui'
ssh server 'journalctl --user -u comfyui -n 50 --no-pager'
# 运行一次性任务(如模型下载、轮询脚本)—— --pipe --collect --wait 会等任务结束
ssh server 'systemd-run --user --unit=my-task --pipe --collect --wait \
/data/comfy/comfy-env/bin/python3 /tmp/my_script.py'
# 清理失效的 unit(同名 unit 再次启动会报 \"already loaded\")
ssh server 'systemctl --user reset-failed'
坑点:
--pipe --collect --wait 适合有明确终点的任务(模型下载、脚本执行)。长运行服务用 -d(detach)already loaded,需先 systemctl --user stop + reset-failed,或用不同名字(comfyui-v2, comfyui-v3...)pkill 杀 ComfyUI——pkill 会连带杀死 SSH 会话自身验证节点已加载: curl -s http://127.0.0.1:8188/api/object_info | python3 -c "import json,sys; d=json.load(sys.stdin); print('NodeName' in d)"
全流程测试 — 24 镜头分镜表,写实电影风格,自动生成到 final.mp4
| 指标 | 实际值 | 说明 |
|---|---|---|
| 分镜数 | 24 镜头 | 6幕,含3个Wan I2V视频镜头(被跳过) |
| 成功生成 | 21/24 帧 | 3个视频镜头因无SaveVideo节点被跳过 |
| 最终视频时长 | 51 秒 | 计划1m54s(缺3个视频段) |
| 视频格式 | 1024×1024, H.264, 2.3MB | 无音频/字幕 |
| 总耗时 | ~25 分钟 | 含队列等待(服务器有其他用户任务) |
| 单帧 Flux 耗时 | ~30-60 秒 | 主要取决于队列排位 |
| 编排器寿命 | ~23 min 后被杀死 | SSH 600s 上限触发 |
| 视频合成 | 手动完成 | 编排器在合成阶段被杀 |
重启全覆盖生成(tmux 后台运行),验证 fix 有效性。
| 指标 | 实际值 | 说明 |
|---|---|---|
| 24 镜头 | 运行中 | tmux send-keys 启动,非 SSH foreground |
| 修复点 | SaveVideo(105)永久加入工作流 + get_output_files 四key检查 | 编排器 generate_wan_video 补 filename_prefix patch |
| venv | 不需要 | 编排器只依赖 Python stdlib,fish 下 activate 失败不影响 |
series/rotten-reality/s1-ep1/
├── frames/ # 21 张 Flux 生成的分镜图
├── seg_0000.mp4 ~ seg_0010.mp4 # 11 个视频片段
├── final.mp4 # 最终合成视频 (2.3MB)
├── concat.txt # ffmpeg concat 清单
├── progress.json # 执行进度
└── subtitles.srt # 空文件(字幕未生成)
| 问题 | 原因 | 优先级 | 修复 |
|---|---|---|---|
| Wan I2V 视频镜头跳过(旧) | WanImageToVideo 的 LoadImage 路径不对(图没 cp 到 ComfyUI/input/) | 已修复✅ | generate_wan_video() 中已 shutil.copy2(img, ComfyUI/input/) |
| Wan I2V 视频返回 None(旧) | 工作流缺少 SaveVideo 节点 + get_output_files 只查 images 不查 videos/files/gifs | 已修复✅ | _manhua_wan_i2v.json 已永久添加 SaveVideo 节点(105);get_output_files() 已改为检查四个 key |
| 字幕文件为空 | 编排器到字幕阶段时已被杀,没走到 TTS + SRT 生成 | 高 | 编排器增加 checkpoint 断点续跑 |
| 无音频轨 | IndexTTS2 节点没被调用 | 中 | 同上,TTS 在合成阶段之前 |
| 编排器中途被杀死 | SSH foreground timeout=600s 上限,~23min 后触发 | 方案 A(推荐): tmux 后台运行。方案 B: terminal(background=true) 起 SSH nohup | |
| 编排器合成阶段中断 | 流程做到 ffmpeg concat 前被杀 | 中 | 分段 checkpoint: 每生成 5 帧写一次 progress.json |
| 缺少断点续跑 | orchestrator 没有 resume 逻辑 | 低 | progress.json 检查已生成帧,跳过已有帧继续后续 |
User Input → [Storyboard JSON] → Orchestrator → [Shot Loop] → ffmpeg Compose → MP4
│
ComfyUI API (Flux/Wan/TTS)
for shot in storyboard["shots"]:
if shot["action_level"] == "action":
# Wan2.2 I2V: 先出 Flux 参考图, 再喂 WanImageToVideo
else:
# Flux T2I: 直接提交
submit → wait_for_completion() → copy output → save progress.json
对于长耗时任务(FLF2V 20min, I2V, T2I 等),不要阻塞等待。使用 submit → verify → cron poll → notify 模式:
Step 1: 通过 SSH API 提交任务,拿到 prompt_id (3s)
Step 2: 确认队列中有任务在跑 (2s)
Step 3: 写 job 标记文件(记录 prompt_id + prefix) (即时)
Step 4: 设 cron 每 5min 轮询一次 (设置完就走)
Step 5: cron 查到完成 → 自动回复/通知 (异步)
关键脚本:/tmp/flf2v_runner.py(参考 skill 同目录下的 scripts/flf2v_runner.py)
# 提交(快速返回)
/data/comfy/comfy-env/bin/python3 /tmp/flf2v_runner.py submit <prefix> <start_img> <end_img> "<prompt_text>"
# 轮询(检查是否有完成任务)
/data/comfy/comfy-env/bin/python3 /tmp/flf2v_runner.py poll
poll 检测逻辑: 不依赖 API history outputs(可能为空),直接扫描 output/ 和 output/video/ 目录查找文件名匹配 prefix 的 mp4 文件。找到即认为完成。
配合 cron 使用(推荐):
# cronjob 定义
- action: create
name: "flf2v-poll"
schedule: "every 5m"
prompt: "Run /data/comfy/comfy-env/bin/python3 /tmp/flf2v_runner.py poll and report any DONE results"
repeat: 10 # 最多跑 10 次 ≈ 50 分钟,足够覆盖 20min 任务
坑点:
SaveVideo 的输出可能不在 outputs["videos"] 中,即使文件名以 prefix 开头。必须直接扫磁盘。方法 A — tmux send-keys(推荐 ✅ 已验证可跑完全程)
远端 shell 是 fish,source venv/bin/activate 在 fish 下会报错(bash 语法不兼容)。但编排器只依赖 Python stdlib,不需要 venv。
ssh server 'bash -c "
cd /data/comfy/manhua
tmux new-session -d -s manhua -x 200 -y 50
tmux send-keys -t manhua \"cd /data/comfy/manhua\" Enter
tmux send-keys -t manhua \"python3 scripts/orchestrator.py storyboard.json --series s --season 1 --episode 1\" Enter
echo Started in tmux session manhua
"'
# 查看进度
ssh server 'bash -c "tmux capture-pane -t manhua -p -S -30"'
# 连接到会话(交互式)
ssh -t server 'tmux attach -t manhua'
方法 B — SSH background=true
# 在服务器上保存 runner3.sh:
cat > /tmp/runner3.sh << 'SCRIPT'
#!/bin/bash
cd /data/comfy/manhua
exec /data/comfy/comfy-env/bin/python3 scripts/orchestrator.py \"$@\"
SCRIPT
# 通过 background=true 运行:
ssh server /tmp/runner3.sh storyboard.json --series s --season 1 --episode 1
| 症状 | 原因 | 修复 |
|---|---|---|
| 队列空但编排器在跑 | 工作流 JSON 不存在 | 检查 _manhua_* 文件 |
| 400 Bad Request | 节点输入字段名错了 | api/object_info/{节点类型} 对参数 |
| Wan I2V 跳过 | LoadImage 路径不对 | 图片先 cp 到 ComfyUI/input/ 用相对路径 |
| SSH 600s 超时 | 默认 foreground 上限 | 用 runner3.sh 脱离会话 |
| ffmpeg 字幕失败 | SRT 是相对路径 | 用绝对路径 subtitles=/abs/path.srt |
scp/sftp 报 Received message too long | fish shell fastfetch greeting 破坏协议 | 注释掉 config.fish 中 fastfetch 行或用 base64 管道下载 |
| ffprobe 报空 JSON | 二进制文件被 fish banner 污染 | 文件头不是 MP4 → 重新用 base64 方式传输 |
| 编排器合成阶段中断 | SSH timeout 杀死进程 | 添加 checkpoint 断点续跑;不要依赖编排器完成合成,手动补全 |
合成阶段崩溃 TypeError: expected str, not NoneType | 视频镜头生成失败后 shot["image"]=None,传给 ffmpeg -i None 直接抛异常退出 | 用 elif shot.get("image") 判空 + else 分支生成纯色占位片段 ffmpeg -f lavfi -i color=c=black:s=1024x1024:d={dur} |
| Wan I2V 视频生成返回 None(但 API 成功)— 已修复 ✅ | 两大原因:(1) 工作流无 SaveVideo 节点;(2) 即使有,get_output_files() 只查 images 不查 videos/gifs/files | 修复: |
(1) 工作流 _manhua_wan_i2v.json 已永久添加 SaveVideo 节点(105) | ||
(2) get_output_files() 已验证修复:检查四个 key(images, videos, files, gifs) | ||
Wan I2V 400 codec: 'h264_nvenc' not in ['auto', 'h264'] | SaveVideo 节点参数允许值有限 | 用 codec: 'h264' 和 format: 'mp4' |
| 输出目录不包含 image_edit.png 等 | Qwen编辑工作流输出文件名被固定 | 修改工作流 JSON 中的 filename_prefix 为唯一值 |
Wan I2V 400 codec: 'h264_nvenc' not in ['auto', 'h264'] | SaveVideo 节点参数允许值有限 | 用 codec: 'h264' 和 format: 'mp4' |
# 静态图 → 视频段
ffmpeg -y -loop 1 -i image.png -c:v libx264 -t 5 seg_0000.mp4
# 拼接
ffmpeg -y -f concat -safe 0 -i concat.txt -c:v libx264 temp.mp4
# 烧录字幕 (需绝对路径)
ffmpeg -y -i temp.mp4 -vf "subtitles=/abs/path/subtitles.srt" final.mp4