前面第 11 篇,我们分析了 Pixelle-Video 的 AI 配图生成流程。
简单来说,一句话不会直接拿去生成图片,而是先经过这样一条链路:
narration
↓
LLM 生成 image_prompt
↓
prompt_prefix 统一画风
↓
StoryboardFrame 保存 image_prompt
↓
FrameProcessor 调用 MediaService
↓
生成图片并写回 frame.image_path
这一篇继续往下看更底层的问题:
Pixelle-Video 是如何接入 ComfyUI 工作流,让本地图像生成跑起来的?
很多人看到 ComfyUI,第一反应是节点工作流、模型、采样器、显存、端口、队列。但在 Pixelle-Video 里,业务代码并不会直接操作 ComfyUI 的每个节点。
它的设计思路是:
Pixelle-Video 只负责准备 prompt、尺寸、seed、steps 等参数
↓
ComfyKit 负责执行工作流
↓
ComfyUI 负责真正跑生图节点
↓
MediaService 拿到生成结果
↓
FrameProcessor 把图片写入当前分镜
所以这一篇的重点不是讲 ComfyUI 节点怎么搭,而是讲 Pixelle-Video 源码里是怎么把“一个 image_prompt”送进 ComfyUI workflow 的。
一、ComfyUI 在 Pixelle-Video 中负责什么?
Pixelle-Video 本身不是图像生成模型。
它不训练 FLUX、SDXL,也不直接实现采样器。它真正做的是“编排”:
LLM 负责写文案和图片提示词
ComfyUI 负责根据提示词生成图片或视频
TTS 负责生成旁白音频
HTML 模板负责合成画面
ffmpeg 负责最终视频拼接
在图像生成环节,ComfyUI 的角色就是:
接收 Pixelle-Video 传来的 prompt 和参数,然后按照指定 workflow 生成图片。
Pixelle-Video 的 MediaService 源码注释也明确写着,它是基于 ComfyUI workflow 的媒体生成服务,使用 ComfyKit 执行图片或视频生成工作流,并支持 image_ 和 video_ 前缀的 workflow。
也就是说,Pixelle-Video 不关心 workflow 内部具体用了什么模型,它只关心:
我有一个 workflow
我传入 prompt、width、height 等参数
workflow 返回图片或视频
这就是它和 ComfyUI 的边界。
二、本地 ComfyUI 的配置入口
先看配置文件。
Pixelle-Video 的 config.example.yaml 中有一个 comfyui 配置块,其中 comfyui_url 默认是:
comfyui:
comfyui_url: http://127.0.0.1:8188
配置注释也说明,comfyui_url 是 selfhost workflow 所需的 ComfyUI server URL;如果使用 Docker,还需要注意容器里的 localhost 和宿主机不是同一个地址。
这意味着,如果你选择本地 ComfyUI 工作流,Pixelle-Video 默认会去访问:
http://127.0.0.1:8188
也就是本机正在运行的 ComfyUI 服务。
这里要注意一个常见误区:
Pixelle-Video 启动了,不代表 ComfyUI 也启动了。
Pixelle-Video 的 WebUI、pipeline、MediaService 是一套程序。
ComfyUI 是另一套服务。
如果你选择 selfhost/image_flux.json 之类的本地 workflow,就必须保证本地 ComfyUI 已经启动,并且地址能被 Pixelle-Video 访问。
三、default_workflow:决定默认走哪个图像工作流
除了 ComfyUI 地址,配置文件里还有一个更关键的配置:
comfyui:
image:
default_workflow: runninghub/image_flux.json
配置注释中写到,image.default_workflow 是必填项,没有 fallback;可选项包括 runninghub/image_flux.json 和 selfhost/image_flux.json,其中 selfhost 需要本地 ComfyUI。
如果你要让本地图像生成跑起来,通常要把它改成:
comfyui:
image:
default_workflow: selfhost/image_flux.json
这样 Pixelle-Video 在生成图片时,就会优先使用 workflows/selfhost/image_flux.json 这个工作流。
这里的命名很重要:
runninghub/image_flux.json
表示 RunningHub 云端工作流
selfhost/image_flux.json
表示本地 ComfyUI 工作流
Pixelle-Video 通过 workflow key 来区分到底是本地执行,还是云端执行。
四、workflows/ 目录:本地工作流放在哪里?
Pixelle-Video 的 workflow 文件放在 workflows/ 目录下。
从 MediaService 源码看,它会扫描 workflow source directories,然后查找所有以 image_ 或 video_ 开头、以 .json 结尾的文件。也就是说,图片工作流文件一般需要命名为 image_xxx.json,视频工作流文件一般需要命名为 video_xxx.json。
这就解释了为什么配置里叫:
selfhost/image_flux.json
runninghub/image_flux.json
它的结构可以理解为:
workflows/
├── selfhost/
│ ├── image_flux.json
│ └── video_xxx.json
│
└── runninghub/
├── image_flux.json
└── video_xxx.json
当然,具体文件以仓库实际内容为准,但源码层面的扫描规则就是:
扫描 workflows 下的来源目录
↓
查找 image_*.json / video_*.json
↓
解析成 workflow_info
↓
通过 key 进行选择
这个设计让 Pixelle-Video 可以同时支持多种来源:
selfhost
本地 ComfyUI
runninghub
RunningHub 云端工作流
api/...
直连图像或视频 API
其中本文重点讲的是 selfhost,也就是本地 ComfyUI。
五、ComfyBaseService:负责扫描和解析工作流
Pixelle-Video 把 ComfyUI 相关的公共逻辑抽到了 ComfyBaseService。
这个基类主要负责:
扫描 workflows 目录
解析 workflow JSON
生成 workflow_info
查找默认 workflow
根据 key 解析 workflow
准备 ComfyKit 配置
从源码注释可以看到,ComfyBaseService 是 ComfyUI workflow-based capabilities 的公共基础服务,子类需要定义 workflow 文件前缀、默认 workflow 和 workflows 目录。它还会解析 workflow 文件,并生成包含 name、display_name、source、path、key 等字段的 workflow 信息。
可以把它理解成:
ComfyBaseService
不负责生成图片
负责找到“该用哪个 workflow”
而真正生成图片的是 MediaService。
这种分层很清楚:
ComfyBaseService
负责 workflow 管理
MediaService
负责媒体生成
ComfyKit
负责执行 workflow
ComfyUI
负责真正跑节点
六、MediaService:图片生成的核心服务
MediaService 继承自 ComfyBaseService。
它的职责是:根据传入的 prompt 和 workflow,生成图片或视频。
源码里 MediaService.__call__() 接收很多参数:
prompt
workflow
media_type
comfyui_url
runninghub_api_key
width
height
duration
output_path
image_path
negative_prompt
steps
seed
cfg
sampler
这些参数就是 Pixelle-Video 传给生图工作流的核心输入。源码也明确说明,prompt 是媒体生成提示词,workflow 是工作流文件,media_type 用于指定生成图片还是视频,width、height、steps、seed、cfg、sampler 等则是常见生成参数。
也就是说,前面第 11 篇生成的 image_prompt,最终会变成这里的:
prompt=frame.image_prompt
再加上尺寸、workflow、seed 等参数,一起送入 ComfyUI 工作流。
七、从 FrameProcessor 到 MediaService
真正触发图片生成的地方不是 MediaService 自己,而是 FrameProcessor。
在标准视频生成流程里,每一段旁白都会变成一个 StoryboardFrame。这个 frame 里保存了:
narration
image_prompt
image_path
video_path
duration
当 FrameProcessor 处理这一帧时,会根据 frame.image_prompt 判断是否需要生成媒体。
如果需要生成,它会调用核心服务里的 media:
FrameProcessor
↓
self.core.media(...)
↓
MediaService.__call__()
而 MediaService.__call__() 接收到的 prompt,就是当前 frame 的 image_prompt。
所以,本地图像生成的真实触发链路是:
StoryboardFrame.image_prompt
↓
FrameProcessor._step_generate_media()
↓
MediaService.__call__()
↓
ComfyKit.execute()
↓
ComfyUI workflow
这条链路说明,ComfyUI 不是直接面向用户输入的主题,而是面向已经处理过的视觉 prompt。
用户输入的是主题。
LLM 生成的是旁白。
LLM 再生成 image_prompt。
ComfyUI 最终吃的是 image_prompt。
八、MediaService 如何选择 selfhost workflow?
MediaService.__call__() 里第一步是确定 workflow。
源码中会先取:
selected_workflow = workflow or self.config.get("default_workflow")
如果 workflow 以 api/ 开头,就走直连 API provider;否则就调用 _resolve_workflow() 解析本地或 RunningHub 工作流。
对于本地 ComfyUI 来说,关键是这个 workflow key:
selfhost/image_flux.json
_resolve_workflow() 会在扫描到的 workflow 列表里查找这个 key。如果找到,就返回对应的 workflow_info。
一个 selfhost workflow_info 大致可以理解为:
{
"name": "image_flux.json",
"display_name": "image_flux.json - Selfhost",
"source": "selfhost",
"path": "workflows/selfhost/image_flux.json",
"key": "selfhost/image_flux.json"
}
这一步解决的是:
Pixelle-Video 到底要执行哪个 workflow 文件?
九、ComfyKit 为什么是懒加载?
在 PixelleVideoCore 中,ComfyKit 并不是程序启动时立即创建。
源码注释写得很清楚:ComfyKit 使用 lazy initialization,第一次使用时才创建,并且会检测配置变化;如果配置变了,就关闭旧实例并重新创建。
这段逻辑在 _get_or_create_comfykit() 中:
第一次生成图片时
↓
读取当前 ComfyUI 配置
↓
计算配置 hash
↓
如果还没有 ComfyKit,则创建
↓
如果配置变化,则关闭旧实例并重新创建
↓
返回 ComfyKit 实例
为什么这样设计?
因为用户可能在 WebUI 中修改:
comfyui_url
comfyui_api_key
runninghub_api_key
runninghub_instance_type
如果 Pixelle-Video 一启动就固定了 ComfyKit 实例,那么用户修改配置后可能不生效。
懒加载加配置 hash 检测,可以让 Pixelle-Video 支持更灵活的配置热更新。
十、ComfyKit 配置从哪里来?
PixelleVideoCore._get_comfykit_config() 会从全局 config_manager 重新读取配置,然后提取 comfyui 配置块。
源码中它会读取:
comfyui_url
comfyui_api_key
runninghub_api_key
runninghub_instance_type
如果这些字段存在,就放入 kit_config,然后交给 ComfyKit(**current_config) 创建实例。
对于本地 ComfyUI,最关键的是:
comfyui_url
也就是本地 ComfyUI 服务地址。
例如:
comfyui:
comfyui_url: http://127.0.0.1:8188
如果你是在 Docker 里跑 Pixelle-Video,而 ComfyUI 跑在宿主机,就不能简单写 127.0.0.1。配置文件注释也提醒 Docker 用户,Mac/Windows 可以用 host.docker.internal:8188,Linux 则要用宿主机 IP。
这个坑非常常见。
因为容器里的 127.0.0.1 指的是容器自己,不是宿主机。
十一、workflow_params:Pixelle-Video 传给 ComfyUI 的参数
选好 workflow 后,MediaService 会构造 workflow_params。
源码里最先放进去的是:
workflow_params = {"prompt": prompt}
然后根据参数是否存在,继续加入:
width
height
duration
negative_prompt
steps
seed
cfg
sampler
最后还会把额外参数 params 合并进去。
也就是说,Pixelle-Video 传给 ComfyUI workflow 的不是一整套固定死的节点数据,而是一组抽象参数:
prompt
width
height
seed
steps
cfg
sampler
negative_prompt
workflow 内部怎么使用这些参数,取决于 ComfyKit 和 workflow 文件的设计。
可以理解成:
Pixelle-Video:
我给你 prompt、尺寸和采样参数
ComfyUI workflow:
我知道这些参数该注入到哪些节点
这就是用 workflow 封装底层节点复杂度的好处。
十二、真正执行:kit.execute()
当 workflow 和参数都准备好后,MediaService 会调用:
result = await kit.execute(workflow_input, workflow_params)
对于 selfhost 工作流,workflow_input 是 workflow 文件路径。源码中明确写到:如果不是 RunningHub workflow,就把 workflow_info["path"] 传给 ComfyKit,日志里也会显示正在执行 selfhost workflow。
也就是说,本地 ComfyUI 的执行链路是:
workflow_info["path"]
↓
workflows/selfhost/image_flux.json
↓
kit.execute(workflow_path, workflow_params)
↓
ComfyKit 请求本地 ComfyUI
↓
ComfyUI 按 workflow 生成图片
Pixelle-Video 自己不直接拼 ComfyUI API 请求。
它通过 ComfyKit 这个中间层来执行 workflow。
十三、生成结果如何判断成功?
ComfyKit 执行完成后,会返回 result。
MediaService 会先检查:
if result.status != "completed":
raise Exception(...)
如果状态不是 completed,就认为媒体生成失败。
如果是图片生成,接着检查:
if not result.images:
raise Exception("No image generated")
如果有图片,就取第一张:
image_url = result.images[0]
然后返回:
MediaResult(
media_type="image",
url=image_url
)
源码中就是这样区分图片和视频结果的:media_type == "video" 时取 result.videos,否则取 result.images。
所以对图片生成来说,成功的标志是:
result.status == "completed"
并且 result.images 不为空
十四、图片 URL 如何变成本地文件?
MediaService 返回的是 MediaResult,里面有:
media_type = "image"
url = image_url
后续 FrameProcessor 会负责把这个 URL 下载或复制到当前任务目录,然后写入:
frame.image_path
从整个流程看,图片会经历三种状态:
1. image_prompt
还只是文本提示词
2. MediaResult.url
ComfyUI / ComfyKit 返回的生成结果地址
3. frame.image_path
Pixelle-Video 本地任务目录中的图片路径
只有到了 frame.image_path,图片才真正成为当前分镜可使用的素材。
后面的 HTML 模板渲染和视频片段合成都依赖这个路径。
十五、本地图像生成完整链路
现在可以把 Pixelle-Video 的本地 ComfyUI 配图链路完整画出来:
【配置阶段】
config.yaml
↓
comfyui.comfyui_url = http://127.0.0.1:8188
comfyui.image.default_workflow = selfhost/image_flux.json
【工作流扫描】
MediaService / ComfyBaseService
↓
扫描 workflows/selfhost/
↓
找到 image_flux.json
↓
生成 workflow_info
【分镜阶段】
narration
↓
image_prompt
↓
StoryboardFrame.image_prompt
【逐帧生成】
FrameProcessor
↓
self.core.media(
prompt=frame.image_prompt,
workflow=selfhost/image_flux.json,
media_type="image",
width=...,
height=...
)
【执行阶段】
MediaService.__call__()
↓
_resolve_workflow()
↓
_get_or_create_comfykit()
↓
kit.execute(workflow_path, workflow_params)
↓
本地 ComfyUI 执行 workflow
【结果阶段】
result.status == completed
↓
result.images[0]
↓
MediaResult(media_type="image", url=image_url)
↓
下载到任务目录
↓
frame.image_path
这就是“本地图像生成是怎么跑起来的”的完整答案。
十六、本地 ComfyUI 和 RunningHub 的区别
Pixelle-Video 同时支持 selfhost 和 RunningHub。
两者在 pipeline 层看起来很像,都是:
MediaService.__call__()
↓
kit.execute(...)
但传给 ComfyKit 的 workflow_input 不同。
源码中写得很清楚:如果 workflow_info["source"] == "runninghub" 且有 workflow_id,就把 workflow_id 传给 ComfyKit;否则就把本地 workflow 文件路径传给 ComfyKit。
也就是说:
selfhost:
workflow_input = workflow_info["path"]
执行本地 workflow 文件
依赖本地 ComfyUI 服务
runninghub:
workflow_input = workflow_info["workflow_id"]
执行云端工作流
依赖 RunningHub API Key
这就是本地和云端的核心区别。
本地 ComfyUI 的优点是:
可控性高
模型和节点自己管理
长期使用成本可能更低
适合有显卡的用户
缺点是:
部署复杂
依赖显卡和模型文件
workflow 节点容易缺失
环境问题较多
RunningHub 的优点是:
不需要本地显卡
配置相对简单
适合快速跑通
缺点是:
需要 API Key
可能有额度或费用
受云端队列和并发限制影响
十七、为什么要通过 workflow,而不是直接写死模型?
Pixelle-Video 没有在 Python 代码里写死“调用某个模型生成图片”,而是通过 workflow 来接入 ComfyUI。
这个设计很重要。
如果写死模型,代码可能会变成:
调用 FLUX
调用 SDXL
调用 ControlNet
调用某个 LoRA
调用某个采样器
每换一个模型或节点组合,都要改 Python。
而使用 workflow 后,Python 只需要传通用参数:
prompt
width
height
seed
steps
cfg
sampler
具体怎么生成,由 workflow 决定。
这带来几个好处:
换模型不用改主流程
换风格可以改 workflow
高级用户可以自己设计节点
同一套 pipeline 可以支持图片和视频
可以同时支持本地 ComfyUI 和云端 RunningHub
所以 Pixelle-Video 的核心不是“内置一个生图模型”,而是“把 ComfyUI workflow 纳入视频生成 pipeline”。
十八、为什么 workflow 文件要有命名规范?
MediaService 扫描 workflow 时,只会匹配:
image_*.json
video_*.json
源码里过滤条件就是文件名以 image_ 或 video_ 开头,并且以 .json 结尾。
这样做有两个好处。
第一,区分用途。
image_*.json
用于图片生成
video_*.json
用于视频生成
第二,方便 UI 展示和默认配置。
WebUI 可以列出图片 workflow 和视频 workflow,用户不需要在一堆 JSON 文件里猜哪个能用。
如果你自己添加本地工作流,最好遵守这个命名规则。
例如:
workflows/selfhost/image_sdxl.json
workflows/selfhost/image_flux_custom.json
workflows/selfhost/video_wan_custom.json
否则 MediaService 可能扫描不到。
十九、本地 ComfyUI 常见问题
理解源码链路后,排查问题会容易很多。
1. 找不到 workflow
如果报类似:
Workflow 'selfhost/image_xxx.json' not found
通常是这些原因:
文件没有放在 workflows/selfhost/
文件名不是 image_*.json
config.yaml 里的 default_workflow 写错
workflow key 写成了 image_flux.json,而不是 selfhost/image_flux.json
源码中的 _resolve_workflow() 会扫描可用 workflow,然后按 key 匹配;如果找不到,就会列出 available workflows。
2. 连不上 ComfyUI
如果 workflow 找到了,但执行失败,优先检查:
ComfyUI 是否已经启动
comfyui_url 是否正确
端口是否是 8188
Pixelle-Video 和 ComfyUI 是否在同一台机器
Docker 场景下 localhost 是否写错
配置文件默认的本地地址是 http://127.0.0.1:8188,并且明确提醒 Docker 用户要用宿主机地址。
3. workflow 执行失败
如果 ComfyUI 能连上,但生成失败,可能是:
ComfyUI 缺少节点
模型文件没下载
workflow 中节点路径不对
参数名和 workflow 期望不一致
显存不足
ComfyUI 后台报错
这类问题通常要看 ComfyUI 控制台日志,而不只是看 Pixelle-Video 的报错。
4. 没有生成图片
MediaService 会检查 result.images,如果为空就抛出 No image generated。
这种情况通常说明 workflow 执行完了,但没有返回 Pixelle-Video 期望的图片结果。
可能原因包括:
workflow 没有正确的输出节点
ComfyKit 没识别到输出图片
输出类型不是 image
workflow 本身是 video,但 media_type 传了 image
5. Docker 中 127.0.0.1 访问错误
如果 Pixelle-Video 在 Docker 容器里,ComfyUI 在宿主机上,容器里的:
127.0.0.1:8188
指的是容器自己,不是宿主机。
配置文件注释也提醒,Mac/Windows 可以用 host.docker.internal:8188,Linux 要用宿主机 IP。
二十、二次开发:如何添加自己的本地生图 workflow?
如果你想给 Pixelle-Video 添加自己的 ComfyUI 生图工作流,可以按下面思路做。
第一,把 workflow 放到:
workflows/selfhost/
第二,文件名用 image_ 开头,比如:
image_my_style.json
第三,在 config.yaml 中设置:
comfyui:
image:
default_workflow: selfhost/image_my_style.json
第四,保证 workflow 能接收 Pixelle-Video 传入的参数:
prompt
width
height
negative_prompt
steps
seed
cfg
sampler
第五,启动本地 ComfyUI,并确认:
comfyui_url: http://127.0.0.1:8188
第六,运行 Pixelle-Video,选择 image 模板生成视频。
如果一切正常,调用链路就是:
frame.image_prompt
↓
MediaService
↓
selfhost/image_my_style.json
↓
本地 ComfyUI
↓
生成图片
↓
frame.image_path
二十一、这套设计的优点
Pixelle-Video 接入 ComfyUI 的设计,有几个明显优点。
1. 解耦主流程和生图细节
Pixelle-Video 不需要关心 workflow 内部节点。
它只传参数,拿结果。
2. 支持本地和云端双路线
同一套 MediaService 可以处理 selfhost 和 RunningHub。
区别只是 workflow source 不同。
3. workflow 可扩展
用户可以添加自己的 image_*.json 或 video_*.json。
4. 配置可切换
只要改 default_workflow,就可以切换不同工作流。
5. ComfyKit 懒加载
第一次使用时才创建 ComfyKit,并且配置变更后会重建,适合 WebUI 场景。
6. 参数抽象清晰
Pixelle-Video 统一传 prompt、width、height、seed、steps 等参数,workflow 内部负责实际节点映射。
二十二、这套设计的局限
当然,它也有一些局限。
1. workflow 参数必须匹配
Pixelle-Video 会传入一组参数,但 workflow 是否正确使用这些参数,要看 workflow 文件设计。
如果 workflow 不认识 prompt 或没有正确映射,生成结果就可能不对。
2. 对 ComfyUI 环境依赖强
本地 selfhost 需要用户自己维护 ComfyUI、模型、节点、显卡环境。
对普通用户来说,这个门槛不低。
3. 错误定位可能跨系统
有些错误发生在 Pixelle-Video。
有些错误发生在 ComfyKit。
有些错误发生在 ComfyUI。
有些错误发生在 workflow 节点内部。
排查时要沿着链路看,而不是只看最外层异常。
4. 图片一致性仍然依赖 prompt 和 workflow
Pixelle-Video 能把每段旁白送进 workflow,但不能天然保证每张图人物一致、场景一致。
如果要做角色连续短剧,还需要额外设计角色一致性方案。
二十三、源码阅读建议
如果你要阅读 Pixelle-Video 的 ComfyUI 接入源码,建议按这个顺序:
1. config.example.yaml
看 comfyui_url、image.default_workflow、selfhost / runninghub 的配置方式
2. pixelle_video/services/comfy_base_service.py
看 workflow 如何扫描、解析和 resolve
3. pixelle_video/services/media.py
看 MediaService 如何接收 prompt、构造 workflow_params、调用 ComfyKit
4. pixelle_video/service.py
看 PixelleVideoCore 如何懒加载 ComfyKit、检测配置变化、初始化 MediaService
5. pixelle_video/services/frame_processor.py
看 frame.image_prompt 如何触发媒体生成
6. workflows/selfhost/
看具体 ComfyUI workflow JSON 如何承接 prompt 和参数
这样读下来,就能从配置、工作流、服务、执行、结果完整串起来。
二十四、总结
这一篇我们分析了 Pixelle-Video 如何接入本地 ComfyUI 工作流。
完整链路可以总结为:
config.yaml
↓
comfyui_url
image.default_workflow = selfhost/image_flux.json
↓
MediaService 扫描 workflows/selfhost/image_*.json
↓
FrameProcessor 传入 frame.image_prompt
↓
MediaService 构造 workflow_params
↓
PixelleVideoCore 懒加载 ComfyKit
↓
ComfyKit 执行本地 workflow 文件
↓
本地 ComfyUI 生成图片
↓
MediaService 返回 MediaResult
↓
FrameProcessor 下载图片并写入 frame.image_path
↓
HTML 模板继续合成视频画面
从设计上看,Pixelle-Video 并没有把 ComfyUI 作为一个孤立工具,而是把它封装成了视频生成 pipeline 中的媒体生成服务。
一句话总结:
Pixelle-Video 的本地 ComfyUI 接入,本质是通过 MediaService 解析 selfhost workflow,把每个分镜的 image_prompt 转成 workflow 参数,再交给 ComfyKit 调用本地 ComfyUI 执行生图,最后把生成图片写回 StoryboardFrame。
307

被折叠的 条评论
为什么被折叠?



