Instagramのリール動画からffmpegで記事画像を切り出す
Instagramに上げたリールを見返していたら、スニーカーの記事に載せたい場面がそのまま写っていた。キッチンスケールに載せて重さを量っているところ。それと、かかとのロゴのアップ。撮り直すのは面倒だし、動画から抜いたほうが早い。
今回の要点はこれら。
- Instagramの公式APIからは動画の1コマを取れなかった。動画ごと手元に落とすのが結局いちばん早い
- 目当てのコマをいきなり探さない。1秒ごとのサムネイルを1枚の画像に並べて、全体を見てから秒数を決める
- 数字が写っているコマは、前後の秒数でも抜いて見比べる。計測が止まる前のコマだと、嘘の数値を載せることになる
公式APIとブラウザ操作では1コマを取れなかった
最初はInstagram Graph APIで済ませるつもりだった。投稿の情報自体はちゃんと取れる。ただ、動画については表紙のコマを指す thumbnail_url が返ってくるだけで、動画本体のURLは入っていなかった。表紙は自分が使いたい場面じゃない。
次に考えたのが、ブラウザを自動操作してリールを再生し、狙った秒数でスクリーンショットを撮る方法。これも空振りだった。自動操作用に立ち上げたブラウザだと、Instagramの再生機能が動き出さない。動画が再生できる状態かを示す video.readyState は 0(まだ何も読めていない)のまま。映像の縦横のサイズも 0 と返ってくる。そもそも再生が始まっていないので、撮れるのは黒い四角だけ。
ここで諦めて方針を変えた。ブラウザ越しに何とかするのをやめて、動画ファイルを手元に落とし、動画を扱うコマンドで切り出す。
ffmpegとyt-dlpをHomebrewなしで置く
このMacにはHomebrewが入っていない。入れてもよかったんだけど、要るのは2つだけなので、配布されているバイナリを直接置くことにした。
ステップ1: ffmpegを置く
公式サイトのダウンロードページに、macOS向けのビルド済みバイナリへのリンクがある。
Download FFmpegOS別の配布先が並んでいる。macOSは「Static builds for macOS 64-bit」からたどる。ソースからビルドせずに、実行ファイルをそのまま落とせる。
www.ffmpeg.orgGUIアプリではないので
/Applicationsには置かない。PATHの通ったディレクトリ(コマンド名を打つだけで実行できるよう、macOSが実行ファイルを探しに行く場所)に置く。自分は~/.local/binにした。落としてすぐ実行しようとしたら、Gatekeeper(未署名のアプリの実行を止めるmacOSの仕組み)に弾かれた。Chrome経由でダウンロードしたファイルには
com.apple.quarantineという印が付くからで、これを外せば動く。xattr -d com.apple.quarantine ~/.local/bin/ffmpegステップ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.jpgfps=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.jpgcrop の引数は左から順に、幅、高さ、左端からの位置、上端からの位置。横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枚に収まらない。だから間隔を空けても複数枚に分けてもいい、とプロンプト側で許可している。最後の「切る前のコマも残して」は、あとでトリミング位置を変えたくなったときのため。何度か切り直す羽目になってから足した。