Pythonで複数枚の静止画を動画化する

### 概要
「同じ場所から毎日撮っている写真」などをつなぎ合わせて、スライドショーっぽい動画にする方法を紹介。PythonのライブラリMoviePyを使用する。

### 動作確認環境
* Python3.6（Python2系での動作は未確認）
* OS : Ubuntu 18.04
  * Pythonがインストール可能であれば、他のOSでも問題なく動くはず

### 事前準備

* Pythonのインストール（説明は割愛）
* 下記コマンドでMoviePyをインストール

```text
$ pip install moviepy
```

* [参考]MoviePyのインストール方法が書かれたサイト
    * https://zulko.github.io/moviepy/install.html
    * 今回やりたいことを実現するだけであれば、``pip install moviepy``のコマンドのみで問題ないはず

* スライドショーにする静止画像を用意
  * ``input``ディレクトリ配下に、スライドショーでつなげたい順番にインデックスをつけて静止画像(拡張子は``.png``,``.jpg``など)を配置
  * 以下はその一例だが、Pythonのソース内でファイル名で昇順ソートした際にスライドショーでつなげたい順番に並ぶのであれば、以下の名前規則に従わなくてもOK。

```text
input
├── IMG_0000.jpg
├── IMG_0001.jpg
├── IMG_0002.jpg
├── ...
```

### ソース全体

* 以下のソースを``python create_slideshow.py``で実行すると、静止画を0.5秒間隔でつなげた動画像``output.mp4``が出来上がる。

```python:create_slideshow.py
#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-

import glob
from moviepy.editor import *

if __name__ == '__main__':

    # inputディレクトリ以下の拡張子が.jpgのファイル名リストを一括取得
    file_list = glob.glob(r'input/*.jpg')
    # ファイル名リストを昇順にソート
    file_list.sort()

    # スライドショーを作る元となる静止画情報を格納する処理
    clips = [] 
    for m in file_list:
        clip = ImageClip(m).set_duration('00:00:00.50')
        clip = clip.resize(newsize=(640,480))
        clips.append(clip)

    # スライドショーの動画像を作成する処理
    concat_clip = concatenate_videoclips(clips, method="compose")
    concat_clip.write_videofile(r"output.mp4", 
                                fps=24,
                                write_logfile=True,
                                )

```

### 解説

#### スライドショーを作る元となる静止画情報を格納する処理

```python
    clips = [] 
    for m in file_list:
        clip = ImageClip(m).set_duration('00:00:00.50')
        clip = clip.resize(newsize=(640,480))
        clips.append(clip)
```

* ``ImageClip(m).set_duration('00:00:00.50')``
    * 各画像の表示時間を設定。数値入力も可能で、その場合の単位は「秒」になる模様
* ``clip.resize(newsize=(640,480))``
    * 動画像(clip)のサイズを変更。一般的に静止画の画像サイズは動画像よりも大きく、そのまま処理するとドデカい動画像が出来てしまうので、予めここでサイズ変更していた方がよさそう。
    * ちなみに筆者が試した画像のサイズは4032×3024で、リサイズせずに変換をかけたらマシンのリソース不足で処理がハングアップした…

#### スライドショーの動画像を作成する処理

```python
    concat_clip = concatenate_videoclips(clips, method="compose")
    concat_clip.write_videofile(r"output.mp4", 
                                fps=24,
                                write_logfile=True,
                                )
```

* concatenate_videoclips(clips, method="compose")
  * 動画像リスト(clips)をつなぎ合わせて一つの動画像にする処理。
  * 引数``method``は``chain``または``compose``を選択可能。ドキュメントを見ると、つなぐ前の動画像のサイズが異なる場合は``compose``を使う方がよさそう。今回の場合は、予め動画像のサイズを640x480にしていて同じサイズになっているので、``method="chain"``にしても動くことを確認済。
* concat_clip.write_videofile(r"output.mp4",…
  * 動画像をファイル出力
  * ``write_logfile=True``にすると、出力ファイルを作成するログが``output.mp4.log``という名前で下記のように出力され、裏でffmpegが動いていることを確認できる。

```text:output.mp4.log
ffmpeg version 4.2.2-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 8 (Debian 8.3.0-6)
  configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gmp --enable-libgme --enable-gray --enable-libaom --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libdav1d --enable-libxvid --enable-libzvbi --enable-libzimg
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 0.000000, bitrate: 176947 kb/s
    Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 640x480, 176947 kb/s, 24 tbr, 24 tbn, 24 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
[libx264 @ 0x6856ac0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0x6856ac0] profile High, level 3.0, 4:2:0, 8-bit
[libx264 @ 0x6856ac0] 264 - core 159 r2991 1771b55 - H.264/MPEG-4 AVC codec - Copyleft 2003-2019 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=24 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to 'output.mp4':
  Metadata:
    encoder         : Lavf58.29.100
    Stream #0:0: Video: h264 (libx264) (avc1 / 0x31637661), yuv420p, 640x480, q=-1--1, 24 fps, 12288 tbn, 24 tbc
    Metadata:
      encoder         : Lavc58.54.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: -1
frame=   85 fps=0.0 q=28.0 size=       0kB time=00:00:01.33 bitrate=   0.3kbits/s speed=2.58x    
frame=  148 fps=145 q=28.0 size=     512kB time=00:00:03.95 bitrate=1059.7kbits/s speed=3.89x    
frame=  205 fps=130 q=28.0 size=     768kB time=00:00:06.33 bitrate= 993.4kbits/s speed=   4x
...
```