Reactで動画レンダリングシステムを構築した方法
動画の問題
3つのブランドのプロモーション動画が必要だった。ShawnOS、GTMOS、ContentOS。それぞれに3つのアスペクト比が必要 - LinkedIn (4:5)、Reels (9:16)、ランドスケープ (16:9)。つまり9つのレンダーターゲット。そしてデザイントークンが変わるたびに、すべての動画を更新する必要がある。
After Effectsのテンプレートを見た。Canvaを見た。人を雇うことも検討した。どのオプションも、色を変更するプッシュをした瞬間にウェブサイトと同期が崩れる手動パイプラインを意味した。
ウェブサイトにはすでにデザインシステムがある。データ層がある。ブランドトークンがある。動画を同じコードベースに入れたらどうなるだろう?
Remotionとモノレポの優位性
RemotionはReactコンポーネントを動画フレームに変換する。JSXを書く。30fpsで各フレームをレンダリングする。MP4が出力される。GPUは不要。タイムラインエディタもない。ただのコードだ。
鍵となるインサイトは、動画アプリを既存のTurborepoモノレポのwebsite/apps/video/に配置したことだ。3つのウェブサイトを動かしているのと同じ@shawnos/sharedパッケージ。同じデザイントークン。同じカラーパレット。共有パッケージのhex値を変更すれば、ウェブサイトも動画も次のビルドですべて更新される。
9つのコンポジションが1つのRoot.tsxにある。3つのブランド掛ける3つのアスペクト比。useScale()フックがすべてを1080x1350のベースに正規化し、比例してスケーリングする。1つのコンポーネントツリーが任意のサイズでレンダリングされる。
シーンアーキテクチャ
動画はTransitionSeriesで接続されたシーンから構成される - Remotionのコンポジションツールで、セグメント間のオーバーラップタイミングを処理する。各シーンは固定フレーム数を持つReactコンポーネントだ。
現在のV3システムは30fpsで約10秒(合計310フレーム):
- Hook (36フレーム / 1.2秒) - オープニングのつかみ
- BootWikiBlitz (110フレーム / 3.7秒) - ウィキカードが高速で切り替わるターミナルブートシーケンス
- Progression (100フレーム / 3.3秒) - RPGスタイルのティアシステムビジュアライゼーション
- CtaNetwork (94フレーム / 3.1秒) - ネットワークグラフ付きのコールトゥアクション
トランジションはシーン間に10フレームのオーバーラップがある。タイミング設定はtiming-v2.tsにある - 1つのファイルが動画全体のリズムを制御する。
コンポーネントライブラリ
3つのコンポーネントがビジュアルの大部分を担当する。
MatrixRainは@remotion/noiseのPerlinノイズを使用して、決定論的な文字の雨エフェクトを生成する。各列が独立してドリフトする。文字の選択は列、行、フレーム番号でシードされる。エフェクトはオーガニックだが再現可能 - 同じシード、同じ出力、毎回のレンダリングで。
TypewriterTextはフレームごとに文字を表示し、点滅するカーソル付き。経過フレームが表示文字数を制御する。カーソルは1Hzで点滅する。シンプルな計算、クリーンなエフェクト。
ParticleFieldはx方向とy方向のドリフトに2つの独立したノイズストリームを使用して、浮遊するパーティクルを生成する。デフォルトで40パーティクル、キャンバスドリフトは8%に制限。3番目のノイズチャネルによる微妙なパルス。
3つのコンポーネントすべてが決定論的だ。Math.random()なし。Remotionはこれを要求する - ランダム値はフレーム間で変化し、レンダリングを壊す。一貫したシードを持つPerlinノイズが、再現可能でオーガニックなアニメーションを実現する。
ビジュアルトリートメント
すべてのシーンはSceneWrapperで包まれ、一貫したビジュアルトリートメントが適用される:
- ダークキャンバス背景 (#0D1117)
- ラジアルビネット (エッジの暗化)
- アクセントカラーウォッシュ 3%不透明度
- パーティクルフィールドのアンビエントノイズ
- SVG feTurbulenceによるフィルムグレイン
- スキャンラインオーバーレイ (CRTエステティック)
結果はターミナルネイティブな動画に見える。手動のポストプロセスなしでShawnOSブランドにフィットする。
コードによるデザイントークン
トークンシステムはtokens.tsにある:
canvas: #0D1117
green: #4EC373 (ShawnOS)
teal: #3DBFA0 (GTMOS)
purple: #9B72CF (ContentOS)
amber: #D2A53C (セカンダリアクセント)
SITE_ACCENTSがブランド名を色にマッピングする。BootWikiBlitzシーンはウィキカードがフリップする際にパレットを循環する - green、teal、amber、purple、green。ブランドアイデンティティはデータの中にあり、コンポーネントにハードコードされていない。
フォントはどこでもJetBrains Mono。モノスペース。3つのサイトすべてのターミナルエステティックと一貫。
レンダリング
npm run render:allで9つのバリエーションすべてを生成する。Remotionが各コンポジションをJPEGフレームにレンダリングし、MP4にエンコードする。package.jsonに記載されたもの以外の外部依存なし。クラウドレンダーファームなし。MacBookで動く。
レンダリングされた出力はwebsite/apps/video/out/に保存され、各サイトのpublic/video/ディレクトリにデプロイされる。SQLiteコンテンツインデックスがすべての動画ファイル、ブランド、アスペクト比、デプロイステータスを追跡する。
なぜ重要か
動画システムは、他のすべてのコンテンツタイプと同じコードベースにあるコンテンツタイプだ。ブログ記事はmarkdownファイル。ナレッジタームはTypeScriptオブジェクト。動画はReactコンポーネント。すべてが同じデザイントークン、同じデータ層、同じデプロイパイプラインを共有する。
新しいウィキカテゴリを追加すれば、BootWikiBlitzシーンがそれを参照できる。ブランドカラーを変更すれば、動画も更新される。mainにプッシュすれば、すべてが一緒にデプロイされる。
別のツールなし。手動エクスポートなし。同期の問題なし。モノレポがシステムだ。
$ remotion render Root LeadMagnetV3 --codec=h264