资讯动态

本地部署图文视频生成网站:ComfyUI+FastAPI全流程搭建教程

发布时间:2026/10/5 17:41:58 来源:尧图企业网站定制
简介这是一份面向AI绘画爱好者的本地化图文视频生成网站搭建教程PDF适合想掌握开源图像生成工具部署、摆脱在线服务限制的读者。教程从Python环境配置开始逐步讲解项目代码拉取、GPU版PyTorch安装再到模型下载与真人模型放置内容覆盖汉化插件安装、模拟真人图片生成、不同风格出图、动画视频生成以及让生成图片配合语音开口说话的进阶玩法。资源为单个PDF文件大小6.45MB图文步骤与命令行示例结合目录分五大模块便于对照学习和排错。教程对每一步骤都配有清晰截图和操作示例常见问题也有排查思路适合边读边练。目前已有719人学习下载适合具备基本Python基础、希望系统搭建本地AI绘画站点并产出逼真图像与动态视频的用户。跟随教程可少走弯路快速完成从环境配置到实际出图的全流程。1. 为什么要把图文视频生成网站搬到本地自己动手搭到底值不值先讲一个具体场景你想在稿子里配一张能商用的海报图或者给产品做一条 8 秒左右的短视频公共生成平台不是要排队就是要压缩关键是你把提示词和原图都交给别人。本地化图文视频生成网站就是把这个过程拉回自己机器上浏览器里打开页面输入提示词、上传参考图后端调用本地模型出图、出视频所有文件都落本地磁盘。这篇详细教程不绕弯从做技术选型到写出能直接复用的源码大约覆盖 80% 的搭建工作。新手跟第 2 章配环境熟手可以直接看第 3 章 API 封装和第 5 章避坑清单。2. 生成引擎选型为什么用 ComfyUI 当本地化后端引擎网站的核心不是页面而是谁在背后替你出图和出视频。我搭这套系统前在本地比较过三种主流做法A1111 的 WebUI、ComfyUI、直接用 diffusers 写采样脚本。最后留在源码里的只有 ComfyUI原因是它的 HTTP API 设计最适合被二次开发而不是只给鼠标用户用。2.1 三种本地化方案对比A1111、ComfyUI、diffusers 谁更适合做网站很多大模型本地化部署工具都自带页面但自带页面不等于适合做网站。A1111 的 WebUI 开箱即用接口也有/sdapi/v1/txt2img可它把采样流程固定住了想接图生视频、多模型融合就得去动它的内部逻辑越往后越像在黑盒子上打补丁。ComfyUI 是节点式工作流模型、采样器、VAE、输出节点都画在一张图里。后端要做的只是把这张图的 JSON 结构提交给它的/prompt接口。这对程序员极其友好前端是网页后端是任务调度生成引擎只负责执行工作流。diffusers 则更底层采样循环完全自己写灵活度最高但你要自己处理模型加载、显存回收、多卡调度工作量直接翻倍。方案界面API 灵活度视频扩展适合做网站吗A1111 WebUI完整 WebUI接口固定改动范围大插件与页面耦合强不太适合越改越脆ComfyUI节点式画布/prompt提交 JSON节点任意组合AnimateDiff、SVD 都能接适合做后端主力diffusers无完全自己写自己写采样与保存适合但开发量高所以在选型这一层我的结论很直接网站入口、用户权限、任务记录这些业务逻辑全放在 FastAPI 里生成部分只当作一个 ComfyUI 客户端。源码里所有调用 ComfyUI 的逻辑都集中在一个comfy_api.py模块里哪一天想换生成引擎只改这层壳。2.2 搭这套源码前的目录结构与运行环境动手之前先把目录立好。下面是我的工程结构模型和代码分开方便你们直接套用local_ai_web/ ├── backend/ │ ├── app.py # FastAPI 主入口网页接口全在这 │ ├── comfy_api.py # ComfyUI HTTP 请求封装 │ ├── workflows.py # 文生图/图生图/视频工作流构造 │ └── requirements.txt ├── comfy/ # ComfyUI 本体git clone 后单独放 ├── models/ # 模型文件checkpoints、vae、loras 各自放 ├── uploads/ # 用户上传的参考图 ├── templates/ # 工作流 JSON 模板 └── outputs/ # ComfyUI 生成结果软链到这里的 output环境部分我一般先把 ComfyUI 跑起来确认能出图再写网站后端。创建虚拟环境并安装 FastAPI 相关依赖cd local_ai_web python -m venv .venv source .venv/bin/activate pip install fastapi uvicorn[standard] python-multipartComfyUI 按它自己的启动方式来把仓库放进comfy/目录装好 Python 依赖后用下面命令启动服务cd comfy python main.py --listen 127.0.0.1 --port 8188 --lowvram--listen 127.0.0.1只允许本机访问网站后端就在同一台机器上所以完全够用。--lowvram是给显存不太宽的机器准备的它会分块加载模型虽然速度慢一点但不容易中途崩。3. 文生图与图生图接口把 ComfyUI API 封装成自己的网站接口这一步做完你的网页就能真正调用本地模型生成图片。要拆成三块和 ComfyUI 通信、FastAPI 接口、前端页面。代码不多但每一行都有坑尤其是工作流 JSON 里节点的输入输出编号错一个下标整张图就废了。3.1 与 ComfyUI 的通信提交工作流、轮询 history、取回图片ComfyUI 的 HTTP 接口有四个常用端点POST /prompt用于提交工作流GET /history/{prompt_id}用于查询结果GET /view用于按文件名取图GET /system_stats用于健康检查。我在这里用 Python 标准库写了一个最小客户端不引入额外依赖注释里标明了每个参数含义。import json import time import urllib.request COMFY_URL http://127.0.0.1:8188 def submit_prompt(workflow: dict) - str: payload json.dumps({prompt: workflow}).encode(utf-8) req urllib.request.Request( f{COMFY_URL}/prompt, datapayload, headers{Content-Type: application/json}, ) with urllib.request.urlopen(req, timeout60) as resp: data json.loads(resp.read().decode(utf-8)) if prompt_id not in data: raise RuntimeError(fComfyUI 返回错误: {data}) return data[prompt_id]注意POST /prompt请求体必须包一层{prompt: 工作流}直接把工作流 JSON 传过去会被 ComfyUI 拒绝。返回的prompt_id是后面轮询结果的钥匙要把它和任务绑定保存。等待生成完成同样用标准库写轮询间隔 1 秒def wait_for_result(prompt_id: str, timeout: int 600) - dict: deadline time.time() timeout while time.time() deadline: req urllib.request.Request(f{COMFY_URL}/history/{prompt_id}) with urllib.request.urlopen(req, timeout30) as resp: history json.loads(resp.read().decode(utf-8)) if prompt_id in history: return history[prompt_id] time.sleep(1) raise TimeoutError(f任务 {prompt_id} 超时请检查 ComfyUI 日志)history返回结果里图片信息在工作流实际执行过的节点下结构形如[outputs][节点ID][images]每个图片对象包含filename、subfolder、type三个字段。随后拼出可访问的文件地址from urllib.parse import urlencode def output_to_view_url(image_item: dict) - str: params urlencode({ filename: image_item[filename], subfolder: image_item.get(subfolder, ), type: image_item.get(type, output), }) return f{COMFY_URL}/view?{params}这段逻辑是整个网站生成的基石后面所有图生图、视频生成本质上都是构造不同的工作流 JSON提交后走同一套轮询。3.2 在 FastAPI 里写 generate 接口前面的通信函数只负责“提交”和“查询”真正面向浏览器的是 FastAPI 的接口。这里提供一个单接口方案不传图片就是文生图传了图片就是图生图。好处是前端只需要写一个按钮。from fastapi import FastAPI, UploadFile, File, Form from fastapi.middleware.cors import CORSMiddleware from typing import Optional import os import random import time import shutil from comfy_api import submit_prompt, wait_for_result, COMFY_URL, output_to_view_url from workflows import build_txt2img_workflow, build_img2img_workflow app FastAPI() app.add_middleware( CORSMiddleware, allow_origins[*], allow_methods[*], allow_headers[*], ) UPLOAD_DIR ../uploads/ app.post(/api/generate) async def generate( prompt: str Form(...), negative: str Form(), width: int Form(512), height: int Form(512), steps: int Form(20), cfg: float Form(7.0), denoise: Optional[float] Form(None), image: Optional[UploadFile] File(None), ): if image is None: workflow build_txt2img_workflow( prompt, negative, width, height, steps, cfg ) else: image_path save_upload(image) workflow build_img2img_workflow( prompt, negative, image_path, steps, cfg, denoise or 0.6 ) prompt_id submit_prompt(workflow) history wait_for_result(prompt_id) # 从 history 里把图片节点输出取出来这里简化为取第一张 images [] for node_id, node_out in history.get(outputs, {}).items(): images.extend(node_out.get(images, [])) if not images: raise RuntimeError(工作流执行完成但未找到图片输出) return { prompt_id: prompt_id, url: output_to_view_url(images[0]), node_count: len(history.get(outputs, {})), }save_upload是很容易被忽略的一环我习惯把上传文件写到uploads/并在文件名前加时间戳避免中文名和重名问题def save_upload(image: UploadFile) - str: ext os.path.splitext(image.filename)[-1] or .png filename f{int(time.time())}_{random.randint(1000, 9999)}{ext} path os.path.join(UPLOAD_DIR, filename) with open(path, wb) as f: shutil.copyfileobj(image.file, f) return os.path.abspath(path)3.3 前端页面的最小实现后端就绪后前端只需要一个index.html原生 HTML 加少量fetch调用不需要前端框架。这样源码更轻新手也容易看懂。input idprompt placeholder输入你想要的画面描述 stylewidth: 80% br input typefile idimageInput acceptimage/* br button onclickgenerate()生成图片/button br img idresultImage stylemax-width: 512px; display: none; script async function generate() { const fd new FormData(); fd.append(prompt, document.getElementById(prompt).value); const imageFile document.getElementById(imageInput).files[0]; if (imageFile) fd.append(image, imageFile); const res await fetch(/api/generate, { method: POST, body: fd }); const data await res.json(); const img document.getElementById(resultImage); img.src data.url; img.style.display block; } /scriptimageInput为空时后端走文生图分支选了图片就走图生图分支。data.url是前面output_to_view_url拼出来的 ComfyUI/view地址图片可以直接跨域显示在页面上。4. 接入视频生成AnimateDiff 工作流与帧数、显存参数图片能出之后视频生成就是同一个套路的延伸。视频不是单独一种魔法它就是把若干帧当成一个 batch 去采样再把帧序列合成 mp4。本地最容易落地的是 AnimateDiff 方案ComfyUI 里有现成自定义节点核心是把EmptyLatentImage的batch_size从 1 改成 16其他结构和文生图几乎一样。4.1 一个能出视频的 AnimateDiff 工作流怎么建先说明一点AnimateDiff 的自定义节点版本很多不同版本的 loader 输出顺序可能不同。我这边用的工作流结构是 AnimateDiff-Evolved 常见写法def build_video_workflow( prompt: str, negative: str, width: int 512, height: int 512, steps: int 20, cfg: float 7.0, frames: int 16, fps: int 8, ): seed random.randint(0, 2**31) return { 3: { class_type: CheckpointLoaderSimple, inputs: {ckpt_name: realisticVisionV60B1_v51VAE.safetensors}, }, 4: { class_type: EmptyLatentImage, inputs: {width: width, height: height, batch_size: frames}, }, 5: { class_type: AnimateDiffLoaderV1, inputs: { model: [3, 0], latents: [4, 0], beta_schedule: sqrt_linear, }, }, 6: { class_type: KSampler, inputs: { seed: seed, steps: steps, cfg: cfg, sampler_name: euler, scheduler: normal, denoise: 1.0, model: [5, 0], positive: [7, 0], negative: [8, 0], latent_image: [5, 1], }, }, 7: {class_type: CLIPTextEncode, inputs: {text: prompt, clip: [3, 1]}}, 8: {class_type: CLIPTextEncode, inputs: {text: negative, clip: [3, 1]}}, }关键在AnimateDiffLoaderV1它同时输出新的模型和隐性图像张量KSampler 必须把model指向[5, 0]、把latent_image指向[5, 1]顺序弄反会出现张量形状错误。batch_size设为frames后采样器一次性处理 16 帧的潜空间模型权重是同一套但运动模块会在帧序列中引入时间一致性。steps仍然按单张图的理解设置不是“每帧步数”总耗时大约是一张图的数倍。4.2 视频合成与输出解析把帧序列封装成 mp4工作流跑完后帧序列需要合成视频文件。这一步我放在工作流末端用 VideoHelperSuite 里的VHS_VideoCombine节点直接让 ComfyUI 输出 mp4def add_video_output(workflow, vae_node_id: str) - None: workflow[9] { class_type: VHS_VideoCombine, inputs: { images: [vae_node_id, 0], fps: 8, format: mp4, codec: h264, audio: None, quality: 85, filename_prefix: local_video, }, }codec必须指定h264有些版本默认按mp4封装但不指定编码器生成的文件浏览器可能识别不了。audio设为None是因为本地生成没有配音频来源先产出无声素材后期再用视频剪辑工具加音轨。解析视频输出和图片略有不同history节点输出里视频对应的是videos字段而非images。在原来的输出解析中要补充这一分支def extract_media(history_item: dict) - list: media [] outputs history_item.get(outputs, {}) for node_out in outputs.values(): media.extend(node_out.get(images, [])) media.extend(node_out.get(videos, [])) return media文件地址拼接照旧走/view前端只需要把它放进video标签video controls srcdata.url autoplay muted muted loop/video。4.3 视频生成参数边界怎么根据显存选帧数和分辨率视频生成最容易翻车的地方是显存。图像生成是一张 512x512视频生成等于一次处理 16 张 512x512模型还是同一个但因为要保留运动信息的中间张量显存占用会成倍增加。我的经验边界如下不同显卡驱动会有细微差异但下限基本一致显存推荐分辨率推荐帧数备注8GB512x5128~1616帧约 40 秒到 1 分钟12GB768x51216~24可以开 MediumVram16GB768x76824~32尽量控制 CFG 在 7 以内24GB1024x102432高分辨率必须配合分块 VAE顺带说明视频的fps只是输出帧率不是生成速度。建议生成时设 8画面效果比较自然。生成耗时由steps和frames决定想要更快就降steps到 15 或把画面切到 512 宽。5. 本地化图文视频生成网站排查5 个高频避坑点网站源码能跑通是一回事别人装上能跑是另一回事。这里挑我踩过最深的 5 个坑每条都按“现象 → 原因 → 解决”写希望你们避免在同样的地方过夜。5.1 前端页面一直提示跨域生成请求发不出去现象页面在127.0.0.1:8000打开但浏览器报CORS错误接口返回不了图片地址。原因前端直接请求了 ComfyUI 的8188端口或者后端 FastAPI 没有设置跨域允许。浏览器对localhost:8000和localhost:8188视作不同源默认拦截。解决前端不要直连 ComfyUI所有请求都走后端/api/generate并在 FastAPI 里挂上 CORSMiddlewareallow_origins[*]在本机内网场景够用。如果以后要加登录权限再把allow_origins收紧成具体前端地址。5.2 工作流提交成功但 history 永远查不到结果现象/prompt返回了prompt_id但轮询/history始终是空最终超时。原因节点执行时出错ComfyUI 不会把错误写进 HTTP 响应而是直接在服务端日志里报错history里只会有失败记录某些版本下失败节点连记录都不写。解决提交前先用 ComfyUI 界面手动跑一遍同款工作流确认能出图再接后端。后端轮询时不要只判断prompt_id in history还要打印history里该任务的status字段定位是哪个节点出错。多数时候是图片路径不存在或者模型文件没放到models/checkpoints下。5.3 图一大就显存不足生成进程直接闪退现象512x512 没问题改成 768 后概率性提示内存不足严重时整个 Python 进程退出。原因生成接口没做参数校验多个浏览器标签同时提交或者视频帧数设得超过显存上限ComfyUI 扛不住。早期很多这类翻车都来自空显存未释放。解决在后端接口里对width、height、frames做上限限制比如 8GB 显存强制width 768、frames 16超了直接返回参数错误。同时给 ComfyUI 启动参数加--lowvram往复使用时显存会回调得更积极。5.4 视频生成成功但网页播放黑屏或文件只有几 KB现象history里明明有视频文件下载下来打不开或者浏览器里video标签黑屏。原因VHS_VideoCombine里format填了gif或mkv也可能是codec用了浏览器不兼容的编码比如mp4v在部分浏览器上播放会花屏。解决固定成formatmp4codech264。如果换别的封装格式就先在本地用播放器验证再考虑接到网页上。更省事的方法是后端拿到 ComfyUI 输出后用 ffmpeg 统一转成 h264 的 mp4ffmpeg -i input.mkv -c:v libx264 -pix_fmt yuv420p -movflags faststart output.mp4-pix_fmt yuv420p是关键参数不带它转出来的 mp4 在浏览器里很容易播放不了。5.5 上传图片带中文名或路径带中文加载直接失败现象前端图生图提交后后端日志提示LoadImage节点找不到文件换成英文名立刻成功。原因ComfyUI 的LoadImage节点读取的是input目录下的相对路径Windows 下中文路径和编码问题会让节点无法定位真实文件。解决后端保存上传文件时统一重命名用时间戳加随机数拼英文文件名再写到 ComfyUI 的input目录或uploads目录。整套项目的安装路径也不要出现中文。这个坑很玄学但只要坚持路径全英文基本能避开 90% 的读取类问题。6. 把整套服务收口成一键脚本启动顺序与接口自检到了最后一步把前面所有模块合成一个能一键启动的服务顺便讲一个让我省力很多的工作流模板技巧。6.1 启动脚本的写法与健康检查ComfyUI 和 FastAPI 是两个独立进程启动顺序有讲究必须先起 ComfyUI再起网站后端。后端启动时可以做一次健康检查ComfyUI 没就绪就提前给出日志提示而不是等请求超时。#!/usr/bin/env bash set -e cd $(dirname $0) # 先起 ComfyUI后台运行 if ! curl -sf http://127.0.0.1:8188/system_stats /dev/null; then nohup python comfy/main.py --listen 127.0.0.1 --port 8188 --lowvram logs/comfy.log 21 echo ComfyUI 启动中等待就绪... for i in $(seq 1 30); do curl -sf http://127.0.0.1:8188/system_stats /dev/null break sleep 1 done fi # 再起 FastAPI source backend/.venv/bin/activate uvicorn backend.app:app --host 127.0.0.1 --port 8000有了脚本服务启用不再依赖手动开两个终端。验证接口时我习惯直接 curl不打开浏览器curl -s -X POST http://127.0.0.1:8000/api/generate \ -F prompt晴天湖边的一只白鹭 \ -F width512 -F height512 -F steps20 \ -o result.json python -m json.tool result.json返回结果里检查url字段能否访问能访问说明整套链路是通的。这个检查也适合放进 CI 脚本里每次改完源码先跑一遍再提交。6.2 让工作流模板可复用而不是把 JSON 写死在 Python 里前期图省事我把工作流 JSON 直接写成 Python 函数参数换模型、加 Lora 都要改代码。后来把工作流模板单独放进templates/目录内容用 JSON 格式保存程序运行时才渲染import json from pathlib import Path def load_template(name: str, **kwargs): template_path Path(templates) / f{name}.json raw template_path.read_text(encodingutf-8) for key, value in kwargs.items(): raw raw.replace({{ key }}, json.dumps(value)) return json.loads(raw)这种做法的好处是不懂 Python 的人也能改模板新增一种风格只加一个 JSON 文件接口侧参数校验没变生成能力却可无限扩展。我第一次搭这套服务时就是犯了“一次把所有功能全做进去”的毛病结果视频接口没调通文生图也带着跑不起来。后面把节奏改成“先跑通文生图 → 再加图生图 → 最后接视频”每一步都自测完再往下走翻车率才降下来。希望这篇本地化图文视频生成网站的搭建过程能帮你少走一段同样的路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑