Blog

Use Dall-E and ChatGPT APIs with Google Apps Script to convert text to images automatically

today March 29, 2023

Since AI is all the rage now, I wanted to explore how to use the OpenAI Dall-E service to create images automatically. How about creating images for my blog posts? I could use the posts' summaries as prompts for ChatGPT, asking the service to produce a list of physical objects that could represent the contents of the summaries. Then I'll feed those objects to Dall-E, asking it to create an image that contains those objects. Once Dall-E provides the image URLs, I'll have the automation download the files to my Google Drive. Let's see how to do this with Google Apps Script.

Side note: I fed the above summary to my automation and got back the image below. I guess ChatGPT really honed in on the "exploring" part.

Dall-E example

Setup

For this automation you'll need a Google Sheet with one tab (I called mine "summaries") that has four columns: Summary, Keywords, Dall-E URL, and GDrive Url. Place one or more summaries in the first column, one summary per row. You'll also need a Google Drive folder where the automation will download and store the Dall-E image files.

Finally, you'll need to obtain an OpenAI API key and store it in your Google Sheet's Apps Script properties. Please see this post if you don't know what I'm talking about or need help with that.

Here are the tasks we need to accomplish here:

  • Create a custom menu to run the automation
  • Create a generic OpenAI API function
  • Iterate through the sheet
  • Generate keywords for each summary
  • Get the Dall-E image URL
  • Save the image to Google Drive

Create a custom menu to run the automation

Let's create a custom menu in Google Sheet that will kick-off the automation. Pretty straightforward:

const g = {
  imageSize: '512x512',
  imageCount: 1,
  outputFolderId: 'paste-the-id-of-your-GDrive-image-folder',
};

function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('🟢 Dall-E').addItem('Create images', 'createImages')
    .addToUi();
}

Above, we define a global object 'g' that we will use throughout the automation. Paste the ID of your Google Drive image folder it. The ID is the sequence of characters at the end of the folder URL. Google Apps Script will run the onOpen function automatically whenever the user opens the Google Sheet. createImages is a function that we will add shortly to run the automation.

Create a generic OpenAI API function

Since our automation will call both ChatGPT and the Dall-E endpoints, we can define a generic function to call the OpenAI services, which we will then use to generate the keywords and the Dall-E images:

function callOpenAI_(apiUrl, payload) {
  const options = {
    method: 'post',
    headers: {
      Authorization: `Bearer ${g.apiKey}`,
      'Content-Type': 'application/json',
    },
    muteHttpExceptions: true,
    payload,
  };
  const response = UrlFetchApp.fetch(apiUrl, options);
  const content = response.getContentText();
  const jsn = JSON.parse(content);
  if (jsn.error) {
    return `Oops, OpenAI API error: ${jsn.error.message}`;
  }
  return jsn;
}

Above we define a private function (note the underscore at the end of the function name) that will receive the API endpoint we want to use, and the payload we want to post to the endpoint. Note that you need to pass your OpenAI API key in the Authorization header, and the function expects it to be in the 'g' object. We'll take care of that momentarily.

The function then calls OpenAI and inspects the response. If the response object contains an error then it logs it to the console for debugging. Otherwise, it returns the object to the function that called it.

Iterate through the sheet

OK, time to write createImages. The idea here is to go row by row of our sheet, pull each summary, check if keywords exists, and if not then generate them. Then do the same with the Dall-E URL and the GDrive URL:

function createImages() {
  const scriptProps = PropertiesService.getScriptProperties();
  g.apiKey = scriptProps.getProperty('openAIKey');
  if (!g.apiKey) {
    throw new Error(`OpenAI API key script property is missing.`);
  }
  g.outputFolder = DriveApp.getFolderById(g.outputFolderId);
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('summaries');
  const data = sh.getDataRange().getValues();
  data.splice(0, 1);
  for (let i = 0; i < data.length; i++) {
    let [summary, keywords, dalleUrl, driveUrl] = data[i];
    if (!summary) {
      continue;
    }
    if (!keywords) {
      keywords = genKeywords_(summary);
      sh.getRange(`B${i + 2}`).setValue(keywords);
      dalleUrl = null;
    }
    if (keywords && !dalleUrl) {
      dalleUrl = genDalleImage_(keywords);
      sh.getRange(`C${i + 2}`).setValue(dalleUrl);
      driveUrl = null;
    }
    if (dalleUrl && !driveUrl) {
      driveUrl = downloadDalleImage_(dalleUrl, keywords);
      sh.getRange(`D${i + 2}`).setValue(driveUrl);
    }
  }
  SpreadsheetApp.flush();
}

Above, we get the OpenAI that we manually stored in Script Properties. We then get the folder and store a pointer to it in our 'g' object. We pull the data out of the sheet and splice out the header row. We iterate through the rows: if there's no summary in the row then we continue to the next row. We then check if we don't have keywords populated. If not then we generate them and store in the sheet. Same with last two columns. When we're done, we flush the changes in the sheet.

Generate keywords for each summary

Here's our generateKeywords_ function that takes the summary and feeds it into ChatGPT, sending back the keywords to be added to the sheet:

function genKeywords_(description) {
  const apiUrl = 'https://api.openai.com/v1/chat/completions';
  const content =
    'Generate a list of up to five, comma-separated ' +
    'everyday items that do not involve computers or software and ' +
    'that can figuratively represent the description below. Simply ' +
    "list the items, and don't specify what they represent. " +
    "Here's the description: " +
    description;
  const payload = JSON.stringify({
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'assistant', content }],
  });
  const jsn = callOpenAI_(apiUrl, payload);
  if (jsn.choices && jsn.choices.length > 0) {
    return jsn.choices[0].message.content;
  } else {
    console.log(jsn);
  }
}

Above we note the endpoint URL we want to reach and the prompt. You could probably do a better job to convert the description to a list of keywords. Anyway, we use the gpt-3.5-turbo model in 'assistant' role to process the content, and call our generic OpenAI function with it. We return the content that ChatGPT generates.

Get the Dall-E image URL

The Dall-E function is very similar to the ChatGPT one. We set the endpoint URL, the prompt, and the number of images we want to receive back (1) and the size of the image (512x512 pixels). We pass the data to our generic function and get back an image URL that Dall-E is generously hosting for us:

function genDalleImage_(keywords) {
  const apiUrl = 'https://api.openai.com/v1/images/generations';
  const prompt = 'Generate a photo of a person holding or wearing ' + 
          keywords;
  const payload = JSON.stringify({
    prompt,
    n: g.imageCount,
    size: g.imageSize,
  });
  const jsn = callOpenAI_(apiUrl, payload);
  if (jsn.data && jsn.data.length > 0) {
    return jsn.data[0].url;
  } else {
    console.log(jsn);
  }
}

Save the image to Google Drive

Our last task is to fetch the Dall-E image and store it in our GDrive folder:

function downloadDalleImage_(dalleUrl, keywords) {
  const response = UrlFetchApp.fetch(dalleUrl);
  const blob = response.getBlob();
  const file = g.outputFolder.createFile(blob);
  file.setName(`${keywords}.png`);
  return file.getUrl();
}

The simple function above fetches the image resource from the Dall-E storage. It gets the blob of the resource and stores it in a file. It names the file using the keywords so we know which file is which. Lastly it returns the GDrive file URL so that we can save it in the sheet.

That's pretty much it. If you run the automation then it should populate the last three columns. You can use the links to get to the images or simply visit the folder and find the files there.

Happy Dall-E-ing!