Batch Image Manipulation with Python: Famous quotes embedded on free stock images

So recently I’ve been wanting to create a years worth of images with quotes from famous people and then schedule them on various social media for a few different pet projects of mine. Please note that I’m not at all a professional developer, so experienced developers, please brace yourselves.

This post will provide a working example of a very simple, yet powerful enough Python image batch script using Pillow, that iterate through stock images, and combine them with famous quotes and authors from CSV files.

At the end of this tutorial, you can generate hundreds or thousands of unique images with added quotes and their respective authors. I also add support for embedding custom graphics and or optional text for company logos or websites, so you can further customize to your needs.

And last but not least I show off the opacity function for overlays, separating the quote texts from the backgrounds.

What you will end up with, could be something like this.

Tons of room for improvement on the design aspect, but it works for now.

I might update the code later with an added transparent image overlay containing a few swirls and lines to give the image a bit more panache, but the main purpose is to show you how to generate images in batch, not give a lesson in design. 

As of writing, there are some issues with the functionality of the code, such as some text being incorrectly padded like this:

Another quite big issue is the fact that the first line is set to simply just use a fixed width, meaning that for quotes particularly, oftentimes it just looks wrong, such as this one:

As I’m not using this script for anything, I doubt I’ll get around to updating it, but I mention it as I hope someone might comment with a quick solution.

It should also be noted that I by no means encourage you to just use this code as I am neither a professional developer, nor am I read up on any theory regarding best practices. (Read: the code is most likely horrible)

So, first we need an installation of Python, I use the lastest version. So create a folder named after you liking, this folder will contain the entire script, including folders for images and CSV files.

Create your main script, name it whatever you like, I will just refer to this as:

import PIL 
import csv 
import textwrap 
from PIL import ImageFont 
from PIL import Image 
from PIL import ImageDraw

First few lines are importing and assigning the libraries we need, namely Pillow, csv and textwrap. If you are new to Python and the above lines are throwing an error, look into package managers, and install the needed modules.

The way I have structured things in this script are as follows:

The authors and quotes are split up for my own benefit, as I originally wrote this script for movie quotes where I needed to link various data from 2 separate files. I’ve left it like this, since it might provide some additional functionality that would otherwise be left out. Feel free to change it.

The folder called /in/, are simply containing a list of JPG’s that I’ve downloaded from various CC0 image archive sites, such as Pexels and Pixabay.

The folder called /out/ are used to store the newly generated images.

I then run a simple windows .bat file thats simply located inside the /in/ folder to rename all files to my liking. This way I could even match up quote #4 with image #4 if I wished to.


@echo off
ren *.jpeg *.jpg
setlocal EnableDelayedExpansion
set i=0
for %%a in (*.jpg) do (
 ren "%%a" "!i!.new"
 set /a i+=1
ren *.new *.jpg



As you can see, both CSV files are simply separated by line breaks.

This is the first time I write a tutorial that covers more than a few lines of code, so I hope you can bear with my bad commenting, and even worse quality of code and formatting. Below is the full script.

#BIMP - Batch Image Manipulating with Python

 Author; Mark Pedersen @makerspender
 License: CC BY 2.0
#load required libraries
import PIL
import csv
import textwrap
from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw

#create lists to store quotes and authors in
lines = [line.rstrip() for line in open('quotes.csv')]
authors = [author.rstrip() for author in open('authors.csv')]

#start the for loop
for idx, val in enumerate(lines):

#echo each quote and author
    print(idx, val)

#create main quote value
    para = textwrap.wrap(val, width=40)

#set image dimensions
    MAX_W, MAX_H = 1920, 1280

#set image location
    imageFile = "D:\imgapp\in\%s.jpg" %idx

#assign im to pillow and open the image
    im ='RGBA')

#resize the image to our chosen proportions and use antialiasing
    im = im.resize((1920, 1280), Image.ANTIALIAS)

#create new layer for adding opacity 
    poly ='RGBA', (1920,1280))
    polydraw = ImageDraw.Draw(poly)

#fill the image with black and 165/255 opacity
    polydraw.rectangle([(0,0),(1920,1280)], fill=(0,0,0,165), outline=None)
#paste in the layer on top of the image im

#command to start merging layers
    draw = ImageDraw.Draw(im)

#setting up fonts
    font = ImageFont.truetype("C:\Windows\Fonts\ArchivoBlack-Regular.ttf",80)
    authorfont = ImageFont.truetype("C:\Windows\Fonts\Roboto-BlackItalic.ttf",60)
    linkfont = ImageFont.truetype("C:\Windows\Fonts\Roboto-Black.ttf",24)

#setting up padding and positioning for quote text
    current_h, pad = 300, 50

#for loop breaking up each quote into lines not exceeding the width of the image dimensions	
    for line in para:
        w, h = draw.textsize(line, font=font)
        draw.text(((MAX_W - w) / 2, current_h), line, font=font)
        current_h += h + pad

#setting up padding and positioning for author text
    current_h2, pad2 = 900, 80
    currentauthor = authors[idx]
    w, h = draw.textsize(currentauthor, font=authorfont)
    draw.text(((MAX_W - w) / 2, (current_h + 100)), currentauthor, font=authorfont)
    current_h2 += h + pad2

#setting up padding and positining for optional text
    current_h3, pad3 = 1200, 30
    sitelink = "Batch Image Manipulating with Python"
    w, h = draw.textsize(sitelink, font=linkfont)
    draw.text(((MAX_W - w) / 2, current_h3), sitelink, font=linkfont)
    current_h3 += h + pad3

#loading optional logo and converting to RGBA for transparency support
    logo ='logo.png').convert('RGBA')
    logo_w, logo_h = logo.size
    im.paste(logo, (50,1150), logo)

#saving the image to our chosen location"D:\imgapp\out\image_%d.jpg" %idx)


I’ve used the official pillow documentation extensively, and while I had an idea that there would be plenty of tutorials out there offering a quick and better solution than the one I came up with, I couldn’t find any complete package.

So while the above code is neither pretty nor optimal, it works for the intended purpose, and hopefully can serve as inspiration or a bootstrap for your own projects.

Please let me know in the comments whether I’ve made a giant error, if you have updates, fixes or wishes for new features.