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


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

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

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

ルパン三世タイトルジェネレーター

この記事で解説をするコードの立ち回りは、PHPの言語仕様の理解を深める特殊な立ち回りに詳しく書きました!

環境

動作確認済み環境

  • macOS Catalina : ver 10.15.2
  • PHP : 7.3.1
  • Web Server : Apache

必要な外部ライブラリ

その他特記事項

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

実行方法

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

lupin-generator/index.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の本体を読み漁って覚えた立ち回り等は使ってます。

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

file_exists(L_FRAME_LIST_FILE) OR touch(L_FRAME_LIST_FILE);

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

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

true

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

true

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

L_FRAME_LIST_FILE

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


file_exists(L_FRAME_LIST_FILE)

はtrueとなり右側の


touch(L_FRAME_LIST_FILE)

は実行されません。

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

要するに、


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

と同様です。

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


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

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

preg_split("//u",$this->string)

UTF-8にマッチする文字のみを検索パターンとしています。

"//u"

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

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

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

0

を返却します。

これはPHPの公式マニュアルにある通り、int型の0をbool値として扱う場合は、

false

と評価されます。

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

他と言えば実行演算子を覚えたくらいです😇

以上です!

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