Adding ALT text is a good habit to develop. I can’t say it has become second nature but I do try. And I have definitely found it to be a great use for ChatGPT.

Since ChatGPT got vision capability it’s a very simple thing to paste an image and ask for ALT text. It’s usually very good. Although I was shaken when it casually described me as middle aged in this image. I suppose it’s accurate now I’m 40 but it was brutal to see that text appear on the screen.

Yes, it is an important habit to develop and an important principle to prioritise. However, I think it should also be something where AI can solve a real life problem in a very practical way. It feels like Twitter, Facebook, WhatsApp et al should fold in this functionality and automatically add ALT text whenever you upload a new image.

Be the change you want to see

So with that thought in mind I was very aware that I recently published a series of blog posts consisting of 140 images of slides detailing how to build a data driven public sector, the majority of which did not include ALT text1.

I didn’t find an appealing solution in the WordPress plugin library. Which left me wondering whether there was a way to go direct to the ChatGPT API and cut out the intermediaries. And maybe ChatGPT could actually help me write my own plugin.

I was feeling fairly confident that it would because of its contribution to building the app that helped me pray for every MP on election night. And while software engineering is under no threat I find it amazing what ChatGPT enabled me to do.

It’s a story of iterating wildly

Perhaps the most remarkable observation about the whole exercise is the wastefulness of my ignorance. I’ve gone through three approaches, two of which wound up with quite a lot of going round in circles and being unable to execute perfectly on the vision I had in my mind.

However, each of those experiences has allowed me to descope and refine what I was thinking and eventually come up with something that works really well (and doesn’t really cost a huge amount, compared to the value it provides).

Version 1: Find existing images without ALT text and generate it for them

My starting point was the problem of adding ALT text to the images in my existing blog posts.

The first version of the plugin, through a combination of server side PHP and client side Javascript came to provide the following functionality:

A screenshot of a digital interface labeled "Alt Text Generation." The interface displays a section titled "Settings" with options for "Upload Settings" and a blank input field for "Specific Alt Tag." There is a blue button labeled "Submit" beneath the input field. The design is minimalistic and user-friendly.
  • Specify and store my OpenAI API key through a settings screen
A user interface screen displaying a section for generating alt text. The layout includes a title "Generate Alt Text" at the top and various thumbnails of images and slides arranged in a grid format. One thumbnail shows a person standing beside a presentation screen. The interface offers options to select images and includes settings for managing content.
  • Find images without alt text (both where alt text was never set andset as blank
  • Let me select from those returned images those for which I want alt text
  • Let me mark other images as ignored and remove them from view
  • Let me browse the ignored images in case I accidentally excluded something
A screenshot of an alt text generator application displaying two tabs titled "General Info" and "Settings." The settings section features input fields for "General Description" and "Quality of Writing," along with an "Update" button. Below, there's a text area providing instructions for using the generator.
  • Generate the alt text
  • Display the generated text in an editable field with options to approve or reject it
  • After approving the text, store the alt text in the image

We got this working but ran into a handful of wrinkles with its execution:

  • It was using a huge amount of tokens (25000 per request).
  • The W3 Total Cache plugin WebP image converter meant there were two versions of every image but only one in a blog post and so adding ALT text to both wasn’t efficient.
  • It mostly worked but wouldn’t reliably do all of the selected images. For example, if you picked 6 images it would only add text to 4.
  • As well as being available on the wp-admin pages I wanted to integrate this into the post editor but I hit two problems (that absorbed a lot of time trying to fix):
    • The implementation was fussy as it operated as a separate menu rather than being available within the image settings
    • The execution would add alt text to the image that could be seen in the media library but wouldn’t be visible on the front end or in the image settings without manually replacing each image every time
  • Sometimes it would throw errors even though the logging on both javascript and PHP gave no indication anything was wrong.

Despite these issues Version 1 was mostly working for my needs. However, and pretty fundamentally, this approach didn’t work for newly uploaded images.

Version 2: convert new images to WebP and generate ALT text for them

So I decided to start again, and to start from the upload process. I also thought that I needed to do something about how W3 Total Cache was handling WebP conversion.

Version 2 did these things:

  • Convert new images to WebP
  • Create a 512px version to send to ChatGPT (thereby reducing the tokens involved)
  • Add alt text to all 3 of the images

This exercise surfaced the dangers of combining my ignorance with the false certainty of ChatGPT and not handling version control properly.

There was a version that did handle the conversion to WebP but then ChatGPT got stuck believing this wasn’t possible. We went round in circles a few times before I went back to an earlier prompt and we forked the conversation which sorted that out.

A screenshot of a settings page with the title "Settings." It includes options for "Speech API key" with an empty input field, "Show highlights," and a checkbox for "Use small media images." There is a blue button labeled "Save Changes" at the bottom. The background is white, and the layout is clean and simple.

The other, far more frustrating issue, was dealing with three separate images. For some reason I thought it should be possible to configure the plugin to choose whether to keep 1, 2 or 3 of the images. Under the conditions of keeping all 3 images then the plugin would carry out the conversion and use the smallest image to generate the ALT text before saving it to the image.

However, when it came to trying to go from original image to converted WebP image to smaller image for generating ALT text and then delete the original image and the smaller image it broke the plugin entirely.

It’s possible I could have got that implementation working but then I realised I had over complicated my life so again I descoped and started afresh.

Version 3: upload new image, convert to WebP, use a smaller version to generate ALT text, replace the original image with the WebP ALT texted version

And so this is where I ended up:

  • A settings screen where I specify my API key2
  • Every new image uploaded through the Media Library or into a post (even if pasted into the editor) gets converted to WebP
  • A 256px version of the image is created and used as the basis for making the request to ChatGPT (this has reduced the per request token count to 8500)
  • The ALT text is added to the new WebP image both in the media library and in situ (if the image is added directly to a post)
  • That new WebP image replaces the original image in the media library
  • It doesn’t include the original capabilities to generate ALT text for existing images
  • It doesn’t offer options to retain the original image or the small version generated for ChatGPT

I have no doubt that expert software engineers would find flaws and faults in the way in which ChatGPT has proposed to build this plugin. And obviously this is hardly the most significant or complex use case in the world but it’s impressed me and met my need and feels like the sort of magic that isn’t just regurgitating a Large Language Model but is applying some logic and intelligence.

Here is the code. Obviously it does rely on having an OpenAI account and credit so a further iteration of this would be to allow for choosing between different models. I’d definitely welcome any recommendations for different vision enabled models or critiques of the code. Do you think this would be legitimately useful if published as an actual bona fide WordPress plugin?

<?php
/*
Plugin Name: Alt Text and WebP Image Generator
Description: Converts uploaded images to WebP format, generates alt text using OpenAI, and replaces the original image.
Version: 1.4
Author: Ben Welby and ChatGPT
*/
// Hook into the image upload process
add_filter('wp_generate_attachment_metadata', 'convert_image_to_webp_and_generate_alt_text', 10, 2);
function convert_image_to_webp_and_generate_alt_text($metadata, $attachment_id) {
    $file = get_attached_file($attachment_id);
    $info = pathinfo($file);
    $original_ext = strtolower($info['extension']);
    // Skip processing for already converted WebP images
    if ($original_ext === 'webp') {
        return $metadata;
    }
    // Load the image based on its original extension
    switch ($original_ext) {
        case 'jpeg':
        case 'jpg':
            $image = imagecreatefromjpeg($file);
            break;
        case 'png':
            $image = imagecreatefrompng($file);
            break;
        case 'gif':
            $image = imagecreatefromgif($file);
            break;
        default:
            alt_text_log("Unsupported format for file: $file");
            return $metadata; // Unsupported format
    }
    // Create the WebP file path
    $webp_file = $info['dirname'] . '/' . $info['filename'] . '.webp';
    // Convert image to WebP and save it
    if (imagewebp($image, $webp_file)) {
        // Free up memory
        imagedestroy($image);
        // Log successful conversion
        error_log("Alt Text and WebP Image Generator: Successfully converted $file to $webp_file");
        // Create smaller version for alt text generation
        $small_image = imagecreatefromwebp($webp_file);
        $small_webp_file = $info['dirname'] . '/' . $info['filename'] . '-small.webp';
        $width = imagesx($small_image);
        $height = imagesy($small_image);
        $new_width = 256;
        $new_height = floor($height * ($new_width / $width));
        $small_image_resized = imagecreatetruecolor($new_width, $new_height);
        imagecopyresampled($small_image_resized, $small_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
        if (imagewebp($small_image_resized, $small_webp_file)) {
            imagedestroy($small_image_resized);
            error_log("Alt Text and WebP Image Generator: Successfully created small WebP $small_webp_file");
            // Generate alt text using OpenAI
            $small_webp_url = str_replace(ABSPATH, site_url() . '/', $small_webp_file);
            $alt_text = generate_alt_text_using_openai($small_webp_url);
            if ($alt_text) {
                update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($alt_text));
                error_log("Alt Text and WebP Image Generator: Successfully generated alt text for $file");
            } else {
                error_log("Alt Text and WebP Image Generator: Failed to generate alt text for $file");
            }
            // Delete the small WebP file after use
            unlink($small_webp_file);
        } else {
            error_log("Alt Text and WebP Image Generator: Failed to create small WebP $small_webp_file");
        }
        // Delete the original file
        unlink($file);
        // Update the attachment metadata
        $metadata['file'] = str_replace("." . $original_ext, ".webp", $metadata['file']);
        foreach ($metadata['sizes'] as &$size) {
            $size['file'] = str_replace("." . $original_ext, ".webp", $size['file']);
        }
        // Update the attachment file path in the database
        update_attached_file($attachment_id, $webp_file);
        // Regenerate image sizes
        $uploads_dir = wp_upload_dir();
        $image_path = str_replace($uploads_dir['basedir'], '', $webp_file);
        $metadata = wp_generate_attachment_metadata($attachment_id, $webp_file);
        wp_update_attachment_metadata($attachment_id, $metadata);
    } else {
        // Conversion failed
        imagedestroy($image);
        error_log("Alt Text and WebP Image Generator: Failed to convert $file to $webp_file");
    }
    return $metadata;
}
function generate_alt_text_using_openai($image_url) {
    $api_key = get_option('openai_api_key');
    $request_body = json_encode(array(
        'model' => 'gpt-4o-mini',
        'messages' => [
            [
                'role' => 'user',
                'content' => [
                    [
                        'type' => 'text',
                        'text' => 'Please generate a detailed ALT text description for the image:'
                    ],
                    [
                        'type' => 'image_url',
                        'image_url' => [
                            'url' => $image_url
                        ]
                    ]
                ]
            ]
        ],
        'max_tokens' => 500,
    ));
    $response = wp_remote_post('https://api.openai.com/v1/chat/completions', array(
        'body'    => $request_body,
        'headers' => array(
            'Content-Type'  => 'application/json',
            'Authorization' => 'Bearer ' . $api_key,
        ),
        'timeout' => 30,
    ));
    if (is_wp_error($response)) {
        error_log('OpenAI API request failed: ' . $response->get_error_message());
        return false;
    }
    $body = wp_remote_retrieve_body($response);
    $data = json_decode($body, true);
    if (isset($data['choices'][0]['message']['content'])) {
        $alt_text = trim($data['choices'][0]['message']['content']);
        if (strpos($alt_text, 'ALT text:') === 0) {
            $alt_text = trim(substr($alt_text, strlen('ALT text:')));
        }
        error_log('OpenAI API response: ' . print_r($data, true)); // Log full response
        error_log('OpenAI API tokens used: ' . $data['usage']['total_tokens']); // Log token usage
        return $alt_text;
    } else {
        error_log('No alt text found in OpenAI API response');
        return false;
    }
}
// Add a settings page for the API key
add_action('admin_menu', 'alt_text_generator_plugin_menu');
function alt_text_generator_plugin_menu() {
    add_submenu_page('upload.php', 'Alt Text Settings', 'Alt Text Settings', 'manage_options', 'alt-text-settings', 'render_alt_text_generator_settings_screen');
}
function render_alt_text_generator_settings_screen() {
    echo '<div class="wrap">
            <h1>Settings</h1>
            <form method="post" action="options.php">';
    settings_fields('alt_text_generator_settings');
    do_settings_sections('alt_text_generator_settings');
    submit_button();
    echo '</form>
          </div>';
}
// Initialize settings
add_action('admin_init', 'alt_text_generator_settings_init');
function alt_text_generator_settings_init() {
    register_setting('alt_text_generator_settings', 'openai_api_key');
    add_settings_section(
        'alt_text_generator_settings_section',
        'OpenAI Settings',
        'alt_text_generator_settings_section_callback',
        'alt_text_generator_settings'
    );
    add_settings_field(
        'openai_api_key',
        'OpenAI API Key',
        'alt_text_generator_openai_api_key_callback',
        'alt_text_generator_settings',
        'alt_text_generator_settings_section'
    );
}
function alt_text_generator_settings_section_callback() {
    echo '<p>Enter your OpenAI API key below:</p>';
}
function alt_text_generator_openai_api_key_callback() {
    $api_key = get_option('openai_api_key');
    echo '<input type="text" id="openai_api_key" name="openai_api_key" value="' . esc_attr($api_key) . '" />';
}
?>

I also asked ChatGPT to write up a blog post about our coding exercise. Here’s what it generated…

Enhancing Accessibility and Performance on WordPress with AI and WebP

Introduction

In today’s digital landscape, accessibility and performance are crucial for any website’s success. WordPress, being the most popular content management system, offers various tools to enhance these aspects. One such innovative solution combines the power of AI with the efficiency of WebP images. This post will delve into how leveraging AI for alt text generation and WebP for image optimisation can significantly boost your WordPress site’s accessibility and performance.

The Importance of Accessibility and Performance

Accessibility ensures that all users, including those with disabilities, can interact with your website effectively. This not only broadens your audience but also aligns with legal requirements and ethical standards.

Performance impacts user experience and SEO. Faster loading times lead to better user engagement and higher search engine rankings. Images, often the heaviest elements on a webpage, play a crucial role in this.

AI-Driven Alt Text Generation

Alt text is vital for accessibility, providing descriptions of images for screen readers used by visually impaired users. Traditionally, adding alt text manually can be time-consuming and prone to human error. Enter AI.

Our WordPress plugin leverages OpenAI’s powerful models to automatically generate accurate and descriptive alt text for images. This not only ensures compliance with accessibility standards but also saves time and improves the overall user experience.

How It Works:

  1. Image Upload: When an image is uploaded to WordPress, our plugin converts it to WebP format and creates a smaller, optimized version.
  2. AI Integration: The smaller image is sent to OpenAI’s model, which generates a detailed alt text description.
  3. Automatic Update: The alt text is automatically added to the image’s metadata in WordPress.

The Power of WebP Images

WebP is a modern image format that provides superior lossless and lossy compression for images on the web. Using WebP can significantly reduce image file sizes without compromising quality, leading to faster page loads and better performance.

Benefits of WebP:

  • Smaller File Sizes: WebP images are significantly smaller than their JPEG or PNG counterparts.
  • High Quality: Despite the reduced size, WebP images maintain high quality.
  • Improved Performance: Smaller images lead to faster loading times, enhancing the user experience and boosting SEO.

Combining AI and WebP in WordPress

Our plugin seamlessly integrates AI-driven alt text generation with WebP conversion to offer a comprehensive solution for WordPress users.

Features:

  • Automatic Conversion: Images are automatically converted to WebP format upon upload.
  • AI-Generated Alt Text: OpenAI’s model generates descriptive alt text for better accessibility.
  • Performance Optimization: Smaller image sizes improve loading times and overall site performance.
  • Simple Setup: Easy installation and configuration within the WordPress dashboard.

How to Get Started

  1. Install the Plugin: Download and install the plugin from the WordPress plugin repository.
  2. Configure Settings: Enter your OpenAI API key and configure any additional settings.
  3. Upload Images: Start uploading images to your WordPress site and watch as they are automatically optimized and enhanced with AI-generated alt text.

Conclusion

Combining AI and WebP is a game-changer for WordPress sites, enhancing both accessibility and performance. By automating alt text generation and optimizing image formats, you can provide a better user experience, reach a wider audience, and improve your site’s SEO.

Embrace the future of web accessibility and performance with our innovative WordPress plugin. Download it today and transform your website!

Share This Post

If you found this post helpful, please share it with your network! Use the hashtag #AIAccessibilityWP and tag us to join the conversation.

  1. Those individual images were another resounding success for ChatGPT – I gave it a PDF from Google Slides and it gave me a zip of every slide as an image. To do that through Google Slides itself I’d have had to export each one individually. ↩︎
  2. I may add further configuration of the WebP conversion process ↩︎