纯 ComfyUI 视频生成工作流方案设计

纯 ComfyUI 视频生成工作流方案设计

方案类型 : 纯 ComfyUI 工程 + 本地模型调用
适用场景 : 本地视频生成、批量处理、工作流自动化
技术栈: ComfyUI + Python API + 本地模型


一、方案概述

1.1 架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    ComfyUI 单机架构                           │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │  HTTP Server │  │ WebSocket    │  │  Web UI      │     │
│  │   (8188)     │  │   Server     │  │  (可视化)    │     │
│  └──────┬───────┘  └──────┬───────┘  └──────────────┘     │
│         │                 │                                 │
│         └─────────────────┼─────────────────────────────────┤
│                           │                                 │
│  ┌────────────────────────▼───────────────────────────────┐│
│  │                    ComfyUI 核心                         ││
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ ││
│  │  │  工作流引擎   │  │  节点系统     │  │  任务调度    │ ││
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘ ││
│  │         │                 │                  │          ││
│  │  ┌──────▼─────────────────┼──────────────────▼───────┐││
│  │  │           节点执行引擎                          │││
│  │  └───────────────────────────────────────────────────┘││
│  └──────────────────────────────┬────────────────────────┘│
│                                 │                           │
│  ┌──────────────────────────────▼────────────────────────┐│
│  │                    本地模型文件                        ││
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ ││
│  │  │ LTX Video    │  │ Wan 2.2      │  │ CogVideoX    │ ││
│  │  │ 8.7GB        │  │ 10GB         │  │ 6GB          │ ││
│  │  └──────────────┘  └──────────────┘  └──────────────┘ ││
│  └────────────────────────────────────────────────────────┘│
│                                                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
                    Python API 调用脚本

1.2 核心组件

组件 功能 状态
ComfyUI 服务 提供工作流执行环境 ✅ 已安装
HTTP API 提供RESTful接口 ✅ 可用
WebSocket 实时进度推送 ✅ 可用
工作流引擎 解析和执行JSON工作流 ✅ 可用
节点系统 支持100+种节点 ✅ 可用
本地模型 LTX/Wan/CogVideoX ✅ 已下载

二、工作流设计

2.1 LTX Video 工作流

工作流结构
json 复制代码
{
  "1": {
    "inputs": {
      "text_encoder": "gemma_3_12B_it_fp4_mixed.safetensors",
      "ckpt_name": "ltx-video-2b-v0.9.safetensors"
    },
    "class_type": "LTXAVTextEncoderLoader",
    "_meta": {"title": "LTX 模型加载器"}
  },
  "2": {
    "inputs": {
      "text": "${prompt}",
      "clip": ["1", 0]
    },
    "class_type": "CLIPTextEncode",
    "_meta": {"title": "正向提示词"}
  },
  "3": {
    "inputs": {
      "text": "${negative_prompt}",
      "clip": ["1", 0]
    },
    "class_type": "CLIPTextEncode",
    "_meta": {"title": "负面提示词"}
  },
  "4": {
    "inputs": {
      "width": ${width},
      "height": ${height},
      "batch_size": 1,
      "frame_count": ${frames}
    },
    "class_type": "EmptyLTXVLatentVideo",
    "_meta": {"title": "空潜空间视频"}
  },
  "5": {
    "inputs": {
      "model": ["1", 0],
      "add_noise": "enable",
      "noise_seed": ${seed},
      "steps": ${steps},
      "cfg": ${cfg},
      "sampler_name": "${sampler}",
      "scheduler": "${scheduler}",
      "positive": ["2", 0],
      "negative": ["3", 0],
      "latent_image": ["4", 0],
      "start_at_step": 0,
      "end_at_step": ${steps},
      "return_noise": "disable"
    },
    "class_type": "KSamplerAdvanced",
    "_meta": {"title": "高级采样器"}
  },
  "6": {
    "inputs": {
      "samples": ["5", 0],
      "vae": ["1", 1]
    },
    "class_type": "VAEDecodeTiled",
    "_meta": {"title": "分块 VAE 解码"}
  },
  "7": {
    "inputs": {
      "filename_prefix": "${filename_prefix}"
    },
    "class_type": "SaveVideo",
    "_meta": {"title": "保存视频"}
  }
}
节点说明
节点ID 节点类型 功能 输入
1 LTXAVTextEncoderLoader 加载LTX模型和文本编码器 模型文件路径
2 CLIPTextEncode 编码正向提示词 提示词 + CLIP
3 CLIPTextEncode 编码负面提示词 负面提示词 + CLIP
4 EmptyLTXVLatentVideo 创建空潜空间视频 分辨率 + 帧数
5 KSamplerAdvanced 执行Diffusion采样 所有输入 + 参数
6 VAEDecodeTiled 分块解码潜空间 采样结果 + VAE
7 SaveVideo 保存生成的视频 解码结果

2.2 Wan 2.2 工作流

关键节点差异
json 复制代码
{
  "1": {
    "inputs": {
      "ckpt_name": "wan2.2_fp16.safetensors"
    },
    "class_type": "CheckpointLoaderSimple"
  },
  "2": {
    "inputs": {
      "text": "${prompt}",
      "width": ${width},
      "height": ${height},
      "video_length": ${frames}
    },
    "class_type": "WanTextToVideo"
  },
  "3": {
    "inputs": {
      "model": ["1", 0],
      "positive": ["2", 0],
      "steps": ${steps},
      "cfg": ${cfg}
    },
    "class_type": "WanSampler"
  }
}

2.3 CogVideoX 工作流

关键节点差异
json 复制代码
{
  "1": {
    "inputs": {
      "ckpt_name": "CogVideoX-2b.safetensors"
    },
    "class_type": "CogVideoXLoader"
  },
  "2": {
    "inputs": {
      "text": "${prompt}",
      "num_frames": ${frames},
      "image_size": ${width}
    },
    "class_type": "CogVideoXTextToVideo"
  }
}

三、API 调用方案

3.1 基础 HTTP API

1. 发送生成请求
python 复制代码
import requests
import json

# ComfyUI 服务地址
COMFYUI_URL = "http://127.0.0.1:8188"

# 加载工作流模板
with open('ltx_workflow.json', 'r') as f:
    workflow = json.load(f)

# 填充参数
parameters = {
    "prompt": "A beautiful sunset over the ocean, cinematic lighting",
    "negative_prompt": "blurry, low quality, distorted",
    "width": 1024,
    "height": 576,
    "frames": 100,  # 4秒 @ 25fps
    "steps": 20,
    "cfg": 7.0,
    "seed": 42,
    "sampler": "euler",
    "scheduler": "simple",
    "filename_prefix": "sunset_video"
}

# 填充工作流
for node_id, node in workflow.items():
    for key, value in node.get("inputs", {}).items():
        if isinstance(value, str) and value.startswith("${"):
            param_name = value[2:-1]
            if param_name in parameters:
                workflow[node_id]["inputs"][key] = parameters[param_name]

# 发送请求
response = requests.post(
    f"{COMFYUI_URL}/prompt",
    json={"prompt": workflow}
)

prompt_id = response.json()["prompt_id"]
print(f"✅ 任务已提交,ID: {prompt_id}")
2. 查询生成进度
python 复制代码
import time

# 轮询查询结果
while True:
    response = requests.get(f"{COMFYUI_URL}/history/{prompt_id}")
    data = response.json()
    
    if prompt_id in data:
        result = data[prompt_id]
        print(f"✅ 生成完成!")
        
        # 提取结果
        outputs = result.get("outputs", {})
        for node_id, node_outputs in outputs.items():
            for output_name, output_list in node_outputs.items():
                if output_name == "video":
                    filename = output_list[0]["filename"]
                    print(f"📁 视频文件: {filename}")
                    print(f"📂 输出目录: ComfyUI/output/")
                    break
        
        break
    else:
        print(f"⏳ 生成中... 已等待 {i * 2} 秒")
        time.sleep(2)
        i += 1

3.2 WebSocket 实时进度

python 复制代码
import asyncio
import websockets
import json

async def watch_generation_progress(prompt_id):
    """监听生成进度"""
    uri = f"ws://127.0.0.1:8188/ws?clientId={prompt_id}"
    
    async with websockets.connect(uri) as websocket:
        print(f"📡 已连接到 WebSocket: {prompt_id}")
        
        while True:
            message = await websocket.recv()
            data = json.loads(message)
            
            if data.get("type") == "status":
                progress = data.get("data", {}).get("value", 0)
                node = data.get("data", {}).get("node", "")
                print(f"⏳ 节点 {node} 进度: {progress}%")
            
            elif data.get("type") == "executing":
                print(f"🔄 正在执行: {data.get('data', {}).get('node', '')}")
            
            elif data.get("type") == "execution_success":
                print(f"✅ 执行完成")
                break

# 使用
asyncio.run(watch_generation_progress(prompt_id))

3.3 完整的 Python 调用类

python 复制代码
import requests
import asyncio
import websockets
import json
import time
from pathlib import Path
from typing import Optional, Dict, Any

class ComfyUIVideoGenerator:
    """ComfyUI 视频生成器"""
    
    def __init__(self, base_url: str = "http://127.0.0.1:8188"):
        self.base_url = base_url
        self.session = requests.Session()
    
    def generate_video(
        self,
        workflow_path: str,
        parameters: Dict[str, Any],
        watch_progress: bool = True
    ) -> Dict[str, Any]:
        """
        生成视频
        
        Args:
            workflow_path: 工作流 JSON 文件路径
            parameters: 生成参数
            watch_progress: 是否监听进度
            
        Returns:
            生成结果
        """
        # 1. 加载工作流
        with open(workflow_path, 'r') as f:
            workflow = json.load(f)
        
        # 2. 填充参数
        filled_workflow = self._fill_parameters(workflow, parameters)
        
        # 3. 发送请求
        response = self.session.post(
            f"{self.base_url}/prompt",
            json={"prompt": filled_workflow}
        )
        prompt_id = response.json()["prompt_id"]
        
        print(f"🚀 任务已提交: {prompt_id}")
        
        # 4. 等待结果
        if watch_progress:
            asyncio.run(self._watch_progress(prompt_id))
        else:
            self._wait_for_completion(prompt_id)
        
        # 5. 获取结果
        result = self._get_result(prompt_id)
        
        return {
            "prompt_id": prompt_id,
            "status": "completed",
            "result": result
        }
    
    def _fill_parameters(
        self, 
        workflow: Dict[str, Any], 
        parameters: Dict[str, Any]
    ) -> Dict[str, Any]:
        """填充工作流参数"""
        filled_workflow = workflow.copy()
        
        for node_id, node in filled_workflow.items():
            for key, value in node.get("inputs", {}).items():
                if isinstance(value, str) and value.startswith("${"):
                    param_name = value[2:-1]
                    if param_name in parameters:
                        filled_workflow[node_id]["inputs"][key] = parameters[param_name]
        
        return filled_workflow
    
    def _wait_for_completion(self, prompt_id: str):
        """等待生成完成"""
        print("⏳ 等待生成完成...")
        
        i = 0
        while True:
            response = self.session.get(f"{self.base_url}/history/{prompt_id}")
            data = response.json()
            
            if prompt_id in data:
                print("✅ 生成完成!")
                break
            
            if i % 5 == 0:  # 每10秒输出一次
                print(f"⏳ 已等待 {i * 2} 秒...")
            
            time.sleep(2)
            i += 1
    
    async def _watch_progress(self, prompt_id: str):
        """监听生成进度"""
        uri = f"ws://127.0.0.1:8188/ws?clientId={prompt_id}"
        
        async with websockets.connect(uri) as websocket:
            print(f"📡 已连接到 WebSocket: {prompt_id}")
            
            while True:
                message = await websocket.recv()
                data = json.loads(message)
                
                if data.get("type") == "status":
                    progress = data.get("data", {}).get("value", 0)
                    node = data.get("data", {}).get("node", "")
                    print(f"⏳ {node}: {progress}%")
                
                elif data.get("type") == "executing":
                    print(f"🔄 {data.get('data', {}).get('node', '')}")
                
                elif data.get("type") == "execution_success":
                    print("✅ 执行完成")
                    break
    
    def _get_result(self, prompt_id: str) -> Dict[str, Any]:
        """获取生成结果"""
        response = self.session.get(f"{self.base_url}/history/{prompt_id}")
        data = response.json()
        
        result_data = data[prompt_id]
        outputs = result_data.get("outputs", {})
        
        # 提取视频文件信息
        videos = []
        for node_id, node_outputs in outputs.items():
            for output_name, output_list in node_outputs.items():
                if output_name in ["video", "output"]:
                    for item in output_list:
                        videos.append({
                            "filename": item["filename"],
                            "subfolder": item.get("subfolder", ""),
                            "type": item.get("type", "output")
                        })
        
        return {
            "videos": videos,
            "output_dir": "ComfyUI/output/",
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
        }

四、本地模型配置

4.1 模型文件结构

bash 复制代码
ComfyUI/
├── models/
│   ├── checkpoints/                      # 主模型目录
│   │   ├── ltx-video-2b-v0.9.safetensors # LTX 主模型 (8.7GB)
│   │   └── svd_xt.safetensors          # SVD 模型 (1.5GB)
│   │
│   ├── text_encoders/                    # 文本编码器目录
│   │   └── gemma_3_12B_it_fp4_mixed.safetensors # LTX 文本编码器 (8.8GB)
│   │
│   ├── clip_vision/                       # CLIP Vision 目录
│   │   └── clip_vision_g.safetensors    # CLIP Vision (1.6GB)
│   │
│   ├── loras/                             # LoRA 目录(可选)
│   │   └── put_loras_here
│   │
│   └── embeddings/                        # 嵌入向量目录(可选)
│       └── put_embeddings_or_textual_inversion_concepts_here
│
└── output/                               # 输出目录
    ├── videos/                            # 视频输出
    └── images/                            # 图片输出

4.2 模型下载脚本

python 复制代码
#!/usr/bin/env python3
"""
模型下载脚本
自动下载所需模型文件
"""

import os
import requests
from pathlib import Path
from typing import Dict, Any

class ModelDownloader:
    """模型下载器"""
    
    def __init__(self, models_dir: str = "ComfyUI/models"):
        self.models_dir = Path(models_dir)
        self.models_dir.mkdir(parents=True, exist_ok=True)
    
    def download_file(
        self, 
        url: str, 
        destination: Path,
        chunk_size: int = 8192
    ):
        """下载文件"""
        destination.parent.mkdir(parents=True, exist_ok=True)
        
        print(f"📥 下载: {destination.name}")
        print(f"🌐 URL: {url}")
        
        # 显示文件大小
        head_response = requests.head(url, allow_redirects=True)
        total_size = int(head_response.headers.get('content-length', 0))
        print(f"📏 文件大小: {total_size / (1024**3):.2f} GB")
        
        # 流式下载
        response = requests.get(url, stream=True)
        response.raise_for_status()
        
        downloaded = 0
        with open(destination, 'wb') as f:
            for chunk in response.iter_content(chunk_size=chunk_size):
                f.write(chunk)
                downloaded += len(chunk)
                
                # 显示进度
                percent = (downloaded / total_size) * 100
                print(f"\r⏳ 进度: {percent:.1f}%", end='', flush=True)
        
        print(f"\n✅ 下载完成: {destination.name}")
    
    def download_ltx_models(self):
        """下载 LTX 模型"""
        models = {
            "ltx-video-2b-v0.9.safetensors": {
                "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.safetensors",
                "dest": self.models_dir / "checkpoints"
            },
            "gemma_3_12B_it_fp4_mixed.safetensors": {
                "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/gemma_3_12B_it_fp4_mixed.safetensors",
                "dest": self.models_dir / "text_encoders"
            },
            "clip_vision_g.safetensors": {
                "url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/clip_vision_g.safetensors",
                "dest": self.models_dir / "clip_vision"
            }
        }
        
        for filename, config in models.items():
            dest_path = config["dest"] / filename
            self.download_file(config["url"], dest_path)

# 使用
if __name__ == "__main__":
    downloader = ModelDownloader()
    downloader.download_ltx_models()

4.3 模型验证脚本

python 复制代码
#!/usr/bin/env python3
"""
模型验证脚本
检查模型文件是否完整
"""

import hashlib
from pathlib import Path
from typing import Dict, Optional

class ModelValidator:
    """模型验证器"""
    
    def __init__(self, models_dir: str = "ComfyUI/models"):
        self.models_dir = Path(models_dir)
    
    def validate_ltx_models(self) -> bool:
        """验证 LTX 模型"""
        required_models = {
            "checkpoints/ltx-video-2b-v0.9.safetensors": 9_340_377_792,  # 8.7GB
            "text_encoders/gemma_3_12B_it_fp4_mixed.safetensors": 9_457_694_720,  # 8.8GB
            "clip_vision/clip_vision_g.safetensors": 1_723_286_016  # 1.6GB
        }
        
        all_valid = True
        for model_path, expected_size in required_models.items():
            full_path = self.models_dir / model_path
            
            if not full_path.exists():
                print(f"❌ 缺失: {model_path}")
                all_valid = False
                continue
            
            actual_size = full_path.stat().st_size
            size_diff = abs(actual_size - expected_size)
            tolerance = expected_size * 0.01  # 允许1%误差
            
            if size_diff > tolerance:
                print(f"❌ 大小不匹配: {model_path}")
                print(f"   期望: {expected_size / (1024**3):.2f} GB")
                print(f"   实际: {actual_size / (1024**3):.2f} GB")
                all_valid = False
            else:
                print(f"✅ 完整: {model_path} ({actual_size / (1024**3):.2f} GB)")
        
        return all_valid
    
    def get_model_summary(self) -> Dict[str, Any]:
        """获取模型摘要"""
        summary = {
            "total_size": 0,
            "model_count": 0,
            "models": []
        }
        
        for model_file in self.models_dir.rglob("*.safetensors"):
            size = model_file.stat().st_size
            summary["total_size"] += size
            summary["model_count"] += 1
            
            summary["models"].append({
                "name": model_file.name,
                "path": str(model_file.relative_to(self.models_dir)),
                "size_gb": size / (1024**3),
                "size_mb": size / (1024**2)
            })
        
        return summary

# 使用
if __name__ == "__main__":
    validator = ModelValidator()
    
    print("🔍 验证 LTX 模型...")
    is_valid = validator.validate_ltx_models()
    
    if is_valid:
        print("\n✅ 所有模型文件完整!")
    else:
        print("\n❌ 部分模型文件缺失或损坏,请重新下载")
    
    summary = validator.get_model_summary()
    print(f"\n📊 模型摘要:")
    print(f"   模型数量: {summary['model_count']}")
    print(f"   总大小: {summary['total_size'] / (1024**3):.2f} GB")

五、批量处理方案

5.1 批量视频生成

python 复制代码
import asyncio
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Any

class BatchVideoGenerator:
    """批量视频生成器"""
    
    def __init__(self, max_concurrent: int = 2):
        self.generator = ComfyUIVideoGenerator()
        self.max_concurrent = max_concurrent
    
    def generate_batch(
        self,
        prompts: List[str],
        workflow_path: str,
        common_params: Dict[str, Any],
        individual_params: Optional[List[Dict[str, Any]]] = None
    ) -> List[Dict[str, Any]]:
        """
        批量生成视频
        
        Args:
            prompts: 提示词列表
            workflow_path: 工作流文件路径
            common_params: 通用参数
            individual_params: 每个提示词的独立参数
            
        Returns:
            生成结果列表
        """
        results = []
        
        with ThreadPoolExecutor(max_workers=self.max_concurrent) as executor:
            # 提交任务
            futures = {}
            for i, prompt in enumerate(prompts):
                params = common_params.copy()
                params["prompt"] = prompt
                
                if individual_params and i < len(individual_params):
                    params.update(individual_params[i])
                
                params["filename_prefix"] = f"batch_{i:03d}"
                
                future = executor.submit(
                    self.generator.generate_video,
                    workflow_path,
                    params,
                    watch_progress=False
                )
                futures[future] = i
            
            # 收集结果
            for future in as_completed(futures):
                i = futures[future]
                try:
                    result = future.result()
                    results.append(result)
                    print(f"✅ 任务 {i+1}/{len(prompts)} 完成")
                except Exception as e:
                    print(f"❌ 任务 {i+1}/{len(prompts)} 失败: {e}")
                    results.append({
                        "index": i,
                        "status": "failed",
                        "error": str(e)
                    })
        
        return results

# 使用示例
batch_generator = BatchVideoGenerator(max_concurrent=2)

prompts = [
    "A beautiful sunset over the ocean",
    "A rainy city street at night",
    "A mountain landscape in autumn",
    "A futuristic cyberpunk city",
    "A peaceful garden with flowers"
]

results = batch_generator.generate_batch(
    prompts=prompts,
    workflow_path="ltx_workflow.json",
    common_params={
        "model": "ltx",
        "duration": 4,
        "resolution": "1024x576"
    }
)

5.2 配置文件管理

python 复制代码
import json
import yaml
from pathlib import Path
from typing import Dict, Any

class WorkflowConfig:
    """工作流配置管理"""
    
    def __init__(self, config_dir: str = "configs"):
        self.config_dir = Path(config_dir)
        self.config_dir.mkdir(parents=True, exist_ok=True)
    
    def load_config(self, config_name: str) -> Dict[str, Any]:
        """加载配置"""
        config_path = self.config_dir / f"{config_name}.yaml"
        
        with open(config_path, 'r') as f:
            return yaml.safe_load(f)
    
    def save_config(self, config_name: str, config: Dict[str, Any]):
        """保存配置"""
        config_path = self.config_dir / f"{config_name}.yaml"
        
        with open(config_path, 'w') as f:
            yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
    
    def create_ltx_config(self):
        """创建 LTX 配置"""
        config = {
            "model": "ltx",
            "workflow": "ltx_workflow.json",
            "default_parameters": {
                "width": 1024,
                "height": 576,
                "frames": 100,
                "steps": 20,
                "cfg": 7.0,
                "sampler": "euler",
                "scheduler": "simple"
            },
            "presets": {
                "cinematic": {
                    "prompt_suffix": ", cinematic lighting, 4k, highly detailed",
                    "cfg": 7.5
                },
                "anime": {
                    "prompt_suffix": ", anime style, vibrant colors, detailed",
                    "cfg": 8.0
                },
                "realistic": {
                    "prompt_suffix": ", photorealistic, highly detailed, 8k",
                    "cfg": 7.0
                }
            }
        }
        
        self.save_config("ltx", config)

# 使用
config_manager = WorkflowConfig()

# 创建配置
config_manager.create_ltx_config()

# 加载配置
ltx_config = config_manager.load_config("ltx")
print(f"📋 默认参数: {ltx_config['default_parameters']}")

# 应用预设
preset = ltx_config["presets"]["cinematic"]
prompt = "A beautiful sunset" + preset["prompt_suffix"]
cfg = preset["cfg"]

六、完整使用示例

6.1 单视频生成

python 复制代码
#!/usr/bin/env python3
"""
单视频生成示例
"""

from comfyui_video_generator import ComfyUIVideoGenerator

# 创建生成器
generator = ComfyUIVideoGenerator()

# 生成参数
parameters = {
    "prompt": "A beautiful sunset over the ocean, cinematic lighting",
    "negative_prompt": "blurry, low quality, distorted",
    "width": 1024,
    "height": 576,
    "frames": 100,  # 4秒 @ 25fps
    "steps": 20,
    "cfg": 7.0,
    "seed": 42,
    "sampler": "euler",
    "scheduler": "simple",
    "filename_prefix": "sunset_video"
}

# 生成视频
result = generator.generate_video(
    workflow_path="ltx_workflow.json",
    parameters=parameters,
    watch_progress=True
)

print(f"🎉 生成完成!")
print(f"📁 视频: {result['result']['videos'][0]['filename']}")
print(f"📂 路径: {result['result']['output_dir']}")

6.2 批量生成

python 复制代码
#!/usr/bin/env python3
"""
批量视频生成示例
"""

from batch_video_generator import BatchVideoGenerator

# 创建批量生成器
batch_generator = BatchVideoGenerator(max_concurrent=2)

# 提示词列表
prompts = [
    "A beautiful sunset over the ocean",
    "A rainy city street at night",
    "A mountain landscape in autumn",
    "A futuristic cyberpunk city"
]

# 通用参数
common_params = {
    "model": "ltx",
    "resolution": "1024x576",
    "duration": 4,
    "fps": 25,
    "steps": 20,
    "cfg": 7.0
}

# 批量生成
results = batch_generator.generate_batch(
    prompts=prompts,
    workflow_path="ltx_workflow.json",
    common_params=common_params
)

# 统计结果
success_count = sum(1 for r in results if r["status"] == "completed")
failed_count = len(results) - success_count

print(f"📊 批量生成完成:")
print(f"   总数: {len(results)}")
print(f"   成功: {success_count}")
print(f"   失败: {failed_count}")

6.3 命令行工具

python 复制代码
#!/usr/bin/env python3
"""
ComfyUI 视频生成命令行工具
"""

import argparse
import json
from pathlib import Path
from comfyui_video_generator import ComfyUIVideoGenerator

def main():
    parser = argparse.ArgumentParser(description="ComfyUI 视频生成工具")
    parser.add_argument("--prompt", required=True, help="生成提示词")
    parser.add_argument("--model", default="ltx", help="模型选择 (ltx/wan/cog)")
    parser.add_argument("--workflow", help="工作流文件路径")
    parser.add_argument("--resolution", default="1024x576", help="分辨率")
    parser.add_argument("--duration", type=int, default=4, help="时长(秒)")
    parser.add_argument("--steps", type=int, default=20, help="采样步数")
    parser.add_argument("--cfg", type=float, default=7.0, help="CFG Scale")
    parser.add_argument("--seed", type=int, default=-1, help="随机种子")
    parser.add_argument("--output", help="输出文件名前缀")
    
    args = parser.parse_args()
    
    # 解析分辨率
    width, height = map(int, args.resolution.split("x"))
    frames = args.duration * 25  # 假设 25fps
    
    # 参数字典
    parameters = {
        "prompt": args.prompt,
        "width": width,
        "height": height,
        "frames": frames,
        "steps": args.steps,
        "cfg": args.cfg,
        "seed": args.seed if args.seed >= 0 else -1,
        "filename_prefix": args.output or "generated_video"
    }
    
    # 选择工作流
    if args.workflow:
        workflow_path = args.workflow
    else:
        workflow_path = f"{args.model}_workflow.json"
    
    # 创建生成器
    generator = ComfyUIVideoGenerator()
    
    # 生成视频
    result = generator.generate_video(
        workflow_path=workflow_path,
        parameters=parameters,
        watch_progress=True
    )
    
    print(f"✅ 生成完成!")
    print(f"📁 视频: {result['result']['videos'][0]['filename']}")
    print(f"📂 路径: {result['result']['output_dir']}")

if __name__ == "__main__":
    main()

使用示例

bash 复制代码
# 基础用法
python video_cli.py \
  --prompt "A beautiful sunset over the ocean" \
  --model ltx \
  --resolution 1024x576 \
  --duration 4

# 完整参数
python video_cli.py \
  --prompt "A futuristic cyberpunk city at night" \
  --model ltx \
  --resolution 1280x720 \
  --duration 8 \
  --steps 25 \
  --cfg 7.5 \
  --seed 42 \
  --output cyberpunk_city

# 使用自定义工作流
python video_cli.py \
  --prompt "Anime style girl dancing" \
  --workflow custom_workflow.json \
  --output anime_girl

七、部署和启动

7.1 ComfyUI 启动脚本

bash 复制代码
#!/bin/bash
# start-comfyui.sh

echo "🚀 启动 ComfyUI 服务..."

# 检查端口
if lsof -Pi :8188 -sTCP:LISTEN -t >/dev/null ; then
    echo "⚠️  端口 8188 已被占用"
    exit 1
fi

# 设置环境
export PYTORCH_ENABLE_MPS_FALLBACK=1
export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0

# 启动 ComfyUI
cd ComfyUI

echo "📍 工作目录: $(pwd)"
echo "📊 系统信息:"
echo "   macOS: $(sw_vers -productVersion)"
echo "   芯片: $(sysctl -n machdep.cpu.brand_string)"
echo "   内存: $(system_profiler SPHardwareDataType | grep "Memory:" | awk '{print $2}')"

# 启动服务
echo ""
echo "🎯 启动 ComfyUI 服务..."
python main.py \
    --listen 0.0.0.0 \
    --port 8188 \
    --output-directory ./output \
    --disable-auto-launch \
    --enable-cors-header "*"

7.2 系统服务配置

创建 ~/Library/LaunchAgents/com.comfyui.plist:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.comfyui</string>
    
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/Users/baohan/Desktop/Engineering-playbook/flowai/ComfyUI/main.py</string>
        <string>--listen</string>
        <string>0.0.0.0</string>
        <string>--port</string>
        <string>8188</string>
        <string>--output-directory</string>
        <string>./output</string>
    </array>
    
    <key>WorkingDirectory</key>
    <string>/Users/baohan/Desktop/Engineering-playbook/flowai/ComfyUI</string>
    
    <key>RunAtLoad</key>
    <true/>
    
    <key>KeepAlive</key>
    <true/>
    
    <key>StandardOutPath</key>
    <string>/tmp/comfyui.log</string>
    
    <key>StandardErrorPath</key>
    <string>/tmp/comfyui_error.log</string>
    
    <key>EnvironmentVariables</key>
    <dict>
        <key>PYTORCH_ENABLE_MPS_FALLBACK</key>
        <string>1</string>
        <key>PYTORCH_MPS_HIGH_WATERMARK_RATIO</key>
        <string>0.0</string>
    </dict>
</dict>
</plist>

加载服务

bash 复制代码
# 加载服务
launchctl load ~/Library/LaunchAgents/com.comfyui.plist

# 启动服务
launchctl start com.comfyui

# 查看状态
launchctl list | grep comfyui

八、性能优化

8.1 模型预加载

python 复制代码
import requests

def preload_models():
    """预加载模型到内存"""
    print("🔧 预加载模型...")
    
    # 发送预加载请求
    response = requests.post(
        "http://127.0.0.1:8188/model/load",
        json={
            "model": "ltx-video-2b-v0.9.safetensors",
            "text_encoder": "gemma_3_12B_it_fp4_mixed.safetensors"
        }
    )
    
    if response.status_code == 200:
        print("✅ 模型预加载完成")
    else:
        print(f"❌ 预加载失败: {response.text}")

# 在 ComfyUI 启动后立即调用
preload_models()

8.2 内存管理

python 复制代码
import psutil

def check_memory_usage():
    """检查内存使用"""
    process = psutil.Process()
    memory_info = process.memory_info()
    
    rss_gb = memory_info.rss / (1024**3)
    vms_gb = memory_info.vms / (1024**3)
    
    print(f"📊 内存使用:")
    print(f"   RSS: {rss_gb:.2f} GB")
    print(f"   VMS: {vms_gb:.2f} GB")
    
    # 检查系统内存
    sys_memory = psutil.virtual_memory()
    available_gb = sys_memory.available / (1024**3)
    
    print(f"   可用: {available_gb:.2f} GB")
    
    if available_gb < 10:
        print("⚠️  可用内存不足 10GB,建议释放一些任务")

# 在生成前检查
check_memory_usage()

8.3 性能监控

python 复制代码
import time
import requests

def monitor_generation(prompt_id):
    """监控生成性能"""
    start_time = time.time()
    
    # 轮询查询
    while True:
        response = requests.get(f"http://127.0.0.1:8188/history/{prompt_id}")
        data = response.json()
        
        if prompt_id in data:
            end_time = time.time()
            duration = end_time - start_time
            
            print(f"⏱️  生成耗时: {duration:.2f} 秒")
            print(f"⚡  平均速度: {duration/4:.2f} 秒/秒")
            
            # 获取视频信息
            outputs = data[prompt_id].get("outputs", {})
            for node_id, node_outputs in outputs.items():
                for output_name, output_list in node_outputs.items():
                    if output_name == "video":
                        filename = output_list[0]["filename"]
                        file_size = output_list[0].get("size", 0)
                        
                        print(f"📁 文件: {filename}")
                        print(f"📏 大小: {file_size / (1024**2):.2f} MB")
                        
                        if file_size > 0:
                            speed = (file_size / (1024**2)) / duration
                            print(f"🚀 速度: {speed:.2f} MB/秒")
            
            break
        
        time.sleep(2)

九、故障排查

9.1 常见问题

问题 1: ComfyUI 无法启动
bash 复制代码
# 检查端口
lsof -i :8188

# 检查权限
ls -la ComfyUI/

# 查看日志
tail -f /tmp/comfyui.log
问题 2: 模型加载失败
python 复制代码
import requests

# 检查模型列表
response = requests.get("http://127.0.0.1:8188/object_info")
models = response.json()

print("📋 可用模型:")
for model_type, model_list in models.items():
    print(f"\n{model_type}:")
    for model in model_list:
        print(f"  - {model}")
问题 3: 生成失败
python 复制代码
# 获取历史记录
response = requests.get("http://127.0.0.1:8188/history")
history = response.json()

print("📜 生成历史:")
for prompt_id, result in history.items():
    status = result.get("status", {})
    print(f"\n{prompt_id}:")
    print(f"  状态: {status}")
    print(f"  节点: {list(result.get('outputs', {}).keys())}")

十、总结

10.1 方案优势

优势 说明
✅ 简单易用 无需额外后端,直接调用 ComfyUI
✅ 功能强大 支持100+种节点,扩展性强
✅ 可视化 Web UI 直观,易于调试
✅ 本地化 完全本地运行,数据安全
✅ 性能优秀 Mac M2/M3 优化,MPS 加速

10.2 技术栈

复制代码
ComfyUI 服务
├── Python 3.10+
├── PyTorch 2.0+ (MPS)
├── HTTP/WebSocket API
└── 工作流 JSON

本地模型
├── LTX Video (8.7GB)
├── Wan 2.2 (10GB)
└── CogVideoX (6GB)

调用脚本
├── Python HTTP Client
├── WebSocket Client
└── 配置文件管理

10.3 使用场景

适用场景

  • ✅ 本地视频生成
  • ✅ 批量处理
  • ✅ 自动化工作流
  • ✅ 离线环境
  • ✅ 高性能需求

不适用场景

  • ❌ 需要 Web 服务(需要额外后端)
  • ❌ 需要多用户协作(需要用户系统)
  • ❌ 需要云端部署(需要分布式架构)

10.4 后续扩展

如需扩展为完整的服务,可以:

  1. 添加 FastAPI 后端封装
  2. 集成 Temporal 工作流
  3. 添加数据库存储
  4. 开发 Web 前端
  5. 实现多用户支持

结论:纯 ComfyUI 方案适合本地使用和批量处理,简单高效,功能强大。

相关推荐
EasyCVR2 小时前
国标GB28181视频监控平台EasyCVR夏季安防高风险场景的解决方案
人工智能·音视频
学术头条4 小时前
清华团队开源SCAIL-2:角色动画告别骨骼依赖,端到端还原视频中动作细节
人工智能·科技·机器学习·ai·开源·音视频·agi
做萤石二次开发的哈哈5 小时前
AI 陪护机器人硬件如何接入萤石ERTC 实现实时通话?
人工智能·音视频·实时音视频·萤石开放平台
禹亮科技6 小时前
上海临港100㎡大型跨国会议室音视频集成方案(思科Webex+思必驰AI音频)
人工智能·音视频·思必驰吸顶麦·禹亮科技
爱吃骨头的鱼儿6 小时前
h264码流结构
音视频·h.264
大蚂蚁2号7 小时前
深度解析:2026短视频批量生成底层技术、架构演进与企业落地实战
架构·音视频
sitellla9 小时前
Pydub:用 Python 处理音频,不写废话
开发语言·python·其他·音视频
大蚂蚁2号11 小时前
短视频批量生成技术深度解析与实战方案
python·aigc·音视频
chase。11 小时前
【学习笔记】Unified World Models:基于视频-动作耦合扩散的机器人预训练新范式
笔记·学习·音视频