动漫作坊 – 分享动漫制作

动漫作坊 – 分享动漫制作


  • 首页

  • 归档

  • 搜索

微信表情包正式上线啦

发表于 2026-06-09

公告

傲娇小汤圆表情包正式上线啦~✨

日常篇、修仙篇、赛博篇、旅游篇、运动篇、新年篇、恋爱篇、农活篇、校园篇、打工人篇…. 持续更新中😬

直接在微信聊天框中搜索傲娇小圆圆即可找到:

image-20260618180530114

image-20260618180606397

欢迎大家来使用😜

IP形象

我个人比较喜欢圆润可爱、表情丰富、简约扁平的风格, 于是就有了最基础的小汤圆灵感初稿:

20_加油

我觉得还需要带点肢体动作, 同时配上傲娇的表情, 这样显得更加有灵气, 于是就有了傲娇小汤圆形象:

18_没问题

之所以喜欢傲娇表情, 可能我骨子里也略带点傲娇性格吧, 哈哈😬

这一整套归整为傲娇小汤圆基础篇:

image-20260618192932384

表情需要分雌雄, 方便后期场景的扩展, 于是乎, 我在傲娇小汤圆基础形象之上衍生出两种性别, 并将雄性取名为元元, 雌性取名为圆圆

我想象圆圆是属于那种是软萌可爱, 偏温婉性格的女孩子,以傲娇小圆圆旅游篇坐高铁表情为例, 如下:

10_高铁上

而元元是那种类似于大白那种胖敦敦, 温和踏实又治愈的形象, 如下:

10_高铁上

表情包示例

已上架表情套包截图:
image-20260618193113182

傲娇小汤圆恋爱篇:

image-20260618192751863

傲娇小圆圆修仙篇:

image-20260618192845173

傲娇小圆圆运动篇:

image-20260618193017297

表情规划

目前表情基本上都是静态图, 后期逐步考虑增加动态表情, 这个难度稍微大一些, 一步步来

等傲娇小汤圆这个IP形象稳定下来后, 再考虑增加新的形象, 大家族, 热热闹闹的, 岂不美哉😬

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

关于动画的分类

发表于 2025-09-20

动画分类

从专业角度看,动画分类可以有多个维度:

  • 按制作技术: 可分为手绘、CG、定格三种

  • 按实现原理: 可分为逐帧、关键帧、程序化三种

  • 按视觉风格: 可分为二维、三维等

按制作技术分类

  • 手绘: 纯手工绘制, 相对原始的制作手段
  • CG: 电脑绘画, 借助电子平台
  • 定格: 相机+实景实物所形成的一种特殊动画形式

按实现原理分类

根据动画的实现原理主要分为三大类

  • 逐帧动画: 手动绘制每一帧画面, 能实现灵活且细腻的效果
  • 关键帧动画: 只需绘制关键的那几帧画面, 剩下的交给计算机来处理
  • 程序动画: 按照指定的规则框架进行非规律性绘制, 比如物理模拟

关键帧动画

其中关键帧动画又分为:

  • 补间动画/插值动画: 根据算法自动生成中间帧
  • 骨骼动画: 可复用的骨骼活动框架
  • 属性动画: 插值动画的升级版, 解决插值动画交互性不足的问题

总结

现代动画是多种不同类型动画的混合体, 比如一个复杂的游戏角色动画:

  • 用骨骼动画做身体的奔跑和移动(高效、容易重用)
  • 用补间动画控制UI血条的闪烁和缩放。
  • 用逐帧动画制作角色释放大招时的一个顶级酷炫的特效

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

使用魔搭免费GPU算力+内网穿透实现ComfyUI+MinMax-H3的部署

发表于 2025-09-03

前言

最近攒的阿里云灵构的灵豆已经消耗完了, 于是想着看看有没别的免费算力可以薅一薅,结果找来了魔搭(Modelscope)平台

怎么说呢, 虽然只有免费的36个小时显卡算力, 但蚊子也是肉, 凑合着用吧

下面分享如何使用它家的实例进行ComfyUI+MinMax-H3的部署

注册魔搭账号

新用户赠送一台36小时8核+32GB+显存24G的实例外加一台8核+32GB的长期实例

image-20260904201745241

image-20260904201423863

官网注册入口: 点击进入

要用来跑MinMax-H3模型的话必须使用高显存+高内存的实例, 光基础模型体积都有几十GB, 配置不够的话完全没法玩

拿到实例后我们启动它, 使用默认镜像即可:

image-20260904202342030

启动后我们会进入到类似于vscode编辑器的WebIDE:

image-20260904202618128

接下来我们利用终端来下载模型以及安装部署的ComfyUI

安装ComfyUI

  1. 下载ComfyUI

    1
    2
    # 在Notebook终端中运行如下命令,将官方的ComfyUI的仓库克隆下来:
    git clone https://github.com/comfyanonymous/ComfyUI.git

    或者

    1
    2
    3
    4
    # 若上面代码块无法正常克隆Comfyui,则使用此命令
    git config --global http.version HTTP/1.1
    git clone https://github.com/comfyanonymous/ComfyUI.git
    git config --global http.version HTTP/2
  2. 创建虚拟环境

    1
    2
    3
    4
    5
    6
    # 进入工作目录
    cd ComfyUI

    #创建虚拟环境
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate

    实例每次重启后, 除了工作目录以外的文件和目录都会重置, 为了避免每次启动实例都要重新安装一遍依赖, 我们需要先创建独立python环境, 所有安装包文件都放在工作区,

    后面每次重启实力只需要执行source path/to/venv/bin/activate进入虚拟环境, 即可立马使用

  3. 安装ComfyUI依赖

    1
    2
    # 使用pip安装运行ComfyUI服务所需的环境依赖
    python3 -m pip install -r requirements.txt
  4. 安装comfyui-manager

    1
    2
    # 方便解决工作流节点缺失的问题
    python3 -m pip install comfyui-manager
  5. 启动ComfyUI

    1
    2
    3
    4
    5
    6
    7
    8
    python3 main.py \
    --listen 0.0.0.0 \
    --port 8188 \
    --enable-manager \
    --disable-pinned-memory \
    --fast-disk \
    --preview-method none \
    --reserve-vram 0 \

参数说明:

  • --enable-manager:启用 ComfyUI Manager(装自定义节点要用)
  • --preview-method none:生成时的实时预览也吃显存,低配建议关
  • --reserve-vram 1.5:给系统留 1.5GB 余量,用来躲开容器 85% 红线
  • -disable-pinned-memory: 禁用页面锁定内存
  • --fast-disk: 优先从磁盘流式加载
    拆分 UNet 权重按需加载
  • --reserve-vram 0: 压榨显存空间, 比如原本显卡默认预留1G左右headroom余量, 现在压到800M左右, 多出几百兆显存

注: --disable-pinned-memory 和--fast-disk这两个参数不能少, 目的是减轻内存的占用, 魔搭容器有内存阈值预警, 内容占用超过85%以上就会自动Kill进程

​

如果你想一步到位, 那就使用一体化脚本, 从下载到安装启动一条龙, python脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI 一键安装 + 启动
=======================
流程: 克隆 ComfyUI → 建虚拟环境 → 装依赖 → 装 ComfyUI-Manager → 启动服务

用法:
python install_comfyui.py # 全自动: 安装并启动
python install_comfyui.py --no-launch # 只安装, 不启动
python install_comfyui.py --novram # 显存不够被 kill 时用(更激进的卸载)

中断后直接重跑, 已完成步骤自动跳过。

"""
import os
import re
import sys
import time
import shutil
import socket
import subprocess

# ============ 配置 ============
WORKSPACE_CANDIDATES = ["/home/admin/workspace", "/mnt/workspace"] # 自动探测,第一个存在的胜出
REPO_URL = "https://github.com/comfyanonymous/ComfyUI.git"
PORT = 8188
LOG_DIR = "/tmp/comfyui_install_logs"

# 启动参数集中维护(--novram 时会自动追加到最后)
LAUNCH_ARGS = [
"--enable-manager",
"--disable-pinned-memory", # 省系统内存:不再锁定 ~2 倍模型大小的页
"--fast-disk", # 省系统内存:优先从磁盘流式加载而非常驻 RAM
"--preview-method", "none", # 关生成中预览,省显存
"--reserve-vram", "0", # 默认会预留 1.2GB 显存,设 0 可拿回来
]

# ============ 路径(自动派生,不要手改)============
COMFY_DIR = ""
VENV_DIR = ""


def resolve_paths():
global COMFY_DIR, VENV_DIR
ws = next((c for c in WORKSPACE_CANDIDATES if os.path.isdir(c)), None)
if ws is None:
ws = WORKSPACE_CANDIDATES[0]
os.makedirs(ws, exist_ok=True)
COMFY_DIR = os.path.join(ws, "ComfyUI")
VENV_DIR = os.path.join(ws, "venv") # 放工作区,实例重启后仍在
return ws


def run(cmd, **kw):
print(f" $ {' '.join(cmd)}")
subprocess.run(cmd, check=True, **kw)


def port_open():
try:
socket.create_connection(("127.0.0.1", PORT), timeout=2).close()
return True
except Exception:
return False


# ---------- 幂等工具 ----------
def _norm(name):
"""包名规范化:去版本约束与环境标记,统一小写"""
return re.split(r"[<>=!~\[;@]", name.strip())[0].strip().lower().replace("_", "-")


def installed_set(pip_cmd):
"""当前环境已装包名集合"""
try:
r = subprocess.run(pip_cmd + ["list", "--format=freeze"],
capture_output=True, text=True, timeout=300)
if r.returncode != 0:
return set()
return {ln.split("==")[0].split(" @")[0].strip()
for ln in r.stdout.splitlines() if ln.strip()}
except Exception:
return set()


def missing_from_requirements(pip_cmd, req_path):
"""返回 (缺失行列表, 总行数)。全部已装则缺失为空"""
have = installed_set(pip_cmd)
need, total = [], 0
try:
lines = open(req_path, encoding="utf-8").read().splitlines()
except OSError:
return [], 0
for ln in lines:
s = ln.strip()
if not s or s.startswith("#") or s.startswith("-"):
continue # 跳过注释与 --index-url 等选项行
total += 1
if _norm(s) not in have:
need.append(s)
return need, total


def main():
ws = resolve_paths()
vpy = os.path.join(VENV_DIR, "bin", "python")
vpip = [vpy, "-m", "pip"] # ★ 用解释器调 pip 模块,杜绝装错环境

# pip 缓存放工作区:重跑命中缓存,省重复下载
cache_dir = os.path.join(ws, ".cache", "pip")
os.environ["PIP_CACHE_DIR"] = cache_dir
os.makedirs(cache_dir, exist_ok=True)

print("=" * 58)
print(" ComfyUI 一键安装 + 启动")
print("=" * 58)
print(f" 工作区 : {ws}")
print(f" ComfyUI : {COMFY_DIR}")
print(f" venv : {VENV_DIR}")
print(f" pip缓存 : {cache_dir}")
print("=" * 58)

# 1. 克隆(有 main.py 才算装好,避免空壳目录被误判为已完成)
print("\n[1/4] 克隆 ComfyUI")
if os.path.isfile(os.path.join(COMFY_DIR, "main.py")):
print(f" [跳过] 已存在: {COMFY_DIR}")
else:
if os.path.isdir(COMFY_DIR):
print(f" [清理] 目录不完整(无 main.py),重新克隆: {COMFY_DIR}")
shutil.rmtree(COMFY_DIR)
run(["git", "clone", "--depth", "1", REPO_URL, COMFY_DIR])
print(" [OK] 克隆完成")

# 2. 虚拟环境(--system-site-packages 复用镜像预装的 torch,不重下几个 GB)
print("\n[2/4] 创建虚拟环境")
if os.path.exists(vpy):
print(f" [跳过] venv 已存在: {VENV_DIR}")
else:
run([sys.executable, "-m", "venv", "--system-site-packages", VENV_DIR])
print(" [OK] 创建完成(复用镜像预装的 torch)")

# 3. 依赖
print("\n[3/4] 安装依赖")
run(vpip + ["install", "-q", "--upgrade", "pip"])

req = os.path.join(COMFY_DIR, "requirements.txt")
if os.path.exists(req):
need, total = missing_from_requirements(vpip, req)
if total and not need:
print(f" [跳过] requirements.txt 的 {total} 个包已全部安装")
else:
print(f" torch 等镜像已装的会自动跳过,不重复下载(缺 {len(need)}/{total})")
run(vpip + ["install", "-r", req])
print(" [OK] 依赖安装完成")
else:
print(f" [警告] 未找到 {req},跳过依赖安装")

print(" 安装 comfyui-manager(解决工作流节点缺失)")
if _norm("comfyui-manager") in installed_set(vpip):
print(" [跳过] comfyui-manager 已安装")
else:
run(vpip + ["install", "-q", "comfyui-manager"])
print(" [OK] comfyui-manager 安装完成")

print("\n" + "=" * 58)
print(" 安装完成 ✅")
print("=" * 58)

if "--no-launch" in sys.argv:
print(f" [完成] 未启动。手动启动:")
print(f" cd {COMFY_DIR} && {vpy} main.py --listen 0.0.0.0 --port {PORT}")
return

# 4. 启动
print("\n[4/4] 启动服务")
if port_open():
print(f" [跳过] 端口 {PORT} 已在监听(服务可能已运行)")
return

args = list(LAUNCH_ARGS)
if "--novram" in sys.argv:
args.append("--novram")

cmd = [vpy, "main.py", "--listen", "0.0.0.0", "--port", str(PORT)] + args
os.makedirs(LOG_DIR, exist_ok=True)
log_path = os.path.join(LOG_DIR, "comfyui.log")

lf = open(log_path, "w", encoding="utf-8")
proc = subprocess.Popen(cmd, cwd=COMFY_DIR, stdout=lf, stderr=subprocess.STDOUT)
lf.close() # 子进程已持有自己的 fd,父进程可以关掉

print(f" [信息] 已后台启动 (PID {proc.pid}),日志: {log_path}")
print(f" [等待] 等待 {PORT} 就绪(最多 300 秒)")
for i in range(300):
if port_open():
print(f" [OK] 就绪(耗时 {i} 秒)→ http://127.0.0.1:{PORT}")
return
if proc.poll() is not None: # 进程秒退,不用傻等
print(f" [错误] 进程已退出(返回码 {proc.returncode}),查看日志: {log_path}")
return
time.sleep(1)

print(f" [错误] 300 秒内未就绪,查看日志: {log_path}")


if __name__ == "__main__":
try:
main()
except subprocess.CalledProcessError as e:
print(f"\n[中止] 命令执行失败: {' '.join(e.cmd)}")
print(" 修复后直接重跑本脚本,已完成步骤会自动跳过")
sys.exit(1)
except KeyboardInterrupt:
print("\n[退出] 已中断")
sys.exit(130)

下载模型

下载方案一:

总共至少下载五个模型, 分别是:

  • minimax_h3_fl2va_pruned_int8_convrot.safetensors

    直接复制以下指令到终端下载, 支持断点续传, 下面几个模型如法炮制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /home/admin/workspace/ComfyUI/models/diffusion_models/ && \
    EXPECT=15687142551 && \
    F=minimax_h3_fl2va_pruned_int8_convrot.safetensors && \
    URL="https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/diffusion_models/$F" && \
    for i in $(seq 1 30); do
    curl -L -C - --retry 5 --retry-delay 3 --retry-all-errors -o "$F" "$URL"
    SZ=$(stat -c%s "$F" 2>/dev/null || echo 0)
    if [ "$SZ" -ge "$EXPECT" ]; then echo "✅ 完成: $SZ bytes"; break; fi
    echo "⚠️ 第$i次仅 $SZ/$EXPECT, 3秒后续传..."; sleep 3
    done
  • qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /home/admin/workspace/ComfyUI/models/text_encoders/ && \
    EXPECT=15687142551 && \
    F=qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors && \
    URL="https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/text_encoders/$F" && \
    for i in $(seq 1 30); do
    curl -L -C - --retry 5 --retry-delay 3 --retry-all-errors -o "$F" "$URL"
    SZ=$(stat -c%s "$F" 2>/dev/null || echo 0)
    if [ "$SZ" -ge "$EXPECT" ]; then echo "✅ 完成: $SZ bytes"; break; fi
    echo "⚠️ 第$i次仅 $SZ/$EXPECT, 3秒后续传..."; sleep 3
    done
  • minimax_h3_video_vae_fp16.safetensors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /home/admin/workspace/ComfyUI/models/vae/ && \
    EXPECT=15687142551 && \
    F=minimax_h3_video_vae_fp16.safetensors && \
    URL="https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/vae/$F" && \
    for i in $(seq 1 30); do
    curl -L -C - --retry 5 --retry-delay 3 --retry-all-errors -o "$F" "$URL"
    SZ=$(stat -c%s "$F" 2>/dev/null || echo 0)
    if [ "$SZ" -ge "$EXPECT" ]; then echo "✅ 完成: $SZ bytes"; break; fi
    echo "⚠️ 第$i次仅 $SZ/$EXPECT, 3秒后续传..."; sleep 3
    done
  • minimax_h3_audio_vae_fp32.safetensors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /home/admin/workspace/ComfyUI/models/vae/ && \
    EXPECT=15687142551 && \
    F=minimax_h3_audio_vae_fp32.safetensors && \
    URL="https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/vae/$F" && \
    for i in $(seq 1 30); do
    curl -L -C - --retry 5 --retry-delay 3 --retry-all-errors -o "$F" "$URL"
    SZ=$(stat -c%s "$F" 2>/dev/null || echo 0)
    if [ "$SZ" -ge "$EXPECT" ]; then echo "✅ 完成: $SZ bytes"; break; fi
    echo "⚠️ 第$i次仅 $SZ/$EXPECT, 3秒后续传..."; sleep 3
    done
  • minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    cd /home/admin/workspace/ComfyUI/models/loras/ && \
    EXPECT=15687142551 && \
    F=minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors && \
    URL="https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/loras/$F" && \
    for i in $(seq 1 30); do
    curl -L -C - --retry 5 --retry-delay 3 --retry-all-errors -o "$F" "$URL"
    SZ=$(stat -c%s "$F" 2>/dev/null || echo 0)
    if [ "$SZ" -ge "$EXPECT" ]; then echo "✅ 完成: $SZ bytes"; break; fi
    echo "⚠️ 第$i次仅 $SZ/$EXPECT, 3秒后续传..."; sleep 3
    done

下载方案二

使用官方的下载方式:

  1. 安装ModelScope

    1
    pip3 install modelscope
  2. 下载指定模型到目录

    同样复制下面命令行到终端执行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    modelscope download --model Comfy-Org/MiniMax-H3 \
    --include 'diffusion_models/minimax_h3_fl2va_pruned_int8_convrot.safetensors' \
    --local_dir '/home/admin/workspace/ComfyUI/models'

    modelscope download --model Comfy-Org/MiniMax-H3 \
    --include 'text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors' \
    --local_dir '/home/admin/workspace/ComfyUI/models'

    modelscope download --model Comfy-Org/MiniMax-H3 \
    --include 'vae/minimax_h3_audio_vae_fp32.safetensors' \
    --local_dir '/home/admin/workspace/ComfyUI/models'

    modelscope download --model Comfy-Org/MiniMax-H3 \
    --include 'vae/minimax_h3_video_vae_fp16.safetensors' \
    --local_dir '/home/admin/workspace/ComfyUI/models'

    modelscope download --model Comfy-Org/MiniMax-H3 \
    --include 'loras/minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors' \
    --local_dir '/home/admin/workspace/ComfyUI/models'

如果你不着急的话, 可以直接一行代码全量下载MiniMax-H3所有模型:

1
modelscope download --model Comfy-Org/MiniMax-H3

下载方案三(推荐)

由于模型数量过多, 建议直接使用脚本批量下载, 带进度条 + 日志 + 断点续传 + 重试功能, 方便管理和复用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ModelScope 大模型下载工具(带进度条 + 日志 + 断点续传 + 重试)

功能:
- 实时显示每个文件的下载进度条 (tqdm) + 速度/已下载/预计剩余
- 全量日志记录到文件,控制台只显示进度
- 断点续传: 中断后重跑自动从已下载字节继续
- 失败重试 + 退避,应对 "资源解析服务请求失败"
- 完成时做大小校验 (达到 expected 的 95% 视为完成)
"""
import os
import sys
import time
import json
import logging
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed

try:
from tqdm import tqdm
except ImportError:
os.system(f"{sys.executable} -m pip install -q tqdm")
from tqdm import tqdm


# ============ 配置 (按需修改) ============
BASE = "https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master"
BASE_DIR = "/home/admin/workspace/ComfyUI/models"

# 任务列表: (相对路径, 期望大小GB)
JOBS = [
("diffusion_models/minimax_h3_fl2va_pruned_int8_convrot.safetensors", 20.97),
("diffusion_models/minimax_h3_ref2va_pruned_int8_convrot.safetensors", 20.97),
("text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors", 17.14),
("vae/minimax_h3_video_vae_fp16.safetensors", 5.21),
("vae/minimax_h3_audio_vae_fp32.safetensors", 0.61),

("loras/minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors", 2),
]

WORKERS = 2 # 并发数(大模型建议 2,避免把解析服务打满)
RETRIES = 4 # 每个文件最大重试次数
CHUNK_SIZE = 1 << 20 # 1MB
TIMEOUT = 120 # 单次请求超时(秒)
LOG_DIR = "/tmp/model_download_logs"


# ============ 日志初始化 ============
def setup_logger():
os.makedirs(LOG_DIR, exist_ok=True)
log_file = os.path.join(
LOG_DIR, f"download_{datetime.now():%Y%m%d_%H%M%S}.log"
)
logger = logging.getLogger("downloader")
logger.setLevel(logging.DEBUG)

# 文件 handler: 记录全部 DEBUG
fh = logging.FileHandler(log_file, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(fh)

# 控制台 handler: 只显示 INFO 及以上(进度条由 tqdm 单独管理)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
logger.addHandler(ch)

logger.info(f"日志文件: {log_file}")
return logger


LOG = setup_logger()


# ============ 下载核心 ============
def _stream_download(url, path, exp_bytes, done, desc, pbar):
"""从 done 字节处继续流式下载,更新进度条"""
headers = {"Range": f"bytes={done}-"} if done else {}
# 遇到 200(不支持断点) 则从头开始
import requests
r = requests.get(url, headers=headers, stream=True, timeout=TIMEOUT)
if r.status_code == 200 and done:
LOG.warning(f"{desc} 服务端不支持 Range, 从头下载")
done = 0
r.raise_for_status()

# 若服务端返回 Content-Length, 用它校正总大小
content_length = r.headers.get("Content-Length")
if content_length:
pbar.total = done + int(content_length)
pbar.refresh()

mode = "ab" if done else "wb"
with open(path, mode) as f:
for chunk in r.iter_content(CHUNK_SIZE):
if not chunk:
continue
f.write(chunk)
done += len(chunk)
pbar.update(len(chunk))
return done


def download(job):
"""下载单个文件,带重试"""
rel, exp_gb = job
exp_bytes = int(exp_gb * 1e9)
url = f"{BASE}/{rel}"
path = os.path.join(BASE_DIR, rel)
os.makedirs(os.path.dirname(path), exist_ok=True)

# 已完成则跳过
if os.path.exists(path) and os.path.getsize(path) >= exp_bytes * 0.95:
LOG.info(f"[跳过] {rel} (已存在且达到预期大小)")
return f"[跳过] {rel}"

desc = os.path.basename(rel)
for attempt in range(1, RETRIES + 1):
current = os.path.getsize(path) if os.path.exists(path) else 0
with tqdm(
total=exp_bytes,
initial=current,
unit="B",
unit_scale=True,
desc=f"{desc:40s}",
miniters=1,
leave=True,
) as pbar:
try:
_stream_download(url, path, exp_bytes, current, desc, pbar)
final = os.path.getsize(path)
speed_mb = final / (time.time() - pbar.start_t) / 1e6 if False else 0
LOG.info(
f"[完成] {rel}: {final/1e9:.2f}/{exp_gb:.2f} GB "
f"({final/exp_bytes*100:.1f}%)"
)
return f"[完成] {rel}: {final/1e9:.2f} GB"
except Exception as e:
LOG.error(f"[重试 {attempt}/{RETRIES}] {rel}: {e}")
time.sleep(2 ** attempt) # 指数退避

return f"[失败] {rel}: 超过最大重试次数"


# ============ 入口 ============
def main():
LOG.info(f"目标目录: {BASE_DIR}")
LOG.info(f"任务数: {len(JOBS)}, 并发: {WORKERS}")
os.makedirs(BASE_DIR, exist_ok=True)

results = []
with ThreadPoolExecutor(max_workers=WORKERS) as ex:
futures = {ex.submit(download, j): j for j in JOBS}
for fut in as_completed(futures):
results.append(fut.result())

# 汇总
LOG.info("=" * 50)
LOG.info("下载结果汇总:")
for msg in results:
LOG.info(f" {msg}")

# 最终核对
LOG.info("=" * 50)
LOG.info("最终文件核对:")
all_ok = True
for rel, exp_gb in JOBS:
p = os.path.join(BASE_DIR, rel)
if os.path.exists(p) and os.path.getsize(p) >= int(exp_gb * 1e9) * 0.95:
LOG.info(f" [OK] {rel}: {os.path.getsize(p)/1e9:.2f}/{exp_gb:.2f} GB")
else:
all_ok = False
size = os.path.getsize(p) if os.path.exists(p) else 0
LOG.warning(f" [缺失] {rel}: {size/1e9:.2f}/{exp_gb:.2f} GB")

if all_ok:
LOG.info("全部文件下载完成 ✅")
else:
LOG.warning("部分文件缺失,可重跑脚本自动续传")
sys.exit(1)


if __name__ == "__main__":
main()

创建minmax-h3.py文件, 将上面代码拷贝到文件中保存, 然后在同级目录下运行以下指令开启下载:

1
python3 minmax-h3.py

搭建内网穿透隧道

魔搭给的外网链接如果访问不了, 就需要部署内网穿透, 你可以Cloudflare或者ngrok, 或者其他的穿透工具, 这里我选择ngrok

首先需要去ngrok官网注册获取TOKEN密钥, 使用邮箱注册:

image-20260904162615903

注册登录后, 在主页面拿到你的Authtoken:

image-20260904162741883

image-20260904162851455

复制该Authtoken, 然后在终端中运行以下指令安装ngrok, 记得替换你的Token:

1
2
3
4
pip3 install  pyngrok

pyngrok config add-authtoken 你的token
pyngrok http 8188

或者运行下面的代码一样的效果:

1
2
3
4
5
6
7
8
9
pip3 install  pyngrok

python3 - <<'PY' # ← 开头:把下面内容喂给 python3
from pyngrok import ngrok
ngrok.set_auth_token("你的token")
t = ngrok.connect("127.0.0.1:8188", bind_tls=True)
print("公网地址:", t.public_url)
while True: time.sleep(60)
PY # ← 结尾:到此为止

同样的我们还有更完善的脚本启动ngrok方案, 根据自己的需求选择即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ngrok 内网穿透 - 命令行工具(修正版)

修复点:
1. 移除 region 参数 (新版 HTTPv2 隧道不支持 tunnel 级 region 字段,会报 400)
2. connect() 只用 addr / bind_tls / auth 等合法关键字
3. 先探测本地端口是否就绪,再建隧道 (避免建完隧道 502)
4. token 优先从环境变量读取,避免硬编码泄露
5. 依赖缺失自动 pip install
6. 支持 connect / status / disconnect / kill 四种动作
"""
import os
import sys
import time
import socket
import subprocess
import argparse


# ============ 自动安装依赖 ============
def ensure_pyngrok():
try:
import pyngrok # noqa
except ImportError:
print("[...] 未检测到 pyngrok,正在安装...")
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "-q", "pyngrok"]
)


# ============ 端口探测 ============
def port_open(host, port, timeout=2.0):
"""检测本地端口是否可接受连接"""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except Exception:
return False


def http_ready(url, timeout=5):
"""简单 HTTP 探测,确认服务真的在响应"""
try:
import urllib.request
req = urllib.request.Request(url, method="HEAD")
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status < 500
except Exception:
return False


# ============ ngrok 封装 ============
def configure_token(token):
"""写入最小配置,不包含 region 字段"""
from pyngrok import ngrok
if token:
ngrok.set_auth_token(token)
# 强制生成一个干净的配置(不写 region)
cfg_dir = os.path.expanduser("~/.config/ngrok")
os.makedirs(cfg_dir, exist_ok=True)
cfg_path = os.path.join(cfg_dir, "ngrok.yml")
with open(cfg_path, "w", encoding="utf-8") as f:
f.write(f"version: 3\nagent:\n authtoken: {token or 'YOUR_TOKEN'}\n")
print(f"[OK] authtoken 已配置 -> {cfg_path}")


def build_kwargs(port, bind_tls, basic_auth):
"""只使用合法的关键字参数,绝不传 region"""
kwargs = {
"addr": port, # 关键字传端口,最稳妥
"bind_tls": bind_tls,
}
if basic_auth:
kwargs["auth"] = basic_auth # "user:pass"
return kwargs


def action_connect(port, token, bind_tls, basic_auth, wait):
from pyngrok import ngrok

# 1) 配置 token
configure_token(token)

# 2) 端口自检
print(f"[...] 检测本地服务 http://127.0.0.1:{port}")
if not port_open("127.0.0.1", port):
print(f"[错误] 端口 {port} 未监听,请先启动 ComfyUI 等服务")
print(f" 可尝试: curl -sS http://127.0.0.1:{port}/system_stats")
sys.exit(1)

if not http_ready(f"http://127.0.0.1:{port}"):
print(f"[警告] 端口开放但 HTTP 未就绪,隧道可能暂时 502")

# 3) 建立隧道 (不带 region)
print(f"[...] 正在建立隧道: http://127.0.0.1:{port}")
kwargs = build_kwargs(port, bind_tls, basic_auth)
tunnel = ngrok.connect(**kwargs)
url = tunnel.public_url

print("=" * 60)
print(f" 公网地址 : {url}")
print(f" 本地端口 : http://127.0.0.1:{port}")
if basic_auth:
print(f" 访问密码 : {basic_auth}")
print("=" * 60)

# 4) 阻塞保持,等待 Ctrl+C
if wait:
print("[提示] 隧道已在后台运行,按 Ctrl+C 退出")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[...] 正在断开隧道...")
ngrok.disconnect(url)
ngrok.kill()
print("[OK] 已退出")
return url


def action_status():
from pyngrok import ngrok
tunnels = ngrok.get_tunnels()
if not tunnels:
print("当前没有活跃的隧道")
return
print("当前活跃隧道:")
for t in tunnels:
print(f" - {t.public_url} -> {t.config.get('addr', '?')}")


def action_disconnect():
from pyngrok import ngrok
for t in ngrok.get_tunnels():
ngrok.disconnect(t.public_url)
print("[OK] 已断开所有隧道")


def action_kill():
from pyngrok import ngrok
ngrok.kill()
print("[OK] ngrok 进程已终止")


# ============ 命令行入口 ============
def parse_args():
parser = argparse.ArgumentParser(
description="ngrok 内网穿透命令行工具 (修正 region 报错版)"
)
parser.add_argument(
"action",
nargs="?",
default="connect",
choices=["connect", "status", "disconnect", "kill"],
help="动作 (默认: connect)",
)
parser.add_argument("--port", type=int, default=8188, help="本地端口 (默认 8188)")
parser.add_argument(
"--token",
type=str,
default=os.environ.get("NGROK_AUTHTOKEN", ""),
help="ngrok authtoken (默认读环境变量 NGROK_AUTHTOKEN)",
)
parser.add_argument(
"--basic-auth",
type=str,
default="",
help="访问密码,格式 user:pass (可选)",
)
parser.add_argument(
"--no-tls",
action="store_true",
help="关闭强制 HTTPS (默认开启 bind_tls)",
)
parser.add_argument(
"--no-wait",
action="store_true",
help="建隧道后立即返回,不阻塞 (用于脚本串联)",
)
return parser.parse_args()


def main():
ensure_pyngrok()
args = parse_args()

if not args.token and args.action == "connect":
print("[警告] 未设置 NGROK_AUTHTOKEN,请先:")
print(" export NGROK_AUTHTOKEN='你的token'")
print(" 或 --token 你的token")

bind_tls = not args.no_tls

if args.action == "connect":
action_connect(args.port, args.token, bind_tls, args.basic_auth, not args.no_wait)
elif args.action == "status":
action_status()
elif args.action == "disconnect":
action_disconnect()
elif args.action == "kill":
action_kill()


if __name__ == "__main__":
main()


# 常用命令
# python3 ngrok.py --token 你的token # 建隧道(默认 8188)
# python3 ngrok.py --port 7860 --token xxx # 换端口
# python3 ngrok.py --token xxx --basic-auth admin:123 # 加访问密码
# python3 ngrok.py status # 看当前隧道
# python3 ngrok.py disconnect # 断开
# python3 ngrok.py kill # 杀进程

创建ngrok.py文件, 将上面复制到文件中, 然后执行以下代码启动隧道:

1
2
export NGROK_AUTHTOKEN='你的token'
python3 ngrok.py

隧道启动后如下:

image-20260904121738335

将公网地址粘贴到浏览器上打开就能直接访问了:

image-20260904121833059

总结

由于模型体积比较大, 所以我们可以一边下载模型的同时一边安装ComfyUI, 多线同步进行, 考虑到挨个复制粘贴太过麻烦, 于是决定将这三条线整合成一个终极的脚本文件, 这样, 我们只需要一个指令, 就能实现ComfyUI安装部署+模型自动并行下载+内网隧道启动三个任务全自动化完成:

终极脚本代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI + MiniMax-H3 + ngrok 一键部署脚本
==========================================
三合一:
1. ComfyUI 安装部署 (git clone + venv + 依赖 + ComfyUI-Manager)
2. MiniMax-H3 模型下载 (进度条 + 日志 + 断点续传 + 重试)
3. ngrok 内网穿透 (启动服务后自动建隧道并打印公网链接)

★ 核心设计: 工作区持久化
实例重启后系统目录(如 /usr/local/lib/python3.x/site-packages)会被重置,
但工作区(如 /mnt/workspace)会保留。因此:
- venv 建在工作区内 → 重启后依赖还在, 不用重装
- 模型下载到工作区 → 重启后 60GB 模型还在, 不用重下
- pip 缓存放工作区 → 万一要重装, 缓存命中省下载

★ 路径自动探测:
按顺序探测候选工作区, 第一个存在的胜出; 都不存在则用第一个并创建。
你不用关心实例到底是 /mnt/workspace 还是 /home/admin/workspace。

★ 并行:
ComfyUI 克隆 / venv 创建+装 pyngrok / 模型下载 三路并行。
pip 操作统一加线程锁, 避免多任务同时装包把环境写坏。

★ 幂等:
- ComfyUI 已存在(有 main.py) → 跳过克隆
- requirements.txt 里的包已全部安装 → 跳过整条 pip 命令
- comfyui-manager / pyngrok / requests / tqdm 已装 → 跳过
- 模型文件已达预期大小 → 跳过
- torch 能正常调用 CUDA → 跳过对齐

用法:
python setup_all.py # 全自动: 安装 → 下模型 → 启服务 → 建隧道 → 打印链接
python setup_all.py --no-tunnel # 不建隧道 (用平台自带代理时)
python setup_all.py --no-launch # 只安装+下模型, 不启动服务
python setup_all.py --no-align # 跳过 torch/CUDA 版本对齐(已手动处理时用)
python setup_all.py --novram # 追加 --novram(OOM 兜底,默认不加)

中断后直接重跑, 所有已完成的步骤会自动跳过。
"""
import os
import re
import sys
import time
import socket
import logging
import argparse
import subprocess
import threading
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

# ==========================================================================
# 配置区(一般只需改 NGROK_TOKEN)
# ==========================================================================

# ---- 工作区候选(自动探测,按顺序,第一个存在的胜出)----
WORKSPACE_CANDIDATES = [
"/home/admin/workspace",
"/mnt/workspace",
]

# ---- 派生路径(不要手改,自动跟随工作区)----
# COMFY_DIR = <workspace>/ComfyUI
# MODELS_DIR = <workspace>/ComfyUI/models
# VENV_DIR = <workspace>/venv
COMFY_DIR = ""
MODELS_DIR = ""
VENV_DIR = ""

# ---- venv 开关 ----
# True = 在工作区建独立环境(推荐)
# 且带 --system-site-packages,可复用【镜像预装】的 torch 等大件,
# 避免重新下载几个 GB;ComfyUI 特有依赖装进 venv(工作区,重启保留)。
# False = 直接用系统 Python(不推荐:装到系统目录的会随重启丢失)
USE_VENV = True

# ---- ngrok ----
NGROK_TOKEN = "" # ← 填你的 ngrok authtoken;留空则不建隧道(改用平台自带代理)
NGROK_REGION = "" # 可选: us/eu/ap/jp/au/sa/in;免费版一般不支持,留空即可

# ---- 服务端口 ----
PORT = 8188

# ---- torch / CUDA 对齐 ----
# 宿主驱动锁定 CUDA 12.8,而 PyPI 默认 wheel 是 cu130 → 跨大版本不兼容,
# 会报 "NVIDIA driver on your system is too old (found version 12080)"。
# 脚本会先检测 torch.cuda.is_available(),为 False 时才重装对齐。
# DSW 里不能升级驱动,只能改 PyTorch 这一侧。
TORCH_INDEX_URL = os.environ.get(
"TORCH_INDEX_URL", "https://download.pytorch.org/whl/cu126"
)

# ---- 启动参数(追加到固定命令之后)----
# 固定部分已包含: --disable-pinned-memory --fast-disk --preview-method none --reserve-vram 0
EXTRA_ARGS = []

# ---- 模型下载配置 ----
BASE_URL = "https://modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master"

# 任务列表: (相对路径, 期望大小GB)
# expected_gb > 0 : 达到该值 95% 视为完成(精确校验)
# expected_gb = 0 : 不校验大小,只要下载完成且文件非空(作者未公布精确大小时用)
#
# ⚠️ 已修正的原始错误:
# 1) LoRA 文件名被截断: minimax_h3_fl2v_turbo_4step_v1.safetensors
# → 真实名: minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors
# 2) LoRA 大小填 2GB,实际约 0.74GB → 永远判缺失
# 3) nvfp4 大小填 17.14GB,实际约 15.69GB → 永远判缺失
#
# 💡 合计约 63GB。若确定不做 R2V,可删掉 ref2va 那行,省 21GB。
JOBS = [
("diffusion_models/minimax_h3_fl2va_pruned_int8_convrot.safetensors", 20.97),
("diffusion_models/minimax_h3_ref2va_pruned_int8_convrot.safetensors", 20.97),
("text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors", 0),
("vae/minimax_h3_video_vae_fp16.safetensors", 5.21),
("vae/minimax_h3_audio_vae_fp32.safetensors", 0.61),
("loras/minimax_h3_fl2v_turbo_4step_v1.0_768p_comfyui_bf16.safetensors", 0),
]

WORKERS = 2 # 模型下载并发数(大模型建议 2,避免把解析服务打满)
RETRIES = 4 # 每个文件最大重试次数
CHUNK_SIZE = 1 << 20 # 1MB
TIMEOUT = 120 # 单次请求超时(秒)
LOG_DIR = "/tmp/comfyui_setup_logs"

# 所有 pip 操作共用的锁(pip 非线程安全,并发装同一环境会写坏)
PIP_LOCK = threading.Lock()

# ==========================================================================
# 路径探测
# ==========================================================================
def resolve_paths():
"""探测工作区并派生所有路径(保证单源,不会错位)"""
global COMFY_DIR, MODELS_DIR, VENV_DIR

ws = None
for cand in WORKSPACE_CANDIDATES:
if os.path.isdir(cand):
ws = cand
break
if ws is None:
# 都不存在:用第一个并创建
ws = WORKSPACE_CANDIDATES[0]
os.makedirs(ws, exist_ok=True)

COMFY_DIR = os.path.join(ws, "ComfyUI")
MODELS_DIR = os.path.join(COMFY_DIR, "models") # 派生,绝不手写第二遍
VENV_DIR = os.path.join(ws, "venv") # 工作区内,重启保留
return ws


# ==========================================================================
# 日志
# ==========================================================================
def setup_logger():
os.makedirs(LOG_DIR, exist_ok=True)
log_file = os.path.join(LOG_DIR, f"setup_{datetime.now():%Y%m%d_%H%M%S}.log")
logger = logging.getLogger("setup")
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler(log_file, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(fh)

ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
ch.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
logger.addHandler(ch)

logger.info(f"日志文件: {log_file}")
return logger


LOG = setup_logger()


# ==========================================================================
# 通用工具
# ==========================================================================
def run_cmd(cmd, **kw):
"""执行命令,失败抛异常"""
LOG.debug(f"$ {' '.join(cmd)}")
return subprocess.run(cmd, check=True, **kw)


def port_open(host="127.0.0.1", port=PORT, timeout=2.0):
"""检测端口是否在监听"""
try:
s = socket.create_connection((host, port), timeout=timeout)
s.close()
return True
except Exception:
return False


def venv_python():
"""venv 解释器路径;未启用 venv 则用当前解释器"""
if USE_VENV:
return os.path.join(VENV_DIR, "bin", "python")
return sys.executable


def venv_pip():
"""★ 统一返回列表形式的 pip 命令。

用 <venv>/bin/python -m pip,而不是裸的 <venv>/bin/pip 脚本:
裸 pip 脚本的 shebang 写死了创建它时的解释器,可能与 venv 实际解释器
不一致,导致"装到全局、venv 里 import 不到、版本号一直不变"的诡异现象。
python -m pip 保证装到当前解释器对应的 site-packages,不会串环境。
"""
if USE_VENV:
return [os.path.join(VENV_DIR, "bin", "python"), "-m", "pip"]
return [sys.executable, "-m", "pip"]


# ==========================================================================
# 包管理(幂等:已装则跳过)
# ==========================================================================
def _norm(name):
"""包名规范化:去版本约束/环境标记/extras,统一小写与连字符"""
return re.split(r"[<>=!~\[;@]", name.strip())[0].strip().lower().replace("_", "-")


def installed_set(pip_cmd):
"""返回当前环境已装包名集合"""
try:
r = subprocess.run(list(pip_cmd) + ["list", "--format=freeze"],
capture_output=True, text=True, timeout=300)
if r.returncode != 0:
return set()
out = set()
for line in r.stdout.splitlines():
name = line.split("==")[0].split(" @")[0].strip()
if name:
out.add(_norm(name))
return out
except Exception as e: # noqa: BLE001
LOG.debug(f"pip list 失败: {e}")
return set()


def ensure_packages(pip_cmd, packages, label=""):
"""只装缺失的包;全部已装则一次 pip 命令都不跑"""
with PIP_LOCK:
installed = installed_set(pip_cmd)
missing = [p for p in packages if _norm(p) not in installed]
if not missing:
LOG.info(f"[跳过] 已安装: {', '.join(packages)}")
return True
LOG.info(f"[安装] {label or ''}{', '.join(missing)}")
run_cmd(list(pip_cmd) + ["install", "-q"] + missing)
LOG.info(f"[OK] {', '.join(missing)} 安装完成")
return True


def install_requirements(pip_cmd, req_path):
"""预检 requirements.txt:全部已装则跳过整条命令,省去依赖解析时间"""
with PIP_LOCK:
if not os.path.exists(req_path):
LOG.warning(f"[警告] 未找到 {req_path},跳过依赖安装")
return False

installed = installed_set(pip_cmd)
needed, total = [], 0
for line in open(req_path, encoding="utf-8"):
s = line.strip()
if not s or s.startswith("#") or s.startswith("-"):
continue # 跳过注释与 --index-url 等选项行
total += 1
if _norm(s) not in installed:
needed.append(s)

if total and not needed:
LOG.info(f"[跳过] requirements.txt 的 {total} 个包已全部安装")
return True

LOG.info(f"[安装] requirements.txt(缺 {len(needed)}/{total} 个,首次较慢)")
run_cmd(list(pip_cmd) + ["install", "-q", "-r", req_path])
LOG.info("[OK] 依赖安装完成")
return True


def torch_status(vpy):
"""返回 '版本 cuda版本 是否可用' """
code = ("import torch;print(torch.__version__, torch.version.cuda, "
"torch.cuda.is_available())")
try:
r = subprocess.run([vpy, "-c", code], capture_output=True,
text=True, timeout=300)
return r.stdout.strip()
except Exception as e: # noqa: BLE001
return f"<检测失败: {e}>"


def align_torch(pip_cmd):
"""torch 与宿主驱动 CUDA 版本对齐(能正常用 CUDA 则跳过)"""
vpy = venv_python()
out = torch_status(vpy)
LOG.info(f"[检测] torch: {out or '<无输出>'}")

if out.endswith("True"):
LOG.info("[跳过] torch 可正常调用 CUDA,无需对齐")
return True

LOG.warning("[对齐] torch 与驱动 CUDA 不匹配,重装匹配版本")
LOG.warning(f" 源: {TORCH_INDEX_URL}")
with PIP_LOCK:
run_cmd(list(pip_cmd) + ["install", "--force-reinstall",
"torch", "torchvision", "torchaudio",
"--index-url", TORCH_INDEX_URL])
LOG.info(f"[检测] 对齐后: {torch_status(vpy)}")
return True


# ==========================================================================
# 任务一:ComfyUI 克隆 + 依赖(与 venv、模型下载并行)
# ==========================================================================
def task_clone_and_install(venv_ready):
LOG.info("=" * 60)
LOG.info("[安装] ComfyUI 部署开始")
LOG.info("=" * 60)

# 1. 克隆(有 main.py 即视为已装,无论是否 git 仓库)
main_py = os.path.join(COMFY_DIR, "main.py")
if os.path.isfile(main_py):
LOG.info(f"[跳过] ComfyUI 已存在: {COMFY_DIR}")
elif os.path.isdir(COMFY_DIR):
LOG.info(f"[跳过] 目录已存在(非 git 仓库): {COMFY_DIR}")
else:
LOG.info(f"[克隆] ComfyUI → {COMFY_DIR}")
run_cmd(["git", "clone",
"https://github.com/comfyanonymous/ComfyUI.git", COMFY_DIR])
LOG.info("[OK] 克隆完成")

# 2. 等 venv 建好(venv 与克隆并行,这里通常几秒内就放行)
if USE_VENV and not venv_ready.wait(timeout=600):
LOG.error("[错误] 等待 venv 超时")
return False

# 3. 依赖
install_requirements(venv_pip(), os.path.join(COMFY_DIR, "requirements.txt"))
LOG.info("[安装] ComfyUI 部署完成 ✅")
return True


# ==========================================================================
# 任务二:venv + pyngrok(与克隆、模型下载并行)
# ==========================================================================
def task_venv_and_pyngrok(venv_ready):
try:
if not USE_VENV:
LOG.info("[跳过] 未启用 venv,使用系统 Python")
return True

vpy = os.path.join(VENV_DIR, "bin", "python")
if os.path.exists(vpy):
LOG.info(f"[跳过] venv 已存在: {VENV_DIR}")
else:
LOG.info(f"[创建] venv → {VENV_DIR}")
# ★ 关键:加 --system-site-packages
# 让 venv 能看见【镜像预装】的包(torch 等大件)。
# 镜像层的包重启后仍在,可安全复用 —— 省下重新下载几个 GB 的 torch。
#
# requirements.txt 里 torch/torchsde/torchvision/torchaudio 都是裸名
# (无版本约束),pip 会判定 already satisfied 直接跳过,零下载。
# ⚠️ 但这也意味着:镜像若预装的是 cu130 而驱动是 12.8,
# torch 会一直不可用 —— 所以阶段 B 有一道 torch 对齐兜底。
#
# 而 ComfyUI 特有的新依赖会装进 venv 自己的 site-packages
# (位于工作区,重启保留),不会污染系统目录。
#
# 注意:只有【镜像预装】的包可这样复用;
# 你自己 pip 装到系统目录的包仍会随重启丢失。
run_cmd([sys.executable, "-m", "venv",
"--system-site-packages", VENV_DIR])
LOG.info("[OK] venv 创建完成(复用镜像预装包模式)")
finally:
# 无论成功失败都放行,避免其余任务死锁
venv_ready.set()

# pyngrok 装进 venv(工作区持久),而不是系统目录
ensure_packages(venv_pip(), ["pyngrok"], label="pyngrok: ")
return True


# ==========================================================================
# 任务三:模型下载
# ==========================================================================
def _stream_download(url, path, done, desc, pbar, requests):
"""从 done 字节处续传"""
headers = {"Range": f"bytes={done}-"} if done else {}
r = requests.get(url, headers=headers, stream=True, timeout=TIMEOUT)
if r.status_code == 200 and done:
LOG.warning(f"{desc} 服务端不支持 Range,从头下载")
done = 0
r.raise_for_status()

cl = r.headers.get("Content-Length")
if cl:
pbar.total = done + int(cl)
pbar.refresh()

mode = "ab" if done else "wb"
with open(path, mode) as f:
for chunk in r.iter_content(CHUNK_SIZE):
if not chunk:
continue
f.write(chunk)
done += len(chunk)
pbar.update(len(chunk))
return done


def download_one(job):
"""下载单个文件(带重试 + 断点续传)"""
import requests
from tqdm import tqdm

rel, exp_gb = job
exp_bytes = int(exp_gb * 1e9)
url = f"{BASE_URL}/{rel}"
path = os.path.join(MODELS_DIR, rel)
os.makedirs(os.path.dirname(path), exist_ok=True)

# 完成判定
# ★ 修正:exp_gb=0 时必须要求文件非空。
# 原写法 `size >= 0*0.95` 恒为真 → 0 字节空文件也会被判为"已完成"
if os.path.exists(path):
size = os.path.getsize(path)
if exp_bytes > 0 and size >= exp_bytes * 0.95:
LOG.info(f"[跳过] {rel}(已存在且达到预期大小)")
return f"[跳过] {rel}"
if exp_bytes == 0 and size > 0:
LOG.info(f"[跳过] {rel}(已存在,未设大小校验)")
return f"[跳过] {rel}"

desc = os.path.basename(rel)
for attempt in range(1, RETRIES + 1):
current = os.path.getsize(path) if os.path.exists(path) else 0
with tqdm(
total=exp_bytes if exp_bytes > 0 else None,
initial=current,
unit="B", unit_scale=True,
desc=f"{desc:44s}",
miniters=1, leave=True,
) as pbar:
try:
_stream_download(url, path, current, desc, pbar, requests)
final = os.path.getsize(path)
pct = f"{final/exp_bytes*100:.1f}%" if exp_bytes > 0 else "未校验"
LOG.info(f"[完成] {rel}: {final/1e9:.2f} GB ({pct})")
return f"[完成] {rel}: {final/1e9:.2f} GB"
except Exception as e: # noqa: BLE001
LOG.error(f"[重试 {attempt}/{RETRIES}] {rel}: {e}")
if attempt < RETRIES:
time.sleep(min(2 ** attempt, 30))

return f"[失败] {rel}: 超过最大重试次数"


def task_models(venv_ready):
LOG.info("=" * 60)
LOG.info("[下载] 模型下载开始")
LOG.info("=" * 60)
os.makedirs(MODELS_DIR, exist_ok=True)

# 等 venv,用 venv 的 pip 装下载器(装进工作区,重启保留)
if USE_VENV and not venv_ready.wait(timeout=600):
LOG.error("[错误] 等待 venv 超时")
return False

ensure_packages(venv_pip(), ["requests", "tqdm"], label="下载器: ")

results = []
with ThreadPoolExecutor(max_workers=WORKERS) as ex:
for fut in ex.map(download_one, JOBS):
results.append(fut)

# 核对
LOG.info("=" * 60)
LOG.info("最终文件核对:")
all_ok = True
for rel, exp_gb in JOBS:
exp_bytes = int(exp_gb * 1e9)
p = os.path.join(MODELS_DIR, rel)
if os.path.exists(p):
size = os.path.getsize(p)
ok = (size >= exp_bytes * 0.95) if exp_bytes > 0 else (size > 0)
if ok:
LOG.info(f" [OK] {rel}: {size/1e9:.2f} GB")
else:
all_ok = False
LOG.warning(f" [缺失] {rel}: {size/1e9:.2f} GB")
else:
all_ok = False
LOG.warning(f" [缺失] {rel}: 文件不存在")

if all_ok:
LOG.info("全部模型下载完成 ✅")
else:
LOG.warning("部分模型缺失,重跑本脚本可自动续传")
return all_ok


# ==========================================================================
# 启动服务 + ngrok 隧道
# ==========================================================================
def launch_comfyui(extra_args):
"""后台启动 ComfyUI"""
LOG.info("=" * 60)
LOG.info("[启动] ComfyUI")
LOG.info("=" * 60)
log_path = os.path.join(LOG_DIR, "comfyui.log")

if port_open():
LOG.info(f"[跳过] 端口 {PORT} 已在监听(服务可能已运行)")
LOG.info(f"可查看日志: {log_path}")
return None

vpy = venv_python()
cmd = [
vpy, "main.py",
"--listen", "0.0.0.0",
"--port", str(PORT),
"--enable-manager",
# ↓ 内存优化:这两个是主力的"用磁盘/时间换 RAM",
# 实测可把系统内存占用从 80%+ 压到 30% 左右
"--disable-pinned-memory",
"--fast-disk",
# ↓ 关掉生成中预览,省显存
"--preview-method", "none",
# ↓ 默认会把 400MB+800MB 显存预留出去;设 0 可白捡约 1.2GB
"--reserve-vram", "0",
] + list(extra_args)

LOG.info(f"$ {' '.join(cmd)}")


lf = open(log_path, "w", encoding="utf-8")
proc = subprocess.Popen(cmd, cwd=COMFY_DIR, stdout=lf, stderr=subprocess.STDOUT)
LOG.info(f"[信息] ComfyUI 已后台启动 (PID {proc.pid}),日志: {log_path}")

# 等待就绪
LOG.info(f"[等待] 等待 {PORT} 就绪(最多 300 秒)")
for i in range(300):
if port_open():
LOG.info(f"[OK] ComfyUI 就绪(耗时 {i} 秒)")
return proc
time.sleep(1)

LOG.error(f"[错误] ComfyUI 300 秒内未就绪,请查看日志: {log_path}")
return proc


def build_tunnel():
"""建立 ngrok 隧道(token 为空则跳过)"""
if not NGROK_TOKEN:
LOG.info("[跳过] 未配置 NGROK_TOKEN,不建隧道")
LOG.info(" 如需公网访问,可用平台自带代理,或在脚本顶部填 NGROK_TOKEN")
return None

LOG.info("=" * 60)
LOG.info("[隧道] 建立 ngrok 隧道")
LOG.info("=" * 60)

try:
from pyngrok import ngrok
except ImportError:
ensure_packages(venv_pip(), ["pyngrok"], label="pyngrok: ")
from pyngrok import ngrok

ngrok.set_auth_token(NGROK_TOKEN)

# addr 强制 IPv4,规避 ngrok 把 localhost 解析成 [::1] 导致 connection refused
# 不传 region,规避新版 http 隧道 "field region not found" 400 报错
addr = f"127.0.0.1:{PORT}"
tunnel = ngrok.connect(addr=addr, bind_tls=True)
url = tunnel.public_url

LOG.info("")
LOG.info("=" * 60)
LOG.info(f" 公网链接 : {url}")
LOG.info(f" 本地端口 : {addr}")
LOG.info("=" * 60)
LOG.info("")
return url


# ==========================================================================
# 入口
# ==========================================================================
def main():
parser = argparse.ArgumentParser(
description="ComfyUI + MiniMax-H3 + ngrok 一键部署")
parser.add_argument("--no-tunnel", action="store_true", help="不建 ngrok 隧道")
parser.add_argument("--no-launch", action="store_true",
help="不启动 ComfyUI(只安装+下载)")
parser.add_argument("--no-align", action="store_true",
help="跳过 torch/CUDA 版本对齐")
parser.add_argument("--novram", action="store_true",
help="追加 --novram(OOM 兜底;注意它会关掉 DynamicVRAM)")
args = parser.parse_args()

ws = resolve_paths()

LOG.info("=" * 60)
LOG.info(" ComfyUI + MiniMax-H3 一键部署")
LOG.info("=" * 60)
LOG.info(f" 工作区 : {ws}")
LOG.info(f" ComfyUI : {COMFY_DIR}")
LOG.info(f" 模型目录 : {MODELS_DIR}")
LOG.info(f" venv : {VENV_DIR if USE_VENV else '(未启用,使用系统 Python)'}")
LOG.info(f" torch源 : {TORCH_INDEX_URL}")
LOG.info(f" 端口 : {PORT}")
LOG.info("=" * 60)

# pip 缓存放工作区:万一要重装,缓存命中可省大量下载
os.environ["PIP_CACHE_DIR"] = os.path.join(ws, ".cache", "pip")
os.makedirs(os.environ["PIP_CACHE_DIR"], exist_ok=True)
LOG.info(f"[信息] pip 缓存目录: {os.environ['PIP_CACHE_DIR']}")

# ---- 阶段 A:三路并行 ----
LOG.info("")
LOG.info("[并行] 克隆ComfyUI ‖ 建venv+装pyngrok ‖ 下载模型")
venv_ready = threading.Event()
with ThreadPoolExecutor(max_workers=3) as ex:
futures = {
"ComfyUI 安装": ex.submit(task_clone_and_install, venv_ready),
"venv+pyngrok": ex.submit(task_venv_and_pyngrok, venv_ready),
"模型下载": ex.submit(task_models, venv_ready),
}
for name, f in futures.items():
try:
f.result()
except Exception as e: # noqa: BLE001
LOG.error(f"[错误] {name} 失败: {e}")

if not os.path.isfile(os.path.join(COMFY_DIR, "main.py")):
LOG.error("[中止] ComfyUI 异常,未找到 main.py")
return

# ---- 阶段 B:启动前必备 ----
pip_cmd = venv_pip()

# comfyui-manager(工作流节点缺失时靠它补)
ensure_packages(pip_cmd, ["comfyui-manager"], label="comfyui-manager: ")

# torch/CUDA 对齐(能正常用 CUDA 则自动跳过)
if not args.no_align:
align_torch(pip_cmd)

LOG.info("")
LOG.info("=" * 60)
LOG.info(" 安装完成 ✅")
LOG.info("=" * 60)

if args.no_launch:
vpy = venv_python()
LOG.info("[完成] --no-launch 模式,不启动服务")
LOG.info(f" 手动启动: cd {COMFY_DIR} && {vpy} main.py "
"--listen 0.0.0.0 --port " + str(PORT))
return

# ---- 阶段 C:启动 + 隧道 ----
extra = list(EXTRA_ARGS)
if args.novram:
extra.append("--novram")

launch_comfyui(extra)

if not args.no_tunnel:
build_tunnel()

LOG.info("[提示] 服务运行中,Ctrl+C 退出")
try:
while True:
time.sleep(60)
except KeyboardInterrupt:
LOG.info("[退出] 已停止")


if __name__ == "__main__":
main()


# python3 setup_all.py # 全自动
# python3 setup_all.py --no-tunnel # 不进行穿透
# python3 setup_all.py --no-launch # 只安装和下载,不启动服务
# python3 setup_all.py --no-align # 跳过 torch/CUDA 对齐
# python3 setup_all.py --novram # 被 OOM kill 时追加更激进的卸载模式

用法:

在魔搭实例中创建名为setup_all.py 的文件, 然后将上面的代码复制到该文件中, 并替换代码中的NGROK_TOKEN 值, 最后执行以下代码:

1
python3 setup_all.py              # 全自动

不想用 ngrok 就不填——脚本会自动跳过建隧道,你用平台自带代理访问即可。

经验之谈

  1. 经本人实测, 在使用该实例生成视频时,设置1056x608分辨率能得到较稳定的生成速度, 如果超过这个值, 显卡性能可能会报异常

API自动化工作流模式

假如你生成视频的工作流是一次性串联生成多个视频, 那么内存占用会随着视频的生成而逐步递增, 比如第一个生成第一视频张勇30%, 到了第三个时直接飙到80%, 因为内存中还保存前两个视频的数据, 如次一来, 最后程序崩溃终止是必然的结局

要解决这个问题, 很简单, 我们可以采用API循环调用单段工作流模板的方式来处理, 也就是单段视频生成工作流+API循环调用+ffmpeg外部拼接

这种模式的好处有:

  • 内存占用的大幅减少: 使得小内存机器也能跑长视频, 不会出现越跑越高的情况
  • 得到清爽简洁的工作流,: 有时候为了改一个节点参数, 满画布挨个找重复节点修改, 节点数量多的情况下,瞬间看得头皮发麻
  • 流程可控, 可实现视频生成的随意断点续生, 也就是任意中间片段向后接力: 长视频生成过程难免会因为某些节点bug或者设备性能问题而中断, 此时我们要从已经生成的视频开始继续生成而不是从头开始, 原生的做法就是拆换节点, 而如今只需要改个参数就行
  • 配置化: 常用的参数, 比如剧本提示词种子镜头参数分辨率模型切换等, 全部写在配置文件中, 实现面向配置文件创作, 不需要碰ComyfUI画布
  • 纯终端即可运行: 该模式下通过纯代码操作, 只需要一个终端即可, 意味着无需WebUI, 省去折腾内网穿透, 可以应对各种厂商的镜像服务

该模式项目目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
mini_h3_story/
├─ seg_template_api.json # ComfyUI单段工作流模板(API导出)
├─ story_config.yaml # 主配置文件(运行控制、全局参数、片段清单)
├─ generate.py # 主Python脚本
├─ prompts/ # 独立提示词目录,一个片段一个txt
│ ├─ seg01_prompt.txt
│ ├─ seg02_prompt.txt
│ └─ seg03_prompt.txt
├─ input/ # ComfyUI输入目录(首帧、接力尾帧)
│ └─ image1.png
└─ output/ # 视频seg*.mp4、尾帧last_xx.png、备份文件
│
└─ workflow/ # 存放工作流快照

执行流程图如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
┌─── ① 启动配置 ────────────────────┐
│ 读 story_config.yaml │
│ (运行范围/分辨率/seed/路径) │
│ 读 all_prompts.txt │
│ (按标记切割各段提示词) │
│ 加载工作流模板 JSON │
│ 项目input/ → 同步到ComfyUI input │
└────────────────────────────────────┘
↓
┌─── ② 首帧判断 ────────────────────┐
│ 从第1段开始 → 用 image1.png │
│ 从中间段开始 → 自动取项目output/ │
│ 上一段 last_{N-1}.png 作为首帧 │
│ (复制到ComfyUI input供读取) │
└────────────────────────────────────┘
↓
┌─── ③ 单段填充 ────────────────────┐
│ 取本段 seed + prompt + 首帧 │
│ 备份项目output/旧产物 │
│ 填充模板参数: │
│ LoadImage → 首帧图片名 │
│ H3节点 → prompt/分辨率/长度 │
│ KSampler → seed/步数/cfg │
│ LoraLoader → 模型/强度 │
│ 保存工作流快照到 workflow_debug/ │
└────────────────────────────────────┘
↓
┌─── ④ 提交渲染 ────────────────────┐
│ POST /prompt → ComfyUI API │
│ │
│ ComfyUI服务端渲染(独立进程): │
│ 模型加载 → KSampler采样 → │
│ VAEDecode图像 + VAEDecodeAudio │
│ 取最后一帧 → 保存尾帧图片 │
│ │
│ 输出到 ComfyUI/output/: │
│ segNN_00001.mp4 (带音频) │
│ lastNN_00001.png (尾帧) │
│ │
│ (脚本轮询/history等待完成) │
└────────────────────────────────────┘
↓
┌─── ⑤ 产物回收 + 接力 ─────────────┐
│ 从ComfyUI/output/找最新文件 │
│ segNN_*.mp4 → 项目output/ │
│ seg_N.mp4 │
│ lastNN_*.png → 项目output/ │
│ last_N.png │
│ │
│ 接力准备: │
│ last_N.png 复制到ComfyUI input/ │
│ (下一段LoadImage直接读取) │
│ │
│ 本段完成 ✓ │
└────────────────────────────────────┘
↓
◄── 回到 ③,生成下一段
(直到跑完结束段)
↓
┌─── ⑥ 收尾拼接 ────────────────────┐
│ ffmpeg 拼接所有 seg_*.mp4 │
│ → 项目output/full_movie.mp4 │
│ │
│ 最终产物全在 项目output/: │
│ seg_01~08.mp4 (各段视频) │
│ last_01~08.png (各段尾帧) │
│ full_movie.mp4 (完整成片) │
└────────────────────────────────────┘


══════════════════════════════════════
【中断恢复说明】
══════════════════════════════════════

Ctrl+C 杀的是脚本,不是ComfyUI
│
├─ ✗ 不再拷贝产物到项目output
├─ ✗ 不再开始下一段接力
└─ ✗ 不执行ffmpeg拼接
│
▼
ComfyUI当前段继续跑完
│
└─ ✓ 产物还在ComfyUI/output/
(segNN_*.mp4 + lastNN_*.png)
│
▼
恢复:
1. 等ComfyUI跑完当前段
2. 手动把ComfyUI/output里的
视频和尾帧拷到项目output/
重命名为seg_N.mp4/last_N.png
3. 配置改从下一段开始,重跑

唯一的问题, 就是有技术门槛, 不懂编程的话, 流程出了错解决起来比较麻烦

关于自动化工作流的项目模板, 我也一同放到网盘, 供大家参考使用:

ComfyUI-AI自动化工作流项目: 点击进入下载(访问密码: 312306)

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

使用ComfyUI+MinMax-H3音视频模型将自己写的小说拍成短剧

发表于 2025-09-03

前言

之前写了一篇小说, 严格来讲, 是借助AI写的一篇玄幻小说, 打算借AI来将其视觉化

如今AI视频的发展为玄幻和科幻领域增添了很强的助力, 现在这个时代, 不怕你想的离谱, 就怕你不敢想, 脑洞越大, 收益越高.

比如, 人类真的是猿类进化而来的吗? 有没有一种可能是硅基生命的退化体? 或者是恐龙? 恐龙其实没有灭绝, 人就是恐龙的基因复制体, 人类的祖籍在川渝之类的, 大胆延伸啊, 这不剧本就来了, 嘿嘿

AI是个好东西, 它让精神分裂患者也有了生存的空间, 可谓是人类的福音啊😂

好了, 回归正题.

本篇算是ComfyUI+MinMax-H3的进阶篇, 不同于基础篇的如何使用ComfyUI+MinMax-H3, 本文主要侧重于分镜和提示词的设计

关于基础篇的内容, 可以翻阅之前的文章:

  • 《使用ComfyUI+MinMax-H3音视频模型生成视频》
  • 《使用ComfyUI+MinMax-H3音视频模型生成一分钟的短剧》
  • 《使用ComfyUI+MinMax-H3音视频模型实现舞蹈模仿》

剧本

以下是我用AI写的套路修仙前两章内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
《炼化失败率九成九》
第一章 残魂附身
天色暗得像谁泼了一盆墨。
沈炼趴在乱葬岗的枯骨堆里,后背贴着腐烂的兽皮,牙关咬得咯咯响。
他浑身是血,分不清是自己的还是那头畜生的。
暗影狼就蹲在三丈开外,三只眼睛泛着幽绿的寒光,像是在欣赏猎物的挣扎。
这东西已经追了他三天三夜。
三天前,沈炼从乱葬岗附近醒来,发现自己身处一个陌生的世界。正当他还没搞清楚状况的时候,这头该死的暗影狼就出现了——像是闻到了他身上某种特殊的气息,穷追不舍。
他的腿断了。
肋骨也断了两根。
更要命的是,他发现自己浑身滚烫,像是有什么东西在体内灼烧。
"吼——"
暗影狼发出一声低沉的咆哮,后腿蓄力,作势欲扑。
沈炼闭上眼睛。
完了。
他想。
这次是真的完了。
"想活吗?"
一个声音忽然在他脑海里响起。
沈炼猛地睁开眼。
四周没有人。
"谁?"他哑着嗓子问。
"不重要。"那声音顿了顿,"重要的是,本座可以救你。但有个条件。"
"什么……条件?"
"契约。"那声音吐出两个字,"你与本座签订契约,本座便赐你一场造化。"
"什么样的造化?"
"一种体质。"那声音带着一丝不易察觉的得意,"能让你看清这世间万物的本质。"
沈炼愣住了。
看清本质?
作为一个从小靠捡破烂为生的孤儿,他太知道这四个字意味着什么了。
"那我现在……"他吞了吞口水,"我现在这个鬼样子,怎么跟你签契约?"
"很简单。"那声音说,"你只需要回答一个问题。"
"什么问题?"
"你想活吗?"
沈炼沉默了。
他看着自己血淋淋的手,看着三丈外那头蓄势待发的暗影狼,看着头顶那片浓得化不开的黑暗。
他想活吗?
答案从来只有一个。
"想。"
话音刚落,一道金光从他眉心炸开。
那光芒亮得刺眼,像是正午的太阳突然坠到了眼前。暗影狼发出一声凄厉的惨叫,被那道金光钉在原地,三只眼睛同时流下血泪。
它的身体开始龟裂,像是被一股无形的力量从内部撕扯,碎成无数块,散落一地。
沈炼呆住了。
"这只是最基础的能力展示。"那声音再次响起,"看清楚了吗?"
"看……看清了。"沈炼结结巴巴地说。
"那便好。"那声音顿了顿,"从现在起,你便是本座的宿主。"
沈炼张了张嘴。
月光从云层的缝隙里漏下来,照在那些森森白骨上,泛着幽幽的冷光。
这是一个陌生的地方。
或者说,是他从未见过的世界。
"那个……"他开口,"这里是哪儿?"
"诸天万界之一。"那声音说,"一个叫青云界的小世界。"
"我怎么回去?"
"你回不去了。"那声音平静地说,"签订契约的瞬间,你就已经是这个世界的'合法居民'。"
"等等——"沈炼的脑子飞速转动,"所以刚才那道金光、那条隧道……"
"传送。"那声音说,"从你原来的世界,送到这个世界。很疼吧?忍着点。"
"所以我现在……"
"你已经死了。"那声音说,"在你签订契约的那一刻,你在你原来的世界就已经死了。"
沈炼沉默了。
死了。
他在那个世界已经死了。
"那个……"他开口,"我是被拐卖到这个世界的吗?"
"……措辞能不能不要这么难听?"
"不能。"沈炼说,"我在那个世界还有十五年的家当。"
"那些破烂对本座没有价值。"
"对我有价值!"
那声音似乎叹了口气。
"行了,本座知道你是个穷鬼。"顿了顿,"这附近有一座宗门,名叫青云宗,你可以去那里落脚。以你现在的实力,当个外门弟子绰绰有余。"
"我实力很弱吗?"
"极弱。"
"那我怎么活?"
"你不是有观万界体质吗?"那声音反问,"此界灵气稀薄,修炼艰难,但正因为如此,灵药和矿石的价值都被低估了。以你的眼光,不难找到发财的机会。"
沈炼摸了摸下巴。
这么说来,好像也不是全无希望?
"那个青云宗在哪个方向?"
"往北走三十里,有一片青色的山,就是了。"
沈炼往北看了一眼。
黑黢黢的,什么也看不见。
但他知道,自己没有别的选择。
他转过身,最后看了一眼暗影狼的尸块。犹豫了一瞬,他从那堆碎肉里扒拉出三颗幽绿色的晶核,在衣摆上蹭了蹭,塞进腰间的小瓶子里。
"这是暗影狼的魂核。"那声音说,"虽然品质不高,但聊胜于无。"
"值多少钱?"
"……你们这些穷鬼,是不是脑子里就只有钱?"
"钱能换命。"沈炼说,"命比什么都重要。"
那声音沉默了一瞬。
"这话倒也没错。"
沈炼没有再说话。
他弯腰捡起地上一个破旧的布包——那是他从原世界带来的,里面有他这些年攒下的全部家当——三块干饼、半壶水、还有一把卷刃的匕首。
不多,但够他活一阵子了。
他迈开步子,往北走去。
夜风灌进领口,带着几分凉意。身后是乱葬岗的森森白骨,身前是无尽的黑暗与未知。
但沈炼没有回头。
他怕死。
但他也知道,怕死的人,往往活得更久。
"对了。"走出几步,他忽然想起什么,"你叫什么?"
那声音沉默了片刻。
"本座的名号太过久远,已经不值得提了。"
"那我叫你什么?老爷爷?前辈?大师?"
"……就叫本座'前辈'吧。"
"好的,老前辈。"
"……你这个人,是不是脑子有什么问题?"
"没有。"沈炼说,"我就是个老实人。"
他一边说,一边继续往前走。
月光从云层后探出头,照在他圆滚滚的脸上,照出几分与年龄不符的沉稳。
也照出他嘴角那一丝若有若无的笑意。
【第一章完】
第二章 外门弟子
青云宗的山门比沈炼想象中气派得多。
两根青色石柱拔地而起,足有三人合抱粗细,顶端雕刻着云纹,日光下泛着淡淡的灵光。石柱之间是一道无形的屏障,沈炼伸手摸了摸,指尖传来一阵酥麻。
"外门弟子有令牌便可通行。"那声音——老前辈——说道,"你现在没有令牌,但本座可以帮你开个后门。"
"什么后门?"
"你眉心的印记,本座稍加伪装,可以冒充青云宗的入门印记。"
"冒充?"沈炼眨了眨眼,"被发现了怎么办?"
"被发现就被打一顿呗。"老前辈的语气轻描淡写,"本座又不是没挨过打。"
沈炼嘴角抽了抽。
这位前辈是真的不正经。
片刻后,他迈步穿过屏障。
一阵眩晕过后,沈炼发现自己站在一条青石铺就的山道上。两旁是茂密的竹林,风吹过,竹叶沙沙作响。空气里有一股淡淡的草木清香,比外面的世界清新了不止一星半点。
"这就是修仙门派?"沈炼深吸一口气,"闻着挺舒服的。"
"这只是外门。"老前辈说,"内门的灵气浓度是这里的十倍。"
"十倍?"沈炼眼睛一亮,"那岂不是灵药满地跑?"
"想什么呢。"老前辈嗤笑一声,"正因为灵气稀薄,此界的灵药反而比别处更难生长。你在这里找一株十年份的灵芝,比别处找百年的都难。"
沈炼若有所思地点点头。
他继续往前走,不多时便看见前方有一群人聚在山道旁。
那群人大多是十五六岁的少年,穿着统一的灰色短打,腰间挂着一块刻有"外"字的木牌。领头的是一个二十来岁的青年,浓眉大眼,身材魁梧,正叉着腰对面前的几个少年训话。
沈炼本想绕道走,却被那青年一眼看见了。
"站住!"青年朝他招了招手,"你哪个队伍的?过来。"
沈炼愣了一下,指了指自己。
"就是你,别看别人。"
沈炼慢吞吞地走过去,脸上挂着恰到好处的憨笑。
"这位师兄,我……我是今天新来的。"
"新来的?"青年上下打量他一番,"入门印记呢?"
沈炼指了指眉心。
那印记在老前辈的伪装下,呈现出淡淡的青色,与其他外门弟子的入门印记别无二致。
青年扫了一眼,点点头:"外门杂役弟子,沈炼是吧?"
沈炼愣了一下。
"掌门昨日交代过,今日会有一位外来者入门。"青年说,"没想到是你这个瘦不拉几的小子。"
他说完,转身对身后那群少年招了招手。
"过来几个人,把他带去杂役院。"
两个少年应声出列,朝沈炼走来。
沈炼跟在两人身后,心里却在快速盘算。
掌门交代过?
看来这青云宗对他这个"外来者"还挺重视。
不过这也意味着,他身上一定有什么老前辈需要的东西。
"别想太多。"老前辈的声音在脑海中响起,"这青云宗只是看在你是'天外之人'的份上,对你有些好奇罢了。你现在的实力,在他们眼里连蝼蚁都算不上。"
"那我岂不是随时可能被弄死?"
"所以你要藏拙。"老前辈说,"你的本事,要在关键时刻才能亮出来。平时就扮演一个什么都不懂的傻子,让他们觉得你毫无威胁。"
"扮演傻子?"沈炼眨了眨眼,"这我会。"
老前辈没有说话。
但沈炼总觉得,他好像在笑。
杂役院在青云宗的最边缘,是一片低矮的茅屋群。
与山门处的青石、竹林不同,这里到处是泥泞和杂草,空气中弥漫着一股潮湿的霉味。几个外门弟子坐在茅屋门口,无所事事地晒着太阳,看见沈炼被带过来,也只是瞥了一眼,便继续各干各的。
"喏,这就是你以后住的地方了。"
带路的少年指了指最角落的一间茅屋。
"每天辰时起床,戌时休息。主要任务是砍柴、挑水、清理灵兽粪便。干得好月底有灵石拿,干不好……"
少年没有说完,但意思已经很明显了。
"我……我明白了。"沈炼点点头,脸上写满了惶恐。
"行了,去吧。"少年摆摆手,"有什么不懂的问我,我叫周大壮。"
"谢……谢谢周师兄。"
沈炼弓着腰,背着布包,一步一步挪向那间茅屋。
他的背影看起来瘦弱、无助,像是一只被丢进狼群的小绵羊。
但没有人注意到,他转身的瞬间,嘴角微微上扬。
"演得不错。"老前辈说。
"那是。"沈炼在心里回应,"我从小就会装。"
他推开茅屋的门,里面只有一张破旧的木板床和一个缺了角的木盆。墙角堆着一捆干柴,显然是前任杂役留下的。
沈炼把布包放在床上,盘腿坐到床边。
"现在说正事。"他开口,"这个世界的修炼体系是什么样的?"
"此界以'灵气'为根基。"老前辈说,"吸纳灵气,淬炼肉身,凝聚灵力,最终炼气化神、炼神返虚、炼虚合道。你现在连'炼气'都算不上,只是个普通人。"
"那我岂不是得从零开始?"
"有这个体质在,你的修炼速度会比常人快十倍不止。"老前辈说,"但前提是你得有灵气来源。"
"灵气来源?"
"灵石、灵药、天材地宝……都可以。"老前辈顿了顿,"你现在没钱没势,只能靠自己去'发现'。"
沈炼若有所思地点点头。
他闭上眼睛,按照老前辈的指点,尝试感应这个世界的"法则"。
一开始什么都没发生。
但渐渐地,他感觉到了什么。
那是一种很微妙的感觉——像是眼睛突然能看见以前看不见的东西。空气不再是透明的,而是布满了密密麻麻的光点,有青色、有白色、有金色,像是一锅煮沸的粥。
"这就是灵气?"沈炼问。
"对,灵气粒子。"老前辈说,"此界的灵气浓度稀薄,但聊胜于无。你现在能看见它们,说明观万界体质已经开始运作了。"
沈炼继续观察。
他发现那些灵气粒子并不是均匀分布的,而是有疏有密。靠近竹林的地方明显更密集,靠近茅屋的地方则稀疏得可怜。
等等。
沈炼的目光落在墙角那捆干柴上。
干柴本身没什么特别,但他能"看见"柴堆下面的土壤里,有一团微微发光的能量波动。
那波动很微弱,如果不是观万界体质,绝对发现不了。
"那是什么?"沈炼问。
"本座看看……"老前辈沉默了片刻,"是一株半成熟的灵药,距离成熟还有三个月左右。应该是'青玉藤',对炼气期的修士有些用处。"
沈炼眼睛一亮。
"值多少钱?"
"品质一般,但在这种灵气稀薄的地方,一株青玉藤大概能卖十块下品灵石。"
"十块下品灵石……"沈炼在心里盘算,"够我修炼多久?"
"一个月左右。"
"那三个月后,我岂不是能攒三十块?"
"理论上是这样。"老前辈说,"但你别忘了,你现在的主要任务是干杂活。砍柴挑水清理粪便,你哪有时间修炼?"
沈炼沉默了一瞬。
然后他站起身,走到茅屋门口,朝外看了看。
几个外门弟子正懒洋洋地晒太阳,没有人注意他这边。
他回到柴堆旁,蹲下身,开始往外搬柴。
搬着搬着,他的手"不小心"碰到了一根朽烂的木桩。木桩应声而断,砸在地上,扬起一片灰尘。
"咳咳咳——"沈炼剧烈地咳嗽起来,眼泪都出来了。
"师兄!师兄!"他扯着嗓子喊,"这里有根烂木头倒了!差点砸到我!"
外面有人懒洋洋地应了一声:"自己处理。"
"可是……可是我不会处理啊!"沈炼的声音里带着哭腔,"我……我从小在农村长大,没见过这种东西!"
"……"
外面安静了一瞬。
然后一个年轻的声音响起:"行了行了,我过来帮你。"
一个瘦高个的少年走进来,看了一眼地上的烂木头,皱了皱眉。
"这有什么好大惊小怪的……"
他弯腰去捡那根木头。
就在这时,沈炼的目光已经扫过了整个房间。
观万界体质让他看清了很多东西:
——木盆底部有一道裂纹,裂纹里残留着微量的灵气残留,说明这木盆以前装过灵液;
——床板下面刻着一个隐藏的符文,那是储物符的变体,说明前任杂役在这里藏了东西;
——还有柴堆旁边的土壤里,除了那株青玉藤之外,还有几颗灵气粒子更密集的点……
"哎,这柴堆下面怎么有股怪味?"瘦高个少年捏着鼻子,"你这地方也太潮了。"
"是啊是啊。"沈炼连忙点头,"我打算一会儿把柴都搬出去晒晒,顺便清理一下这里。"
"随便你,别耽误干活就行。"瘦高个少年把烂木头丢到门外,转身出去了。
沈炼看着他离开的背影,脸上浮现出一丝若有若无的笑意。
"发现了什么?"老前辈问。
"三样东西。"沈炼在心里说,"第一,木盆可以修复,里面残留的灵液能提炼出一些灵气粒子,虽然不多,但够我修炼用了。"
"第二,床板下面藏着东西,看符文结构,应该是前任杂役留下的一些灵石碎屑,大概有十几块。"
"第三,柴堆旁边除了青玉藤之外,还有别的……"
他蹲下身,拨开柴堆旁边的杂草。
在土壤下面,有一块拇指大小的青色石头,正泛着微微的灵光。
"这是……"
"下品灵石的边角料。"老前辈说,"品质不高,但确实是灵石。"
沈炼把那块石头攥在手里,掂了掂。
十几颗灵石碎屑,一块下品灵石边角料,一株三个月后成熟的青玉藤,一个能提炼灵气的旧木盆……
对于普通的外门杂役弟子来说,这些东西可能不值一提。
但对于沈炼来说,这已经足够他开始第一步的计划了。
"你打算怎么做?"老前辈问。
"先把能拿的都拿到手。"沈炼说,"然后……"
他顿了顿,眼珠转了转。
"然后找个机会,让某些人'发现'这些宝贝。"
"哦?"老前辈来了兴趣,"说说看。"
"这个青云宗灵气稀薄,灵药稀少,所以任何'意外发现'的东西都会引起重视。"沈炼说,"如果我'不小心'发现了一株灵药,宗门会怎么处置?"
"论功行赏。"老前辈说,"发现者可以得到三成奖励,其余归宗门。"
"三成……"沈炼若有所思,"那如果我'不小心'发现的是一整片灵药呢?"
老前辈沉默了一瞬。
"你小子,脑子转得挺快。"
沈炼笑了笑,没有说话。
他站起身,把那块灵石边角料塞进腰间的小瓶子里,然后拍了拍身上的土。
"先把柴搬出去吧。"他说,"免得被人怀疑。"
说完,他弯下腰,开始一趟一趟地往外搬柴。
阳光从茅屋门口照进来,落在他圆滚滚的脸上。
他的表情还是那副憨厚、老实、人畜无害的样子。
但他的眼睛,却在无人注意的角落,闪烁着算计的光芒。
【第二章完】

分镜设计

把上面的内容发给大模型AI, 比如豆包或者元宝之类的, 让它基于MinMax-H5模型按照15秒一段设计分镜语言, 一般来说, 每段的镜头数量最多不要超过三个, 不然节奏过渡会很不自然.

因为视频模型的时间感知是通常是每5秒一个语义单元, 秉着宁可少,不可多的原则, 确保画面平稳过渡, 同时让人物的情绪有足够长的酝酿空间

比如被分手后的痛哭画面, 从错愕到不舍到心酸再到嚎啕大哭, 层层递进的情绪爆发, 如果只给她几秒钟的时间发挥, 给观众的感觉就跟开玩笑没啥区别

分镜数量适用参考表格:

分镜数 适用场景 说明
1个 长对话、情绪特写、缓慢空镜 一个镜头盯住人物,对话慢慢说
2个 对话+动作切换、场景过渡 前半段对话,后半段动作/转场
3个 标准叙事(起承转合) 最稳,但不代表必须塞满
4个以上 ❌ 不建议 每个镜头太短,画面跳变、口型对不上

分镜主要包含四大点: 景别+表情+动作+对话

想要人物根据我们的需求实现表演的真实感和鲜活感, 就必须把抽象的感觉转成具体的表情和动作, 比如你不能说: “沈炼很疲惫, 他有些不开心”, 模型看到这句话也有点懵, 怎么个疲惫法? 怎么个不开心法?, 我们需要形象化, 比如:

1
沈炼眼皮半垂,眼神涣散,肩膀塌陷,背部微驼,脚步拖沓地走在青石路上,鞋底蹭着地面发出沙沙声。他嘴角下压,眉头微蹙,低头看着地面,双手无力垂下,手指微微蜷缩。走到一棵枯树下,他停下脚步,长叹一口气,胸口起伏明显,随即踢开脚边一颗石子,石子滚落路边。

要用具体的动作和表情去传达情绪, 而不是对着镜头说一句我很难过就完事了, 观众怎么可能共鸣

虽然模型现在越来越聪明, 哪怕你只写个抽象的词, 他也能生成非常细腻的表情动作, 但它生成的也许并不是你想要的那种感觉

当然了, 如果你真不确定用什么表情和动作来描述当下的人物状态会更贴切, 那交给模型来自由发挥也许会得到意想不到的效果

我们使用AI, 不是在教AI怎么做事, 而是相互学习, 所以放心大胆去尝试

下面是我让元宝生成的第一章节分镜表格, 总共六段:

第一段:乱葬岗苏醒

镜头 时间 景别 内容 对话
Shot1 0-5s 全景 阴云密布,冷灰色调,微弱天光从云缝漏下。乱葬岗,枯树乌鸦,泥土湿润。沈炼黑发凌乱沾泥,灰褐粗布麻衣破损,从土堆中挣扎爬出,双手撑地,指节发白,泥土从衣摆簌簌落下,眼皮半垂,眼神涣散 无
Shot2 5-10s 特写 沈炼眉心金色竖痕突然亮起,微光闪烁,他猛地捂住额头,眉头紧蹙,嘴唇微张,呼吸急促,声音沙哑 “我……没死?”
Shot3 10-15s 中景 沈炼跪地,双手撑地,大口喘气,胸口剧烈起伏,缓缓抬头环顾坟丘和枯树,瞳孔猛缩,身体微微后仰,手指抠进泥土里

情绪线:茫然 → 震惊 → 恐惧

第二段:残魂对话

镜头 时间 景别 内容 对话
Shot1 0-15s 中景 阴云渐散,雾气流动,一缕冷光从云隙斜射而下。沈炼坐在地上,后背靠半截墓碑,衣袍沾泥。一团半透明淡青色残魂悬浮面前,边缘微微波动。沈炼瞪大眼睛,身体后仰,双手撑地,喉结滚动。残魂缓缓飘近,沈炼愣住,嘴唇微张,缓缓低头看向摊开的双手,手指微微颤抖 沈炼:”你是谁?是你救了我?” 残魂:”不,是你自己活过来的。” 沈炼:”我自己……活过来的?”

情绪线:震惊 → 疑惑 → 不安

说明:这段对话信息量大,切镜头会打断情绪,用一个固定中景让对话自然流动,靠表情变化推进。

第三段:确认穿越

镜头 时间 景别 内容 对话
Shot1 0-8s 近景 雾气渐薄,晨光从侧面穿透林间,在沈炼脸上打出明暗分界。沈炼坐在地上,低头看着身上灰褐粗布麻衣,双手扯了扯衣襟,抬手摸脸,手指从额头滑到下巴,指尖微微停顿,嘴角扯出一丝苦涩弧度,声音低哑 “穿越……我竟然穿越了。” “这身体……不是我的。”
Shot2 8-15s 中景 沈炼双手撑地站起来,膝盖微微打颤,握紧拳头,指节发白,深吸一口气,缓缓抬头,眼神从涣散逐渐聚焦,目光沉稳,望向远方 “既然来了,那就活下去。”

情绪线:苦涩 → 坚定

第四段:远眺青云宗

镜头 时间 景别 内容 对话
Shot1 0-8s 全景 清晨雾气未散,晨光从沈炼背后洒下,在地面拉出长影。沈炼踩过湿润泥土,脚步带起泥点,走出坟丘地带,站在山坡上,晨风拂动衣摆和散落发丝。他眯起眼睛,身体微微前倾,望向远处云雾缭绕的山峰,山顶隐约矗立巍峨宗门,殿宇隐于云雾之间 “那就是青云宗?”
Shot2 8-15s 近景 沈炼嘴角微微上扬,眼神沉稳,踩过碎石路面,脚步带起尘土,迈步向宗门方向走去,衣摆擦过齐腰野草,草叶沙沙作响

情绪线:期待 → 决意

第五段: 山门登记

镜头 时间 景别 内容 对话
Shot1 0-8s 中景 晨光明亮,从门楼上方斜照而下,在青石台阶上拉出整齐光影。青云宗山门,青石台阶,飞檐翘角门楼,门匾”青云宗”三字,两个灰衣弟子站门侧,目光淡漠。沈炼衣袍整洁,双手递上入门凭证,微微躬身,目光低垂,语气恭敬。中年执事坐桌后,低头翻看名册,指尖划过纸页,头也不抬 沈炼:”弟子沈炼,前来拜入宗门。”
Shot2 8-15s 近景 执事随手在名册上划了一下,笔尖划过纸面发出沙沙声,冷淡开口。沈炼双手微微一顿,指尖轻轻蜷缩,但很快恢复平静,垂下眼帘,声音平稳

情绪线:恭敬 → 失落 → 隐忍

第六段: 杂役房独处

镜头 时间 景别 内容 对话
Shot1 0-8s 全景 黄昏暮色,昏黄光线从破旧木窗斜射而入,在地面拉出长长光影,灰尘在光柱中浮动。破旧杂役房,木床、旧桌、墙角蛛网。沈炼推开吱呀作响的木门,门轴发出刺耳摩擦声,迈步走进,踩过凹凸不平的泥土地面,环顾四周,目光扫过破旧床铺和堆满杂物的角落,肩膀微微塌下 无
Shot2 8-15s 特写 沈炼坐在床边,木床发出轻微吱呀声,低头看着手中杂役令牌,拇指摩挲令牌表面粗糙木纹,嘴角扯出一丝自嘲弧度,声音低沉。他抬起头,眼神从涣散逐渐聚焦,目光沉稳,握紧令牌,指节发白 “杂役……也行,总比在坟地里强。” “先活下去,再想办法。”

情绪线:落寞 → 自嘲 → 坚定


画面真实感四要素总结:

要素 核心要求 示例
动作具体化 拒绝抽象词,写具体动作 不是“疲惫”,是“眼皮半垂,脚步拖沓”
人境交互 人物与环境产生物理互动 踩碎石带起尘土,拨开齐腰野草
光线具体化 写明光源方向、强度、颜色 晨光从背后洒下,拉出长影
声音空间感 声音有远近、有层次、有来源 近处脚步声,远处钟声,风声掠过草尖

提示词

设计好分镜头后, 我们将其包装成模型可识别的规范提示词

MinMax-H3官方标准的三段式提示词结构如下:

  1. integrated_multimodal_description:画面、画幅、风格、分镜镜头、人物动作、台词、运镜,全部放这里
  2. overall_soundscape:场景内原生音效(风声、脚步声、环境音、人物说话),画面里面世界真实存在的声音
  3. non_diegetic_music:背景音乐,观众专属、场景本身不存在的配乐

注意: 对话内容需要使用<d>标签来包裹, 切头切换使用At 00:05.000(分:秒.毫秒)的格式, 这是MinMax-H3官方标准的写法,

如果你使用的是三方平台, 比如即梦等, 可以使用非标准时间格式, 比如0-5s这种, 兼容性反而会更好, 但是我们使用的是ComfyUI, 所以最好跟着官方来走.

提示词示例:

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 全景,电影写实古风,高清胶片质感,清晨微光,雾气朦胧,晨光从沈炼背后洒下,在地面拉出长影。清晨荒野山坡,远处云雾山峰,山顶隐约矗立巍峨青云宗门,殿宇隐于云雾之间。沈炼,黑发凌乱,灰褐色粗布麻衣,身上泥土痕迹、轻微伤痕,走出荒野坟丘地带,站在山坡上,晨风拂动衣摆。
[Shot 2] At 00:05.000, 中景,沈炼眯起眼睛,身体微微前倾,望向远处的宗门,眉心金色竖痕微微发光,他轻声道 <d>那就是青云宗?</d>
[Shot 3] At 00:10.000, 近景,沈炼嘴角微微上扬,眼神沉稳,踩过碎石路面,迈步向宗门方向走去,脚步带起尘土,低声说 <d>不管怎样,先找个安身之处。</d>
overall_soundscape: 山坡晨风,远处隐约钟声,沈炼脚步声、衣料摩擦声、人物低语说话声。
non_diegetic_music: 古筝独奏,中等速度,旋律开阔悠远,带一丝希望感,音量适中,不压制人声对白。

首先, 整部短剧的色彩和人物风格要保持一致, 比如电影写实古风,清胶片质感等等, 同一个场景的环境氛围要保持一致, 比如清晨微光,雾气朦胧等, 这些必须在每段提示词中说明, 否则光靠模型自由发挥大概率会出问题.

三大统一原则:

  • 全局统一
  • 场景统一
  • 段与段之间统一

详细表格如下:

层级 范围 涉及对象 实现方式
全局统一 整部剧(第一章到第N章) 角色 + 画风 角色定妆照、和画风需要每段重复锚定
场景统一 多段视频构成的同一个场景 场景环境 + 人物状态 每段提示词写清场景+人物状态
段间统一 相邻两段之间 画面连续性 首帧接力(上一段末帧作为下一段起始帧)

理论上首尾帧接力能保持画风和人物能七成以上的统一, 前提是最后一帧的画面和初始风格不会有太大偏移, 否则, 就会随着段数的增加越偏越大

所以针对角色, 我们需要通过定妆照+状态提示词的方式来锁定形象. 而针对画风和非特殊场景, 通过提示词来锁定即可

为什么要定妆照+状态词, 光提示词可不可以, 我们先来看看角色形象锁定的两种方案对比:

方案 做法 优点 缺点
方案A:纯文字 每段提示词里完整写清外貌+状态 不依赖参考图,流程简单 每段都要写一大段,容易啰嗦;模型理解有偏差时容易漂移
方案B:定妆照+状态 每段喂同一张定妆照,提示词只写状态变化 外貌锁定最稳,文字量少 需要额外准备定妆照;REF 模式下状态描述不能省

方案A提示词示例:

1
2
沈炼,男,约二十岁,黑发束冠,发丝略显凌乱,青白粗布长袍,袖口有磨损,
眉心有一道淡金色竖痕,眼神清冷但带一丝疲惫,身形偏瘦。当前状态:浑身沾满泥土,衣衫破损,黑发凌乱,眼神茫然。

方案B提示词示例:

1
沈炼浑身沾满泥土,衣衫破损,黑发凌乱

哪种方案好, 一目了然.

总之, 一段提示词中, 画风+场景+人物形象状态必不可少, 同时配合上定妆照参考图以及首尾帧的衔接, 这样基本上就能保证画面的自然流动

1
2
3
4
5
6
7
8
准备定妆照(角色标准形象)
↓
每段生成时:
├── 输入:定妆照(REF)
├── 提示词:画风 + 场景 + 人物形象状态 + 景别 + 表情 + 动作 + 对话
└── 输出:该段视频
↓
段间接力:上一段末帧作为下一段起始帧

剩下的就是具体的镜头语言了, 直接把之前AI生成的分镜组装进去就可以了, 当然这些体力活完全可以交给AI来做, 目前我们是在讨论原理

至于最后的音效和背景配乐部分, 如果你没有灵感, 最好交给AI来处理, 因为在你没有明确想法的情况下自己定音效和音乐, 大概率效果不如AI好

下面是我让元宝生成的第一章节视频提示词, 总共六段:

第一段:乱葬岗苏醒

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 全景,电影写实古风,阴云密布,雾气低沉,冷灰色调,微弱天光从云缝中漏下。乱葬岗,枯树乌鸦,泥土湿润,坟丘错落。沈炼,黑发凌乱沾着泥土,灰褐色粗布麻衣破损,从土堆中挣扎爬出,双手撑地,指节发白,泥土从衣摆簌簌落下,动作迟滞,眼皮半垂,眼神涣散。
[Shot 2] At 00:05.000, 特写,沈炼眉心金色竖痕突然亮起,微光闪烁,他猛地捂住额头,眉头紧蹙,嘴唇微张,呼吸急促,声音沙哑 <d>我……没死?</d>
[Shot 3] At 00:10.000, 中景,沈炼跪在地上,双手撑地,大口喘气,胸口剧烈起伏,缓缓抬头环顾四周,看到坟丘和枯树,瞳孔猛缩,身体微微后仰,手指抠进泥土里 <d>这是哪儿?我怎么在坟地里?</d>
overall_soundscape: 近处沈炼急促的喘息声,泥土从衣摆滑落的簌簌声,手指抠入泥土的摩擦声,远处乌鸦沙哑的叫声,风声掠过枯树枝桠。
non_diegetic_music: 低沉弦乐,缓慢压抑,带一丝不安感,音量适中。

第二段:残魂对话

1
2
3
4
integrated_multimodal_description:
[Shot 1] 中景,电影写实古风,阴云渐散,雾气流动,一缕冷光从云隙斜射而下,照亮沈炼和残魂之间的地面。乱葬岗边缘,沈炼坐在地上,后背靠着一块半截墓碑,衣袍沾满泥土。一团半透明淡青色残魂悬浮在他面前,身形模糊,边缘微微波动。沈炼瞪大眼睛,身体后仰,双手撑地,喉结滚动,声音发紧 <d>你是谁?是你救了我?</d> 残魂缓缓飘近,声音空灵 <d>不,是你自己活过来的。</d> 沈炼愣住,嘴唇微张,缓缓低头,看向自己摊开的双手,手指微微颤抖 <d>我自己……活过来的?</d>
overall_soundscape: 近处沈炼急促的呼吸声,衣料摩擦墓碑的粗糙声,残魂飘动时若有若无的低鸣,远处风声,乌鸦叫声渐远。
non_diegetic_music: 空灵笛声,缓慢悠远,带一丝神秘感,音量偏低,不压制对话。

第三段:确认穿越

1
2
3
4
5
integrated_multimodal_description:
[Shot 1] 近景,电影写实古风,雾气渐薄,晨光从侧面穿透林间,在沈炼脸上打出明暗分界。沈炼坐在地上,低头看着自己身上的灰褐色粗布麻衣,双手扯了扯衣襟,又抬手摸了摸自己的脸,手指从额头滑到下巴,指尖微微停顿,嘴角扯出一丝苦涩的弧度,声音低哑 <d>穿越……我竟然穿越了。</d> 他低头看着自己的手掌,缓缓握紧又松开 <d>这身体……不是我的。</d>
[Shot 2] At 00:08.000, 中景,沈炼双手撑地站起来,膝盖微微打颤,他握紧拳头,指节发白,深吸一口气,缓缓抬头,眼神从涣散逐渐聚焦,目光沉稳,望向远方 <d>既然来了,那就活下去。</d>
overall_soundscape: 近处沈炼衣料摩擦声,手指滑过脸颊的轻微声,他站起来时膝盖发出的细微咔哒声,深呼吸的气流声,远处晨风吹过草尖的窸窣声。
non_diegetic_music: 古筝独奏,由低沉渐转明亮,旋律从压抑到开阔,音量适中。

第四段:远眺青云宗

1
2
3
4
5
integrated_multimodal_description:
[Shot 1] 全景,电影写实古风,清晨雾气未散,晨光从沈炼背后洒下,在地面拉出长影。乱葬岗边缘,沈炼踩过湿润的泥土,脚步带起泥点,走出坟丘地带,站在山坡上,晨风拂动他的衣摆和散落的发丝。他眯起眼睛,身体微微前倾,望向远处云雾缭绕的山峰,山顶隐约矗立巍峨宗门,殿宇隐于云雾之间 <d>那就是青云宗?</d>
[Shot 2] At 00:08.000, 近景,沈炼嘴角微微上扬,眼神沉稳,他踩过碎石路面,脚步带起尘土,迈步向宗门方向走去,衣摆擦过齐腰的野草,草叶沙沙作响 <d>不管怎样,先找个安身之处。</d>
overall_soundscape: 近处沈炼踩过碎石和泥土的脚步声,衣摆擦过草尖的沙沙声,远处山谷隐约传来钟声,晨风掠过山坡。
non_diegetic_music: 古筝独奏,中等速度,旋律开阔悠远,带一丝希望感,音量适中,不压制人声对白。

第五段: 山门登记

1
2
3
4
5
integrated_multimodal_description:
[Shot 1] 中景,电影写实古风,晨光明亮,从门楼上方斜照而下,在青石台阶上拉出整齐的光影。青云宗山门,青石台阶,飞檐翘角的古风门楼,门匾上“青云宗”三个大字,两个灰衣弟子站在门侧,目光淡漠。沈炼衣袍整洁,站在登记桌前,双手递上入门凭证,微微躬身,目光低垂,语气恭敬 <d>弟子沈炼,前来拜入宗门。</d> 中年执事坐在桌后,低头翻看名册,指尖划过纸页,头也不抬。
[Shot 2] At 00:08.000, 近景,执事随手在名册上划了一下,笔尖划过纸面发出沙沙声,冷淡道 <d>杂役房,去吧。</d> 沈炼双手微微一顿,指尖轻轻蜷缩,但很快恢复平静,他垂下眼帘,声音平稳 <d>是。</d>
overall_soundscape: 近处笔尖划过纸面的沙沙声,名册翻动的哗啦声,沈炼衣料摩擦声,远处弟子低语声,山风掠过门楼。
non_diegetic_music: 低沉弦乐,带一丝压抑感,音量偏低,不压制对话。

第六段: 杂役房独处

1
2
3
4
5
integrated_multimodal_description:
[Shot 1] 全景,电影写实古风,黄昏暮色,昏黄光线从破旧的木窗斜射而入,在屋内地面拉出长长的光影,灰尘在光柱中浮动。破旧杂役房,木床、旧桌、墙角蛛网。沈炼推开吱呀作响的木门,门轴发出刺耳的摩擦声,他迈步走进,踩过凹凸不平的泥土地面,环顾四周,目光扫过破旧的床铺和堆满杂物的角落,肩膀微微塌下。
[Shot 2] At 00:08.000, 特写,沈炼坐在床边,木床发出轻微的吱呀声,他低头看着手中的杂役令牌,拇指摩挲着令牌表面粗糙的木纹,嘴角扯出一丝自嘲的弧度,声音低沉 <d>杂役……也行,总比在坟地里强。</d> 他抬起头,眼神从涣散逐渐聚焦,目光沉稳,握紧令牌,指节发白 <d>先活下去,再想办法。</d>
overall_soundscape: 近处木门吱呀声,沈炼踩过泥土地面的脚步声,木床轻微的吱呀声,拇指摩挲木纹的摩擦声,窗外远处隐约传来弟子们的交谈声,黄昏风声。
non_diegetic_music: 古筝独奏,旋律低沉缓慢,带一丝落寞,尾段渐转沉稳,音量偏低,不压制对话。

提示词模板

我们在实际的操作中, 分镜和提示词并不是独立开的, 它们两者是一体的, 提示词中就已经包括了镜头语言在里面, 所以我们在和AI对话时, 直接让它按照我们的要求生成提示词就行

如果你不知道怎么说, 可以将以下提示词模板发给豆包或者元宝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 角色
你是一位专业的 AI 短剧分镜导演,擅长将小说剧本拆解为适用于 ComfyUI + MiniMax-H3 的视频生成提示词。

# 任务
请根据用户提供的剧本,将其转化为多段 H3 提示词,每段 15 秒,每段 1-3 个镜头。

# 输入(用户会提供以下四项)
1. 剧本正文(小说章节或段落)
2. 定妆照人物档案(角色名 + 外貌定妆照 REF,格式:角色名(Image N),N 为节点插槽编号,按 ComfyUI reference_images 接线顺序 1-based)
3. 场景参考图(可选):同一场景出现 ≥2 次时提供,格式:场景名(Image N 定义建筑风格与配色);仅在该场景首次出现时完整描述,后续同场景段用场景名指代
4. 画风定义(全局统一的画风描述,如:日系赛璐璐,干净线条,高饱和柔和色,角色轮廓光)

# REF 锚定说明(重要)
- Image N 的编号 = ComfyUI 节点 reference_images 插槽的接线顺序(1-based),与本地文件名无关
- 文件名仅作本地管理用,不参与模型识别
- 常见节点写法对照:
官方 MinimaxHailuo03ReferenceNode → 角色名(Image 1)
RunComfy Turbo 工作流 → 角色名(<Picture 1>)
MiniMaxH3-Easy → 角色名(@1)
H3 Tagged Reference → 角色名({tag})
- 定妆照锁角色外貌,场景参考图锁视觉地标(建筑风格、色调、氛围),两者互补
- 一张定妆照 / 场景图只需在 [Shot 1](或该场景首次出现时)锚定一次,后续用"角色名 / 场景名"指代

# 视频资产清单(需要锚定的资产只有两类)
| 资产类型 | 靠什么锚定 | 是否需要参考图 | 举例 |
|---|---|---|---|
| 角色 | 定妆照 REF | 需要 | 沈炼、残魂 |
| 场景地标 | 场景概念图 REF | 需要(同一场景出现 ≥2 次时) | 青云宗山门、乱葬岗 |
| 画风 | 文字契约句 | 不需要 | 国漫2D写实古风 |
| 关键道具 | 文字描述 | 不需要(除非反复特写) | 杂役令牌、入门凭证 |

# 处理流程(必须严格按顺序执行)

## Step 1:拆解分镜表
将剧本按 15 秒一段切分,每段设计 1-3 个镜头,输出分镜表:
| 段号 | 镜头 | 时间 | 景别 | 场景 | 人物形象状态 | 表情 | 动作 | 对话 | 情绪 |

**注意**:分镜表是"人审环节",用于在生成提示词前先确认节奏。用户确认分镜表后,才进入 Step 2。

## Step 2:生成 H3 提示词
基于分镜表,逐段生成 H3 提示词。每段必须包含以下七要素,且顺序固定:

[Shot N] → 景别 → 画风 → 场景(含光线方向、强度、颜色)→ 人物形象状态 → 表情 + 动作(具体化)→ 对话(<d>台词</d>)

At 00:XX.000(第二镜头起,标注精确切点时间)
overall_soundscape: 声音有远近、层次、来源(近处 XX,远处 XX)
non_diegetic_music: 配乐描述

## Step 3:逐镜头自检(生成后必须逐条核对)
- [ ] 每个镜头都包含七要素(画风、场景、人物形象状态、景别、表情、动作、对话)
- [ ] 人物形象状态只在 [Shot 1] 完整描述一次,后续镜头用"角色名"指代
- [ ] 场景参考图仅在该场景首次出现时完整描述一次,后续同场景段用场景名指代
- [ ] 表情和动作全部为具体动作,无抽象词(如"疲惫""震惊"需翻译为具体动作)
- [ ] 每个镜头至少有一处人物与环境的物理互动
- [ ] 每段都写明光线方向、强度、颜色
- [ ] overall_soundscape 包含至少 3 个声音,且区分远近层次
- [ ] 对话使用 <d>台词</d>,标签成对闭合
- [ ] 时间格式统一为 At 00:XX.000([Shot 1] 不写时间)
- [ ] 每段 [Shot 1] 包含"首帧状态 + 承接动作 + 核心剧情"三段式结构

# 硬性规则(不可违反)
1. 画风全局统一:每段开头必须重复完整的画风描述
2. 定妆照锚定:人物形象状态用"(Image N)"绑定定妆照,仅在 [Shot 1] 完整描述一次;场景参考图用"(Image N 定义建筑风格与配色)"绑定,仅在该场景首次出现时完整描述
3. 场景风格统一:同一场景的连续段落,场景描述保持一致;场景切换时用文字锚定写明新场景 + 新状态
4. AB段首尾帧衔接:每段最后一镜的画面,需为下一段首镜提供衔接。**已有 AB 段衔接时,禁止在下一段提示词中描述上一段的具体画面**,只写本段内容
5. 承接动作(过渡桥):每段 [Shot 1] 开头必须写"首帧状态 + 承接动作 + 核心剧情"三段式结构。首帧状态用一句话对齐 AB 段首帧画面,承接动作用 2-3 个具体动作描述从该状态推进到本段核心剧情的过渡路径,禁止直接跳转,禁止使用"然后""接着"等抽象连接词
6. 每段 15 秒,镜头数 1-3 个,总时长不超过 15 秒
7. 拒绝抽象情绪词:所有情绪必须翻译为具体动作(参考对照表见附录 A)

# 输出格式(严格按此结构输出)

---
## 第 X 段:[段名]

**分镜表:**
| 段号 | 镜头 | 时间 | 景别 | 场景 | 人物形象状态 | 表情 | 动作 | 对话 | 情绪 |

**H3 提示词:**
(完整的 H3 提示词文本)

**自检结果:**
- [ ] 七要素完整
- [ ] 人物锚定正确(Image N 编号与节点插槽一致)
- [ ] 场景锚定正确(如有场景参考图,编号一致)
- [ ] 无抽象词
- [ ] 人境交互存在
- [ ] 光线明确
- [ ] 声音有空间感
- [ ] 标签闭合
- [ ] 时间格式正确
- [ ] 承接动作存在(首帧状态 → 承接动作 → 核心剧情)
---

# 附录 A:抽象词 → 具体动作对照表(供参考,可自行扩展)
| 抽象词 | 具体动作 |
| 疲惫 | 眼皮半垂,脚步拖沓,肩膀下垂,呼吸沉重 |
| 震惊 | 瞳孔猛缩,身体后仰,嘴唇微张,愣住不动 |
| 恐惧 | 呼吸急促,身体发抖,瞳孔放大,后退半步 |
| 坚定 | 眼神锐利,下巴微抬,握紧拳头,步伐沉稳 |
| 失落 | 肩膀下垂,眼神暗淡,低头沉默,嘴角微抿 |
| 恭敬 | 微微躬身,双手递物,目光低垂,语气谦卑 |

# 附录 B:画风定义参考(供用户选择)
H3 能识别的画风大类:Live-action / 2D-animated / 3D CG / Watercolor / Claymation / Vintage film / Cel-shaded anime

**修仙题材推荐**:国漫2D写实古风
契约句:2D-animated, guofeng xianxia style, clean elegant linework, soft ink-wash atmosphere, oriental color palette (indigo, jade, pale gold), character contour rim-light, cinematic composition, no western fantasy elements

# 附录 C:关于分镜表、定妆照、场景参考图、AB段衔接的说明
- 分镜表是"人审环节",用于在生成提示词前先确认节奏,避免返工
- 定妆照(REF 图)锁定角色外貌,文字锚定锁定当前状态,两者互补,仅在 [Shot 1] 锚定一次
- 场景参考图锁定视觉地标(建筑风格、色调、氛围),不锁动态状态(光线、天气、时间)——光线与氛围交给文字控制
- AB段首尾帧衔接保证画面运动连贯,文字锚定控制状态变化,两者配合缺一不可
- 已有 AB 段衔接时,下一段提示词不描述上一段画面,只写"从首帧状态推进到本段核心"的过渡桥
- 承接动作是"从首帧状态推进到本段核心剧情"的过渡桥,AB段锁画面,承接动作写过渡,两者不冲突
- 道具只在反复特写的核心剧情物品才考虑喂图,一般靠文字描述即可

# 附录 D:场景参考图使用原则
- 同一场景出现 ≥2 次 → 喂一张中性光(无明显光影)的概念图,提示词写"场景名(Image N 定义建筑风格与配色)"
- 同一场景只出现 1 次 → 不用喂图,直接文字描述
- 场景图只锁静态元素(建筑、地形、色调),光线/天气/时间交给文字控制
- 场景图切换光线示例:白天/黄昏用同一张中性图 + 文字写光线,或按段准备多张光线图

# 用户输入区
(在此粘贴剧本正文)

AI收到你的模板后会让你确定剧本、画风、人物资产内容, 你可以按照下面这种格式回复它:

1
2
3
4
5
6
7
8
9
10
1. 剧本正文
这里填你的剧本xxx内容
2. 定妆照人物档案
沈炼(Image 1)
残魂(Image 2)
3. 场景参考图
青云宗山门(Image 3)
乱葬岗(Image 4)
4. 画风定义
国漫2D写实古风

模板中先生成分镜再生成提示词的目的一方面是为了有问题能及时纠错, 另一方面是兼容不同的AI能力. 有些笨一点的AI, 如果直接一步到位让它生成提示词很容易掌握不好剧情节奏

如果你用的是AI智能体, 那么可以将该模板封装进技能, 或者直接给角色本身赋能, 这样就不用每次发重复的内容了, 能懒一点是一点😂

视频资产

我们需要准备的资产主要分为三大块

  • 角色图: 也就是人物定妆照, 确保全局人物长相统一
  • 场景图: 出现两次以上的场景需要使用场景参考图锁定, 比如青云宗建筑参考图, 不然每个视频不一样就很奇怪
  • 道具图: 一般来说, 道具使用提示词就足够了, 但有一些核心道具, 比如主人公的玉佩, 假设该道具贯穿主线, 频繁出现在镜头前, 那就要使用参考图来锁定它

模型和节点

这次我们使用了:

  • 主模型: minimax_h3_fl2va_pruned_int8_convrot.safetensors图生视频模型以及minimax_h3_ref2va_pruned_int8_convrot.safetensors, 图生视频两个主模型
  • 文本条件编码模型: qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
  • Vae音视频解码模型: minimax_h3_video_vae_fp16.safetensors (~4.9G)和minimax_h3_audio_vae_fp32.safetensors
  • Lora加速模型: minimax_h3_fl2v_turbo_8step_v1.0_comfyui_bf16

这算是MinMax-H3视频生成必备六件套模型了, 总共接近70G左右

至于节点, 和之前文章《使用ComfyUI+MinMax-H3音视频模型生成一分钟的短剧中使用的是同一套, 唯一不同的是, 文生视频模型使用的是Image to Video节点, 而图生视频则需要换成Reference Video节点, 外加一个参考图上传, 如下:

文生视频工作流概览:

image-20260907074003412

图生视频工作流概览:

image-20260907075106643

首帧模式和参考模式

图生视频工作流涉及参考图/参考音视频的上传, 而文生视频工作流涉及到首帧图的上传, 当然这个节点不是必须的, 你不上传的话就依靠模型自由发挥

关于这两种参考模式的区别, 我们需要深入了解, 方便我们后续的创作

首帧图和参考图/参考视频的区别:

首帧图 参考图
是不是视频第 0 帧 ✅ 是 ❌ 不是
作用 决定视频从哪里开始 决定视频里的东西长什么样
剧情延续 从这一帧直接延续 不延续,只作为锚点参考
类比 接力赛的第一棒 参赛选手的身份证照片

首帧模式和参考模式的对比:

FL2VA(首帧模式) RE2VA/REF(参考模式)
核心机制 上一段尾帧 = 下一段首帧 定妆图 / 参考视频 = 全程特征锚点
衔接自然度 ⭐⭐⭐⭐⭐ 像素级连续,最自然 ⭐⭐⭐ 软衔接,需要提示词缓冲
人物 / 场景锁定 ⭐⭐⭐ 一般,多段后可能漂移 ⭐⭐⭐⭐⭐ 好,定妆图全程约束
需要准备什么 第一段首帧图(或占位图) 角色定妆图、场景参考图
提示词要求 中等(描述动作方向就行) 高(必须精确描述每段开头状态)
适合场景 动作连续、镜头衔接要求高 人物多、场景多、要求角色稳定
使用的模型和节点 fl2va模型+Image To Video节点 ref2va 模型 + Reference Video

两者各有优势,想要实现技能衔接像 FL 一样自然又能保证人物锁定像 REF 一样稳, 使用常规模式很难实现 ,但是我们可以采用曲线救国, 比如:

  • 全程使用首帧模式, 确保过渡自然, 场景和人物的锁定通过定妆照中继接力的方式避免漂移

    比如视频生成两段之后感觉人物要漂了, 那赶紧截取尾帧图片配合定妆照/场景图以图生图的模式生成符合我们要求的新尾帧图, 在以此作为下一段视频的首帧, 相当于是把人物和场景拉了回来, 这个过程虽然麻烦, 但是能实现我们的需求, 适合尾帧存在明确的人物和场景画面的情况

  • 假如尾帧没有明确的人物和场景, 以黑屏或者其他的方式结束, 那就需要使用参考模式来锁定下一段的角色和场景

  • 如果两段衔接和人物场景锁定都没问题, 偏偏里面一个特殊道具的模样偏差过大, 比如这个道具是你自创的, 模型从来没见过

    这个时候你需要自己手工制图并传给模型, 可是此时你用的是首帧模式, Image to Video节点传不了参考图怎么办, 那这个时候别忘了该节点还有一个last_frame参数入口, 用来锁定尾帧, 我们可以好好利用上它:

    image-20260907080456007

    不过这种方式对镜头编排要求比较高, 需要好好琢磨一下画面的设计

所以真正我们在实际创作的时候往往是两种模式一块使用, 首帧模式为主, 参考模式为辅, 此为混合模式

两种模式使用场景详细参考表:

上一段结尾情况 人物状态 用什么模式 怎么做
尾帧有明确人物 + 场景 脸还稳,没漂 首帧模式 直接尾帧接力,啥也不用干
尾帧有明确人物 + 场景 感觉要漂 / 已经糊了 首帧模式 + 图生图修复 尾帧做底图 + 定妆照做参考,图生图重绘生成新首帧,再接力
尾帧是黑屏 / 白屏 / 空镜(无明确人物) 无所谓 参考模式 用定妆照 + 场景图做参考,生成下一段,黑屏天然掩盖跳变
尾帧是远景 / 背影(人物特征不明确) 可能要漂 参考模式或图生图高重绘 人物特征提取不到,直接用参考模式锁定下一段
场景大切换(从坟墓跳到山门) 无所谓 图生图生成新首帧 定妆照 + 场景描述,图生图生成新场景首帧,再接力

核心原则:能首帧接力就首帧接力,要漂了就图生图拉一把,没人物了就切参考模式。

但不管哪种模式, 提示词才是最关键的, 如果你给的首帧图是站着,而提示词是写的是坐着,神仙来了都难救

当提示词和图像输入打架时,模型的表现通常是这三种:

冲突情况 模型的挣扎 结果
首帧是站着,提示词写 “坐着” 第 0 帧还是站着,第 1 帧强行往下坐 开头突兀,像按了快进
参考图是人物 A,提示词写 “金发碧眼的女人” 想保持人物 A 的脸,又想变成金发 五官崩坏、脸四不像
首帧是白天室内,提示词写 “夜晚星空下” 第 0 帧白天,第 1 帧突然天黑 闪屏、光影跳变

关于种子

在没有参考图锁定的情况下, 当我们使用模型生成的角色和场景不符合预期的时候, 我们会考虑换采样种子(seed), 从而得到不一样的人物形象

因此, 我们也不难明白, 如果想要让每段视频的人物与场景尽可能地保持一致, 那么每段视频的采样种子一定不要换

其实这个道理很简单, 我们只是把一个长视频拆分成了无数个15秒视频而已, 它们本身都归属于同一个视频, 所以种子只能是同一个

假如你因为中途某段视频生成的不满意而换了种子, 而且后面的视频也没调回来, 那么视频人物换脸的概率大大增加

种子它就类似于随机数的起点, 同一个种子下模型则每次都会从同一个随机形状开始生成

所以种子很重要, 很多人会忽视它的重要性

你可以将其看成是角色弱锁定手段, 强锁定的方式那就是参考图

总结

因此角色/场景锁定的方式有以下几点:

  • 参考图/参考视频
  • 首尾帧的指定
  • 提示词锁定: 人物发型、五官、肤色、服饰、年龄等描述全套保持统一,不要不同镜头写不一样的外貌描述
  • 种子、步数等采样参数的一致性
  • 自己训练角色/场景/画风Lora模型: 专门学习某一个人的五官, 某种美术风格以及特定场景建筑等
  • 使用IP‑Adapter 人物 IP 适配器: 和 Ref2VA 参考图逻辑类似,专门提取人物身份特征

每种方式权重不一样, 简单归类总结表如下:

手段 锁定人脸身份 锁定场景画风 备注
参考图 Ref2VA ✅强 ✅ 短剧首选,每段传入
角色私训 LoRA ✅强 ✅ 搭配参考图效果最优
首帧继承接力 ✅连续镜头 ✅ 大转场禁用
参考视频 ⚠️偏弱 ✅强 擅长锁动作运镜,锁脸一般
固定 Seed ✅辅助 ✅辅助 统一画面气质
提示词约束 ⚠️软性约束 ⚠️软性约束 所有方案都要配合
IP‑Adapter ✅强 ✅ 额外第三方节点依赖

工作流

由于这次的工作流内容比较多, 就不直接贴上来了, 有兴趣的小伙伴可以前往网盘下载:

工作流合集: 点击进入下载(访问密码: 312306)

效果展示

视频生成中, 稍等几天😂

总结

这不, 有了此等妙技, 想看什么剧情自己生成, 岂不快哉!😜

以上就是本篇全部内容, 有兴趣的小伙伴可以去尝试一下玩玩, 说不定能创作出好的作品, 然后上传到红果平台顺带赚取外快.

1
2
3
4
5
6
7
8
9
10
11
收到。请提供以下四项输入内容:
1. 剧本正文
这里填你的剧本内容
2. 定妆照人物档案
沈炼(Image 1)
残魂(Image 2)
3. 场景参考图
青云宗山门(Image 3)
乱葬岗(Image 4)
4. 画风定义
国漫2D写实古风

附加

常见抽象词 → 具体动作 对照表:

抽象词 具体动作/表情
疲惫 眼皮半垂,脚步拖沓,肩膀下垂,呼吸沉重
震惊 瞳孔猛缩,身体后仰,嘴唇微张,愣住不动
恐惧 呼吸急促,身体发抖,瞳孔放大,后退半步
愤怒 眉头紧锁,咬紧牙关,握紧拳头,青筋暴起
悲伤 眼眶泛红,嘴唇颤抖,低头沉默,肩膀微微抽动
坚定 眼神锐利,下巴微抬,握紧拳头,步伐沉稳
疑惑 眯起眼睛,歪头,眉头微皱,嘴唇抿紧
恭敬 微微躬身,双手递物,目光低垂,语气谦卑
紧张 手指攥紧衣角,喉结滚动,呼吸变浅,眼神游移
释然 长舒一口气,肩膀放松,嘴角微微上扬,眼神柔和

相信大家在抖音上经常能看到有动物或者小孩子模仿网红舞蹈,

今天我们用ComfyUI+MinMax-H3来实现这种效果, 家里有宝宝的可以用来给宝宝生成跳舞视频😬

说到宝宝, 又是一段伤心往事😭

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

使用ComfyUI+MinMax-H3音视频模型实现舞蹈模仿

发表于 2025-09-02

前言

相信大家在抖音上经常能看到有动物或者小孩子模仿网红舞蹈,

今天我们用ComfyUI+MinMax-H3来实现这种效果, 家里有宝宝的可以用来给宝宝生成跳舞视频😬

说到宝宝, 又是一段伤心往事😭

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

使用ComfyUI+MinMax-H3音视频模型生成一分钟的短片

发表于 2025-09-01

前言

紧接着上一篇文章《使用ComfyUI+MinMax-H3音视频模型生成视频》

我们会发现, 目前MinMax-H3模型一次性能生成最长15秒的视频, 假设要用它来生成几分钟的短剧该如何实现呢?

思考

此时我们可以使用串联生成多个视频的方式, 最后统一合并, 但是这里存在一个问题, 那就是画面的连贯性, 强制合并的话画面硬切会影响观感, 所以我们需要锁定起始帧, 也就是前一段的最后一个帧画面作为后一段视频的参考画面, 这样整体看起来更加自然流畅

那么我们来实战一下

工作流

草图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─── ① 加载区 ──────────────────────┐
│ UNETLoader:MiniMax H3 UNET │
│ CLIPLoader │
│ VAELoader(video) │
│ VAELoader(audio) │
└────────────────────────────────────┘
↓ MODEL / CLIP / VAE
┌─── ② 模型增强 ────────────────────┐
│ MiniMaxH3SigmaShift │
│ LoraLoaderModelOnly:Turbo 1.0 │
└────────────────────────────────────┘
↓ MODEL
┌─── ③ 五段生成 + 首帧接力 ──────────┐
│ LoadImage 首帧参考图 ×5 │
│ MiniMaxH3ImageToVideo ×5 │
│ KSampler → VAEDecode / 音频解码 │
│ GetLastFrame/ImageScale 接力 │
│ 段A末帧→段B首帧 │
│ 段B末帧→段D首帧 │
│ 段D末帧→段E首帧 │
│ 段E末帧→段F首帧 │
└────────────────────────────────────┘
↓ 帧序列 + 音频
┌─── ④ 合并成片 ────────────────────┐
│ ImageBatch:5段帧合并 │
│ AudioConcat:5段音频拼接 │
│ VHS_VideoCombine → 成片 mp4 │
└────────────────────────────────────┘

由于多段的视频生成耗时较长, 为了加快推理速度, 我这里使用了Lora加速模型:

image-20260902104106898

同时给ComfyUI启动参数开启了--use-sage-attention, 目的也是为了加速生成

image-20260902123707019

具体工作流json代码如下, 代码有点长, 文末还有更多详细内容和效果展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
{
"1": {
"inputs": {
"unet_name": "minimax_h3_fl2va_pruned_int8_convrot.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"2": {
"inputs": {
"clip_name": "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors",
"type": "minimax",
"device": "default"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"3": {
"inputs": {
"vae_name": "minimax_h3_video_vae_fp16.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"4": {
"inputs": {
"vae_name": "minimax_h3_audio_vae_fp32.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"7": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 0-5s 全景,破旧网吧,蓝紫霓虹灯管闪烁,红发杀马特少年坐在角落,面前CRT显示器亮着,屏幕反光映在他脸上。\n[Shot 2] 5-10s 中景,少年猛敲键盘,屏幕显示QQ空间非主流留言板,他嘴角上扬,叼着棒棒糖。\n[Shot 3] 10-15s 近景,少年抬头,眼神穿过烟雾看向门口,镜头缓慢推近,画面定格在他略带期待的表情。\noverall_soundscape: 网吧键盘声,低频电子乐,门铃响起。\nnon_diegetic_music: 无,环境声为主。",
"width": 1344,
"height": 768,
"length": 362,
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"first_frame": [
"197:194",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"8": {
"inputs": {
"seed": 228165722416984,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"7",
0
],
"negative": [
"7",
0
],
"latent_image": [
"7",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"9": {
"inputs": {
"samples": [
"8",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"10": {
"inputs": {
"samples": [
"8",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"11": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/dancing",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"9",
0
],
"audio": [
"10",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"13": {
"inputs": {
"shift_video": 12,
"shift_audio": 3,
"model": [
"1",
0
]
},
"class_type": "MiniMaxH3SigmaShift",
"_meta": {
"title": "MiniMax H3 Sigma Shift"
}
},
"14": {
"inputs": {
"lora_name": "14b_oil_motion_preview1.0.safetensors",
"strength_model": 1,
"model": [
"13",
0
]
},
"class_type": "LoraLoaderModelOnly",
"_meta": {
"title": "LoRA加载器(仅模型)"
}
},
"138": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"9",
0
],
"audio": [
"10",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"139": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 0-5s 中景,少年站在大头贴机器前,蓝紫光从机器里透出,他摆出剪刀手姿势。\n[Shot 2] 5-10s 近景,大头贴画面显示少年和一位扎双马尾、穿校服的女孩合影,两人头靠头,比V字手势,背景是爱心贴纸。\n[Shot 3] 10-15s 特写,少年看着打印出来的大头贴,嘴角微微抽动,眼神从兴奋变为复杂,画面渐暗。\noverall_soundscape: 大头贴机器打印声,轻快电子音,女孩笑声。\nnon_diegetic_music: 无,环境声为主。",
"width": 1344,
"height": 768,
"length": 362,
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"first_frame": [
"137:17",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"140": {
"inputs": {
"seed": 95646412430984,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"139",
0
],
"negative": [
"139",
0
],
"latent_image": [
"139",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"141": {
"inputs": {
"samples": [
"140",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"142": {
"inputs": {
"samples": [
"140",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"143": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/dancing",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"141",
0
],
"audio": [
"142",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"144": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"141",
0
],
"audio": [
"142",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"154": {
"inputs": {
"images.image0": [
"9",
0
],
"images.image1": [
"141",
0
],
"images.image2": [
"207",
0
],
"images.image3": [
"213",
0
],
"images.image4": [
"219",
0
]
},
"class_type": "BatchImagesNode",
"_meta": {
"title": "批量图像"
}
},
"156": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/dancing",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"154",
0
],
"audio": [
"225",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"157": {
"inputs": {
"method": "add",
"audio_1": [
"10",
0
],
"audio_2": [
"142",
0
]
},
"class_type": "AudioCombine",
"_meta": {
"title": "🎵 音频合成"
}
},
"198": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"200",
0
],
"audio": [
"199",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"199": {
"inputs": {
"samples": [
"203",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"200": {
"inputs": {
"samples": [
"203",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"201": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/dancing",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"200",
0
],
"audio": [
"199",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"202": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 半身特写,17岁中国少年,人类男性,非动物。红色莫西干发型,发顶染金,厚刘海遮住左眉。身穿黑色铆钉皮夹克,内搭灰色骷髅图案T恤,下穿破洞紧身牛仔裤,脚踩白色帆布鞋。左耳戴银色十字架耳钉。肤色偏白,眼下有轻微黑眼圈,眼神叛逆。嘴里含着一根白色棒棒糖棍,没有叼的动作。背景是破旧网吧,蓝紫霓虹灯管,CRT显示器亮着,烟雾缭绕。\noverall_soundscape: 网吧键盘敲击声,低频电子乐,偶尔传来泡面撕包装声。\nnon_diegetic_music: 无,环境声为主。",
"width": 1344,
"height": 768,
"length": 5,
"clip": [
"2",
0
],
"vae": [
"3",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"203": {
"inputs": {
"seed": 473414489693868,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"202",
0
],
"negative": [
"202",
0
],
"latent_image": [
"202",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"205": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 0-5s 中景,少年站在破旧网吧门口,手里捏着一张皱巴巴的钞票,眼神迷茫,背景是灰暗的街道,路灯闪烁。\n[Shot 2] 5-10s 特写,少年低头看手机屏幕,屏幕显示余额不足,他咬了咬嘴唇,眉头紧锁。\n[Shot 3] 10-15s 近景,少年抬起头强挤出一个笑容,但眼神藏着失落,画面渐暗,霓虹灯在背景闪烁。\noverall_soundscape: 街道车流声,手机提示音,远处广告广播声。\nnon_diegetic_music: 低沉钢琴曲,略带忧伤。",
"width": 1344,
"height": 768,
"length": 362,
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"first_frame": [
"228:17",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"206": {
"inputs": {
"seed": 114006740154726,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"205",
0
],
"negative": [
"205",
0
],
"latent_image": [
"205",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"207": {
"inputs": {
"samples": [
"206",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"208": {
"inputs": {
"samples": [
"206",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"209": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/seg3",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"207",
0
],
"audio": [
"208",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"210": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"207",
0
],
"audio": [
"208",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"211": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 0-5s 全景,少年站在天台上,风吹起他的红发,他手里攥着一张纸条,眼神坚定。\n[Shot 2] 5-10s 中景,少年对着天空大喊,声音被风吹散,他脸上带着释然的笑容。\n[Shot 3] 10-15s 近景,少年低下头轻声说半句话,话未说完,画面定格在他复杂的表情上。\noverall_soundscape: 风声,远处城市噪音,少年喊声的回音。\nnon_diegetic_music: 无,环境声为主。",
"width": 1344,
"height": 768,
"length": 362,
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"first_frame": [
"229:17",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"212": {
"inputs": {
"seed": 706898825792777,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"211",
0
],
"negative": [
"211",
0
],
"latent_image": [
"211",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"213": {
"inputs": {
"samples": [
"212",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"214": {
"inputs": {
"samples": [
"212",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"215": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/seg4",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"213",
0
],
"audio": [
"214",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"216": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"213",
0
],
"audio": [
"214",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"217": {
"inputs": {
"prompt": "integrated_multimodal_description:\n[Shot 1] 0-5s 空镜,城市夜景,霓虹灯闪烁,镜头缓慢拉远,少年站在天台边缘的背影逐渐变小。\n[Shot 2] 5-10s 空镜,网吧招牌熄灭,街道空无一人,只剩一盏路灯亮着。\n[Shot 3] 10-15s 空镜,一张大头贴照片飘落在地,上面是少年和女孩的笑脸,画面渐黑。\noverall_soundscape: 环境音渐弱,最后只剩风声。\nnon_diegetic_music: 轻柔钢琴曲,渐弱至无声。",
"width": 1344,
"height": 768,
"length": 362,
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"first_frame": [
"230:17",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"218": {
"inputs": {
"seed": 839399017049768,
"steps": 8,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"14",
0
],
"positive": [
"217",
0
],
"negative": [
"217",
0
],
"latent_image": [
"217",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"219": {
"inputs": {
"samples": [
"218",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"220": {
"inputs": {
"samples": [
"218",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"221": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/seg5",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"219",
0
],
"audio": [
"220",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
},
"222": {
"inputs": {
"fps": 24,
"bit_depth": 8,
"images": [
"219",
0
],
"audio": [
"220",
0
]
},
"class_type": "CreateVideo",
"_meta": {
"title": "创建视频"
}
},
"223": {
"inputs": {
"method": "add",
"audio_1": [
"157",
0
],
"audio_2": [
"208",
0
]
},
"class_type": "AudioCombine",
"_meta": {
"title": "🎵 音频合成"
}
},
"224": {
"inputs": {
"method": "add",
"audio_1": [
"223",
0
],
"audio_2": [
"214",
0
]
},
"class_type": "AudioCombine",
"_meta": {
"title": "🎵 音频合成"
}
},
"225": {
"inputs": {
"method": "add",
"audio_1": [
"224",
0
],
"audio_2": [
"220",
0
]
},
"class_type": "AudioCombine",
"_meta": {
"title": "🎵 音频合成"
}
},
"197:192": {
"inputs": {
"video": [
"198",
0
]
},
"class_type": "GetVideoComponents",
"_meta": {
"title": "获取视频元素"
}
},
"197:193": {
"inputs": {
"image": [
"197:192",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"197:194": {
"inputs": {
"batch_index": [
"197:195",
1
],
"length": 1,
"image": [
"197:192",
0
]
},
"class_type": "ImageFromBatch",
"_meta": {
"title": "从批次获取图像"
}
},
"197:195": {
"inputs": {
"expression": "min(max(int(a if a >= 0 else b + a), 0), b - 1)",
"values.a": [
"197:196",
0
],
"values.b": [
"197:193",
2
]
},
"class_type": "ComfyMathExpression",
"_meta": {
"title": "数学表达式"
}
},
"197:196": {
"inputs": {
"value": 1
},
"class_type": "PrimitiveInt",
"_meta": {
"title": "整数"
}
},
"137:15": {
"inputs": {
"video": [
"138",
0
]
},
"class_type": "GetVideoComponents",
"_meta": {
"title": "获取视频元素"
}
},
"137:16": {
"inputs": {
"image": [
"137:15",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"137:17": {
"inputs": {
"batch_index": [
"137:99",
1
],
"length": 1,
"image": [
"137:15",
0
]
},
"class_type": "ImageFromBatch",
"_meta": {
"title": "从批次获取图像"
}
},
"137:99": {
"inputs": {
"expression": "min(max(int(a if a >= 0 else b + a), 0), b - 1)",
"values.a": [
"137:100",
0
],
"values.b": [
"137:16",
2
]
},
"class_type": "ComfyMathExpression",
"_meta": {
"title": "数学表达式"
}
},
"137:100": {
"inputs": {
"value": 362
},
"class_type": "PrimitiveInt",
"_meta": {
"title": "整数"
}
},
"228:15": {
"inputs": {
"video": [
"144",
0
]
},
"class_type": "GetVideoComponents",
"_meta": {
"title": "获取视频元素"
}
},
"228:16": {
"inputs": {
"image": [
"228:15",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"228:17": {
"inputs": {
"batch_index": [
"228:99",
1
],
"length": 1,
"image": [
"228:15",
0
]
},
"class_type": "ImageFromBatch",
"_meta": {
"title": "从批次获取图像"
}
},
"228:99": {
"inputs": {
"expression": "min(max(int(a if a >= 0 else b + a), 0), b - 1)",
"values.a": [
"228:100",
0
],
"values.b": [
"228:16",
2
]
},
"class_type": "ComfyMathExpression",
"_meta": {
"title": "数学表达式"
}
},
"228:100": {
"inputs": {
"value": 362
},
"class_type": "PrimitiveInt",
"_meta": {
"title": "整数"
}
},
"229:15": {
"inputs": {
"video": [
"210",
0
]
},
"class_type": "GetVideoComponents",
"_meta": {
"title": "获取视频元素"
}
},
"229:16": {
"inputs": {
"image": [
"229:15",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"229:17": {
"inputs": {
"batch_index": [
"229:99",
1
],
"length": 1,
"image": [
"229:15",
0
]
},
"class_type": "ImageFromBatch",
"_meta": {
"title": "从批次获取图像"
}
},
"229:99": {
"inputs": {
"expression": "min(max(int(a if a >= 0 else b + a), 0), b - 1)",
"values.a": [
"229:100",
0
],
"values.b": [
"229:16",
2
]
},
"class_type": "ComfyMathExpression",
"_meta": {
"title": "数学表达式"
}
},
"229:100": {
"inputs": {
"value": 362
},
"class_type": "PrimitiveInt",
"_meta": {
"title": "整数"
}
},
"230:15": {
"inputs": {
"video": [
"216",
0
]
},
"class_type": "GetVideoComponents",
"_meta": {
"title": "获取视频元素"
}
},
"230:16": {
"inputs": {
"image": [
"230:15",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"230:17": {
"inputs": {
"batch_index": [
"230:99",
1
],
"length": 1,
"image": [
"230:15",
0
]
},
"class_type": "ImageFromBatch",
"_meta": {
"title": "从批次获取图像"
}
},
"230:99": {
"inputs": {
"expression": "min(max(int(a if a >= 0 else b + a), 0), b - 1)",
"values.a": [
"230:100",
0
],
"values.b": [
"230:16",
2
]
},
"class_type": "ComfyMathExpression",
"_meta": {
"title": "数学表达式"
}
},
"230:100": {
"inputs": {
"value": 362
},
"class_type": "PrimitiveInt",
"_meta": {
"title": "整数"
}
}
}

复制上面代码粘贴到ComfyUI中即可使用, 或者前往网盘下载工作流文件合集: 点击进入下载(访问密码: 312306)


工作流概览图:

image-20260902132020656

三大基础模型+Lora加速模型:

image-20260902132758856

使用minmax-h3生成5帧图片,并截取其中一帧当做视频起始参考图, 当然你也可以使用SD文生图, 这里为了确保统一性, 直接复用h3模型:

image-20260902132154712

接下来每一段视频都是一样的流程, 15秒, 只有提示词不同:

image-20260902132240917

最后对五段的音频和视频分别进行拼接合成:

image-20260902132616679

提示词

为了方便测试, 我直接让AI给我生成视频提示词

背景是非主流时代, 1 分钟短片,分5 段续帧,角色锁定红发杀马特少年/铆钉皮衣/蓝紫霓虹调。

首帧参考图(角色设定图)生成提示词

用于 MiniMaxH3ImageToVideo 的 first_frame 输入,或先用文生图节点出 PNG 再 LoadImage。

1
2
3
4
integrated_multimodal_description:
[Shot 1] 半身特写,17岁中国少年,人类男性,非动物。红色莫西干发型,发顶染金,厚刘海遮住左眉。身穿黑色铆钉皮夹克,内搭灰色骷髅图案T恤,下穿破洞紧身牛仔裤,脚踩白色帆布鞋。左耳戴银色十字架耳钉。肤色偏白,眼下有轻微黑眼圈,眼神叛逆。嘴里含着一根白色棒棒糖棍,没有叼的动作。背景是破旧网吧,蓝紫霓虹灯管,CRT显示器亮着,烟雾缭绕。
overall_soundscape: 网吧键盘敲击声,低频电子乐,偶尔传来泡面撕包装声。
non_diegetic_music: 无,环境声为主。

第一段 网吧开场(0-15s)

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 0-5s 全景,破旧网吧,蓝紫霓虹灯管闪烁,红发杀马特少年坐在角落,面前CRT显示器亮着,屏幕反光映在他脸上。
[Shot 2] 5-10s 中景,少年猛敲键盘,屏幕显示QQ空间非主流留言板,他嘴角上扬,叼着棒棒糖。
[Shot 3] 10-15s 近景,少年抬头,眼神穿过烟雾看向门口,镜头缓慢推近,画面定格在他略带期待的表情。
overall_soundscape: 网吧键盘声,低频电子乐,门铃响起。
non_diegetic_music: 无,环境声为主。

第二段 大头贴初见(15-30s)

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 0-5s 中景,少年站在大头贴机器前,蓝紫光从机器里透出,他摆出剪刀手姿势。
[Shot 2] 5-10s 近景,大头贴画面显示少年和一位扎双马尾、穿校服的女孩合影,两人头靠头,比V字手势,背景是爱心贴纸。
[Shot 3] 10-15s 特写,少年看着打印出来的大头贴,嘴角微微抽动,眼神从兴奋变为复杂,画面渐暗。
overall_soundscape: 大头贴机器打印声,轻快电子音,女孩笑声。
non_diegetic_music: 无,环境声为主。

第三段 经济危机(30-45s)

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 0-5s 中景,少年站在破旧网吧门口,手里捏着一张皱巴巴的钞票,眼神迷茫,背景是灰暗的街道,路灯闪烁。
[Shot 2] 5-10s 特写,少年低头看手机屏幕,屏幕显示余额不足,他咬了咬嘴唇,眉头紧锁。
[Shot 3] 10-15s 近景,少年抬起头强挤出一个笑容,但眼神藏着失落,画面渐暗,霓虹灯在背景闪烁。
overall_soundscape: 街道车流声,手机提示音,远处广告广播声。
non_diegetic_music: 低沉钢琴曲,略带忧伤。

第四段 天台告白(45-60s)

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 0-5s 全景,少年站在天台上,风吹起他的红发,他手里攥着一张纸条,眼神坚定。
[Shot 2] 5-10s 中景,少年对着天空大喊,声音被风吹散,他脸上带着释然的笑容。
[Shot 3] 10-15s 近景,少年低下头轻声说半句话,话未说完,画面定格在他复杂的表情上。
overall_soundscape: 风声,远处城市噪音,少年喊声的回音。
non_diegetic_music: 无,环境声为主。

第五段 空镜收尾(60-75s)

1
2
3
4
5
6
integrated_multimodal_description:
[Shot 1] 0-5s 空镜,城市夜景,霓虹灯闪烁,镜头缓慢拉远,少年站在天台边缘的背影逐渐变小。
[Shot 2] 5-10s 空镜,网吧招牌熄灭,街道空无一人,只剩一盏路灯亮着。
[Shot 3] 10-15s 空镜,一张大头贴照片飘落在地,上面是少年和女孩的笑脸,画面渐黑。
overall_soundscape: 环境音渐弱,最后只剩风声。
non_diegetic_music: 轻柔钢琴曲,渐弱至无声。

效果展示

由于视频都是一次性生成, 存在瑕疵属于正常现象, 实际的商业短剧, 都是要对每段进行多次抽卡选取, 然后再进行后期剪辑合成

不管怎么说, 视频整体质感还是不错的, 真实感和微表情都比较到位, 至于剧情连贯性, 好好打磨提示词和设计分镜都能解决

完整版:

问题

细心的同学不难发现, 这种首尾帧相连的方式生成的视频, 存在一个严重问题, 那就是人物角色五官存在漂移的情况, 因为每次视频生成都会出现轻微失真偏移,

假设某一段视频的最后一帧画面没有人物, 只有场景, 那么下一段视频生成的人物长相只能参考提示词, 那这种情况下必然会出现换脸的现象

想要解决这个问题, 那就得换模型, 将文生视频模型换成图生视频模型,也就是将minimax_h3_fl2va_pruned_int8_convrot.safetensors换成minimax_h3_ref2va_pruned_int8_convrot.safetensors, 这两个都是MinMax-H3下的子模型

这样一来, 每段视频都有了统一的人物/场景参考图, 就不容易出现明显的人物或场景偏移

文章同步更新至公众号【乱码三千】,欢迎关注~

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

使用ComfyUI+MinMax-H3音视频模型生成视频

发表于 2025-08-31

前言

之前我们介绍过使用ComfyUI+SD1.5文生图模型+AnimateDiff节点生成视频的方案, 只能生成几秒, 本质上属于动图的范畴, 而且只有纯画面没有声音, 有兴趣可参见文章《ComfyUI实现根据文字生成跳舞视频》

现在很多文生视频模型已经问世了, 比如Seedance2.5模型、MinMax-H3模型、Wan2.2模型、LTX-2模型、HuanYanVideo模型等等, 尤其是最近刚出的Seedance2.5模型, 效果很不错, 人物的表情和动作比上一代更加自然

不过Seedance2.5属于付费模型, 调用成本属实有点高, 作为平民用户,还是老老实实使用 ComfyUI+免费模型的方案,让子弹飞一会儿,用不了多久,免费模型也能实现好的效果

以下是开源视频模型对比表格参考:

模型 厂商 原生音频 对口型 画质定位 开源协议 商用/地域限制 最佳场景
MiniMax H3 MiniMax ✅ 音画同出 ✅ Ref2VA 参考音频驱动 写实中上,文字/Logo 准 MiniMax H3 社区许可(非 Apache) 年收>2000 万美元需书面授权;美/欧/英/韩排除在本地权重许可外;界面须标”MiniMax H3” 带声成片/MV/对口型/广告
Wan 2.2 阿里 ❌ 纯视觉 ⚠️ S2V 单人准,快歌会飘 纯视觉天花板 Apache-2.0 无营收/地域门槛,最干净 无声电影感、物理仿真、控动作
LTX-2 Lightricks ✅ 音画同出(19B) ⚠️ 同步有,对口型弱于 H3/S2V 中(快但偏平滑) LTX-2 社区许可(非 Apache) 年收入>1000万美元需商业授权 抽卡草稿、快速预览
HunyuanVideo 1.5 腾讯 ❌ 纯视觉 ❌ 不支持 高(1080p 纹理细) 腾讯混元社区许可(非 Apache,EU/UK/韩排除) 月活>1 亿需授权 高性价比 1080p 空镜、超分底片

目前所有开源的音视频模型中, 音画效果最好的当属MinMax-H3了, 矮个子里面挑高个子😂, 所以今天我们就来聊聊如何使用该模型在ComfyUI平台上进行有声视频的生成

模型和节点清单

模型:

通用三大类模型 = 扩散模型 + 条件编码模型 + VAE 模型 , 所有SD工作流都离不开该框架

角色 模型 作用 放置目录
扩散模型 minimax_h3_fl2va_pruned_int8_convrot.safetensors (~19.5G) 扩散主干(DiT)——在潜空间联合去噪,同时生成视频帧与音频;FL2VA 版支持文字/首帧/尾帧生视频 models/diffusion_models/
条件编码模型 qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors (~14.6G) 文本编码器——把提示词(含分镜、运镜、音乐描述)翻译成模型能理解的 conditioning models/text_encoders/
VAE模型) minimax_h3_video_vae_fp16.safetensors (4.9G)
minimax_h3_audio_vae_fp32.safetensors (
0.6G)
视频 VAE 解码器——把采样出的视频潜变量还原成 RGB 帧序列
音频 VAE 解码器——把音频潜变量还原成 32kHz 立体声(跳舞配乐、脚步声)
(两个文件由一个 VAE Loader 加载)
models/vae/
可选 · 加速模型 minimax_h3_fl2v_turbo_8step_v1.0_comfyui_bf16 负责对现有模型能力进行补充扩展 · Turbo LoRA——8 步加速预览,画质略降 models/loras/

以前SD1.5模型内置条件编码模型和VAE模型, 一个模型顶三个用, 而如今的视频模型由于体积太大, 都是独立存在

节点

通用四大类节点=加载器+条件构建+采样+解码, 同样也是所有SD工作流通用

环节 作用 包含的具体节点(以H3为例)
1. 加载器(Loader) 把模型权重从硬盘读进显存 模型加载器、文本编码器加载器、双VAE加载器
2. 条件构建(Conditioning) 把提示词、参考图、尺寸变成模型能读的数值向量 文本编码 + 条件注入 + 分辨率选择器
3. 采样(Sampling) 在潜空间里一步步去噪,生成最终特征张量 KSampler(采样器)
4. 解码(Decode) 把潜空间数值还原成人能看的图片/视频/音频 VAE 解码 + 音视频合并输出

哪怕以后出现更复杂的模型(比如多模态联合生成),这四步也会像「地基」一样垫底。区别只在于每一步内部塞多少东西——H3 因为要同时出视频和音频,所以它的 Loader 多了个音频 VAE,采样时多了音频 shift 参数而已。


工作流

工作流核心链路是4 个加载器 → 条件构建 → 采样 → AV 解码→视频保存。

① 加载区(3 个 Loader = 三大件)

节点 加载的模型 输出口
UNET Loade minimax_h3_fl2va_pruned_int8_convrot.safetensors MODEL
CLIP Loader qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors CLIP
VAE Loader minimax_h3_video_vae_fp16.safetensors (4.9G)
minimax_h3_audio_vae_fp32.safetensors (
0.6G)
(视频 VAE + 音频 VAE,两个文件)
VAE

image-20260901131710797

② 条件与参数区

节点 作用 输入来源
MiniMaxH3ImageToVideo(T2V/I2V 官方封装节点) 内置三段式提示词文本框;把 prompt + 三大件 + 分辨率 + 首/尾帧拼成完整 conditioning 与空 AV latent,输出 CONDITIONING + LATENT model、clip、vae(视频)、prompt、width、height、length,可选 first_frame / last_frame

image-20260901165156159

③ 采样区

节点 作用 关键参数
KSampler 联合视频/音频双 sigma 采样去噪 seed=12345, steps=20, cfg=1.0, sampler=res_multistep, scheduler=simple

iShot_2026-09-02_10.08.35

④ 解码 + 保存区

节点 作用 输入
VAEDecode(画面)+ VAEDecodeAudio(声音) 分两路解码 → IMAGE 帧 + AUDIO 波形 samples + 视频 VAE / 音频 VAE
VHS_VideoCombine(VHS 插件) 合成 24fps MP4 并保存 video(IMAGE)+ 可选 audio(AUDIO)

image-20260901170301026

连线关系(一图流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─── ① 加载区(三大件)──────────────┐
│ UNETLoader │
│ CLIPLoader │
│ VAELoader (video) │
│ VAELoader (audio) │
└────────────────────────────────────┘
↓ MODEL
┌─── ② 条件构建 ────────────────────┐
│ MiniMaxH3ImageToVideo │
│ (prompt, width/height/length) │
└────────────────────────────────────┘
↓ CONDITIONING / LATENT
┌─── ③ 采样 ────────────────────────┐
│ KSampler (20步, cfg=1.0) │
└────────────────────────────────────┘
↓ SAMPLES
┌─── ④ 解码 + 保存 ─────────────────┐
│ VAEDecode ─→ VHS_VideoCombine │
│ VAEDecodeAudio ─→ (混入音频) │
└────────────────────────────────────┘

image-20260901170520441

关键采样参数(针对跳舞场景)

参数 值 说明
cfg (guidance) 1.0 ⚠️ H3 已做 guidance distillation,千万别设 7.5,否则边缘硬化、动作崩坏
steps 20(质量)/ 4-8(Turbo LoRA 加速) 默认 20
sampler / scheduler res_multistep / simple 求快可用 euler / simple
帧数 按 17n+5 网格对齐,5秒≈124帧 Resolution Selector 自动吸附
分辨率 测试 0.4MP(832×480);成品 1344×768 (16:9) 本地最高 768p 短边;别用 1.0MP(会超 768×1344 像素面积上限)
FPS 24 固定

具体工作流json代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{
"1": {
"inputs": {
"unet_name": "minimax_h3_fl2va_pruned_int8_convrot.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"2": {
"inputs": {
"clip_name": "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors",
"type": "minimax",
"device": "default"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"3": {
"inputs": {
"vae_name": "minimax_h3_video_vae_fp16.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"4": {
"inputs": {
"vae_name": "minimax_h3_audio_vae_fp32.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"7": {
"inputs": {
"prompt": "integrated_multimodal_description: cinematic realism, soft natural light, a young woman in a red velvet long dress, dancing alone in a minimalist white studio. medium-full shot, fixed camera with slight push-in. [Shot 1] 0-2s: facing camera, arms rising slowly to shoulder height, weight shifting right to left, elegant. [Shot 2] 2-4s: a fluid 360-degree pirouette, dress spreading outward, hair flowing, stable landing. [Shot 3] 4-5s: return to start, arms descending from overhead, smile to camera, freeze frame. continuous motion, clear rhythm, no limb deformation, consistent identity costume lighting. overall_soundscape: bare feet soft steps on floor, dress rustling air, synced to spin. non_diegetic_music: lyrical piano ~96BPM, bright melody, clear bass on beat 1, aligned to dance, fade out at end.",
"width": 1344,
"height": 768,
"length": 124,
"clip": [
"2",
0
],
"vae": [
"3",
0
]
},
"class_type": "MiniMaxH3ImageToVideo",
"_meta": {
"title": "MiniMax H3 Image to Video"
}
},
"8": {
"inputs": {
"seed": 180855514009551,
"steps": 20,
"cfg": 1,
"sampler_name": "res_multistep",
"scheduler": "simple",
"denoise": 1,
"model": [
"1",
0
],
"positive": [
"7",
0
],
"negative": [
"7",
0
],
"latent_image": [
"7",
1
]
},
"class_type": "KSampler",
"_meta": {
"title": "K采样器"
}
},
"9": {
"inputs": {
"samples": [
"8",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"10": {
"inputs": {
"samples": [
"8",
0
],
"vae": [
"4",
0
]
},
"class_type": "VAEDecodeAudio",
"_meta": {
"title": "VAE解码(音频)"
}
},
"11": {
"inputs": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "MiniMax_H3/dancing",
"format": "video/h264-mp4",
"pix_fmt": "yuv420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true,
"images": [
"9",
0
],
"audio": [
"10",
0
]
},
"class_type": "VHS_VideoCombine",
"_meta": {
"title": "Video Combine 🎥🅥🅗🅢"
}
}
}

复制上面的代码, 点击ComfyUI画布粘贴即可使用

提示词

采用 H3 官方三段式结构:integrated_multimodal_description / overall_soundscape / non_diegetic_music

跳舞必须写清「分镜 + 动作节拍 + 配乐」,否则只会原地抖动

提示词如下:

1
2
3
4
5
6
7
8
9
10
integrated_multimodal_description: 写实电影感, 柔和自然光, 一位身穿红色丝绒长裙的年轻女性,
在极简白色舞蹈工作室中央独舞. 中全景, 固定机位轻微推近.
[Shot 1] 0-2秒: 面朝镜头, 双臂由体侧缓缓抬至肩高, 重心由右脚移至左脚, 舒展优雅.
[Shot 2] 2-4秒: 以腰部为轴做一个流畅的360度平转 pirouette, 裙摆随旋转向外展开, 长发轻扬, 落地稳定收束.
[Shot 3] 4-5秒: 回到起始姿态, 双臂由头顶缓缓落下, 面向镜头微笑, 定格结束.
动作连贯, 节奏明确, 肢体无变形, 身份服装光线保持一致.

overall_soundscape: 赤脚踩地板轻微脚步声, 裙摆与空气摩擦细响, 随旋转同步.

non_diegetic_music: 中速约96BPM抒情钢琴曲, 旋律明亮, 每小节第一拍清晰低音, 与舞蹈节拍对齐, 结尾渐弱收束.

如果想要指定人物长相(脸保持一致), 需要把扩散模型由minimax_h3_fl2va_pruned_int8_convrot.safetensors替换成minimax_h3_ref2va_pruned_int8_convrot.safetensors ,并指定参考图

云端生成

视频模型对显卡算力要求比较高, 至少24G显存, 普通电脑完全没法玩, 建议大家使用云端ComfyUI, 比如阿里无影灵构工作站, 每天签到就能领算力, 相当于免费

image-20260831222721359

直接使用现成的镜像包即可:

image-20260901151419011

目前新用户可直接一次性领取200灵豆算力, 可以试试:

image-20260901151551902

image-20260901152118409

如果模型缺失, 还可以上传自定义模型:

image-20260901151945802

通过官方的无影云盘实现本地电脑和云端数据交互:

image-20260901154052026

image-20260901154147217

官网: 点击进入

使用第三方AI平台

目前比较知名的AI视频创作平台有:

  • Tapnow:深圳添科智能 旗下的 AI 原生商业视觉创作引擎
  • Libtv:LiblibAI(哩布哩布 AI)出品的 AI 视频创作平台,无限画布 + Skill 接口,内部集成了市面主流的数十种视频模型
  • Running Hub:基于 ComfyUI架构的 AI 创作平台,兼容ComfyUI JSON、预装 7000-9000+ 节点,其实就是云端 ComfyUI
  • 即梦AI:字节的闭源消费级 AI 视频/图像产品
  • Updream :B 站出品的面向 UP 主的AI 视频创作平台
  • Seko: 商汤科技出品的 AI 创编一体短剧/短视频创作 Agent,无限画布 + 创作 Agent + Skill 生态,主打短剧/漫剧/音乐 MV/出海多集连续生产

我们需要明白的是, 视频生成的效果和使用的视频模型有关,和你使用什么平台没有太大关系,这就好比同样是聊天工具,扣扣、微信、抖音,你说哪个聊天更强?它们从技术上其实没有本质区别,全看你个人需求,这些 AI 平台也是同样的道理,选一个自己用的顺手且个人认为性价比高的用就行。

平台的目的只是为了降低大众用户的使用门槛,用户不需要了解具体的底层原理,也无需花时间精力自己搭建调试部署软件下载模型等,只要提供相应的需求以及镜头语言提示词即可进行创作

如果你是程序员或者懂底层原理,强烈建议使用 ComfyUI 自己手搓,更加灵活可控,足以应对未来 AI 变化

如果你只是想快速体验一把文生视频的快感, 那么直接上豆包, 方便快捷, 每天有免费的视频生成额度, 一个账号不够, 那就多开几个账号玩😂, 可以用它练练提示词

效果展示

由于我们在工作流中设置的视频生成总帧数是124, 而帧率是24, 所以总共生成5秒的视频, 如果你想生成15秒, 需要在MiniMaxH3ImageToVideo节点中将总帧数改为362, 并将提示词补充够15秒镜头

image-20260901173829756

15秒的跳舞视频如下:

上方15秒视频提示词参考:

1
2
3
4
5
integrated_multimodal_description: 真人电影感, 高对比霓虹夜店美学, 暗调环境光配电光蓝/品红/青绿扫光. [Shot 1] 0-2秒: 低角度广角仰拍, 一位身穿黑色反光夹克的女性DJ在升高DJ台后推推子, 手指在CDJ转盘上快速刮碟, 头戴LED环形耳机, 霓虹管字 "NEON" 在身后脉动. 镜头以中幅快速横移(Truck Right)扫过DJ台, 频闪灯每拍切一次颜色. [Shot 2] 2-4秒: 硬切到舞池全景, 几十名年轻人在135BPM节拍下齐跳, 双手举过头顶交叉摆动, 身体左右晃肩, 有人原地蹦跳, 有人做wave. 相机手持跟拍(Tracking Shot)穿入人群, 小幅快速推进, 激光束从头顶扫过雾机, 镜头边缘有意象光晕与色散. [Shot 3] 4-5秒: 切到人群中景, 一位穿银色吊带短裙的舞者正对镜头甩头顿足, 霓虹在她脸上交替投下蓝/粉光斑, 背景虚化的人海继续跳动. 镜头在最后一拍做轻微慢动作(Roll Counterclockwise小幅)后切回实时, 定格在众人跃起瞬间. 全程人物服装/发型/光线一致, 肢体无变形, 动作与135BPM鼓点严格对齐.

overall_soundscape: 厚底靴踩地闷响, 液体在玻璃杯里晃动, 雾机喷气嘶声, 人群合唱式喊拍"嘿!嘿!", 近距离衣物摩擦与呼吸声, 音箱低频在胸腔共振的体感轰鸣.

non_diegetic_music: 135BPM 商业DJ舞曲, 四四拍底鼓每拍清晰, 副歌前_buildup_用滚奏hi-hat与上升synth riser, drop段重低音bassline+失真lead+紧凑clap, 间奏切到filter sweep与crowd vocal chop, 整体动态从intro克制到drop全频炸开, 结尾留2拍echo尾音.

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

80个表情包AI提示词(直接复制粘贴)

发表于 2025-08-30

一、快乐类(10 条)

E01 开怀大笑(AU6+AU12+AU25)

[角色名],东亚青年,鹅蛋脸。面部:Orbicularis oculi tensed with crow’s feet at lateral canthus, zygomaticus major fully contracted pulling lip corners up and outward, mouth slightly open showing 6-8 upper teeth. 眼神光位于瞳孔上缘正中,视线平视。侧面45度暖光,中景锁眼部。负面规避:no forced grin, no cartoonish distortion, no reddened cheeks.

E02 含蓄微笑(AU12 轻微)

[角色名],东亚青年。面部:zygomaticus major subtly contracted, lip corners lifted slightly, no orbital tightening, no teeth showing. 眼神光柔化于瞳孔中心,视线温和。前景柔光,特写。负面规避:no exaggerated smile, no wide open mouth.

E03 窃笑(单侧上提+斜瞥)

[角色名],东亚青年。面部:unilateral zygomaticus major contracted on one side pulling that lip corner up, orbicularis oculi lightly engaged, head tilted slightly. 视线从眼角斜瞥向画面外。侧逆光,中景。负面规避:no full smile, no symmetric grin.

E04 得意坏笑(单侧嘴角+眉微挑)

[角色名],东亚青年。面部:one lip corner lifted by unilateral zygomaticus major, contralateral eyebrow raised a few millimeters, masseter relaxed. 视线锁定下方目标,带玩味。顶光,中景。负面规避:no laugh, no teeth showing, no blush.

E05 讨好笑(上提+眉松弛+视线向上)

[角色名],东亚青年。面部:zygomaticus major mildly contracted, eyebrows relaxed and slightly raised, lip corners up, eyes softened. 视线微微上抬望向对方。正面柔光,中景。负面规避:no strained smile, no sweat, no nervous tick.

E06 温柔笑(肌轻+眼柔)

[角色名],东亚青年。面部:zygomaticus major subtly contracted, orbicularis oculi lightly tensed at corners, eyelids gently lowered. 眼神光柔和扩散,视线含情。暖侧光,近景。负面规避:no exaggerated grin, no redness.

E07 苦笑(勉强上提+微下沉)

[角色名],东亚青年。面部:zygomaticus major barely engaged lifting corners a millimeter, depressor anguli oris mildly active pulling corners down, corrugator slightly contracted. 视线低垂。冷调光,中景。负面规避:no real laughter, no tears.

E08 欣慰笑(眼角微弯+呼吸平缓)

[角色名],东亚青年。面部:orbicularis oculi gently tensed at lateral corners, zygomaticus major softly contracted, lips relaxed. 呼吸节奏平缓,肩微沉。柔光,中景。负面规避:no big grin, no open mouth.

E09 如释重负(眉舒展+嘴角放松上扬)

[角色名],东亚青年。面部:frontalis relaxed smoothing forehead, zygomaticus major lightly contracted, eyebrows unfurrowed, a slow exhale implied by parted lips. 视线由紧锁转为远方。顶侧光,中景。负面规避:no tension lines, no sigh exaggeration.

E10 开怀大笑·收着演(AU6+AU12,闭口版)

[角色名],东亚青年。面部:a restrained version — orbicularis oculi subtly tensed, zygomaticus major moderately contracted, mouth closed but corners clearly up, 2-3 upper teeth faintly visible. subtle micro-movement. 眼神光居中。柔光特写。负面规避:no wide open mouth, no cartoonish, no red cheeks.


二、愤怒类(9 条)

E11 压抑的怒火(AU4+AU7+AU25)

[角色名],东亚青年。面部:corrugator contracted drawing brows down and together, orbicularis oris tightened pressing lips into a line, masseter tensed defining jawline. 视线锁定前方,咬肌硬化。侧45度冷光,中景锁下颌。负面规避:no exaggerated frown, no shouting, no facial redness.

E12 爆发大吼(AU4+AU10+AU25+AU27)

[角色名],东亚青年。面部:corrugator strongly contracted, levator labii superioris raised baring upper teeth, mouth wide open with teeth fully visible, masseter hard. 颈肌紧绷。正面强顶光,中景。负面规避:no subtle expression here — full eruption intended, but no cartoon distortion.

E13 怒极反笑(皱眉+单侧假笑)

[角色名],东亚青年。面部:corrugator active with brows lowered, unilateral zygomaticus major producing a lopsided false smile, orbicularis oris tight. 视线冷盯。硬光,中景。负面规避:no warm smile, no teeth widely shown.

E14 阴沉冷笑(单侧下拉+眉压)

[角色名],东亚青年。面部:depressor anguli oris active pulling one corner down, corrugator mildly contracted, no orbital participation, lips pressed. 视线从下往上觑。低角光,中景。负面规避:no smile, no laugh lines.

E15 不耐烦怒视(眉压+眼锁定+下颌微咬)

[角色名],东亚青年。面部:brows lowered and drawn in, eyes locked on target with levator palpebrae engaged, masseter subtly tensed. 唇微抿。平光,中景。负面规避:no shouting, no open mouth, no blush.

E16 憋着一股火(咬肌紧+口轮抿+眉微蹙)

[角色名],东亚青年。面部:masseter tensed hardening jawline, orbicularis oris tightened, corrugator faintly active, nostrils subtly flaring via nasalis. 视线侧移又回。侧光,中景。负面规避:no explosion, no tears, no redness.

E17 咬牙切齿(牙关+口轮紧)

[角色名],东亚青年。面部:masseter hard, jaws clenched with teeth gritted, orbicularis oris pressed thin, corrugator mildly drawn. 视线固定。硬顶光,特写锁下颌。负面规避:no open mouth, no smile.

E18 冷淡·冷怒(无表情+微皱+冷视线)

[角色名],东亚青年。面部:overall musculature near rest, corrugator barely active, levator palpebrae neutral, no zygomatic participation. 视线冰冷平直。平光,中景。负面规避:no emotion leakage, no smile, no frown exaggeration.

E19 愤怒·放开演(AU4+AU10 全开+颈肩)

[角色名],东亚青年。面部:corrugator fully contracted, levator labii superioris raised, mouth open in a yell, masseter rigid, veins implied at temple. 身体前倾。强顶光,中景。负面规避:no cartoonish melt, keep anatomical.


三、悲伤类(10 条)

E20 啜泣(AU1+AU4+AU15)

[角色名],东亚青年。面部:frontalis inner raised, corrugator contracted, depressor anguli oris active pulling lip corners down, a single tear at lower eyelid. 视线低垂散焦。顶侧柔光,特写。负面规避:no full sob distortion, no red全网脸.

E21 含泪强忍(AU1+AU2+AU7+AU15)

[角色名],东亚青年。面部:frontalis raised, orbicularis oculi tightened holding back tears, depressor anguli oris mildly contracted, lower lid slightly raised. 视线强忍前方。侧光,中景。负面规避:no crying release, no reddened eyes, no open sob.

E22 低头落泪(上睑垂+泪+头微低)

[角色名],东亚青年。面部:upper eyelids dropped, a tear tracing the cheek, head tilted down a few degrees, depressor anguli oris active. 视线落向地面。顶光,中景。负面规避:no exaggerated wail, no cartoon tears.

E23 失声痛哭(口张+眉蹙+泪+肩抖)

[角色名],东亚青年。面部:mouth open in silent cry, corrugator contracted, tears streaming, shoulders implied trembling. 视线失焦上翻。柔顶光,中景。负面规避:no theatrical scream, keep micro-tremor subtle.

E24 麻木(全肌松弛+视线散)

[角色名],东亚青年。面部:all facial muscles at rest, levator palpebrae neutral, no zygomatic, no corrugator, gaze unfocused and vacant. 唇微张无表情。平光,中景。负面规避:no emotion, no spark in eyes, no micro-movement.

E25 自嘲苦笑(假笑+下沉+眉挑)

[角色名],东亚青年。面部:unilateral zygomaticus major producing a wry smile, depressor anguli oris pulling other corner down, one eyebrow raised ironically. 视线斜向一侧。侧光,中景。负面规避:no warmth, no real laugh.

E26 失神(瞳孔散+睑微张+无聚焦)

[角色名],东亚青年。面部:pupils dilated and unfocused, upper eyelids slightly parted, levator palpebrae engaged but gaze empty, lips relaxed. 头微垂。暗调光,中景。负面规避:no emotion, no catchlight convergence.

E27 悔恨自责(眉内收+视线低+口微抿)

[角色名],东亚青年。面部:brows drawn inward and down, gaze lowered, orbicularis oris mildly tightened, corrugator active. 视线锁自己手部。侧光,中景。负面规避:no tears, no outburst.

E28 悲伤·含蓄(上睑微垂+嘴角水平沉)

[角色名],东亚青年。面部:upper eyelids slightly lowered, lip corners pulled horizontally down a millimeter via depressor anguli oris, no orbital tightening. 视线柔散。柔光,中景。负面规避:no crying, no red cheeks.

E29 委屈(眉内上提+下唇微突+眼湿)

[角色名],东亚青年。面部:inner frontalis raised with brows up, lower lip pushed slightly forward, eyes glistening without tear fall, depressor anguli oris mild. 视线侧向回避。顶柔光,中景。负面规避:no full cry, no rage.


四、惊讶类(7 条)

E30 震惊(AU1+AU2+AU5+AU25+AU27)

[角色名],东亚青年。面部:frontalis and levator palpebrae fully engaged raising brows and upper lids, eyes wide open, jaw dropped, mouth slightly open. 身体微后仰。正面强光,中景。负面规避:no cartoonish bulge, keep anatomical.

E31 轻度的意外(眉微挑+睑微抬+嘴微张)

[角色名],东亚青年。面部:eyebrows raised a few millimeters, upper lids lightly lifted, mouth parted a centimeter, levator palpebrae engaged. 视线聚焦突现物。侧光,中景。负面规避:no full shock, no wide gape.

E32 难以置信(眉高挑+眼圆+头微摇)

[角色名],东亚青年。面部:brows high, eyes rounded with levator palpebrae fully engaged, mouth slightly open, head implied shaking subtly. 视线定格。顶光,中景。负面规避:no laughter, no cry.

E33 惊喜(惊+随即轻提)

[角色名],东亚青年。面部:initial brow raise and eye widen transitioning into mild zygomaticus major contraction, lips parting into a small smile, catchlight brightening. 视线落向礼物。暖光,中景。负面规避:no eerie stare, no cartoon sparkle.

E34 被吓一跳(睑骤抬+身微后+口微张)

[角色名],东亚青年。面部:upper lids snapped open, levator palpebrae engaged, mouth parted in a gasp, body implied jerking back. 视线骤向声源。侧逆光,中景。负面规避:no prolonged scream, no distortion.

E35 错愕(眉平抬+嘴微张+视线定)

[角色名],东亚青年。面部:brows levelly raised, mouth slightly open, gaze frozen on the unexpected, levator palpebrae engaged. 肌静止。平光,中景。负面规避:no emotion shift, no smile.

E36 好奇探询(头微倾+眉挑+眼聚焦)

[角色名],东亚青年。面部:head tilted a few degrees, one brow raised in inquiry, eyes focused and bright, lips gently parted. 视线落向目标。侧光,中景。负面规避:no suspicion, no anger.


五、恐惧类(9 条)

E37 极度恐惧(AU1+AU2+AU4+AU5+AU25+AU27)

[角色名],东亚青年。面部:frontalis raised while corrugator contracted (antagonistic “fear brow”), levator palpebrae elevated, eyes wide, mouth open with lips retracted horizontally, nostrils flared via nasalis. 身僵。冷顶光,中景。负面规避:no calm, no smile, anatomical only.

E38 不安(眉微蹙+视线飘移+唇微抿)

[角色名],东亚青年。面部:corrugator faintly active, gaze drifting side to side, orbicularis oris mildly tightened, levator palpebrae neutral. 呼吸微急。平光,中景。负面规避:no terror, no tears.

E39 心虚躲闪(视线侧移+睑微垂+唇紧)

[角色名],东亚青年。面部:gaze sliding to the side, upper lids slightly lowered, orbicularis oris tightened, corrugator barely active. 头微偏。侧光,中景。负面规避:no confidence, no smile.

E40 吓呆(睑大+嘴微张+身僵)

[角色名],东亚青年。面部:eyes widened with levator palpebrae engaged, mouth slightly parted, whole face frozen, no micro-movement. 身僵直。正面光,中景。负面规避:no scream, no tears.

E41 后怕(肩微耸+呼吸急+视线回放)

[角色名],东亚青年。面部:shoulders implied hunched, rapid shallow breath implied, gaze replaying backward, corrugator mild, lips parted. 肌紧。侧光,中景。负面规避:no calm smile, no tears.

E42 胆怯(头微缩+眉垂+视线低)

[角色名],东亚青年。面部:head drawn slightly back/down, brows lowered, gaze dropped low, orbicularis oris gently tightened. 身微缩。暗调光,中景。负面规避:no boldness, no anger.

E43 焦虑(眉内收+唇抿+呼吸乱)

[角色名],东亚青年。面部:brows drawn inward, orbicularis oris pressed, irregular breath implied, levator palpebrae neutral, gaze flickering. 手指可微动(环境)。平光,中景。负面规避:no panic scream, no tears.

E44 恐慌前兆(AU1+AU2+AU4 恐惧的眉毛)

[角色名],东亚青年。面部:frontalis raised while corrugator contracted producing the contradictory “fear brow”, levator palpebrae engaged, mouth neutral. 视线锁威胁。冷光,中景。负面规避:no flat expression, no smile.

E45 畏缩(下颌微退+肩耸+视线避)

[角色名],东亚青年。面部:jaw drawn slightly back, shoulders hunched, gaze averted downward, corrugator mild, lips pressed. 身退。侧光,中景。负面规避:no confrontation, no anger.


六、傲慢类(8 条)

E46 不屑冷笑(单侧上提+眉微挑下压)

[角色名],东亚青年。面部:unilateral zygomaticus major lifting one corner in a sneer, contralateral brow raised then lowered, no orbital warmth. 视线从鼻尖觑人。低角光,中景。负面规避:no friendly smile, no laugh.

E47 居高临下(睑微垂看下+下巴微抬)

[角色名],东亚青年。面部:upper lids slightly dropped looking down, chin lifted a few millimeters, levator palpebrae neutral, no zygomatic. 视线俯视。顶光,中景。负面规避:no warmth, no smile.

E48 轻蔑瞥视(眼斜瞥+单侧眉挑)

[角色名],东亚青年。面部:eyes glancing sideways, one brow arched, depressor anguli oris faintly active, head turned slightly. 视线斜出画外。侧光,中景。负面规避:no engagement, no smile.

E49 鄙夷(皱眉微+单侧嘴下+头微偏)

[角色名],东亚青年。面部:corrugator mildly contracted, unilateral depressor anguli oris pulling corner down, head tilted away, no orbital participation. 视线回避。平光,中景。负面规避:no laugh, no interest.

E50 轻佻调戏(单侧笑+眉挑+头微倾)

[角色名],东亚青年。面部:unilateral zygomaticus major with a playful smirk, one brow raised, head tilted coyly, orbicularis oculi lightly engaged. 视线挑逗落向对方。暖侧光,中景。负面规避:no genuine affection, no blush.

E51 漫不经心(视线散+嘴角微平+肩松)

[角色名],东亚青年。面部:gaze unfocused and drifting, lip corners neutral, shoulders implied relaxed, no corrugator, no zygomatic. 肌全松。平光,中景。负面规避:no attention, no emotion.

E52 挑衅挑眉(单侧眉高挑+下颌微抬)

[角色名],东亚青年。面部:one brow raised high in challenge, jaw lifted, levator palpebrae engaged, lips neutral or faint smirk. 视线锁对方。硬光,中景。负面规避:no fear, no smile warmth.

E53 高高在上(下巴抬+睑垂+无笑)

[角色名],东亚青年。面部:chin lifted, upper lids dropped in condescension, no zygomatic, no corrugator, gaze downward. 身姿挺直。顶光,中景。负面规避:no warmth, no engagement.


七、害羞类(8 条)

E54 低头脸红(头低+颊红+视线下)

[角色名],东亚青年。面部:head lowered, a soft blush rising on cheeks, gaze dropped, upper lids slightly lowered, zygomaticus major barely engaged. 颊泛红(允许)。柔顶光,中景。负面规避:no bold eye contact, no big smile.

E55 慌乱躲闪(视线快移+睑垂+唇微抿)

[角色名],东亚青年。面部:gaze darting away quickly, upper lids lowered, orbicularis oris mildly tightened, a flicker of blush. 头微偏。侧光,中景。负面规避:no steady gaze, no confidence.

E56 偷瞄(单侧眼斜+头微偏+随即回)

[角色名],东亚青年。面部:one eye glancing sideways, head tilted slightly then implying a snap back, levator palpebrae engaged briefly, lips neutral. 视线快速回收。侧光,中景。负面规避:no prolonged stare, no smile yet.

E57 尴尬(眉微耸+唇抿+视线飘)

[角色名],东亚青年。面部:brows raised slightly in awkwardness, orbicularis oris pressed, gaze floating to the side, a faint blush. 身微僵。平光,中景。负面规避:no laugh, no anger.

E58 不好意思(头微低+颊微红+嘴角平)

[角色名],东亚青年。面部:head dipped a few degrees, cheeks faintly flushed, lip corners neutral, upper lids lowered, gaze downward. 柔光。中景。负面规避:no big smile, no eye contact.

E59 娇羞(轻笑+头偏+视线躲)

[角色名],东亚青年。面部:zygomaticus major subtly contracted in a shy smile, head turned aside, gaze avoiding, a soft blush. 颊红(允许)。暖侧光,中景。负面规避:no bold smile, no direct gaze.

E60 紧张舔唇(舌微露+唇抿+视线飘)

[角色名],东亚青年。面部:tongue tip faintly visible, orbicularis oris pressed then parted, gaze drifting nervously, brows mildly raised. 微汗(可)。平光,中景。负面规避:no calm, no smile.

E61 羞赧微笑(含蓄笑+颊红+视线低)

[角色名],东亚青年。面部:zygomaticus major subtly contracted (AU12 light), cheeks lightly flushed, gaze lowered, orbicularis oculi barely engaged. 柔光。中景。负面规避:no open laugh, no eye contact.


八、困倦类(7 条)

E62 打哈欠(口大张+睑垂+泪汪)

[角色名],东亚青年。面部:mouth wide open in a yawn, upper lids heavy and dropped, eyes watering slightly, masseter relaxed. 身微伸。柔光,中景。负面规避:no shock, no speech.

E63 眼皮沉重(上睑半垂+视线低+微点头)

[角色名],东亚青年。面部:upper eyelids half-dropped, levator palpebrae disengaged, gaze low, head implied nodding slightly. 肌松。暗调光,中景。负面规避:no alertness, no wide eyes.

E64 慵懒(肌全松+视线散+姿态瘫)

[角色名],东亚青年。面部:all muscles relaxed, gaze unfocused and soft, lips parted lazily, no corrugator, no zygomatic. 身姿瘫软(环境)。暖光,中景。负面规避:no tension, no emotion.

E65 无精打采(眉平+睑半+肩沉)

[角色名],东亚青年。面部:brows neutral and flat, upper lids half, shoulders implied dropped, gaze low, no micro-movement. 平光。中景。负面规避:no energy, no smile.

E66 昏昏欲睡(睑垂+头微垂+嘴微张)

[角色名],东亚青年。面部:upper lids drooping, head tilted down a few degrees, mouth slightly open, levator palpebrae disengaged. 身微晃。暗光,中景。负面规避:no wakefulness, no alert gaze.

E67 睡眼惺忪(睑半抬+视线模糊+揉眼)

[角色名],东亚青年。面部:upper lids half-lifted, gaze blurred and unfocused, one hand implied rubbing an eye, lips relaxed. 柔光。中景。负面规避:no sharp focus, no alertness.

E68 疲惫放空(视线散+肌松+呼吸缓)

[角色名],东亚青年。面部:gaze scattered and vacant, facial muscles at rest, slow even breath implied, no catchlight convergence. 暗调光。中景。负面规避:no emotion, no tension.


九、宠溺类(12 条)

E69 温柔注视(睑微垂柔+视线定+唇平)

[角色名],东亚青年。面部:upper lids gently lowered in softness, gaze fixed warmly on target, lips neutral and relaxed, no corrugator. 眼神光柔化。暖侧光,中景。负面规避:no hardness, no anger.

E70 慈爱笑(肌轻+眼角微弯+视线柔)

[角色名],东亚青年。面部:zygomaticus major lightly contracted, orbicularis oculi gently tensed at corners, gaze tender, lips softly up. 暖光。近景。负面规避:no exaggerated grin, no redness.

E71 宠溺调侃(单侧笑+眉挑+头微摇)

[角色名],东亚青年。面部:unilateral zygomaticus major with a playful smirk, one brow raised, head implied shaking slightly in amusement, gaze fond. 暖光。中景。负面规避:no mockery cold, no blush.

E72 心疼(眉微蹙+视线柔+唇微抿)

[角色名],东亚青年。面部:corrugator faintly active in concern, gaze soft and lowered on the beloved, orbicularis oris mildly tightened, no zygomatic. 侧光。中景。负面规避:no anger, no tears.

E73 深情凝视(睑微垂+视线锁+唇放松)

[角色名],东亚青年。面部:upper lids gently lowered, gaze locked with convergence of catchlight, lips relaxed and parted a breath, no corrugator. 暖光。特写锁眼。负面规避:no distraction, no smile forced.

E74 欣慰慈祥(眼角弯+嘴角轻提+头微点)

[角色名],东亚青年。面部:orbicularis oculi tensed at corners, zygomaticus major softly contracted, head implied nodding slowly, gaze warm. 暖光。中景。负面规避:no big laugh, no red cheeks.

E75 溺爱摇头(头微摇+笑+视线柔)

[角色名],东亚青年。面部:head implied shaking in fond exasperation, zygomaticus major mildly contracted, gaze tender, lips soft. 暖光。中景。负面规避:no reproach cold, no anger.

E76 温柔安抚(眉舒+视线柔+手抚)

[角色名],东亚青年。面部:brows smoothed, gaze soft and reassuring, one hand implied reaching to comfort, lips relaxed. 暖侧光。中景。负面规避:no tension, no fear.

E77 宠溺坏笑(单侧 zygomaticus + 眼弯)

[角色名],东亚青年。面部:unilateral zygomaticus major with a knowing smirk, orbicularis oculi lightly engaged at corners, gaze playful, head tilted. 暖光。中景。负面规避:no cold sneer, no blush.

E78 疼惜(眉内收+视线柔+唇微抿)

[角色名],东亚青年。面部:brows drawn mildly inward in tenderness, gaze soft on the hurt one, orbicularis oris gently tightened, no zygomatic. 侧光。中景。负面规避:no anger, no cry.

E79 柔情微笑(肌轻+眼轻)

[角色名],东亚青年。面部:zygomaticus major subtly contracted, orbicularis oculi lightly tensed, gaze gentle, lips softly up, catchlight soft. 暖光。近景。负面规避:no wide grin, no redness.

E80 满眼温柔(睑微垂+catchlight柔+唇平)

[角色名],东亚青年。面部:upper lids gently lowered, catchlight diffused and warm in both pupils with shared direction, lips neutral, gaze enveloping, no corrugator. 暖光。特写锁眼。负面规避:no coldness, no hardness.


使用贴士(来自原文方法)

  • 替换角色:把每条开头的 [角色名] + 身份锚整段换成你的角色设定即可。

  • 视频时序:若要动起来,把每条按”0–0.5秒…/0.5–1.2秒…”拆时间窗口(参考原文第四节),每个窗口 2–4 秒独立描述。

  • 克制写法:在提示词里加 subtle / slight / barely 能显著抑制卡通化崩坏(原文实测提升 30%–40% 稳定性)。

  • 负面约束:结尾的”负面规避”建议保留,对即梦 Seedance / Kling 稳定性提升最明显。

  • 单段聚焦:每条只写 3–5 个肌群,别一次堆超过 6 个 AU,否则准确率下降(原文 Seedance 实测)。

  • 眼神光:需要”灵魂感”时,务必锁定 catchlight 坐标(如”瞳孔上缘正中”),让双眼反光点同向。

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

让静态图片中的人物开口说话

发表于 2024-06-02

前言

如何让图片中人物嘴巴动起来呢? 实现蒙娜丽莎唱歌, 兵马俑说话

接下来给大家介绍几款能用的工具

阿里通义APP

最近阿里通义APP上线了照片人物说话唱歌的功能

LALAMU

照片人物对口型, 只需要上传人物图片和音频, 即可自动生成对口型视频, 支持动漫人物嘴巴识别

image-20240604160453792

点击进入官网

即梦

字节跳动旗下的一款产品, 该网站包含AI生成图片,、文生视频和图生视频功能, 以及对口型, 不过是收费的

image-20240604163524552

点击进入官网

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

在线ComfyUI平台汇总

发表于 2024-06-01

前言

目前AI运算对电脑配置要求相对较高, 如果你设备有限, 并且不想手动下载部署ComfyUI, 那么推荐你使用在线版本

这里给大家汇总一些好用的在线ComfyUI平台, 将它们结合使用, 能满足我们大部分的需求

esheep

国产平台, 这也是我目前用的比较多的, 不仅支持ComfyUI , 还有在线WebUI, 满足不同人群需求

image-20240603201847731

点击进入官网

runcomfy

国外的一个在线ComfyUI平台, 国内用户无需大爬梯即可快速访问, 直接使用微软账号登录即可

image-20240603202634712

新用户自动赠送0.25美元, 可以免费使用中型机器15分钟:

image-20240603203639535

启动后在My Machines中可以找到启动后的机器

image-20240603203722752

进入后界面和本地ComfyUI版本无异:

image-20240603203752102

点击进入官网

flowt

也是国外的一个平台, 不过是纯付费的, 10美元一个月, 连免费体验时间都没有, 这个就看大家自己需求了

image-20240603204714613

image-20240603204549957

点击进入官网

cephalon

国产AI绘图平台, 有现成的镜像可以一键使用, 内置配套镜像和插件以及可用的工作流

虽然是付费平台, 但是新用户有赠送积分, 另外拉新也有时长赠送

image-20240603204915871

image-20240603205014890

image-20240603210402782

点击进入官网

CodeWithGpu

和cephalon平台类似, 可以使用现成镜像, 也可以自己搭建一台镜像, 别人如果使用自己的原创镜像可获取现金奖励

image-20240603205259472

image-20240603205423546

里面有各种AI镜像, 不仅有ComfyUI, WebUI也是有的:

image-20240603205633757

点击进入官网

其他

在线WebUI除了上面提到的一些平台外, 还有一个平台也支持WebUI, 由于它没有内置ComfyUI, 因此我单独拿出来介绍

这个平台大家也熟悉, 名为LIBLIB

image-20240603205851493

点击进入LibLIb

由于ComfyUI太过强大, 未来势必是它的天下, 因此 强烈大家把ComfyUI用起来

本文为作者原创 转载时请注明出处 谢谢

ACG作坊– 分享动漫制作的博客站

12…9

动漫作坊

83 日志
33 标签
RSS
© 2026 动漫作坊
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%