ターミナル操作の記録(ttyrec)からGIFアニメを生成するツールを作った

ttyrec で録画したデータを使ってターミナル上で再生しつつ、そのスクリーンキャプチャを使ってアニメーションGIFを生成するツールをGoで作ってみた。

Mac, Linux Desktopで動作。再生速度はオプションで変更可能。

背景

percolを使ってターミナル操作を早く、便利に。 - すぎゃーんメモ のような記事を書いたりする際に、ターミナル操作を録画してGIFアニメにしたい需要があり。
そういった用途に使える汎用のデスクトップ録画ツールとしてはLICEcapやGifzoなどがある。

これらはWindows, OSXあたりが対象で、Linuxの場合はまた他のツールがあるらしい(よく知らない)。

汎用的なキャプチャツールでも良いけれど、いちいち録画対象のターミナルのウィンドウを指定するのも面倒だし、ターミナル専用のツールがあっても良いのではないか。
ttyrec というツールが、古くから存在する。これを使うとコマンドからターミナルの操作を録画・再生することができる。
これで各コマを再生しつつターミナルのスクリーンショットを撮り、それらを繋げてGIFアニメにする、というツールも既に幾つか存在する。

これらは大抵 ImageMagick に依存しており、importコマンドを利用してスクリーンショットを撮ったり、convertコマンドでGIFを生成したりしている。言語はC/C++, Pythonで、対象はOSXLinux両方だったり片方だけだったり。


ところで Go には標準パッケージとしてGIF画像を扱うものが用意されている。

これを使えばImageMagickに依存することなく前述のようなツールが作れるのでは? しかもPure Goで書けるはずだからクロスコンパイルして各環境用のバイナリを配布することも可能なはず!

ということで作ってみた次第。
(まぁターミナルの録画を撮りたい需要がある人なら各言語の実行環境用意したりImageMagickをインストールしたりするのに障壁は無いでしょうけども)

実装

ttyrecordの読み取り

ttyrecは自分でインストールしてもらうとして(RPMパッケージが用意されていたりhomebrewでも入るし そこは問題ないはず)。
それで撮って得られたバイナリデータは、

  • 各コマの時刻と、出力内容の長さを格納したヘッダ
  • それに続く出力内容の中身

がただ順番で書き込まれているだけなので、ひたすら順番に読み取っていく。

再生

読み取った内容を元に、各時刻の差分を計算したりしつつ ただターミナル上に出力していくだけで、再生できる。

キャプチャ

各コマを再生しつつ、TMPDIR以下にスクリーンショットを放り込んでいく。が、ここは結構面倒なところで。


OSXでは、

  • AppleScriptを使って対象アプリケーション(Terminal.appだったりiTerm.appだったり)のアクティブウィンドウのIDを取得
  • そのIDを指定してscreencaptureコマンドを実行する

という手順でスクリーンショットを撮る。実行中のターミナルアプリケーションがTerminal.appなのかiTerm.appなのか、はたまた別のアプリケーションなのか?は、とりあえずは$TERM_PROGRAM環境変数で二択の判別だけしているけれど 他に良い方法ないだろうか…
あと、Retina display上で撮ると高い解像度のデカい画像が作られてしまうので、そこからさらにsipsコマンドを使ってdpi情報を取得して調整したり。


Linux というかX Window Systemでは、xwdというコマンドで指定したWINDOWIDの画面をダンプできる、ということなのでそれを使うことにした。
ただこれはxwdフォーマットという独自のビットマップ形式で保存されるので、それを読み取って画像データとして得るためのdecoderは自分で書いた。


OSX/Linux各環境で分けられるかというとそうでもなく、Mac OS X上でも XQuartz を使ってX Window Systemを立ち上げてその上でターミナル動かしたりするので、出来る限りどちらにも対応できるようにした、つもり。

これはXQuartz上のxtermで撮ったもの。


xwdでは余計な部分が撮られることなくて済むのだけど、OSXscreencaptureではどうしても「ウィンドウ全体」が撮られるため、タイトルバーなどが含まれてしまう。この部分が何故かコマによって白飛びしてしまったりするし、出来れば除外したいのだけど…
少し調べた限りでは良い方法は無さそうだった。決め打ちで上20pixelくらい削ったりしようにも アプリケーションによって または単一タブのときと複数タブのときとでバーの高さが変わったりするだろうし 逆にフルスクリーンモードのときは勝手に消えるからその場合も考慮しないといけないし…

キャプチャからのGIF生成

1コマずつ再生しつつ スクリーンショット取得->画像ファイル読み取り とやっていると再生終了まで時間がかかってしまうので、とりあえず撮るだけ撮って保存だけしておき、一通り再生し終わったらそれらを読み込んでGIF生成にとりかかる、というようにしている。
ここで早速 pipeline and cancellation並行性パターンの勉強 - すぎゃーんメモ で勉強した内容が役立った。CPU使う処理なので並行化でそこまで劇的に早くなるというわけでもないけど、runtime.GOMAXPROCS(runtime.NumCPU())指定してコア数ぶん動かすことで可能な限りの処理をしてくれるようになった、と思う。

TODO

Windowsのことは完全に無視して作った(確認できる環境も持ってないし…)けど、コマンドプロンプトスクリーンショットを簡単に撮る方法はあるのだろうか?

Linux Desktopも自分では普段使ってないからあまり詳しく知らない、他にもサポートすべきものはあるのかな。

バイナリ配布するならttyrec自体もGoで再実装して使えるようにしておいた方がよかったりするのだろうか?

スピード調整はできるようにしたけど、「この前半3コマは削りたい」「このtypoの部分を削りたい」みたいな場面は出てくると思うので、一度各コマの時刻と出力内容概要をファイル出力して それを編集して使うことで各コマのタイミング調整や削除をできるようにしたいな、と思っている。