S3に動画がアップロードされたら自動でHLS出力する仕組みを作る

2023.01.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

この記事の目標

S3バケットに動画ファイルがアップロードされたら、自動的にストリーミング再生用に変換してS3バケットに保存する仕組みを構築していきます。

STEP1: 事前準備

ここでは必要な下準備を行っていきます。

S3バケットの作成

S3バケットは、バケット名以外はデフォルトで作成します。

作成したS3バケットの中にさらにinputフォルダとoutputフォルダを作成します。

inputフォルダに元の動画データがアップロードされ、変換後のファイルがoutputフォルダに出力されるイメージです。

inputフォルダにテストデータを置いておきます。

MediaConvertテンプレートの作成

ファイルを変換するにはAWS Elemental MediaConvertというサービスを使用します。

1回限りの変換ジョブを単体で作成することもできますが、今回はS3にファイルがアップロードされるたびに同じ処理を実行したいので、テンプレートを作成します。

左ペインで「ジョブテンプレート」を選択し、「テンプレートの作成」をクリックします。

「一般設定」ではテンプレート名に任意の名前を入力します。

「入力」で「追加」をクリックすると「入力1」というものが作成されます。こちらは触らずデフォルトのままにします。

続いて「出力グループ」で「追加」をクリックします。

ダイアログが表示されるので、「Apple HLS」を選択します。

すると、「出力グループ」が画像のようになります。

「Apple HLS」をクリックしてカスタムグループ名と送信先S3バケットを指定します。

送信先S3バケットは先ほど作成したoutputフォルダです。

「H.264, AAC」をクリックして出力フォーマットと名前修飾子を指定します。

出力フォーマットは要件に合わせて好きな値を入力することもできますが、ここでは適当なプリセットを選択しました。

名前修飾子は、出力されるファイル名の末尾に付加される文字列です。ここでは「_hls」と入力しておきます。

以上の設定を終えたら右下の「作成」からテンプレートを作成します。

MediaConvert用ロールの作成

MediaConvertにはS3バケットのファイルを操作するためのロールが必要です。

IAMロールの画面から「ロールの作成」をクリックします。

信頼されたエンティティタイプで「AWSのサービス」、ユースケースで「MediaConvert」を選択します。

S3とAPI Gatewayへのフルアクセス権が追加されます。

任意のロール名を入力して右下の「ロールを作成」をクリックします。

STEP2: 手動でテンプレートからジョブを実行する

ここでは、まずはLambda関数を手動で動かして、固定の動画ファイルを変換できるようにしていきます。

Lambda関数の作成

ランタイムでNode.js 18.xを選択してLambda関数を作成します。

Lambda実行ロールの編集

Lambda関数にデフォルトで作成されるロールにポリシーを追加します。

このポリシーは、Lambda関数がMediaConvertを実行できるようにするためのものです。STEP1で作成したロールとは別物なので注意してください。

Lambdaの「設定」⇒「アクセス権限」の「実行ロール」からIAMロールの画面に遷移します。

「許可を追加」⇒「ポリシーをアタッチ」をクリックします。

AWS管理の「AWSElementalMediaConvertFullAccess」を選択して、右下の「ポリシーをアタッチ」をクリックします。

許可ポリシーに「AWSElementalMediaConvertFullAccess」が追加されます。

Lambda関数の中身を作成

MediaConvertを利用する際には、リージョン固有のエンドポイントが必要になります。

そこで、まずはエンドポイントを取得してみます。

import { MediaConvertClient, DescribeEndpointsCommand } from "@aws-sdk/client-mediaconvert";

export const handler = async(event) => {    
    const client = new MediaConvertClient();
    const command = new DescribeEndpointsCommand({});
    const response = await client.send(command);
    console.log(response.Endpoints[0].Url)
};

Lambda関数をテスト実行してみると、エンドポイントが取得できることがわかります。

このエンドポイントを元にMediaConvertClientを作成し、クライアントから各種コマンドを実行することでMediaConvertを操作するかたちになります。

クライアントを作成する部分を別モジュールに切り出します。

Lambdaのコードソース画面で、以下のように新規ファイル「emcClient.mjs」を作成します。

emcClient.mjsの内容を以下のように編集します。エンドポイントの取得とクライアントの作成を行い、作成したクライアントをエクスポートしています。

import { MediaConvertClient, DescribeEndpointsCommand } from "@aws-sdk/client-mediaconvert";
    
// エンドポイントを取得
const client = new MediaConvertClient();
const command = new DescribeEndpointsCommand({});
const response = await client.send(command);
    
const ENDPOINT = {
    endpoint: response.Endpoints[0].Url,
};
    
const emcClient = new MediaConvertClient(ENDPOINT);
export { emcClient };

次に、MediaConvertテンプレートからジョブを動かすコードを作成します。

index.mjsを以下のように編集します。Roleで指定するのは、STEP1で作成したロールのARNです。

import { CreateJobCommand } from "@aws-sdk/client-mediaconvert";
import { emcClient } from "./emcClient.mjs";

export const handler = async(event) => {
    const params = {
        JobTemplate: "テンプレート名",
        Role: "ロールのARN",
        Settings: {
            Inputs: [
                {
                    FileInput: "入力ファイルのS3 URI(s3://から始まるアドレス)",
                }
            ]
        }
    };
    try {
        const response = await emcClient.send(new CreateJobCommand(params));
        console.log("Success");
    } catch (err) {
        console.log("Error", err);
    }
};

テスト実行してみます。

MediaConvertのジョブ画面を見ると、ジョブが作成されてPROCESSINGになっています。

少し待つとステータスがCOMPLETEになりました。

S3バケットのoutputフォルダを見ると、sample.mp4がストリーミング再生用に変換されたファイルが作成されています。

ここまでで、テンプレートを使って固定のファイルを手動で変換することができました。

最後に、S3バケットのinputフォルダにアップロードされたら、この処理が自動で動くようにしていきます。

STEP3: S3のPutイベントをトリガーに実行されるようにする

S3イベント通知の設定

S3バケットの「プロパティ」⇒「イベント通知」で「イベント通知を作成」をクリックします。

イベント名は任意の名前を入力します。

inputフォルダにアップロードされたときのみ実行したいので、プレフィックスにinput/を入力します。

イベントタイプでPUTにチェックを入れます。

送信先は先ほど作成したLambda関数を選択します。

これでS3の準備は完了しました。

Lambda関数の編集

固定のファイルを処理している状態から、S3バケットにアップロードされたファイルを処理するようにLambda関数を変更します。

index.mjsを以下のように変更します。

import { CreateJobCommand } from "@aws-sdk/client-mediaconvert";
import { emcClient } from "./emcClient.mjs";

export const handler = async(event) => {
    // アップロードされたファイルのS3 URIを取得
    const file = `s3://${event.Records[0].s3.bucket.name}/${event.Records[0].s3.object.key}`

    const params = {
        JobTemplate: "テンプレート名",
        Role: "ロールのARN",
        Settings: {
            Inputs: [
                {
                    FileInput: file,
                }
            ]
        }
    };
    try {
        const response = await emcClient.send(new CreateJobCommand(params));
        console.log("Success");
    } catch (err) {
        console.log("Error", err);
    }
};

Lambdaに渡される引数からバケット名とオブジェクト名を取得し、S3 URIを組み立てます。そしてMediaConvertに渡すパラメータに組み立てたURIを渡します。

では、inputフォルダにsample2.mp4をアップロードしてみます。

MediaConvertのジョブ画面を見ると、sample2.mp4を入力ファイルとしたジョブが実行されていることがわかります。

S3バケットのoutputフォルダにも、sample2.mp4を変換したデータができています。

これで、S3バケットに動画ファイルがアップロードされたら、自動的にストリーミング再生用に変換してS3バケットに保存するという当初の目標を達成することができました。

まとめ

IAMロールの設定や、Lambda関数の作成など、最初はエラーになったりうまく動かなかったりして少し苦労しました。しかし、一度自動化できてしまえばその後の作業はとても楽になります。

また、初めてAWS Elemental MediaConvertを使ってみましたが、メディアファイルの処理が簡単にできて驚きました。変換だけでなく他にも様々な処理ができるようなので、色々と試してみたいと思います。