Extensions/H3 Latent Relay
ComfyUI Extension

H3 Latent Relay

Latent bridge for MiniMax-H3 multi-segment continuation: the previous segment's AV latent is spliced into this segment's conditioning — zero re-encoding.

By ZenHG·Created about a month ago·Updated 2 days ago· 36
ZenHG/ComfyUI-H3-Latent-Relay
Nodes9
On cloudLocal install
CategoryH3 Latent Relay
Stars36
Updated2 days ago
Readme

ComfyUI-H3-Latent-Relay

🌐 中文 · English (condensed)

MiniMax-H3 多段续接的 latent 桥(零重编码)—— 一个可独立使用、零第三方节点包依赖的 ComfyUI 节点包。 只依赖 ComfyUI 自带的 torch 与 safetensors,不与任何第三方 H3 节点包耦合。

| 项 | 值 | |---|---| | 版本 | 0.6.37 | | 许可 | MIT(第三方出处见 THIRD-PARTY-NOTICES.md) | | 宿主 | ComfyUI ≥ 0.35.0,且带 MiniMax-H3 支持(宿主自身为 GPL-3.0,见 §许可与出处) |

🔴 一条铁律:每个功能都有两条用法 —— 画布手动(多数用户)与 API 提交图 JSON,二者必须走同一套节点实现。 只写在脚本里的功能不算本包的功能(判据见 CONTRIBUTING.md)。

它解决什么:H3 分段生成时,"续接"要回答"新的一段怎么知道上一段结束在什么状态"。 主流做法是像素续接(解码上一段 mp4 → VAE 重编码 → 当 anchor),代价是多一次 VAE 往返(有损)、 anchor 与采样 latent 不同源(会漂)、只能锚第 0 帧。本包直接从上一段的 AV latent 里切尾段、 按真实位置写进 minimax_keyframes:不重编码,也不在磁盘上缓存解码帧(每段只在 output/relay_kit/<run_id>/ 多一份约 12 MB 的 latent 边车)。原理与实测见 docs/01。


核心功能

8 个节点分三层 —— 一个最小工作流只用得到前 3 个:

| 层 | 节点 | 说明 | |---|---|---| | 必备 3 | Latent 存 · 拷贝桥(复合桥)· 裁重叠 | 少一个就不叫续接 | | 可选 5 | 后处理 Post · 音频缝 · 连跑 Chain · Latent Upscale · Seed from Video | 各自独立、默认全关 = 逐位直通(Seed from Video 只在「从已有视频起段」时用) | | 手动接线才用 1 | Latent 读 | 桥自己会从磁盘取源;只在显式换源时接 |

| 节点 | 作用 | |---|---| | 🔗 H3 Relay · Latent Save | 本段采样后把 AV latent 落盘到 output/relay_kit/<run_id>/stage_NNNNN.safetensors | | 🔗 H3 Relay · Latent Load | 读回上一段(段号 −1),断点续跑可指定 explicit_path 换源 | | 🔗 H3 Relay · Copy Bridge(复合桥) | 上一段尾 AV latent 逐位拷贝进本段初始 latent + 噪声掩码(钉住区不重绘);接 [16] conditioning 时并联钉帧(管取景)。mask_mode 默认 hard | | 🔗 H3 Relay · Trim AV | 裁掉本段头部的重生成帧(音画同裁)——不裁就会在拼接处重播/跳变;[3] 输出 prev_tail | | 🔗 H3 Relay · Post | 画质域:跨段统计匹配 / 低频残差 / 色调 / 反卷积 / 高频迁移 / 糊区锐化;0.6.34 起 settle_auto 默认 0.5(治重绘糊),其余默认关 | | 🔗 H3 Relay · Audio Seam | 音频域:把上一段环境声补进本段头部,长度守恒(零 A/V 位移);0.6.34 起默认开(patch_seconds 1.2,填 0 = 关) | | 🔍 H3 Relay · Latent Upscale | 画质域 · latent 层:拆 AV 打包 latent → 逐块过学习式 3D 放大器 → 回包。零去噪、时间维一帧不动、音频原样带回(需可选上游依赖,见 §安装) | | 🔗 H3 Relay · Chain | 自动连跑控制器:自动推进段号并排队;0.6.7 起还能换词(prompts 按 --- 分块)与拼片(🧩) | | 🔗 H3 Relay · Seed from Video | 入口:把一段已有视频编码成第 1 段(种子段)的边车,之后各段照常往外接。自动挡三处坑:时序栅格(5+17k 帧)、画布栅格(16 的倍数)、音画等长(裁尾/补零/静音种子) |

三个域别混挂:时间轴 = H3RelayTrimAV(冻结)/画质域 = H3RelayPost/音频域 = H3RelayAudioSeam。

节点标签自 0.6.18 起是纯英文;中文术语 ↔ 标签的对照表见 docs/04 §术语 ↔ 节点标签对照。


环境依赖

| 依赖 | 必需? | 说明 | |---|---|---| | 带 MiniMax-H3 支持的 ComfyUI ≥ 0.35.0 | ✅ | 需要 comfy_extras/nodes_minimax_h3.py 与消费 minimax_keyframes / minimax_refs 的 comfy/model_base.py。装在旧版上节点能注册但续接静默无效 | | torch | ✅ | 顶层 import(缺了整包注册失败),ComfyUI 自带 | | safetensors | ✅ | 延迟 import(缺了只在落盘那步报错),ComfyUI 自带 | | av >= 17 | 拼接时 | 「拼成一条」与 tools/concat_segments.py 需要(宿主自带)。缺模块会明确报错并给出安装指引;PyAV 太旧、拿不到流拷贝模板时会报出原因并退回重编码(报告里写明),不静默 | | 社区节点包 Comfyui_Minimax_h3_latent_Upscaler + 其权重 | 🔍 放大节点需要 | 见 §安装·可选 |

minimax_keyframes / minimax_refs / resolved_frame_index 全是 ComfyUI 原生协议: 零 monkey patch、零 patch 别人的包。


安装

cd ComfyUI/custom_nodes
git clone https://github.com/ZenHG/ComfyUI-H3-Latent-Relay.git

或下载 ZIP → 解压 → 文件夹改名为 ComfyUI-H3-Latent-Relay → 放进 custom_nodes/。

或 ComfyUI Manager → Custom Nodes Manager → 搜 h3-latent-relay (节点页:https://registry.comfy.org/nodes/h3-latent-relay)。包名与仓库名不同是官方要求: 包名不允许带 "ComfyUI"。

装完必须重启 ComfyUI 后端(ComfyUI-Manager 点 Restart;没装就重启 Python 进程)—— 仅刷新浏览器不会加载新节点。节点列表里搜 🔗 H3 Relay(8 个)+ 🔍 H3 Relay · Latent Upscale + 🧩 H3 Relay · Join AV + ⏩ H3 Relay · Finite Run 即为全部 11 个。

可选:用 🔍 H3 Relay · Latent Upscale 才需要装(不装不影响其他 7 个节点,用时会报一条写明仓库与权重地址的错):

cd ComfyUI/custom_nodes
git clone https://github.com/LBH-123-AI/Comfyui_Minimax_h3_latent_Upscaler.git   # 作者 LBH-123-AI(MIT)

再从 https://huggingface.co/LBH-123-AI/Minimax_h3_latent_upscaler 下权重放进 ComfyUI/models/latent_upscale_models/,重启后端。⚠️ 要的是 H3 latent 放大权重(24 通道专用), 不是 ESRGAN 那类像素放大模型。

节点 API 出口:V3(默认)/ V1(H3RELAY_NODE_API=v1 一行回退,改后需重启)。 两条出口的节点名、输入输出顺序、默认值全部相同(机检逐字段一致)⇒ 切换不需要改图。

为什么只能二选一:宿主加载顺序是 if 模块有 NODE_CLASS_MAPPINGS → return / elif 模块有 comfy_entrypoint ⇒ 两套一起导出时 V3 永不生效;所以 V3 模式下本包把 NODE_CLASS_MAPPINGS 显式置 None。 V3 的已知差异:schema 在包加载时构造(V1 是惰性的)⇒ 某个节点的参数表构造失败时只会 跳过那一个节点并打日志,其余照常加载(不会整包失效)。 下限依据(2026-10-02 由 git 历史反查重定):下限 = >=0.35.0,由本包最晚的硬依赖决定 —— 掩码硬锁需要宿主 mask_row_values(#15375,v0.34.0 起)且 forward 里把掩码行速度场按掩码缩放 (#15988,v0.35.0 起);其余依赖都更早(H3 支持本身 = v0.30.0)。已核实 v0.35.0 → 最新之间这两处未被改动。 元数据侧 web/ 挂载(WEB_DIRECTORY,2023 起)与 comfy_config 解析 pyproject.toml(2025-06 起) 在 v0.35.0 已具备 ⇒ 功能与元数据两个层面同时成立。 requires-python = ">=3.10" 是刻意保守(本包未使用 3.10+ 独有语法,真要放宽到 3.9 亦无语法障碍)。


使用方法

5 分钟跑通

懒人路线:打开 examples/minimal_relay_official.json(说明见 examples/README.md),把 4 个加载器的下拉改成你本机的模型文件。 想从一段已有视频起段:打开 examples/seed_from_video_ui.json(23 节点)。

第 1 段:① 「段号」填 0 → ② 在官方出词节点填 prompt → ③ 点 Queue。 (什么都不用做:段号 0 时桥自动直通。0.6.37 起 context_latent 留空也行——桥自己按 run_id 读上一段。)

第 2 段起:④ 「段号」改成 1(只改这一个数)→ ⑤ prompt 换成第 2 段内容(开头接住上一段的动作/机位,别重新起手)→ ⑥ 再点 Queue。 日志里应看到 钉住 22 帧 / 裁首 N 帧 = 钉住 22 + 沉降 0 / 起点干净。

| 关键参数 | 第 1 段 | 第 2 段起 | 填在哪 | |---|---|---|---| | stage_index(段号) | 0 | 1、2… | 读上段 latent + 桥 + 落盘(三处必须一样大;用 Chain 会自动同步) | | run_id | 同一个片子名,如 myfilm | 与第 1 段一字不差 | 六处(落盘 / 桥 / 读上段 latent / 裁重叠 / 音频缝 / 连跑);画布上只改一处,同组其余格自动跟随 | | context_frames | 22 | 22(不用动) | 桥(钉住窗,只认 5/22/39/56/73/90/107/124) | | settle_frames | —(首段不裁) | 保持 0 | 裁重叠(0 = 不裁沉降,默认即推荐) | | seam_ghost / settle_sharpen | — | 保持 0 | 裁重叠 / 后处理 Post |

| 最常见的三个翻车点 | 处置 | |---|---| | 报错「说清是第 N 段却拿不到上一段」 | 六处 run_id 必须一字不差(画布上改一处即自动同步同组其余格;不同步时见 docs/05);确认第 1 段跑过 | | 画面从第 1 帧就开始重播上一段 | 「Trim AV」没接上,或它的 trim_frames 没接桥的 [2] | | 换新片子却接了旧片尾巴 | 没换 run_id(同名会覆盖同段号文件) |

提示词里跟本包有关的两件事

本包不产出提示词、也不解析它 —— 桥与裁重叠只看张量。提示词里只有两件事会影响本包的行为:

① 声锚「该给谁」:判据在提示词

| 环节 | 靠什么 | |---|---| | 声锚的执行 | voice_anchor 这个输入(LoadAudio → VAEEncodeAudio 的音频 latent)—— 节点只吃张量,看不到提示词,所以它无法知道"这一段谁在说话、有几个人" | | 「锚该给谁」的判断 | 提示词:<Subject N> … <d>[台词]</d> 是唯一能机器读出「本段谁先开口」的地方 | | 段内后续换人 | 提示词里的 <Subject N>(模型自己切)—— 声锚管不到,锚窗只有缝区 ~0.9 秒 |

一条命令给出判断:

python tools/voice_bank.py advise --bank <声库> --prompt-file 段N.md --prev-file 段N-1.md

规则:锚 = 本段第一个开口说话的人;同人续接不必接、缝上换人才必须接; 一段内 ≥2 人 ⇒ 锚只覆盖缝区,后面靠提示词 —— 细则见 docs/07 §声锚。

② 接缝处的台词时机 —— 三件必须按纪律写的事

| 纪律 | 一句话 | |---|---| | 段首缓冲 | 续接段开头要留出「裁头 + 余量」,别把台词压在第 0 帧 | | 台词安全时刻 | 台词不要贴着缝(缝区会被裁、被钉),按公式往后放 | | 末帧锚链 | 段 N 的末帧状态要在段 N+1 的出词里逐字复写,别只写"继续" |

机制、公式与实测见 docs/06。

最小接线

出词节点(官方 / 第三方)
  ├─ positive ─────────────────→ 桥 [16] conditioning
  └─ LATENT ──────────────────→ 桥 [0] latent
                                桥 [1] context_latent  ← 留空(自动读上一段)
                                桥 [2] context_frames  = 22
                                桥 [17] run_id / [18] stage_index
                                        │
          桥 [0] latent ───────────────┼──→ 采样器 latent_image   ★必须过桥
          桥 [3] conditioning ─────────┼──→ 采样器 positive
          桥 [2] trim_frames ──────────┼──→ 裁重叠 [1] trim_frames  ★必须接
                                        │
                                 采样器 → LATENT ──→ 🔗 H3 Relay · Latent Save [0]
                                                 └──→ VAEDecode / VAEDecodeAudio
                                                           ↓
                               🔗 H3 Relay · Trim AV [0] images ← IMAGE / [3] audio ← VAEDecodeAudio
                                     │
                               [1] audio(可选经 🔗 音频缝)──────→ CreateVideo → SaveVideo
                               [0] images ───────────────────────↗

🔴 音频线只有一条是对的:裁重叠 [1] audio(或再经 音频缝 [0] audio)→ CreateVideo.audio。 直接把 VAEDecodeAudio 接到 CreateVideo 会音画不同步 —— 画面裁了头部重叠帧、音频没裁, 每缝差 ~0.9 s 且逐段累积(第 3 段起口型明显对不上)。节点只能发现"输入没接", 发现不了"输出被悬空"。 只要某节点输出 CONDITIONING + LATENT,接法就一样 —— 把它替掉图里的出词节点即可。 产线现役的「一采 → 🔍 放大 → 二采 → 续接」8 节点全接法见 docs/03 §4.1。

多段拼接成一条

每段各出一个 mp4。Chain 节点上 auto_concat 默认开(连跑完自动拼)或随时点 🧩 拼成一条(画面流拷贝无损、 音频逐段去 priming 对齐、拼完自检四项);不开画布也能拼: python tools/concat_segments.py s1.mp4 s2.mp4 -o film.mp4(退出码 0 = 过四项断言)。 ⚠️ 别用 ffmpeg -f concat -c copy 或 acrossfade(实测会虚增时长 / 每缝偷 0.25 s 且累积)。 细节与音轨档位(AAC 256k 默认 / PCM 无损母版)见 docs/10 §7.1。

跨说话人音色:声锚(voice_anchor,0.6.19)

台词逐段换人时,桥会把上一段说话人的嗓音当成本段的生成条件 ⇒ 音色交叉污染 (实测同一角色 F0 漂 +15%、谱质心漂 +27%)。解法 = 给 Copy Bridge 的可选输入 voice_anchor 接上「本段说话人」的音频锚(LoadAudio → VAEEncodeAudio): 不接 = 逐位同旧版;接了 = 音色锁定且削顶消失(实测离基准距离缩到 1/5)。

🔵 两个示例图里声锚默认已经接好(examples/ 的 LoadAudio → VAEEncodeAudio → 桥的 voice_anchor)—— 你要做的只有一件事:把那个 LoadAudio 换成你自己的角色语音(≥0.9 秒干净语音,情绪与台词相近; 文件放进 ComfyUI/input/ 再在下拉里选)。用 tools/voice_bank.py 可以从已渲染的段里自动采集。

接错也不会出坏片 —— 四道兜底:

| 情形 | 行为 | |---|---| | 不想用 / 没准备好语音 | 把 voice_anchor 那根线拔掉 = 逐位同旧版(图与参数都不用改,零迁移成本) | | 接了个没有内容的锚(全零 / 常量 / 静音 / 含 NaN) | 🔴 当场报错(0.6.20 起 fail-closed),报错文案点名原因并告诉你"想关就拔线"——绝不静默出坏片 | | 锚比窗口短 | 取锚全长 + report 提醒(建议 ≥0.9 秒) | | pin_audio=False | 声锚被忽略,report 里写明(降级可见) |

声锚的局限:

| 局限 | 说明 | |---|---| | 🔴 占用 1 个「音频参考」额度 | 官方参考节点 MiniMaxH3ReferenceToVideo 的 ref_audios 上限是 3 个;本包在 stage_index ≥ 1 时恒定占 1 个(声锚尾窗 或 上一段音频尾,二选一)。你把这 3 个填满 ⇒ 模型侧看到 4 个音频参考 —— 模型不报错(它对个数不做校验),但超出官方口径、我们未实测。⚠️ 本包这 1 个没有「完全不用」的开关(不接声锚时用的是上一段音频尾)⇒ 要腾额度只能自己减少参考节点的 ref_audio 槽位。画布上桥节点的标题会直接挂警告,report 里给权威数字。详见 docs/07 §音频参考额度 | | 锚替换整个音频尾窗 | 尾窗里的 BGM / 环境垫乐也会被换掉。有垫乐就把垫乐混进声锚再接 | | 音色 / 语速 / 情绪会被部分钉住 | 锚不是「只偷音色」—— 情绪与节奏也会跟随。请选与目标台词情绪相近的干净片段 | | 缝区只锚第一个,参考窗可含多人(0.6.27) | 缝区钉住的那一段(= 视频钉住窗 context_frames,默认 22 帧 ⇒ 0.925 秒;可调 5/22/39/…/124)恒只覆盖「缝上接着说的那个人」;而 audio_ref 参考窗 0.6.27 起 0 = 按声锚自身自动定长(取满上限 ≤6 秒)⇒ 接一段多人录音当锚,参考里就含多个说话人的音色。想「指名某人」用官方 ref_audios 槽 | | 自动参考窗理论上可能触发复述 | 0.6.25 起不接声锚时窗长自动(2~6 秒 —— 官方建议 2~12 秒,本包下限对齐官方 2 s、上限故意取 6 s:参考行随每步采样,更长更吃显存也更慢;比旧口径 0.93 秒长)⇒ 窗里是上一段的台词,模型理论上可能复述。实测(窗 0.93→4.18 秒,4.4×)未观察到复述,但只覆盖一段素材一次生成 —— 台词密集的素材请自己听一遍;要绝对稳妥就填 audio_ref_seconds(0.93 = 旧行为) | | 🟣 其余人的参考要走官方槽 | 本包只锚缝区那 1 个。要让其余说话人也有声音参考,把他们的音频接官方 ref_audios 槽 —— 只有官方那条路会生成文本标签 <Audio j>,prompt 才引用得到(本包注入的那 1 个是事后追加到 DiT 侧的,没有标签、prompt 引不到它,report 会写明)。先接官方 TrimAudioDuration 裁到 ~0.9 s(参考行随每一步采样,长参考=慢)。⚠️ 编号按「已接线顺序」数,不是槽号:只接了 ref_audio_2 时它是 <Audio 1>,照槽号写 <Audio 3> 会指空且不报错(槽位跳号时桥节点标题会直接写出实际编号) | | 声锚时长 | 尾窗要 0.925 秒 ⇒ 锚 ≥0.9 秒(短了按全长取用并在 report 提醒);建议 1–4 秒 | | 🔴 锚的尾部有停顿 ⇒ 音色条件打折 | 消费端 _voice_anchor_tail 只取锚的尾部 0.925 秒(a_frames/FPS)⇒ 锚的尾部静音等于那部分没锚(模型只拿到静音)。voice_bank.py 采集时会择优让尾部全程有声,采不到就在 report 与 voices.json 的 tail_clean 里点名「音色条件会打折」 —— 遇到这条提示说明素材连续人声太短,换更长更干净的单人素材 | | 🔴 提高锚的采样率不提高音色上限 | 锚最终喂给 H3 音频 VAE(原生 32 kHz)。提高采样率只是如实保存已有频谱,不会凭空补出高频 —— 实测把锚从 16 k 提到 32 k,−40dB 带宽从 4.7~5.3 kHz 抬到 9.0~10.5 kHz,但再往上走受音源限制:H3 自己吐的音轨有效带宽只有 ~4.9 kHz(即便 H3 能产出 15 kHz 的,那也是音效/环境声不是人声)。⇒ 要更好的音色必须换音源(真人 / 原始录音),不是换采样率 | | pin_audio=False 时被忽略 | report 里写明(降级可见,不静默) |

⚠️ 一个常见的错接:别把 context_latent 接到 voice_anchor —— 那是「上一段的 AV latent」, 拿它当锚等于把锚源设回默认的「上一段音频尾」⇒ 看着像开了,其实是空转(还会绕过音频栅格对账)。 锚源必须是本段说话人的语音。

🔵 一段里有多个人说话怎么办:锚回答的问题只有一个 —— 「缝上接着说的那个人是谁」。 ⇒ 锚 = 本段第一个开口说话的人;同人续接不必接、缝上换人才必须接; 段内后续换人由 prompt 的 <Subject N> 决定(锚管不到)。

📋 群像戏(跑很多段、角色很多、每段重合度都低)的完整决策表 + 额度账本 + 降级路径见 docs/07 §群像戏 —— 一张表回答「本段有 N 个人说话 ⇒ 锚怎么接、额度怎么分」。

其余人的声音参考:走官方槽 + prompt 里的 <Audio j>(有格式要求)

本包的声锚只覆盖缝区那 1 个,而且它没有文本标签(事后追加到 DiT 侧,tokenize 早已发生) ⇒ prompt 引不到它。要让其余说话人各自有声音参考,只能走官方参考节点那条路 —— 三步:

  1. 把该说话人的音频接 TrimAudioDuration 裁到 ~0.9 秒(参考行随每一步采样,长参考 = 慢)。
  2. 接到官方 MiniMaxH3ReferenceToVideo 的 ref_audios.ref_audio_N 槽(别接本包的桥节点)。
  3. 在 prompt 里用 <Audio j> 引用它 —— 标签由官方在 tokenize 那一刻按已接线顺序发出、1-based。

🔴 三条格式要求(写错不会报错,只是不生效 / 指空):

| # | 要求 | 说明 | |---|---|---| | 1 | 编号 = 第 j 个「已接线」的槽(按槽号升序),不是槽号 | 见下表。槽位跳号时,桥节点标题会直接写出实际编号(不跳号时闭嘴) | | 2 | 本包追加的那 1 个(声锚 / 上一段音频尾)没有标签 | ⇒ prompt 引不到它;report 会写明它在 DiT 侧的序号 = 官方数 + 1 | | 3 | 位置:与 <Picture i> / <Video k> 同一套标签(官方:Use the same tags when prompting) | 放在对应 <Subject N> 的定义 / 台词描述处最直接 |

编号对照(最容易踩、且完全不报错的一脚):

| 你实际接的槽 | prompt 该写 | 写错的后果 | |---|---|---| | ref_audio_0 + ref_audio_1 + ref_audio_2 | <Audio 1> / <Audio 2> / <Audio 3> | 编号与槽号一致,没问题 | | 只接 ref_audio_2 | <Audio 1> | 写 <Audio 3> ⇒ 指空,任何一层都不报错 | | 接 ref_audio_0 + ref_audio_2 | <Audio 1> / <Audio 2> | 同上 |

⚠️ 要求 1、2 是读源码确证的(tokenizer 按枚举顺序发标签、本包走 conditioning_set_values 追加); 要求 3 的「措辞怎么写在效果上最好」我们还没做 A/B 实测 —— 编号与「引不到」两条可以直接依赖。 原理与源码出处:docs/07-chain.md §一段内多人。

想要「段 2 复刻所有人的音色」?一个槽就够(0.6.25;0.6.27 起声锚也支持)

上面那条路(官方槽 + <Audio j>)是「指定谁是谁」。如果你要的是「不指定、一整个音色都带过来」 (多锚,只占一个音频参考槽)—— 用 audio_ref_seconds:

| | 旧口径 | 0.6.25 起 | |---|---|---| | 那个参考槽里装多长 | = 视频钉住窗(22 帧 ⇒ 0.93 秒,只装得下最后一个人) | 自动 2~6 秒(从上一段尾部往前累计够 2 秒有声;官方建议 2~12 秒,本包上限故意取 6 s 省显存与速度) | | 额度占用 | 1 个 | 仍是 1 个(不变) |

  • 不用手填秒数:0 = 自动,素材自己说话(min_voiced_s=2 秒有声 / 上限 6 秒)。
  • 不接声锚 ⇒ 原料 = 上一段音频尾(「上一段里所有人」那条路)。
  • 🔵 接了声锚也行(0.6.27) ⇒ 原料 = 声锚自身,0 = 自动按声锚的有声尾巴定长(取满上限 ≤6 秒 —— 单人参考越长、音色保持越好) ⇒ 把你自己的多人录音接进声锚就能「一带多人」,不必依赖上一段有谁。缝区钉住的那 0.925 秒不受影响。
  • ⚠️ 代价:不接声锚时窗里就是上一段的台词 ⇒ 模型理论上可能复述。实测未观察到(4.4× 窗长下 ASR 无 seg1 内容),但只一段素材一次生成。 (接声锚时窗里是该说话人自己的语音 ⇒ 复述风险远低于上一段台词。) 想要"可被 prompt 指名到某个人"仍然只能用上面的官方槽那条路。
  • 两条通道互补不冲突:要「一带多人」用本项;要「指名某人」用官方槽;要「音色钉死 + 管住缝区」用声锚。

原理、三条通道的分工与实测数字:docs/07-chain.md §一段内多人 ⑥。

实现细节与更多说明(音频参考额度、BGM、情绪、一段多人)详见 docs/07-chain.md §声锚;配套自动采集器 tools/voice_bank.py (可选 ASR 台词守卫,没装 funasr 也能用)。

节点中英切换(0.6.28,画布按钮)

每个本包节点上多一个「中 / EN」按钮:点一下,本包 8 个节点的标题、参数标签、 端口名、鼠标悬浮提示一起切成中文 / 英文(下拉框显示中文、存档值不变)。

  • 默认跟随官方语言设置(设置 → Language,即 Comfy.Locale)⇒ 界面是英文就别把节点翻成中文; 点按钮才覆盖,覆盖后官方再切语言不冲掉你的选择(记在浏览器本地)。 单向:官方改语言 ⇒ 节点跟着变;我们的按钮只改本包节点,不改官方语言设置 (改官方会触发官方重载当前工作流,代价不可接受)。

  • 🔴 只改显示,绝不改图:只改 label / title / widget.tooltip, 从不改 name / 下拉框的值 ⇒ 工作流 JSON 一个字节都不变。你自己改过的节点标题原样保留。

  • 悬浮提示已精简为「这参数干什么 + 推荐填什么」(最长 1084 → 150 字符;历史沿革留在 docs/); 词表里没有的参数保留后端原文,不会被改坏。

  • 与「音频参考额度」提示共存:标题只换名字那一段,追加在后面的额度提示不会被抹掉。

  • 🔴 与界面汉化插件(ComfyUI-Chinese-Translation / Global Translation 等)会抢字段 —— 本包已做帧级防覆盖兜底,实测抢 344 次一次没抢成(正常显示)。机理:那类插件有一个 每 1 秒的守护轮询,会按它自己的词典把节点标签/提示改回中文(它源码注释原话: 「兜底补刷被第三方扩展覆盖的节点槽位标签」),且不对英文界面做任何屏蔽。 本包的对策 = 写值时记下期望值、每帧比对写回(帧率远高于它的 1 Hz)⇒ 显示归我们。

  • 🔴 UI-only:脚本提交 JSON(/prompt)不经过画布 ⇒ 拿不到这个按钮(不影响任何功能)。

  • 🔴 升级后必须刷新浏览器页面(F5)。重启 ComfyUI 进程不算:前端是单页应用, 页面打开后扩展 JS 就常驻内存,不刷新 = 还在跑旧版。已有人因此误报"提示还是中文" (磁盘上是新版、浏览器里是旧版)。

  • 💡 自己改代码做本地验证时:本机可能装了不止一个 ComfyUI(例如独立版 + 桌面版)。 改完先确认 8188 到底在服务哪一份,再谈"改了没生效":

    curl -s http://127.0.0.1:8188/extensions/ComfyUI-H3-Latent-Relay/relay_kit_i18n_ui.js \
      | grep -c '<新代码里才有的标识符>'     # 0 ⇒ 服务端还是旧副本
    netstat -ano | grep ':8188.*LISTENING'  # 拿 PID ⇒ 再查该进程的 exe 路径,看是哪个安装
    

    「我把文件同步过去了」≠「宿主读的是我同步的那份」。

细节与词表: docs/04 §节点中英切换。


目录结构

ComfyUI-H3-Latent-Relay/
├── __init__.py            # 包入口:注册 /h3relay/concat 路由、选 V1/V3 出口、启动横幅
├── relay_core/            # 业务内核(唯一真相源,11 文件包):时序网格 / 拷贝桥 / 裁重叠 / 音频缝 / 拼接
├── nodes.py               # 节点层(8 个节点的输入输出与人话报告),直接 import relay_core
├── layout_contract.py     # 布局契约:找不到上游时放行 + 留痕,只在真的不一致时 raise
├── v3/                    # V3 外壳(io.ComfyNode + comfy_entrypoint);V1 走 NODE_CLASS_MAPPINGS
├── exp/history_anchor_v2/ # E1' 时不变历史锚(顶层 import ⇒ 必需;无 _tiha.json 即不生效)
├── web/                   # 前端 JS:🧩 拼接按钮、Chain 面板、`run_id` 一处改全组、**节点中英切换按钮**(画布用;脚本用户见 docs/10 §7.4)
├── examples/              # 三个可直接打开的工作流:最小续接(21)· 全流程(47)· 视频起段(23)
├── docs/                  # 深度文档 01–10(原理 / 参数 / 采样链 / 画布 / 排障 / 脚本 / Chain / 测试 / 观测 / 音频)
├── tests/                 # 离线自测(零 GPU):632 项断言 + V3 逐字段 + 词分发纯函数
├── tools/                 # 自检 / 取证 / 拼接 CLI / 打包器(含英文文档同步闸 en_sync.py)
├── licenses/              # 随包分发的第三方许可全文
├── dist/                  # 打包器的产出(最小分发集 + zip,不入库)
├── pyproject.toml         # 元数据(宿主真读 requires-comfyui/依赖;Comfy Registry 读 name/Icon/PublisherId)
├── .comfyignore           # `comfy node publish` 打什么包(不写 ⇒ 把 docs/tests/tools 一起推给用户)
├── icon.png / icon.svg    # Registry / Manager 卡片图标(400×400;`.svg` 是源,`.png` 是发布物)
├── banner.png / banner.svg # Registry 节点页横幅(21:9 = 1680×720;同样 svg 是源)
├── requirements.txt       # 供 ComfyUI-Manager 安装依赖
└── (顶层还有 README_EN.md · CHANGES.md · CONTRIBUTING.md · AGENTS.md · SECURITY.md ·
      CODE_OF_CONDUCT.md · THIRD-PARTY-NOTICES.md · LICENSE · .github/)

安装后运行期真正会被加载的只有 __init__.py · relay_core/(11 文件)· nodes.py · layout_contract.py · v3/(4 文件)· exp/__init__.py 与 exp/history_anchor_v2/(3 文件)· exp/voice_accum/(3 文件) (都被 nodes.py 顶层 import,所以必需)· web/(7 个 JS) —— dist/ 里的最小分发集就是这些 + 示例 + 元数据;docs/ tests/ tools/ 都是开发件。


配置与注意事项

| # | 硬纪律 | |---|---| | 1 | run_id 六处必须一字不差(落盘 / 桥 / 读上段 latent / 裁重叠 / 音频缝 / 连跑)。画布上只改一处,同组的其余格会自动跟随(名字冲突时会拦下连跑并列清单,不猜);脚本提交 JSON 请把这一格写成同一个变量(见 docs/10 §7.4)。段号顺序跑、别跳段 | | 2 | 第 1 段什么都不用做:桥会自动直通;0.6.37 起 context_latent 留空也行(桥自取上一段),接了则以接线为准 | | 3 | 🔴 音频必须走裁重叠(或音频缝)的输出,不能把 VAEDecodeAudio 直连落盘节点(否则每缝差 ~0.9 s 且累积) | | 4 | context_frames 只认 5+17k(5/22/39/56/73/90/107/124),须小于本段帧数;越界直接报错不吸附 | | 5 | mask_mode 生产档 = hard(钉住区零重绘);taper 不钉住,仅对照实验用 | | 6 | settle_frames 保持 0:实测「裁沉降」才是缝处跳帧的源头;要治糊用 Post 的 settle_sharpen/settle_auto(画质域修复,不碰时间轴 ⇒ 原理上不会引入跳帧) | | 7 | patch_seconds ≤ 1.2(音频缝):盖住段首 10 ms 解码静默已足够;台词贴头的素材用 0 关掉。台词安全的主防线是缩短 patch,守卫只是兜底(守卫对"整段人声铺底"的素材原理性无效) | | 8 | 🔍 放大在一条链里只做一次**:本包放大节点与第三方渐进采样器(SelfLift 等)内置的那次**是同一件事**,都开 = 同一段 latent 被放大两次 | | 9 | **chunks=1 才是与上游整段推理一致的唯一路径**:chunks>1 会**改画面**(3D 体积注意力被切断),只在 OOM 时升,升完**必须重看缝** | | 10 | 🔴 **续接契约取原生域**:Latent 存接在放大**之前**;**二采的 guider 不接桥的conditioning**(网格不同 ⇒ 当场炸) | | 11 | diagnostics 默认关(三路纯打印、**不参与裁量**);想看 DTW / 裁量→跳跃曲线 / 外观漂移就打开它。⚠️ 网上旧文里的「沉降 1」是 0.5.0 前口径 —— **以本页与节点报告为准** | | 12 | 🔴 **每个功能的画布路与脚本路必须是同一套节点实现**(见文首铁律);纯前端能力(🧩 按钮、Chain 面板格、**run_id 一处改全组同步**、**节点中英切换按钮**、**stage_index一处推进全组**、**被旁路的「读上段 latent」自动恢复启用**、**音频参考额度角标**)脚本提交 JSON 时**会被忽略**。⚠️ 后三条**不是死角**,各有非 UI 替代路:段号**自己逐节点写同一个值**(Chain 的stage_index tooltip 与 [docs/10](docs/10-audio-seam-and-concat.md) §7.4 都写了);「读上段 latent」**别旁路**(JSON 里 mode保持0,Chain 才不会需要替你恢复);额度**看桥的 report` 输出(与角标是同一份数字)。其余走 §7.4 的三条非 UI 路径 |


许可与出处

  • 本包 = MIT(LICENSE):可商用、可修改、可再发布,保留版权声明即可。
  • 第三方出处与署名见 THIRD-PARTY-NOTICES.md(含"运行时宿主 ComfyUI 是 GPL-3.0"的 事实与立场)。
  • ⚠️ 关于 H3RelayMotionContext(GPL-3.0):其锚位合成段在 v0.2.1–v0.5.0 的公开历史里曾与该包构成 表达层重合,已于 2026-09-19 整体重写,当前版本不含其派生表达;历史提交未改写、可检出复核 (NOTICES §一·B)⇒ 严格合规请使用 ≥ 0.6.0。
  • 模型权重不含在本包内:MiniMax-H3 等权重需自备,许可与商用条件由提供方决定。
  • 使用合规:本包是通用视频生成工具,使用者须遵守当地法律与所用模型/素材许可,不得用于伪造肖像、 传播虚假信息或侵犯他人权利。MIT 不含任何用途担保。
  • 本包非 MiniMax 官方作品,与 MiniMax、ComfyUI 官方均无隶属或背书关系。
  • 再发布时请一并保留 LICENSE · THIRD-PARTY-NOTICES.md · licenses/。

文档索引

中文文档是唯一真相源;README_EN.md 与 docs/01–10 的英文版是派生物,由 tools/en_sync.py 按节机检同步(改了中文必须同步英文,否则门槛红)。

| 文档 | 内容 | |---|---| | docs/01-mechanism.md · 🇬🇧 _EN | 两条续接路线的历史与取舍 · 协议出处 · 放大之后哪条线留在原生域 · 时序网格 · 为什么必须裁头 · 运行时契约 | | docs/02-parameters.md · 🇬🇧 _EN | 参数全量手册(桥 / 裁重叠 / Post / 音频缝 / 🔍 放大,含每个 advanced 项与默认值) | | docs/03-sampling-and-design.md · 🇬🇧 _EN | 采样链取舍(该量的不让用户配)· §4.1 全流程档:8 节点全在场 | | docs/04-canvas-and-widgets.md · 🇬🇧 _EN | 画布外观 · advanced 折叠 · 旧图看不到 prev_tail 的处理 | | docs/05-troubleshooting.md · 🇬🇧 _EN | 完整排障表 · 常见疑问 FAQ · 工作流文件自检 · API 提交 | | docs/06-continuity-scripting.md · 🇬🇧 _EN | 出词纪律:段首缓冲 · 台词安全时刻 · 末帧锚链 · 音频缝配套 | | docs/07-chain.md · 🇬🇧 _EN | Chain 自动连跑(换词 / 拼片 / 断点续跑) | | docs/08-testing.md · 🇬🇧 _EN | 离线自测:36 组断言明细(632 项)· 工具清单 | | docs/09-metrics.md · 🇬🇧 _EN | 观测量参考区间(DTW 残留 / 外观漂移)· 怎么自校准 | | docs/10-audio-seam-and-concat.md · 🇬🇧 _EN | 音频缝与多段拼接:缝的口径与调参指南 · 台词保护 · 判据能力边界 · 音轨档位 · 边车 · 不开画布的脚本用法 | | RELEASE-NOTES.md | 面向用户的发布说明(中英双语,一版一节)· 升级前先看这个 | | CHANGES.md | 版本史与每次实测证据(开发者记录,细节更全) | | CONTRIBUTING.md | 开发环境 · 一次改动的闭环 · 自测纪律 · 英文文档同步纪律 · 许可条款 | | AGENTS.md | 给改这个仓库的 agent 的作业入口:一次改动的闭环 · 三档检查(本地 / CI / 宿主+浏览器)· 铁律↔执行体映射表 · 全部机检一览 · 禁区 · 已知薄弱点 | | RELEASING.md | 发布规范(唯一真相源):GitHub + Comfy Registry 两个渠道必须成对、一条命令 tools/release.py、失败处置、密钥纪律 | | SECURITY.md | 密钥 / 依赖 / 网络行为声明 | | tools/README.md | 20 个脚本的用途与期望值(15 个自检/取证 + 拼接 CLI + 打包器 + 发布器 + 声库采集器 + 浏览器冒烟门) | | examples/README.md | 三份可直接打开的工作流(最小续接 21 节点 / 全流程 47 节点 / 视频起段 23 节点)· 生成器 | | README_EN.md | 英文精简版(安装 / 接线 / 节点 / 参数 / 排障 / FAQ)—— 深度推导一律外链本文件与 docs/ |