Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2012-11-28

デスクトップを連続撮影してタイムラプス動画にしてみる

きっかけはこちらのページ。最近スクリーンショットのことばかり追跡していたら、コメントで面白い使い道を教えてもらったのだ。(u3さん、ありがとう!)

実験

  • まず、command-shift-3で適当な間隔をあけて10枚くらいデスクトップのスクリーンショットを撮影しておく。
    • 変化のないデスクトップを動画にしても「動かない動画」=「写真」と同等で面白みがないので、
    • いつもの操作をしながら、そのついでに撮影しておく方が、あとで見て面白いはず。
  • 10枚撮影したら、ファイル名を連番に変更しておく。
    • 10枚くらいなら頑張って手作業でOK。
    • 例:001.png 002.png ... 010.png
    • 撮影時間順に古い方が001、新しい方が010。
  • ところで自分の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

20121127085720

  • これで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枚?)
  • 上記の形式はモーションJPEGなので動画サイズが巨大になりがち。(1.1MB)
    • JPEGのパラパラアニメなので、JPEG画像×枚数分のサイズなってしまう。
  • そこで、再びffmpegを使って、今度はmpeg4に変換してみる。(213KB)
$ ffmpeg -i ./out.avi -vcodec libx264 -f mp4 -y out.mp4
動画変換(Quicktime Pro)

デスクトップのインターバル撮影

以上の実験で理解した仕組みを可能な限り自動化してみる。

撮影&縮小&ファイル数制限
  • 定期的に自分で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を使えば簡単に設定できる。

20121127112234

  • 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
  • 当初、ファイル名を連番にするために、forループを使って一生懸命やろうとしていたが、
  • awkを使えばワンライナーで素早く完了してしまうことを知って驚愕!(素晴らしい情報に感謝です!)
    • よって、1行目を処理中の場合、awkの処理式は以下のような状態になっている。
    • 「awk '{ printf "mv %s %04d.png\n", "XXXXXXXX.png", 1 }'」
    • awkはprintf書式に従って、「mv XXXXXXXX.png 0001.png」を出力する。
    • そして「mv XXXXXXXX.png 0001.png」をshにパイプで渡すと、コマンドとして実行されるのだ。
    • つまり、lsで出力されたファイル名を、順番(デフォルトでファイル名順になっているはず)に0001.png、0002.pngと連番にすることになるのだ!

ループがワンライナーになってしまうawkコマンドって素晴らしい!

  • その後は、実験した時と同じようにffmpegコマンドを実行して、動画を生成している。
  • 最後に、不要になった一時フォルダのcaptureを削除して、
  • 生成された動画をクイックルックで表示している。
  • 上記シェルスクリプトを~/Desktop/mov.bashとして保存し、
  • ターミナルで実行権限を追加しておいた。
$ 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」
    • ファイルフォーマット = アプリケーション
    • オプション =「実行後、自動的に終了しない」チェックあり

20121128085046

  • そして、デスクトップにあるシェルスクリプト・launchdの設定ファイルは、すべて上記DesktopLoggerのResourcesフォルダに入れておくのだ。
    • ~/Desktop/com.zarigani.DesktopCapture.plist
    • ~/Desktop/cap.bash
    • ~/Desktop/mov.bash(↓若干の修正を加えた↓)

20121128084549

      • ~/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ハンドラを利用する方式に書き換えた。
  • シェルスクリプトの実行は、バックグラウンドジョブとして実行するようにした。
  • ビットレートなどの関係か、モーションJPEGをMP4に変換するときエラーが発生してしまうことがあるので、MP4変換はやめた。
  • シェルスクリプトに引数を指定できるようにして、各種パラメーターを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"

ダウンロード2

小泉小泉 2012/11/28 12:51 こんにちは。いつもためになります。

私も以前間違えていましたが、タイム「プラス」→タイムラプスですね。

zariganitoshzariganitosh 2012/11/28 12:57 小泉 さん、

いつもコメントありがとうございます!
たいへん失礼しました。(恥ずかしい)
教えていただきありがとうございます。
さっそくタイトルを変更させていただきました。
一方、URLは変更するといろいろ問題がありそうなので、timeplusのままにしておきます。

u3u3 2012/11/28 21:42 こんにちは。

機能がとってもグレードアップしていてびっくりしました!!
アプリが魅力的なので即ダウンロードして使ってしまいたいところですが、
ひとつひとつじっくり勉強(マネ?)してみて、理解しながら実装してみようと思います。

後で忘れてしまっても使えるようにしておくところは特に身につけたいところです…
よく以前つくったコードを理解するのに難儀したりするので(苦笑)

ご紹介頂いたLingonを購入して触りはじめているのですが、
いろいろな事が出来るようになりそうでワクワクしています。
記事にして頂いて本当にありがとうございました。

では!

zariganitoshzariganitosh 2012/11/30 06:35 撮影する秒間隔や、再生する時のスピード、画像に変化がない時は無視する設定などを追加すると、
かなり本格的なアプリケーションになるかもしれませんね。

おっと、Lingonはいつの間にかバージョン3になって、Appストアに並んでいたのですね。
自分が使っているのは初期のバージョンのようです。(ずいぶん前にダウンロードしたもの)
できることは限られるのですが、かなりシンプルな設定画面が気に入っています。
以下のページから歴代のバージョンもダウンロードできるようです。
http://sourceforge.net/projects/lingon/

zariganitoshzariganitosh 2012/11/30 07:54 使用中のLingonのバージョンは2.1.1でした。

zariganitoshzariganitosh 2012/11/30 08:01 http://sourceforge.net/projects/lingon/files/Lingon/

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/zariganitosh/20121128/desktop_logger_timeplus
リンク元