Skip to main content

Adding alt text to my all my images with OpenAI

·3 mins

I’ve used images in a number of posts on this blog since it was started in 2008. Unfortunately, I haven’t always been good at adding alt text for each of them, particularly in the early years.

If you’re not familiar with alt text, it is a description of an image that allows screen readers to convey the image’s content to visually impaired users. Alt text also serves as a fallback when an image fails to load.

In HTML alt text looks like this:

<img src="cat.jpeg" alt="A fluffy orange cat sitting on a windowsill." />

I wanted to go back and add alt text to all of the images on my site that do not already have them, but adding them manually would take a long time.

Luckily, LLMs are quite good at creating alt text for images. So, I wrote a script to go through all the references to images that do not contain the alt text and add them.

Because this site is built with Hugo, this actually means going through the markdown files and looking for figure blocks and adding the alt text there.

The code for this script can be found below and on Github. It uses the nice llm library to call OpenAI, and uses their gpt-4.1-nano model, which made this very cheap (~$0.03 for ~130 images). The results were good, with just a few needing editing to improve the description.

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["llm", "requests"]
# ///
#
# A script to add alt text to figures in a Hugo site, using ChatGPT to generate the alt text.
# Works for both images saved locally and those with a URL.

import os
import glob
import llm
import requests

def process_markdown_for_alt_text(content):
    lines = content.splitlines()
    updated_lines = []
    for line in lines:
        if '{< figure src="' in line and not 'alt="' in line:
            src_start = line.find('src="') + 5
            src_end = line.find('"', src_start)
            image_src = line[src_start:src_end]

            attachments = []
            if image_src.startswith("http"):
                response = requests.head(image_src, allow_redirects=False, timeout=5)
                if response.status_code == 200:
                    attachments.append(llm.Attachment(url=image_src))
            else:
                image_path = f"static{image_src}"
                if os.path.exists(image_path):
                    attachments.append(llm.Attachment(path=image_path))
                else:
                    print(f"Warning: Image not found: {image_path}")

            if attachments:
                model = llm.get_model("gpt-4.1-nano")
                try:
                    response = model.prompt(
                        "Generate a concise alt text for the image. The alt text should be descriptive and suitable for a screen reader. Do not include any introductory phrases like 'Alt text:' or 'An image of'",
                        attachments=attachments,
                    )
                    alt_text = response.text().replace('"', '\\"')
                    updated_line = line.replace('>}}', f'alt="{alt_text}" >}}}}')
                    updated_lines.append(updated_line)
                except Exception as e:
                    print("Exception from OpenAI: " + e)
                    updated_lines.append(line)
            else:
                updated_lines.append(line)
        else:
            updated_lines.append(line)
    content = "\n".join(updated_lines)
    return content

def generate_alt_text_for_all_markdown_files():
    content_directory = "content"
    markdown_files = glob.glob(os.path.join(content_directory, "**/*.md"), recursive=True)

    for file_path in markdown_files:
        with open(file_path, 'r') as f:
            content = f.read()

        updated_content = process_markdown_for_alt_text(content)

        with open(file_path, 'w') as f:
            f.write(updated_content)

if __name__ == "__main__":
    generate_alt_text_for_all_markdown_files()

To run the script, save it to the root of your Hugo site and, if you have uv installed, simply run with ./alt-text.py.


Want great, practical advice on implementing data mesh, data products and data contracts?

In my weekly newsletter I share with you an original post and links to what's new and cool in the world of data mesh, data products, and data contracts.

I also include a little pun, because why not? 😅

(Don’t worry—I hate spam, too, and I’ll NEVER share your email address with anyone!)


Andrew Jones
Author
Andrew Jones
I build data platforms that reduce risk and drive revenue.