• #ffmpeg
  • #webm
  • #vite
  • #troubleshooting
  • #technical-decision
未分類

FFmpeg.wasm断念の経緯とWebM出力への切り替え

概要

proportional-animation-qqq.vueのアニメーションエクスポート機能において、当初はTwitter投稿に対応するためMP4(H.264)出力を目指していた。しかしFFmpeg.wasmの読み込みで問題が発生しWebM出力に切り替えた。

発生した問題

症状

エクスポートボタンをクリックすると「FFmpegを読み込み中...」で永遠に止まり、処理が進まない。

エラーログ

ブラウザのコンソールとVite開発サーバーに以下の警告が表示された:

The file does not exist at "worker.js?worker_file&type=module" which is in the optimize deps directory. The dependency might be incompatible with the dep optimizer. Try adding it to optimizeDeps.exclude.

原因

FFmpeg.wasmは内部でWeb Workerを使用しておりViteの依存関係最適化(optimizeDeps)と互換性がない。

具体的には:

  1. FFmpeg.wasmはSharedArrayBufferを使用したマルチスレッド処理を行う
  2. Web Workerファイルを動的にロードしようとする
  3. Viteのoptimize depsディレクトリ内でworker.jsが見つからず404エラーになる

検討した解決策

1. Viteの設定変更(未実施)

// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    optimizeDeps: {
      exclude: ['@ffmpeg/ffmpeg', '@ffmpeg/util']
    }
  }
})

2. COOP/COEPヘッダーの設定(未実施)

FFmpeg.wasmはSharedArrayBufferを使用するため、以下のHTTPヘッダーが必要:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

断念した理由

  1. 設定の複雑さ: nuxt.config.tsの変更、サーバーヘッダーの設定など、複数の変更が必要
  2. 副作用のリスク: COOP/COEPヘッダーは他の機能(外部リソースの読み込みなど)に影響を与える可能性がある
  3. 即時性: 今回は動作確認を優先し、シンプルな解決策を選択

採用した解決策

WebM出力への切り替え

MediaRecorder APIはブラウザネイティブでWebM(VP9)をサポートしているため、FFmpegを経由せずに直接WebMファイルを出力する方式に変更。

変更内容

useAnimationExport.ts:

// 変更前
format = 'mp4',

// 変更後
format = 'webm',

// FFmpeg変換をスキップ
if (format === 'mp4') {
  console.warn('MP4出力はFFmpegの設定が必要です。WebMで出力します。')
}

proportional-animation-qqq.vue:

// 変更前
format: 'mp4', // Twitter投稿対応

// 変更後
format: 'webm', // WebMで出力(MP4はFFmpeg設定が必要)

トレードオフ

メリット

  • FFmpegの読み込み・変換時間が不要
  • 追加の設定変更が不要
  • エラーなく動作

デメリット

  • Twitter非対応: TwitterはWebMの直接アップロードをサポートしていない
  • MP4が必要な場合は外部ツールで変換が必要

今後の対応(必要な場合)

MP4出力が必須になった場合、以下の手順で対応:

  1. nuxt.config.tsにoptimizeDeps.excludeを追加
  2. サーバーミドルウェアでCOOP/COEPヘッダーを設定
  3. FFmpeg.wasmの初期化コードを調整
  4. テスト環境と本番環境の両方で動作確認

参考リンク

結論

WebM出力は「動く」ことを優先した実用的な選択。Twitter投稿など特定の要件がある場合は、FFmpegの正式対応を検討する。


補足:なぜバックエンドで変換しなかったのか

問題の本質

今回の問題はフロントエンド(ブラウザ内)でFFmpeg.wasmを使おうとしたことにある。

代替案:バックエンドで変換

[ブラウザ]                    [サーバー]
WebM生成 → アップロード → ffmpeg変換 → MP4をダウンロード

バックエンド(Node.js等)なら:

  • ネイティブffmpegが使える → SharedArrayBuffer不要
  • COOP/COEPヘッダー設定不要
  • 変換も高速

実装例(概念)

// フロントエンド
const webmBlob = await exportAnimation()
const formData = new FormData()
formData.append('video', webmBlob, 'animation.webm')

const response = await fetch('/api/convert-to-mp4', {
  method: 'POST',
  body: formData
})
const mp4Blob = await response.blob()
downloadFile(mp4Blob, 'animation.mp4')
// バックエンド (Node.js)
import ffmpeg from 'fluent-ffmpeg'

app.post('/api/convert-to-mp4', async (req, res) => {
  ffmpeg(inputPath)
    .outputFormat('mp4')
    .pipe(res)
})

方式比較

方式難易度備考
フロントのみ(FFmpeg.wasm)ヘッダー設定、Vite互換性問題
バックエンドで変換低〜中Node.js + ffmpegで簡単
外部ツールで手動変換最も簡単UXは劣る

Cloudflare環境での選択肢

Cloudflare Workersではffmpegは直接使えないため:

  1. Cloudflare R2 + 外部変換サービス
  2. Cloudflare Stream(動画アップロードサービス)
  3. 自前のVPS/EC2でffmpegを動かす

今回の判断

バックエンドAPIを新規構築するコストとWebM出力で十分なユースケース(Discord、YouTube等)を考慮しWebM出力で妥協した。MP4が必須になった場合はバックエンド変換APIの追加を検討する。