← knym.net

Instagramのリール動画からffmpegで記事画像を切り出す

·14 min read
ffmpegyt-dlpInstagramブログ運営画像

Instagramに上げたリールを見返していたら、スニーカーの記事に載せたい場面がそのまま写っていた。キッチンスケールに載せて重さを量っているところ。それと、かかとのロゴのアップ。撮り直すのは面倒だし、動画から抜いたほうが早い。

今回の要点はこれら。

公式APIとブラウザ操作では1コマを取れなかった

最初はInstagram Graph APIで済ませるつもりだった。投稿の情報自体はちゃんと取れる。ただ、動画については表紙のコマを指す thumbnail_url が返ってくるだけで、動画本体のURLは入っていなかった。表紙は自分が使いたい場面じゃない。

次に考えたのが、ブラウザを自動操作してリールを再生し、狙った秒数でスクリーンショットを撮る方法。これも空振りだった。自動操作用に立ち上げたブラウザだと、Instagramの再生機能が動き出さない。動画が再生できる状態かを示す video.readyState は 0(まだ何も読めていない)のまま。映像の縦横のサイズも 0 と返ってくる。そもそも再生が始まっていないので、撮れるのは黒い四角だけ。

ここで諦めて方針を変えた。ブラウザ越しに何とかするのをやめて、動画ファイルを手元に落とし、動画を扱うコマンドで切り出す。

ffmpegとyt-dlpをHomebrewなしで置く

このMacにはHomebrewが入っていない。入れてもよかったんだけど、要るのは2つだけなので、配布されているバイナリを直接置くことにした。

  1. ステップ1: ffmpegを置く

    公式サイトのダウンロードページに、macOS向けのビルド済みバイナリへのリンクがある。

    Download FFmpeg

    OS別の配布先が並んでいる。macOSは「Static builds for macOS 64-bit」からたどる。ソースからビルドせずに、実行ファイルをそのまま落とせる。

    www.ffmpeg.org

    GUIアプリではないので /Applications には置かない。PATHの通ったディレクトリ(コマンド名を打つだけで実行できるよう、macOSが実行ファイルを探しに行く場所)に置く。自分は ~/.local/bin にした。

    落としてすぐ実行しようとしたら、Gatekeeper(未署名のアプリの実行を止めるmacOSの仕組み)に弾かれた。Chrome経由でダウンロードしたファイルには com.apple.quarantine という印が付くからで、これを外せば動く。

    xattr -d com.apple.quarantine ~/.local/bin/ffmpeg
  2. ステップ2: yt-dlpを置く

    yt-dlpはGitHubで配布されている。配布ページ(Releases)にある yt-dlp_macos を落として、こっちも ~/.local/bin へ。quarantineの外し方は同じ。

    yt-dlp/yt-dlp

    動画サイトから動画を取得するコマンド。macOS向けには、Pythonを別途入れなくてもそのまま動く単体の実行ファイル yt-dlp_macos がReleasesに置かれている。

    GitHub

順番はffmpegが先のほうがいい。Instagramのリールは映像と音声が別々に落ちてくるので、yt-dlpは最後にffmpegを呼んで1つのmp4に結合する。ffmpegが無いと、そこで止まる。

yt-dlp -o "reel.%(ext)s" https://www.instagram.com/reel/xxxxxxxxxxx/

コンタクトシートを作ってから秒数を決める

ここが効率の分かれ目だった。

最初は勘で3秒あたり、5秒あたりと1コマずつ抜いては開いて、また抜いては開いて、を繰り返していた。15秒しかない動画なのに、10回以上ffmpegを叩いている。さすがに馬鹿らしくなって、1秒ごとのサムネイルを1枚のタイル画像にまとめた。

ffmpeg -y -i reel.mp4 -vf "fps=1,scale=200:-1,tile=4x4" -update 1 contact.jpg

fps=1 で1秒に1枚。scale=200:-1 で横200pxに縮める(-1 は縦横比を保った高さ)。tile=4x4 で16枚を1枚に並べる。15秒の動画なら、これで全部入る。左上が0秒。右へ1枚進むごとに1秒進んで、右端まで行ったら次の行の左端に戻る。開いた瞬間に、スケールの表示が写っているのは10秒あたり、ロゴのアップは13秒あたり、と分かった。

ちなみに -update 1 を付けないと「連番のパターンが無い」という警告が出る。警告が出ても画像はちゃんと書き出されるんだけど、エラーに見えて紛らわしいので付けている。

秒数さえ決まれば、あとはその1コマを元の解像度で抜くだけ。

ffmpeg -y -ss 9.8 -i reel.mp4 -frames:v 1 -q:v 2 -update 1 out.jpg

-ss-i より前に置くと、先頭から順に読まずその位置へ飛ぶので速い。-q:v 2 はJPEGの品質で、数字が小さいほど高画質になる。

数字が写っているコマは複数の秒数で抜く

危なかったのがここ。

スケールの表示は、9.4秒のコマでは138g、9.8秒のコマでは155gだった。0.4秒の差で17gも違う。片足を置いた直後で、まだ数字が上がっている途中だったからだ。

コンタクトシートの小さいサムネイルでは、そこまで読めない。結局、9秒台だけを0.2秒刻みで何枚か抜いて、表示が変わらなくなったところを採った。155g。最初の1枚をそのまま使っていたら、実際とは違う重さを載せるところだった。

縦長のまま置かず、被写体の帯だけ切る

リールは9:16の縦長だから、抜いたコマも1080x1920になる。そのまま記事に置くと、本文の幅に対して画像がひょろ長い。両脇が余る。

被写体が写っている横帯だけを切り出して、横長にした。

ffmpeg -y -i out.jpg -vf "crop=1080:608:0:660,scale=1200:-1" -q:v 2 -update 1 crop.jpg

crop の引数は左から順に、幅、高さ、左端からの位置、上端からの位置。横1080x縦608の範囲を、上から660pxの位置で切り出している。これでだいたい16:9になる。本文幅いっぱいで見せられるし、同じ表示面積でも被写体は大きく写る。

撮り直さずに、計測の数字を画像で見せられる

やったことは、すでに撮ってあった15秒の動画から2コマ抜いて、トリミングしただけ。新しく撮影はしていない。それでも「軽い」という感想しかなかったところに、スケールの表示が読める画像と155gという数字が加わった。

効くのは、計測や開封みたいに撮り直しがきかない場面だと思う。写真だと構えた瞬間の1枚しか残らない。動画を回しておけば、あとから何コマでも選べる。

Claudeに投げたプロンプト

ここまでのコマンドは、実際には自分で打っていない。Claude Codeに実行させている。同じことをやりたい人のために、投げたプロンプトを置いておく。ffmpegもyt-dlpも入っていない状態から始めたので、インストールも任せた。

このInstagramのリールから、記事に使う静止画を切り出したい。
https://www.instagram.com/reel/xxxxxxxxxxx/
 
- 動画の取得は yt-dlp を使う。ffmpeg と yt-dlp が入っていなければ、
  Homebrew は使わず配布バイナリを ~/.local/bin に置いて、
  quarantine 属性の解除までやってほしい
- いきなり目当てのコマを探さないこと。まず1秒ごとのサムネイルを
  4x4 のコンタクトシート1枚にまとめて見せて
- 使いたい場面を指定したら、その前後を0.2秒刻みで数枚抜いて並べて。
  スケールの数字が写っているので、表示が変わらなくなったコマを選びたい
- 選んだコマは、被写体のある横帯だけを切って16:9くらいにして、
  横幅1200pxで書き出して

肝は2番目と3番目、つまり「まずコンタクトシートを作らせる」と「前後を0.2秒刻みで抜かせる」の2つ。ここを書いておかないと、いきなり適当な秒数のコマを抜いてきて「これでどうですか」と聞かれることになる。何度かやり直させた末に、最初からコンタクトシートを作らせる形に落ち着いた。

リール限定の話ではない。iPhoneで撮った動画が手元にあって、その中の特定の場面を画像にしたいときは、こういうプロンプトにしている。

IMG_4821.MOV から、蓋を開けた瞬間の場面を静止画で切り出したい。
 
- まず1秒ごとのサムネイルを4x4のコンタクトシートにまとめて見せて。
  1枚に収まらない長さなら、間隔を空けるか複数枚に分けていい
- 秒数を指定するので、その前後1秒を0.2秒刻みで抜いて並べて
- 手ブレやピンボケが混ざるはずなので、いちばん止まっているコマを選びたい
- 決まったら、被写体のある部分だけを切って横幅1200pxで書き出して。
  切る前のコマも消さずに残しておいて

4x4は16枚なので、16秒を超える動画は1秒ごとだと1枚に収まらない。だから間隔を空けても複数枚に分けてもいい、とプロンプト側で許可している。最後の「切る前のコマも残して」は、あとでトリミング位置を変えたくなったときのため。何度か切り直す羽目になってから足した。