はじめに
システム開発推進部第2Gの笹野です。 今回はAWSで動画納品後のHLS変換を自動化したことについてまとめました。
諸事情により、もともと利用していたCDNのサービスをAWSに移行する必要が生じたのですが、 もとのサービスで配信のための動画変換も行っていたため、動画の変換処理もAWSに移行しました。
今回は、動画の納品からHLS変換して配信できるようにするまでのフローを、Lambdaを使って自動化するという対応をしましたので、一例として紹介できればと思います。
納品からHLS変換の手順
以下AWSで作成済みの環境でのHLS変換手順です。 「S3に動画ファイルをアップロード」をきっかけに以降の手順が自動化されることを目指します。
- S3上に動画単位でディレクトリが作成される&3つの画質別mp4がアップロードされる
- アップロードされた動画ファイル名からsmilファイルを作成して同ディレクトリにアップロード
- 作成したsmilファイルを元にMediaPackageでアセット作成
- 配信サービス上の動画IDとアセット情報(再生URL)を紐づけるテーブルに登録
以降でも最後の紐づけテーブルに登録する手順についてほんの少々触れますが、サービス固有の仕組みになるので、適宜読み飛ばしていただければと思います。
変換フローとAWS構成図
自動化にあたっての処理フローと構成図です。
- S3に動画が納品
- mp4ファイルのアップロードをきっかけにLambdaをトリガー
- 動画が3つある場合、LambdaからMediaPackageのアセットを作成
- アセットの作成完了をきっかけにEventBridgeからLambdaをトリガー
- LambdaからRDSに動画配信データを登録

RDSを別のVPCに置いている都合でDB関連の処理は別のLambdaに分けています。伴ってEventBridgeを利用しています。
解説
今回は以下2つのLambdaのコードを抜粋して解説していきます。
- MediaPackageVODアセットを作成するLambda(Node.js 22)
- RDSにある紐づけテーブルに再生URLを登録するLambda(Python 3.13)
MediaPackageVODアセットを作成するLambda(Node.js 22)
- mp4ファイル一覧の取得
S3に動画がアップロードされるたびにLambdaが実行されます。 動画が必要数ない場合でも実行されるため、ListObjectsでmp4の数を取得し分岐しています。 (S3にある各ディレクトリ名がサービス上の動画IDとなる仕様のため、ここで動画IDも取得できるようにしています。)import { S3Client, ListObjectsV2Command, PutObjectCommand } from "@aws-sdk/client-s3"; const s3Client = new S3Client({ region: process.env.REGION }); // ..省略 // S3イベントからバケット名とオブジェクトキーを取得 const bucketName = event.Records[0].s3.bucket.name; const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' ')); // ディレクトリ名を抽出(最後のスラッシュ以前の部分) const directoryName = key.substring(0, key.lastIndexOf('/')); const videoId = key.substring(0, key.lastIndexOf('/')); // ListObjects用のパラメータ params = { Bucket: bucketName, Prefix: directoryName + '/', Delimiter: '/', }; const command = new ListObjectsV2Command(params); const data = await s3Client.send(command); const filePathList = data.Contents .map(item => item.Key) // ファイルキーを取得 .filter(fileName => fileName.endsWith('.mp4')); // 拡張子が.mp4のファイルに絞り込む smilファイルの作成とアップロード
S3のPutObjectでsmilファイルをアップロードします。 今回は素材の動画ファイルと同じディレクトリにsmilファイルを配置します。const fileNameList = filePathList.map(filePath => filePath.split('/').pop()); const smilData = getSmilTextByFileNameList(fileNameList); const filePath = `${videoId}/${videoId}.smil`; // S3のパラメーターを設定 params = { Bucket: bucketName, Key: filePath, Body: smilData, ContentType: "application/smil+xml", }; // S3にオブジェクトをアップロード const command = new PutObjectCommand(params); const data = await s3Client.send(command);MediaPackageVODアセットの作成
Lambdaからのアセット作成にはimport { MediaPackageVodClient, CreateAssetCommand } from "@aws-sdk/client-mediapackage-vod"; const mediaPackageClient = new MediaPackageVodClient({ region: process.env.REGION }); // ...省略 const packagingGroupId = process.env.PACKAGING_GROUP_ID; const arnS3Origin = process.env.ARN_S3_ORIGIN; const arnRoleMediaPackage = process.env.ARN_ROLE_MEDIA_PACKAGE; const params = { Id: videoId, PackagingGroupId: packagingGroupId, SourceArn: `${arnS3Origin}/${filePath}`, SourceRoleArn: arnRoleMediaPackage }; // CreateAssetCommandのインスタンス化 const command = new CreateAssetCommand(params); const response = await mediaPackageClient.send(command);client-mediapackage-vodを利用します。(参考) アセット作成が完了すると、EventBridge経由で次のLambdaが呼び出されます。 今回EventBridgeでアセット作成でエラーが発生したとき、再生可能なアセットが作成できたときの2イベントのルールを設定しました。 他のMediaPackageのイベントについても公式が参考になります。{ "detail": { "event": [{ "prefix": "IngestError" }, { "prefix": "VodAssetPlayable" }] } }
RDSにある紐づけテーブルに再生URLを登録するLambda(Python 3.13)
detail = event.get('detail')
asset_status = detail['event']
arn_list = event.get('resources') # arnに動画IDが含まれている
content_url = detail['manifest_urls'][0] # DRM省略のため受け取るURLは一つのみ
if asset_status == 'IngestError':
# 失敗時の処理
# 処理終了
# 後続の登録処理…
先ほどEventBridgeで設定した2つのイベントで分岐します。 また、紐づけテーブルに登録する動画IDはディレクトリ名に使用しているのでARNから抽出する形を取っています。 以降の処理はサービス固有のところなので割愛しますが、残りはRDSに接続して紐づけテーブルに登録する処理になります。
おわりに
本記事では、動画の納品からHLS変換までの作業を自動化する仕組みの一例を解説しました。 LambdaからMediaPackageのアセットを作成する方法やMediaPackageのEventBridgeルール設定の具体例については調べてもなかなかヒットしなかったので、こうして記事として残すことにしました。 同じように検索した方の参考になれば幸いです。 最後まで読んでいただき、ありがとうございました!
笹野