Code explanation

There are three parts to the code in the previous section.

Retrieving an uploaded file in the inout S3 file:

Since our lambda function will be configured to execute in response to S3 put events (file upload), the event will contain information about the input bucket name:

bucket = urllib.parse.unquote(event['Records'][0]['s3' ['bucket']['name'])

and the file within the bucket (referred to as key) that was just uploaded:

key = urllib.parse.unquote(event['Records'][0]['s3']['object']['key'])

Converting the input file to HLS:

First we extract the extension of the input file to determine wether it is an MP4 or an MP3 file.

path_no_ext, file_ext = os.path.splitext(key)

where file_ext is the file extension.

if file_ext == '.mp4' or file_ext == '.mp3':
      file_name = os.path.basename(key)
      file_name_no_ext = os.path.basename(path_no_ext)
      os.chdir(tempfile.gettempdir())

Since we want to only work with these two file types, we will ignore any other file types that are uploaded to the S3 bucket. We determine the file name with and without the file extension. We will need this information when creating our .ts and .m3u8 files. The output of the ffmpeg command will be saved to the lambda file system before copying to the output S3 bucket (hls-ready). So we change the working directory to the /tmp directory. 

os.chdir(tempfile.gettempdir())

Every lambda function has a default 512MB of space in the /tmp directory available to it. Note that this limits the size of files that be converted to the available space.

After we have determined the input file type, we are now ready to convert our input files. Note that , we are creating a low resolution (640x360) HLS file  for mp4 inputs in the project. You can change it to your desired resolution. 

s3_client.generate_presigned_url(
      'get_object',
      Params={
         'Bucket': bucket,
         'Key': key
      })

We first get the source URL of the input file and then we make sure that the lambda instance we are using is not being reused. We do this by checking and emptying the /tmp directory.

files = [f for f in os.listdir('.') if os.path.isfile(f)]
for f in files:
    if f.endswith('.mp3') or f.endswith('.mp4') or f.endswith('.ts') or f.endswith('.m3u8'):
       os.remove(f)

For an mp4, we use these input parameters to the ffmpeg command:

cmd = ['/opt/ffmpeg', '-i', presigned_file_url, '-profile:v', 'baseline', '-level', '3.0', '-s', '640x360', '-start_number', '0', '-hls_time', '10', '-hls_list_size', '0', '-f', 'hls', file_name_no_ext + '.m3u8']

and for an mp3, we use this other parameters.

cmd = ['/opt/ffmpeg', '-i', presigned_file_url, '-vn', '-ac', '2', '-acodec', 'aac', '-f', 'segment', '-segment_format', 'mpegts', '-segment_time', '10', '-segment_list', file_name_no_ext + '.m3u8', file_name_no_ext + '-%05d.ts']

Now execute the command:

p = subprocess.call(cmd, stdin=subprocess.DEVNULL)

If the command is successfull, then it is time for the last step; uploading the output to our hls-ready S3 bucket.

Uploading to S3 bucket

if p == 0:
         for f in glob.glob(file_name_no_ext + "*.ts"):
            s3_client.upload_file(f, media_out_bucket_name, file_name_no_ext + '/' + f)
           s3_client.upload_file(file_name_no_ext + '.m3u8', media_out_bucket_name, file_name_no_ext + '/' + file_name_no_ext + '.m3u8')

The code above will look for all .ts files and a .m3u8 file and upload them to a directory in our S3 bucket. The directory will have the same name as the input file without the extension.