MediaConvert: Video Transcoding on AWS

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Baseline date: 2026-10-07. AWS pages in this sub-section use the Bookshelf names: region us-east-1 and account 111122223333 as placeholders, the uploads bucket bookshelf-uploads-111122223333, and a new bucket bookshelf-media-111122223333 for results. Check the MediaConvert User Guide for the current limits, job settings and price model (prices are not quoted here).

AWS Elemental MediaConvert is the managed, file-based video transcoding service. You describe the input, the outputs and where to write them, and AWS runs the encode. It is the managed counterpart of the FFmpeg workers in video encoding fundamentals and upload pipelines. Amazon Elastic Transcoder was discontinued on 13 November 2025, and MediaConvert is its replacement. Migrate any remaining Elastic Transcoder pipelines.

Concepts

Job

One transcode: inputs, output groups and settings. Created with CreateJob, and described by JSON.

Output group

A set of outputs of one kind: File (MP4, MOV and so on), Apple HLS, DASH ISO, CMAF, or Microsoft Smooth. The group decides the packaging and the destination, and each output in it is a rendition (a resolution and bit rate).

Output preset

A saved output configuration (codec, resolution, bit rate) you reuse. AWS provides system presets, for example for HLS at several resolutions, and you can create your own.

Job template

A saved job: output groups and presets, with the input left to each job. A template makes each CreateJob call small, and lets you change the ladder in one place.

Queue

Jobs run in a queue. The Default on-demand queue is enough to start. Reserved queues give guaranteed capacity.

Service role

An IAM role that MediaConvert assumes to read the input bucket and write the output bucket. The caller needs iam:PassRole for it.

Rate control: QVBR and Automated ABR

QVBR (quality-defined variable bit rate) is the recommended rate control for H.264, H.265 and AV1. You set a quality level (1 to 10, where 7 to 9 is a typical range) and a maximum bit rate, and the encoder spends bits only as the content needs, so simple scenes cost less. It is the managed counterpart of capped CRF. See the QVBR guidelines.

Automated ABR lets MediaConvert choose the ladder for each input: you give a maximum number of renditions and minimum and maximum bit rates, and the service picks resolutions and bit rates that fit the content. It is available for HLS, DASH and CMAF output groups. See Automated ABR. It replaces the hand-built table in the ladder section.

A job: HLS ladder plus cover frames

The job below reads the uploaded video, writes a two-rendition HLS group with segments of 6 seconds, and writes cover frames with a frame capture output, see frame capture and thumbnails. The settings were validated against the MediaConvert API model of the AWS SDK for Python (boto3 1.43) for this page.

{
  "Role": "arn:aws:iam::111122223333:role/bookshelf-mediaconvert",
  "Queue": "arn:aws:mediaconvert:us-east-1:111122223333:queues/Default",
  "UserMetadata": {
    "mediaId": "42"
  },
  "Settings": {
    "TimecodeConfig": {
      "Source": "ZEROBASED"
    },
    "Inputs": [
      {
        "FileInput": "s3://bookshelf-uploads-111122223333/uploads/42/original.mov",
        "AudioSelectors": {
          "Audio Selector 1": {
            "DefaultSelection": "DEFAULT"
          }
        },
        "VideoSelector": {}
      }
    ],
    "OutputGroups": [
      {
        "Name": "HLS",
        "OutputGroupSettings": {
          "Type": "HLS_GROUP_SETTINGS",
          "HlsGroupSettings": {
            "Destination": "s3://bookshelf-media-111122223333/videos/42/hls/",
            "SegmentLength": 6,
            "MinSegmentLength": 0,
            "SegmentControl": "SEGMENTED_FILES",
            "DirectoryStructure": "SINGLE_DIRECTORY"
          }
        },
        "Outputs": [
          {
            "NameModifier": "_720p",
            "ContainerSettings": {
              "Container": "M3U8",
              "M3u8Settings": {}
            },
            "VideoDescription": {
              "Width": 1280,
              "Height": 720,
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "RateControlMode": "QVBR",
                  "QvbrSettings": {
                    "QvbrQualityLevel": 8
                  },
                  "MaxBitrate": 3000000,
                  "GopSize": 2,
                  "GopSizeUnits": "SECONDS",
                  "SceneChangeDetect": "TRANSITION_DETECTION"
                }
              }
            },
            "AudioDescriptions": [
              {
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "Bitrate": 128000,
                    "CodingMode": "CODING_MODE_2_0",
                    "SampleRate": 48000
                  }
                }
              }
            ]
          },
          {
            "NameModifier": "_360p",
            "ContainerSettings": {
              "Container": "M3U8",
              "M3u8Settings": {}
            },
            "VideoDescription": {
              "Width": 640,
              "Height": 360,
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "RateControlMode": "QVBR",
                  "QvbrSettings": {
                    "QvbrQualityLevel": 7
                  },
                  "MaxBitrate": 800000,
                  "GopSize": 2,
                  "GopSizeUnits": "SECONDS"
                }
              }
            },
            "AudioDescriptions": [
              {
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "Bitrate": 96000,
                    "CodingMode": "CODING_MODE_2_0",
                    "SampleRate": 48000
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "Name": "Covers",
        "OutputGroupSettings": {
          "Type": "FILE_GROUP_SETTINGS",
          "FileGroupSettings": {
            "Destination": "s3://bookshelf-media-111122223333/videos/42/covers/"
          }
        },
        "Outputs": [
          {
            "NameModifier": "_cover",
            "ContainerSettings": {
              "Container": "RAW"
            },
            "VideoDescription": {
              "Width": 1280,
              "Height": 720,
              "CodecSettings": {
                "Codec": "FRAME_CAPTURE",
                "FrameCaptureSettings": {
                  "FramerateNumerator": 1,
                  "FramerateDenominator": 10,
                  "MaxCaptures": 6,
                  "Quality": 80
                }
              }
            }
          }
        ]
      }
    ]
  }
}

The HLS group above writes MPEG-TS segments. Use a CMAF output group for fMP4 segments that serve HLS and DASH from the same files, see video streaming.

Automated ABR variant

{
  "Role": "arn:aws:iam::111122223333:role/bookshelf-mediaconvert",
  "Settings": {
    "Inputs": [
      {
        "FileInput": "s3://bookshelf-uploads-111122223333/uploads/42/original.mov",
        "AudioSelectors": {
          "Audio Selector 1": {
            "DefaultSelection": "DEFAULT"
          }
        },
        "VideoSelector": {}
      }
    ],
    "OutputGroups": [
      {
        "Name": "HLS ABR",
        "AutomatedEncodingSettings": {
          "AbrSettings": {
            "MaxRenditions": 6,
            "MaxAbrBitrate": 8000000,
            "MinAbrBitrate": 600000
          }
        },
        "OutputGroupSettings": {
          "Type": "HLS_GROUP_SETTINGS",
          "HlsGroupSettings": {
            "Destination": "s3://bookshelf-media-111122223333/videos/42/abr/",
            "SegmentLength": 6
          }
        },
        "Outputs": [
          {
            "ContainerSettings": {
              "Container": "M3U8",
              "M3u8Settings": {}
            },
            "VideoDescription": {
              "CodecSettings": {
                "Codec": "H_264",
                "H264Settings": {
                  "RateControlMode": "QVBR"
                }
              }
            },
            "AudioDescriptions": [
              {
                "AudioSourceName": "Audio Selector 1",
                "CodecSettings": {
                  "Codec": "AAC",
                  "AacSettings": {
                    "Bitrate": 96000,
                    "CodingMode": "CODING_MODE_2_0",
                    "SampleRate": 48000
                  }
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

Run it

AWS CLI

aws mediaconvert create-job --region us-east-1 --cli-input-json file://job-hls.json
aws mediaconvert get-job --id 1700000000000-abc123 --query 'Job.[Status,JobPercentComplete]'
aws mediaconvert list-jobs --status ERROR --max-results 10

Recent AWS SDKs and the CLI use the regional endpoint (mediaconvert.us-east-1.amazonaws.com) directly. Older SDK versions required a call to DescribeEndpoints first for an account-specific endpoint, so if you see an endpoint error, upgrade the SDK. Reference: aws mediaconvert create-job.

Job template

Save the outputs once and create jobs from the template:

aws mediaconvert create-job-template --cli-input-json file://template.json
aws mediaconvert create-job --role arn:aws:iam::111122223333:role/bookshelf-mediaconvert \
  --job-template bookshelf-hls-ladder \
  --settings '{"Inputs":[{"FileInput":"s3://bookshelf-uploads-111122223333/uploads/42/original.mov"}]}'

The Settings in the job override the template: here, only the input is provided.

Python (boto3)

import boto3, json

client = boto3.client("mediaconvert", region_name="us-east-1")

def start_transcode(media_id: str, source: str) -> str:
    job = client.create_job(
        Role="arn:aws:iam::111122223333:role/bookshelf-mediaconvert",
        JobTemplate="bookshelf-hls-ladder",
        Settings={"Inputs": [{"FileInput": source}]},
        UserMetadata={"mediaId": media_id},
        ClientRequestToken=f"media-{media_id}",         # idempotent: the same token creates one job
    )
    return job["Job"]["Id"]

Node.js (AWS SDK for JavaScript v3)

import { MediaConvertClient, CreateJobCommand } from '@aws-sdk/client-mediaconvert';

const client = new MediaConvertClient({ region: 'us-east-1' });

export async function startTranscode(mediaId, source) {
  const { Job } = await client.send(new CreateJobCommand({
    Role: 'arn:aws:iam::111122223333:role/bookshelf-mediaconvert',
    JobTemplate: 'bookshelf-hls-ladder',
    Settings: { Inputs: [{ FileInput: source }] },
    UserMetadata: { mediaId },
    ClientRequestToken: `media-${mediaId}`,
  }));
  return Job.Id;
}

Java (AWS SDK for Java 2.x)

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.mediaconvert.MediaConvertClient;
import software.amazon.awssdk.services.mediaconvert.model.*;
import java.util.Map;

MediaConvertClient client = MediaConvertClient.builder().region(Region.US_EAST_1).build();

String startTranscode(String mediaId, String source) {
    CreateJobRequest request = CreateJobRequest.builder()
        .role("arn:aws:iam::111122223333:role/bookshelf-mediaconvert")
        .jobTemplate("bookshelf-hls-ladder")
        .settings(JobSettings.builder()
            .inputs(Input.builder().fileInput(source).build())
            .build())
        .userMetadata(Map.of("mediaId", mediaId))
        .clientRequestToken("media-" + mediaId)
        .build();
    return client.createJob(request).job().id();
}

.NET (AWS SDK for .NET)

using Amazon;
using Amazon.MediaConvert;
using Amazon.MediaConvert.Model;

var client = new AmazonMediaConvertClient(RegionEndpoint.USEast1);

async Task<string> StartTranscodeAsync(string mediaId, string source)
{
    var response = await client.CreateJobAsync(new CreateJobRequest
    {
        Role = "arn:aws:iam::111122223333:role/bookshelf-mediaconvert",
        JobTemplate = "bookshelf-hls-ladder",
        Settings = new JobSettings { Inputs = new List<Input> { new Input { FileInput = source } } },
        UserMetadata = new Dictionary<string, string> { ["mediaId"] = mediaId },
        ClientRequestToken = $"media-{mediaId}",
    });
    return response.Job.Id;
}

Job state events with EventBridge

MediaConvert emits MediaConvert Job State Change events to EventBridge for SUBMITTED, PROGRESSING, COMPLETE, ERROR, CANCELED and INPUT_INFORMATION. Use them instead of polling:

{
  "source": ["aws.mediaconvert"],
  "detail-type": ["MediaConvert Job State Change"],
  "detail": { "status": ["COMPLETE", "ERROR"] }
}
aws events put-rule --name media-job-finished --event-pattern file://pattern.json
aws events put-targets --rule media-job-finished \
  --targets 'Id=1,Arn=arn:aws:lambda:us-east-1:111122223333:function:media-job-finished'

# Without this resource-based permission EventBridge cannot invoke the function, and the rule silently does nothing.
aws lambda add-permission --function-name media-job-finished --statement-id events-invoke \
  --action lambda:InvokeFunction --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:111122223333:rule/media-job-finished

The event’s detail.userMetadata carries the mediaId you set, which is how a Lambda maps a finished job back to its database row, see the Video on Demand pipeline. Reference: EventBridge, job complete events and Messaging and Integration for EventBridge itself.

Where the pieces live

S3 stores the input and the output, see S3 object storage, and CloudFront delivers it, see CloudFront and streaming and delivery. The role needs s3:GetObject on the uploads bucket and s3:PutObject on the media bucket, nothing more.