Files
rui40000-RUI-Nodes/example_workflow/八方向行走动画.json
T
rui40000andClaude Opus 4.8 28fd14aaad feat: 新增八方向序列拆分节点(8方向行走动画管线)
把每帧排布着 8 个朝向的雪碧图序列,一次拆成 8 条独立动画序列,
完成方向编号与分组。整条管线只有这一环缺失,其余复用现成实现:
视频转帧用 VHS 的 force_rate,透明 webm 用 VHS 的
format=video/webm + pix_fmt=yuva420p,序列帧用 SaveImage
(4 通道输入 PIL 会自动存成 RGBA)。

三个关键设计:
- 用固定网格而非连通区域拆分:连通区域按包围盒排序,角色走动时
  位置浮动,跨过排序行界方向就错乱,上百帧错一帧整条动画就废;
  且各自 bbox 裁剪导致尺寸不一无法合成视频
- 白底转透明用边缘连通性判断:角色常穿白衣服,按亮度一刀切会把
  白衬衫掏空,故只把与画面边缘相连的白色判为背景
- 裁剪框取全序列并集:逐帧各自裁剪会尺寸不一且角色帧间跳动

另加碎片清理(按连通块面积比):网格切分会把相邻格探进来的手杖尖
切入本格,既难看又撑大裁剪范围。实测 E 方向 172x370 -> 150x335,
跨帧重心极差 8.0 -> 0.5 px。

实测 97 帧 834x1112 素材:拆分约 4 秒,导出 webm 回读透明像素占比
0.635 与素材一致。注意验证 webm 透明需显式 -c:v libvpx-vp9 解码,
内置 vp9 解码器不处理 alpha 边带,否则会误判为透明丢失。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 18:48:50 +08:00

1086 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"id": "5f8c3d21-7b94-4e06-a1d8-9c2e4b7f350a",
"revision": 0,
"last_node_id": 19,
"last_link_id": 17,
"nodes": [
{
"id": 1,
"type": "VHS_LoadVideo",
"pos": [
-2260,
340
],
"size": [
340,
480
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1
]
},
{
"name": "frame_count",
"type": "INT",
"links": []
},
{
"name": "audio",
"type": "AUDIO",
"links": []
},
{
"name": "video_info",
"type": "VHS_VIDEOINFO",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_LoadVideo"
},
"widgets_values": {
"video": "八方向行走.mp4",
"force_rate": 24,
"custom_width": 0,
"custom_height": 0,
"frame_load_cap": 0,
"skip_first_frames": 0,
"select_every_nth": 1
},
"color": "#232",
"bgcolor": "#353",
"title": "① 加载视频(force_rate 设帧率)"
},
{
"id": 2,
"type": "RuiEightDirSplit",
"pos": [
-1850,
340
],
"size": [
360,
320
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 1
}
],
"outputs": [
{
"name": "dir_1",
"type": "IMAGE",
"links": [
2,
3
]
},
{
"name": "dir_2",
"type": "IMAGE",
"links": [
4,
5
]
},
{
"name": "dir_3",
"type": "IMAGE",
"links": [
6,
7
]
},
{
"name": "dir_4",
"type": "IMAGE",
"links": [
8,
9
]
},
{
"name": "dir_5",
"type": "IMAGE",
"links": [
10,
11
]
},
{
"name": "dir_6",
"type": "IMAGE",
"links": [
12,
13
]
},
{
"name": "dir_7",
"type": "IMAGE",
"links": [
14,
15
]
},
{
"name": "dir_8",
"type": "IMAGE",
"links": [
16,
17
]
},
{
"name": "info",
"type": "STRING",
"links": []
}
],
"properties": {
"aux_id": "rui40000/Ruinode",
"Node name for S&R": "RuiEightDirSplit"
},
"widgets_values": [
3,
3,
"4",
"SW,S,SE,W,E,NW,N,NE",
"白底转透明(推荐)",
0.92,
1.0,
0.05,
true,
8
],
"color": "#232",
"bgcolor": "#353",
"title": "② 八方向拆分(拆分+编号+分组)"
},
{
"id": 3,
"type": "VHS_VideoCombine",
"pos": [
-1380,
-160
],
"size": [
320,
460
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/1_SW",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"color": "#232",
"bgcolor": "#353",
"title": "1. SW → 透明 webm"
},
{
"id": 4,
"type": "SaveImage",
"pos": [
-1020,
-160
],
"size": [
300,
300
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 3
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/1_SW/SW"
],
"title": "1. SW → 透明 PNG 序列帧"
},
{
"id": 5,
"type": "VHS_VideoCombine",
"pos": [
-1380,
170
],
"size": [
320,
460
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/2_S",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "2. S → 透明 webm"
},
{
"id": 6,
"type": "SaveImage",
"pos": [
-1020,
170
],
"size": [
300,
300
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 5
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/2_S/S"
],
"title": "2. S → 透明 PNG 序列帧"
},
{
"id": 7,
"type": "VHS_VideoCombine",
"pos": [
-1380,
500
],
"size": [
320,
460
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 6
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/3_SE",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "3. SE → 透明 webm"
},
{
"id": 8,
"type": "SaveImage",
"pos": [
-1020,
500
],
"size": [
300,
300
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 7
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/3_SE/SE"
],
"title": "3. SE → 透明 PNG 序列帧"
},
{
"id": 9,
"type": "VHS_VideoCombine",
"pos": [
-1380,
830
],
"size": [
320,
460
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 8
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/4_W",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "4. W → 透明 webm"
},
{
"id": 10,
"type": "SaveImage",
"pos": [
-1020,
830
],
"size": [
300,
300
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/4_W/W"
],
"title": "4. W → 透明 PNG 序列帧"
},
{
"id": 11,
"type": "VHS_VideoCombine",
"pos": [
-1380,
1160
],
"size": [
320,
460
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 10
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/5_E",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "5. E → 透明 webm"
},
{
"id": 12,
"type": "SaveImage",
"pos": [
-1020,
1160
],
"size": [
300,
300
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 11
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/5_E/E"
],
"title": "5. E → 透明 PNG 序列帧"
},
{
"id": 13,
"type": "VHS_VideoCombine",
"pos": [
-1380,
1490
],
"size": [
320,
460
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 12
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/6_NW",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "6. NW → 透明 webm"
},
{
"id": 14,
"type": "SaveImage",
"pos": [
-1020,
1490
],
"size": [
300,
300
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 13
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/6_NW/NW"
],
"title": "6. NW → 透明 PNG 序列帧"
},
{
"id": 15,
"type": "VHS_VideoCombine",
"pos": [
-1380,
1820
],
"size": [
320,
460
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 14
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/7_N",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "7. N → 透明 webm"
},
{
"id": 16,
"type": "SaveImage",
"pos": [
-1020,
1820
],
"size": [
300,
300
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 15
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/7_N/N"
],
"title": "7. N → 透明 PNG 序列帧"
},
{
"id": 17,
"type": "VHS_VideoCombine",
"pos": [
-1380,
2150
],
"size": [
320,
460
],
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 16
},
{
"name": "audio",
"type": "AUDIO",
"link": null
},
{
"name": "meta_batch",
"type": "VHS_BatchManager",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": null
}
],
"outputs": [
{
"name": "Filenames",
"type": "VHS_FILENAMES",
"links": []
}
],
"properties": {
"Node name for S&R": "VHS_VideoCombine"
},
"widgets_values": {
"frame_rate": 24,
"loop_count": 0,
"filename_prefix": "8dir/8_NE",
"format": "video/webm",
"pix_fmt": "yuva420p",
"crf": 20,
"save_metadata": true,
"trim_to_audio": false,
"pingpong": false,
"save_output": true
},
"title": "8. NE → 透明 webm"
},
{
"id": 18,
"type": "SaveImage",
"pos": [
-1020,
2150
],
"size": [
300,
300
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 17
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.49",
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"8dir_frames/8_NE/NE"
],
"title": "8. NE → 透明 PNG 序列帧"
},
{
"id": 19,
"type": "Note",
"pos": [
-2260,
880
],
"size": [
700,
900
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"八方向行走动画 —— 完整管线\n\n【前置步骤(在本工作流之外完成)】\n 角色图 →(GPTimage2)→ 八方向静态图 →(Seedance 首尾帧)→ 循环行走视频\n 视频每一帧上都排布着 8 个朝向的角色。\n\n【本工作流做的事】\n ① VHS「Load Video」把视频转成序列帧\n · force_rate 就是帧率选择。设 0 用源视频帧率;\n 设 12 可减半出帧、素材更小,走路动画通常 8~12 帧一循环就够\n · frame_load_cap 可限制总帧数,先小批量试参数很有用\n ② 本仓库「八方向序列拆分」:切格 + 编号 + 按方向分组,一次给出 8 条序列\n ③ 每个方向各接一个 VHS「Video Combine」出透明 webm\n ④ 每个方向各接一个 SaveImage 出透明 PNG 序列帧\n\n【拆分节点的关键参数】\n · grid_cols / grid_rows / empty_cells\n 3×3 中间留空即 empty_cells=4。序号行优先、从 0 开始。\n · direction_names\n 按「跳过空格后的先后顺序」命名,默认对应下面这个排布:\n SW S SE ← 行1:面向观众\n W E ← 行2:侧面\n NW N NE ← 行3:背对观众\n 与你的素材不符就直接改这行,名字只影响标注不影响画面。\n · bg_mode / bg_threshold\n 白底转透明。只把「与画面边缘相连」的白色判为背景,\n 所以角色身上的白衬衫、白高光不会被掏空。\n 背景没扣净就调低阈值,角色边缘被啃就调高。\n · auto_crop\n 裁掉多余空白。裁剪框取该方向所有帧的并集,\n 因此整条序列尺寸一致(能合成视频),角色也不会在帧间跳动。\n\n【为什么不用连通区域拆分(本仓库的「素材拆分」节点)】\n 那个节点按包围盒排序,角色走动时位置浮动,一旦跨过排序行界方向就会错乱;\n 且每个 sprite 按各自 bbox 裁剪、尺寸不一,无法直接合成视频。\n 固定网格没有这两个问题 —— 但它更适合单张静态合图,两者各有用途。\n\n【webm 透明的关键】\n Video Combine 的 format 必须选 video/webm,且 pix_fmt 选 yuva420p。\n 注意:alpha 存放在 WebM 的独立边带里,所以用 ffprobe 看主流会显示\n yuv420p,这是正常的。播放器/引擎(走 libvpx 解码)能正确读到透明通道;\n 若要用 ffmpeg 自行验证,需显式加 -c:v libvpx-vp9 解码,\n 否则内置 vp9 解码器不会输出 alpha,会让人误以为透明丢了。\n\n【实测(97 帧 834×1112 的素材)】\n 拆分耗时约 4 秒,8 个方向各 97 帧,尺寸 172×370 ~ 206×327。"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
1,
0,
2,
0,
"IMAGE"
],
[
2,
2,
0,
3,
0,
"IMAGE"
],
[
3,
2,
0,
4,
0,
"IMAGE"
],
[
4,
2,
1,
5,
0,
"IMAGE"
],
[
5,
2,
1,
6,
0,
"IMAGE"
],
[
6,
2,
2,
7,
0,
"IMAGE"
],
[
7,
2,
2,
8,
0,
"IMAGE"
],
[
8,
2,
3,
9,
0,
"IMAGE"
],
[
9,
2,
3,
10,
0,
"IMAGE"
],
[
10,
2,
4,
11,
0,
"IMAGE"
],
[
11,
2,
4,
12,
0,
"IMAGE"
],
[
12,
2,
5,
13,
0,
"IMAGE"
],
[
13,
2,
5,
14,
0,
"IMAGE"
],
[
14,
2,
6,
15,
0,
"IMAGE"
],
[
15,
2,
6,
16,
0,
"IMAGE"
],
[
16,
2,
7,
17,
0,
"IMAGE"
],
[
17,
2,
7,
18,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.55,
"offset": [
2400,
100
]
}
},
"version": 0.4
}