「動画から音声だけ取り出したい」——BGMを抜き出したり、会議の録画を音声メモにしたり、地味によくある作業です。でも無料の変換サイトは動画をサーバーにアップロードさせるものが多く、大きいファイルだと時間もかかるしプライバシーも気になります。
そこでこの記事では、動画をアップロードせず、ブラウザの中だけで音声を抽出する方法を、ffmpeg.wasm を使ってまとめます。実装で実際にハマったポイント(SharedArrayBuffer / COOP・COEP / CSP)も共有します。
ゴール:ブラウザ内で MP4 などの動画から音声を抜き出し、MP3 で保存できるようになること。
ffmpeg.wasm とは
ffmpeg を WebAssembly にコンパイルしたもので、サーバーなしでブラウザ内で ffmpeg のコマンドを実行できます。動画→音声の抽出はもちろん、フォーマット変換なども可能です。
まずは CDN 版で最小のサンプルから。
<script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script>
<input type="file" id="file" accept="video/*">
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
document.getElementById("file").onchange = async (e) => {
const file = e.target.files[0];
await ffmpeg.load(); // 初回はコア(数十MB)を読み込む
ffmpeg.FS("writeFile", "input.mp4", await fetchFile(file));
// -vn = 映像を捨てる / libmp3lame で MP3 にエンコード
await ffmpeg.run("-i", "input.mp4", "-vn", "-c:a", "libmp3lame", "-q:a", "2", "output.mp3");
const data = ffmpeg.FS("readFile", "output.mp3");
const url = URL.createObjectURL(new Blob([data.buffer], { type: "audio/mpeg" }));
const a = document.createElement("a");
a.href = url; a.download = "output.mp3"; a.click();
};
ポイントは ffmpeg のコマンドそのままな点です。
-
-vn:映像トラックを除外(音声だけにする) -
-c:a libmp3lame -q:a 2:MP3 でエンコード(-q:aは品質、0が最高〜9が最低) - M4A なら
-c:a aac -b:a 192k output.m4a、WAV なら-c:a pcm_s16le output.wav
ハマりどころ①:SharedArrayBuffer is not defined
@ffmpeg/core のマルチスレッド版(0.11系の既定など)は SharedArrayBuffer を使うため、ページが cross-origin isolation されている必要があります。これを満たさないと SharedArrayBuffer is not defined で落ちます。
サーバー側で、そのページに次の2つのレスポンスヘッダーを付けます。
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
COEP を有効にすると、そのページが読み込むクロスオリジンのリソースは原則ブロックされます(同一オリジンのものはOK)。外部の解析タグや画像などは、このページだけ外すか、CORP対応が必要になります。
ヘッダーを触りたくない場合は、シングルスレッド版のコア(
@ffmpeg/core0.12系)を使うとSharedArrayBuffer不要で、COOP/COEP なしで動きます。ただしコアのファイルサイズは大きめ(30MB前後)です。マルチスレッド版は23MB前後で速い、という違いがあります。
ハマりどころ②:CSP で blob: をブロックしてしまう
ffmpeg.wasm は内部で Blob URL のワーカーを生成し、Blob を fetch します。Content-Security-Policy を設定していると、ここが引っかかります。ローカル(CSPなし)では気づけず、本番で初めて出るので厄介です。
script-src と connect-src に blob: を許可します(worker-src を使っている場合はそちらにも)。
Content-Security-Policy:
script-src 'self' 'wasm-unsafe-eval' blob:;
connect-src 'self' blob:;
worker-src 'self' blob:;
...
-
'wasm-unsafe-eval':WebAssembly のコンパイルに必要 -
blob::ワーカー生成・Blob fetch のため
ハマりどころ③:コアの自ホスティングとサイズ
createFFmpeg({ corePath }) でコアの場所を指定すれば、CDN ではなく自分のサーバーからコアを配信できます(=実行時に外部通信なし・オフライン対応)。
const ffmpeg = createFFmpeg({
corePath: new URL("assets/ffmpeg/ffmpeg-core.js", location.href).href,
log: false,
});
このとき、ffmpeg-core.js と同じ場所に ffmpeg-core.wasm(と、マルチスレッド版は ffmpeg-core.worker.js)を置きます。コアの .wasm は20〜30MBあるので、静的ホスティングの1ファイルサイズ上限(サービスによっては25MB程度)に引っかからないか注意してください。
コードを書かずに使いたいとき
「1回だけ音声を抜きたい」「動画をアップロードしたくない」なら、ブラウザ完結のツールが手軽です。
私が作っている Morphy の「動画→音声」なら、
- 動画を選ぶだけで MP3・M4A・WAV に抽出
- 処理はすべてブラウザ内で完結し、動画はアップロードされません
- 登録不要・無料
初回だけ変換エンジンの読み込みが入りますが、以降はキャッシュされます。
👉 動画→音声 抽出ツール:https://morphytools.com/video-to-audio.html
まとめ
- ffmpeg.wasm を使えば、サーバーなし・ブラウザだけで動画から音声を抽出できる
- コマンドは
-i input.mp4 -vn -c:a libmp3lame output.mp3のように ffmpeg そのまま - マルチスレッド版は COOP/COEP が必要(
SharedArrayBuffer)。避けたいならシングルスレッド版 - CSP を使うなら
blob:の許可を忘れずに(本番で初めてハマりがち)
「動画をアップロードせずに音声だけ欲しい」という需要に、ブラウザ完結はよく合います。参考になれば嬉しいです。