Laravel FFMpegでmp3に埋め込まれた画像を取得する

投稿日 最終更新日

2024年11月16日

Laravel FFMpegを使い、mp3に埋め込まれた画像を取得する方法を説明します。

Laravel FFMpegについて

Laravel FFMpegは、FFMpegをPHPで使えるようにしたPHP FFMpegをLaravelで使いやすくしたもの。

今回はそんなLaravel FFMpegを使ってmp3に埋め込まれたカバー画像を取得する。

結論

以下で基本は取得できるはず。

FFMpeg::fromDisk($disk)
    ->open($readPath)
    ->addFilter('-map', '0:v:0')
    ->addFilter('-codec', 'copy')
    ->export()
    ->toDisk($disk)
    ->save($savePath);

ちなみにこれで出力されるコマンドは以下

ffmpeg.EXE -y -i readPath -map 0:v:0 -codec copy -threads 12 savePath

readPathとsavePathはファイル名も含めたパスの必要があるので注意。

何をしているのか

大事なのが-map 0:v:0の部分。
ffmpegのmapオプションを利用している。

公式ドキュメントに記述もあるが、Qiita記事のffmpegのmapを解説するがわかりやすい。

やっていることは、0番目のファイルの、ビデオストリームの、0番目のストリームを選択するという感じ。
0番目のファイルというのは、FFMpegは複数のファイルを操作できるので明示的に記述する必要がある感じ。

コマンドの内容を理解する前にffmpegがカバー付きmp3をどう解釈するか見てみよう。

mp3のffprobe結果をみてみる

カバーアート付きmp3をffmpegがどう解釈しているのかを見てみる。

見てみるmp3は、カバー画像が2つ付いたちょっと特殊なmp3。

$ ffprobe ./sample_cover.mp3

Input #0, mp3, from '.../sample_cover.mp3':
  Metadata:
    encoder         : Lavf59.27.100
  Duration: 00:00:08.05, start: 0.025056, bitrate: 196 kb/s
  Stream #0:0: Audio: mp3 (mp3float), 44100 Hz, stereo, fltp, 128 kb/s
    Metadata:
      encoder         : Lavc59.37
  Stream #0:1: Video: mjpeg (Baseline), yuvj420p(pc, bt470bg/unknown/unknown), 1000x1000 [SAR 120:120 DAR 1:1], 90k tbr, 90k tbn (attached pic)
    Metadata:
      comment         : Cover (front)
  Stream #0:2: Video: png, rgba(pc, gbr/bt709/iec61966-2-1), 460x460 [SAR 4724:4724 DAR 1:1], 90k tbr, 90k tbn (attached pic)
    Metadata:
      comment         : Cover (front)

Streamが3つあるのが分かると思う。
Stream #0:0: Audio: mp3はAudio部分のストリーム。
Stream #0:1: Video: mjpegStream #0:2: Video: pngはこのmp3に埋め込まれたカバー画像のストリーム。

何故かVideoとあるけど、しっかり画像データなので安心して欲しい。

つまり、カバー付きmp3にはオーディオ部分を扱うオーディオストリーム1つと、カバーの枚数だけVideoストリームがあることがわかる。

であれば、取得したカバー付きmp3からオーディオストリーム部分を削除し、一番最初のVideoストリームだけ残せばメインのカバー画像が取得できるよね、という考え。

結果-map 0:v:0で0番目のファイルの、ビデオストリームの中の、0番目のストリームを選択し、それだけを出力することで取得ができた。

画像の形式を指定する

先の例だと->addFilter('-codec', 'copy')を使っているので画像の形式が元の画像に依存する。

これだと扱いが面倒なので、全てWebP形式で出力するようにする。 以下がそのコード。

FFMpeg::fromDisk($disk)
    ->open($readPath)
    ->addFilter('-map', '0:v:0')
    ->addFilter('-vcodec', 'libwebp')
    ->export()
    ->toDisk($disk)
    ->save($savePath);

自分でcodecの種類を調べてもらえればwebp以外ももちろん可能なはず。

以上です。