iOS シミュレータで動画を収録して gif に自動で変換するスクリプトを書いた

- 知人に作ったアプリを見せたり
- SNS に投稿したり
- Pull Request のメッセージで動作を説明したり

するときに iOS シミュレータの動画を収録 → gif に変換ということを頻繁に行いませんか？
僕もそうで、特に Github の Pull Request で実装した様子を手軽に見せる手段として gif 画像を使います。

でも、毎回 収録 → gif に変換の手順を踏むのが面倒になったので適当にシェルスクリプトを書いてみました。

## 要求

- iOS シミュレータの動画を収録して gif として output するのを 1 コマンドで解決する
- ファイル名はなんでもいい
- だけどできれば収録した順に並んでると嬉しい

## 設計

### iOS シミュレータの動画を収録して gif として output するのを 1 コマンドで解決する
これに対してはシェルスクリプトを作るということで解決します。

### ファイル名はなんでもいい
ファイル名に制限は設けません。

### だけどできれば収録した順に並んでると嬉しい
最初はランダムな文字列.gif とかで考えましたが、この要求を満たすために、
`日付-時間.gif`
というファイル名の形式で output させることにしました。

## 作ってみる

### ファイル名 (`日付-時間.gif`) から作る

こんなの慣れた人からしたら朝飯前なんでしょうけど、僕はあんまりなれてないので、ここから解説します。

[この Qiita 記事](https://qiita.com/niwasawa/items/9502e97b6c4d28d24042) を参考にすると、 `date` コマンドによって日付および時刻が取得できそうです。
試しに

```shell
$ date
# → "Fri Aug  7 00:52:48 JST 2020" と出力された
```

いい感じです。あとはこれを整形します。
今回は `日付_時間.gif` とするので

```shell
$ date +%Y%m%d_%H%M%S
# → "20200807_005517" と出力された
```

のように `+%Y%m%d_%H%M%S` を付けてあげると秒までの時間が出力されます。
これでファイル名で並べたときに収録した順番に並んでくれそうです。

今回はこれをファイル名として使いたいので、使い回せるように変数に格納しときましょう。

```shell:gif-generator.sh
DATETIME=`date +%Y%m%d_%H%M%S`
echo $DATETIME
```

なんとなく `echo` しておきました。

### iOS シミュレータの動画を取るには？
[こちらの Qiita 記事](https://qiita.com/Sho-heikun/items/62b0b8b4150a1c401e3a) によると、

> 1. simulatorは起動しておく
> 2. コマンド `xcrun simctl io booted recordVideo test.mov` 実行(ディレクトリどこでも)
> 3. simulatorを操作
> 4. ctrl + cで終了

でできそうです。
なので、先程 `DATETIME` 変数を作ったのでそれをファイル名としてシェルスクリプトに以下のように追加すればできそうですね。

```diff
DATETIME=`date +%Y%m%d_%H%M%S`
echo $DATETIME
+ xcrun simctl io booted recordVideo $DATETIME.mov
```

これを実行して ctrl + C で interrupt すれば `20200807_005517.mov` のような動画が取れます。

あとはこれを gif にするだけだ！

### 動画を gif に変換
変換するにはコマンドラインから使えるツールとして有名な [FFmpeg](https://ffmpeg.org/) を使いました。
インストールは Homebrew でやるのが一番ラクだと思います。

```
brew install ffmpeg
```

コマンドの使い方はググってもらうとして、僕は Pull Reqest で動作を見せるので割と画質は高いほうがいいです。
調べてみると [ffmpegでとにかく綺麗なGIFを作りたい](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040) という僕にぴったりな記事が見つかりました。

いくつか手法が紹介されていますが、その中で

> 手軽に綺麗なGIFを作りたい
> グローバルパレットを使用して最適化

を使うことにしました。
コマンドは以下のとおりです。

```shell
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" output-palette.gif
```

`input.mov` に入力動画、`output-palette.gif` が出力する gif のファイル名ですね。
ではこれを、さっきのシェルスクリプトに追加してっと。

```diff:gif-generator.sh
DATETIME=`date +%Y%m%d_%H%M%S`
echo $DATETIME
xcrun simctl io booted recordVideo $DATETIME.mov
+ ffmpeg -i $DATETIME.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
```

はい、完成。
これで適当なディレクトリで `sh gif-generator.sh` 入力 → シミュレータ操作 → ctrl + C 押下で gif が作られます。
簡単ですね。

### ちょっと改造
これで目的は達成されたわけですけど、もうちょっとだけ今後のことを考えて手を入れてみます。

`xcrun simctl io booted recordVideo $DATETIME.mov` の性質上、どうしても ctrl + C を押しますよね。
なので、 ctrl + C で呼ばれるかたまりに分けると今後拡張するときも楽そうです。

細かいことは検索してもらって、僕は最終的に以下のように変更しました。

```diff
+ #!/bin/sh
DATETIME=`date +%Y%m%d_%H%M%S`
echo $DATETIME
+
+ trap "final; exit 1" 2
+ 
+ function final {
+   echo "Ctrl+C pushed."
+
+   # mov -> gif by using ffmpeg
+   # ref. https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040 
+   # ffmpeg -i $DATETIME.mov -r 10 -vf scale=640:-1 -f gif $DATETIME.gif
+   ffmpeg -i $DATETIME.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
+ }
+ 
xcrun simctl io booted recordVideo $DATETIME.mov
- ffmpeg -i $DATETIME.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
+
```

diff だと読みにくいかも…

```sh:gif-generator.sh
#!/bin/sh
DATETIME=`date +%Y%m%d_%H%M%S`
echo $DATETIME

trap "final; exit 1" 2

function final {
  echo "Ctrl+C pushed."

  # mov -> gif by using ffmpeg
  # ref. https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040 
  # ffmpeg -i $DATETIME.mov -r 10 -vf scale=640:-1 -f gif $DATETIME.gif
  ffmpeg -i $DATETIME.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
}

xcrun simctl io booted recordVideo $DATETIME.mov

```

`final` 関数が ctrl + C を押されたときに呼ばれる関数です。
これでちょっと見通しよくなったかな？

### 2020/09/12 追記: パワーアップ版をシェア頂きました！
@BlueEventHorizon 様より、コメントにて完全上位互換のシェルスクリプトを共有して頂きました。

なんとこちらのスクリプトを使えば、複数回とぎれとぎれに録画した動画を、一つの gif 画像にまとめてエンコードしてしまうことができます！！！！

```shell
#!/bin/sh

DATETIME=`date +%Y%m%d_%H%M%S`

list=${@:1}" "$DATETIME.mov
num=$#
echo $list
echo $num

indexFile="index.txt"

if  [ $num = 0 ]; then
   if [ -e $indexFile ]; then
     echo "Remove index."
     rm $indexFile
   fi
fi

echo "file ./"$DATETIME.mov >> $indexFile

trap "final; exit 1" 2

function final {

echo ""
echo "🍏 Stop Recording!!"

read -p "Do you make GIF? or continue recording (y/N): " yn
case "$yn" in [yY]*) ;; *) sh $0 $list; exit ;; esac

  # mov -> gif by using ffmpeg
  # ref. https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040 
  # ffmpeg -i $DATETIME.mov -r 10 -vf scale=640:-1 -f gif $DATETIME.gif

echo "⭐️ Start Encoding!!"

if  [ $num = 0 ]; then
    ffmpeg -i $DATETIME.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
else
   ffmpeg -safe 0 -f concat -i $indexFile -vcodec copy -acodec copy $DATETIME"_merged".mov
   ffmpeg -i $DATETIME"_merged".mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" $DATETIME.gif
fi

#rm $indexFile
}

echo "🍎 Start Recording!!"

xcrun simctl io booted recordVideo $DATETIME.mov
```

## おわり: 改造案をコメントで募集！

あとは煮るなり焼くなりどうぞ。
オプション受け取れるようにして fps, 解像度を指定できるようにしたり、色々改善できそうです。

僕はシェルスクリプトに全然慣れてないので、「こうしたらいいよ！」「これ無駄かも！」とかあったらコメントで教えてほしいです🙇🏻‍♂️