MediaConvert+Lambda+S3イベントで全自動変換フローの構築

# この記事は？
[前の記事](https://qiita.com/gion/items/cab2a230ffe15366816f)でMediaConvertにジョブテンプレートを登録し、MediaConvertコンソールから動画変換を行いました。
今回はMediaConvertのジョブを生成するLambda関数（本記事ではPython3を使いますが、他の言語でも同等の関数は記述できるはずです）を作成し、これをS3のイベントから起動することで全自動の動画変換フローを構築してみます。

# Lambda関数の準備
1. Lambda コンソールを開きます
2. 『関数の作成』をクリックします
3. 『設計図』から『s3-get-object-python3』を選択し、『設定』をクリックします
4. 『基本的な情報』を次のように設定します
    - ロール: 『テンプレートから新しいロールを作成』
    - ポリシーテンプレートに『S3 オブジェクトの読み取り専用アクセス権限』があることを確認
    - 名前・ロール名は適宜設定してください
5. 『S3トリガー』を次のように設定します
    - イベントタイプ: 『オブジェクトの作成（すべて）』
    - トリガーの有効化: チェックを入れる
    - ほかの項目は適宜設定してください
    - ただし同一のバケットに変換結果を出力する場合はプレフィックスなりサフィックスを考慮しないと無限ループになると思います（未検証）
6. 『関数の作成』をクリックします

## 関数コードの書き換え
作成できたら、関数を次のように書き換えます。

```python:Lambda関数のコード
import os
import logging
import urllib.parse
import boto3

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


# MediaConvertのエンドポイントURL
MEDIACONVERT_ENDPOINT = os.environ['MEDIACONVERT_ENDPOINT']

# MediaConvert用のロールのARN
MEDIACONVERT_ROLE = os.environ['MEDIACONVERT_ROLE']

# 前回作成したジョブテンプレートの名前
JOB_TEMPLATE_NAME = os.environ['JOB_TEMPLATE_NAME']


def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    settings = make_settings(bucket, key)
    user_metadata = {
        'JobCreatedBy': 'videoConvertSample',
    }

    client = boto3.client('mediaconvert', endpoint_url=MEDIACONVERT_ENDPOINT)
    result = client.create_job(
        Role=MEDIACONVERT_ROLE,
        JobTemplate=JOB_TEMPLATE_NAME,
        # 入力ファイルの情報や、上書きしたいパラメータの情報などを渡す
        Settings=settings,
        # タスクにユーザ定義のデータを紐付けることもできる。
        # キーと値が両方 `str` でないとダメ。
        UserMetadata=user_metadata,
    )

    logger.info(str(result))


def make_settings(bucket, key):
    basename = os.path.basename(key).split('.')[0]
    
    # APIリファレンスを参考に設定
    return \
    {
        "Inputs": [
            {
                "FileInput": f"s3://{bucket}/{key}",
            }
        ],
        "OutputGroups": [
            {
                "Name": "Apple HLS",
                "OutputGroupSettings": {
                    "Type": "HLS_GROUP_SETTINGS",
                    "HlsGroupSettings": {
                        # 出力先パス。別バケットも可。トリガーの設定に応じて適宜変更してください。
                        "Destination": f"s3://{bucket}/converted/{basename}/",
                    },
                },
                "Outputs": [
                    {
                        "VideoDescription": {
                            "Width": 640,
                            "Height": 360,
                        },
                    },
                ],
            },
        ],
    }
```

## 動作の準備
このコードを動作させるために環境変数を3つ設定する必要があります。
- `JOB_TEMPLATE_NAME`: 前回作成したテンプレートの名前
- `MEDIACONVERT_ENDPOINT`: MediaConvertのコンソールから『アカウント→APIエンドポイント』と辿ったページに記述されているエンドポイントURL
- `MEDIACONVERT_ROLE`： MediaConvert用のロールのARN

また、Lambda関数を実行するロールに `mediaconvert:CreateJob` の権限とMediaConvert用ロールに対する `iam:PassRole` の権限を付与する必要がります。

## 変換ジョブについて
### 設定のオーバーライド
プログラム側で設定を変更する必要がある場合、`create_job` メソッドの `Settings` 引数にテンプレートとの差分を渡すと
その設定を用いて変換を行うことが可能です。設定の詳細については、[AWSのAPIドキュメント](https://docs.aws.amazon.com/mediaconvert/latest/apireference/jobs.html#jobspost)
や[Boto3のドキュメント](https://boto3.readthedocs.io/en/latest/reference/services/mediaconvert.html#MediaConvert.Client.create_job)を参考にしてください。

### メタデータの付与
キーと値が両方 `str` である辞書に情報を追加し、 `create_job` メソッドの `Metadata` 引数に渡すとジョブにユーザー定義のデータを付与することができます。


# 実行
S3 コンソールやAWS CLIなどでトリガーの対象となるパスに動画ファイルを配置すると、Lambda関数が自動で起動してMediaConvertにジョブが追加されます。
エラーが起こらずに変換処理が完了すれば、元動画のあるバケットの `/converted/[変換元ファイル名から拡張子を除いたもの]/` 以下にHLSのファイル群が生成されています。
バケットが適切に公開設定されていれば、マスタープレイリストをHLS対応ブラウザから開くことで動画を再生できます。


# 変換後に処理を行う場合
変換後に何らかの処理を行いたい場合、CloudWatch Eventsを使って完了通知を受けとるのが最も簡単です。
CloudWatch Eventsのルール作成から以下のようなイベントパターンを設定することで、ジョブの完了（成功・失敗両方）を捕捉することができます。

```json:イベントパターン
{
  "source": [
    "aws.mediaconvert"
  ],
  "detail": {
    "status": [
      "COMPLETE",
      "ERROR"
    ]
  }
}
```

このルールから流されてくるイベントには、ジョブの固有ID、成功・失敗の情報、ジョブ作成時に付加したメタデータ、動画長や縦横の幅などのデータが入っています。

```json:流れてくるイベント
{
  "version": "0",
  "id": "ff855457-96e9-59f7-dfc9-f3688a3f5276",
  "detail-type": "MediaConvert Job State Change",
  "source": "aws.mediaconvert",
  "account": "123456789012",
  "time": "2017-11-29T18:57:23Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:mediaconvert:us-east-1:123456789012:jobs/123456789012-smb6o7"
  ],
  "detail": {
    "timestamp": 1511981843037,
    "accountId": "123456789012",
    "queue": "arn:aws:mediaconvert:us-east-1:123456789012:queues/Default",
    "jobId": "123456789012-smb6o7",
    "status": "COMPLETE",
    "userMetadata": {},
    "outputGroupDetails": [
      {
        "outputDetails": [
          {
            "durationInMs": 10010,
            "videoDetails": {
              "widthInPx": 1280,
              "heightInPx": 720
            }
          }
        ]
      }
    ]
  }
}
```

たとえば、Lambda関数をこのルールのターゲットにすれば、ポーリング等を行うことなく変換後の処理を定義することができます。


# まとめ
MediaConvertによる動画変換の方法と、S3+Lambdaとの連携で全自動の変換フローを構築できることをみてきました。更にCloudWatch Eventsのルールを設定することで、変換完了のイベント発火を捕捉してLambdaやSNS, SQSに渡す方法も説明しました。
応用次第で様々なワークフローが構築できると思うので、マネージドな動画変換サービスを探している方はぜひ試してみるとよいのではないでしょうか……！