FFmpegを使ってルパン三世タイトルジェネレーターをPHPで書いてみた

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560692/28ad0dbd-c6d5-5cb7-472d-f839c448ee6c.gif)

新年も明けて2020年になりましたね！

話は飛びますが、年末辺りからTwitterのエンジニア界隈でルパン三世を模した動画を幾度と無くTLで見ました！
ググって同様の動画を作成するジェネレーターを幾つか確認しましたが、せっかくならPHPで動画作成したい！
ということで開発しました！

``※都度、リファクタを加えながら開発してたので5時間くらいかかりましたｗ``
``※出力結果は音付きのmp4ファイルです``

[ルパン三世タイトルジェネレーター](https://github.com/huuyafwww/lupin-generator)

この記事で解説をするコードの立ち回りは、[PHPの言語仕様の理解を深める特殊な立ち回り](https://qiita.com/huuya/items/f9bedb6b600c55613c4b)に詳しく書きました！

## 環境

### 動作確認済み環境

- macOS Catalina : ``ver 10.15.2``
- PHP : ``7.3.1``
- Web Server : ``Apache``

### 必要な外部ライブラリ

- [FFmpeg](https://www.ffmpeg.org/)

### その他特記事項

- PHPにGDモジュールがインストールされていること

## 実行方法

FFmpegのコマンドパスが通った上記の環境下で、

```
lupin-generator/index.php
```

の

```php

$string = "あけおめ！";
```

を編集して実行してください！

出力先は、

```
lupin-generator/output/output.mp4
```

です。

※既に出力結果が存在する場合は、上書き保存されます

### 設定関連

基本的に画像やフレームの作成に用いるパラメータは、

```
lupin-generator/Define.php
```

に記述しています。

以下は各定数の概要です。

|定数  |概要  |
|---|---|
|L_FFMPEG_COMMAND_PATH  |FFmpegのコマンドパス  |
|L_FONT_FILE  |出力する文字のフォント(TrueType)ファイルパス。デフォルトではGoogleのWebフォントを利用  |
|L_IMG_WIDTH  |出力結果の横幅  |
|L_IMG_HEIGHT  |出力結果の縦幅  |
|L_BACKGROUND_COLOR_R  |出力結果背景のR(0 〜 255)  |
|L_BACKGROUND_COLOR_G  |出力結果背景のG(0 〜 255)  |
|L_BACKGROUND_COLOR_B  |出力結果背景のB(0 〜 255)  |
|L_FONT_COLOR_R  |出力結果文字のR(0 〜 255)  |
|L_FONT_COLOR_G  |出力結果文字のG(0 〜 255)  |
|L_FONT_COLOR_B  |出力結果文字のB(0 〜 255)  |
|L_FONT_SIZE  |1文字専用フレームのフォントサイズ  |
|L_IMG_ANGLE  |文字の角度  |
|L_IMG_X  |左上を基点としたX軸方向の開始位置  |
|L_IMG_Y  |左上を基点としたY軸方向の開始位置  |
|L_TEMP_IMAGE_PATH  |フレームの作成に必要な画像ファイルの保存パス  |
|L_TEMP_FRAME_PATH  |動画の作成に必要なフレームファイルの保存パス  |
|L_OUTPUT_PATH  |出力結果（動画）の保存パス  |
|L_TYPE_SOUND  |タイピングサウンドの参照パス  |
|L_TITLE_SOUND  |タイトルサウンドの参照パス  |
|L_FRAME_LIST_FILE  |FFmpegを用いてフレームを連結する際に参照パスリストを記述するファイルのパス  |

## ギジュツ的なこと

### 内部仕様

1. 入力文字列を1文字ずつ画像に書き出す
2. 入力文字列をタイトル画像に書き出す
3. 画像を1枚ずつサウンド付きのフレームに書き出す
4. フレームを連結する

### その他

コマンドラインから引数を投げて実行することを想定しても良かったのですが、それならそもそもPHPで書かないので直ガキ仕様にしました笑

あと、出力結果は一行で文字列を埋めるので、

```
\n
```

区切りでパースしたり、形態素解析のIgo等用いて違和感無く複数行にすることも出来たのですがやってません🥺

他に特別なことと言えば、

- リファクタリングに専念した

くらいです😢

個人でPHPのフレームワークを現在進行系で開発しているのですが、その際に[CodeIgniter](https://codeigniter.com/)の本体を読み漁って覚えた立ち回り等は使ってます。

例えば、このルパン三世タイトルジェネレーターでは、

```
file_exists(L_FRAME_LIST_FILE) OR touch(L_FRAME_LIST_FILE);
```

といった処理があります。

大抵どの言語でも同様の仕様だと思いますが、論理和は左側の条件式の評価が

```
true
```

であれば、右側の条件式を評価せずに返り値を

```
true
```

とします。
上記コードの場合は

```
L_FRAME_LIST_FILE
```

に該当ファイルが存在していれば、左側の

```php

file_exists(L_FRAME_LIST_FILE)
```

はtrueとなり右側の

```php

touch(L_FRAME_LIST_FILE)
```

は実行されません。

逆にファイルが存在しなければ、右側の式も評価されることになるので実行されます。

要するに、

```php

if ( !file_exists(L_FRAME_LIST_FILE) )
{
    touch(L_FRAME_LIST_FILE)
}
```

と同様です。

あとは、マルチバイト文字も許容することになるので、それらの文字列を一文字ずつ配列に格納する処理を施しました！

```php

$this->strings = array_values(
    array_filter(
        preg_split("//u",$this->string),
        "strlen"
    )
);
```

上記コードですが、まずは正規表を利用して入力文字列を配列に格納しています。

```php
preg_split("//u",$this->string)
```
UTF-8にマッチする文字のみを検索パターンとしています。

```
"//u"
```

そして、上記処理を施すと空文字のvalueが格納されたインデックスが生成されるためにarray_filterで配列を走査しています。

array_filterの第二引数に指定するコールバック関数は返り値がfalseであるインデックスを削除した配列を返却します。

strlenは文字列の長さをint型で返却するPHPの組み込み関数で、上記のようなvalueが空文字のインデックスの場合は

```
0
```

を返却します。

これは[PHPの公式マニュアル](https://www.php.net/manual/ja/types.comparisons.php)にある通り、int型の0をbool値として扱う場合は、

```
false
```

と評価されます。

そして、連番画像として扱っているため歯抜けのkey名をarray_valuesで整えて実装しています。

他と言えば[実行演算子](https://www.php.net/manual/ja/language.operators.execution.php)を覚えたくらいです😇

以上です！

ありがとうございました！