HEIC to JPG converter in Azure, serverless

Background

I wanted to build an app that would run on Azure’s serverless compute, and allow the user to just interact a simple web UI. I figured a file converter would be a simple enough way to make this happen.

At first I was going to make a .mov to .mp3 converter, but decided against it. .mov files will obviously be larger and would require more compute to convert, and I’m trying to keep costs low after all!

I went with HEIC to JPG because I could actually get some use out of this conversion, and the files will be much smaller.

Here’s a link to the Github repo with the finished product.

Architecture & Design

The API and Azure function are written in Python. I decided to go with FastAPI for the actual API as I had a base level of familiarity with it, and it would suit my simple use case.

A Quick Breakdown of how this all works:

  1. FastAPI serves a very basic web UI and prompts for a file upload

  2. User uploads a file and the API sends that file to an Azure Storage Account container for uploaded files

  3. When a new file lands in the “Uploads” storage container, Azure Event Grid sends a message to our Azure Function to notify it of a new file

  4. The Event Grid message provides details on the name of the file and the file extension

  5. The Function app does some basic input validation, making sure the file is a .heic and was uploaded to the expected container

  6. The Function app converts the .heic to .jpg using the pillow-heif library and uploads it to the “Processed” container in Azure

  7. FastAPI polls the “Processed” container every few seconds until it sees a file with the expected name (ex. original_file_name.jpg) and downloads the file

  8. The converted file is returned to the user

Fast API

The FastAPI part is the was the newest for me. I had a basic understanding of API’s, but had no idea how to build one myself. FastAPI’s documentation is great, so I spent some time reading and refreshing my memory on HTTP methods and response codes.

The API consists of just two routes. The root ‘/’ route serves the most basic web page with a means of uploading a file.

The route that does all the work is the ‘/upload’ route. This builds our connection to the Azure storage account and copies the file into the “uploads” storage account in Azure.

While the Azure Function is running, the API will poll the output storage account in regular intervals while it waits for the converted file to appear. Once this file appears, it’s downloaded and returned to the user.

Azure Function

This is where the actual file conversion happens. The Function uses an Event Grid trigger, so it only wakes up when a blob lands in the “uploads” container.

When it fires, the Event Grid message gives the Function the name and URL of the blob. The function does a few quick validations to make sure the blob is actually in the correct container, and the file extension is .heic.

The conversion itself is handled by pillow-heif. This library is great, it kept me from having to spend too much time on the technical specifics of the conversion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pillow_heif import register_heif_opener
from PIL import Image
import io
 
register_heif_opener()
 
image = Image.open(io.BytesIO(heic_bytes))
 
buffer = io.BytesIO()
image.convert("RGB").save(buffer, format="JPEG", quality=90)
buffer.seek(0)

From there the JPG gets written to the “processed” container under the original filename, which is what FastAPI is waiting to see.

Infrastructure with Terraform

None of this would be complete without some IaC. All of the infra was written in Terraform. The primary resources are:

  • A storage account with two blob containers: ‘uploads’ and ‘processed’
  • A Function App (Linux, Python, consumption plan) runs the conversion
  • An App Service that hosts the web UI and FastAPI
  • Event Grid wiring that connects the storage account and the function

.Zip files containing the actual Function and FastAPI/Python code are pushed via Azure CLI after the main infrastructure is applied with Terraform. In a true prod environment, this is not the way you would go, but it’s sufficient for a short lived project.

I got stuck for a while on the Event Grid aspect of the IaC. I tried to apply the Event Grid in the same set of .tf files that contained everything else, but it failed over and over. It turns out that Event Grid, used in this way, won’t initialize until the function it’s targeting is up and running. Even with Terraform’s smart dependency handling, Event Grid was trying to initialize before the Function was alive.

I ended up separating out the Event Grid code into a second set of .tf files and running a separate ’terraform apply’ once the Function was up. Again, not the smoothest way to do this, but I was able to make it repeatable.

Final Notes

If I revisit this project in the future, there are several things I will add. I’d like add a prettier web UI with additional upload options. I’d also like to push the code for the Function and FastAPI using a true CI/CD pipeline, to get away from the manual deploying of the .zip’s.

All in all I am satisfied with the way this turned out, and really enjoyed getting to work with Azure’s serverless offerings.

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy