Skip to content

Instantly share code, notes, and snippets.

@shameen
Last active January 11, 2023 09:29
Show Gist options
  • Save shameen/18fdc4246f11df09e6039737f275fcf0 to your computer and use it in GitHub Desktop.
Save shameen/18fdc4246f11df09e6039737f275fcf0 to your computer and use it in GitHub Desktop.
png: add iTXt chunk metadata (javascript / node.js / typescript)
import fs from "node:fs";
import extract from "png-chunks-extract";
import encode from "png-chunks-encode";
/** Using png-chunks-extract + png-chunks-encode */
function add_itxt_pngchunk(
fileBytes: Uint8Array | Buffer,
value: string,
keyword: string
) {
const chunks = extract(fileBytes);
const nul = String.fromCharCode(0x00);
/* iTXt chunk as defined by PNG (http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt)
Keyword: 1-79 bytes (character string)
Null separator: 1 byte (0x00)
Compression flag: 1 byte (0x00 off, 0x01 on)
Compression method: 1 byte (0x00 = deflate)
Language tag: 0 or more bytes (character string)
Null separator: 1 byte (0x00)
Translated keyword: 0 or more bytes
Null separator: 1 byte (0x00)
Text: 0 or more bytes
*/
const iTXtValue = `${keyword}${nul}${nul}${nul}${nul}${nul}${value}`;
const chunkNew = {
name: "iTXt",
data: new TextEncoder().encode(iTXtValue),
};
//add chunk as the second-last item (before the "IEND")
chunks.splice(-1, 0, chunkNew);
// join
const pngNew = Buffer.from(encode(chunks));
return pngNew;
}
const simplePngBytes = Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02,
0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x10, 0x49, 0x44,
0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x03, 0x01, 0x01,
0x00, 0x18, 0xdd, 0x8d, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
0xae, 0x42, 0x60, 0x82,
]);
const pngNew = add_itxt_pngchunk(
simplePngBytes,
JSON.stringify({ score: 100 }),
"myKeyword"
);
// save to file
fs.writeFileSync("output3.png", pngNew, "binary");
//The resulting PNG file can be verified using https://www.dcode.fr/png-chunks
import { Stream, Readable } from 'node:stream';
import fs from 'node:fs';
import pngitxt from "png-itxt";
/** Using png-itxt */
async function add_itxt_pngitxt(
inputStream: Stream,
value: string,
keyword: string
): Promise<Stream> {
return inputStream.pipe(
pngitxt.set({
keyword,
compressed: false,
//language: '',
//translated: '',
value,
})
);
}
const simplePngBytes = Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02,
0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x10, 0x49, 0x44,
0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x03, 0x01, 0x01,
0x00, 0x18, 0xdd, 0x8d, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
0xae, 0x42, 0x60, 0x82,
]);
//Convert Buffer to Stream
const inputStream = new Readable({
read() {
this.push(simplePngBytes);
this.push(null);
},
});
const pngNewStream = await add_itxt_pngitxt(
inputStream,
JSON.stringify({ score: 100 }),
"myKeyword"
);
// save to file
pngNewStream.pipe(fs.createWriteStream("output.png"));
//The resulting PNG file can be verified using https://www.dcode.fr/png-chunks
import pngMetadata from "png-metadata";
import fs from "node:fs";
/** Using png-metadata */
function add_itxt_pngmetadata(
fileBytes: Uint8Array | Buffer,
value: string,
keyword: string
): string {
// split
const list = pngMetadata.splitChunk(
Buffer.from(fileBytes).toString("binary")
);
console.debug("metadata list: ", list);
// append metadata
const nul = String.fromCharCode(0x00);
/* iTXt chunk as defined by PNG (http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iTXt)
Keyword: 1-79 bytes (character string)
Null separator: 1 byte (0x00)
Compression flag: 1 byte (0x00 off, 0x01 on)
Compression method: 1 byte (0x00 = deflate)
Language tag: 0 or more bytes (character string)
Null separator: 1 byte (0x00)
Translated keyword: 0 or more bytes
Null separator: 1 byte (0x00)
Text: 0 or more bytes
*/
const chunkNew = pngMetadata.createChunk(
"iTXt",
`${keyword}${nul}${nul}${nul}${nul}${nul}${value}`
);
list.splice(-1, 0, chunkNew);
// join
return pngMetadata.joinChunk(list);
}
const simplePngBytes = Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02,
0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x10, 0x49, 0x44,
0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x03, 0x01, 0x01,
0x00, 0x18, 0xdd, 0x8d, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
0xae, 0x42, 0x60, 0x82,
]);
const pngNew = add_itxt_pngmetadata(
simplePngBytes,
JSON.stringify({ score: 100 }),
"myKeyword"
);
//save to file
const filePath = "output2.png";
fs.writeFileSync(filePath, pngNew, "binary");
//check (Can also be verified using https://www.dcode.fr/png-chunks )
var s = pngMetadata.readFileSync(filePath);
const chunks = pngMetadata.splitChunk(s);
console.log("metadata chunks: ", chunks);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment