LoginSignup
4
2

More than 3 years have passed since last update.

MediaLiveで出力したアーカイブファイルをMP4に自動変換する

Last updated at Posted at 2020-01-24

はじめに

MediaLiveでライブ配信を行う際、アーカイブを残しておくことがあるかと思います。
2020年1月現在では、MediaLiveのアーカイブ出力はtsファイル形式のみ対応しています。

tsファイルはVLCなどの動画プレイヤーを使わないと再生することができないため、
利用しやすいmp4ファイルに変換する必要があるという機会に何度か遭遇しました。

そのときに構築した自動でmp4に変換する仕組みを簡単に記載しておきます。

構成図

MediaLive -> S3 -> Lambda -> MediaConvert -> S3
Qiita用の構成図.png

MediaLive

Output Groupで「Archive」を選択します。

S3

tsファイルを置くフォルダと
mp4ファイルを置くフォルダを用意します。

IAM

IAMロールを2つ作成します。

MediaConvert用 IAMロール

Policyはロール作成時に自動でアタッチされます。

  • AmazonS3FullAccess
  • AmazonAPIGatewayInvokeFullAccess

Lambda用 IAMロール

AWSデフォルトのPolicyを使用する場合はこのPolicyがあれば動作します。
(本番環境で構築する際は権限を絞りましょう)

  • AWSElementalMediaConvertFullAccess

MediaConvert

mp4に変換させるジョブテンプレートを作成します。
(参考までに、JSONを貼っておきます)

{
  "Queue": "arn:aws:mediaconvert:ap-northeast-1:<account-id>:queues/<キューの名前>",
  "Name": "<ジョブテンプレートの名前>",
  "Settings": {
    "OutputGroups": [
      {
        "Name": "File Group",
        "Outputs": [
          {
            "ContainerSettings": {
              "Container": "MP4",
              "Mp4Settings": {
                "CslgAtom": "INCLUDE",
                "CttsVersion": 0,
                "FreeSpaceBox": "EXCLUDE",
                "MoovPlacement": "PROGRESSIVE_DOWNLOAD"
              }
            },
            "VideoDescription": {
              "Width": 1280,
              "ScalingBehavior": "DEFAULT",
              "Height": 720,
              "TimecodeInsertion": "DISABLED",
              "AntiAlias": "ENABLED",
              "Sharpness": 50,
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "InterlaceMode": "PROGRESSIVE",
                  "NumberReferenceFrames": 3,
                  "Syntax": "DEFAULT",
                  "Softness": 0,
                  "GopClosedCadence": 1,
                  "GopSize": 90,
                  "Slices": 1,
                  "GopBReference": "DISABLED",
                  "SlowPal": "DISABLED",
                  "SpatialAdaptiveQuantization": "ENABLED",
                  "TemporalAdaptiveQuantization": "ENABLED",
                  "FlickerAdaptiveQuantization": "DISABLED",
                  "EntropyEncoding": "CABAC",
                  "Bitrate": 1000000,
                  "FramerateControl": "INITIALIZE_FROM_SOURCE",
                  "RateControlMode": "CBR",
                  "CodecProfile": "MAIN",
                  "Telecine": "NONE",
                  "MinIInterval": 0,
                  "AdaptiveQuantization": "HIGH",
                  "CodecLevel": "AUTO",
                  "FieldEncoding": "PAFF",
                  "SceneChangeDetect": "ENABLED",
                  "QualityTuningLevel": "SINGLE_PASS",
                  "FramerateConversionAlgorithm": "DUPLICATE_DROP",
                  "UnregisteredSeiTimecode": "DISABLED",
                  "GopSizeUnits": "FRAMES",
                  "ParControl": "INITIALIZE_FROM_SOURCE",
                  "NumberBFramesBetweenReferenceFrames": 2,
                  "RepeatPps": "DISABLED",
                  "DynamicSubGop": "STATIC"
                }
              },
              "AfdSignaling": "NONE",
              "DropFrameTimecode": "ENABLED",
              "RespondToAfd": "NONE",
              "ColorMetadata": "INSERT"
            },
            "AudioDescriptions": [
              {
                "AudioTypeControl": "FOLLOW_INPUT",
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "AudioDescriptionBroadcasterMix": "NORMAL",
                    "Bitrate": 96000,
                    "RateControlMode": "CBR",
                    "CodecProfile": "LC",
                    "CodingMode": "CODING_MODE_2_0",
                    "RawFormat": "NONE",
                    "SampleRate": 48000,
                    "Specification": "MPEG4"
                  }
                },
                "LanguageCodeControl": "FOLLOW_INPUT"
              }
            ],
            "Extension": ".mp4"
          }
        ],
        "OutputGroupSettings": {
          "Type": "FILE_GROUP_SETTINGS",
          "FileGroupSettings": {
            "Destination": "s3://<任意のS3バケット>/"
          }
        }
      }
    ],
    "AdAvailOffset": 0,
    "Inputs": [
      {
        "AudioSelectors": {
          "Audio Selector 1": {
            "Offset": 0,
            "DefaultSelection": "DEFAULT",
            "ProgramSelection": 1
          }
        },
        "VideoSelector": {
          "ColorSpace": "FOLLOW",
          "Rotate": "DEGREE_0",
          "AlphaBehavior": "DISCARD"
        },
        "FilterEnable": "AUTO",
        "PsiControl": "USE_PSI",
        "FilterStrength": 0,
        "DeblockFilter": "DISABLED",
        "DenoiseFilter": "DISABLED",
        "TimecodeSource": "EMBEDDED"
      }
    ]
  },
  "AccelerationSettings": {
    "Mode": "DISABLED"
  },
  "StatusUpdateInterval": "SECONDS_60",
  "Priority": 0
}

Lambda

S3にtsファイルが作成されたら発火するLambda関数を作成します。

S3トリガー

  • イベントタイプ:ObjectCreated
  • サフィックス:.ts
  • プレフィックス:<任意のS3バケット>

関数

  • ランタイム:Python 3.8
lambda_function.py
from __future__ import print_function

import json
import urllib.parse
import boto3
import os

print('Loading function')
mediaconvert =  boto3.client('mediaconvert', region_name='ap-northeast-1', endpoint_url='https://xxxxxxxx.mediaconvert.ap-northeast-1.amazonaws.com')
s3 = boto3.client('s3')

def lambda_handler(event, context):
    # Get the object from the event and show its content type
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    inputFile = "s3://" + bucket + "/" + key
    outputKey = "s3://<任意のS3バケット>/"

    try:
        # Load job.json from disk and store as Python object: job_object
        with open("job.json", "r") as jsonfile:
            job_object = json.load(jsonfile)

        # Input/Output Setting
        job_object["Inputs"][0]["FileInput"] = inputFile
        job_object["OutputGroups"][0]["OutputGroupSettings"]["FileGroupSettings"]["Destination"] = outputKey

        # Exec MediaConvert's job
        response = mediaconvert.create_job(
          JobTemplate='arn:aws:mediaconvert:ap-northeast-1:<account-id>:jobTemplates/<ジョブテンプレートの名前>',
          Queue='arn:aws:mediaconvert:ap-northeast-1:<account-id>:queues/<キューの名前>',
          Role='arn:aws:iam::<account-id>:role/<ロールの名前>',
          Settings=job_object
        )
    except Exception as e:
        print(e)
        raise e
job.json
{
  "OutputGroups": [
    {
      "Name": "File Group",
      "OutputGroupSettings": {
        "Type": "FILE_GROUP_SETTINGS",
        "FileGroupSettings": {
          "Destination": ""
          }
        }
      }
  ],
    "Inputs": [
      {
        "FileInput": ""
      }
    ]
}
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2