mp4等の動画ファイルからwebmでプレビューを作成する方法
TL;DR
プレビューが未作成の場合のみ実行:
やりたいこと
よく動画サイトとかであるホバーするとプレビューが表示されるのを作りたい。
検討した方法
- spriteイメージにて実現
- webpアニメーションにて実現
- webmにて実現
検討の結果3のwebmがメンテナビリティも高く、コードも複雑にならなさそうなので採用。
評価詳細
読みたい人用
# | 実現方法 | 実装イメージ | いいところ | わるいところ |
---|---|---|---|---|
1 | sprite |
css ・ background とbackground-position でごにょごにょする |
・枯れてる ・単純 |
・フレームが入れ替わる度にJSを発火させる必要があるが、処理を書くのがめんどくさそう (特に複数ある場合に同時に動かすと重くなりそうなので、同期処理機構がほしくなりそう) |
2 | webp |
html ・webpを作成し、 <img> タグで埋め込む |
・実装はシンプル | ・一時停止不可 ・作成方法は単純明快(連番で画像を作って、それをアニメーションにするだけ) |
3 | webm |
html ・webmを作成し、 <video> タグで埋め込む |
・実装はシンプル ・一時停止もDOMから直接触れるメソッドを呼ぶだけで簡単 |
・動画ファイルを作るのに秘伝のタレ的な技術が必要 |
とりあえず、2と3を検討。結果は3を採用。
webmの作成方法
基本的にはffmpegから呼ぶだけ。brewで入るffmpegではデフォルトでVP9が使える(libxvid
)ため、特に何も考えずにbrew install ffmpeg
しとけば環境はできる。
作成の流れ
デコード設定
プレビュー作成のためにどうせフレームを間引くことが決定しているため、追加のデコード処理はしたくない。
そのため、-skip_frame nokey
や-fps_mode passthrough
でキーフレームのみを渡すように指示する。
-skip_frame
は動画の読込時、-fps_mode
はStreamが渡される際のオプションのため、-i
との前後関係に注意する。(-skip_frame
→ -i
→ -fps_mode
の順番で設定するのが正しい)
参考: キーフレームとは?Iフレーム・Pフレーム・Bフレームの違い
vfフィルター
vfフィルターは3つ指定する
setpts
setpts={VAR}
で該当フレームにタイムスタンプを付与する。
今回はプレビューを作りたいので、適当に元の150倍ぐらいになるように、setpts=PTS/150
で現在のタイムスタンプを圧縮する。
参考: ffmpeg documentation setpts
fps
fps={VAR}
で出力する動画のfpsを指定する。
プレビューでは24fpsで動かす必要もないので、ある程度紙芝居的に動くfps=6
を指定する。
こうすることで、6fpsに近いタイムスタンプに該当するフレームが抽出され、その他のフレームが間引かれる。
参考: ffmpeg documentation fps
scale
scale={VAR}
で出力する動画のサイズを指定する。
プレビューでは小さくても問題ないので、scale=320:180
(16:9動画の場合)ぐらいで指定しておく。
エンコード設定
webmを使うため、 VP9でのエンコード設定を探す。
過去にVP9のエンコードはやったことがないため、先人たちの知恵を拝借する。
基本的にはニコラボさんの解説を元に設定を行う。
品質設定
プレビュー動画の場合はファイルサイズ最適化の余地も少ないため、1passにてエンコードを行うべく、crfでの設定を採用。
上記サイトにある推奨設定よりも出力サイズが小さいため、上限値の-crf 37
をとりあえず採用。
上記サイト冒頭にもあるように忘れずに-b:v
も設定。
CPU設定
こちらも上記サイトの推奨設定より設定。
一番小さいサイズの設定を参考に、-cpu-used 0 -row-mt 1 -thread 1
を設定。
(-thread
の設定値が2の指数であることに注意。今回は2 threadsで動作させたいため、2^1となる1を設定)
参考: タイリングとスレッド化に関する推奨事項
最終的なコマンド
local file="hoge.mp4"
ffmpeg -hide_banner -nostats -skip_frame nokey -i "$file" -fps_mode passthrough -an -vf setpts=PTS/150,fps=6,scale=320:180 -vcodec libvpx-vp9 -crf 37 -b:v 0 -threads 1 -row-mt 1 -cpu-used 0 "${file:t:r}.webm"
webpの作成方法
採用しなかったが一応実装したのでメモ書き程度に。
ffmpegに加え、webpmuxというアニメーションwebpを使えるユーティリティツールを利用する。
(brew install ffmpeg
していれば、依存関係で既に入っているため、特に追加の環境設定は不要)
作成の流れ
webmに比べると多少複雑に見えるが、webmを出力して、それをpackするだけの実装。
実装
特にwebmuxがフレームごとにオプションの指定が必要なため、簡単なshellscriptを書いて対応した。
function create_preview () {
local file=$1
local TMP_DIR=$(mktemp -d)
mkdir -p "${TMP_DIR}/${file:t:r}"
ffmpeg -hide_banner -nostats -skip_frame nokey -i "$file" -fps_mode passthrough -an -vf "fps=fps=1/90,scale=320\:180" -vcodec libwebp -preset picture "${TMP_DIR}/${file:t:r}/"%04d.webp
local -a frames
(){
emulate -L zsh -o extended_glob
for f in ${TMP_DIR}/${file:t:r}/*.webp; do
frames+="-frame ${f} +250"
done
}
eval "webpmux $frames -o ${file:t:r}.webp"
rm -r "${TMP_DIR}/${file:t:r}"
}
問題点
<img>
タグを利用する場合、DOMがHTMLMediaElementではなく、HTMLImageElementであるため、再生を止めたり・再開したりするAPIがnativeに存在しない。
gifアニメーションの一時停止を可能とするfreezframe.js等のライブラリの実装も確認したが、自らCanvasに描画する等の方法をとっており、今回のユースケースだと<img>
タグ単体での実装は難しそうであることがわかった。
逆に、loopが前提となるアイコンやローディング画像であったりすると適している方法と思われるため、備忘録として記録を残しておく。
Discussion