ffmpeg で岡持ちMac Pro(Mid 2010)のディスプレイインカメラから繰り返し撮影した静止画像を自動生成する 定点カメラ を組んだ前回に続き、今回は画像をffmpegで連結して1つの タイムラプス 動画ファイルへ変換する処理に取り組みます。
ストレージ内に溢れる撮影画像
前回、岡持ちMac Pro(Mid 2010)のApple LED Cinema Display (27”) の上部インカメラをffmpegで定点カメラのように静止画像を自動撮影する仕組みを構築しました。その後なかなかタイムラプス動画にする仕組みに取り掛かれずに放置していたので、ストレージ内は撮影画像の山に。
前回作ったバッチは、 yyyymmdd 名で作ったフォルダ内に毎分撮影した静止画像を日付時刻名で保存していて、次のようなフォルダツリー構成になります。
1 2 3 4 5 6 7 8 9 10 |
iSight/ ├── 20211118 │ ├── cam_20211118_151339.jpg │ │ . │ │ . │ └── cam_20211118_195902.jpg ├── 20211119 ├── 20211122 . . |
今回はこの日ごとに小分けされたサブフォルダ毎にタイムラプス動画を生成し、問題なければその日のフォルダごと画像を削除し、タイプラプス動画ファイルも決められた個数を維持するよう、自動処理バッチを構築します。
ffmpegによるjpg画像の連結
ffmpegでは、連番ファイル名の画像群から以下の要領で簡単にタイムラプス動画を生成することが出来ます。
1 2 |
$ ffmpeg -hide_banner -v error -framerate 2 -f image2 -pattern_type glob -i 'cam*.jpg' -pix_fmt yuv422p -vcodec libx264 \ -threads 4 -segment_format mp4 -strftime 1 "tl_%Y%m%d.mp4" |
-framerate は秒当りのコマ数で、 1 にすると毎秒1コマ、画像は毎分撮影したものなので、1時間の画像をパラパラ閲覧するのに1分かかる計算になります。これでは少し遅い一方、あまり速くしてしまうと確認出来なくなるので、 2 としました。
h265エンコーディングで小さく
日当り3時間180枚の画像の総容量は11MB程度なのに対し、生成したタイムラプス動画がこれを上回るサイズになるのは納得出来ないので、h265エンコーディングに変えてフレームレートの違いともあわせてファイルサイズを比較してみました。
1 2 3 4 5 6 7 8 9 10 |
$ du -shc ~/Pictures/iSight/* 11M /20220301 13M /20220302 11M /20220303 11M /20220304 11M /20220307 21M tl_20220301_x264_frate1.mp4 18M tl_20220301_x264_frate2.mp4 11M tl_20220301_x265_frate1.mp4 7.5M tl_20220301_x265_frate2.mp4 |
これにより、h265エンコーディング、フレームレート2とするのが現実的でしょう。
ちなみにh265エンコーディングをしていると、 loglevel を error にしていても次のような出力が抑制されずに出てきてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
x265 [info]: HEVC encoder version 2.6 x265 [info]: build info [Linux][GCC 7.2.0][64 bit] 8bit+10bit+12bit x265 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX x265 [info]: Main 4:2:2 10 profile, Level-4 (Main tier) x265 [info]: Thread pool created using 4 threads x265 [info]: Slices : 1 x265 [info]: frame threads / pool features : 4 / wpp(16 rows) x265 [info]: Coding QT: max CU size, min CU size : 64 / 8 x265 [info]: Residual QT: max TU size, max depth : 32 / 1 inter / 1 intra x265 [info]: ME / range / subpel / merge : hex / 57 / 2 / 2 x265 [info]: Keyframe min / max / scenecut / bias: 2 / 250 / 40 / 5.00 x265 [info]: Lookahead / bframes / badapt : 20 / 4 / 2 x265 [info]: b-pyramid / weightp / weightb : 1 / 1 / 0 x265 [info]: References / ref-limit cu / depth : 3 / on / on x265 [info]: AQ: mode / str / qg-size / cu-tree : 1 / 1.0 / 32 / 1 x265 [info]: Rate Control / qCompress : CRF-28.0 / 0.60 x265 [info]: tools: rd=3 psy-rd=2.00 rskip signhide tmvp strong-intra-smoothing x265 [info]: tools: lslices=6 deblock sao x265 [info]: frame I: 1, Avg QP:21.16 kb/s: 1667.01 x265 [info]: frame P: 38, Avg QP:15.08 kb/s: 1057.31 x265 [info]: frame B: 141, Avg QP:18.63 kb/s: 865.45 x265 [info]: Weighted P-Frames: Y:23.7% UV:18.4% x265 [info]: consecutive B-frames: 2.6% 2.6% 5.1% 10.3% 79.5% |
これはコーデック指定の後ろに、x265向けのパラメータ -x265-params log-level=error を追加することで抑制できました。
以上より、タイムラプスを生成するためのffmpegパラメータは次のようになります。
1 2 |
$ ffmpeg -hide_banner -v error -y -framerate 2 -f image2 -pattern_type glob -i "cam*.jpg" -pix_fmt yuv422p \ -vcodec libx265 -x265-params log-level=error -threads 4 -segment_format mp4 out.mp4 |
動画ファイルの妥当性チェック
静止画からタイムラプス動画を生成し終えたら画像ファイル群を削除したいのですが、その前に動画ファイルに問題無いか、その妥当性をチェックしたいと思います。チェックにはいくつかの方法がある中で、今回使ってみるのはこちらのフォーラムで見掛けた ffmpeg -f null に掛けてみる手法です(Great Tnx!!)。
まず生成したタイムラプス動画をチェックしてみると、正常な動画・画像ファイルでは無出力で、 $? は になります。
1 2 3 |
$ ffmpeg -v error -i tl_20220301.mp4 -f null - 2>&1 $ echo $? 0 |
次に拡張子を詐称したファイルをチェックに掛けてみると不正が見つかり、 $? は 1 になります。
1 2 3 4 5 |
$ ffmpeg -v error -i db.mp4 -f null - 2>&1 [mov,mp4,m4a,3gp,3g2,mj2 @ 0x5652baf878c0] moov atom not found db.mp4: Invalid data found when processing input $ echo $? 1 |
以上より、 $? を見て処理を切り分けることは出来そうです。
サブフォルダの一覧ループ処理
サブフォルダの一覧を取得してのループ処理は以下の要領で簡単ですが、得られるのがフルパスであることに少し注意が必要。末尾のフォルダ名のみが欲しい場合は、 basename $d とする必要があります。
1 2 3 4 5 6 7 8 9 10 |
for d in */ ; do echo "$d" done #実行結果# /Users/user/Pictures/iSight/20220301/ /Users/user/Pictures/iSight/20220302/ /Users/user/Pictures/iSight/20220303/ /Users/user/Pictures/iSight/20220304/ /Users/user/Pictures/iSight/20220307/ |
保持ファイル上限を設けて古いものから削除
保持するタイムラプス動画数に上限を設け、それを上回る場合は古いものから削除する、という処理は以前、こちらの記事でさまざまなプラットフォームを例に紹介しました。
その中のシェルスクリプト版をベースに、macOS向けのデバッグを進めて、以下の3点について修正を施しました。
1.findの振る舞い
Linuxと同じ要領で find へパスを渡すと、渡したパスの末尾にスラッシュで重なってしまいます。
1 2 3 4 5 6 7 8 9 |
$ find /Users/user/Pictures/iSight/ -maxdepth 1 -type f -size +0c /Users/user/Pictures/iSight//tl_20220221.mp4 /Users/user/Pictures/iSight//tl_20220209.mp4 /Users/user/Pictures/iSight//tl_20220208.mp4 /Users/user/Pictures/iSight//tl_20220222.mp4 /Users/user/Pictures/iSight//tl_20220223.mp4 /Users/user/Pictures/iSight//.DS_Store /Users/user/Pictures/iSight//tl_20220218.mp4 /Users/user/Pictures/iSight//tl_20220224.mp4 |
これは find へ渡すパスの後ろに * を付与して回避出来ますが、そうすると対象階層を指定する depth の起点がずれてしまうので、カレントディレクトリのみ対象とするには、 -maxdepth 0 とします。そして、Finderが生成した .DS_Store などの隠しファイルを除外する為の条件を追加して、ようやく期待通りの結果を得ることが出来ました。
1 |
$ find /Users/user/Pictures/iSight/* -maxdepth 0 -type f -size +0c -not -name '.*' |
2.xargsオプションの差異
find の結果を渡す xargs のオプションに -r0 と言うのを使っていると怒られるのですが、これは単に以下のようにして解決。
1 |
xargs -0 |
3.lsのモダンなオプションが無い
ls の出力にパスやファイル名に空白などのダメ文字が含まれる場合を想定して、クオーテーションで常に囲む次のオプションを利用していたのですが、これもありません。
1 |
ls --quoting-style=shell-always |
これは以前検証した、BSDベースのAlt-Fと同じなのですが、macOSではダメ文字が含まれる場合には自動的にクオーテーションで囲んでくれるようなので、Alt-Fの時のようにエスケープで逃げる処理は加えませんでした。
完成したバッチ
こうして出来上がったバッチファイルは以下の通りです(cronで呼び出すつもりなので、ffmpegはフルパスで記述)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#!/bin/bash basedir="/Users/user/Pictures/iSight/" keepFiles=30 echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Batch Start" for dir in $basedir*/ ; do echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Target Folder: $dir" sub=`basename $dir` tlfile=$basedir"tl_"$sub".mp4" # Gen. TimeLapse echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] TimeLapse Encoding Start" # /usr/local/bin/ffmpeg -hide_banner -v error -y -framerate 2 -f image2 -pattern_type glob -i $dir"cam*.jpg" -pix_fmt yuv422p -vcodec libx264 -threads 4 -segment_format mp4 $tlfile /usr/local/bin/ffmpeg -hide_banner -v error -y -framerate 2 -f image2 -pattern_type glob -i $dir"cam*.jpg" -pix_fmt yuv422p -vcodec libx265 -x265-params log-level=error -threads 4 -segment_format mp4 $tlfile echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] TimeLapse Encoding End" # Vid Validity Check / Whether Delete Dir sleep 2 echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Video File Check: $tlfile" /usr/local/bin/ffmpeg -v error -i $tlfile -f null - 2>&1 if [ $? -eq 0 ]; then echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Video File Normal" echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] About to Delete: $dir" rm -rf $dir else echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Video File Corrupt" echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Rename Video File to: $tlfile.corrupt" mv $tlfile $tlfile".corrupt" fi done numFiles=`find "$basedir"* -maxdepth 0 -type f -size +0c -not -name '.*' | wc -l` #macOS (BSD) #numFiles=`find $basedir -maxdepth 1 -type f -size +0c | wc -l` echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Total Video Files Here: $numFiles" if [ $numFiles -le $keepFiles ]; then echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] No Need to delete any files, Batch end." exit 1 else delFiles=`expr $numFiles - $keepFiles` echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] About to delete $delFiles Files..." # macOS (BSD) # find "$basedir"* -maxdepth 0 -type f -size +0c -not -name '.*' -print0 | xargs -0 ls -1t | tail -n $delFiles #DRY_RUN find "$basedir"* -maxdepth 0 -type f -size +0c -not -name '.*' -print0 | xargs -0 ls -1t | tail -n $delFiles | xargs rm if [ $? -ne 0 ]; then echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] An error ocurred deleting the files, Batch abnormal end." exit 1 else echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] $delFiles file(s) Deleted, Batch end." fi fi |
これを前回作った静止画像撮影タスクが終わる頃合いで始めるよう、cronへ登録して数日動かしたところで、ファイルに埋め尽くされていたストレージに秩序が戻りました。