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
CreateJobcall small, and lets you change the ladder in one place. - Queue
-
Jobs run in a queue. The
Defaulton-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:PassRolefor 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
}
}
}
}
]
}
]
}
}
References: CreateJob API reference and the job settings guide.
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"]
Reference: boto3 MediaConvert create_job.
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;
}
Reference: AWS SDK for JavaScript v3, MediaConvert client.
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();
}
Reference: AWS SDK for Java 2.x developer guide and the MediaConvert API.
.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;
}
Reference: AWS SDK for .NET, MediaConvert.
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.