Pythonで美しい動画を作ろう

DeNAのAIシステム部、音声チームの[豆谷](https://www.linkedin.com/in/kohki-mametani-016b09135/)と申します。[前回の記事](https://qiita.com/Kohki_Mametani/items/03d863486dd3d806c69c)では音声変換を解説しました。本記事では、ManimというPythonのモジュールを使って美しい動画を作る方法を紹介します。

## 本記事のターゲット

- 教育系の動画制作でかなりの実績（YouTubeでの評価など）あるアニメーションエンジンを紹介します。日本語に対応する方法を解説することで、より多くの日本語話者（特にエンジニア）が自らの知識や経験を簡単に動画にできると知ってもらえると嬉しいです。今まであまり世に伝えられなかった学識や表現が、世界中の人々にシェアされるキッカケになると最高です（具体例を挙げてみました[^will]）。

- Python好きに向けて美しい動画の作り方を説明します。Pythonの楽しさ[^will2]と動画制作[^pydouga]というクリエイティブな組み合わせにより多くの人が関心を持ってもらえると嬉しいです。


## Manim

[3Blue1Brown](https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw)（以下、3b1b）というYouTubeチャンネルをご存知でしょうか？数学の理論や問題を美しく直感的に動画で説明していることで有名なYouTubeチャンネルです。Grant Sandersonさんが運営するチャンネルで、チャンネル登録者数が300万人を超えています。最近の動画では、Grantさんが出演してカジュアルなYouTubeチャンネルの様相もありますが、過去のほとんどの動画では一切人物は出演しません。例えば、「fourier transform」とGoogle検索してトップページに表示される、次のフーリエ変換の解説動画を見てください。

[<img src="http://i3.ytimg.com/vi/spUNpyF58BY/maxresdefault.jpg" width="300">](https://www.youtube.com/watch?v=spUNpyF58BY)

動画の開始から波形やキャラクターが登場し、複雑な（モーション）グラフィクスが使われています。また、各所で登場する数式も美しく整頓されています。（動画制作の専門家ではありませんが）こうした動画は普通、After Effectなどの有償の動画制作ソフトで作られます。しかし、実はこの動画（を含めて他の動画も全て）、Pythonで作られています。「Pythonで作られている」というのは、デザインから各物体の動きなどをPythonの`.py`に定義し、実行することでこうした美しい動画を生成するという意味です。そのコアの技術を提供しているのが**Manim**です。

Manimは前述の3b1bを運営する中で、Grantさんによって開発されたオープンソースのアニメーションエンジンです[^maniminuse]。このプロジェクトはお手本のようなオブジェクト指向で、Pythonicなコードになっているので、Python初心者の方も勉強を兼ねて[コード](https://github.com/3b1b/manim)を読んでみることをお勧めします。拡張性が優れていて、新しいアニメーションやキャラクターを定義したい場合も比較的に実現しやすいです。

本記事では、Manimのインストール方法については書きません。$\LaTeX$をインストールする必要があるので、若干手間がありますが頑張ってください。（**わからないことはコメントで聞いてください。**お助けできるかもしれません。）ちょうど、最近別のAdvent CalendarでManimが紹介されていたようで、確認してみることをお勧めします[^ja1][^ja2]。

## Hello World（動画制作の世界へようこそ）

（追記）2021年2月にManimのリリース1.0.0があり、以下のコードサンプルは最新版に対応していません。ただ、コアの技術は大きく変わっておらず、コア部分の呼び出し方が大きく変わっています。対応を検討していますが、急かされると急ぎます😄

Manimが手に入れば、あなたのクリエイティビティとコード能力で動画を作るだけです！

ManimのGitHubのREADME.mdで説明されているように、ハローワールド的な動画生成コードが用意されているので、最初はそれを動かしてみましょう。`git clone`したmanimのトップディレクトリに移動して次のコマンドを実行してみましょう

```
python3 -m manim.py example_scenes.py SquareToCircle
```
そうすると、`./media/videos/example_scenes/1440p60`に次のような動画`SquareToCircle.mp4`が生成されます。デフォルトでは`2560x1440@60FPS`の高品質な動画が生成されます。

[<img src="http://i3.ytimg.com/vi/mu99ZGKnP7U/maxresdefault.jpg" width="300">](https://www.youtube.com/watch?v=mu99ZGKnP7U)

先のコマンドの解説をします。Manimで動画を作るときに必要な開発は、基本的にはただ一つ、Sceneファイルの作成です。Manimのコア部分である`manimlib`ディレクトリに様々なアニメーションや動画生成に必要なプロセスが定義されています。これらをSceneファイルで適宜呼び出し、動画をコーディングしていきます。

先のコマンドでは、Sceneファイルは`example_scenes.py`です。一つのSceneファイルに複数のシーンを定義することができ、`SquareToCircle`もシーン名の一つです。他にもサンプルシーンが用意されているので実行してみてください。ちなみに、シーン名を指定せずに実行すると、コマンドラインから指定できます。

`example_scenes.py`を見てみると、次のように`SquareToCircle`が定義されています。

```python3:example_scenes.py
from manimlib.imports import *

class SquareToCircle(Scene):
    def construct(self):
        # 動画に登場する物体を定義します
        circle = Circle()
        square = Square() 
        square.flip(RIGHT)
        square.rotate(-3 * TAU / 8)
        circle.set_fill(PINK, opacity=0.5)

        # 動画の動きを決めて、動画を生成します
        self.play(ShowCreation(square))
        self.play(Transform(square, circle))
        self.play(FadeOut(square))
```

**これだけでアニメーションを描けるとは感動ですね。**しかも、オブジェクト名と関数名を見ると、どのような動きを定義しているか、すぐに理解できます。`Circle`や`Square`といったよく使うオブジェクトは`manimlib/imports.py`でインポートされていて、そのファイルをインポートすることで、インポート文を一行にまとめています。この手法は開発に慣れてくると見やすくて便利です。

## Dive into `manimlib`

ここからは、manimのコア部分である`manimlib`を見ていきます。パラメタをいじってアニメーションの挙動を変えたりすると理解が深まると思います。PyCharmで開発する方は、manimのトップディレクトリを`Mark Directory As Source`と設定することでIDEがモジュール全体を認識してくれます。

シーン`SquareToCircle`を定義する上で重要な3つのオブジェクト「`Scene`・`Mobject`・`Animation`」があります。これらのオブジェクトは、それぞれ`manimlib/scene`、`manimlib/mobject`、`manimlib/animation`の下に定義されています。

##### Scene 

`Scene`クラスは`SquareToCircle`が継承していますね。そして、重要な2つの関数`construct`と`play`を使用しています。`construct`はアニメーションを作るために必ずOverwriteします。`construct`内で、はじめにアニメーションで使う物体や色の指定などを定義します。これらを動かす（Play）するときに、`Scene`クラスの`play`を呼びます。`play`で動かすアニメーションの実行時間は、引数である`run_time`から調節できます。

##### Mobject

次に、`Mobject`は先に登場した`Circle`や`Square`など、アニメーションで使う全ての**物体**の親クラスです。Manimでは、一般的なアニメーションで使いそうな物体の多くが用意されています。幾何的な物体だけでなく、画像を定義できる`ImageMobject`、テキストを表示する際に使うSVGファイルを定義できる`SVGMobject`などがあります。

##### Animation

`Animation`は`Mobject`をどのように動かすかを定義します。このクラスは、`Scene.play(Animation(Mobject))`の形式で使われます。先の例でも、3つのアニメーションを使用しています。`ShowCreation`は単純なアニメーションで、アニメーション実行時間内に物体を表示します。ただ、動画を見て分かるように、物体を補完しながら表示します。単に表示したいだけの場合は、（アニメーションではないので）`Scene.add(Mobject)`関数を使います。その他のアニメーションも動画を見ると、挙動が理解できると思います。

## テキストの表示

Manimの強みはなんと言っても、LaTeX（`xelatex`と`latex`）を完全にサポートしている点です。実際に`manimlib`を見ると、`ctex_template.tex`（中国語などを扱えるUTF8に対応；日本語開発では`xelatex`とともに使用）と`tex_template.tex`があります。LaTeXをPython側から呼び出し、どちらかのTeXファイルを使いテキストを作ります。LaTeXユーザの多くはテキストをPDFファイルに書き出しますが、LaTeXはPDF以外にも様々なファイル形式にテキストを書き出すことができます。ManimはLaTeXを使い、**テキストをSVGファイルに書き出します**。SVG形式では、テキストを点の集合（ベクトル）として扱えるので、高解像度にスケールでき、点を操作してエフェクトもつけられます。また、LaTeXが持つ機能をを余すことなく動画作成に活用できます。すなわち、`tikz`などのパッケージ、フォントやコマンドのカスタマイズなどがLaTeXで文章を書く時と同様に使えます。

では、Manimで文字を扱う動画を作ってみましょう。新しく`my_scenes.py`というファイルを作り、以下のシーンを定義します。実行の前に、テンプレートTeXファイルとして`ctex_template.tex`を使用するように、`manimlib/constants.py`の`TEX_USE_CTEX = True`に書き換えましょう。

```python3:my_scenes.py
from manimlib.imports import *

class WriteQiita(Scene):
    def construct(self):
        # オブジェクトの定義
        example_text = TextMobject(
            "Manim Introduction $\sim$ Qiita for DeNA Advent Calendar 2020",
            tex_to_color_map={"Qiita": YELLOW}
        )
        example_tex = TexMobject(
            "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}",
        )
        # グループを作り、物体をまとめて動かす
        group = VGroup(example_text, example_tex)
        group.arrange(DOWN)
        group.set_width(FRAME_WIDTH - 2 * LARGE_BUFF) # テキストグループの幅を指定
        next_text = TextMobject("Day 23")

        # アニメーションの定義と実行
        self.play(Write(example_text), run_time=3)
        self.play(FadeInFromDown(example_tex))
        self.wait()
        self.play(ReplacementTransform(group, next_text))
```

次のような動画が生成されます。

[<img src="http://i3.ytimg.com/vi/VrL3sSLPJTI/hqdefault.jpg" width="300">](https://youtu.be/VrL3sSLPJTI)


Manimで文字を書くには`TextMobject`か`TexMobject`をよく使います。`TextMobject`は一般的なテキストを扱います。これに渡すテキストはLaTeXでレンダリングされるので、`$$`を使った数式表現も組み込めます。`TexMobject`にはTeXのコマンドを使った表現を渡すことができ、数式を書く際に便利です。

この段階では、日本語を表示したり、自動でテキストを折り返すことができません。次章では、これらの問題の解決方法を見ていきましょう。

## 美しい日本語を表示する

実際に動画を作り始めると、いろいろ微調整したくなると思います。例えば、1画面の幅に何文字表示し、どこでテキストを折り返すか決めたいものですね。`TextMobject`のデフォルトの挙動では、与えられたテキストを枠内に一行で書きます。「枠内」とは、上の例では`group.set_width`で決められた範囲であり、文字数が多くなるほどフォントサイズを小さくして一行に納めます。これでは動画の視認性がかなり悪化してしまいます。

適度に改行を入れることでテキストが読みやすくなります。3b1bでは、一行ごとに`TextMobject`を作り、`VGroup`でまとめてから並べて、読みやすいテキストを表現しています。これは、あらかじめ文章をハードコードする必要があるので、手間があります。また、任意の文章でテキストの折り返しを行う場合には適していません。

任意のテキストでうまく文章を折り返す方法の一つとして、LaTeXの`minipage`コマンドが使えます。それではLaTeXの開発例として、`ctex_template.tex`を以下のように書き換えてみましょう。

```latex:manimlib/ctex_template.tex
\documentclass[preview]{standalone}

%\usepackage[UTF8]{ctex}　これは使わない
\usepackage{seqsplit} % 追加パッケージ
\usepackage{fontspec} % 日本語に対応する
\usepackage[english]{babel}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{dsfont}
\usepackage{setspace}
\usepackage{tipa}
\usepackage{relsize}
\usepackage{textcomp}
\usepackage{mathrsfs}
\usepackage{calligra}
\usepackage{wasysym}
\usepackage{ragged2e}
\usepackage{physics}
\usepackage{xcolor}
\usepackage{microtype}
%\DisableLigatures{encoding = *, family = * }
\linespread{1}

% 日本語用のフォントを定義
\newfontfamily\kakuSan{Hiragino Kaku Gothic Pro W3} 
\newfontfamily\kakuRoku{Hiragino Kaku Gothic Pro W6}
\newfontfamily\kakuHachi{Hiragino Kaku Gothic Std W8}

% ミニページを作り、自動的にテキストを折り込む
\newcommand{\widthchange}[2]{
    \begin{minipage}{#1}\seqsplit{#2}\end{minipage}
}

\begin{document}
YourTextHere % ここがPythonから与えるテキストに置き換わる
\end{document}
```

日本語に対応するためにパッケージを追加し、フォントやコマンドを定義していることに注意してください。今回定義したヒラギノ角ゴシックは、Macには標準搭載されていますが、UbuntuやWindowsではシステムにインストールしなければ利用できません。各々の環境で Linux系ならば`fc-list`でシステムで使えるフォントを調べ、日本語に対応しているフォントなど、好きなフォントを定義してください。

さて、新しく定義したフォントとコマンドをPythonから呼び出すため、`my_sceans.py`に便利な関数`_t()`を加えます。


```python3:my_scenes.py
from manimlib.imports import *

def _t(text, centi=5, font_name='kakuRoku', continua=False):
    # LaTeXのレンダリング用にテキストを操作します
    def get_tex_string(text):
        # （日本語など）Scriptio Continuaの言語に対応するために便利
        if continua:
            text = '\-'.join(list(text))
        # フォントコマンドを呼ぶ
        if font_name:
            text = '\\%s{%s}' % (font_name, text)
        # minipageコマンドを呼ぶ
        return '\\widthchange{%.2fcm}{%s}' % (centi, text)
    
    text = get_tex_string(text)
    return text


class WriteQiita2(Scene):
    def construct(self):
        # オブジェクトの定義
        example_text = TextMobject(
            _t("Manim $\sim$ Qiita for ディエヌエー Advent Calendar 2020"),
        )
        # ... 以下 WriteQiitaと同じ
```

上のシーンを実行すると、次のような動画が得られます。

[<img src="http://i3.ytimg.com/vi/TyvqsYgs2x0/hqdefault.jpg" width="300">](https://www.youtube.com/watch?v=TyvqsYgs2x0)

適当なテキスト折り返しにより視認性がグッと上がりましたね。さらに、動画のテーマに合うフォントを使うことで魅力的な動画を作ることが可能になります。

この段階で、日本語におおよそ対応できていますが、一つ問題が残っています。LaTeXでは、単語間の空白を基準にテキストの自動折り返しを行います。すなわち、単語間に空白がない言語（Scriptio Continuaと呼ばれる）では`minipage`を設定してもテキストが折り返されません。この問題は、日本語の長文を入力すると明らかになります。以下のようなシーン`WriteQiita3`を実行してみましょう。

```python3:my_scenes.py
class WriteQiita3(Scene):
    def construct(self):
        # オブジェクトの定義
        example_text = TextMobject(
            _t("ディ・エヌ・エーの新卒よりキータのアドベントカレンダーで連日記事が投稿しました。"),
        )
        # アニメーションの定義と実行
        self.play(Write(example_text), run_time=3)
        self.wait()
        self.play(FadeOutAndShift(example_text))
        self.wait()
```

次のような動画が得られます。

[<img src="http://i3.ytimg.com/vi/nZrDNzvR3nw/maxresdefault.jpg" width="300">](https://youtu.be/nZrDNzvR3nw)

テキストが折り返されていないので、画面から切れてしまっていますね。LaTeXでPDF文章を作る際に`minipage`を使用しても、このようなエラーは発生しません。しかし、ManimでSVGを作ると「はっきりしない原因[^hakkirishinai]」により、こうしたバグが生じます。

このバグに対する簡単なwork-aroundを見ていきましょう。先の`_t`関数に与えられる`continua`がその解決策に対応していて、実に一行で問題解決です。全ての文字の間にハイフン`\-`を挟むという、なかなかアグリーなやり方です😓。テキストの折り返しを可能にするためにハイフンを挟む手法は、LaTeXで`\texttt{URL}`などでURLを表示する際によく使われています。つまり、テキストを任意の位置で折り返し可能にし、あとはLaTeXが`minipage`のサイズに応じて適当に折り返しを行います。では、最後にこのwork-aroundを用いた下のシーンでのテキスト表示を見ていきましょう。


```python3:my_scenes.py
class WriteQiita4(Scene):
    def construct(self):
        # オブジェクトの定義
        example_text = TextMobject(
            _t("ディ・エヌ・エーの2021年の新卒は、アドベントカレンダーで連日記事を投稿しました。",
               continua=True),
        )
        # 以下 WriteQiita3と同じ
```

これを実行すると、以下のような動画が得られます。しっかり日本語が折り返されていますね。これで日本語への対応はほとんど完了です[^hotondokanryo]！

[<img src="http://i3.ytimg.com/vi/B8OVuhAy_BE/maxresdefault.jpg" width="300">](https://youtu.be/B8OVuhAy_BE)

## 3b1bの過去作品を動かす

以上で、日本語をManimで表示する際に引っかかりそうな部分を解決しました。ここから先は、実際に作りたい動画を作りながらManimの使い方を学ぶことが吉だと思います。

`from_3b1b`のディレクトリに3b1bで使用されたシーンが公開されています。最初に見たフーリエ変換の解説動画は、`from_3b1b/active/diffyq`の元にシーンが定義されています。例えば、`parts/fourier_series.py`の`FourierOfN`ではかなり面白いアニメーションが見られます。いろいろ動作確認することでManimへの理解が深まると思います。

**注意**としては、これらは過去の作品でありメンテナンスされていません。すなわち、最新版のManimと仕様が異なる場合があり、実行できないこともあります。また、3b1bで使われているキャラクターのSVGファイルなどは公開されていないので、実行時には、これらへの参照を回避するなどの改変が必要になることもあります。さらに、これらのシーンファイルは教育的な目的で公開されているだけであって、これらのシーンを利用したアニメーションには3b1bの著作権が発生します。

[^joytanmemo]: これはJoytanというYouTubeチャンネルを運営してきた経験から得た個人的な見解ですが、動画はメディアの王様です。動画は基本的に、（テキストを含む）画像＋音声ですが、その価値は単なる足し算以上のものがあると思います。例えば、前述のJoytanが提供する語学学習動画ですが、必ずしも動画にしないと学べない内容ではありません。テキストベースの教科書で文章を読み、音声を別に聞けば同じ学習が可能でしょう。しかし、動画にしていなければ平均ライク95％以上で数十万回視聴されるコンテンツは生まれなかったはずです。これは、動画の質ではなく、現代人の性質・コンテンツ消費の傾向に依るところが大きいです。メディア論（？）は本記事の主眼ではないので多くは書きません。

## 結び

本記事では、Manimを用いて動画を作る方法を日本のエンジニア向けに紹介しました。Manimはかなり多機能で、日本語の網羅的な解説やチュートリアルは存在しません。中国語ではかなりしっかりした[ドキュメント](https://manim.ml/)が有志で製作されています。機械翻訳を使いながら読んでみるとためになると思います。また、Manimには[Redditコミュニティ](https://www.reddit.com/r/manim/)が存在するので、ご関心があれば覗いてみると仲間を見つけられるかもしれません。

今回は「Manimこんなことができるよ！」ということをシェアし、Manimがより日本で注目されることが狙いです。Manimの日本語化を進めたい・ドキュメントを作りたいという方がいましたら協力します。

##宣伝

この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ Twitter や facebook、はてなブックマークにてコメントをお願いします！
また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい！
<a href="https://twitter.com/DeNAxTech?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-size="large" data-lang="ja" data-show-count="false">Follow @DeNAxTech</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

[^will]: **「ある程度型にハマっていて」**かつ**「速いアウトプット」**が強みになる動画制作の場面で特に役立ちます。例1：競技プログラミングの解説動画。LeetCodeやatCoderでは毎日新しい問題が作られ、解かれています。ですので、動画を使った直感的な解説を素早く発信できると強みになります。また、動画で使う物体は「配列」や「幾何的図形」などある程度決まっているので、Manimでコードを使いまわせます。例2：型にハマっていると言えば、受験の問題もそうですね。日本には日本特有の受験問題があるので、まだまだ開拓されていないコンテンツがたくさんあると思います。「[高校数学の美しい物語](https://mathtrain.jp/)」で出てくる受験数学と絡めた数学は、Manimで動画化する最適のテーマだと思います。情報系の学生さんで塾でバイトしているならば、（数学だけでなく）授業用にキレイな解説動画を作ったりすると生徒に喜ばれるかもしれません。

[^will2]: Pythonって直感的にコード書けて楽しいですね。How-To本を見なくても、「こんな感じで動くかな？」ってコードを書いてみると実際動いたりします。こんな楽しい言語ですが、就職市場を見てみると、Pythonはほとんど「WebかAI/データ界隈」でしか使われていないようです。少し悲しいですね。本記事では、Pythonのまた異なる活用法を紹介し、アイデア次第では収益にもつなげられるということを紹介します。

[^pydouga]: Pythonで動画を作るとしたら、最も単純な方法だとPillow + moviepy。Pygameを使っても動画が作れますね。あと、OpenGLのラッパーであるPygletを使うのもありでしょう。

[^maniminuse]: 3b1b以外では[PRIMER](https://www.youtube.com/channel/UCKzJFdi57J53Vr_BkTfN3uQ)という教育系のYouTubeチャンネルでManimが使われていることが有名です。このチャンネルでは、（Python APIを通して）Blenderも使っているそうです。かくいう私の[Joytan](https://www.youtube.com/c/JoytanApp)というチャンネルも昔はPillow + moviepyでしたが、今ではフルManimです

[^hakkirishinai]: いくつか原因は考えられますが、自分はLaTeXの専門家ではないので詳しいことは書けません。より良いwork-aroundを発見された方はご一報お願いいたします。

[^hotondokanryo]: このwork-aroundを使うと、テキストの一部を隠す関数`hide_part`などで望まない挙動がおきます。これらを考慮するともう一工夫必要です。

[^ja1]: [MacbookにManimを入れて、MP4形式の動画を出力してみた](https://qiita.com/Afo_guard_enthusiast/items/462218687dd2f2a87904)
[^ja2]: [Pythonで数式・グラフのアニメを描ける「Manim」](https://qiita.com/Tonoyama/items/a53d55f96008a5b8296c)