Technically Impossible

Lets look at the weak link in your statement. Anything "Technically Impossible" basically means we haven't figured out how yet.

Google ColabでのGoogle Driveマウント、MP4からGIFへの変換


Google Colaboratory (以下Colab)*1は、Googleが提供するJupyterノートブック環境だ。Google Driveと連携することで、そこに保存されたファイルをColab上で加工することもできる。

この投稿では、Google Driveに保存した動画ファイル(MP4ファイル)を加工し、最終的にGIFアニメーションとして出力する手順を紹介する。GIFを出力するにあたって、MP4を次のように加工する。

  • 画面からGIF出力対象部位をトリミング(クロップ)する。
  • 画面サイズをオリジナルから縮小する。
  • オリジナルのフレームレートを間引く。

加工に際し、MoviePyを使用する場合と、OpenCV + Pillowを用いる場合での処理パフォーマンスも比較した。MoviePyを使用した書式は分かりやすいのだが、OpenCVの処理パフォーマンスは圧倒的に高速だった。

Google Driveのマウント

Google Driveのマウントについて、Colabは様々な方法をサポートしている*2。注意したいのは、マウント後のパスだ。以下に示すのはColabのディレクトリ構造だ。

🔎Colabのディレクトリ構造

"/content"にGoogle Driveが"/drive"としてマウントされているのが分かる。Colab上で、実際に次のLinuxコマンドを実行しても確認することができる。
これはColab環境のGUIを介してマウントした場合だ。

!pwd
!ls


もしPythonプログラムから手動でマウントした場合、マウント・パスの"/drive"が、プログラムで指定した通りに変化する。例えば、次の場合は"/gdrive"に変化する。ディレクトリ構造も変化しているのが確認できる。

!pwd

from google.colab import drive
drive.mount('./gdrive')

事前準備、前提条件

この投稿で加工する動画ファイル(MP4ファイル)は、先日の投稿末尾に添付したTwitter投稿*3に含まれるものを使用している。

ColabのGUIを介してGoogle Driveをマウントした。このMP4ファイルを、Google Drive上の次のパスへ保存している。

/content/drive/MyDrive/20230404/screen-20230331-142030.mp4

この動画の上半分、ちょうどコンソールが表示されている部分をGIFアニメーションとして出力する。出力に際し、フレームレートを12fpsへ落とすことにした。

動画のサイズ

width height
MP4のサイズ 1800 2784
トリミングしたサイズ
cropしたサイズ
1800 1392
GIFのサイズ
resizeしたサイズ
640 495

下記Pythonプログラム中には、これらの情報が暗黙に反映されていることに注意すること。

MP4からGIFへの変換

理屈と仕組み。

MP4からGIFへ変換するに際し、動画を構成する一連の画像(フレーム)を取り出し、一つずつ変換していくのが、単純かつ原理的な仕組みだ。変換処理において、変換対象となる画像が小さいほど効率が良いため、対象が小さくなる処理を優先的に実行する。ここでは

  1. トリミング(crop)
  2. 画像縮小(resize)

という順序で対応することになる。フレームレートを落とすことによって、さらに変換対象となる画像の枚数を減らすことができる。

OpenCV + Pillowの場合

まずは変換処理にOpenCV (CV: Computer Vision)*4とPillow (PIL: Python Imaging Library)を用いる。どちらも画像処理ライブラリだ。

関数getCroppedFramesでGIF変換対象となる一連の画像を、Pillow imageの配列として出力する。ここではOpenCVを利用している。この仕様について一つ注意しておきたいが、次の特性だ。
つまり、デフォルトのカラー・フォーマットがBGRであることから、RGBへ変換する必要がある。

Note that the default color format in OpenCV is often referred to as RGB but it is actually BGR (the bytes are reversed).

OpenCV: Color Space Conversions

def getCroppedFrames(movie, crop_size, resize_size):
  ret_images = []

  while True:
    ret, bgr_images = movie.read()

    if ret:
      cropped_images = bgr_images[0:crop_size["h"], 0:crop_size["w"]]
      resized_images = cv2.resize(cropped_images, (resize_size["w"], resize_size["h"]))
      rgb_images = cv2.cvtColor(resized_images, cv2.COLOR_BGR2RGB)
      pillow_images = Image.fromarray(rgb_images)
      ret_images.append(pillow_images)
    else:
      return ret_images

次に関数makeGIFでGIFファイルを出力する。ここではFPSを指定することで、変換枚数を間引いている。出力形式は指定されたパスに従って特定されるため、あえて関数のパラメータとして画像形式を指定する必要はない。

def makeGIF(path, images, fps):
  dur = int(1000.0 / fps)
  images[0].save(path, save_all=True, append_images=images[1:], duration=dur, loop=0)

一連のコードは次のようになる。約7~8秒で変換処理を終えているのが分かる。

🔎MP4toGIF-OpenCV.py
gist.github.com

MoviePyの場合

MoviePy*5は動画編集用のライブラリだ。OpenCVに比べて、書式が非常に分かりやすい。おそらく目的に応じて「このように書きたい」という考えそのままに書き下すことができ、結果として非常に分かりやすいプログラムが出来上がる。たとえば、このような具合だ。

input_video = VideoFileClip(source_path)
cropped_video = input_video.crop(x1=0, y1=0, width=1800, height=1392)
resized_video = cropped_video.resize(height=640)
resized_video.write_gif(target_path_ffmpeg, fps=12, program='ffmpeg', logger='bar')

🔎MP4toGIF-MoviePy-simple.py
gist.github.com

OpenCVの場合と構成を合わせて書き換えると、次のようになる。

関数getCroppedFrames

def getCroppedFrames(movie, crop_size, resize_size):
  cropped_movie = movie.crop(x1=0, y1=0, width=crop_size["w"], height=crop_size["h"])
  resized_movie = cropped_movie.resize(height=resize_size["h"])
  return resized_movie

関数makeGIF

def makeGIF(path, movie, fps, program):
  movie.write_gif(path, fps=fps, program=program, logger='bar')

しかし、これが遅いのだ。たとえ分かりやすくプログラムを書けたとしても、同じ変換処理に20倍以上の時間を要するとしたらどうだろう。一つのGIFを出力するのに3分超を要するのだ。
GIF変換に際し、使用するプログラム(FFmpeg*6、imageio*7など)を選択することができるが、処理時間に違いはない。
2つの変換プログラムを用いて、2種類のGIFを出力すると、通しの処理時間は8分足らず。一連のコードは次のようになる。一つの変換処理に3分程度を要しているのが分かる。

🔎MP4toGIF-OpenCV.py
gist.github.com

出力されたGIFの比較

最後に、出力されたGIFの比較だ。変換処理プログラムの書式が異なるだけで、その他に大差のないことが分かる。

Pillow
6.10MB
ffmpeg
4.57MB
imageio
4.62MB