hero-demo について
Editframe のランディングページ向け Hero Demo は、React のタイムラインコンポーネントで構成された約 51 秒のアニメーションです。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 Demo Composition
Editframe の React/タイムラインコンポーネントで構成された約 51 秒のアニメーション作品で、ランディングページで再生されます。sequence Timegroup 内に 8 つのシーンを配置し、クロスフェードで重ねています。音声ナレーションは途切れずに再生され、キャプションは単語単位で同期します。
File Map
| File | Purpose |
|---|---|
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 |
Architecture
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
- 重なりは 495 ms(30 fps で 15 フレーム)です。各シーンはこの時間の間にフェードイン/フェードアウトします。
- 重なりを含む合計時間は約 51.3 秒です。
- DUR の値は、音声の長さにバッファーを加えたものです。バッファーによって、ナレーションが終わった後もシーンの映像に余裕を持たせます。
- すべてのアニメーションは CSS のみで実行し、各シーンのローカルタイムラインを基準にした
animationDelayで制御します。必要に応じて、ef-text 以外の要素にフィルモードを設定してください(css-animations スキルを参照)。ef-text は自動的に処理します。
Voiceover Script
| # | Scene | Text |
|---|---|---|
| 1 | タイトル | Video is a web page that moves. |
| 2 | 制作者 | It starts with HTML and CSS. When you need more, it's just React. |
| 3 | レイヤー | Stack layers the way you stack divs. Video, text, shapes, 3D, mix everything. |
| 4 | タイムライン | Need an editor? Snap together GUI primitives. Timeline, waveforms, captions, into any editing experience you want. |
| 5 | エディター | A full NLE. A simple trim tool in a form. It's your UI. These are just the building blocks. |
| 6 | テンプレート | Feed in data, and one template becomes ten thousand unique videos. |
| 7 | ストリーム | Preview is instant. Change the code, see the frame. |
| 8 | レンダリング | When it's ready, render to the cloud, the browser, or the command line. Same composition, every target. |
メッセージ:Editframe は宣言的なレイヤーです。HTML/CSS が基盤で、その上に React と JS という強力なツールがあります。「React だけ」でも「HTML だけ」でもありません。
テキストのルール:エムダッシュは使わないでください(TTS が間を空けてしまいます)。短く、歯切れのよい文にしてください。
Voiceover Regeneration Workflow
スクリプトのテキストが変わった場合は、以下の手順を正確に実行してください。
Step 1: Update the generation script
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 モデルのウォームアップ時に発生するノイズを吸収します。削除しないでください。
セグメントは " ... " のポーズ区切り文字でつなぎ、1 回の TTS 処理で生成します。
Step 2: Generate audio locally
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:シーンごとの長さ(秒)
Step 3: Trim the preamble
プリアンブルは削除する必要があります。スクリプトのコンソール出力に表示されるプリアンブルの終了時刻を確認し、その位置でトリミングしてから、次を実行します。
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
Step 4: Get global word timestamps
トリミングしたファイルに対して 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/ から配信されます。
ファイル名にはコンテンツハッシュを含めてください。同じ URL を上書きできないよう、CDN キャッシュが設定されています。
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 を更新する
更新する項目は 3 つです。
-
VOICEOVER_SRC— コンテンツハッシュを含む新しい CDN URL を設定します。 -
CAPTIONS配列 — グローバルな whisper タイムスタンプから再構築します。自然な文やフレーズの区切りに合わせて、単語を字幕グループにまとめます。各グループには次の値が必要です。showMs:グループ内の最初の単語のタイムスタンプ(または少し前の時刻)hideMs:最後の単語の終了時刻 + 約 400ms の余裕words:whisper の出力から得た{ w, s, e }の配列
字幕グループは文の区切りで分け、対応するシーンに合わせてください。1 つのシーンに複数のグループを割り当てるのが一般的です。タイムスタンプは単一のナレーショントラックから取得したグローバルな値であり、シーンごとの相対値ではありません。
DUR定数 — 音声の長さが大きく変わった場合は、シーンの長さを調整します。計算式はDUR.scene = voiceDurationMs + bufferです。余裕時間は通常 800〜1200ms です。30fps に合うよう、33.33ms の倍数に丸めてください。シーンごとの音声の長さはtiming.jsonで確認できます。
ステップ 7:確認する
ブラウザーでコンポジションを再生し、次の点を確認します。
- 字幕内の単語が音声と同期してハイライトされる
- シーンの切り替えでナレーションが文の途中で途切れない
- シーン間に無音の隙間がない(オーバーラップが自然に感じられる)
シーンの編集
各シーンは、明示的な duration={DUR.xxx}ms を持つ Timegroup mode="fixed" を返す関数(SceneTitle、SceneAuthor など)です。
よく使うパターン:
sceneStyle(d)は CSS アニメーションでクロスフェードの開始と終了を適用します- ビジュアルアニメーションでは、ずらしや順序付けに
animationDelayを使用します。ef-text はデフォルトで fill-mode を自動設定しますが、通常の要素には明示的な fill-mode が必要です CompositionCanvasは R3F Three.js のコンテンツ(SceneStream のパーティクルなど)をラップします<Text split="char">は文字単位のずらしアニメーションに使用します
アニメーションを追加・編集するときは、landing.css に hero- プレフィックスを付けた新しいキーフレームを定義してください。
Changelog MDX の作成
変更履歴のエントリーは telecine/services/web/app/content/changelogs/{version}.mdx にあります。各エントリーでは、HeroDemo と同じコンポーネントセットを MDX の props 経由で使用します。
コンポーネント API
ChangelogIntroCard — 必須の props:version、codename、title。TypeScript は MDX の作成時に検証しませんが、title prop は必須です。title がないと、実行時に title.trim() がエラーをスローし、ハイドレーションが失敗してページに空のプレースホルダーが表示されます。illustration prop は、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 が空の灰色のボックスとして表示され、プレーヤーにハイドレーションされない場合、ほとんどの場合、原因はシーンコンポーネントの実行時エラーです。ReleaseVideo は isClient によるゲーティングを使用するため、SSR では常にプレースホルダーが表示されます。シーンコンポーネントのエラーはクライアントのハイドレーションを黙って失敗させます。ブラウザーのコンソールで、エラーをスローしているコンポーネントの TypeError を確認してください。
注意点
- TTS CustomVoice モデルは動作しません:VoiceDesign(
generate_voice_design)のみを使用してください。CustomVoice では意味不明な音声が生成されます。 - プリアンブルが必要です:TTS 生成の最初の 1〜2 秒はノイズが入ることがあります。このノイズを使い捨てのプリアンブルで吸収します。アップロードする前に必ず切り取ってください。
- GCS のファイル名の再利用:CDN は積極的にキャッシュします。新しい音声をアップロードする際は、必ずコンテンツハッシュを含むファイル名を使用してください。
- キャプションのタイムスタンプは全体基準です:シーンごとのタイムスタンプではなく、1 本にまとめたトリミング済みのナレーションを whisper にかけた結果です。シーンごとの相対時間にしないでください。
- CompositionCanvas のレンダリング:同期エクスポートパイプラインで動作するよう、
flushSyncとuseLayoutEffectを使用します。手動でgl.render()/gl.finish()を呼び出さないでください。GPU の処理が二重になります。 - ナレーションテキスト内のエムダッシュ:TTS 出力で不要な間が生じます。代わりにカンマまたはピリオドを使用してください。