Automating Blog Post Headers with Firefly Services

Yesterday I introduced you to Adobe’s new offering, Firefly Services, and demonstrated a simple example of how to generate images from prompt using the REST APIs. Today I thought I’d share one of the little demos I’ve made with the API, and one specifically built to help out with my blog – generating headers.

My usual process for headers is to go to the Firefly website, enter a prompt, let it load, and then promptly change it to landscape and re-generate my prompt again. I always feel bad that the initial, square, images are essentially trashed. It occurred to me I could build a Node.js utility to generate the images at the exact right size and even quickly display them. Here’s how I did it.

First, I designed the CLI so I can simply pass in a prompt. Here’s how I handled that:

if(process.argv.length < 3) {	console.log(styleText('red', 'Usage: makeheader.js <<prompt>>'));	process.exit(1);} const prompt = process.argv[2];console.log(styleText('green', `Generating headers for: $ {prompt}`));

Next, I authenticate, and create my images:

let token = await getFFAccessToken(FF_CLIENT_ID, FF_CLIENT_SECRET);let result = await textToImage(prompt, FF_CLIENT_ID, token);

I showed both of these methods yesterday, but my parameters for the Firefly API to generate images are slightly tweaked though. First, the authentication method again:

async function getFFAccessToken(id, secret) {	const params = new URLSearchParams();	params.append('grant_type', 'client_credentials');	params.append('client_id', id);	params.append('client_secret', secret);	params.append('scope', 'openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis');		let resp = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', 		{ 			method: 'POST', 			body: params		}	);	let data = await resp.json();	return data.access_token;}

And here’s the call to the text to image API:

async function textToImage(text, id, token) {	let body = {		"n":4,		"prompt":text,		"size":{			"width":"2304",			"height":"1792"		}	}	let req = await fetch('https://firefly-api.adobe.io/v2/images/generate', {		method:'POST',		headers: {			'X-Api-Key':id, 			'Authorization':`Bearer $ {token}`,			'Content-Type':'application/json'		}, 		body: JSON.stringify(body)	});	let resp = await req.json();	return resp;}

Note two things here:

  • First, I set n to 4 so I get 4 results, not the default of 1.
  • My size is hard coded to the landscape size.

Ok, so that’s the easy bit honestly. But I wanted to do something cool with the results. There is a really useful npm package called open that will open URLs and files. The result of the Firefly API call above will include 4 URLs and I could have simply opened all four of them in individual browser tabs, but I wanted one page where I could see them all, much like the Firefly website. While not directly supported by open yet, I got around it by generating a temporary HTML file locally:

let html = `<style>img {	max-width: 650px;}.results {	display: grid;	grid-template-columns: repeat(2, 50%);}</style><h2>Results for Prompt: $ {prompt}</h2><div class="results">`;result.outputs.forEach(i => {	html += `<p><img src="$ {i.image.presignedUrl}"></p>`;});html += '</div>';let filename = `$ {uuid4()}.html`;fs.writeFileSync(filename, html, 'utf8');await open(filename, {	wait: true});fs.unlinkSync(filename);

So now what happens is, I run my prompt, and when it’s done, I get an HTML page. Here’s the result of using:

node makeheader "a somber, moody picture of a cat in painters clothes, standing before an easel, thinking about what to paint next"

Sample output.

And yes, I used the fourth image for this post. Here’s the complete script, but you can also find it in my Firefly API repo: https://github.com/cfjedimaster/fireflyapi/tree/main/demos/makeheader

// Requires Node 21.7.0process.loadEnvFile();import { styleText } from 'node:util';import { v4 as uuid4 } from 'uuid';import open from 'open';import fs from 'fs';const FF_CLIENT_ID = process.env.FF_CLIENT_ID;const FF_CLIENT_SECRET = process.env.FF_CLIENT_SECRET;async function getFFAccessToken(id, secret) {	const params = new URLSearchParams();	params.append('grant_type', 'client_credentials');	params.append('client_id', id);	params.append('client_secret', secret);	params.append('scope', 'openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis');		let resp = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', 		{ 			method: 'POST', 			body: params		}	);	let data = await resp.json();	return data.access_token;}async function textToImage(text, id, token) {	let body = {		"n":4,		"prompt":text,		"size":{			"width":"2304",			"height":"1792"		}	}	let req = await fetch('https://firefly-api.adobe.io/v2/images/generate', {		method:'POST',		headers: {			'X-Api-Key':id, 			'Authorization':`Bearer $ {token}`,			'Content-Type':'application/json'		}, 		body: JSON.stringify(body)	});	let resp = await req.json();	return resp;}if(process.argv.length < 3) {	console.log(styleText('red', 'Usage: makeheader.js <<prompt>>'));	process.exit(1);} const prompt = process.argv[2];console.log(styleText('green', `Generating headers for: $ {prompt}`));let token = await getFFAccessToken(FF_CLIENT_ID, FF_CLIENT_SECRET);let result = await textToImage(prompt, FF_CLIENT_ID, token);console.log(styleText('green', 'Results generated - creating preview...'));let html = `<style>img {	max-width: 650px;}.results {	display: grid;	grid-template-columns: repeat(2, 50%);}</style><h2>Results for Prompt: $ {prompt}</h2><div class="results">`;result.outputs.forEach(i => {	html += `<p><img src="$ {i.image.presignedUrl}"></p>`;});html += '</div>';let filename = `$ {uuid4()}.html`;fs.writeFileSync(filename, html, 'utf8');await open(filename, {	wait: true});fs.unlinkSync(filename);

Raymond Camden

Posted in: JavaScript

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.