プログラミング

FFmpeg.wasmの使い方。クロスオリジンアイソレーション(COOP,COEP)って何ですの?

投稿日:2021年12月29日 更新日:

今さらですがJavascriptでFFmpegが使えるようになっているらしいとの情報を得て早速試してみました。FFmpeg.wasmというらしいです。公開された当初は容易に使用できたらしいですが、現在は「sharedarraybuffer」の使用制限の影響で少々難解になっているようです。プログラムを仕事としている方たちはしっかりと情報の意味を理解して対応をしているようですが、私を含めプログラム勉強中の身にとってはそうはいきません。情報をかき集めて何とかFFmpeg.wasmを使用できるようになったので記事にしてみました。
(素人目線の記事なので多くの人の疑問点に寄り添える形となっていると思われます。)

SharedArrayBufferとクロスオリジンアイソレーション

詳しい事は分かりませんが、FFmpeg.wasmの使用の障害になっている事のみを述べると

「FFmpeg.wasmにはSharedArrayBufferというものが使用されていて、SharedArrayBufferを使用するにはクロスオリジンアイソレーションがなされている環境でなくてはならない」

そうです。
また、この制限はChromeブラウザに関してはChrome91かららしいです。

https://developers-jp.googleblog.com/2021/03/chrome-91-sharedarraybuffer.html

なので、古いバージョンのChromeに戻せば利用できるのでしょうか?しかし、自分だけ使用できてもサービスとしては利用ができないのでChromeバージョン下げによる対応はやめました。

クロスオリジンアイソレーションについて詳細は分かりませんが、私は「隔離された環境にすれば使用できる」と理解しました。そして上記リンクでGoogle先生が言われているように

Cross-Origin-Embedder-Policy: require-corp // 以下、COEPとします。
Cross-Origin-Opener-Policy: same-origin   // 以下、COOPとします。

をページでヘッダーに送信すれば良いそうです。
はい、素人には全く意味が分かりません。上記ヘッダーとやらをどこに書くのか、どこで設定するのかが分かりません。とにかく検索して皆さんの解決策を探し当てるしかありませんね。

なるべくシンプルな解決策を模索

検索すれば解決方法は結構見つかりました。しかし、Node.jsを使用しているものが多かったです。Node.jsにはヘッダーを送信する関数?があるようなので、上記の2行のヘッダーを容易に送信できるようです。
私はNode.jsを使いたくありませんでした。理由は、ちょっとした例題を試したいだけなので、わざわざNode.jsを使用したアプリをデプロイできるサービスを利用するのが面倒に感じました。

ゴリゴリと検索しているうちにPHPでヘッダーを設定できるという記事を発見しました。PHPは少しだけ扱ったことがあったのでこれで行けそうです。(PHPがサーバー側で動く言語だと思い出すのに1時間くらいかかりました。。。)

PHP方式なら自分のドメインだけで試すことが可能なので結構お手軽ではないでしょうか。

PHPにCOOP、COEPヘッダーを書く方法:ソースコード

FFmpeg.wasmで検索すれば sharedarraybufferの規制がかかる前に動作していたサンプルプログラムが多数見つかります。こちらの記事のコードを利用させてもらいましょう。

https://zenn.dev/sugar/articles/ce971201435814b0350c

Chrome 86とあるので規制前の記事ですね。

改良した点は以下の通りです。
1. ffmpeg.min.js、ffmpeg-core.js、ffmpeg-core.worker.jsをDLしてプログラムと同じ場所に置く。
2. サンプルのindex.htmlをindex.phpにしてCOOP, COEPヘッダーを追加する

恥ずかしながらコードです。
1~4までの数字を1秒間ずつ表示する動画を生成するサンプルとなっています。
4枚のImageを作成してffmpegでmp4動画にしています。

index.php

<?php
ini_set('mbstring.internal_encoding' , 'UTF-8');

header('Cross-Origin-Opener-Policy: same-origin');
header('Cross-Origin-Embedder-Policy: require-corp');
?>

<html>
<head>
  <script>
    if (crossOriginIsolated) {
      // Post SharedArrayBuffer
      console.log('crossOriginIsolated');
    } else {
      // Do something else
      console.log('NOT crossOriginIsolated');
    }
  </script>
  <script src="ffmpeg.min.js" ></script>
 </head>

  <body>
  <script src="app.js"></script>
  </body>
</html>

11行目のcrossOriginIsolatedでクロスオリジンアイソレーションがされているか確認をしています。4,5行目をコメントアウトするとNOT crossOriginIsolatedとなります。


app.js

(async () => {
  console.log('app.js');
  const { createFFmpeg } = FFmpeg

  function generateImages() {
    console.log('generateImages');
    const canvas = document.createElement('canvas')
    canvas.width = 320
    canvas.height = 240

    const ctx = canvas.getContext('2d')
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    ctx.font = '64px serif'

    const arr = []

    for (let i = 0; i < 4; i++) {
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      ctx.fillStyle = '#fff'
      ctx.fillRect(0, 0, canvas.width, canvas.height)
      ctx.fillStyle = '#000'
      ctx.fillText(i + 1, canvas.width / 2, canvas.height / 2)

      const dataUrl = canvas.toDataURL()
      arr.push(dataUrl)
    }

    return arr
  }

  async function generateVideo(images) {
    console.log('generateVideo');
    //const ffmpeg = createFFmpeg({ log: true })
    const ffmpeg = createFFmpeg({
      corePath: 'ffmpeg-core.js',
      log: true
    });

    await ffmpeg.load()

    images.forEach(async (image, i) => {
      await ffmpeg.write(`image${i}.png`, image)
    })

    await ffmpeg.run('-r 1 -i image%d.png -pix_fmt yuv420p output.mp4')
    const data = ffmpeg.read('output.mp4')
    return data
  }

  function createObjectUrl(array, options) {
    console.log('createObjectUrl');
    const blob = new Blob(array, options)
    const objectUrl = URL.createObjectURL(blob)
    return objectUrl
  }

  function insertVideo(src) {
    console.log('insertVideo');
    const video = document.createElement('video')
    video.controls = true

    video.onloadedmetadata = () => {
      document.body.appendChild(video)
    }

    video.src = src
  }

  const div = document.createElement('div')
  div.innerText = '動画生成中'
  document.body.appendChild(div)

  const images = generateImages()
  const video = await generateVideo(images)
  const objectUrl = createObjectUrl(, { type: 'video/mp4' })
  insertVideo(objectUrl)

  document.body.removeChild(div)
})()

35-38行目が重要です。クロスオリジンアイソレーションにしたので自分のドメインのみでFFmpegを準備しなくてはなりません。つまり”https://unpkg.com/@ffmpeg/ffmpeg@0.8.3/dist/ffmpeg.min.js”のように公式ページからDLして使用する事ができません。createFFmpeg内で ffmpeg-core.js、ffmpeg-core.worker.js を参照しに行くので、 ffmpeg.min.jsを含めて全て自分のドメインに置いておく必要があります。

プログラムの動作はこちらで確認できます。

https://g-llc.co.jp/videoTest2.php


以上です。

-プログラミング

執筆者:

関連記事

Cycle inside OOO; building could produce unreliable results. Xcodeで史上最高に難解なエラーでハマった話

あるiPhoneアプリを約2年ぶりにアップデートしようと思い、XcodeでArchiveを実行したら見たことのないエラーが出ました。 Cycle inside OOO; building could …

Railway.appでついにRegion選択が可能になった – 2023年版PaaS選び

PaaS選びの際、Web検索で情報収集をすると多くの記事が見つかりますが、料金情報などを並べただけで実際に利用した情報がないように思われます。本記事では実際に身銭を切っている立場からPaaS選びについ …

サイトの仕様: ナビゲーションを解決する方法。Admobポリシーセンター広告制限

一年ほど前、突如としてAndroidアプリのAdmob広告に配信制限がかかりました。理由は「サイトの仕様: ナビゲーション」。これが厄介で、一体何を修正すべきなのかが全く分かりません。Web検索をかけ …

2重振り子の数値シミュレーション、解析から結果描画まで[Octave / Matlab / Android iPhoneアプリもあるよ]

久々に数値解析をやってみたくなりました。題材としては多くの人がやっている二重振り子が面白そうです。では言語は何にしようかと迷うところですが、コードを書いて計算して結果を描画するまで可能な「Octave …

Xcode iOSアプリでGUIをコードだけで作成する Storyboard, Sceneなし

アプリメンテナンスをしている際、どうしようもないビルドエラーに遭遇して解決できず、新規プロジェクトで作り直すという事がありました。私のiPhoneアプリ開発歴は結構長く、始めたのは2013年あたりです …

スポンサーリンク