絶対にやっちゃダメだというgif変換方法と、その他の変換方法をやってみた

気になるツイートを見つけました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちなみにgif変換したい時これは絶対にやっちゃダメです。余裕で容量が10倍とかになります <a href="https://t.co/7ecUhGd5V0">pic.twitter.com/7ecUhGd5V0</a></p>&mdash; 大渕 (@yuki_obuchi) <a href="https://twitter.com/yuki_obuchi/status/1495630813912010752?ref_src=twsrc%5Etfw">February 21, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```
ffmpeg -i input.mp4 output.gif
```

で「容量が10倍とかになります」で「絶対にやっちゃダメです」と主張しているようですが、何の10倍かわかりません。
そこで、実際にgif変換を試してみることにしました。

# 使用するffmpeg

執筆時点で最新のWindows版を用意しました。
[Builds - CODEX FFMPEG @ gyan.dev](https://www.gyan.dev/ffmpeg/builds/) の `ffmpeg-git-full.7z` を用いました。

```
ffmpeg version 2022-02-21-git-b8e58f0858-full_build-www.gyan.dev Copyright (c) 2000-2022 the FFmpeg developers
built with gcc 11.2.0 (Rev7, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libdavs2 --enable-libuavs3d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
libavutil      57. 21.100 / 57. 21.100
libavcodec     59. 21.100 / 59. 21.100
libavformat    59. 17.102 / 59. 17.102
libavdevice    59.  5.100 / 59.  5.100
libavfilter     8. 27.100 /  8. 27.100
libswscale      6.  5.100 /  6.  5.100
libswresample   4.  4.100 /  4.  4.100
libpostproc    56.  4.100 / 56.  4.100
```

# 変換元

今回は、以下の2種類の変換元を用意しました。

* gif変換しやすそうなデータ：Bad Apple!!
  * [Bad Apple!! PV-FC 2.5](https://littlelimit.net/bad_apple_2_5.htm) のデータを [FCEUX](https://fceux.com/web/home.html) で再生し、FCEUXの機能でAVIに録画しました。
* gif変換しにくそうなデータ：動きのあるフリー素材の動画
  * [FDR and 7th Street Skateboarding Free Stock Video Footage Download HD Clips](https://mazwai.com/video/fdr-and-7th-street-skateboarding/454950)
  * Tom Harmon さんの作品で、Creative Commons 3.0 (CC-BY 3.0) ライセンスとなっている

これを10fps、音声なしに変換し、1分間を切り出しました。
Bad Apple!!は最初の1分を、フリー素材は動きが激しくなってくる30秒地点から1分を切り出しました。
さらに、フリー素材のもとのサイズは1280×720でしたが、Giamでのgif変換に失敗したので縮小することにしました。

```
ffmpeg -i bad_apple_2_5.avi -t 60 -r 10 -an -q:v 2 bad_apple_2_5.mp4
ffmpeg -ss 30 -i FDR_and_7th_Street_Skateboarding-HD.mp4 -t 60 -r 10 -s 800x450 -an -q:v 2 street.mp4
```

以下のようなデータが得られました。

|ファイル名|性質|解像度|長さ|フレームレート|ファイルサイズ|
|---|---|---|---|---|---|
|bad_apple_2_5.mp4|影絵|256×224|01:00|10|242,472 バイト|
|street.mp4|日常|800×450|01:00|10|5,659,549 バイト|

# gif変換

## 絶対にやっちゃダメとされる方法

まずは、元ツイートで絶対にやっちゃダメとされている方法をやってみました。

```
ffmpeg -i bad_apple_2_5.mp4 bad_apple_2_5_dame.gif
ffmpeg -i street.mp4 street_dame.gif
```

変換結果のファイルサイズは以下のようになりました。

|変換結果|ファイルサイズ|元ファイルの何倍か|
|---|---|---|
|bad_apple_2_5_dame.gif|2,886,557 バイト|11.90|
|street_dame.gif|55,128,416 バイト|9.74|

両方について、ファイルサイズは元ファイルの10倍前後になりました。
元ツイートの「余裕で容量が10倍とかになります」は、変換元のファイルとの比較だった可能性が考えられます。
ただし、これだけではffmpegが悪いのかgifの性質が悪いのかはわかりません。

## Giam

ffmpegで動画を画像群に変換した後、[Giam](https://www.vector.co.jp/soft/win95/art/se041233.html) Version 2.09 を用いてgifに変換してみました。

```
mkdir bad_apple_2_5
ffmpeg -i bad_apple_2_5.mp4 bad_apple_2_5\%04d.png
mkdir street
ffmpeg -i street.mp4 street\%04d.png
```

Giamの書き込みオプションは、以下のようにしました。

* パレット：できるだけパレットを統一
* 最大色数：256色
* 無条件ディザ：オフ
* インターレース：オフ

変換結果のファイルサイズは以下のようになりました。
`_noopt` がついているファイルはGiamの「各コマサイズの最適化」の適用前、ついていないファイルは適用後です。

|変換結果|ファイルサイズ|元ファイルの何倍か|
|---|---|---|
|bad_apple_2_5_giam_noopt.gif|3,539,418 バイト|14.60|
|bad_apple_2_5_giam.gif|3,335,043 バイト|13.75|
|street_giam_noopt.gif|92,632,809 バイト|16.37|
|street_giam.gif|94,372,316 バイト|16.67|

いずれも「絶対にやっちゃダメとされる方法」より変換結果のファイルサイズは大きくなってしまいました。
さらに、フリー素材の変換結果では、「各コマサイズの最適化」の適用後の方が適用前よりファイルサイズが大きくなっています。
これは、入力の性質上各コマの画像サイズの削減はできず、前のコマと色が(偶然)同じ部分を無理矢理透明化したことで、
画像が複雑になり、圧縮が効きにくくなったためであると推測できます。

## ffmpegで画像群からgifを作る

元ツイートは「これは絶対にやっちゃダメです」と主張していますが、「これ」が何かはよくわかりません。
もしかしたら、入力にmp4を使っているのが悪いかもしれません。
そこで、mp4のかわりに、Giamの入力として用いた画像群を入力として用いてみました。

```
ffmpeg -r 10 -i bad_apple_2_5\%04d.png bad_apple_2_5_frompng.gif
ffmpeg -r 10 -i street\%04d.png street_frompng.gif
```

|変換結果|ファイルサイズ|元ファイルの何倍か|
|---|---|---|
|bad_apple_2_5_frompng.gif|2,886,557 バイト|11.90|
|street_frompng.gif|99,971,673 バイト|17.66|

Bad Apple!!の変換結果は「絶対にやっちゃダメとされる方法」とピッタリ同じサイズでしたが、
フリー素材の変換結果はGiamによる変換結果よりさらに大きくなってしまいました。
Giamで開いて数コマ見てみた結果、mp4から直接変換した時に比べ、透過色で塗られる部分が少なくなっているようでした。
mp4から直接変換した時に比べ、エンコードの際の画像の劣化が減っている可能性があります。
例えば、4コマ目の画像は以下のようになっていました。

|絶対にやっちゃダメとされる方法|Giam(最適化後)|画像群から変換|
|---|---|---|
|![street_dame_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/4e759e6c-59b2-2fbd-abf1-c5a97859b89e.png)|![street_giam_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/e455b14c-8be3-28e7-ed72-8596f54f9876.png)|![street_frompng_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/253731d3-d9f7-5df2-a764-f66fa12d4a64.png)|

## ImageMagickで画像群をgifに変換後、まとめる

餅は餅屋です。
事前に画像群を画像処理ソフトウェア…[ImageMagick](https://imagemagick.org/index.php)でgifに変換した後、それをアニメーションにしてみましょう。
Windows Binary Release の `ImageMagick-7.1.0-25-Q16-HDRI-x64-dll.exe` を使いました。

```
Version: ImageMagick 7.1.0-25 Q16-HDRI x64 2022-02-16 https://imagemagick.org
Copyright: (C) 1999-2021 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenCL OpenMP(2.0)
Delegates (built-in): bzlib cairo flif freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib
Compiler: Visual Studio 2022 (193030709)
```

gifに変換した画像を直接ffmpegに入力しようとするとうまく変換できなかったので、変換したgifを一旦pngに変換してからgifアニメに変換しました。

```
mogrify -format gif bad_apple_2_5\*.png
mogrify -format gif street\*.png
mkdir bad_apple_2_5_gif
mv bad_apple_2_5\*.gif bad_apple_2_5_gif\
mkdir street_gif
mv street\*.gif street_gif\
mogrify -format png bad_apple_2_5_gif\*.gif
mogrify -format png street_gif\*.gif
ffmpeg -r 10 -i bad_apple_2_5_gif\%04d.png bad_apple_2_5_mogrify_ffmpeg.gif
ffmpeg -r 10 -i street_gif\%04d.png street_mogrify_ffmpeg.gif
```

ffmpegだけでなく、Giamを用いても変換を行いました。
Giamの入力には、gifから変換したpngではなく、gifを用いました。
`_noopt` がついているファイルはGiamの「各コマサイズの最適化」の適用前、ついていないファイルは適用後です。

変換結果のファイルサイズは以下のようになりました。

|変換結果|ファイルサイズ|元ファイルの何倍か|
|---|---|---|
|bad_apple_2_5_mogrify_ffmpeg.gif|2,384,378 バイト|9.83|
|bad_apple_2_5_mogrify_giam_noopt.gif|2,381,371 バイト|9.82|
|bad_apple_2_5_mogrify_giam.gif|2,383,864 バイト|9.83|
|street_mogrify_ffmpeg.gif|101,550,512 バイト|17.94|
|street_mogrify_giam_noopt.gif|98,906,161 バイト|17.48|
|street_mogrify_giam.gif|98,746,853 バイト|17.45|

Bad Apple!! の変換結果は、「絶対にやっちゃダメとされる方法」より小さくなり、かつffmpegとGiamでほとんど同じサイズになりました。
しかし、元ツイートで「絶対にやっちゃダメとされる方法」を絶対にやっちゃダメとしている根拠である「容量が10倍とか」にはなってしまっています。
フリー素材の変換結果は、ffmpeg、Giamともに、pngの画像群から変換を行った場合より、gifへの変換を行った場合のほうが大きくなりました。
減色処理が2回走ることで、何か悪い影響があるのかもしれません。

## ImageMagickで画像群をアニメーションgifにする

ImageMagickにもgifアニメを作る機能があるので、試してみました。

```
convert -delay 10 -loop 0 bad_apple_2_5\*.png bad_apple_2_5_convert.gif
convert -delay 10 -loop 0 street\*.png street_convert.gif
```

|変換結果|ファイルサイズ|元ファイルの何倍か|
|---|---|---|
|bad_apple_2_5_convert.gif|2,842,270 バイト|11.72|
|street_convert.gif|101,544,003 バイト|17.94|

Bad Apple!! のデータは「絶対にやっちゃダメとされる方法」に近いサイズに、フリー素材のデータはImageMagickでgifに変換した画像群をffmpegで変換した時に近いサイズになりました。

# さらに良い入力を与えてみる

Bad Apple!! のデータは、白黒なのでgifにしやすそうということで今回の入力として選びました。
しかし、mp4に変換する過程で劣化し、余計な色が入っているかもしれません。
「絶対にやっちゃダメとされる方法」で変換した結果のコマをGiamでチェックすると、不自然な点々が入っているものがあり、効率の良い変換ができていない可能性が考えられます。

以下、31コマ目を比較してみます。
ただし、Giamで最適化をかけると一部の(動きの無い？)フレームが飛んでずれるようなので、Giamについては31コマ目に相当すると思われる16コマ目を用います。

|ffmpeg<br>「絶対にやっちゃダメとされる方法」|Giam|ImageMagick → ffmpeg|ImageMagick → Giam|ImageMagick|
|---|---|---|---|---|
|![bad_apple_2_5_dame_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/7e4054fb-cb70-3a79-fcb4-098bede93082.png)|![bad_apple_2_5_giam_016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/b584f597-075b-d6fd-d88c-358504d103e8.png)|![bad_apple_2_5_mogrify_ffmpeg_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/16172526-ae24-1f9e-b528-0d6e476b3af5.png)|![bad_apple_2_5_mogrify_giam_016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/d857a06b-458e-a62d-af2a-3a14a425ed24.png)|![bad_apple_2_5_convert_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/f313340b-ddf1-2623-bf5b-5ddfc8c2a72a.png)|

そこで、FCEUXから出力するAVIファイルを未圧縮のものにし、それを直接ffmpegに入力してgifアニメや画像群に変換してみることにしました。
画像群からも、ffmpeg、Giam、およびImageMagickを用いてgifアニメに変換します。

```
ffmpeg -i bad_apple_2_5_raw.avi -t 60 -r 10 bad_apple_2_5_raw.gif
mkdir bad_apple_2_5_raw
ffmpeg -i bad_apple_2_5_raw.avi -t 60 -r 10 bad_apple_2_5_raw\%04d.png
ffmpeg -r 10 -i bad_apple_2_5_raw\%04d.png bad_apple_2_5_raw_frompng.gif
convert -delay 10 -loop 0 bad_apple_2_5_raw\*.png bad_apple_2_5_raw_convert.gif
```

さらに、比較のためmp4にも変換しておきます。

```
ffmpeg -i bad_apple_2_5_raw.avi -t 60 -r 10 -an -q:v 2 bad_apple_2_5_raw.mp4
```

変換結果のファイルサイズは以下のようになりました。

|変換結果|説明|ファイルサイズ|ffmpeg直接の何倍か|mp4の何倍か|
|---|---|---|---|---|
|bad_apple_2_5_raw.mp4|mp4|102,964 バイト|0.06|1.00|
|bad_apple_2_5_raw.gif|ffmpeg直接|1,802,129 バイト|1.00|17.50|
|bad_apple_2_5_raw_frompng.gif|ffmpeg画像群経由|1,824,862 バイト|1.01|17.72|
|bad_apple_2_5_raw_giam_noopt.gif|Giam最適化前|587,201 バイト|0.33|5.70|
|bad_apple_2_5_raw_giam.gif|Giam最適化後|555,235 バイト|0.31|5.39|
|bad_apple_2_5_raw_convert.gif|ImageMagick|608,777 バイト|0.34|5.91|

全体的に、mp4から変換した時よりファイルサイズが小さくなっています。
しかし、ffmpegでの変換結果は、GiamやImageMagickでの変換結果に比べてファイルサイズが約3倍大きくなっています。
一方、GiamやImageMagickでの変換結果は、mp4に変換した時と比べてもファイルサイズが6倍以下に収まりました。

31コマ目(Giamは16コマ目)を比較してみます。

|ffmpeg直接|ffmpeg画像群経由|Giam最適化後|ImageMagick|
|---|---|---|---|
|![bad_apple_2_5_raw_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/0de7061b-7c5a-395d-7194-cf32860f6dd9.png)|![bad_apple_2_5_raw_frompng_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/bbbe24ca-44cd-d878-e909-20049d434387.png)|![bad_apple_2_5_raw_giam_016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/51b91b1c-bcef-6222-0d75-da78e0fc17a9.png)|![bad_apple_2_5_raw_convert_031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/9a204390-fcbd-ca21-da4b-8f78f35daf18.png)|

Giamでは白と黒の変化の最低限の記録になっているようであるのに対し、ffmpegではやはり謎の点々が入ってしまいました。
そこで、以下のコマンドを用い、gifから各コマの画像を取り出してみました。

```
ffmpeg -i bad_apple_2_5_raw.gif bad_apple_2_5_raw_gif_frames\%04d.png
ffmpeg -i bad_apple_2_5_raw_giam.gif bad_apple_2_5_raw_giam_gif_frames\%04d.png
```

その結果、ffmpegで変換をした方には背景の白い部分に謎の黄色い点々が入っており、画像が劣化していることがわかりました。
以下は31コマ目(Giamは16コマ目)です。

|ffmpegによる変換結果|Giamによる変換結果|
|---|---|
|![0031.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/ce5d4936-e643-3b52-3907-c27bf36c636f.png)|![0016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/03642a2a-6489-86c9-b884-b945c1eae387.png)|

# まとめ

## Bad Apple!! の変換結果

|ファイル名|説明|ファイルサイズ|入力の何倍か|
|---|---|---|---|
|bad_apple_2_5.mp4|入力|242,472 バイト|1.00|
|bad_apple_2_5_dame.gif|ffmpeg|2,886,557 バイト|11.90|
|bad_apple_2_5_frompng.gif|画像群 → ffmpeg|2,886,557 バイト|11.90|
|bad_apple_2_5_convert.gif|画像群 → ImageMagick|2,842,270 バイト|11.72|
|bad_apple_2_5_giam_noopt.gif|画像群 → Giam (最適化なし)|3,539,418 バイト|14.60|
|bad_apple_2_5_giam.gif|画像群 → Giam|3,335,043 バイト|13.75|
|bad_apple_2_5_mogrify_ffmpeg.gif|画像群 → gifに変換 → ffmpeg|2,384,378 バイト|9.83|
|bad_apple_2_5_mogrify_giam_noopt.gif|画像群 → gifに変換 → Giam (最適化なし)|2,381,371 バイト|9.82|
|bad_apple_2_5_mogrify_giam.gif|画像群 → gifに変換 → Giam|2,383,864 バイト|9.83|

## フリー素材の変換結果

|ファイル名|説明|ファイルサイズ|入力の何倍か|
|---|---|---|---|
|street.mp4|入力|5,659,549 バイト|1.00|
|street_dame.gif|ffmpeg|55,128,416 バイト|9.74|
|street_frompng.gif|画像群 → ffmpeg|99,971,673 バイト|17.66|
|street_convert.gif|画像群 → ImageMagick|101,544,003 バイト|17.94|
|street_giam.gif|画像群 → Giam (最適化なし)|94,372,316 バイト|16.67|
|street_giam_noopt.gif|画像群 → Giam|92,632,809 バイト|16.37|
|street_mogrify_ffmpeg.gif|画像群 → gifに変換 → ffmpeg|101,550,512 バイト|17.94|
|street_mogrify_giam.gif|画像群 → gifに変換 → Giam (最適化なし)|98,746,853 バイト|17.45|
|street_mogrify_giam_noopt.gif|画像群 → gifに変換 → Giam|98,906,161 バイト|17.48|

## Bad Apple!! (未圧縮) の変換結果

|ファイル名|説明|ファイルサイズ|mp4の何倍か|
|---|---|---|---|
|bad_apple_2_5_raw.mp4|mp4 (ffmpeg)|102,964 バイト|1.00|
|bad_apple_2_5_raw.gif|ffmpeg|1,802,129 バイト|17.50|
|bad_apple_2_5_raw_frompng.gif|画像群 → ffmpeg|1,824,862 バイト|17.72|
|bad_apple_2_5_raw_giam_noopt.gif|画像群 → Giam (最適化なし)|587,201 バイト|5.70|
|bad_apple_2_5_raw_giam.gif|画像群 → Giam|555,235 バイト|5.39|
|bad_apple_2_5_raw_convert.gif|画像群 → ImageMagick|608,777 バイト|5.91|

# 結論

ドット絵などの色数が少なく、それぞれの色の領域がはっきりしている画像をgifアニメに変換する場合は、その前に不可逆圧縮をかけて画像を劣化させてしまうと、画像が複雑になり、gifにした時にファイルサイズが大きくなる原因となるので、不可逆圧縮をかけてはいけません。
また、不可逆圧縮をかけなくても、ffmpegによりgifアニメへの変換を行った場合、ファイルサイズが比較的大きくなり、コマのデータに謎の点々が入ることがありました。
ffmpegの今回の使い方でgifアニメへの変換を行うと、劣化させずに変換できる画像でも劣化してしまうことがあるため、この使い方はこのようなケースのgifアニメの作成には適さないようです。

一方、実写の動画などの色数が多い画像をgifアニメに変換する場合は、ffmpegで直接変換する方法が今回の実験では一番ファイルサイズが小さくなりました。
容量が10倍など大きくなるのが許容できないのであれば、そもそも実写の動画などをgifアニメに変換するのがいけないと考えられます。

特定の変換方法を「絶対にやっちゃダメです」などと言って代案も提示せずに放り出すではなく、入力の性質、求める画質やファイルサイズ、再生環境などの条件に合わせた適切な変換方法を探すのがいいでしょう。

# 参考サイト

今回は、各ソフトウェアの使い方について、以下のサイトを参考にしました。

* [【ffmpeg】動画の解像度を指定してリサイズ、アスペクト比を維持したまま解像度を変更する、回転する - Qiita](https://qiita.com/riversun/items/d09d8e596a20ec1798f3)
* [ffmpeg で指定時間でカットするまとめ | ニコラボ](https://nico-lab.net/cutting_ffmpeg/)
* [ffmpeg：動画のフレームレート（fps）を強制的に変更して出力したい | 人生は読めないブログ](https://torisky.com/ffmpeg%EF%BC%9A%E5%8B%95%E7%94%BB%E3%81%AE%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AC%E3%83%BC%E3%83%88%EF%BC%88fps%EF%BC%89%E3%82%92%E5%BC%B7%E5%88%B6%E7%9A%84%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%97/)
* [アニメーションGIFの作り方 ImageMagick編](http://www.protein.osaka-u.ac.jp/rcsfp/supracryst/suzuki/jpxtal/Katsutani/gif1.php)