デスクトップを連続撮影してタイムラプス動画にしてみる
きっかけはこちらのページ。最近スクリーンショットのことばかり追跡していたら、コメントで面白い使い道を教えてもらったのだ。(u3さん、ありがとう!)
実験
- まず、command-shift-3で適当な間隔をあけて10枚くらいデスクトップのスクリーンショットを撮影しておく。
- 変化のないデスクトップを動画にしても「動かない動画」=「写真」と同等で面白みがないので、
- いつもの操作をしながら、そのついでに撮影しておく方が、あとで見て面白いはず。
- ところで自分のRetina環境では、デスクトップのスクリーンショットは3840×2400という馬鹿でかいピクセル数になってしまう。
- さすがに動画にした時の再生負荷を考えると気が引けるので、適当な大きさにリサイズしておいた。
- Finderで画像ファイルを選択してコピー、ターミナルで「sips -Z 960 」と入力して、それに続けてペースト、そしてreturnキーで実行した。
$ sips -Z 960 001.png 002.png ... 010.png
- リサイズまで完了したら、デスクトップにworkフォルダを作って、そこに入れておいた。
- 以上で、動画にするための事前準備はすべて完了。
- いよいよ、ffmpegを使って静止画を動画にしてみる。
諸々インストール
その前に、ffmpegがインストールされていない場合...
- ちなみに、ffmpegはHomebrew経由でインストールした。
- さらには、HomebrewのためにはXcodeが必要。
- XcodeはApp Storeからダウンロードできる。
- Xcodeをインストールしたら、以下のツールもインストールしておいた。
- Xcode >> 環境設定...(Preferrence...)>> Downloads >> Command Line Tools
- これでHomebrewをインストールして、
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- やっとffmpegのインストールに辿り着ける。
$ brew install ffmpeg # エラーが出る場合は以下も試す $ brew install --use-clang --HEAD ffmpeg
動画変換(ffmpeg)
- ffmpegが使えるようになったら、デスクトップのworkフォルダに移動して、動画にしてみた。
$ cd ~/Desktop/work $ ffmpeg -r 3 -i %03d.png -vcodec mjpeg -sameq -y ./out.avi
できた、できた!画面の変化の様子がパラパラアニメになっている!
- rオプションを変更すれば、1秒間に再生する画像の枚数を指定できる。
- 上記の例では秒間3枚。
- rオプションなしならデフォルト値(=24枚?)
$ ffmpeg -i ./out.avi -vcodec libx264 -f mp4 -y out.mp4
デスクトップのインターバル撮影
以上の実験で理解した仕組みを可能な限り自動化してみる。
撮影&縮小&ファイル数制限
- 定期的に自分でcommand-shift-3を押すなんてやってられないので、5秒間隔で自動撮影するようにした。
- screencaptureコマンドを使えば、シャッター音なしで静かに実現できる。
- デスクトップにcaptureフォルダを作って、そこに保存しておく。
- 撮影したらすぐに、画像サイズを960pxに縮小しておく。
- また、無制限に画像ファイルが増えてしまっても困るので、最新の100ファイルのみ保持するようにした。
-
-
- ~/Desktop/cap.bash
-
#!/bin/bash # デスクトップを撮影して、最新の100枚だけ保持する fdir="$HOME/Desktop/capture" fname="`date +%s`.png" [ -e $fdir ] || mkdir $fdir cd $fdir screencapture -xC $fname 2>/dev/null sips -Z 960 $fname limit=100 fnum=`ls|wc -l` over=`expr $fnum - $limit` rm `ls 2>/dev/null | head -n$over`
- ターミナルで実行権限を追加しておいた。
$ chmod a+x ~/Desktop/cap.bash
launchdの設定
- そしてlaunchdによって、上記シェルスクリプトを5秒間隔で実行するようにすれば良いのだ。
-
-
- ~/Desktop/com.zarigani.DesktopCapture.plist
-
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.zarigani.DesktopCapture</string> <key>ProgramArguments</key> <array> <string>/Users/zari/Desktop/cap.bash</string> </array> <key>StartInterval</key> <integer>5</integer> </dict> </plist>
- launchdの設定ファイルはxmlであり、慣れないと訳が分からないが、Lingon.appを使えば簡単に設定できる。
- 5秒間隔で、デスクトップに置いたcap.bashを実行する設定である。
- ~/Library/LaunchAgents/com.zarigani.DesktopCapture.plist に置いておくと、ログイン時に自動実行される。
- しかしまだテスト段階なので、~/Desktop/com.zarigani.DesktopCapture.plist に移動しておいた。
launchdによる起動と終了
- launchdに設定ファイルをloadすれば、設定したとおりに5秒間隔で撮影を始める。
$ launchctl load ~/Desktop/com.zarigani.DesktopCapture.plist
- unloadすれば、5秒間隔の撮影を中止する。
$ launchctl unload ~/Desktop/com.zarigani.DesktopCapture.plist
動画に変換する
- そして、デスクトップのcaptureフォルダに画像がたまったら、以下のシェルスクリプトで動画に変換するのだ。
- 動画にするには事前にファイル名が数字の連番になっている必要がある。
- さすがに100ファイルを連番にリネームするのは、手作業ではやってられない...。
- その作業をするために、一時フォルダにcaptureフォルダをコピーして、
- そこで、0001.png 0002.png ...というファイル名に変更している。
- ファイル名が連番になったら、上記実験の要領で動画としてデスクトップに出力している。
-
-
- ~/Desktop/mov.bash
-
#!/bin/bash # 静止画から動画を作成する fdir="$HOME/Desktop/capture" wdir="${TMPDIR}TemporaryItems" [ -e $wdir ] || mkdir $wdir cp -r $fdir $wdir cd "$wdir/`basename $fdir`" # [ヅ] AWK でファイルに連番を振って一括リネームするワンライナー (2011-10-09) # http://www.nilab.info/z3/20111009_05.html ls *.png|awk '{ printf "mv %s %04d.png\n", $0, NR }'|sh # ffmpeg静止画→動画作成苦戦しまくり - ヰタ・デテスタビリス (reuniの研究日記) # http://d.hatena.ne.jp/reuni/20080131/1201771541 #ffmpeg -i %04d.png -y $fdir.mpg # インターバル撮影/インターバル撮影した静止画から動画作成 - matoken's wiki. # http://hpv.cc/~maty/pukiwiki1/index.php?%A5%A4%A5%F3%A5%BF%A1%BC%A5%D0%A5%EB%BB%A3%B1%C6%2F%A5%A4%A5%F3%A5%BF%A1%BC%A5%D0%A5%EB%BB%A3%B1%C6%A4%B7%A4%BF%C0%C5%BB%DF%B2%E8%A4%AB%A4%E9%C6%B0%B2%E8%BA%EE%C0%AE ffmpeg -r 3 -i %04d.png -vcodec mjpeg -sameq -y ./out.avi ffmpeg -i ./out.avi -vcodec libx264 -f mp4 -y $fdir.mp4 rm -fr "$wdir/`basename $fdir`" qlmanage -p $fdir.mp4
-
- データファイル処理に便利なUNIXコマンド - awk
- awk sed 入門
- awkは、lsが出力するテキストデータに対して、1行ごとに{}内のコマンドで処理する。
- $0には、1行すべてのテキストが代入されている。
- NRは、awkコマンドのグローバル変数で、現在処理中の行番号が数値として代入される。
ループがワンライナーになってしまうawkコマンドって素晴らしい!
$ chmod a+x ~/Desktop/mov.bash
AppleScriptにまとめる
- 「launchctl load ~/Desktop/com.zarigani.DesktopCapture.plist」で5秒間隔の連続撮影を始めて、
- 「launchctl unload ~/Desktop/com.zarigani.DesktopCapture.plist」で連続撮影の中止。
- 動画を見るには、~/Desktop/mov.bashを実行する。
- 以上はシンプルなコマンドだけど、いずれ忘れる...。(1ヶ月後には忘れている自信がある)
- だから、AppleScriptに組み込んで、起動中は連続撮影して、終了したら連続撮影を中止するようにしてみる。
- それから、ドラッグ&ドロップで静止画を動画に変換して、プレビューできるようにするのも良さそう。
--起動したら、5秒間隔で連続撮影する
on run
do shell script "launchctl load " & launchd_plist_path() end run
--終了したら、連続撮影をやめる
on quit
do shell script "launchctl unload " & launchd_plist_path() continue quit --quitハンドラではなく、アプリケーションのquitを実行する
end quit
--フォルダをドラッグ&dロップした時の処理
on open drop_items
try
--1つだけかどうか?--複数ではエラーにする
if drop_items's number > 1 then error
--フォルダかどうか?--フォルダでなければエラーになる
tell application "Finder" to folder (drop_items's item 1 as text) set fdir to (drop_items's item 1)'s POSIX path
do shell script mov_bash_path() & space & fdir
on error
"画像の入ったフォルダを1つだけドラッグ&ドロップしてください。"
display dialog result buttons "OK" default button 1 with icon 0
end try
--5秒間隔で連続撮影していない時は即終了する
if not exists_DesktopCapture() then continue quit
end open
on launchd_plist_path() (path to resource "com.zarigani.DesktopCapture.plist")'s POSIX path
end launchd_plist_path
on mov_bash_path() (path to resource "mov.bash")'s POSIX path
end mov_bash_path
on exists_DesktopCapture() try
do shell script "launchctl list | grep com.zarigani.DesktopCapture"
true
on error
false
end try
end exists_DesktopCapture
- 上記AppleScriptを以下の形式で保存しておいた。
- ファイル名 = 「DesktopLogger」
- ファイルフォーマット = アプリケーション
- オプション =「実行後、自動的に終了しない」チェックあり
- そして、デスクトップにあるシェルスクリプト・launchdの設定ファイルは、すべて上記DesktopLoggerのResourcesフォルダに入れておくのだ。
-
-
- ~/Desktop/mov.bash
-
#!/bin/bash # 静止画から動画を作成する # "${1%/}"=パス末尾の/を取り除く # 例: /a/b/c/ -> /a/b/c fdir="${1%/}" wdir="${TMPDIR}TemporaryItems" [ -e $wdir ] || mkdir $wdir cp -r $fdir $wdir cd "$wdir/`basename $fdir`" # AWKでファイルに連番を振って一括リネームするワンライナー ls *.png|awk '{ printf "mv %s %04d.png ", $0, NR }'|sh # 静止画から動画作成 & MP4変換 /usr/local/bin/ffmpeg -r 3 -i %04d.png -vcodec mjpeg -sameq -y ./out.avi /usr/local/bin/ffmpeg -i ./out.avi -vcodec libx264 -f mp4 -y $fdir.mp4 rm -fr "$wdir/`basename $fdir`" qlmanage -p $fdir.mp4
- DesktopLoggerを起動すると5秒間隔の連続撮影が始まり、終了すると連続撮影は停止する。
- DesktopLoggerにcaptureフォルダをドラッグ&ドロップすると、その中の静止画が動画に変換される。
これで、1か月後にすべてを忘れても使えるアプリケーションになった!
追記1
- デスクトップに置いた実験用の~/Desktop/com.zarigani.DesktopCapture.plistに依存してしまう状態になっていたので、
- アプリケーションバンドル内のcom.zarigani.DesktopCapture.plistを使うように修正しました。
--起動したら、5秒間隔で連続撮影する
on run
do shell script "defaults write " & launchd_plist_path() & " StartInterval -int 5"
do shell script "defaults write " & launchd_plist_path() & " ProgramArguments -array " & cap_bash_path() do shell script "launchctl load " & launchd_plist_path() end run
--終了したら、連続撮影をやめる
on quit
do shell script "launchctl unload " & launchd_plist_path() continue quit --quitハンドラではなく、アプリケーションのquitを実行する
end quit
--フォルダをドラッグ&dロップした時の処理
on open drop_items
try
--1つだけかどうか?--複数ではエラーにする
if drop_items's number > 1 then error
--フォルダかどうか?--フォルダでなければエラーになる
tell application "Finder" to folder (drop_items's item 1 as text) set fdir to (drop_items's item 1)'s POSIX path
do shell script mov_bash_path() & space & fdir
on error
"画像の入ったフォルダを1つだけドラッグ&ドロップしてください。"
display dialog result buttons "OK" default button 1 with icon 0
end try
--5秒間隔で連続撮影していない時は即終了する
if not exists_DesktopCapture() then continue quit
end open
on launchd_plist_path() (path to resource "com.zarigani.DesktopCapture.plist")'s POSIX path
end launchd_plist_path
on cap_bash_path() (path to resource "cap.bash")'s POSIX path
end cap_bash_path
on mov_bash_path() (path to resource "mov.bash")'s POSIX path
end mov_bash_path
on exists_DesktopCapture() try
do shell script "launchctl list | grep com.zarigani.DesktopCapture"
true
on error
false
end try
end exists_DesktopCapture
ダウンロード1
追記2
- デスクトップにファイルを保存すると、常にTimeMachineでバックアップの対象となってしまい無駄が多いので、
- デフォルトの保存場所を一時フォルダに変更した。(AppleScriptのpath to temporary items)
- launchdで繰り返しの秒間を10秒以下に設定しても、実際には10秒以上の繰り返し間隔になってしまうことが判明。(自分の環境では)
- 指定した正確な秒数間隔で実行するため、AppleScriptのon idleハンドラを利用する方式に書き換えた。
- シェルスクリプトの実行は、バックグラウンドジョブとして実行するようにした。
- シェルスクリプトに引数を指定できるようにして、各種パラメーターをAppleScriptから変更しやすい仕様にした。
-
-
- DesktopLogger_on_idle.app
-
property interval : 5 --5秒間隔で撮影する
property pxwh : 960 --画像サイズを960px以内に縮小する
property limit : 100 --最新の100枚のスクリーンショットを保持する
property fps : 8 --1秒間に8コマ再生する
--5秒ごとに繰り返す
on idle
--" >& /dev/null &" = バックグラウンド処理で実行するため
do shell script cap_bash_path() & space & capture_folder() & space & pxwh & space & limit & " >& /dev/null &"
return interval
end idle
--フォルダをドラッグ&ドロップした時の処理
on open drop_items
try
--1つだけかどうか?--複数ではエラーにする
if drop_items's number > 1 then error
--フォルダかどうか?--フォルダでなければエラーになる
tell application "Finder" to folder (drop_items's item 1 as text) set fdir to (drop_items's item 1)'s POSIX path
do shell script mov_bash_path() & space & fdir & space & fps
on error
"画像の入ったフォルダを1つだけドラッグ&ドロップしてください。"
display dialog result buttons "OK" default button 1 with icon 0
end try
--5秒間隔で連続撮影していない時は即終了する
--if not exists_DesktopCapture() then continue quit
end open
--Dockアイコンをクリックした時の処理
on reopen
{"キャンセル", "画像フォルダを開く", "動画を見る"} set res to display dialog "" buttons result default button 3 with title (my name as text) if res's button returned is "画像フォルダを開く" then
tell application "Finder" to open (my capture_folder() as POSIX file) else if res's button returned is "動画を見る" then
do shell script mov_bash_path() & space & capture_folder() & space & fps
end if
end reopen
on launchd_plist_path() (path to resource "com.zarigani.DesktopCapture.plist")'s POSIX path
end launchd_plist_path
on cap_bash_path() (path to resource "cap.bash")'s POSIX path
end cap_bash_path
on mov_bash_path() (path to resource "mov.bash")'s POSIX path
end mov_bash_path
on exists_DesktopCapture() try
do shell script "launchctl list | grep com.zarigani.DesktopCapture"
true
on error
false
end try
end exists_DesktopCapture
on capture_folder() (path to temporary items)'s POSIX path & "DesktopLogger_capture"
end capture_folder
-
-
- cap.bash
-
#!/bin/bash # デスクトップを撮影して、最新の100枚だけ保持する # cap.bash fdir pxwh limit fdir="${1:-${TMPDIR}TemporaryItems/DesktopLogger_capture}" fname="`date +%s`.png" [ -e "$fdir" ] || mkdir "$fdir" cd "$fdir" screencapture -xC $fname 2>/dev/null pxwh=${2:-960} sips -Z $pxwh $fname limit=${3:-100} fnum=`ls|wc -l` over=`expr $fnum - $limit` rm `ls 2>/dev/null | head -n$over`
-
-
- mov.bash
-
#!/bin/bash # 静止画から動画を作成する # mov.bash fdir fps # ${変数名:-"abc"} = 変数に値が設定されていなければ"abc"を代入する # ${変数名%/} = パス末尾の/を取り除く(例: /a/b/c/ -> /a/b/c ) fdir="${1:-${TMPDIR}TemporaryItems/DesktopLogger_capture}" fdir="${fdir%/}" wdir=${TMPDIR}TemporaryItems/DesktopLogger_tmp rm -fr $wdir mkdir $wdir cp -r "$fdir"/* $wdir cd $wdir # AWKでファイルに連番を振って一括リネームするワンライナー ls *.png|awk '{ printf "mv %s %04d.png ", $0, NR }'|sh # 静止画から動画作成 & MP4変換 fps=${2:-4} /usr/local/bin/ffmpeg -r $fps -i %04d.png -vcodec mjpeg -sameq -y "$fdir.avi" #ビットレートなどの関係でmp4に変換できなくなることがあるのでコメントアウト #/usr/local/bin/ffmpeg -i $fdir.avi -vcodec libx264 -f mp4 -y $fdir.mp4 rm -fr $wdir qlmanage -p "$fdir.avi"