mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-22 23:18:27 +01:00
89 lines
2.7 KiB
JavaScript
89 lines
2.7 KiB
JavaScript
const fs = require('fs');
|
|
|
|
const encode = require('png-chunks-encode');
|
|
const extract = require('png-chunks-extract');
|
|
const PNGtext = require('png-chunk-text');
|
|
|
|
/**
|
|
* Writes Character metadata to a PNG image buffer.
|
|
* Writes only 'chara', 'ccv3' is not supported and removed not to create a mismatch.
|
|
* @param {Buffer} image PNG image buffer
|
|
* @param {string} data Character data to write
|
|
* @returns {Buffer} PNG image buffer with metadata
|
|
*/
|
|
const write = (image, data) => {
|
|
const chunks = extract(image);
|
|
const tEXtChunks = chunks.filter(chunk => chunk.name === 'tEXt');
|
|
|
|
// Remove existing tEXt chunks
|
|
for (const tEXtChunk of tEXtChunks) {
|
|
const data = PNGtext.decode(tEXtChunk.data);
|
|
if (data.keyword.toLowerCase() === 'chara' || data.keyword.toLowerCase() === 'ccv3') {
|
|
chunks.splice(chunks.indexOf(tEXtChunk), 1);
|
|
}
|
|
}
|
|
|
|
// Add new chunks before the IEND chunk
|
|
const base64EncodedData = Buffer.from(data, 'utf8').toString('base64');
|
|
chunks.splice(-1, 0, PNGtext.encode('chara', base64EncodedData));
|
|
const newBuffer = Buffer.from(encode(chunks));
|
|
return newBuffer;
|
|
};
|
|
|
|
/**
|
|
* Reads Character metadata from a PNG image buffer.
|
|
* Supports both V2 (chara) and V3 (ccv3). V3 (ccv3) takes precedence.
|
|
* @param {Buffer} image PNG image buffer
|
|
* @returns {string} Character data
|
|
*/
|
|
const read = (image) => {
|
|
const chunks = extract(image);
|
|
|
|
const textChunks = chunks.filter((chunk) => chunk.name === 'tEXt').map((chunk) => PNGtext.decode(chunk.data));
|
|
|
|
if (textChunks.length === 0) {
|
|
console.error('PNG metadata does not contain any text chunks.');
|
|
throw new Error('No PNG metadata.');
|
|
}
|
|
|
|
const ccv3Index = textChunks.findIndex((chunk) => chunk.keyword.toLowerCase() === 'ccv3');
|
|
|
|
if (ccv3Index > -1) {
|
|
return Buffer.from(textChunks[ccv3Index].text, 'base64').toString('utf8');
|
|
}
|
|
|
|
const charaIndex = textChunks.findIndex((chunk) => chunk.keyword.toLowerCase() === 'chara');
|
|
|
|
if (charaIndex > -1) {
|
|
return Buffer.from(textChunks[charaIndex].text, 'base64').toString('utf8');
|
|
}
|
|
|
|
console.error('PNG metadata does not contain any character data.');
|
|
throw new Error('No PNG metadata.');
|
|
};
|
|
|
|
/**
|
|
* Parses a card image and returns the character metadata.
|
|
* @param {string} cardUrl Path to the card image
|
|
* @param {string} format File format
|
|
* @returns {string} Character data
|
|
*/
|
|
const parse = (cardUrl, format) => {
|
|
let fileFormat = format === undefined ? 'png' : format;
|
|
|
|
switch (fileFormat) {
|
|
case 'png': {
|
|
const buffer = fs.readFileSync(cardUrl);
|
|
return read(buffer);
|
|
}
|
|
}
|
|
|
|
throw new Error('Unsupported format');
|
|
};
|
|
|
|
module.exports = {
|
|
parse,
|
|
write,
|
|
read,
|
|
};
|