0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザ完結型AI動画編集で、プレビューと書き出しを一致させる設計

0
Posted at

多くの動画編集Webアプリでは、素材をサーバーへアップロードし、サーバー側で解析・レンダリングします。この構成は実装しやすい一方、素材が大きいほど転送時間とコストが増え、個人的な映像を扱う際にはプライバシー上の説明も必要になります。

そこで、オープンソースの動画エディター Timeline Studio では、対応する編集処理とAI推論をブラウザ内で完結させる「ローカルファースト」構成を採用しました。

実装で特に難しかったのは、AIモデルをWebGPUで動かすことだけではありません。リアルタイムプレビューとオフライン書き出しで、同じタイムラインが同じ映像になることでした。

この記事では、そのために採用した設計を整理します。

1. タイムラインを唯一の状態として扱う

動画エディターには、素材のトリム、再生速度、位置、拡大率、回転、字幕、エフェクト、キーフレームなど、多数の時間依存パラメータがあります。

プレビュー画面のDOMやCanvasをそのまま録画して書き出すと、デコード遅延、フレーム落ち、タブのスケジューリング、端末負荷によって結果が変わります。

そこで、画面の状態ではなく、タイムライン上の宣言的なプロジェクトデータを正とします。

const clip = {
  id: "visual-001",
  sourceStart: 2.4,
  timelineStart: 5.0,
  duration: 8.0,
  playbackRate: 1.25,
  transform: {
    x: 0.5,
    y: 0.5,
    scale: 1,
    rotation: 0,
  },
  keyframes: [],
};

プレビューと書き出しは、どちらも「指定時刻におけるクリップの状態」を同じ評価関数から取得します。

function evaluateClipAtTime(clip, timelineTime) {
  const localTime = timelineTime - clip.timelineStart;
  const sourceTime = clip.sourceStart + localTime * clip.playbackRate;

  return {
    sourceTime,
    transform: evaluateTransform(clip.keyframes, localTime),
    visible: localTime >= 0 && localTime < clip.duration,
  };
}

実際の実装では、字幕、オーバーレイ、音声、トランジション、色補正、エフェクトも同じ時刻基準で評価します。

2. プレビューと書き出しの役割を分ける

プレビューと書き出しでは、求められる性能が異なります。

  • プレビュー: 操作にすぐ反応し、編集の手触りを保つ
  • 書き出し: フレームごとの状態を再現可能に評価する

プレビューではブラウザのネイティブ動画再生を活用し、Canvas上で合成します。一方、書き出しでは出力FPSからフレーム時刻を決め、その時刻の状態を順番に評価します。対応ブラウザではWebCodecsを利用し、MP4またはWebMとして構成します。

for (let frame = 0; frame < totalFrames; frame++) {
  const time = frame / fps;
  const scene = evaluateTimelineAtTime(project, time);

  renderScene(ctx, scene);
  await encoder.encode(canvas, { timestamp: time });
}

重要なのは、描画経路を完全に同一にすることではなく、時間、座標、補間、エフェクトの定義を共有することです。これが分離すると、「編集画面では合っているのに書き出すと字幕やエフェクトがずれる」という不具合が発生します。

3. ランダム表現もシードで固定する

ランダムな位置にエフェクトを出す場合、Math.random() をそのまま使うとプレビューと書き出しで結果が変わります。

そのため、プロジェクトID、クリップID、イベント番号などからシードを作り、決定論的な疑似乱数を使います。

function seededRandom(seed) {
  let state = seed >>> 0;

  return () => {
    state = (state * 1664525 + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}

たとえばリズムに同期した波紋では、クリック位置だけでなく、発生時刻、波の進行率、グレースケールからカラーへの復帰率も同じパラメータから計算します。見た目がランダムでも、同じプロジェクトからは同じ結果を得られます。

4. AIモデルの配信も編集体験の一部にする

ブラウザ内推論でも、初回だけはモデルをダウンロードする必要があります。モデルが大きいほど、推論コードより配信設計がユーザー体験を左右します。

Timeline Studioでは次の方針を採用しています。

  • 機能を初めて使う時だけモデルを遅延取得する
  • モデルを不変のリビジョンに固定する
  • Service Workerだけを永続キャッシュの書き込み元にする
  • Hugging FaceとModelScopeに所有ミラーを用意する
  • 配信元が変わっても同じモデルは一つのキャッシュIDへ正規化する
  • 2回目以降はダウンロードではなくキャッシュから再利用する

AI推論はWebGPUを中心にしつつ、安定性や互換性を優先する処理ではWASMも利用します。「すべてWebGPU」が必ず最速・最安定になるわけではないためです。

5. AIの結果を編集可能な素材へ戻す

AI機能を単独のデモボタンにせず、結果を通常の編集モデルへ戻すことも重要です。

  • 音声認識の結果は時間付き字幕クリップにする
  • 生成したナレーションは音声トラックに置ける素材にする
  • ボーカル分離結果は独立した音声・音楽クリップにする
  • AI音楽は自動挿入せず「My assets」へ追加する
  • 修復や被写体分離の結果も再編集可能にする

AI処理後もタイムラインが唯一の状態であれば、Undo、移動、分割、再書き出しといった通常の編集操作を維持できます。

6. ローカルファーストの制約

ローカル処理にも明確な制約があります。

  • 初回モデル取得には時間と保存容量が必要
  • GPUメモリやドライバによって実行可能なモデルが異なる
  • モバイルではより保守的な処理経路が必要
  • WebGPUやWebCodecsの対応状況に差がある
  • アプリとモデルを取得するまでは完全なオフラインではない

そのため「何でもオフラインで動く」ではなく、対応するワークフローでは、プロジェクト素材を編集バックエンドへアップロードせず処理できる、という範囲を明確にしています。

まとめ

ブラウザ動画編集でプレビューと書き出しを一致させるには、次の4点が特に重要でした。

  1. タイムラインの宣言的データを唯一の状態にする
  2. プレビューと書き出しで時間評価ロジックを共有する
  3. ランダム表現もシード付きで決定論的にする
  4. AIの結果を通常の編集可能な素材へ戻す

Timeline StudioはMIT Licenseで公開しています。WebCodecs、WebGPU、ONNX、ブラウザメディア処理に関心がある方からの技術的なフィードバックやIssueを歓迎します。

なお、本日 Product Huntでも公開 しました。実際の端末で動かなかった処理や、プレビューと書き出しの差異を見つけた場合は、率直なフィードバックをいただけると助かります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?