AWS Elemental MediaConvertを利用して、動画の拡張子をmp4に自動変換する構築

# はじめに
AWS Elemental MediaConvertを利用して、様々な拡張子の動画をmp4に変換する構築についてまとめます。
下記が変換フローになります。

1. アップロード用S3に動画・画像をアップロードする
1. Lambdaにイベント通知する
3. 画像の場合、視聴用S3にコピーする
3. 動画の場合、MediaConvertを使い、拡張子をmp4に変換
1. ユーザーが動画・画像にアクセスし視聴する
1. Media Convert で動画をS3にアップロードする前に、アクセスされた場合、オリジンフェールオーバーし、オリジン先がアップロード用S3に変更


6.に関して、Media Convertが実行完了する時間が長い場合、S3にアップロードされる前にリクエストされる可能性があります。
その際に、表示されるよう、オリジンフェールオーバーを利用しています。


![スクリーンショット 2022-10-04 17.28.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/94f75c31-f3d3-f5b9-15cf-2bd9c6dc25c6.png)

# S3を作成
S3をアップロード用と動画の拡張子変換後の用のバケット２つ作成します。
S3をアップロード用は、`upload-movie-and-image`、視聴用（動画の拡張子変換後）`viewing-movie-and-image`という名前にしました。

設定は以下の通りです。
![スクリーンショット 2022-10-02 16.02.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/df16f6d5-4f44-9714-b59b-c189243884ac.png)

# Cloudfront作成
設定は、以下以外は全てデフォルトです。
- オリジンドメインは、s3の`viewing-movie-and-image`を選択
- S3 バケットアクセスは、`Origin access control settings (recommended)`を選択
    - コントロール設定を作成する（設定はデフォルト）
- キャッシュポリシーは、開発中は、`CachingDisabled`にしておく
- ビューワープロトコルポリシーは、`Redirect HTTP to HTTPS`
![スクリーンショット 2022-10-02 16.10.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/cc55dcdc-7ec1-63d9-c23f-0a3b339dc815.png)
![スクリーンショット 2022-10-02 16.10.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/8db6b1bf-3136-7daf-569e-000a6b00465d.png)
ディストリビューション作成が成功すると、ポリシーをコピーし、S3のバケット権限に反映させましょう。
![スクリーンショット 2022-10-02 16.13.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/691618e7-5883-373f-8b79-8f3eef7d8485.png)

そして、s3に適当な画像`aws-icon.png`をアップロードし、表示されるか確認しましょう。

![スクリーンショット 2022-10-02 16.20.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/b309e9c4-4d0c-47cc-807e-394a69bda308.png)
表示されることを確認しました。
![スクリーンショット 2022-10-02 16.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4f202dc7-7aee-3f0c-c530-088fa9ffc93b.png)

## オリジンフェイルオーバーを設定
動画変換完了前に、アクセスが来た場合、動画変換前のS3オリジンに向き先を変える`オリジンフェイルオーバー`を利用します。
これは、複数のオリジンをグループ化し、オリジンの優先順位を付けることで、優先順位の高いものからオリジンリクエストし、特定のステータスコードが返ってきた場合は優先順位の低いものにフェイルオーバー出来る機能になります。

まず、CloudFrontにS3の`upload-movie-and-image`をオリジンとして追加しましょう。
先程と同様の設定でよいです。S3のバケットポリシーの変更も行いましょう。
![スクリーンショット 2022-10-02 16.26.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/aecfcb3f-3691-72cf-cbd2-e29c1e5a074b.png)

### オリジングループ作成
それではオリジングループを作成します。
![スクリーンショット 2022-10-02 16.28.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4873c144-04a9-b784-4e6a-1002e6aa226b.png)
グループ名は、適当に`movie-and-image-group`とします。
プライマリが一番優先順位が高いため、動画変換後のオリジンである`viewing-movie-and-image`をプライマリにします。
フェールオーバー基準は、全て選択します。
![スクリーンショット 2022-10-02 16.29.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/c9a9bcff-dbc0-f821-8d36-75b390e29de6.png)

### ビヘイビア修正
`オリジンとオリジングループ`をオリジングループである`movie-and-image-group`に変更します。
![スクリーンショット 2022-10-02 16.32.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/44e6f407-2097-d1d0-6c7f-99c3aaa3463f.png)

これで、動画変換完了前に、リクエストが来た場合、リクエスト先を動画変換前の`upload-movie-and-image`バケットにオリジンリクエストされます。

# Media Convertのテンプレート作成
動画に関しては、以下の設定にします。
- 解像度は、`1920×1080`
- 拡張子は、`.mp4`

## ジョブテンプレート作成
ジョブテンプレートを作成します。
![スクリーンショット 2022-10-03 15.57.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/845369db-c23f-9c23-847f-9f6e515d0bb6.png)

一般設定は、以下のようにしました。
![スクリーンショット 2022-10-03 16.18.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/464b7367-f346-025d-2d09-4e38c69cb756.png)

`入力`を追加し、デフォルトままにします。
![スクリーンショット 2022-10-03 16.03.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/5f6131d7-af8a-e101-aee6-27f54f14bd3f.png)

`出力グループ`を追加をクリックし、`ファイルグループ`を選択します。
![スクリーンショット 2022-10-03 16.03.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/dc39eaef-9b86-7efe-6699-54e38ff1641c.png)

以下の設定をし、作成します。
- 拡張子：`mp4`
- コンテナ：`MPEG-4 コンテナ`(デフォルト)
- 解像度：`1920×1080`
- レート制御モード：`QVBR`(デフォルト)
- `品質チューニングレベル`：`シングルパス`(デフォルト)
- `大ビットレート (ビット/秒)`：`5000000`

## Media ConvertのIAMロール
Media ConvertからS3に動画をアップロードするためには、Media ConvertのジョブにS3へのアップロード権限を付与する必要がありますので、Media Convertのジョブ用のIAMロールを作成します。

IAMロールは、以下の２つに権限を付与し、`MediaConvert_Default_Role`というロール名で作成します。
- `AmazonAPIGatewayInvokeFullAccess`
- `AmazonS3FullAccess`

![スクリーンショット 2022-10-03 16.54.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/deb511bb-7d69-4dd3-4775-b9fcf16e3959.png)
![スクリーンショット 2022-10-03 16.54.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/deeac676-4a2a-3a77-f38d-c621196629c9.png)
![スクリーンショット 2022-10-03 16.55.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/3972e2e8-0952-b009-aecf-c0200f27c3ac.png)

作成後、IAMロールの`ARN`をコピーしておきましょう。Lambdaの環境変数で使用します。

# Lambdaを作成
アップロード用S3に動画や画像がアップロードされたとき、以下を実行するLambdaを作成します。
- 動画の場合、拡張子が`.mp4 .mov .m4v`の動画を、Media Convertで動画の拡張子を`.mp4`に変換する
- 画像の場合、拡張子が`.png .jpg .jpeg`の画像を、視聴用S3にコピーする

Lambdaは、最新の`Python3.9`を選択し、他はデフォルトで作成します。
![スクリーンショット 2022-10-03 15.46.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/a081f99f-e17e-7a00-f1ae-6d89c9f462aa.png)

## IAMロール
作成したLambdaのIAMロールに対して、以下の権限を追加付与します。
- `AmazonS3FullAccess`をIAMロールにアタッチ
- `iam:PassRole`と`mediaconvert:CreateJob`をIAMポリシーに付与


LambdaのIAMポリシー（AWSLambdaBasicExecutionRole-xxxxx）に権限を付与し、以下の通りになります。
```diff_json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "mediaconvert:CreateJob"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:xxxxxxxxxxx:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:xxxxxxxxxxx:log-group:/aws/lambda/media_convert-movie-and-image:*"
            ]
        }
    ]
}
```
## 設定
タイムアウトは、１分に変更します。

環境変数は、以下の４つを設定します。

- JOB_TEMPLATE_NAME：`arn:aws:mediaconvert:ap-northeast-1:[アカウントID]:jobTemplates/[ジョブテンプレート名]`
- MEDIACONVERT_ENDPOINT：`https://[ランダム文字列].mediaconvert.ap-northeast-1.amazonaws.com`
    - MediaConvertのAPIエンドポイント（MediaConvertのコンソールで確認できます）
    ![スクリーンショット 2022-10-03 17.04.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/cd0ed503-6870-649a-80f0-75014a13d708.png)
- MEDIACONVERT_ROLE：`先程作成したIAMロールのARN`
- OUTPUT_BUCKET：`viewing-movie-and-image`
    - 視聴用S3のバケット名

以下のようになります。
![スクリーンショット 2022-10-03 18.24.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/aaf6c004-24e0-bff2-d29b-debafc60011b.png)

## コード
ファイルは、`lambda_function.py`の他に、`job.json`ファイルを作成します。
Media Convertの処理は、ジョブテンプレートをベースに、Lambda側でインプットとアウトプット用のS3バケットを指定するようにしているため、`job.json`にインプットとアウトプットのバケット情報を追加する必要があります。

![スクリーンショット 2022-10-03 18.28.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/32fcffff-83e2-e902-c467-15cf98fb9550.png)
```python:lambda_handler.py
import os
import urllib.parse
import boto3
import json
from decimal import Decimal

MEDIACONVERT_ENDPOINT = os.environ['MEDIACONVERT_ENDPOINT']
MEDIACONVERT_ROLE = os.environ['MEDIACONVERT_ROLE']
JOB_TEMPLATE_NAME = os.environ['JOB_TEMPLATE_NAME']
OUTPUT_BUCKET = os.environ['OUTPUT_BUCKET']
s3 = boto3.client('s3')

def decimal_to_int(obj):
    if isinstance(obj, Decimal):
        return int(obj)

def lambda_handler(event, context):
    print("Received event:" + json.dumps(event, default=decimal_to_int, ensure_ascii=False))
    input_bucket_name = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    basename = os.path.basename(key).split('.')[0]
    extension = os.path.basename(key).split('.')[1]
    input_bucket_path = key.split('/')[0]
    input_bucket = f"s3://{input_bucket_name}/{key}"
    output_bucket = f"s3://{OUTPUT_BUCKET}/{input_bucket_path}/"

    if extension in ["mp4", "m4v", "mov"]:
        print("mp4,m4v,mov")
        try:
            with open("job.json", "r") as jsonfile:
                job_object = json.load(jsonfile)
    
            job_object["Inputs"][0]["FileInput"] = input_bucket
            job_object["OutputGroups"][0]["OutputGroupSettings"]["FileGroupSettings"]["Destination"] = output_bucket
    
            client = boto3.client('mediaconvert', endpoint_url=MEDIACONVERT_ENDPOINT)
            response = client.create_job(
                Role=MEDIACONVERT_ROLE,
                JobTemplate=JOB_TEMPLATE_NAME,
                # 入力ファイルの情報や、上書きしたいパラメータの情報などを渡す
                Settings=job_object,
            )
    
            print("Received response:" + json.dumps(response, default=decimal_to_int, ensure_ascii=False))
            return "ok"
    
        except Exception as e:
            print(e)
            raise e
            return "error"

    # png,jpeg,jpg
    try:
        print("png,jpeg,jpg")
        # 上記で抜き出したバケット情報とkey情報よりアップロードファイルにアクセスし、コンテンツのタイプを出力する。
        response = s3.get_object(Bucket=input_bucket_name, Key=key)
        print("CONTENT TYPE: " + response['ContentType'])

        # 実際のコピーコマンド
        response = s3.copy_object(Bucket=OUTPUT_BUCKET, Key=key, CopySource={'Bucket': input_bucket_name, 'Key': key})
        print("Received response:" + json.dumps(response, default=decimal_to_int, ensure_ascii=False))
        return "ok"
            
    except Exception as e:
        print(e)
        raise e
```

```json:job.json
{
  "OutputGroups": [
    {
      "Name": "File Group",
      "OutputGroupSettings": {
        "Type": "FILE_GROUP_SETTINGS",
        "FileGroupSettings": {
          "Destination": ""
        }
      }
    }
  ],
  "Inputs": [
    {
      "FileInput": ""
    }
  ]
}
```
## イベント通知
S3に動画・画像がアップロードされた際に、Lambdaが起動できるよう、Lambdaのコンソール画面からトリガー追加をクリックします。

![スクリーンショット 2022-10-04 10.33.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/249cba1f-9e21-f394-557a-407ba30662ea.png)
トリガーは、アップロード用のS3を指定し、`Suffix(拡張子)`は、`.jpg`などを選択し、保存します。

![スクリーンショット 2022-10-04 10.31.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/14adbc51-d9e7-f8cd-dbf7-b6e60e9b9e07.png)

これを対象の拡張子の数だけトリガーとして追加します。
そのため、トリガー数は、`.png .jpg .jpeg`と`.mp4 .mov .m4v`の合計で6つになりますね。
![スクリーンショット 2022-10-04 10.45.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/4b9aeedc-38b0-f87f-477d-a67367fbb9d7.png)

# テスト
では、アップロード用のS3に、動画(.m4v) と 画像(.png)をアップロードしてみましょう。
![スクリーンショット 2022-10-04 10.44.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/d6fbf88d-acdf-e7ac-b647-43ebad96c40d.png)

画像は、コピーされ、動画はmp4に変換されていることが分かります。
動画は、解像度を`1920*1080`に変換したため、サイズが小さくなっていることが分かります。
![スクリーンショット 2022-10-04 10.54.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/988747/aa68876e-f0fc-5154-534a-212828fd1e9c.png)


# 参照
## コーデック

https://docs.aws.amazon.com/ja_jp/mediaconvert/latest/ug/reference-codecs-containers-input.html


https://www.slideshare.net/skikuchi/akibaaws-aws-elemental-mediaconvert-86885691

## 作成方法

https://qiita.com/gion/items/80a63d242663ea6259de


## アーキテクチャー

https://cloudsmith.co.jp/blog/cloud/2022/06/1941276.html

https://tech.appbrew.io/entry/2020/06/30/120000