Reactで動画を作る ~ remotionでのつまづきと解決策について&作った所感 ~

趣味分野で楽に動画を作りたくてゆっくり実況系？の動画をReactで作ってみることにました。<a href="https://www.remotion.dev/">remotion</a>と呼ばれるライブラリを使用することで動画編集ソフトのようなUIが立ち上がり動画を作っていくことができます。
![スクリーンショット 2023-02-09 13.41.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/d1ba1567-d8ea-fd8b-9205-ed0a66b79910.png)



ちなみに動画の方針が決まっていないのでこの記事では成果物は非公開にしています。（まだ冒頭しか動画出力できてない）

過去にYoutube動画をiMovieで手作業で作ったことがあるのですがとても手間がかかり片手間でできるレベルじゃなかったのでRemotionには希望を見出していました。
実際は思った以上に柔軟に、楽には作れないもののRemotionのために学習コストを支払ってしまったからにはこっちを使っていきたいよね。といった消極的な理由で今後も使っていくと思います。沼。

早く動画を作りたい人はゆっくり実況メーカーを使った方が良さそうです（筆者は使ったことはありませんが解説動画を見て作りやすそうで感動した）

<a href="https://zenn.dev/seya/articles/47c2c9ee354b60">seyaさん著：React で作るゆっくり解説feat. Remotion</a> を参考にしました。具体的にコードに落とし込む際にはseyaさんの公開されてるコードが有用になると思います。
この記事ではもう少しざっくりした部分について語りました。

上記の記事では動画編集ソフトを新たに覚えるよりReactで作った方が学習コストが低くて良いよね。
とのことでしたがremotionの1フレーム毎に1レンダリングとReactとの兼ね合いがややこしく、またiMovieで動画を作ったことのある自分の用途では動画編集ソフトを覚えた方が学習コストは低い印象でした。

作っていく過程は概ね上の記事のような流れになり、参考にしたところは多いです。また案の定のポイントで手こずる場面が多かったです。
霊夢や魔理沙は使用せず別のイラストで作りました。口パク、目パチに加え独自のモーションをつけながら作っていきました。

ソースコードをある程度は真似して作れば２、３日で作れるだろうと見込んでいましたが結果として２週間かかりました。（スクリプトデータを生成する工程を少しこだわってたのも大きいけれど）
事前に以下の内容を把握しておけばもう少し早く完成したと思います。

ちなみに上記記事だと親切にソースコードも置いてくれていますがそのままクローンしても自分の使い方への移行がしづらかったので結局は０から作ることにしました。部分的には真似させてもらっています。

冒頭が長くなりましたが以下のポイントでつまづいたところと解決策などをまとめました。

・ビルド時にレンダリングが遅い
・lambdaからレンダリングするとチラつきが生じる
・cssによるアニメーションが使えない
・useStateが使えない(too many renderingsになる)
・声ごとにオーディオファイルを作って再生させる
・Remotinoのためだけのコードが必要なことを踏まえると学習コストは低くない
・Reactにロマンを求める人向け

## ビルド時のレンダリングが遅い
自分のローカルからだとデフォルトではCPUのスレッド数が2になっています。
20分ほどの動画になったのですがレンダリングに1分動画あたり 20分 ほどの時間がかかりました。

スペック：
MacBook Pro 2016
3.3 GHz デュアルコアIntel Core i7
16 GB 2133 MHz LPDDR3

のデバイスを使用しています。GPUがないので遅いですね。20分レンダリングするために7時間近くかかってしまいそのままでは使用するのは現実的ではありません。(6時間くらいのところでエラーが生じたら泣く)


```remotion.config.ts
import {Config} from 'remotion';
Config.setConcurrency(4);
```
ということで同時実行数のConcurrencyを設定してあげることでデバイスのCPUのスレッド数に応じた数を設定することに。

もしくは remotion render MyPerfectMovie out/video.mp4 --concurrency 4
のようにプロンプトでオプション指定すると変更できます。

CPUの最大スレッド数を確認するためには --concurrency のオプションであえてエラーが生じる数値を設定することでエラーに最大スレッド数を表示してくれます。


```oscpuCheck.ts
import os from 'os';
console.log(` スレッド数: ${os.cpus().length} ` );
console.log(os.cpus());
```

もしくは上のような形で単体ファイルか何かでosライブラリをnodeで呼び出せばスレッド数の確認ができます。
スレッド数が2から4に変更することで 25% 速度が改善しました。うーん、それでも遅すぎる。

## lambdaからレンダリングするとキャパシティ制限をくらう
※現在メモリ数を増加させようとしている途中です。

さて、20分の動画に地獄のようなレンダリング時間がかかるため @remotion/lambda というlambdaでレンダリングできるライブラリを使用してサーバーサイドレンダリングをすることにしました。

lambdaだとconcurrency数が1000まで使えるため先ほどよりかなり時間を節約できそうです。
セットアップ方法は公式の https://www.remotion.dev/docs/lambda/setup に親切な手順が掲載されています。英語になっているのでAWS上の日本語表記と対応づけが混乱するかもしれないのでそこだけは翻訳しながら注意が必要。

無事、lambdaでのデプロイを果たしてレンダリングしようとしたのですが

```
 npx remotion lambda quotas  

Concurrency limit: 10 - Increase recommended!
```
 npx remotion lambda quotas   で concurrency を確認すると 10 になってしまっています。新アカウントだとこうなることがあるそうです。

```
npx remotion lambda quotas increase

Sending request to AWS to increase concurrency limit from 10 to 5000.
Send? (Y/n) y

AWS returned an "AccessDenied" error message meaning a permission is missing. Read the permissions troubleshooting page: https://remotion.dev/docs/lambda/troubleshooting/permissions. The original error message is:
DependencyAccessDeniedException: Service-linked role creation access denied.
```
npx remotion lambda quotas increase で 1000 に変更したかったのですがパーミッションエラーが。

公式だと npx remotion lambda quotas increase で concurrency を増やせるよ。とのことでしたが上記エラーのため解決策がみつかりませんでした。

### AWSのconcurrencyコンソール画面から申請すれば１日後に concurrency が1000になった
プロンプト画面からだと concurrency を増やすことができません。そのため 10 のまま実行するのですがスレッド数が足りないというエラーが生じてビルドに失敗してしまいます。

結果としてはAWSのWebサイトの方からconcurrencuyを増やすための申請を行うことで 1000 にすることができました。

検索窓でService Quotas > Concurrent executions > クォータの引き上げをリクエスト
で対処しました。

![スクリーンショット 2023-02-09 12.42.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/fb1b7e3e-d92a-1ff4-c666-2408671d91df.png)



## lambdaからレンダリングするとチラつきが生じる
無事にレンダリングできたのですが途中で黒画面がチラつく動画がレンダリングされました。30秒あたりに4回あり手作業で直すにもしんどい頻度です。

https://github.com/remotion-dev/remotion/issues/991#issuecomment-1129315811
>Lambdaのメモリを3GB以上にすると、エラーが発生しにくくなります。今のところ、これを回避策として使うことができます。

とあるのでデフォルトの 2048MB から引き上げを実行してみることにしました。このissue以降でチラつき（flickering）への対処のアプデもあったようですがゆっくり実況系のように頻繁に口パク、目パチなどで画像が切り替わる動画にはまだ対応できていないのかもしれません。

いずれにしろメモリを増やしてみたいところです。

### しかしメモリの引き上げができない
（現在対処中）

---
23.2.10追記：
レンダリングするイメージフォーマットをjpegからpngへ変更することによりチラつきが解消されました。
何らかの理由で無のレイヤー(jpegなので黒)がz-indexマックスで乗っかってきて暗くなっていたところpngにしたことで透過できたのかな？とか推測しています。（違ったらごめんなさい）

しかしどのみちlambda上で20分を超える動画をレンダリング中にタイムアウトしてしまう問題が浮上しました。lambda上は 900秒 までしかビルド時間を設定できなません。同時に1000スレッド処理できるので数分で終わると見込んでいたのですがフレームのレンダリングに入る前の段階でタイムアウトしました。

どうしたらいいんだろう。。と言うことで解決策を模索中です。
```remotion.config.ts
// 追加した
Config.setImageFormat('png');
```
---
![スクリーンショット 2023-02-09 12.46.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/a00ab745-fb37-3572-b69a-67ce62cae9b8.png)


>新しいAWSアカウントは、コンカレンシーとメモリのクォータが減少しています。AWSは、お客様の使用状況に応じてこれらのクォータを自動的に引き上げます。また、クォータの増加を要求することもできます。

とあるように シン・アカウント制限 によりメモリの引き上げができないようです。この記事を作成する前日にアカウントを作ったためだと思われます。
リージョンの問題もあるようですがデフォルトのアメリカのリージョンを指定しておりおそらくその線はなさそうです。（現在、筆者は南米にいるため日本を選択していません）

concurrencyと違ってメモリをWebから引き上げもできないようなので待ちます。（AWS全然知らん民なので違かったらご指摘お願いします）

あと数日待って変わらなければお問合せかリージョンを変えてみます。この記事を作ったのも何もすることができないから書くことにしました草

### レンダリングが始まるまでは遅い
チラつきはあったものの concurrency を無事に 1000 にした状態でとりあえずビルドは成功しました。
しかしテスト的に 30秒程 の動画をレンダリングしたのですがローカルより20,30%早い程度でした。

これはレンダリングが実際に始まるまでのスタートが遅いからでした。
ですがレンダリングが一度始まると 40フレーム毎 にレンダリングをしてくれました。（ローカルデバイスだと 4フレーム毎 なので 10倍）

動画が長くなるほどフレーム数も増加していくようです。
![スクリーンショット 2023-02-09 14.23.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/727d50ac-99ab-9540-5152-82be59a78320.png)
https://www.remotion.dev/docs/lambda/concurrency より

## cssによるアニメーションが使えない
cssの @keyframes でアニメーションできれば楽ですがその技は自分の用途では使えませんでした。

remotionでは 1fps 毎に 1レンダリング で動画を描画しているようです。cssのアニメーションとfpsは同期していないためだと思われます。
またレンダリングは Nフレーム毎 に分解してレンダリングされますから cssアニメーション開始位置 がバラバラになるのもあり上手くいかないのかなと。

## useStateが使えない(長い動画の場合)
useStateを使うとレンダリング数が多すぎてエラーとなります。

口パクと目パチのための処理でレンダリングエラーが生じました。口パクと目パチはそれぞれ6枚の画像を用意して1fps毎に画像を切り替えることで実現させています。配列に画像名を入れてfps毎に応じてどの配列を取得するのかを取得して呼び出すことで口パクと目パチを実現しています。

例：['05','04','03', ...省略, ]で口を開いてから閉じるまでの配列を用意して1fpsずつズラして読み込むようにしている。


```Character.tsx
const facePathRendering = (
	currentFrames: number,
	speakerName: string,
	characterName: string
) => {
	// キャラクターが喋っていない場合のデフォルト
	if (speakerName !== characterName)
		return {imageMouthFileName: '00', imageEyeFileName: '05'};

	const mouthCycle = mouthPattern.normal.length; // 口を開くと閉じる
	const imageMouthFileName = mouthPattern.normal[currentFrames % mouthCycle];

	const eyeCycle = eyePattern.normal.length; // 目を開くと閉じる
	const imageEyeFileName = eyePattern.normal[currentFrames % eyeCycle];

	return {imageMouthFileName, imageEyeFileName};
};
```

口パクと目パチが1パターンだと不自然なので呼び出す配列をランダムにすることを useState で現在の配列を保持しながら行おうとしましたがレンダリング数が多すぎてエラーになりました。

![スクリーンショット 2023-02-09 13.13.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651103/1322264d-77ac-df31-50cc-c242ab5e231e.png)

1fps 毎に 1レンダリング を自前になされているのでそれだけ自前に State が更新されまくってエラーになるようです。シーケンスが短い（動画自体が短い）ければ使えます。

そのため値を保持する手段として useState は使えません。面倒になったのでそもそも値を保持しなくても良いように配列をロングにして色々なパターンで口パク、目パチするようにしました。

## 声ごとにオーディオファイルを作って再生させる
別に定住してるわけでもない南米にいるので魔理沙、霊夢のボイスが使える Aquestalk10 のライセンスを購入して自宅にライセンスを届けることはできないこともあり諦めました。

その代わり、VOICE_BOXという無料のボイスソフトでAPIを立てて(アプリ起動で勝手に立ち上がるのとDocker版もあるよ)音声を読み上げ再生させました。

と言ってもAPIをリアルタイムで叩きながら動画を再生すると途中で読み上げが止まる可能性が考えられたので1トーク毎にAPIを叩いてオーディオとして保存して、1つずつを呼び出す形になりました。

Aquestalk10 だと nodeのライブラリ もあるそうでもっと楽に音声実装できるかと思います。

## remotionのためだけのコードが必要なことを踏まえると学習コストは低くない
上記のように 1fps 毎に 1レンダリング されることを踏まえてこのタイミングに同期させた実装が必要なため簡単なアニメーション１つもfpsの遷移に応じた分岐処理が必要です。

キャラクターを沈黙させたり、キャラクターを同時に喋らせたり、コメントの度に音声を呼び出したり、トランジション（画面の切り替わり）、解説用の画像を任意のタイミングで表示して非表示に切り替えるなどを不具合なく調整することには手間がかかります。

今後ライブラリ側でアニメーションを充実させることで使いやすくなるかもしれません。

## Reactにロマンを求める人向け
しかし出来上がりへ近づくにつれ自分で用意したキャラクターがReact上で動いて喋ってくれるのは感動でした。
Reactで動画を作るとなんだか嬉しくなる、ということがremotionで判明しました。


## まとめ
GCPのインスタンス上で実行とかも時間があればやってみたい。