こんにちは、広野です。
MP4 録画した自分の PC の操作画面を、そのままではサイズが大きいのでアニメーション GIF にしたいと思いました。ネット上の無料サービスはあるのですが、セキュリティ的に心配があったので AWS Step Functions と AWS Elemental MediaConvert を使って変換ジョブをつくりました。
つくったもの
PC 操作の録画 MP4 を GIF に変換したものです。
画質は 640 x 360 px、10 fps にしているので粗いですが、パラメータ次第で綺麗にできると思います。その分サイズは大きくなりますが。
仕様
- MP4 ファイルを Amazon S3 バケットに配置したら、自動で AWS Step Functions ステートマシンが呼び出される。
- ステートマシンは、AWS Elemental MediaConvert を呼び出し、S3 上の MP4 ファイルを GIF に変換し S3 に保存する。
- 最後に完了したことを Amazon SNS で通知する。
アーキテクチャ
- Amazon S3 のイベント通知により、Amazon EventBridge ルールが発火します。
- Amazon EventBridge ルールは AWS Step Functions ステートマシンを呼び出します。
- ステートマシンの中の AWS Elemental MediaConvert CreateJob API を実行し、Amazon S3 バケットにある MP4 を GIF に変換します。AWS Lambda 関数は一切使用していません。
- 変換した GIF は Amazon S3 バケットに保存されます。
- CreateJob の結果を Amazon SNS を使用してユーザーに通知します。
Amazon SNS の通知は以下のように届きます。とりあえず完了したことがわかるだけの簡易な出来です。
ハマりポイント
この構成を作成しようと思ったときは楽勝ーと思っていたのですが意外とハマり、半日かかってしまいました。
今回、CreateJob のところで以下の「タスクが完了するまで待機」の設定を有効にしました。この設定はそのタスクの完了 (コールバック) を待ってくれる設定なのですが、IAM ロールは特殊な設定をしないといけないようです。通常、この設定を OFF にすると、タスクの実行命令後は次のタスクに移ってしまいます。(非同期)
「タスクが完了するまで待機」の設定は、裏で AWS 管理の EventBridge を使用するようです。そのため、それにアクセスするための IAM ロールが必要です。
今回、私は当初 MediaConvert に関する権限に関してはすべて AWS マネージドポリシーを使用して網羅していたつもりだったのですが、EventBridge に関するロールが抜け落ちていました。これになかなか気付けずハマりました。
AWS CloudFormation テンプレート
実際にデプロイしたときに使用した AWS CloudFormation テンプレートを貼り付けます。詳細な設定はこちらをご覧ください。
AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a Step Functions state machine. It provides converting MP4 to GIF. The IAM role MediaConvert_Default_Role must be created in your AWS account before creating a job. Please refer https://docs.aws.amazon.com/mediaconvert/latest/ug/creating-the-iam-role-in-mediaconvert-configured.html for details.
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
SystemName:
Type: String
Description: System name. use lower case only. (e.g. example)
Default: example
MaxLength: 10
MinLength: 1
SubName:
Type: String
Description: System sub name. use lower case only. (e.g. prod or dev)
Default: dev
MaxLength: 10
MinLength: 1
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "General Configuration"
Parameters:
- SystemName
- SubName
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${SystemName}-${SubName}-mp4-gif-conv
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: 14
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
NotificationConfiguration:
EventBridgeConfiguration:
EventBridgeEnabled: true
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
# ------------------------------------------------------------#
# Elemental MediaConvert
# ------------------------------------------------------------#
MediaConvertQueue:
Type: AWS::MediaConvert::Queue
Properties:
Description: !Sub For ${SystemName}-${SubName}
Name: !Sub ${SystemName}-${SubName}
PricingPlan: ON_DEMAND
Status: ACTIVE
Tags:
Cost: !Sub ${SystemName}-${SubName}
MediaConvertJobTemplate:
Type: AWS::MediaConvert::JobTemplate
Properties:
Name: !Sub ${SystemName}-${SubName}-mp4-animated-gif-640-360
Description: !Sub The transcoding configuration for ${SystemName}-${SubName} (MP4 to Animated GIF)
Category: !Ref SystemName
AccelerationSettings:
Mode: DISABLED
Priority: 0
Queue: !GetAtt MediaConvertQueue.Arn
SettingsJson:
Inputs:
- VideoSelector:
ColorSpace: FOLLOW
SampleRange: FOLLOW
Rotate: DEGREE_0
EmbeddedTimecodeOverride: NONE
AlphaBehavior: DISCARD
PadVideo: DISABLED
FilterEnable: AUTO
PsiControl: IGNORE_PSI
FilterStrength: 0
DeblockFilter: DISABLED
DenoiseFilter: DISABLED
InputScanType: AUTO
TimecodeSource: ZEROBASED
OutputGroups:
- CustomName: GIF_output
Name: File Group
Outputs:
- ContainerSettings:
Container: GIF
Extension: gif
NameModifier: _animated
VideoDescription:
Width: 640
Height: 360
ScalingBehavior: DEFAULT
Sharpness: 50
CodecSettings:
Codec: GIF
GifSettings:
FramerateControl: SPECIFIED
FramerateNumerator: 10
FramerateDenominator: 1
FramerateConversionAlgorithm: DUPLICATE_DROP
OutputGroupSettings:
Type: FILE_GROUP_SETTINGS
FileGroupSettings:
Destination: !Sub s3://${S3Bucket}/output/
DestinationSettings:
S3Settings:
StorageClass: STANDARD
TimecodeConfig:
Source: ZEROBASED
StatusUpdateInterval: SECONDS_60
Tags:
Cost: !Sub ${SystemName}-${SubName}
DependsOn:
- MediaConvertQueue
# ------------------------------------------------------------#
# Step Functions State Machine
# ------------------------------------------------------------#
StateMachineMp4GifConv:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: !Sub ${SystemName}-${SubName}-mp4-gif-conv
StateMachineType: STANDARD
DefinitionSubstitutions:
DsSystemName: !Ref SystemName
DsSubName: !Ref SubName
DSRegion: !Ref AWS::Region
DsAwsAccountId: !Ref AWS::AccountId
DsMediaConvertJobTemplateArn: !GetAtt MediaConvertJobTemplate.Arn
DsMediaConvertQueueArn: !GetAtt MediaConvertQueue.Arn
DsSnsTopicArn: !GetAtt SNSTopic.TopicArn
DefinitionString: |-
{
"StartAt": "CreateJob",
"States": {
"CreateJob": {
"Type": "Task",
"Resource": "arn:aws:states:::mediaconvert:createJob.sync",
"Arguments": {
"JobTemplate": "${DsMediaConvertJobTemplateArn}",
"Queue": "${DsMediaConvertQueueArn}",
"Role": "arn:aws:iam::${DsAwsAccountId}:role/service-role/MediaConvert_Default_Role",
"Settings": {
"Inputs": [
{
"FileInput": "{% 's3://' & $states.input.detail.bucket.name & '/' & $states.input.detail.object.key %}"
}
]
},
"Tags": {
"Cost": "${DsSystemName}-${DsSubName}"
}
},
"Next": "SnsPublish",
"QueryLanguage": "JSONata",
"TimeoutSeconds": 600
},
"SnsPublish": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"QueryLanguage": "JSONata",
"Arguments": {
"TopicArn": "${DsSnsTopicArn}",
"Message": {
"Input": "{% $states.input.Job.Settings.Inputs[0].FileInput %}",
"Status": "{% $states.input.Job.Status %}",
"Messages": "{% $states.input.Job.Messages %}"
}
},
"TimeoutSeconds": 30,
"End": true
}
},
"TimeoutSeconds": 660,
"Comment": "For converting a MP4 media to animated GIF"
}
LoggingConfiguration:
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt LogGroupStateMachineMp4GifConv.Arn
IncludeExecutionData: true
Level: ERROR
RoleArn: !GetAtt StateMachineMp4GifConvRole.Arn
TracingConfiguration:
Enabled: false
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
DependsOn:
- LogGroupStateMachineMp4GifConv
- StateMachineMp4GifConvRole
- MediaConvertJobTemplate
# ------------------------------------------------------------#
# Step Functions State Machine LogGroup (CloudWatch Logs)
# ------------------------------------------------------------#
LogGroupStateMachineMp4GifConv:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/vendedlogs/states/${SystemName}-${SubName}-mp4-gif-conv
RetentionInDays: 365
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
# ------------------------------------------------------------#
# Step Functions State Machine Execution Role (IAM)
# ------------------------------------------------------------#
StateMachineMp4GifConvRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${SubName}-StateMachineMp4GifConvRole
Description: This role allows State Machines to invoke specified AWS resources.
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
states.amazonaws.com
Action:
- sts:AssumeRole
Path: /service-role/
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
- arn:aws:iam::aws:policy/AWSElementalMediaConvertFullAccess
Policies:
- PolicyName: !Sub ${SystemName}-${SubName}-StateMachineMp4GifConvPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "s3:GetObject"
Resource:
- !Sub "${S3Bucket.Arn}/input/*"
- Effect: Allow
Action:
- "s3:PutObject"
Resource:
- !Sub "${S3Bucket.Arn}/output/*"
- Effect: Allow
Action:
- "events:PutTargets"
- "events:PutRule"
- "events:DescribeRule"
Resource:
- !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForMediaConvertJobRule"
- Effect: Allow
Action:
- "sns:Publish"
Resource:
- !GetAtt SNSTopic.TopicArn
DependsOn:
- S3Bucket
- SNSTopic
# ------------------------------------------------------------#
# EventBridge Rule for starting State Machine
# ------------------------------------------------------------#
EventBridgeRuleStartSfn:
Type: AWS::Events::Rule
Properties:
Name: !Sub ${SystemName}-${SubName}-mp4-gif-conv-start-sfn
Description: !Sub This rule starts mp4 gif converter Sfn for ${SystemName}-${SubName}. The trigger is the S3 event notifications.
EventBusName: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"
EventPattern:
source:
- "aws.s3"
detail-type:
- "Object Created"
detail:
bucket:
name:
- !Ref S3Bucket
object:
key:
- wildcard: "input/*.mp4"
State: ENABLED
Targets:
- Arn: !GetAtt StateMachineMp4GifConv.Arn
Id: !Sub ${SystemName}-${SubName}-mp4-gif-conv-start-sfn
RoleArn: !GetAtt EventBridgeRuleSfnRole.Arn
DependsOn:
- EventBridgeRuleSfnRole
# ------------------------------------------------------------#
# EventBridge Rule Invoke Step Functions State Machine Role (IAM)
# ------------------------------------------------------------#
EventBridgeRuleSfnRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${SubName}-EventBridgeSfnRole
Description: !Sub This role allows EventBridge to invoke mp4 gif converter Sfn for ${SystemName}-${SubName}.
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub ${SystemName}-${SubName}-EventBridgeSfnPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- "states:StartExecution"
Resource:
- !GetAtt StateMachineMp4GifConv.Arn
DependsOn:
- StateMachineMp4GifConv
# ------------------------------------------------------------#
# SNS Topic
# ------------------------------------------------------------#
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TracingConfig: PassThrough
DisplayName: !Sub ${SystemName}-${SubName}-mp4-gif-conv
FifoTopic: false
Tags:
- Key: Cost
Value: !Sub ${SystemName}-${SubName}
AWS Elemental MediaConvert は、前提として MediaConvert に割り当てる IAM ロールが必要になります。以下のドキュメント参考に作成が必要ですが、今回は雑に広い権限を作成をしています。以前は権限決め打ちで、かつ AWS アカウント共通で持たないといけなかった記憶がありますが、今は細かく定義できるようになっています。
AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a MediaConvert_Default_Role in your AWS account. It is needed when you create MediaConvert jobs.
Resources:
# ------------------------------------------------------------#
# Elemental MediaConvert Default Role (IAM)
# ------------------------------------------------------------#
MediaConvertDefaultRole:
Type: AWS::IAM::Role
Properties:
RoleName: MediaConvert_Default_Role
Description: This role allows MediaConvert to execute jobs.
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- mediaconvert.amazonaws.com
Action:
- sts:AssumeRole
Path: /service-role/
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonS3FullAccess
- arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess
まとめ
いかがでしたでしょうか?
インターネット上では多くの方が似たようなハマりをしていますが、AWS Elemental MediaConvert に関する情報は少なかったので今回の件を私の方で書かせていただきました。
本記事が皆様のお役に立てれば幸いです。




