Networking & Content Delivery

On-the-fly video conversion with Amazon CloudFront, Lambda@Edge, and AWS Elemental MediaConvert

Introduction:

Whether your media library includes long form featured movies or short form “how-to” clips, the popularity of each video asset is typically set by your viewers preference. In order to deliver your online video content, AWS offers multiple solutions that you can use to automate your media supply chain, and streamline your content distribution.

You can further optimize your overall cost of media delivery by deploying an on-the-fly video conversion workflow. Consider your video assets that are viewed infrequently, at a single quality rendition, or never viewed at all. You can find it relevant in models like ‘free to watch’ with ads, where the return on investment for video processing and delivery ties directly to the video content popularity, or in other use cases, for example:

  • Unpopular User Generated Content (UGC)
  • Clips related to a featured movie, like commentary, or critique review
  • Stock footage preview
  • Recorded Video Conference calls for future replay
  • Infrequent replay of Online DVR (Digital Video Recorder) programs

This blog post introduces a serverless workflow for on-the-fly video conversion; From MP4 video source files stored in an Amazon S3 bucket to HTTP Live Streaming (HLS) served through Amazon CloudFront. The workflow uses Lambda@Edge function to invoke an AWS Elemental MediaConvert job.

On-The-Fly video conversion workflow overview

Diagram shows the workflow steps, converting video from mp4 source file to HLS. follow the steps below.

  1. End user client sends a request for an HLS video stream to Amazon CloudFront (The request include a resolution and mp4 video source file name parameters in the query string).
  2. CloudFront forwards your user request to your origin (assuming cache miss), and triggers a Lambda@Edge function on Origin-Request event.
  3. Lambda@Edge function parses the query string parameters, and attempts to fetch the requested manifest from an HLS stream Amazon S3 media bucket (the Origin).
  4. If the manifest is found, Lambda@Edge function returns the manifest. If the manifest is not found, Lambda@Edge invokes a MediaConvert job (providing the resolution and mp4 video source file name parameters in a job setting).
  5. MediaConvert receives the conversion job request, and attempts to fetch the mp4 video source from the media source S3 bucket.
  6. MediaConvert begins to generate an HLS manifest and segments. It stores them in the HLS Stream S3 bucket, and continues to update the manifest until the conversion process is complete.
  7. While MediaConvert initiates the conversion job, Lambda@Edge function generates an HLS manifest pointing to an intro video segment, and return it immediately.
  8. CloudFront forwards the intro manifest back to the end user for playback.

Assuming the end user’s player sends a refresh manifest request, this process begins all over at step 1. This time, Lambda@Edge function will find the newly generated Manifest in the HLS stream S3 bucket, fetch it, and return to CloudFront for delivery to your end user.

Solution deployment pre-requisite

  1. Create a new AWS account or use an existing account.

CloudFormation deployment walkthrough

Step 1: Choose the Launch Stack button to open the AWS CloudFormation console pre-loaded with the template:

Click here to Launch the CloudFromation Stack.

 

Your CloudFormation Console will open in us-east-1 Region. Choose Next to configure the stack options.Screen Shot showing AWS CloudFromation Console's First step, Create Stack image.

Step 2: Provide names for your media source S3 Bucket, and for your HLS streams S3 Bucket.

The template appends your AWS Account ID as suffix in this format:

your-hls-stream-bucket-<yourAWSAccountId>

your-media-source-bucket-<yourAWSAccountId>

The “Stack name” populates a default name: “on-the-fly-video-convert”. You can keep the default name, or change it to a name of your choice.

Screen Shot showing AWS CloudFromation Console's step 2, provide bucket name.

Step 3: Use the default configuration options in this step and choose Next. (Optional – specify tags to resources in the Stack).

Screen Shot showing AWS CloudFromation Console's step 3, add tags, optional.

Step 4: Review and acknowledge the stack configuration, and choose Create stack.

Screen Shot shows AWS CloudFromation Console's step 4, acknowledge configuration, and click create stack.

It can take up to 10 minutes for CloudFormation to complete deployment.

CloudFormation deployed resources review

When your CloudFormation deployment is completed, check the outputs tab values and you should see four resources key values:

  1. Media source S3 Bucket name
  2. HLS stream S3 Bucket name
  3. CloudFront distribution endpoint domain name
  4. Lambda@Edge function ARN (Amazon Resource Name)

Screen shot showing CloudFromation outputs tab with bucketname, CloudFront domain name, and lambda at edge Amazon Resource Name.

Amazon CloudFront

A CloudFront distribution is deployed with the HLS stream S3 bucket as origin; with two additional Cache Behaviors:

0 – *.m3u8 path pattern for HLS manifest requests.

1 – *.ts path pattern for the HLS video segment requests.

Screen shot showing CloudFront Cache Behaviors for manifest and segments file extensions.

For each Cache Behavior, there is a Cache Policy and Origin Request Policy attached.

Screen shot showing the cache policy and Origin Request Policy, attached to the Cache behavior.

Both Cache Behavior policies enable CORS headers. While the *.m3u8 Cache Behavior also forwards Query String parameters: width, height, and mediafilename.

Screen Shot of Cache Policy configuration, whitelist CORS headers, and Query String parameters: width, height, and file name.

Lambda@Edge function

The deployed Lambda@Edge function is associated to CloudFront *.m3u8 Cache Behavior, and triggered on “Origin Request“ Event.

Screen shot showing Lambda at Edge function associated to CloudFront's manifest cache behavior, and triggered on origin request event.

The Lambda@Edge function uses an IAM Role with two permission policies:

  1. AWS Managed Policy for Basic Lambda Role
  2. Inline policy that allows GetObject and ListBucket actions from the deployed HLS Stream S3 bucket, MediaConvert CreateJob, and IAM pass-role to MediaConvert, allowing read/write to and from the deployed S3 buckets.

Screen shot showing Lambda at Edge role policies including lambda basic role, and inline policy.

Helper Lambda function

The ‘Helper’ Lambda function runs only once during the CloudFormation stack deployment. It adds AWS MediaConvert API Endpoint as a custom header in CloudFront Origin configuration. CloudFront adds custom headers when forwarding the user request to the Origin. This allows Lambda@Edge function to use these custom headers as parameters at run time, triggered by CloudFront from any one of its global edge locations, not only in us-east-1 Region (where this CloudFormation Stack is deployed).

Screen shot showing CloudFront's Origin custom headers added by Lambda helper function, during the stack deployment.

There are three more CloudFront Origin Custom Headers added in this deployment, and used by Lambda@Edge function at run time: media source S3 bucket name, HLS stream S3 bucket name, and MediaConvert job role.

AWS Elemental MediaConvert API endpoint

This deployment uses MediaConvert API endpoint in us-east-1. You can find it in your MediaConvert Console under Account.

Screen shot showing MediaConvert API endpoint in us-east1, in MediaConvert console.

CloudFormation creates a role for MediaConvert to use when a job is invoked. The Role has an Inline Policy that allows GetObject and PutObject actions, restricted to your media source bucket and HLS bucket only.

Screen shot showing IAM console with MediaConvert role details, include one inline permission policy.

{
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::your-source-media-bucket-<yourAccountId>/*",
                "arn:aws:s3:::your-hls-media-bucket-<yourAccountId>/*"
            ],
            "Effect": "Allow"
        }
    ]
}

Amazon S3 Buckets

In Step 2, you provided names for two Amazon S3 buckets deployed by CloudFormation.

Your HLS stream S3 bucket is deployed with a permission policy attached, that is restricted by:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E2FXXXXXYYYYYY"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-media-bucket/*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::AWSAccountId:role/LambdaFunctionRoleARN",
                    "arn:aws:iam::AWSAccountId:role/MediaConvertRoleARN"
                ]
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3::: your-media-bucket/*"
        }
    ]
}

A second bucket policy attached to your Media Source S3 bucket, restricting read access permissions to resources with the attached role that was created for MediaConvert to invoke a job:

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "PolicyForMediaConvertJobRole",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::Your-Account-ID:role/otf-video-convert-MediaConvertJobRole-Your-Stack-ID"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::Your-Media-Source-Bucket-Your-Account-ID/*"
        }
    ]
}

Test your deployment

  1. Upload your MP4 media file to your Media Source S3 bucket (yourMediaSourceBucket-<yourAWSAccountId>).
  2. Create your 6-seconds intro HLS video segment in the following resolutions: 416×234, 640×360, 768×432, 960×540, 1280×720, 1920×1080
  3. Use this naming format for your intro segment files: ‘intro640x360.ts’ for 640×360 resolution.
  4. Upload the intro segments you created to your HLS stream bucket.

Your buckets should look like this after uploading your media file and intro segments:

Media Source Bucket:

Screen shot showing an example of your S3 source media bucket with mp4 video source file.

HLS Stream Bucket:

Screen shot showing an example of your S3 HLS stream bucket with multiple HLS segments files in diferent resolutions for intro video segment.

5. Make an HTTPS request from your HLS player in this format:

https://d1abcd.cloudfront.net/your-media-file768x432.m3u8?width=768&height=432&mediafilename=your-media-file.mp4
  • The HTTPS request format require the following parameters in this deployment:
    • The manifest file name should be a combination of the source file name (without the mp4 extension), and the stream resolution: your-media-file768x432.m3u8.
    • The resolution parameters in the query string should match the resolution in the manifest file name: width=768&height=432,
    • The source video file name parameter in the query string should point to the file name you want to convert on-the-fly: your-media-file.mp4
  • Change ‘your-media-file’ to the mp4 filename in your media source bucket.
  • Change the CloudFront domain to your CloudFront distribution domain name (provided in the CloudFormation Stack Outputs).

Now you can make more requests using the same HTTPS request format, but use different resolutions to retrieve the stream in different renditions.

Cost consideration

Overall, a cost model for on-the-fly video conversion workflow assumes that video assets can be converted only on request, and at a specific resolution.

For example, converting a video library on-the-fly based on this request rate:

  • 30% of assets are requested in 6x renditions (3xHD, 3xSD)
  • 30% of assets are requested in 4x renditions (2xHD, 2xSD)
  • 40% of assets are requested in 2x renditions (1xHD, 1xSD)

Can reduce 36.6% of transcoding time, compared to 100% conversion of the same library:

  • 100% of assets converted in 6x renditions (3xHD, 3xSD)

To calculate pricing for services deployed by the CloudFormation in this blog check the following services pricing:

Scale consideration

Using this solution at scale require additional considerations that are not covered in this deployment:

  • Multi-Region MediaConvert Endpoint
    • We recommend that you use MediaConvert in the same Region that the S3 media bucket is located for lower latency, and save on data transfer cost between Regions.
  • Duplicate requests from different Edge locations
    • Amazon CloudFront forwards requests from global edge locations to your origin. There may be cases where your viewers send simultaneous requests for the same content rendition. It is recommended to keep a state of the conversion in progress, so only one MediaConvert job is invoked for each content rendition.
      • Consider using CloudFront Origin Shield to consolidate origin requests for viewers in different geographical Region.
      • Consider using a DynamoDB table to store current conversions-in-progress state. Use the data to determine if a job is in progress, and whether to invoke MediaConvert job or not.
  • Update your main manifest with the newly created rendition.
    • Your MediaConvert job generates a new main manifest and playlist (child) manifest with every conversion, and stores it in your HLS bucket. If you want to include multiple renditions in the main manifest, you should consider rewriting the main manifest. You can use an S3 event to trigger a Lambda function every time a conversion is completed. Your Lambda function appends the new rendition to a separate main manifest that you can serve Adaptive Bitrate (ABR). Alternatively, if you want to generate multiple renditions at once, you can use MediaConvert Auto-generate ABR package .

Cleanup

To clean up the artifacts from the solution in this post, first delete all files in your Amazon S3 buckets created in this deployment:

  • yourHlsStreamBucket-<yourAWSAccountId>
  • yourMediaSourceBucket-<yourAWSAccountId>

Then delete the CloudFormation stack that was provisioned at the start of this process in order to clean up all remaining infrastructure.

If the Stack deletion fails, rerun the Stack deletion. This time, CloudFormation opens a dialog, with the option to delete or retain the Lambda@Edge function resource, select Delete.

Conclusion

In this blog, you have learned how to deploy a solution for on-the-fly video conversion. You can apply this workflow to convert your mp4 video source files to HLS video streams in different resolutions. MediaConvert supports other conversion and transformation capabilities, like clipping, cropping, adding quality filters (deblock/denoise). You can use the same on-the-fly workflow to invoke MediaConvert jobs, and apply different conversions as your business require. For more information about MediaConvert conversion capabilities, and API usage refer to MediaConvert documentation.