SillyTavern/src/character-card-parser.js

78 lines
2.5 KiB
JavaScript

const fs = require('fs');
const json5 = require('json5');
const ExifReader = require('exifreader');
const extract = require('png-chunks-extract');
const PNGtext = require('png-chunk-text');
const utf8Decode = new TextDecoder('utf-8', { ignoreBOM: true });
const parse = async (cardUrl, format) => {
let fileFormat;
if (format === undefined) {
if (cardUrl.indexOf('.webp') !== -1)
fileFormat = 'webp';
else
fileFormat = 'png';
}
else
fileFormat = format;
switch (fileFormat) {
case 'webp':
try {
const exif_data = await ExifReader.load(fs.readFileSync(cardUrl));
let char_data;
if (exif_data['UserComment']['description']) {
let description = exif_data['UserComment']['description'];
if (description === 'Undefined' && exif_data['UserComment'].value && exif_data['UserComment'].value.length === 1) {
description = exif_data['UserComment'].value[0];
}
try {
json5.parse(description);
char_data = description;
} catch {
const byteArr = description.split(",").map(Number);
const uint8Array = new Uint8Array(byteArr);
const char_data_string = utf8Decode.decode(uint8Array);
char_data = char_data_string;
}
}
else {
console.log('No description found in EXIF data.');
return false;
}
return char_data;
}
catch (err) {
console.log(err);
return false;
}
case 'png':
const buffer = fs.readFileSync(cardUrl);
const chunks = extract(buffer);
const textChunks = chunks.filter(function (chunk) {
return chunk.name === 'tEXt';
}).map(function (chunk) {
return PNGtext.decode(chunk.data);
});
if (textChunks.length === 0) {
console.error('PNG metadata does not contain any character data.');
throw new Error('No PNG metadata.');
}
return Buffer.from(textChunks[0].text, 'base64').toString('utf8');
default:
break;
}
};
module.exports = {
parse: parse
};