Often your creative team will produce master videos in 4k or 1080p, but you need to downcode these videos into 720p/1080p for web broadcasting. Here we automate transcoding of masters into web-friendly formats like 720p h264 mp4 & webm.

AWS Elastic Transcoder is a cloud video transcoding service. At it’s simplest it transcodes video files from one bitrate, framerate, codec, container, etc–into another. By default you trigger new jobs either manually in the aws console or via the rest API. And naturally all inputs & outputs are saved in S3.

Transcoder setup includes creating a pipeline and presets. Then for each file you want to transcode, you call createJob with the input file, preset and output file. That part is easy to configure and test in the console without extra code.

Once the pipeline and jobs are tested, Lambda can watch a S3 bucket prefix (aka directory) to trigger the createJob. For this example we’ll watch transcoder/upload. Every file put there will trigger a single transcode job that outputs (2) 720p files–one webm & one h264 .mp4– and save them to transcoder/output.


var aws = require('aws-sdk');
var elastictranscoder = new aws.ElasticTranscoder();

// return basename without extension
function basename(path) {
   return path.split('/').reverse()[0].split('.')[0];
}

// return output file name with timestamp and extension
function outputKey(name, ext) {
   return name + '-' + Date.now().toString() + '.' + ext;
}

exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(event, null, 2));
    // Get the object from the event and show its content type
    var key = event.Records[0].s3.object.key;
    var params = {
      Input: { 
        Key: key
      },
      PipelineId: '1440795695386-x512ni', /* test-web-transcoder */
      OutputKeyPrefix: 'transcoder/output/',
      Outputs: [
        {
          Key: outputKey(basename(key),'mp4'),
          PresetId: '1441222625682-nnthmh', // h264
    	},
        {
          Key: outputKey(basename(key),'webm'),
          PresetId: '1441222599518-vt9jbu', // webm
        }
      ]
    };

    elastictranscoder.createJob(params, function(err, data) {
      if (err){
        console.log(err, err.stack); // an error occurred
        context.fail();
        return;
      }
      context.succeed();
    });
};

Lambda jobs are authorized via an IAM role . By default Lambda will add the required perms to access logging & s3. Add the createJob rights as well like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    },
    {
      "Sid": "Stmt1441234334958",
      "Action": [
        "elastictranscoder:CreateJob"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

To test it out, just use the s3 event tester in the Lambda console to trigger events with a known mp4 input file. Once that works reliably, you can activate the real event on whatever prefix location you want.

Here’s how it works in practice. I’ll upload a file as master-3.mp4. Then we’ll see the output files in the transcoder/output prefix

# copy the file
aws s3 cp "$f" s3://media.bucket/transcoder/upload/test-master-3.mp4

# list the file to make sure it's there
aws s3 ls --recursive s3://media.bucket/transcoder/upload|grep master-3
2015-09-02 18:30:20    4516582 transcoder/upload/test-master-3.mp4

# wait about 60s and check the output prefix
# notice the lambda function timestamps the filenames to prevent collisions.
aws s3 ls --recursive s3://media.bucket/transcoder/output|grep master-3
2015-09-02 18:30:46    4519170 transcoder/output/test-master-3-1441243826159.mp4
2015-09-02 18:31:42    5390002 transcoder/output/test-master-3-1441243826159.webm

For more info you can see the createJob API which is very powerful and frankly intimidating. Thankfully the simple example above does all you need.