Category Archives: AWS

AWS S3

Bucket Policy Example:

{
    "Version": "2012-10-17",
    "Id": "Policy1618732609027",
    "Statement": [
        {
            "Sid": "Stmt1618732601748",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::xxxx-upload-test/*"
        }
    ]
}

注意:

  • Principal一定要是*, 測試結果就算是用root IAM也不能上傳
  • Action一定要有 s3:PutObjectAcl

https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html

設定Bucket的CORS Policy

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://www.example1.com"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "http://www.example2.com"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

AWS IAM

ARN 的區域部分是空白的,因為 IAM 資源是全域。

Examples:

arn:aws:iam::account-id:root  
arn:aws:iam::account-id:user/user-name-with-path
arn:aws:iam::account-id:group/group-name-with-path
arn:aws:iam::account-id:role/role-name-with-path
arn:aws:iam::account-id:policy/policy-name-with-path
arn:aws:iam::account-id:instance-profile/instance-profile-name-with-path
arn:aws:sts::account-id:federated-user/user-name
arn:aws:sts::account-id:assumed-role/role-name/role-session-name
arn:aws:iam::account-id:mfa/virtual-device-name-with-path
arn:aws:iam::account-id:u2f/u2f-token-id
arn:aws:iam::account-id:server-certificate/certificate-name-with-path
arn:aws:iam::account-id:saml-provider/provider-name
arn:aws:iam::account-id:oidc-provider/provider-name

https://docs.aws.amazon.com/zh_tw/IAM/latest/UserGuide/reference_identifiers.html

AWS Lambda

https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html

Python use Boto3 package in Lambda:

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/mediaconvert.html#MediaConvert.Client.create_job

#!/usr/bin/env python

import glob
import json
import os
import uuid
import boto3
import datetime
import random

from botocore.client import ClientError

def handler(event, context):

    assetID = str(uuid.uuid4())
    sourceS3Bucket = event['Records'][0]['s3']['bucket']['name']
    sourceS3Key = event['Records'][0]['s3']['object']['key']
    sourceS3 = 's3://'+ sourceS3Bucket + sourceS3Key
    # os.path.basename(sourceS3) -> test.mp4
    # splitext() -> test
    sourceS3Basename = os.path.splitext(os.path.basename(sourceS3))[0]
    
    destConfig = event['Records'][0]['s3']['dest'];
    destURI = destConfig['uri']
    
    # s3://celeb-videos
    destinationS3 = 's3://' + destConfig['bucket']
    # celeb-videos
    destinationS3basename = os.path.splitext(os.path.basename(destinationS3))[0]
    mediaConvertRole = os.environ['MediaConvertRole']
    region = os.environ['AWS_DEFAULT_REGION']
    statusCode = 200
    body = {}
    
    
    
    # Use MediaConvert SDK UserMetadata to tag jobs with the assetID 
    # Events from MediaConvert will have the assetID in UserMedata
    jobMetadata = {'assetID': assetID, 'videoId': event['Records'][0]['videoId']}

    # print (json.dumps(event))
    # print (destinationS3basename)
    # return;
    
    try:
        # Job settings are in the lambda zip file in the current working directory
        with open('job.json') as json_data:
            jobSettings = json.load(json_data)
            # print(jobSettings)

        # get the account-specific mediaconvert endpoint for this region
        mc_client = boto3.client('mediaconvert', region_name=region)
        endpoints = mc_client.describe_endpoints()

        # add the account-specific endpoint to the client session 
        client = boto3.client('mediaconvert', region_name=region, endpoint_url=endpoints['Endpoints'][0]['Url'], verify=False)

        # Update the job settings with the source video from the S3 event and destination 
        # paths for converted videos
        jobSettings['Inputs'][0]['FileInput'] = sourceS3
        
        # Watermark destURI
        # S3KeyWatermark = 'assets/' + assetID + '/mp4/' + sourceS3Basename
        S3KeyWatermark = destURI + '/mp4/' + sourceS3Basename
        jobSettings['OutputGroups'][0]['OutputGroupSettings']['FileGroupSettings']['Destination'] \
            = destinationS3 + S3KeyWatermark
            
        # Thumbs
        # S3KeyThumbnails = 'assets/' + assetID + '/thumbs/' + sourceS3Basename
        S3KeyThumbnails = destURI + '/thumbs/' + sourceS3Basename
        jobSettings['OutputGroups'][1]['OutputGroupSettings']['FileGroupSettings']['Destination'] \
            = destinationS3 + S3KeyThumbnails
        
        # HLS Streaming
        # S3KeyHLS = 'assets/' + assetID + '/hls/' + sourceS3Basename
        S3KeyHLS = destURI + '/hls/' + sourceS3Basename
        jobSettings['OutputGroups'][2]['OutputGroupSettings']['HlsGroupSettings']['Destination'] \
            = destinationS3 + S3KeyHLS
         

        # print ('jobSettings:')
        # print (json.dumps(jobSettings))
        # return 'ok'

        # Convert the video using AWS Elemental MediaConvert
        job = client.create_job(Role=mediaConvertRole, UserMetadata=jobMetadata, Settings=jobSettings)
        print (json.dumps(job, default=str))
        print ('job:')
        print (job['Job'])
        
        body['job_id'] = job['Job']['Id']

    except Exception as e:
        print ('Exception: %s' % e)
        statusCode = 500
        raise

    finally:
        return {
            'statusCode': statusCode,
            'body': json.dumps(body),
            'headers': {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'}
        }

How to get job ID

# Convert the video using AWS Elemental MediaConvert
job = client.create_job(Role=mediaConvertRole, UserMetadata=jobMetadata, Settings=jobSettings)
print (json.dumps(job, default=str))
print ('job:')
print (job['Job'])

body['job_id'] = job['Job']['Id']

Pass custom data to Lambda function

public function invoke_lambda($video, $version=5){
	$event = Storage::get('lambda_event_template.json');
	$event = json_decode($event, true);
	//logg($event);

	$event['Records'][0]['s3']['object']['key'] = $video->uri;
        $event['Records'][0]['s3']['dest']['uri'] = $video->base;

        //(string) is very important!
        $event['Records'][0]['videoId'] = (string)$video->id;

	$sharedConfig = [
		'region' => 'ap-northeast-1',
		'version' => 'latest', 
		'credentials' => [
			'key'    => env('AWS_ACCESS_KEY_ID'),
			'secret' => env('AWS_SECRET_ACCESS_KEY'),
		],
	];
	//logg($sharedConfig);

	// Create an SDK class used to share configuration across clients.
	$sdk = new Sdk($sharedConfig);

	// Create an Amazon S3 client using the shared configuration data.
	$client = $sdk->createLambda();

	$result = $client->invoke([
		'ClientContext' => '',
		'FunctionName' => 'arn:aws:lambda:ap-northeast-1:12341234:function:VODLambdaConvert:' . $version, // REQUIRED
		'InvocationType' => 'Event',
		'LogType' => 'Tail',
		'Payload' => json_encode($event),
		'Qualifier' => $version,
	]);

	//logg($result);
	return true;
}

Notice:

  • Version is very important!!
  • Make sure convert integer values to strings.
  • 有時候PHP會看起來沒有錯誤,但是MediaConvert卻沒有invoke成功,要去CloudWatch查是不是有Exception

SNS Listener

public function sns_listener(Request $req){
	$data = file_get_contents('php://input');
	$data = json_decode($data);
	//logg(json_decode($data, true));

	$log = new ApiLog;
	$log->title = 'SNS Listener';
	$log->request = json_encode($data);
	$log->save();

	//$headers = getallheaders();
	$message = json_decode($data->Message);
	$meta = $message->detail->userMetadata;
	$log->response = json_encode($meta);
	$log->save();

	if($message->detail->status == 'COMPLETE'){
		$video = VideoM::find($meta->videoId);
		if($video != false){
			$video->is_ready = 1;
			$video->cover = AwsLib::get_thumb($video);
			$video->save();
		}
	}

	echo 'ok';
}

AWS SDK for PHP

Document

https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/getting-started_installation.html

composer require aws/aws-sdk-php

API Reference

https://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html

https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.Sdk.html

Sample Code

use Aws\Exception\AwsException;
use Aws\Lambda\LambdaClient;
use Aws\S3\S3Client;

// The same options that can be provided to a specific client constructor can also be supplied to the Aws\Sdk class.
// Use the us-west-2 region and latest version of each client.
$sharedConfig = [
    'region' => 'us-west-2',
    'version' => 'latest'
];

// Create an SDK class used to share configuration across clients.
$sdk = new Aws\Sdk($sharedConfig);

// Create an Amazon S3 client using the shared configuration data.
$client = $sdk->createS3();
$client = $sdk->createLambda();

Configuration for the AWS SDK for PHP Version 3

https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/guide_index.html

$s3Client = new S3Client([
    'version'     => 'latest',
    'region'      => 'us-west-2',
    'credentials' => [
        'key'    => 'my-access-key-id',
        'secret' => 'my-secret-access-key',
    ],
]);

Lambda Client

https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.Lambda.LambdaClient.html

執行時可能會權限問題,要在Lambda console中加入Root帳號的權限. 另外測試有發生奇怪的問題,明明應該是用root, 但錯誤訊息卻說 “arn:aws:iam::12341234:user/s3-bucket-user-20210120 is not authorized” 目前無解,硬把這個User加入Lambda的Policy就好了。

"message": "Error executing \"Invoke\" on \"https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn%3Aaws%3Alambda%3Aap-northeast-1%3A146960510302%3Afunction%3AVODLambdaConvert%3A2/invocations?Qualifier=2\"; AWS HTTP error: Client error: `POST https://lambda.ap-northeast-1.amazonaws.com/2015-03-31/functions/arn%3Aaws%3Alambda%3Aap-northeast-1%3A146960510302%3Afunction%3AVODLambdaConvert%3A2/invocations?Qualifier=2` resulted in a `403 Forbidden` response:\n{\"Message\":\"User: arn:aws:iam::146960510302:user/s3-bucket-user-20210120 is not authorized to perform: lambda:InvokeFunc (truncated...)\n AccessDeniedException (client): User: arn:aws:iam::146960510302:user/s3-bucket-user-20210120 is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-northeast-1:146960510302:function:VODLambdaConvert:2 - {\"Message\":\"User: arn:aws:iam::146960510302:user/s3-bucket-user-20210120 is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-northeast-1:146960510302:function:VODLambdaConvert:2\"}",
    "exception": "Aws\\Lambda\\Exception\\LambdaException",
    "file": "D:\\ww

組態 -> 許可 -> 基於資源的政策

Lambda的function至少也要有一個完整的版本

完整sample code

use Aws\Exception\AwsException;
use Aws\Sdk;
use Aws\S3\S3Client;
use Aws\Lambda\LambdaClient;

public function invoke_lambda(){
	$event = Storage::get('lambda_event_template.json');
	$event = json_decode($event);
	//logg($event);

	$sharedConfig = [
		'region' => 'ap-northeast-1',
		'version' => 'latest', 
		'credentials' => [
			'key'    => env('AWS_ACCESS_KEY_ID'),
			'secret' => env('AWS_SECRET_ACCESS_KEY'),
		],
	];
	//logg($sharedConfig);

	// Create an SDK class used to share configuration across clients.
	$sdk = new Sdk($sharedConfig);

	// Create an Amazon S3 client using the shared configuration data.
	$client = $sdk->createLambda();

	$result = $client->invoke([
		'ClientContext' => '',
		'FunctionName' => '<Function ARN>', // REQUIRED
		'InvocationType' => 'Event',
		'LogType' => 'Tail',
		'Payload' => json_encode($event),
		'Qualifier' => '1',//版本號
	]);

	logg($result);
}

AWS SNS

SNS Console

In Laravel: (HTTPS endpoint)

Steps

AWS CloudFront

同時設定多個Source (S3 Bucket) 會有問題,有可能找不到object key

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html

設定完後會有 Allow-Control-Allow-Origin Error

https://medium.com/@marsztw/aws-cloudfront-%E4%B8%8A%E9%96%8B%E5%95%9F-cors-8d77b98747cc

在S3解決CORS問題

MediaConvert

  • 在MediaConvert console建立任務後會立即執行
  • 可以複製某任務後再調整參數
  • 可以轉檔,製作縮圖和加入浮水印圖片
  • 可以將設定輸入JSON,可以用在Lambda的script裡
  • IAM帳號要確定有GET and List S3資料夾的權限,寫入PUT也同樣要有權限
  • 位元率 Bitrate:
    輸入8000000的話就表示8000Kb/s = 8Mb/s = 1000KB/s = 1MB/s
  • 什麼是 HTTP 即時串流?| HLS 串流
    https://www.cloudflare.com/zh-tw/learning/video/what-is-http-live-streaming/
  • print()的完整結果要在CloudWatch Logs裡才看得到:
    https://ap-northeast-1.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-1#logsV2:log-groups
  • 匯出的JSON中,要把”DescriptiveVideoServiceFlag”刪掉,否則執行會造成 Unknown parameter 錯誤
  • 如果您的函數將物件寫入 S3 儲存貯體,請確保您使用不同的 S3 儲存貯體進行輸入和輸出 (就是輸出和輸入的資料夾要不同)
  • 在Lambda設定output路徑,直接指定就好了,系統會自動建立路徑
  • VideoSelector的Rotate一定要設定auto, 否則手機直拍的video會變橫的

Example JSON (with watermark and thumbs)
這個Example是先在MediaConvert console建立一個任務測試沒問題後,再把包在外層的code刪掉,只保留設定的部份

{
  "OutputGroups": [
    {
      "CustomName": "MP4",
      "Name": "File Group",
      "Outputs": [
        {
          "ContainerSettings": {
            "Container": "MP4",
            "Mp4Settings": {
              "CslgAtom": "INCLUDE",
              "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",
                "MaxBitrate": 3000000,
                "SlowPal": "DISABLED",
                "SpatialAdaptiveQuantization": "ENABLED",
                "TemporalAdaptiveQuantization": "ENABLED",
                "FlickerAdaptiveQuantization": "DISABLED",
                "EntropyEncoding": "CABAC",
                "FramerateControl": "INITIALIZE_FROM_SOURCE",
                "RateControlMode": "QVBR",
                "QvbrSettings": {
                  "QvbrQualityLevel": 7
                },
                "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"
              }
            },
            "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"
            }
          ]
        }
      ],
      "OutputGroupSettings": {
        "Type": "FILE_GROUP_SETTINGS",
        "FileGroupSettings": {
          "Destination": "s3://celeb-videos/output/",
          "DestinationSettings": {
            "S3Settings": {
              "AccessControl": {
                "CannedAcl": "PUBLIC_READ"
              }
            }
          }
        }
      }
    },
    {
      "CustomName": "Thumbnails",
      "Name": "File Group",
      "Outputs": [
        {
          "ContainerSettings": {
            "Container": "RAW"
          },
          "VideoDescription": {
            "Width": 800,
            "ScalingBehavior": "DEFAULT",
            "Height": 450,
            "TimecodeInsertion": "DISABLED",
            "AntiAlias": "ENABLED",
            "Sharpness": 50,
            "CodecSettings": {
              "Codec": "FRAME_CAPTURE",
              "FrameCaptureSettings": {
                "FramerateNumerator": 1,
                "FramerateDenominator": 5,
                "MaxCaptures": 3,
                "Quality": 80
              }
            },
            "AfdSignaling": "NONE",
            "DropFrameTimecode": "ENABLED",
            "RespondToAfd": "NONE",
            "ColorMetadata": "INSERT"
          }
        }
      ],
      "OutputGroupSettings": {
        "Type": "FILE_GROUP_SETTINGS",
        "FileGroupSettings": {
          "Destination": "s3://celeb-videos/output/",
          "DestinationSettings": {
            "S3Settings": {
              "AccessControl": {
                "CannedAcl": "PUBLIC_READ"
              }
            }
          }
        }
      }
    },
    {
      "CustomName": "HLS_GROUP_SETTINGS",
      "Name": "Apple HLS",
      "Outputs": [
        {
          "ContainerSettings": {
            "Container": "M3U8",
            "M3u8Settings": {
              "AudioFramesPerPes": 4,
              "PcrControl": "PCR_EVERY_PES_PACKET",
              "PmtPid": 480,
              "PrivateMetadataPid": 503,
              "ProgramNumber": 1,
              "PatInterval": 0,
              "PmtInterval": 0,
              "Scte35Source": "NONE",
              "NielsenId3": "NONE",
              "TimedMetadata": "NONE",
              "VideoPid": 481,
              "AudioPids": [
                482,
                483,
                484,
                485,
                486,
                487,
                488,
                489,
                490,
                491,
                492
              ],
              "AudioDuration": "DEFAULT_CODEC_DURATION"
            }
          },
          "VideoDescription": {
            "ScalingBehavior": "DEFAULT",
            "VideoPreprocessors": {
              "ImageInserter": {
                "InsertableImages": [
                  {
                    "ImageX": 30,
                    "ImageY": 30,
                    "Layer": 1,
                    "ImageInserterInput": "s3://celeb-videos/logo.png",
                    "Opacity": 70
                  }
                ]
              }
            },
            "TimecodeInsertion": "DISABLED",
            "AntiAlias": "ENABLED",
            "Sharpness": 50,
            "CodecSettings": {
              "Codec": "H_264",
              "H264Settings": {
                "InterlaceMode": "PROGRESSIVE",
                "ScanTypeConversionMode": "INTERLACED",
                "NumberReferenceFrames": 3,
                "Syntax": "DEFAULT",
                "Softness": 0,
                "GopClosedCadence": 1,
                "GopSize": 90,
                "Slices": 1,
                "GopBReference": "DISABLED",
                "SlowPal": "DISABLED",
                "EntropyEncoding": "CABAC",
                "Bitrate": 3000000,
                "FramerateControl": "INITIALIZE_FROM_SOURCE",
                "RateControlMode": "CBR",
                "CodecProfile": "MAIN",
                "Telecine": "NONE",
                "MinIInterval": 0,
                "AdaptiveQuantization": "AUTO",
                "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",
              "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"
            }
          ],
          "OutputSettings": {
            "HlsSettings": {
              "AudioGroupId": "program_audio",
              "AudioOnlyContainer": "AUTOMATIC",
              "IFrameOnlyManifest": "EXCLUDE"
            }
          },
          "NameModifier": "_hls"
        }
      ],
      "OutputGroupSettings": {
        "Type": "HLS_GROUP_SETTINGS",
        "HlsGroupSettings": {
          "ManifestDurationFormat": "INTEGER",
          "SegmentLength": 10,
          "TimedMetadataId3Period": 10,
          "CaptionLanguageSetting": "OMIT",
          "Destination": "s3://celeb-videos/output/",
          "DestinationSettings": {
            "S3Settings": {
              "AccessControl": {
                "CannedAcl": "PUBLIC_READ"
              }
            }
          },
          "TimedMetadataId3Frame": "PRIV",
          "CodecSpecification": "RFC_4281",
          "OutputSelection": "MANIFESTS_AND_SEGMENTS",
          "ProgramDateTimePeriod": 600,
          "MinSegmentLength": 0,
          "MinFinalSegmentLength": 0,
          "DirectoryStructure": "SINGLE_DIRECTORY",
          "ProgramDateTime": "EXCLUDE",
          "SegmentControl": "SEGMENTED_FILES",
          "ManifestCompression": "NONE",
          "ClientCache": "ENABLED",
          "AudioOnlyHeader": "INCLUDE",
          "StreamInfResolution": "INCLUDE"
        }
      }
    }
  ],
  "AdAvailOffset": 0,
  "Inputs": [
    {
      "AudioSelectors": {
        "Audio Selector 1": {
          "Offset": 0,
          "DefaultSelection": "DEFAULT",
          "ProgramSelection": 1
        }
      },
      "VideoSelector": {
        "ColorSpace": "FOLLOW",
        "Rotate": "AUTO"
      },
      "FilterEnable": "AUTO",
      "PsiControl": "USE_PSI",
      "FilterStrength": 0,
      "DeblockFilter": "DISABLED",
      "DenoiseFilter": "DISABLED",
      "TimecodeSource": "EMBEDDED",
      "ImageInserter": {
        "InsertableImages": [
          {
            "ImageX": 30,
            "ImageY": 30,
            "Layer": 1,
            "ImageInserterInput": "s3://celeb-videos/logo.png",
            "Opacity": 70
          }
        ]
      },
      "FileInput": "s3://celeb-videos/test.mp4"
    }
  ]
}

Reference:

AWS Streaming資料整理

ARN (Amazon Resource Name)

https://docs.aws.amazon.com/zh_tw/general/latest/gr/aws-arns-and-namespaces.html

使用 CloudWatch 活動 搭配 AWS Elemental MediaConvert

https://docs.aws.amazon.com/zh_tw/mediaconvert/latest/ug/cloudwatch_events.html

Automating MediaConvert Jobs with Lambda

https://github.com/aws-samples/aws-media-services-simple-vod-workflow/blob/master/7-MediaConvertJobLambda/README.md

Lambda function handler in Python

https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html

Building Lambda functions with Python

https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html

Amazon CloudFront 媒體串流教學

https://aws.amazon.com/tw/cloudfront/streaming/

Mediaconvert Notes