Build a photo book using PIL on Python

Alicia Wong
4 min readJun 3, 2022

I wish I had an inspirational story about why I decided to build this.

Honestly, I was following along an AWS QwikLab on building a photo book printing serverless application using event-driven architecture. I wanted something newer on my AWS portfolio.

Due to my ferocious independence (and limited budget), I chose to build this project outside the lab environment. Turns out the lab provisions the Image Processing state machine for you. Since I was outside the lab environment, I wrote my own.

From reading the lab carefully, I deduced that the Image Processing state machine had a few steps. Each image would undergo type check, image content validation, rotation and/or resizing, and then all the images would be stitched into a single PDF file.

Granted, I still have to make some changes to this to implement on AWS Lambda (ie., pull from a specific S3 bucket). But this short script would work on your computer and you can upload the file to a third-party vendor for printing.

You’ll need the obvious requirement: Python 3.9. There are plenty of ways to install this but I use pip and PyCharm.

Once that’s sorted, you only need to install 1 module: the PIL (Python Imaging Library) module. glob is a part of Python’s standard library, but you still have to import it.

You can specify the dir path (but remember to escape the forward slash) or run the script in the desired directory.

from PIL import Image
import glob
# specify folder path or execute in the specified folder
imagelist = glob.glob(r'C:\FOLDERPATH\*.jpg')

The openimage(image_list) function is a loop. For each image in the list, we are retrieving the size. If the image is a portrait (ie., height exceeds width), we’ll rotate it 90 degrees to fit the dimensions of the page.

I used Image.thumbnail((height, width)) because it preserves the aspect ratio of the image. If you use Image.resize(), the photo is stretched or cut off.

The remaining few lines save the image as its original filename and append the file names into a new list, which we’ll use in our next function.

def openimage(image_list):
new_list = []
for i in image_list:
image = Image.open(i)
width, height = image.size
# if image is portrait, rotate it:
if height > width:
image = image.rotate(90, expand=True)
image.thumbnail((1754, 1240))
image_filename = i.split(".")[0]
image.save(image_filename + '.jpg', format='JPEG')
new_list.append(image_filename + '.jpg')
return(new_list)
image.thumbnail((1754, 1240))
image_filename = i.split(".")[0]
image.save(image_filename + '.jpg', format='JPEG')
new_list.append(image_filename + '.jpg')
return(new_list)

The split_list(new_list) function uses zip to create tuples of images… which is a fancy way of saying: Pair Item #1 in List 1 with Item #1 in List 2, pair Item #2 in List 1 with Item #2 in List 2, and so forth. Since that takes too many lines and I’m super creative, I used splicing within the one list to make these paired tuples.

def split_list(new_list):
# zip one list into pairs to create pairs of images
# odd image will create pair with black image
split_list = list(zip(new_list[::2], new_list[1::2]))
return(split_list)

***NOTE: This part is an explanation. Do not include this in your code.

This is the syntax for splicing:

a[start:stop]  # items start through stop-1
a[start:] # items start through the rest of the array
a[:stop] # items from the beginning through stop-1
a[:] # a copy of the whole array
a[start:stop:step] # start through not past stop, by step

So essentially: zip(new_list[start at beginning: to the end of list: skip every other], new_list[skip the first item: to the end of list: skip every other]).

list(zip(new_list[::2], new_list[1::2]))

***

The convert_to_PDF(split_list, pdfname) function is very aptly named. We first create a blank list called merged_imagelist. Using a loop, we iterate over our paired tuples from our previous function, merge them into a new RGB image, and then append them to our blank merged_imagelist. At the end, we save all the images on the merged_imagelist into a PDF.

def convert_to_PDF(split_list, pdfname):
# convert pairs of jpg files into multiple PDF pages
# append pages to each other to make 1 file
# write and save to file
merged_imagelist = []
for x, y in split_list:
image1 = Image.open(x)
image2 = Image.open(y)
merged_image = Image.new('RGB', (image1.width, image1.height + image2.height))
merged_image.paste(image1, (0,0))
merged_image.paste(image2, (0, image1.height))
merged_image = merged_image.convert('RGB')
merged_imagelist.append(merged_image)
merged_image.save(pdfname, save_all=True, append_images=merged_imagelist[:-1])

So that’s how the script works. Now, you could call each function individually and specify the ‘filename.pdf’… or you can use it in a bash script and call it from your shell.

new_list = openimage(imagelist)
updated_list = split_list(new_list)
convert_to_PDF(updated_list, 'FILENAME.pdf')

If you choose to implement this on AWS, you could break these down into Lambda functions and orchestrate them using Step Functions. You could also add a front-end allowing multi-uploads into an S3 bucket or add SNS functionality to email the user whenever their photo book is generated.

I hope this was useful and you learned something. Thanks for reading!

--

--

Alicia Wong

Pythonista. AWS Solutions Architect Associate. Fascinated by serverless, infrastructure as code, and cloud architecture.