Hyperframes 嵌入字幕分组实战:如何把 Whisper 词级转写切分为 plan.json 视觉短语
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
本篇技术指南围绕 Hyperframes 内置embedded-captions技能中的核心编排环节——**字幕分组(Caption Grouping)**展开,讲解如何把 Whisper 生成的词级转写(transcript.json)转成plan.json中的groups[]数组,让每个字幕组成为"一个视觉短语":进场、逐词揭示、退场。你将掌握五类切分边界、组时间窗口的精确计算公式、样式与语气(style/tone)的选取规则,以及如何借助仓库内的fill-timings.cjs与check-timing.cjs脚本保证时间戳与转写严格对齐。
分组的目标:一组 = 一个视觉短语
在 Hyperframes 的embedded-captions工作流中,最终呈现在画面上的不是逐词飘过的字幕条,而是一组一组有排版身份的视觉短语。每个 group 拥有统一的入场动画、统一的字重字号、统一的语义角色(intro / phrase / emph / dream / crown),并在组内按词做卡拉 OK 式的逐词揭示。
判断分组是否合理的经验法则很简单:1 个 group ≈ 1 个逗号到逗号的小句(comma-to-comma clause),或 1 次呼吸的气口(breath of speech)。换句话说,分组依据的是语义和语调的自然停顿,而不是机械的"每 N 个词一组"。这是本技能与普通烧录字幕最大的分野:普通字幕像法庭记录,嵌入字幕是"写在画面里的排版"。
输入:从transcript.json的词级转写开始
分组的输入是由scripts/transcribe.cjs生成的transcript.json。该脚本读取项目目录下的source.mp4,优先通过uvx驱动 WhisperX(wav2vec2 强制对齐,词级时间精度远高于 whisper.cpp 的段级插值;可通过TRANSCRIBE_ENGINE=whisperx|whisper切换,模型默认small,可用WHISPER_MODEL覆盖),输出结构如下:
{ "words": [ { "text": "Some", "start": 0.24, "end": 0.44, "type": "word" }, { "text": " ", "start": 0.44, "end": 0.48, "type": "spacing" }, { "text": "memories", "start": 0.48, "end": 0.82, "type": "word" } ] }做分组的第一步是丢弃type: "spacing"的条目——你只需要带真实时间的word条目。从源码实现看,transcribe.cjs会做三类健壮性收尾:
- 对齐缺失的词:WhisperX 偶尔给出无时间戳的词(OOV、数字),脚本会从前一词的
end与后一词的start插值补齐; - 尾部幻觉词裁剪:Whisper 会在静音尾巴上"编造"重复词(如反复说 "I'm sorry."),脚本用 ffmpeg
silencedetect找出真实可听内容的终点,删除落在其后 0.4 秒之外的词; - 近静音护栏:整段平均音量低于 -45dB 时直接警告"这是幻觉转写,不是真实语音",提醒遵守决策门禁、拒绝为编造的词配字幕。
正是因为这层清洗,transcript.json的词级时间才是后续分组与对齐的可靠基准。这也解释了为什么技能里强调:转写是垃圾就拒绝配字幕,一个由幻觉词拼成的逐字 rail 比没有字幕更糟。
切分边界:满足任意一条就开新组
把词流切成组时,以下五个断点条件任意命中其一,就应在该处新起一个组:
- 停顿 ≥ 500ms(
word.end[i]与word.start[i+1]之间的间隙)——说话人换了一口气; - 句末终止符——词以
.、?、!结尾,或出现破折号式的长停; - 强逗号——
,后跟 ≥ 250ms 的停顿; - 话语重置(discourse reset)——"but"、"so"、"and then"、"you know" 这类开启新小句的词,往往值得独占一组或开启新组;
- 组达到 6 个词或 2.5 秒——哪个先到算哪个。过长的组读起来像字幕条,而不是嵌入排版。
同时还有三条硬约束必须满足:
- 每组最少 2 个词(唯一的 1 词例外:感叹词如 "Wait.",或 crown 金句行);
- 组在屏时长最少 0.5 秒,不足则并入相邻组;
- 组与组不得在时间上重叠——同一时刻至多一个组可见。
第 5 条边界(6 词 / 2.5 秒上限)与"1 组 ≈ 1 个小句"的目标相互配合:短句不会因为停顿不够而长到失去节奏,长句则被自然断点拆成若干视觉呼吸单位。
组时间窗口:in与out的精确算法
对一组词w[0]..w[n-1],组窗口的时间按下式计算:
in = w[0].start - 0.08——在第一个词出声前稍早进场(约提前 80ms),避免"词到了字还没到"的错位感;out = min(next_group.in - 0.05, w[n-1].end + 0.6)——最后一个词结束后再滞留约 0.6 秒让观众读完,但绝不能撞上下一个组的in(留 50ms 安全间隙);- 最后一组如需可延伸到视频结尾。
从实现层面看,这个公式与scripts/check-timing.cjs的校验规则互为表里:校验器要求group.in ≤ 组内最早词的 start、group.out ≥ 组内最晚词的 end——一旦in晚于某个词的 start,该词会被静默推迟到容器挂载后才显示(仓库注释记录过由此产生的 800ms 滞后 bug);一旦out早于某个词的 end,词会被硬生生截断。
需要强调的是:组窗口只做"包住词"的保证,不做"收紧"。fill-timings.cjs的源码注释明确说明:作者故意设置的更晚out(高潮滞留、句子累积)与更早in(预进场)会被原样保留,仅当它们会裁剪词时间时才做夹取(clamp)。这保证了编排水准不被机械规则抹平。
样式与语气:每个组都要有排版身份
分组确定后,按references/typography-presets.md为每个组挑选style与tone,并从左到右逐组推进。五种命名样式对应模板中的cap-*CSS 类:
| style | CSS 规格 | 适用场景 |
|---|---|---|
intro | 66px 斜体 500 | 首行、填充语("You know…"、"So…")、沉思式开场,视觉重量低 |
phrase | 78px 正体 600 | 主要陈述小句,大多数行的默认值 |
emph | 92px 正体 800 | 情绪峰值或关键成就("I've achieved incredible things") |
dream | 82px 斜体 700 | 愿景类、回忆类("was dreaming of…"),斜体暗示记忆与想象 |
crown | 140px 正体 900 大写 | 仅限高潮行,用在居中crown-plane,全片至多一个——通常就是最后一行 |
tone与 style 相互独立,二选一:
- soft——柔和淡入 + 8px 垂直漂移,
power2.out缓动,飘浮、怀旧,用于记忆、开场、dream; - present——干脆的 6px 位移 + 1.04 倍缩放弹入,
power3.out,以中心为变换原点,果断、当下感,用于强调与 crown。
选样式的推进顺序:
- 第一组:默认
intro+soft; - 观察强调信号(转写中全大写罕见;更常见的是语义信号——最高级形容词、专有名词);
- 独白从铺垫转为陈述时,把语气升为
present; crown最多保留一个组,通常是最后一行。
字号还随字幕平面的列宽缩放:typography-presets.md给出的默认值是针对约 560px 列宽(champion 原始构图)调校的,平面更宽时字号要等比放大(如 600–760px 中宽时 phrase 提到 108px、crown 提到 220px);若平面rotateY超过约 8°,可见宽度收缩,字号应上调约 10% 补偿。
编辑手术:你不是在誊写法庭记录
字幕组并不要求逐字全配。为了视觉节奏,允许合理的编辑取舍:
- 删填充词——多余的 "you know"、"um"、"I mean" 若拖垮视觉节奏可以删;
- 压缩——把 6 词长句通过删功能词压成 4 词,只要语义与时间仍然真实;
- 整体跳过——明显的静音或非语音段(笑声、纯音乐间奏)可以不配。
总原则一句话:你写的是支撑语音的排版,不是逐字法庭记录——保住含义,裁掉噪音。这与composition-craft.md的"逐词角色标注"一脉相承:纯语音感叹词(um/uh/er)、完全重复的结巴("the the the" 只留一个)、自我纠正回退("I think— I mean actually…" 只留 "I mean actually")才被允许从转写中移除;内容词与结构词(冠词、连词、介词、代词)一律保留。
完整示例:champion 分组的"黄金样本"
原始转写(约 15 秒):
"You know, for me I've had this kind of upbringing, had the great foundation and, you know, I've achieved incredible things. I was dreaming of becoming number one in the world and becoming a Wimbledon champion"
经过编辑取舍后的分组(plan.json的groups[]片段):
[ { "id": "cg-0", "style": "intro", "tone": "soft", "words": ["You", "know", "for", "me"], "in": 0.1, "out": 1.45 }, { "id": "cg-1", "style": "phrase", "tone": "soft", "words": ["I've", "had", "this", "kind", "of", "upbringing"], "in": 1.4, "out": 3.35 }, { "id": "cg-2", "style": "phrase", "tone": "soft", "words": ["the", "great", "foundation"], "in": 3.5, "out": 5.35 }, { "id": "cg-3", "style": "emph", "tone": "present", "words": ["I've", "achieved", "incredible", "things"], "in": 6.05, "out": 8.3 }, { "id": "cg-4", "style": "dream", "tone": "present", "words": ["dreaming", "of", "becoming", "number", "one"], "in": 8.5, "out": 10.4 } ]加上 crown(独立于 groups[] 之外的crown_group字段):
{ "id": "cg-crown", "style": "crown", "words": ["Wimbledon", "Champion"], "in": 10.8, "out": 12.08 }注意这里的三处编辑手术痕迹:cg-2 删掉了 "had"("had the great foundation" → "the great foundation"),cg-4 删掉了 "I was",crown 删掉了 "a"——全部是为了视觉节奏。同时注意时间窗口的衔接:cg-0 的out是 1.45,cg-1 的in是 1.4(先出后进的相邻交接,或靠空间分离共存);cg-3 选择了emph+present因为 "incredible" 是最高级;cg-4 用dream+present因为 "dreaming of" 是愿景表达;crown 独占最终高潮。
词级时间戳:原样透传,绝不重排
分组只负责决定哪些词组成一组、以及组窗口 in/out,组内每个词的start/end必须原样透传转写中的时间戳——不要对词重新计时。组内的逐词卡拉 OK 揭示动画用的是原始w.start。
这一点在scripts/fill-timings.cjs中有完整的工程化支撑:该脚本按转写顺序(sequence)而非文本匹配为plan.json填充词级时间。它维护一个转写游标,在 40 词前瞻窗口内按位置匹配每组词——这正是为了规避文本匹配在重复词(如第二个 "and"、"actions")上配错时间戳的经典 bug;匹配不到的词保留原时间并报警告,让拼写错误优雅降级而不是静默错位。
最后,scripts/check-timing.cjs --strict会在渲染前把守时间真相:
- 词时间与转写偏差 ≤ 80ms(
DRIFT_TOL = 0.08)——一个偏差 500ms 的字幕会毁掉场景幻觉; - 组窗口包住词——
group.in晚于最早词 start、group.out早于最晚词 end 都会报错; - 时间 + 屏幕区域双重重叠检测——同时在时间与垂直带上重叠的组会报冲突,需空间分离、交接(前组
out ≤后组in)或显式allow_overlap: true; - 创意替换(如 "15%" 替代 "fifteen percent")通过
CREATIVE_SUBS注册表放行,替换词按对应转写词位对齐。
这套"分组由人(或 Agent)决定、时间由脚本按位置填充、校验器在渲染前把关"的流水线,正是caption-grouping.md与整个embedded-captions技能"一切确定性步骤都计算/编译、绝不手写"原则的缩影——你只需要做好一件事:判断哪些词构成一个视觉短语,剩下的事交给脚本和校验器。
更多上下文
分组只是embedded-captions技能链条上的一环。若想继续深入,可查阅仓库内的配套文档与实现:
- skills/embedded-captions/SKILL.md——五步流水线总览(prepare → 编排 JSON → preview → render),rail + embed 双轨字幕模型,以及"嵌入是稀缺的、值得争取的高潮"这一核心设计哲学;
- skills/embedded-captions/references/typography-presets.md——五种样式 × 两种语气的完整对照表、字号随列宽的缩放矩阵、以及"每个渲染最多一个 crown"等禁忌清单;
- skills/embedded-captions/references/composition-craft.md——embed 轨道的深水区:短语分组、平面与净区锚定、高潮弹出、遮挡三步判断法;
- skills/embedded-captions/scripts/transcribe.cjs、skills/embedded-captions/scripts/fill-timings.cjs、skills/embedded-captions/scripts/check-timing.cjs——词级转写、按序填时间、80ms 严格校验的完整实现;
- skills/embedded-captions/references/layout-heuristics.md——分组之后该把组放在画面哪里:净区选择、视线方向、crown 居中的三个条件。
其中每个文件都建议按需精读:分组规则回答"切到哪",排版预设置回答"长什么样",布局启发式回答"放哪里",时间脚本回答"何时出现"——四者合起来,才是一条真正嵌入场景、而不是浮在画面上的字幕。
【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考