AWS + Rails で動画配信サイトを作る （３）S3 上の動画ファイルを Lambdaで自動的に動画変換する方法

# 1. この記事で扱う内容
AWSの各種サービスとRailsを使用して動画配信（VOD）サイトを構築する手順を、複数回に分けて紹介しています。　全体の概要については 「[（１）概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)」 を参照してください。


このページでは、S3 に MP4 動画ファイルをアップロードすると、Lambda によって自動的に HLS形式に変換する方法を紹介します。今回は AWS での作業のみで、Rails の操作はありません。

記載した内容は、私が開発中に試行錯誤した結果であって、使用する環境や目的によっては必ずしも最善ではないかもしれませんので、その点をご注意ください。

## 1-1. システム全体の構成
詳しくは 「[（１）概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)」 を参照してください。

## 1-2. この記事で使用する環境と前提条件
**環境**

- CentOS 6.5
- Rails 5.2
- ruby 2.4.1
- Gem
 - aws-sdk-rails 2.0.1
 - aws-sdk-s3 1.23.0

**前提**

- AWS のアカウントを取得済み
- Ruby, Rails で独自にアプリを開発できる程度の知識がある

# 1-3. 事前準備（S3 バケット）
以降の作業で使用する S3 のバケットを事前に作成しておいてください。

- オリジナルの動画ファイルを保存するためのバケット
- HLS形式に変換した動画ファイルを保存するためのバケット

# 2. Elastic Transcoder でパイプラインを作成
Elastic Transcoder とは、AWS のクラウドベースのメディア変換サービスです。
参照：[https://docs.aws.amazon.com/ja_jp/elastictranscoder/latest/developerguide/introduction.html](https://docs.aws.amazon.com/ja_jp/elastictranscoder/latest/developerguide/introduction.html)

作業の流れは以下の通りです。

- SNSのトピックを作成
- Elastic Transcoder のパイプラインを作成

### 2-1. SNSのトピックを作成する
Amazon SNS（Simple Notification Service）とは、AWSの各種サービスで発生したメッセージの配信を管理するサービスです。配信方法には、Email や HTTP などを指定することができます。
参照：[https://docs.aws.amazon.com/ja_jp/sns/latest/dg/welcome.html](https://docs.aws.amazon.com/ja_jp/sns/latest/dg/welcome.html)

ここでは、Elastic Transcoder からの通知をメールで受け取るものとして設定します。

**(1)**
 AWS コンソールで「SNS」と入力して表示される「Simple Notification Service」を選択します。

**(2)**
SNSトピックを作成するリージョンを選択します。

**(3)**
Elastic Transcoder の通知に指定できる SNS トピックは、パイプラインと同じリージョンに登録されたトピックに限られますので、2-2. で作成するパイプラインと同じリージョンを選択してください。

**(4)**
SNS ダッシュボードで「トピックの作成」を選びます。
トピックを初めて作成する場合は、SNS の紹介ページが表示されるので、「今すぐ始める」ボタンを押して、SNS ダッシュボードを表示します。  
![fig7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/5b88385a-7352-5a35-eadb-ac3792612b04.png)

**(5)**
「新しいトピックの作成」ダイアログが表示されるので、トピック名と表示名を入力して「トピックの作成」ボタンを押します。
　●　トピック名 ： 当該トピックのARN（一意な識別子）を作成する際に使用されます
　●　表示名 ： 配信の際に送信元として表示されます

**(6)**
トピックが作成されると「トピックの詳細」画面が表示されますので、「サブスクリプションの作成」ボタンを押します。  
![fig8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/e181f098-4d13-f067-da0f-b556896a2796.png)

**(7)**
「サブスクリプションの作成」ダイアログが表示されるので、プロトコルとエンドポイントを入力して、「サブスクリプションの作成」ボタンを押します。  
![fig9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/8a8d16ad-8b3b-8eaa-3052-bcee57124abf.png)
　●　トピックARN ： このトピックのARNが自動的に入力されます
　●　プロトコル ： メッセージの配信方法を選びます（ここでは Email を選択します）
　●　エンドポイント ： メッセージを受信するメールアドレスを入力します

**(8)**
「トピックの詳細」画面に戻り、サブスクリプション欄に今作成したサブスクリプションが表示されます。作成直後はサブスクリプションIDが「PendingConfirmation」になっています。  
![fig10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/812f5b38-b1c1-e265-2613-1047fe847c75.png)


**(9)**
サブスクリプションが作成されると、エンドポイントに指定したメールアドレスに確認のメッセージが送信されます。差出人は 4. で指定した表示名になっています。  
![fig11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/898cf63e-7621-8799-8fd1-99150c0a03f0.png)

**(10)**
受信した確認メッセージに記載されている「Confirm subscription」のリンクをクリックすると、確認処理が完了したことを示すページが表示されます。  
![fig12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/f4a3e8cd-c260-8126-1f9d-55a52a26f895.png)

**(11)**
確認処理が完了すると、サブスクリプションIDが発行され、「トピックの詳細」画面の当該サブスクリプションのサブスクリプションIDが「PendingConfirmation」から一意のIDに変わります。  
![fig13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/cbdbc2da-3647-553d-5385-3aa4df8e3522.png)

**(12)**
11の状態になれば、この SNS トピックを使用してメッセージを配信することができます。

ここで作成した SNS トピックを Elastic Transcoder で指定すれば、SNS を通じて通知を受け取ることができます。

## 2-2. Elastic Transcoder を設定する
S3 上の動画ファイルを変換するために、Elastic Transcoder のパイプラインを作成します。

**(1)**
AWS コンソールで、検索ボックスに「elastic tra」と入力すると表示される「Elastic Transcoder」を選びます。

**(2)**
ダッシュボードが表示されたら、変換する動画が配置された S3 バケットと同じリージョンか最も近いリージョンに変更します。「Region Unsupported」の画面が表示された場合は、「Supported Regions」に表示されているリージョンから適切なものを選択してください。

**(3)**
ダッシュボード左側の「Pipelines」を選択し、「Create New Pipeline」ボタンを押します。初めてダッシュボードを開くときは、「Welcome to Amazon Elastic Transcoder」の画面が表示されるので、その下にある「Create New Pipeline」ボタンを押します。  
![fig14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/577b3942-7904-9984-f907-dc2209097242.png)

**(4)**
Create New Pipline 画面が表示されるので、各項目を入力します。  
![fig15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/1ddfeba3-0caf-a544-9ce4-a2336689d4ff.png)  
　●　Pipeline Name : パイプラインの名前（後で分かるように適当な名前を付けます）
　●　Input Bucket : 変換前の動画ファイルが入っているS3のバケット名（既存のバケット名がプルダウンで表示されますので、変換前の動画ファイルが配置されているバケットを選びます）
　●　IAM Role : Elastic Transcoder が使用するロール（初めてパイプラインを作成する場合は、[Create Default Role] と出ているのでそのままにしておきます）  
![fig16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/aeca02ac-fb62-9075-5b37-da941840a46f.png)  
　●　Bucket : 変換後のファイルとプレイリストファイルを保存するバケット名（ 既存のバケット名がプルダウンで表示されますので、変換後の動画ファイルを配置するバケットを選びます ）
　●　Storage Class : Amazon S3 ストレージクラスを指定（ここではStandard を選びます）  
※ Amazon S3 では各ファイルのアクセス頻度に応じてパフォーマンスを最適化したストレージクラスと呼ばれる保存領域が幾つか用意されています。ストレージクラスはそのタイプによって使用料金が異なるので、最適なものを選びます。
(参照：https://aws.amazon.com/jp/s3/storage-classes/)  
![fig17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/69cf60af-235a-b829-5463-b6aeac93586b.png)  
　●　Bucket : サムネイルを保存するバケット名（ 既存のバケット名がプルダウンで表示されますので、サムネイルファイルを配置するバケットを選びます ）
　●　Storage Class : Amazon S3 ストレージクラスを指定（ここではStandard を選びます）  
![fig18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/950b0f8e-aa58-6002-20be-6aefa6ebd0a2.png)  
　●　On Progressing Event ： 処理開始時の通知
　●　On Warning Event ： 警告発生時の通知
　●　On Completion Event ： 処理完了時の通知
　●　On Error Event ： エラー発生時の通知  
通知を受け取りたいタイミングに対応する項目で [Use an existing SNS topic] を選び、予め作成しておいた SNS トピックを選択します（ここでは [ SNSTest] を選択します ）  。
　
※ ここで指定できる SNS は、作成中のパイプラインと同じリージョンに作成した SNS トピックに限られます。

**(5)**
すべて入力しましたら、画面右下の「Create Pipeline」ボタンを押して、パイプラインの作成を完了します。

# 3. Lambdaで自動的に動画を変換する
Lambda とは、サーバーを使用せずにスクリプトを実行できる AWS のサービスで、任意のタイミングで自動的にコードを実行することができます。
参照：https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html

ここでは、S3 の特定のバケットに動画ファイルを入れると、そのファイルに対して自動的にElastic Transcoder が実行されるように設定します。

初めて Lambda 関数を作成する場合は、途中でロールを作成する工程がありますので、ロール作成の権限を持った IAM ユーザーでログインしてください。

**(1)**
AWS マネジメントコンソールの検索ボックスに「lambda」と入力して、表示された「Lambda」を選択します。

**(2)**
Lambda ダッシュボードが表示されるので、「関数の作成」ボタンを押します。

**(3)**
「関数の作成」画面が表示されるので、「一から作成」を選択して、表示された項目に以下のように設定します。  
![fig19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/576cd1ff-986e-3a30-f591-32b5361f7dd3.png)
　● 名前 ： 関数の名前を入力します
　● ランタイム ： 関数を実行するプログラム言語またはスクリプト言語を指定します。　ここでは「Node.js 8.10」を選択します。
　● ロール ： 関数の実行に必要な権限を持ったロールを指定します。　はじめて Lambda 関数を作成する場合や新しくロールを作成したい場合は「カスタムロールの作成」を選択します（既存のロールを使用する場合はプルダウンメニューから「既存のロールを選択」を選び、「既存のロール」欄のプルダウンメニューから当該ロールを選択します）。  

**(4)**
「カスタムロールを作成」を選ぶと、新しいタブが開きロール作成の画面が表示されますので、「ロール名」に適当なロール名を入れ、ポリシードキュメントの右端にある「編集」をクリックして、下のテキストボックスに以下のJSONコードを入力し、「許可」を押します。  
  ![fig20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/80a5a105-bbed-efd7-0a02-e38db6ef43ab.png)  

  ```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": [ // 事前に作成しておいたバケット（オリジナル用、変換後用）のARN
        "arn:aws:s3:::bucket-original/*",
        "arn:aws:s3:::bucket-encoded/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "elastictranscoder:CreateJob"
      ],
      // 2-2.で作成したPipelineのARN
      "Resource": "arn:aws:elastictranscoder:ap-northeast-1:000000000000:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sns:Publish"
      ],
      // 2-1.で作成したSNSのARN
      "Resource": "arn:aws:sns:ap-northeast-1:000000000000:ElasticTranscoderNotification"
    }
  ]
}
```

**(5)**
カスタムロールの作成が完了すると自動的に「既存のロール」欄で選択された状態になるので、ロール名に間違いがないことを確認して、「関数の作成」ボタンを押します。

**(6)**
関数編集画面が開いたら、関数コードのエディタに以下のコードを入力します（リージョンやバケット名など環境固有の情報は適宜書き換えてください）。  
![fig21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/af179daa-9728-0c69-a884-6381e1e85b3d.png)

```javascript
'use strict';

var AWS = require('aws-sdk');
var s3 = new AWS.S3({
    apiVersion: '2012-09-25'
});

var transcoder = new AWS.ElasticTranscoder({
    apiVersion: '2012-09-25',
    // ElasticTranscoderのpiplineがあるリージョンを指定
    region: '<Region>'
});

// return dirname without extensionn
function dirname(path) {
    var p = path.split(path.sep).pop().split('.')[0];
    return decodeURIComponent(p);
}

exports.handler = function(event, context) {
  var pipelineId = '<Pipeline-ID>'; // PiplineのID
  var key = event.Records[0].s3.object.key;
  var dkey = dirname(key);

  var params = {
    Input: {
      Key: key,
      FrameRate: 'auto',
      Resolution: 'auto',
      AspectRatio: 'auto',
      Interlaced: 'auto',
      Container: 'auto',
    },
    PipelineId: pipelineId,
    Outputs: [
      //変換したい形式を指定する
      { // hls 1M の場合
               // ディレクトリ名
        Key: dkey + '/1M/s', 
        // ElasticTranscoderに用意されているプリセットのID
        PresetId: '1351620000001-200030',
        //1ファイル当たりの再生時間（秒）
        SegmentDuration: '10' 
      },
      // 以下同様に必要な変換の分だけ設定を記載する
      // hls 600k
      // {
      //   Key: dkey + '/600k/s',
      //   PresetId: '1351620000001-200040',
      //   SegmentDuration: '10'
      // },
    ],
    Playlists: [
      {
        Name: dkey,
        Format: 'HLSv3',
        OutputKeys: [
          dkey + '/1M/s',
          // dkey + '/600k/s',
        ]
      }
    ]
  };

  transcoder.createJob(params, function(err, data){
    if (err) {
      console.log(err, err.stack);
      context.fail();
      return;
    }
    context.succeed('Job well done');
  });
};
```

**(7)**
「Designer」タブの左のメニューから「S3」を選んでトリガーを追加します。  
![fig22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/e6130be1-488a-0959-d970-bb125448dae3.png)

**(8)**
「Designer」の下に表示される「トリガーの設定」でLambda関数を呼び出すトリガーを設定し、「追加」を押します（ここで設定したトリガーは自動的に対象バケットのEventsに設定されます）。  
![fig23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/80a4c514-9c46-befe-bc90-373e2fec6d2a.png)

- バケット :　オリジナルファイルが置かれるバケットを指定
- イベントタイプ　:　「オブジェクトの作成（すべて）」を選択
- サフィックス　:　対象となるファイルの拡張子（.mp4）を指定

**(9)**
画面右上の「保存」ボタンを押して関数を保存します。

**(10)**
以上でLambdaの設定が完了しましたので、対象のバケットにMP4ファイルをアップロードして、自動的にHLS形式に変換されることを確認します。

今回はElasticTranscoderのPiplineでSNS（メール）を設定しているので、Jobの開始時と終了時に通知メールが届きます。
![fig24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/413038/c9c7a36f-7ea7-4756-8d3b-c2007cff133a.png)


<br/>
次回は、S3に保存した動画ファイルを CloudFront 経由で配信する方法について紹介します。
<br/>

# 目次
本記事では、システム構築の手順を以下のように数回に分けて紹介します。

- （１）[概要](https://qiita.com/mas_yam/items/5d6e4973fbdfeb9b7489)
- （２）[開発環境から S3 へのアクセス](https://qiita.com/mas_yam/items/568c1f2f5098ec1aae4b)  
aws-sdk を使用して Rails から S3 にアクセスする方法について紹介します。
- （３）S3 上の動画ファイルを Lambdaで自動的に動画変換する方法  
このページ
- （４）[S3 上の動画ファイルを CloudFront で配信する方法](https://qiita.com/mas_yam/items/7116de3c795b4dc8c015)  
S3 に保存した動画ファイルを CloudFront 経由で配信する方法について紹介します


# Appendix
**参考にしたサイト**

- メドピア開発者ブログ　Rails + AWS でモバイルフレンドリーな動画配信サイト構築（https://tech.medpeer.co.jp/entry/2018/01/18/174542）