WrodPressでアップロードしたメディアファイルをS3に保存して必要に応じてエンコードする方法

要件

  • wordpressのメディアからアップロードされたファイルをS3に保存
  • アップロードされたファイル形式がMOVだった場合mp4にエンコードする

バケットを作る

サービスからS3を選択する。
「バケットを作成する」をクリックしてバケットを作成する。
ほぼデフォルトでOK。
バケット名は任意。今回は「wp-bucket」とする
リージョンは東京とする。

S3のファイルを一般公開する

ファイルにアクセスした時にだれでも読み込み出来るように権限を変更する必要がある。
そうしないとwordpress側などからファイルにアクセス出来なくなる。

【設定手順】
1. バケットの「アクセス権限」をクリック
2. バケットポリシーをクリック
3. バケットポリシーエディターに以下の記述を追加
※wp-bucketの箇所にはバケット名を入力する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::wp-bucket/*"
        }
    ]
}
  1. 入力したら保存する。

参考サイト
http://techtipshoge.blogspot.jp/2016/02/s3.html

パイプラインを作成する

  1. サービスからElastic Transcoderを選択する。
  2. 「Create a new Pipeline」をクリックする。
  3. 設定値を入力
    Pipeline Nameには任意の名前をつける。英語の方がよい
    今回は「wpUploadPipeline」とする。
    input Bucket、
    「Configuration for Amazon S3 Bucket for Transcoded Files and Playlists」のBucket、
    「Configuration for Amazon S3 Bucket for Thumbnails」のbucket
    には全て同じバケットを設定する。今回はwp-bucket
    それ以外は全部デフォルトのまま。

roleを作る

権限を作る
1. サービスからIAMを選択する。
2. 「ロール」を選択する。
3. 「新しいロールの作成」をクリック
4. ロールタイプの選択
「AWS Lambda」を選択
5. ポリシーのアタッチ
何も選択せずに「次のステップ」をクリック
6. 確認

|項目|値|
|–|–|
|ロール名|lambda_auto_transcoder_role (今回は←とする)|
|Role description| (空でいい)|
|信頼されたエンティティ|ID プロバイダー lambda.amazonaws.com (勝手に入ってる)|
|ポリシー|(何も選択しない)|
7. 先に作成した「lambda_auto_transcoder_role」を選択します。
8. インラインポリシーを開いて「表示するインラインポリシーはありません。作成するには、ここをクリックしてください。」のここをクリックする。
9. カスタムポリシーにチェックして「選択」する
10. ポリシーの確認に値を入力
ポリシー名:lambda_auto_transcoder_role_policy
ポリシードキュメント

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:GetRole",
                "iam:PassRole"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:Put*",
                "s3:Get*",
                "s3:*MultipartUpload*"
            ],
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elastictranscoder:*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "sns:CreateTopic",
                "sns:Publish"
            ],
            "Resource": "*"
        }
    ]
}

なお、以下のポリシーは、AWS Lambda(というか CloudWatch)、S3、Amazon Elastic Transcoder、および Amazon SNS への、今回使う機能でなるべく最小限のアクセスを許可したものになります。

  1. 「ポリシーの適用」をクリック
  2. 「信頼関係」のタブをクリックして「信頼関係の編集」をクリックする
  3. 信頼関係の編集
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "elastictranscoder.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

これで「elastictranscoder.amazonaws.com」が追加される。

Lambdaでエンコードの自動化

S3にファイルがアップロードされる。
movファイルだった場合Lambdaの処理が走る。
■処理内容
Elastic TranscoderにアクセスしてJobを作ってそのJobを使ってエンコードする。
今回はmov形式のファイルをmp4にエンコードしたいのでElastic TranscoderのPresetsにある「System preset: Generic 320×240」を使ってエンコードします。

【設定手順】
1. サービスからLambdaを選択。右上からリージョンを東京に変更。ここはバケットで指定したリージョンと合わせる。
2. 「s3-get-object-python」を選択
3. トリガーを設定する
■設定値

|項目|値|
|–|–|
|バケット|wp-bucket|
|イベントタイプ|オブジェクトの作成 (すべて)|
|プレフィックス|(今回は設定しない。こちらでバケットのディレクトリを限定することが出来る)|
|サフィックス|mov|
これでwp-bucketというバケットにオブジェクトの作成 (すべて)されて、そのファイルが.movというファイルだった時に処理が走るということになる。
[トリガーの有効化]にチェックをつける。

今すぐトリガーを有効化するか、テスト用に無効化した状態でトリガーを作成します (推奨)。

  1. 関数を設定する
    ■設定値

|名前|autoTranscoder (任意だが今回はautoTranscoderとする)|
|–|–|
|説明|(任意)|
|ランタイム
| Python 2.7|
5. Lambda 関数のコード
コード エントリ タイプは[コードをインラインで編集]のままにして以下のコードを入力します。

import boto3
from botocore.client import ClientError
import json
import urllib

REGION_NAME = 'ap-northeast-1'
TRANSCODER_ROLE_NAME = 'lambda_auto_transcoder_role'
PIPELINE_NAME = 'wpUploadPipeline'
OUT_BUCKET_NAME = 'wp-bucket'
COMPLETE_TOPIC_NAME = 'test-complete'

print('Loading function')

s3 = boto3.resource('s3')
iam = boto3.resource('iam')
sns = boto3.resource('sns', REGION_NAME)
transcoder = boto3.client('elastictranscoder', REGION_NAME)


def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))

    # Get ARN
    complete_topic_arn = sns.create_topic(Name=COMPLETE_TOPIC_NAME).arn
    transcoder_role_arn = iam.Role(TRANSCODER_ROLE_NAME).arn

    # Get the object from the event
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf8')
    print("bucket={}, key={}".format(bucket, key))
    try:
        obj = s3.Object(bucket, key)
    except Exception as e:
        print(e)
        print("Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.".format(key, bucket))
        # Publish a message
        sns.Topic(complete_topic_arn).publish(
            Subject="Error!",
            Message="Failed to get object from S3. bucket={}, key={}, {}".format(bucket, key, e),
        )
        raise e

    # Delete inactive pipelines
    pipeline_ids = [pipeline['Id'] for pipeline in transcoder.list_pipelines()['Pipelines'] if pipeline['Name'] == PIPELINE_NAME]
    for pipeline_id in pipeline_ids:
        try:
            response = transcoder.delete_pipeline(Id=pipeline_id)
            print("Delete a transcoder pipeline. pipeline_id={}".format(pipeline_id))
            print("response={}".format(response))
        except Exception as e:
            # Raise nothing
            print("Failed to delete a transcoder pipeline. pipeline_id={}".format(pipeline_id))
            print(e)

    # Create a pipeline
    try:
        response = transcoder.create_pipeline(
            Name=PIPELINE_NAME,
            InputBucket=bucket,
            OutputBucket=OUT_BUCKET_NAME,
            Role=transcoder_role_arn,
            Notifications={
                'Progressing': '',
                'Completed': complete_topic_arn,
                'Warning': '',
                'Error': ''
            },
        )
        pipeline_id = response['Pipeline']['Id']
        print("Create a transcoder pipeline. pipeline_id={}".format(pipeline_id))
        print("response={}".format(response))
    except Exception as e:
        print("Failed to create a transcoder pipeline.")
        print(e)
        # Publish a message
        sns.Topic(complete_topic_arn).publish(
            Subject="Error!",
            Message="Failed to create a transcoder pipeline. bucket={}, key={}, {}".format(bucket, key, e),
        )
        raise e

    # Create a job
    try:
        job = transcoder.create_job(
            PipelineId=pipeline_id,
            Input={
                'Key': key,
                'FrameRate': 'auto',
                'Resolution': 'auto',
                'AspectRatio': 'auto',
                'Interlaced': 'auto',
                'Container': 'auto',
            },
            Outputs=[
                {
                    'Key': 'output/{}'.format('.'.join(key.split('.')[:-1])) + '.mp4',
                    'PresetId': '1351620000001-000061',  # System preset generic 320x240
                },
            ],
        )
        job_id = job['Job']['Id']
        print("Create a transcoder job. job_id={}".format(job_id))
        print("job={}".format(job))
    except Exception as e:
        print("Failed to create a transcoder job. pipeline_id={}".format(pipeline_id))
        print(e)
        # Publish a message
        sns.Topic(complete_topic_arn).publish(
            Subject="Error!",
            Message="Failed to create transcoder job. pipeline_id={}, {}".format(pipeline_id, e),
        )
        raise e

    return "Success"

TRANSCODER_ROLE_NAME = ‘lambda_auto_transcoder_role’
PIPELINE_NAME = ‘wpUploadPipeline’
OUT_BUCKET_NAME = ‘wp-bucket’

はこれまでに作ってきたものの名前に合わせてください。
elastic transcoderにあるPresetsのページにpresetの一覧があるので、そこから使いたいpresetのIDのをコピーして「PresetId」にペーストします。
今回はSystem preset generic 320×240のIDである[1351620000001-000061]を記述しました。
もしcontainerがtsの場合は

Outputs=[
    {
        'Key': 'output/{}'.format('.'.join(key.split('.')[:-1])),
        'PresetId': '1351620000001-200030',  # System preset: HLS 1M
        'SegmentDuration': '10',
    },
],

こんな感じで「SegmentDuration」が必要っぽいです。

  1. Lambda 関数ハンドラおよびロール
    先程作成したロールを選択する。
項目 設定値
ハンドラ* lambda_function.lambda_handler
ロール* 既存のロールを選択
既存のロール* lambda_auto_transcoder_role
  1. 詳細設定
    実際の処理には 2200ms ほど必要なので、念のため、Advanced settingsで、Timeout を 3秒から 10秒に変更しておきます。
項目 設定値
メモリ (MB)* 128
タイムアウト* 0分10秒
  1. 後は全部デフォルとのままで「次へ」

■参考サイト

uploadされたファイルを削除する。

エンコードされたファイルが生成された後、エンコード前のファイルを削除する。
autoTranscoderにエンコード前のファイルを削除する記述をすると、エンコードされる前にエンコード前のファイルが削除されてしまうので新しく関数を作って、「エンコードされたファイルが生成されたら」というトリガーを作る。
新しく作成した関数の関数名は「deleteInputFunction」とする。
トリガータイプはS3とする。

項目
バケット wp-bucket
イベントタイプ オブジェクトの作成 (すべて)
プレフィックス output/
サフィックス (空)
from __future__ import print_function

import json
import urllib
import boto3

print('Loading function')

OUT_BUCKET_NAME = 'wp-ana'

s3 = boto3.client('s3')
s3client = boto3.client('s3')


def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))

    # Get the object from the event and show its content type
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key'].encode('utf8'))


    #delete input file
    try:
        inputKey = key.replace('mp4', 'mov')
        inputKey = inputKey.replace('output/', '')
        response = s3client.delete_object(
            Bucket=bucket,
            Key=inputKey
        )
        print(bucket)
        print(inputKey)
        print("is deleted")
    except Exception as e:
        print(bucket)
        print(inputKey)
        print("not deleted")
        print(e)
        raise e

    return "Success"

バケットの権限に「DeleteObject」を追加する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::wp-ana/*"
        }
    ]
}

参考サイト
https://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.delete_object

ログの見方

関数のページから「モニタリング」のタブをクリックして「CloudWatch のログを表示」をクリック
するとログの一覧ページに飛ぶ。


Offload S3の設定

AWSの右上にある自分のアカウントをクリックして、そこのドロップダウンからセキュリティ認証情報をクリック。
「アクセスキー(アクセスキー ID とシークレットアクセスキー)」を開いて「新しいアクセスキーの作成」をクリック。
「アクセスキーを表示」をクリックして「アクセスキー ID」と「シークレットアクセスキー」をどっかにコピーしておく。
wordpressのAWS→Accsess Keysの「Access Key ID」「Secret Access Key」に先程コピーした内容を貼り付ける。
Save Changesをクリック
S3 and CloudFrontをクリックしてBUCKETを指定する。
Remove Files From ServerをオンにすることでWPの入っているサーバーにはアップロードしたファイルは保存されなくなる。

WordPressからアップした動画ファイルのURLのパスを変更する方法

Offload S3というプラグインを使って、メディアファイルをS3にアップロードするようにしています。
なので動画ファイルのURLにはS3からのフルパスが入ります。
ただmov形式の動画ファイルの場合のみAWS内でmp4形式にエンコードするので、wordpressのパスもエンコード後のパスに変更する必要があります。
ex.
変更前
http://s3.amazonaws.com/baket/uploads/2017/05/15035446/movie.mov
変更後
http://s3.amazonaws.com/baket/output/uploads/2017/05/15035446/movie.mp4

方法

WordPressのDBにS3のパスが保存されるので、DBに保存される前に値を変更する。
***_postmetaというテーブルのamazonS3_infoというカラムに情報が入っている。
ex.

a:3:{s:6:"bucket";s:6:"baket-name";s:3:"key";s:46:"output/uploads/2017/05/16082025/movie.mp4";s:6:"region";s:9:"us-east-3";}

Offload S3のプラグインのソースを書き換えます。
amazon-s3-and-cloudfront-pro/classes/amazon-s3-and-cloudfront.php
こちらのファイルを書き換えます。
upload_attachment_to_s3という関数の中の
add_post_meta( $post_id, 'amazonS3_info', $s3object);
の箇所でDB保存しているので、

    //s3のエンコード後のパスに変更する為の関数
    private function s3object_output($s3object){
        $file_info = pathinfo($s3object['key']);
        $img_extension = strtolower($file_info['extension']);
        if($img_extension == 'mov'){
            $s3object['key'] = 'output/' . str_replace(".mov", ".mp4", $s3object['key']);
        }
        return $s3object;
    }

という関数を追加して、

add_post_meta( $post_id, 'amazonS3_info', $this->s3object_output($s3object));

と書き換えます。
s3object_outputの関数ではもしアップロードしたファイルの拡張子がmovだった場合mp4に変更してディレクトリもoutput/を追加するようにいたしました。

コメントを残す