Run FFmpeg in AWS Lambda for Video Transcoding

Running FFmpeg on AWS Lambda enables a lightweight, cost-effective, and fully serverless video processing pipeline without managing dedicated virtual machines. This guide details how to bundle a static FFmpeg binary into a Lambda Layer, configure the required execution environment, manage temporary local storage, and invoke transcoding commands triggered by Amazon S3 events using Python.

1. Package FFmpeg into a Lambda Layer

AWS Lambda functions run on Amazon Linux. To run FFmpeg, you must provide a statically compiled Linux binary that includes all necessary codecs.

  1. Download a static build of FFmpeg targeting x86_64 (or arm64, depending on your Lambda architecture).

  2. Create a directory structure matching /bin:

    mkdir -p ffmpeg-layer/bin
    cp /path/to/ffmpeg ffmpeg-layer/bin/
    chmod +x ffmpeg-layer/bin/ffmpeg
  3. Compress the directory into a zip file:

    cd ffmpeg-layer
    zip -r ../ffmpeg-layer.zip .
  4. In the AWS Lambda Console, navigate to Layers, click Create layer, upload ffmpeg-layer.zip, and set the compatible architecture to match your binary (x86_64 or arm64).

Attaching this layer puts the ffmpeg executable into /opt/bin/ffmpeg, which AWS Lambda includes in the default execution $PATH.

Alternatively, if your binary and dependencies exceed Lambda's 250 MB unzipped layer limit, package your application as an OCI-compliant container image based on an AWS Lambda base image.

2. Configure Lambda Storage, Memory, and Timeout

Video transcoding is compute- and I/O-intensive. Standard Lambda defaults are insufficient for this workload:

  • Ephemeral Storage (/tmp): Lambda offers between 512 MB and 10,240 MB (10 GB) of /tmp disk space. Set this to at least 3–5 times the size of your input video to accommodate the input file, output file, and intermediate chunks.
  • Memory: Allocate between 2048 MB and 10240 MB of RAM. Lambda scales vCPU allocation proportionally to memory; higher memory allocations provide more CPU cores to FFmpeg, significantly reducing processing time.
  • Timeout: Set the timeout close to the maximum limit of 15 minutes (900 seconds) to prevent premature termination during complex encoding passes.

3. Implement the Lambda Handler

Use AWS SDK (boto3 for Python) along with the native subprocess module to stream or download the file from Amazon S3, execute the command, and upload the result.

import os
import subprocess
import boto3

s3_client = boto3.client('s3')

def lambda_handler(event, context):
    # Extract bucket and key from the event
    record = event['Records'][0]['s3']
    source_bucket = record['bucket']['name']
    source_key = record['object']['key']
    
    input_path = f"/tmp/{os.path.basename(source_key)}"
    output_key = f"transcoded/{os.path.splitext(os.path.basename(source_key))[0]}.mp4"
    output_path = f"/tmp/output.mp4"
    
    # 1. Download file from S3 to /tmp
    s3_client.download_file(source_bucket, source_key, input_path)
    
    # 2. Run FFmpeg command
    # Converts input video to H.264 / AAC 720p
    command = [
        "ffmpeg",
        "-y",
        "-i", input_path,
        "-vf", "scale=-2:720",
        "-c:v", "libx264",
        "-preset", "fast",
        "-crf", "23",
        "-c:a", "aac",
        "-b:a", "128k",
        output_path
    ]
    
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    
    if result.returncode != 0:
        print(f"FFmpeg error: {result.stderr}")
        raise RuntimeError("Video processing failed.")
        
    # 3. Upload the transcoded file back to S3
    s3_client.upload_file(output_path, source_bucket, output_key)
    
    # 4. Clean up /tmp directory to avoid filling disk across invocations
    if os.path.exists(input_path):
        os.remove(input_path)
    if os.path.exists(output_path):
        os.remove(output_path)
        
    return {
        "statusCode": 200,
        "body": f"Successfully transcoded {source_key} to {output_key}"
    }

4. Optimize Performance

  • Multi-threading: Ensure your FFmpeg command utilizes available cores by default, or explicitly pass the -threads flag matching your allocated vCPU count.
  • Stream I/O with Pipes: For formats that support streaming (such as fragmented MP4 or certain audio streams), pipe input from standard input and output to standard output using S3 streaming wrappers to avoid disk I/O bottlenecks.
  • Long Video Limitations: If video files take longer than 15 minutes to transcode, split videos into chunks using AWS Step Functions to process parts in parallel, or offload the job to AWS Batch or AWS Elemental MediaConvert.