MP4 をアニメーション GIF に自動変換する AWS Step Functions ジョブをつくる

こんにちは、広野です。

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 に関する情報は少なかったので今回の件を私の方で書かせていただきました。

本記事が皆様のお役に立てれば幸いです。

著者について
広野 祐司

AWS サーバーレスアーキテクチャと React を使用して社内向け e-Learning アプリ開発とコンテンツ作成に勤しんでいます。React でアプリを書き始めたら、快適すぎて他の言語には戻れなくなりました。近年は社内外への AWS 技術支援にも従事しています。AWS サービスには AWS が考える IT 設計思想が詰め込まれているので、いつも AWS を通して勉強させて頂いてまます。
取得資格:AWS 認定は15資格、IT サービスマネージャ、ITIL v3 Expert 等
2020 - 2025 Japan AWS Top Engineer 受賞
2022 - 2025 AWS Ambassador 受賞
2023 当社初代フルスタックエンジニア認定
好きなAWSサービス:AWS AppSync Events / AWS Step Functions / AWS CloudFormation

広野 祐司をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSクラウド
シェアする
タイトルとURLをコピーしました