PythonとFFmpegとWebMで透過動画を作る

# はじめに
+ 透過ありの動画を作ります。
+ MoviePyの出力用の関数では透過の出力ができなかったため、FFmpegを直接利用しています。
+ 一時ファイルを作る方法はストレージの負荷になるため、ターミナルのパイプラインを利用しています。

# 制作物
Youtube上では透過部分は黒色になっています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/HW_ZpxXDIKI?si=rhgUEW-bVptn9dPi" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


実際の利用ではこの動画を別の動画に張り付けるなどの利用を想定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242971/c2f64c76-9668-45a5-84c1-7de39d6ae932.png)

# コード
```python
def create_video(clip, audio_file_path, output_file, fps=30):
    width, height = clip.size
    ffmpeg_cmd = [
        'ffmpeg',
        '-y',
        '-f', 'rawvideo',
        '-pixel_format', 'rgba',
        '-s', f'{width}x{height}',
        '-r', str(fps),
        '-i', '-',
        '-i', audio_file_path,
        '-c:v', 'libvpx-vp9',  # VP9ビデオコーデック
        '-c:a', 'libopus',     # Opusオーディオコーデック
        '-pix_fmt', 'yuva420p',
        '-lossless', '1',
        '-threads', '4',
        '-f', 'webm',          # 出力形式をWebMに設定
        output_file
    ]
     
    process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)
    for frame in clip.iter_frames(fps=fps):
        process.stdin.write(frame.tobytes())
    process.stdin.close()
    process.wait()
```
+ clip：MoviePyで作れるクリップです。clipから生成される画像は4チャネルのRGBA画像を想定しています。
    + process.stdin.write(frame.tobytes())の部分のframeではNumpyに変換されています。frameがNumpyであればよいので、MoviePyを利用せずにNumpyでの処理も可能です。

+ audio_file_path：音声ファイルのパス
+ output_file：出力ファイルのパス
+ fps：フレームレート

ffmpegのインストールが必要です。
透過動画でなければMoviePyの関数で問題なく出力できます。
音声ファイルも保存せずにVOICEVOXからちょくせｔ入力できそう。

# テスト用のコード
MoviePyのバージンは1.0.3です。
```python test.py
import subprocess
from moviepy.editor import VideoClip
import numpy as np
from PIL import Image, ImageDraw

def create_video(clip, audio_file_path, output_file, fps=30):
    width, height = clip.size
    ffmpeg_cmd = [
        'ffmpeg',
        '-y',
        '-f', 'rawvideo',
        '-pixel_format', 'rgba',
        '-s', f'{width}x{height}',
        '-r', str(fps),
        '-i', '-',
        '-i', audio_file_path,
        '-c:v', 'libvpx-vp9',  # VP9ビデオコーデック
        '-c:a', 'libopus',     # Opusオーディオコーデック
        '-pix_fmt', 'yuva420p',
        '-lossless', '1',
        '-threads', '4',
        '-f', 'webm',          # 出力形式をWebMに設定
        output_file
    ]
     
    process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)
    for frame in clip.iter_frames(fps=fps):
        process.stdin.write(frame.tobytes())
    process.stdin.close()
    process.wait()

# ダミー動画クリップ
def create_dummy_clip(duration=5, size=(640, 480), fps=30):
    def make_frame(t):
        image = Image.new("RGBA", size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(image)
        draw.rectangle((100, 100, 200, 200), fill=(255, 0, 0, 255))
        image = image.rotate(t*10)
        return np.array(image)
    return VideoClip(make_frame, duration=duration)
# ダミーwavファイル
def create_dummy_audio(output_file, duration=5, sample_rate=44100):
    ffmpeg_cmd = [
        'ffmpeg',
        '-y',
        '-f', 'lavfi',
        '-i', f'sine=frequency=440:duration={duration}',
        '-ar', str(sample_rate),
        output_file
    ]
    subprocess.run(ffmpeg_cmd)

if __name__ == "__main__":
    duration = 10
    # ダミー動画クリップ作成
    dummy_clip = create_dummy_clip(duration)

    # ダミー音声ファイル作成
    dummy_audio_file = "dummy_audio.wav"
    create_dummy_audio(dummy_audio_file, duration)

    create_video(dummy_clip, dummy_audio_file, "output.webm")
```