動画ファイルのサイズを圧縮し、ストリーミング速度を向上させ、データ転送のコストを削減する方法として、Amazon Elastic TranscoderまたはAWS Elemental MediaConvertサービスを利用することがあります。私はこの課題に対する解決策としてAWS Elemental MediaConvertを選択しました。なぜなら、これはTranscoderよりも費用が抑えられ、かつ高度なセキュリティ機能(DRM)が組み込まれているからです。今回はスムーズな動画変換ガイドを紹介したいと思います。
[AWS Elemental MediaConvert]って何?
AWS Elemental MediaConvertでは、設定した構成で動画をストリーム配信することができます。変換ジョブは、動画ファイルがS3バケットにアップロードされるたびに自動的に作成されます。ビデオ変換の完了およびエラーのステータスに関する通知も受信できます。 コスト削減に関しては、ファイルを他の形式に変換するための非常にコスト効果の高い選択肢です。 AWS Elemental MediaConvertは、ファイルベースの動画変換サービスであり、放送レベルの機能が備わっています。これにより、オンデマンド(VOD)の動画コンテンツを容易に作成し、大規模な多画面配信が可能となります。このサービスは、高度なビデオおよびオーディオ機能をシンプルなウェブサービスインターフェースと、使用量に応じた課金で組み合わせています。 AWS Elemental MediaConvertを使用することで、自身のビデオ処理インフラストラクチャの構築と運用の複雑さを気にせず、魅力的なメディアエクスペリエンスを提供することに焦点を当てることができます。詳細情報については、AWS Elemental MediaConvertのドキュメントを参照してください。 この記事では、MediaConvertとLambdaを利用して動画ファイルをMP4形式に変換し、完了またはエラーのステータスに関する通知を電子メールで受け取る方法を共有します。開発フレームワークはAWS Amplifyです。
AWSアーキテクチャ概要
動画変換の処理流れ
- 管理者が動画ファイルをS3フォルダー/video/にアップロードします。
- Event Bridgeは管理して/video/フォルダーに新しいファイルがアップロードされると、イベントがトリガーとなり、動画情報がLambda関数に送信されます。
- Lambda側で動画情報を受け取った後で動画変換の初期設定を行って、Lambda Layer FFProbeを使用してビデオファイルのサイズ情報を取得した上でMediaConvertでジョブの作成を起動します。
- MediaConvertはジョブ情報を基にして動画変換処理を実行します。処理が成功した場合、結果はS3フォルダー/video-convert/にアップロードされます。
- Event Bridgeは「Completed」または「Error」のイベントを監視し、イベントが発生するとAWS SNSサービスを介してユーザーに通知のメールを送信します。
- ユーザーはCloudFront経由でS3フォルダー/video-convert/内の変換した動画ファイルを確認できます。
AWS開発開始
コマンド実行:amplify init 名前: env AWSプロフィールを入力する 他のパラメーターはデフォルトにしても結構です。 コマンド実行:amplify add function 初期作成:Lambda 関数(serverless function) 機能名:videoConvertを入力する 開発言語:nodeJS Do you want to configure advanced settings > No
mediaconvertのため、下記の2URLが必要です。
- Url MediaConvert endpoint(Endpoint)
- Default queue(ARN)
MediaConvertのURLを取得方法 AWS Console→リージョンを選択→MediaConvert→メニュー→アカウントを選択すると下記のイメージ画像が表示される。 Endpoint:https://xxxxxxxx.mediaconvert.ap-southeast-1.amazonaws.com Default queueのURLを取得方法 AWS Console→リージョンを選択→MediaConvert→Queue→Defaultを押下すると下記の画面が表示されます。 ARN: arn:aws:mediaconvert:ap-southeast-1:573580132305:queues/Default
S3 バケット内に動画の保存場所を作成
AWS Console→リージョンを選択→S3→Create bucket→Bucket name 名前:onetech-demo-mediaconvert S3バケットへのアクセス権限とオブジェクトへのアクセス権限の設定を行います。 Bucket→onetech-demo-mediaconvert→Permissions→Bock public access→Edit→Uncheck Block all public access Bucket → onetech-demo-mediaconvert → Permissions → Object Ownership → Edit Event BridgeはS3バケットへのアクセス許可設定 onetech-demo-mediaconvertバケットをクリックする。 下記の画面に沿って設定を行う。 Properties → Event notifications → Amazon EventBridge → Edit → Turn ON → Save Changes
node JSを使って開発プログラム開始
node JSライブラリをインストールする。
"devDependencies": {
"aws-sdk": "^2.1416.0",
"fluent-ffmpeg": "^2.1.2",
"fs": "^0.0.1-security",
"path": "^0.12.7"
}
cd amplify/backend/function/videoConvert/src npm install -D aws-sdk fluent-ffmpeg fs path ↓ Develop node_modules ※モック作成のため、サーバへアップロードすると情報が消えてします。 インストールが完了したらVisual studio codeでrootフォルダを開く 新しいファイルを作成:config/resize.json
下記のデータを入力する
{
"Queue": "xxxxxx",
"UserMetadata": {
"input": "s3://xxxxxx/videos/Video400mb.mp4",
"environment": "dev"
},
"Role": "xxxxxxxx",
"Settings": {
"TimecodeConfig": {
"Source": "ZEROBASED"
},
"OutputGroups": [
{
"CustomName": "MP4",
"Name": "File Group",
"Outputs": [
{
"ContainerSettings": {
"Container": "MP4",
"Mp4Settings": {}
},
"VideoDescription": {
"Width": 3840,
"Height": 2160,
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"GopClosedCadence": 1,
"GopSize": 90,
"GopBReference": "DISABLED",
"MaxBitrate": 3000000,
"SpatialAdaptiveQuantization": "ENABLED",
"TemporalAdaptiveQuantization": "ENABLED",
"FlickerAdaptiveQuantization": "DISABLED",
"RateControlMode": "QVBR",
"QvbrSettings": {
"QvbrQualityLevel": 7
},
"CodecProfile": "MAIN",
"MinIInterval": 0,
"AdaptiveQuantization": "HIGH",
"SceneChangeDetect": "DISABLED",
"QualityTuningLevel": "SINGLE_PASS",
"GopSizeUnits": "FRAMES",
"NumberBFramesBetweenReferenceFrames": 2
}
}
},
"AudioDescriptions": [
{
"CodecSettings": {
"Codec": "AAC",
"AacSettings": {
"Bitrate": 96000,
"CodingMode": "CODING_MODE_2_0",
"SampleRate": 48000
}
}
}
],
"NameModifier": "Resize"
}
],
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "s3://xxxxxx/convert-video/",
"DestinationSettings": {
"S3Settings": {
"AccessControl": {
"CannedAcl": "PUBLIC_READ"
}
}
}
}
}
}
],
"Inputs": [
{
"AudioSelectors": {
"Audio Selector 1": {
"DefaultSelection": "DEFAULT"
}
},
"VideoSelector": {
"Rotate": "AUTO"
},
"TimecodeSource": "ZEROBASED",
"FileInput": "s3://xxxxxx/videos/IMG_0961.MOV"
}
]
},
"AccelerationSettings": {
"Mode": "DISABLED"
},
"StatusUpdateInterval": "SECONDS_60",
"Priority": 0,
"Tags": {
"MediaConvert": "DemoMediaConvert"
}
}
index.jsファイルを開いて動画設定などを行います。 今回は下記の動画サイズを固定にします。 1920×1080、1080×1920 要求により変更設定をおこなってください。 黄色ところは上記の値を修正する。
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.REGION,
});
const S3 = new AWS.S3({
signatureVersion: 'v4'
});
//Chỗ này cần update lại endpoint mediaconvert
const MediaConvert = new AWS.MediaConvert({
apiVersion: '2017-08-29',
endpoint: 'https://xxxxx.mediaconvert.ap-southeast-1.amazonaws.com',
});
//Read file from local
const fs = require('fs');
//Get info video
const ffmpeg = require('fluent-ffmpeg');
exports.handler = async (event) => {
try {
if(!event.detail) {
return false;
}
//Chỗ này cần update lại s3 bucket name
const bucket = 'onetech-demo-mediaconvert';
let convertWidth = 1920;
let convertHeight = 1080;
const objectKey = event.detail.object.key;
const regex = /\/[^/]*$/;
let convertFolder = objectKey.replace(regex, '/');
convertFolder = convertFolder.replace('video/', 'convert-video/');
console.log('convertFolder:::', convertFolder);
console.log('objectKey:::', objectKey);
//Check video vertical or horizontal
try {
const s3Params = {
Bucket: bucket,
Key: objectKey
};
const s3Stream = S3.getObject(s3Params).createReadStream();
const info = await new Promise((resolve, reject) => {
ffmpeg(s3Stream).ffprobe((err, info) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(info);
});
});
console.log('Video Information:', info);
for (const stream of info.streams) {
if (!stream.width) {
continue;
}
console.log('Video INFO Steam', stream);
if(+stream.width < +stream.height) {
convertWidth = 1080;
convertHeight = 1920;
}
if(stream.rotation && stream.rotation === '-90') {
convertWidth = 1080;
convertHeight = 1920;
}
break;
}
} catch (error) {
console.error(error);
}
console.log('convertWidth', convertWidth);
console.log('convertHeight', convertHeight);
//End check video vertical or horizontal
const jsonData = fs.readFileSync('./config/resize.json', 'utf8');
let convertData = JSON.parse(jsonData);
//Update queue
convertData.Queue = 'arn:aws:mediaconvert:ap-southeast-1:111111111111:queues/Default';
//Update role
convertData.Role = process.env.MEDIA_CONVERT_ROLE;
//Update input file
const inputSource = `s3://${bucket}/${objectKey}`;
const outSource = `s3://${bucket}/${convertFolder}`;
convertData.Settings.Inputs[0].FileInput = inputSource;
//Update output folder
convertData.Settings.OutputGroups[0].OutputGroupSettings.FileGroupSettings.Destination = outSource;
//Update video width and height
convertData.Settings.OutputGroups[0].Outputs[0].VideoDescription.Width = convertWidth;
convertData.Settings.OutputGroups[0].Outputs[0].VideoDescription.Height = convertHeight;
//Update UserMetadata
convertData.UserMetadata.input = objectKey;
convertData.UserMetadata.environment = process.env.ENV;
convertData.UserMetadata.output = convertFolder;
convertData.UserMetadata.s3_bucket = bucket;
console.log('JSON MEDIACONVERT DATA', convertData);
const response = await MediaConvert.createJob(convertData).promise();
console.log('MediaConvert Job created:', response.Job);
return true;
} catch (error) {
console.error(error);
return false;
}
};
プログラムが一旦完了します。これからインフラ構成を基づいてcloudformationを設定する
編集ファイル: amplify/backend/function/videoConvert/videoConvert-cloudformation-template.json
説明:
LambdaFunction: Lambda function ビルド
DemoVideoConvertEventBridgeS3TriggerRule: Event bridge trigger をビルドしてファイルが指定したフォルダへアップロードできるか
MyLambdaPermission: Event bridgeからイベントを受け取り
DemoVideoConvertEventBridgeProcessRule:MediaConvert処理が成功または失敗した場合にEvent Bridgeトリガーを構築するために使用されます
DemoVideoConvertSNSProcessTopic: Event Bridgeからmediaconvert処理が成功か失敗か通知メールする
DemoVideoConvertSNSProcessTopicPolicy: Event BridgeからのイベントをSNSが受け取るように許可します
MyEmailSubscription:成功または失敗したMediaConvertの通知を受信するためのメール設定をします。
MediaConvertExecutionRole: role Lambda function
MediaConvertExecutionPolicy: Lambda functionで実行権限ポリシー
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Lambda Function resource stack creation using Amplify CLI",
"Parameters": {
"CloudWatchRule": {
"Type": "String",
"Default" : "NONE",
"Description" : " Schedule Expression"
},
"deploymentBucketName": {
"Type": "String"
},
"env": {
"Type": "String"
},
"s3Key": {
"Type": "String"
}
},
"Conditions": {
"ShouldNotCreateEnvResources": {
"Fn::Equals": [
{
"Ref": "env"
},
"NONE"
]
}
},
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {
"aws:asset:path": "./src",
"aws:asset:property": "Code"
},
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "deploymentBucketName"
},
"S3Key": {
"Ref": "s3Key"
}
},
"Handler": "index.handler",
"FunctionName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"demoVideoConvert",
{
"Fn::Join": [
"",
[
"demoVideoConvert",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Environment": {
"Variables": {
"ENV": {
"Ref": "env"
},
"REGION": {
"Ref": "AWS::Region"
},
"MEDIA_CONVERT_ROLE": {
"Fn::GetAtt": [
"MediaConvertExecutionRole",
"Arn"
]
}
}
},
"Role": {
"Fn::GetAtt": [
"LambdaExecutionRole",
"Arn"
]
},
"Runtime": "nodejs18.x",
"Layers": [
],
"MemorySize": 2048,
"EphemeralStorage": {
"Size": 2048
},
"Timeout": 300
}
},
"DemoVideoConvertEventBridgeS3TriggerRule": {
"DependsOn": [
"LambdaFunction"
],
"Type": "AWS::Events::Rule",
"Properties": {
"Name": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"DemoVideoConvertEventBridgeS3TriggerRule",
{
"Fn::Join": [
"",
[
"DemoVideoConvertEventBridgeS3TriggerRule",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Description": "Trigger Lambda from S3 events",
"EventPattern": {
"source": [
"aws.s3"
],
"detail-type": [
"Object Created"
],
"detail": {
"bucket": {
"name": [
"onetech-demo-mediaconvert"
]
},
"object": {
"key": [
{
"prefix": "video/"
}
]
}
}
},
"Targets": [
{
"Arn": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Id": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppEventBridgeRuleTargetId",
{
"Fn::Join": [
"",
[
"VideoAppEventBridgeRuleTargetId",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
]
}
},
"MyLambdaPermission": {
"DependsOn": [
"DemoVideoConvertEventBridgeS3TriggerRule"
],
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Principal": "events.amazonaws.com",
"SourceArn": {
"Fn::GetAtt": [
"DemoVideoConvertEventBridgeS3TriggerRule",
"Arn"
]
}
}
},
"DemoVideoConvertEventBridgeProcessRule": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::Events::Rule",
"Properties": {
"Name": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"DemoVideoConvertEventBridgeProcessRule",
{
"Fn::Join": [
"",
[
"DemoVideoConvertEventBridgeProcessRule",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Description": "Trigger mediaconvert process",
"EventPattern": {
"source": [
"aws.mediaconvert"
],
"detail": {
"status": [
"COMPLETE",
"ERROR"
],
"userMetadata": {
"environment": [
{
"Ref": "env"
}
]
}
}
},
"Targets": [
{
"Arn": {
"Ref": "DemoVideoConvertSNSProcessTopic"
},
"Id": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppEventBridgeMediaconvertProcessRule",
{
"Fn::Join": [
"",
[
"VideoAppEventBridgeMediaconvertProcessRule",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
]
}
},
"DemoVideoConvertSNSProcessTopic": {
"DependsOn": [],
"Type": "AWS::SNS::Topic",
"Properties": {
"DisplayName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppSNSMediaconvertProcessTopic",
{
"Fn::Join": [
"",
[
"VideoAppSNSMediaconvertProcessTopic",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
},
"DemoVideoConvertSNSProcessTopicPolicy": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::SNS::TopicPolicy",
"Properties": {
"Topics": [
{
"Ref": "DemoVideoConvertSNSProcessTopic"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEventBridgePublish",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sns:Publish",
"Resource": {
"Ref": "DemoVideoConvertSNSProcessTopic"
}
}
]
}
}
},
"MyEmailSubscription": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::SNS::Subscription",
"Properties": {
"Protocol": "email",
"Endpoint": "duy@onetech.vn",
"TopicArn": {
"Ref": "DemoVideoConvertSNSProcessTopic"
}
}
},
"MediaConvertExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"videoAppMediaConvertRole",
{
"Fn::Join": [
"",
[
"videoAppMediaConvertRole",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"mediaconvert.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
}
}
},
"MediaConvertExecutionPolicy": {
"DependsOn": [
"MediaConvertExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "media-convert-execution-policy",
"Roles": [
{
"Ref": "MediaConvertExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"s3-object-lambda:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke",
"execute-api:ManageConnections"
],
"Resource": "arn:aws:execute-api:*:*:*"
}
]
}
}
},
"LambdaExecutionRole": {
"DependsOn": [
"MediaConvertExecutionRole",
"MediaConvertExecutionPolicy"
],
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"videoappconvertLambdaRole30cacecd",
{
"Fn::Join": [
"",
[
"videoappconvertLambdaRole30cacecd",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
}
}
},
"LambdaExecutionPolicy": {
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "lambda-execution-policy",
"Roles": [
{
"Ref": "LambdaExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
{
"Fn::Sub": [
"arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
{
"region": {
"Ref": "AWS::Region"
},
"account": {
"Ref": "AWS::AccountId"
},
"lambda": {
"Ref": "LambdaFunction"
}
}
]
}
]
},
{
"Action": [
"iam:PassRole"
],
"Resource": [
{
"Fn::GetAtt": [
"MediaConvertExecutionRole",
"Arn"
]
}
],
"Effect": "Allow",
"Sid": "PassRole"
},
{
"Action": [
"mediaconvert:*"
],
"Resource": [
"*"
],
"Effect": "Allow",
"Sid": "MediaConvertService"
},
{
"Sid": "AllowS3Access",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "*"
}
]
}
}
}
},
"Outputs": {
"Name": {
"Value": {
"Ref": "LambdaFunction"
}
},
"Arn": {
"Value": {"Fn::GetAtt": ["LambdaFunction", "Arn"]}
},
"Region": {
"Value": {
"Ref": "AWS::Region"
}
},
"LambdaExecutionRole": {
"Value": {
"Ref": "LambdaExecutionRole"
}
}
}
}
ffmpegライブラリを実行するためのLambdaレイヤーをビルドします
コマンド実行:amplify add function ↓ Lambda layerを選択 ↓ 名前:videoConvertLayer ↓ Node JS ↓ Enter ↓ https://github.com/phuocduy1988/ffmpeg からffmpegライブラリをダウンロードする。 ↓ bin/ へコピー ↓ amplify/backend/function/lambdamediaconvertvideoConvertLayer/opt/ ↓ Node JSライブラリをインストールする “devDependencies”: {
“aws-sdk”: “^2.1416.0”,
“fluent-ffmpeg”: “^2.1.2”,
“fs”: “^0.0.1-security”,
“path”: “^0.12.7”
} cd amplify/backend/function/lambdamediaconvertvideoConvertLayer/lib/nodejs npm install aws-sdk fluent-ffmpeg fs path このライブラリをインストールする目的は、Lambda関数のコードが共通ライブラリとして使用できるようにすることです。
Lambda MediaConvertにLambdaレイヤーを追加します。
コマンド実行:amplify update function Lambda function
videoConvert → Lambda layers configuration → Yes スペースを押下 lambdamediaconvertvideoConvertLayerを選択
Amplify MediaConvertのソースをAWS Cloudにデプロイします。
コマンド実行:amplify push → YES
MediaConvertの機能テスト
AWS Console → リージョン → S3 → onetech-demo-mediaconvert → videoフォルダ作成 → 動画をアップロード
Cloudwatch logを確認
CloudWatch → Log groups → /aws/lambda/demoVideoConvert-dev convert-videoフォルダで確認 元々動画ファイルは128MBから9MBになりました。
この検証のために作成したAWSリソースを削除します。
AWS Console →リージョン→ AWS Amplify → lambdamediaconvert → Delete appを選択
S3 バケット内に削除する
Amazon S3 → Buckets → onetech-demo-mediaconvert → Empty bucket
まとめ
AWS Elemental MediaConvertを使用して動画をMP4形式に変換してAWSの開発者にとって欠かせないスキルです。 最新技術を使いこなして、ユーザーにとって魅力的な動画配信体験を提供しましょう。 この記事が、効果的な動画変換ソリューションをお探しの皆様にとって有益であることを願っています。
関連ページ
「自社独自の動画配信システムを作成したい」と考えている方も多いでしょう。しかし、具体的な構築方法や手順がわからず動き出せない人も多いはずです。そこでこのコラムでは、動画配信システムの作り方をわかりやすく解説します。システム構築の選択肢と各種メリット・デメリット、そして構築の流れもまとめているので、ぜひ参考にしてみてください。