数字人 · combo workflow
OmniHuman 单次只能跑 60 秒。超了的音频要切几段分别跑、再拼回来。难点不在切,在拼完之后还能不能跟"没切"一样准。
combo workflow voiceover_digital_human_pip_subtitle_mai 里,数字人这一棒把整条配音喂给 OmniHuman v1.5。模型 720p 单次最多 60 秒,这条 case 的音频 71.58 秒,于是 skill 直接报 audio_too_long,任务失败。
animate.py:307 严格判 60s、不容 epsilon——是 OmniHuman 本身的天花板。要破只能切。8bc83ee0,result 写着 audio_too_long=71.58s。配音是 Tobatsu 内部生成的,不是 Mai 入参。task_type 和 workspace 路径——workspace 还在机器上。segment-*-final.mp3 + 一条合并的 final-audio.mp3(71.576s)+ 字符级时间戳。两段都中招:OmniHuman 输出的画面比声音长。话说完了,它自己续了一截没有声音的画面——而且不是定格,人还在动。
| 段 | 容器时长 | 画面流 | 声音流 | 多出的空转尾巴 |
|---|---|---|---|---|
| A | 53.800s | 53.800s | 53.000s | +0.800s |
| B | 18.760s | 18.760s | 18.531s | +0.229s |
它带来三件麻烦:
尾巴是纯尾部、又没声音,所以按"这段声音多长"一刀切到那里,不会动到任何对口型的内容。一招同时治好时长和接缝。
| 不裁尾 | 裁到声音末 | |
|---|---|---|
| 拼完总时长 | 72.583s(+1007ms) | 71.663s(+87ms) |
| 接缝 | 空转 0.8s 卡顿 | 说完即切,连贯 |
skill 不动、agent 不动。逻辑加在编排层 mai-combo-runner 的 run_digital_human()——音频超 58s 才走分段,≤58s 老路完全不变。产物还是单个 digital-human.mp4,下游 PIP/字幕完全无感。
def run_digital_human(payload, audio_url):
D = probe_dur(audio_url)
if D <= 58.0:
return _omnihuman_once(audio_url, payload) # ≤60s 老路,零改动
cuts = pick_cuts_from_segment_boundaries(58.0) # 切点落段间停顿
chunks = split_audio_zero_drift(audio, cuts) # 按段分组 / PCM,0 漂移
parts = []
for ch in chunks:
m = _omnihuman_once(ch.url, payload) # 同 skill,各 ≤60s
parts.append(trim_video_to(m.video, ch.dur)) # ★ 裁掉无声尾巴
concat(parts, OUTPUTS/"digital-human/digital-human.mp4")
* split / trim / concat 用已声明的 ffmpeg-media-tools,不加新依赖。
同一张主播图、同一句 prompt。裁过的明显比没裁的连贯;Hedra 是"整条单次跑"的参照(单独提供,见说明)。
| 问题 | 建议 |
|---|---|
| 安全上限 | 58s(给 strict 60 留 2s 垫) |
| 切点来源 | 配音段边界(现成、干净) |
| 音频切法 | 按段文件分组(段还在盘上,0 漂移) |
| 裁到哪 | 该段配音的时长(下游时间轴对齐) |
| 接缝处理 | 硬拼(姿态重置,已确认可接受) |
omnihuman-over60s-handoff.zip(handoff MD + 证据 JSON + 复现脚本),改 combo 的人可直接照着改 run_combo_workflow.py:868。