mcpField
H
hero-demo

hero-demo

github.com/editframe创意与媒体1974 次安装2 周前更新
代码仓库

关于 hero-demo

Editframe 的落地页 Hero Demo 是一段约 51 秒的动画作品,由 React 时间线组件构成,包含 8 个场景、连续旁白和逐词同步字幕。项目文档说明了场景结构、旁白生成、字幕时间戳和更新流程,适合维护这段演示及相关视频内容的开发者。

主要功能

  1. 018 个场景的时间线构成
  2. 02场景交叠与淡入淡出
  3. 03连续旁白与逐词字幕
  4. 04本地生成并切分旁白
  5. 05字幕时间戳与时长维护

适用场景

  1. 01调整落地页 Hero Demo 场景
  2. 02重新生成旁白并校准字幕
  3. 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.pyTTS 生成与 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 会自动处理。

配音脚本

#场景文本
1title视频是一种会动的网页。
2author从 HTML 和 CSS 开始。需要更多功能时,只需用 React。
3layers像堆叠 div 一样堆叠图层。视频、文本、形状、3D,任意组合。
4timeline需要编辑器?把 GUI 基础组件拼在一起。时间线、波形、字幕,打造你想要的任意编辑体验。
5editor完整的 NLE。也可以是在表单中的简单剪辑工具。这是你的 UI。这些只是构建模块。
6template输入数据,一个模板就能生成一万个独特视频。
7stream预览即时呈现。修改代码,即可看到画面。
8render准备就绪后,可渲染到云端、浏览器或命令行。同一构成,适用于所有目标。

传达重点: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

需要更新三项:

  1. VOICEOVER_SRC——设置为带内容哈希的新 CDN URL。

  2. CAPTIONS 数组——根据全局 whisper 时间戳重新构建。将单词分组为符合自然句子或短语边界的字幕组。每组需要:

    • showMs:组内第一个单词的时间戳(或稍早一些)
    • hideMs:组内最后一个单词结束时间戳 + 约 400ms 缓冲
    • words:由 whisper 输出中的 { w, s, e } 组成的数组

字幕组应在句子边界处拆分,并与所属场景对应。每个场景包含多个字幕组很正常。时间戳是全局的(来自单条配音轨道),不是相对于场景的。

  1. 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 输出中出现不必要的停顿。请改用逗号或句号。

评论

支持 Markdown