关于 hero-demo
Editframe 的落地页 Hero Demo 是一段约 51 秒的动画作品,由 React 时间线组件构成,包含 8 个场景、连续旁白和逐词同步字幕。项目文档说明了场景结构、旁白生成、字幕时间戳和更新流程,适合维护这段演示及相关视频内容的开发者。
主要功能
- 018 个场景的时间线构成
- 02场景交叠与淡入淡出
- 03连续旁白与逐词字幕
- 04本地生成并切分旁白
- 05字幕时间戳与时长维护
适用场景
- 01调整落地页 Hero Demo 场景
- 02重新生成旁白并校准字幕
- 03维护发布说明中的视频组件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ editframe/ skills --skill hero-demo安装后在 Claude Code 中输入/hero-demo确认已加载。
详细介绍
Hero 演示构成
这是一个约 51 秒的动画构成,使用 Editframe 的 React/时间线组件构建,并在落地页上播放。8 个场景位于一个 sequence Timegroup 中,彼此交叉淡化重叠;配有一段连续的配音,以及与语音同步到词级别的字幕。
文件一览
| 文件 | 用途 |
|---|---|
telecine/services/web/app/components/landing-v5/HeroDemo.tsx | 构成:DUR 常量、CAPTIONS 数组、8 个场景函数、HeroDemoContent 布局 |
telecine/services/web/app/styles/landing.css | 所有 hero-* 关键帧动画 |
telecine/services/web/scripts/generate-hero-voiceover-local.py | TTS 生成与 whisper 切分脚本 |
telecine/services/web/public/audio/hero/ | 生成的 WAV/MP3 文件、timing.json |
架构
FitScale
Timegroup mode="contain" (960x540, relative)
Audio src={VOICEOVER_SRC} ← single continuous track
Timegroup mode="sequence" overlapMs=495
SceneTitle (3333ms)
SceneAuthor (6333ms)
SceneLayers (7067ms)
SceneTimeline (9767ms)
SceneEditor (8200ms)
SceneTemplate (5500ms)
SceneStream (5700ms)
SceneRender (8833ms)
SceneCaptions groups={CAPTIONS} ← absolute overlay, global timestamps
- 重叠时长为 495ms(30fps 下的 15 帧)。每个场景都会在此时间段内淡入和淡出。
- 包含重叠部分的总时长约为 51.3s。
- DUR 值 = 配音时长 + 缓冲时间。缓冲时间为场景画面提供额外空间,让其在配音结束后仍可舒展呈现。
- 所有动画均为纯 CSS,由相对于每个场景本地时间线的
animationDelay驱动。根据需要,为非 ef-text 元素设置填充模式(参见 css-animations 技能);ef-text 会自动处理。
配音脚本
| # | 场景 | 文本 |
|---|---|---|
| 1 | title | 视频是一种会动的网页。 |
| 2 | author | 从 HTML 和 CSS 开始。需要更多功能时,只需用 React。 |
| 3 | layers | 像堆叠 div 一样堆叠图层。视频、文本、形状、3D,任意组合。 |
| 4 | timeline | 需要编辑器?把 GUI 基础组件拼在一起。时间线、波形、字幕,打造你想要的任意编辑体验。 |
| 5 | editor | 完整的 NLE。也可以是在表单中的简单剪辑工具。这是你的 UI。这些只是构建模块。 |
| 6 | template | 输入数据,一个模板就能生成一万个独特视频。 |
| 7 | stream | 预览即时呈现。修改代码,即可看到画面。 |
| 8 | render | 准备就绪后,可渲染到云端、浏览器或命令行。同一构成,适用于所有目标。 |
传达重点:Editframe 是一个声明式层。HTML/CSS 是基础;React 和 JS 是在此之上的强大工具。既不是“只有 React”,也不是“只有 HTML”。
文本规则:不要使用 em dash(它会导致 TTS 停顿)。句子要简短有力。
重新生成配音的工作流
脚本文本发生变化时,请严格按照以下步骤操作。
步骤 1:更新生成脚本
编辑 telecine/services/web/scripts/generate-hero-voiceover-local.py 中的 SEGMENTS。每个片段包含:
key:文件名前缀(例如01-title)text:配音台词last_word:用于基于 whisper 进行切分的最后一个词干discard:仅对前导片段设为 true
第一个片段必须是前导片段("discard": True),并使用类似 "Here is the introduction." 这样的无关文本,以消除 TTS 模型预热噪声。不要删除它。
这些片段会用 " ... " 停顿分隔符连接,进行一次 TTS 生成。
步骤 2:在本地生成音频
cd telecine/services/web
python scripts/generate-hero-voiceover-local.py
这需要:
- 带有 VoiceDesign 模型(
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign)的qwen_tts包 whisper、soundfile、numpy、torch
不要使用 CustomVoice 模型(generate_custom_voice)。它会生成乱码。只能使用 language="English" 的 generate_voice_design。
输出将写入 telecine/services/web/public/audio/hero/:
voiceover.wav/.mp3:包含前导片段的完整音频01-title.wav到08-render.wav/.mp3:每个场景的切分文件(仅供参考)timing.json:每个场景的时长,单位为秒
步骤 3:裁掉前导片段
必须移除前导片段。从脚本的控制台输出中找到裁剪点(其中会打印前导片段的结束时间),然后:
ffmpeg -y -i public/audio/hero/voiceover.wav -ss <PREAMBLE_END_SEC> public/audio/hero/voiceover-trimmed.wav
ffmpeg -y -i public/audio/hero/voiceover-trimmed.wav -codec:a libmp3lame -b:a 128k public/audio/hero/voiceover-trimmed.mp3
步骤 4:获取全局词级时间戳
对裁剪后的文件运行 whisper,以获取全局时间戳(所有时间戳均相对于裁剪后音频的 t=0):
import whisper, json
model = whisper.load_model("small")
result = model.transcribe("public/audio/hero/voiceover-trimmed.wav", language="en", word_timestamps=True)
words = []
for seg in result["segments"]:
for w in seg.get("words", []):
words.append({"word": w["word"].strip(), "start": round(w["start"] * 1000), "end": round(w["end"] * 1000)})
with open("/tmp/hero-whisper/global.json", "w") as f:
json.dump(words, f, indent=2)
步骤 5:上传到 GCS
文件应上传到 gs://editframe-assets-7ac794b/hero/(不是 editframe-assets)。CDN 提供的地址为 https://assets.editframe.com/hero/。
文件名中使用内容哈希——CDN 缓存不允许覆盖相同的 URL:
HASH=$(md5 -q public/audio/hero/voiceover-trimmed.mp3 | head -c 8)
gsutil -h "Content-Type:audio/mpeg" -h "Cache-Control:public, max-age=31536000" \
cp public/audio/hero/voiceover-trimmed.mp3 "gs://editframe-assets-7ac794b/hero/voiceover-${HASH}.mp3"
步骤 6:更新 HeroDemo.tsx
需要更新三项:
-
VOICEOVER_SRC——设置为带内容哈希的新 CDN URL。 -
CAPTIONS数组——根据全局 whisper 时间戳重新构建。将单词分组为符合自然句子或短语边界的字幕组。每组需要:showMs:组内第一个单词的时间戳(或稍早一些)hideMs:组内最后一个单词结束时间戳 + 约 400ms 缓冲words:由 whisper 输出中的{ w, s, e }组成的数组
字幕组应在句子边界处拆分,并与所属场景对应。每个场景包含多个字幕组很正常。时间戳是全局的(来自单条配音轨道),不是相对于场景的。
DUR常量——如果配音时长变化显著,则调整场景时长。公式:DUR.scene = voiceDurationMs + buffer,其中缓冲通常为 800-1200ms。将时长取整到 30fps 对齐值(33.33ms 的倍数)。查看timing.json获取各场景的配音时长。
步骤 7:验证
在浏览器中播放合成内容。检查:
- 字幕单词是否与音频同步高亮
- 场景切换是否会在句子中途截断配音
- 场景之间是否没有静音间隙(重叠部分应过渡流畅)
场景编辑
每个场景都是一个函数(SceneTitle、SceneAuthor 等),返回带有显式 duration={DUR.xxx}ms 的 Timegroup mode="fixed"。
常见模式:
sceneStyle(d)通过 CSS 动画应用淡入淡出- 视觉动画使用
animationDelay实现错开或排序;ef-text 会自动设置默认的 fill-mode,普通元素需要显式设置 fill-mode CompositionCanvas包装 R3F Three.js 内容(SceneStream 中的粒子)<Text split="char">用于逐字符错开动画
添加或编辑动画时,在 landing.css 中使用 hero- 前缀定义新的关键帧。
Changelog MDX 编写
Changelog 条目位于 telecine/services/web/app/content/changelogs/{version}.mdx。每个条目通过 MDX 属性使用与 HeroDemo 相同的一组组件。
组件 API
ChangelogIntroCard——必需属性:version、codename、title。即使 TypeScript 在编写 MDX 时不会强制要求,title 属性也不是可选的。缺少 title 会导致运行时 title.trim() 抛出错误,破坏 hydration,并使页面停留在空白占位内容。illustration 属性是一个无操作的存根,仅为兼容 MDX 保留。
<ChangelogIntroCard
version="0.47.0"
codename="Mocha Relay"
title="Short title for the card display"
durationMs={4000}
/>
ReleaseVideo——在自身的 <Timegroup mode="contain"> 中包装子元素。通过内部的 <Timegroup mode="sequence"> 将场景元素作为直接子元素传入:
<ReleaseVideo aspect="16/9">
<Timegroup mode="sequence" overlapMs={600} style={{ width: 1920, height: 1080, position: "relative" }}>
<ChangelogIntroCard ... />
<TextMoment ... />
<ChangelogOutroCard ... />
</Timegroup>
</ReleaseVideo>
ChangelogOutroCard——必需属性:version 和 tagline。durationMs 有默认值。
TextMoment——必需属性:headline 和 body。motif、accentColor、durationMs 为可选属性。
注意:页面加载时出现空白占位内容
如果 ReleaseVideo 显示为空白灰色框,且始终没有 hydrate 成播放器,原因几乎总是某个场景组件中出现了运行时错误。ReleaseVideo 使用 isClient 条件渲染,因此 SSR 始终会显示占位内容——场景组件中的错误会悄无声息地阻止客户端 hydration。检查浏览器控制台,查找抛出错误的组件对应的 TypeError。
注意事项
- TTS CustomVoice 模型不可用:只使用 VoiceDesign(
generate_voice_design)。CustomVoice 生成的音频无法辨认。 - 必须使用前导音频:TTS 生成的前 1-2s 可能有杂音。用于丢弃的前导音频可以吸收这些杂音。上传前务必将其裁掉。
- GCS 文件名复用:CDN 缓存非常激进。上传新音频时,务必使用内容哈希文件名。
- 字幕时间戳是全局的:它们来自对单段裁剪后旁白的 whisper 处理,而非按场景生成。不要将其设为场景相对时间。
- CompositionCanvas 渲染:在同步导出管线中使用
flushSync+useLayoutEffect。不要添加手动的gl.render()/gl.finish()调用——这会使 GPU 工作量翻倍。 - VO 文本中的长破折号:会导致 TTS 输出中出现不必要的停顿。请改用逗号或句号。