Created
December 16, 2025 09:42
-
-
Save bguiz/d0a6c30966351d111af106f0a553d752 to your computer and use it in GitHub Desktop.
Generate images using Nano Banana using an OpenRouter API key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| import fs from 'node:fs/promises'; | |
| // extract inputs | |
| const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY; | |
| const IMAGE_NAME = process.env.IMAGE_NAME; | |
| const AI_MODEL = process.env.AI_MODEL || 'google/gemini-3-pro-image-preview'; | |
| const ASPECT_RATIO = process.env.ASPECT_RATIO || '1:1'; | |
| const IMAGE_SIZE = process.env.IMAGE_SIZE || '1K'; | |
| // read prompt from text file | |
| const inputFileName = `./${IMAGE_NAME}.txt`; | |
| const IMAGE_PROMPT = (await fs.readFile(inputFileName)).toString(); | |
| // don't allow script to run in output file exists | |
| const outputFileName = `./${IMAGE_NAME}.json`; | |
| const outputFileAlreadyExists = await fileExists(outputFileName); | |
| if (outputFileAlreadyExists) { | |
| console.error(`output file exists, overwrite not allowed: ${outputFileName}`); | |
| process.exit(-1); | |
| } | |
| // make the API request to nano banana | |
| const requestBody = { | |
| model: AI_MODEL, | |
| messages: [{ role: 'user', content: IMAGE_PROMPT }], | |
| modalities: ['image', 'text'], | |
| image_config: { | |
| aspect_ratio: ASPECT_RATIO, | |
| image_size: IMAGE_SIZE, | |
| } | |
| }; | |
| console.log(requestBody); | |
| const response = await fetch( | |
| 'https://openrouter.ai/api/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| Authorization: `Bearer ${OPENROUTER_API_KEY}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(requestBody), | |
| }); | |
| // write the raw LLM response into a JSON file | |
| const result = await response.json(); | |
| await fs.writeFile(outputFileName, JSON.stringify(result, undefined, 2)); | |
| console.log(`wrote raw response: ${outputFileName}`); | |
| // parse the raw LLM response and create individual image files from each one | |
| const responseImages = result?.choices[0].message?.images; | |
| const writeImagePromises = responseImages.map((image, index) => { | |
| const imageBase64Url = image.image_url.url; | |
| const { fileType, fileBuffer } = parseImageBase64Url(imageBase64Url); | |
| const outputImageFileName = `${IMAGE_NAME}--${index + 1}.${fileType}`; | |
| console.log(`writing image file: ${outputImageFileName}`); | |
| return fs.writeFile(outputImageFileName, fileBuffer); | |
| }); | |
| await Promise.all(writeImagePromises); // parallel | |
| console.log('wrote all image files'); | |
| // utility function to convert base64 URIs to image data ready to be written to disk | |
| function parseImageBase64Url(base64Url) { | |
| const matches = base64Url.match(/^data:image\/(\w+);base64,(.+)$/); | |
| if (!matches || matches.length !== 3) { | |
| throw new Error('invalid base64 image URL'); | |
| } | |
| const [, imageType, base64Data] = matches; | |
| return { | |
| fileType: imageType, | |
| fileBuffer: Buffer.from(base64Data, 'base64') | |
| }; | |
| } | |
| // utility function to check if file exists | |
| async function fileExists(filePath) { | |
| try { | |
| await fs.access(filePath, fs.constants.F_OK); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment