Skip to content

Instantly share code, notes, and snippets.

@shinshin86
Last active May 14, 2023 12:25
Show Gist options
  • Save shinshin86/b0313c37e4c13ef97e0c4ac12c547427 to your computer and use it in GitHub Desktop.
Save shinshin86/b0313c37e4c13ef97e0c4ac12c547427 to your computer and use it in GitHub Desktop.
Sample Node.js to read metadata of PNG files created with Stable Diffusion web UI.
/**
* Sample Node.js to read metadata of PNG files created with Stable Diffusion Web UI
*
* Code from the following page is used as a reference
* URL: https://qiita.com/javacommons/items/472e85be1b11098172b3
*/
const fs = require('fs').promises;
async function getPngInfo(fileName) {
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const crcSize = 4;
let result = "";
const buffer = await fs.readFile(fileName);
const realSig = buffer.slice(0, 8);
if (!realSig.equals(signature)) {
return "";
}
let position = 8;
while (position < buffer.length) {
const length = buffer.readUInt32BE(position);
position += 4;
const chunkType = buffer.slice(position, position + 4).toString();
position += 4;
if (chunkType === 'tEXt' || chunkType === 'iTXt') {
const s = buffer.slice(position, position + length);
position += length;
if (s.slice(0, 10).toString() === 'parameters') {
let sRest = s.slice(10);
while (sRest.length > 0 && sRest[0] === 0) {
sRest = sRest.slice(1);
}
result = chunkType === 'tEXt' ? sRest.toString('latin1') : sRest.toString('utf8');
break;
}
} else {
position += length;
}
position += crcSize;
}
return result;
}
async function getPngInfoJson(fileName) {
const infoString = await getPngInfo(fileName);
const lines = infoString.split("\n");
let phase = 0;
let prompt = "";
let negativePrompt = "";
const data = {};
for (const line of lines) {
const re = /([A-Z][a-zA-Z0-9 ]*: [^,]+)(, )?/g;
if (phase === 0) {
if (line.startsWith("Negative prompt: ")) {
negativePrompt = line.slice(17);
phase = 1;
continue;
}
const m = re.exec(line);
if (m) {
phase = 2;
} else {
prompt += "\n" + line;
continue;
}
}
if (phase === 1) {
const m = re.exec(line);
if (!m) {
negativePrompt += "\n" + line;
continue;
}
phase = 2;
}
if (phase === 2) {
let match1;
while ((match1 = re.exec(line)) !== null) {
const [_, word] = match1;
const rx1 = /([A-Z][a-zA-Z0-9 ]*): ([^,]+)(, )?/g;
let match2;
while ((match2 = rx1.exec(word)) !== null) {
const [_, name, value] = match2;
data[name] = value;
}
}
}
}
data["Prompt"] = prompt.trim();
data["Negative prompt"] = negativePrompt.trim();
return JSON.stringify(data);
}
(async () => {
const args = process.argv.slice(2);
if (args.length !== 1) {
console.error("Usage: node app.js <filename>");
process.exit(1);
}
const fileName = args[0];
try {
const result = await getPngInfo(fileName);
console.log('===PNG INFO===')
console.log(result);
const json = await getPngInfoJson(fileName);
console.log('===PNG INFO (JSON)===')
console.log(json);
} catch (err) {
console.error(`Error reading file ${fileName}: ${err.message}`);
process.exit(1);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment