mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 19:07:40 +01:00
Import character icon from CHARX
This commit is contained in:
parent
66fd973830
commit
60b7164c28
@ -44,7 +44,7 @@ async function readCharacterData(inputFile, inputFormat = 'png') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the character card to the specified image file.
|
* Writes the character card to the specified image file.
|
||||||
* @param {string} inputFile - Path to the image file
|
* @param {string|Buffer} inputFile - Path to the image file or image buffer
|
||||||
* @param {string} data - Character card data
|
* @param {string} data - Character card data
|
||||||
* @param {string} outputFile - Target image file name
|
* @param {string} outputFile - Target image file name
|
||||||
* @param {import('express').Request} request - Express request obejct
|
* @param {import('express').Request} request - Express request obejct
|
||||||
@ -60,8 +60,20 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Read the image, resize, and save it as a PNG into the buffer
|
|
||||||
const inputImage = await tryReadImage(inputFile, crop);
|
/**
|
||||||
|
* Read the image, resize, and save it as a PNG into the buffer.
|
||||||
|
* @returns {Promise<Buffer>} Image buffer
|
||||||
|
*/
|
||||||
|
function getInputImage() {
|
||||||
|
if (Buffer.isBuffer(inputFile)) {
|
||||||
|
return parseImageBuffer(inputFile, crop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryReadImage(inputFile, crop);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputImage = await getInputImage();
|
||||||
|
|
||||||
// Get the chunks
|
// Get the chunks
|
||||||
const outputImage = characterCardParser.write(inputImage, data);
|
const outputImage = characterCardParser.write(inputImage, data);
|
||||||
@ -84,6 +96,32 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u
|
|||||||
* @property {boolean} want_resize Resize the image to the standard avatar size
|
* @property {boolean} want_resize Resize the image to the standard avatar size
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an image buffer and applies crop if defined.
|
||||||
|
* @param {Buffer} buffer Buffer of the image
|
||||||
|
* @param {Crop|undefined} [crop] Crop parameters
|
||||||
|
* @returns {Promise<Buffer>} Image buffer
|
||||||
|
*/
|
||||||
|
async function parseImageBuffer(buffer, crop) {
|
||||||
|
const image = await jimp.read(buffer);
|
||||||
|
let finalWidth = image.bitmap.width, finalHeight = image.bitmap.height;
|
||||||
|
|
||||||
|
// Apply crop if defined
|
||||||
|
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
|
||||||
|
image.crop(crop.x, crop.y, crop.width, crop.height);
|
||||||
|
// Apply standard resize if requested
|
||||||
|
if (crop.want_resize) {
|
||||||
|
finalWidth = AVATAR_WIDTH;
|
||||||
|
finalHeight = AVATAR_HEIGHT;
|
||||||
|
} else {
|
||||||
|
finalWidth = crop.width;
|
||||||
|
finalHeight = crop.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.cover(finalWidth, finalHeight).getBufferAsync(jimp.MIME_PNG);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads an image file and applies crop if defined.
|
* Reads an image file and applies crop if defined.
|
||||||
* @param {string} imgPath Path to the image file
|
* @param {string} imgPath Path to the image file
|
||||||
@ -509,11 +547,25 @@ async function importFromCharX(uploadPath, { request }) {
|
|||||||
throw new Error('Invalid CharX card file: missing spec field');
|
throw new Error('Invalid CharX card file: missing spec field');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {string|Buffer} */
|
||||||
|
let avatar = defaultAvatarPath;
|
||||||
|
const assets = _.get(card, 'data.assets');
|
||||||
|
if (Array.isArray(assets) && assets.length) {
|
||||||
|
for (const asset of assets.filter(x => x.type === 'icon' && typeof x.uri === 'string')) {
|
||||||
|
const pathNoProtocol = String(asset.uri.replace(/^(?:\/\/|[^/]+)*\//, ''));
|
||||||
|
const buffer = await extractFileFromZipBuffer(data, pathNoProtocol);
|
||||||
|
if (buffer) {
|
||||||
|
avatar = buffer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsetFavFlag(card);
|
unsetFavFlag(card);
|
||||||
card['create_date'] = humanizedISO8601DateTime();
|
card['create_date'] = humanizedISO8601DateTime();
|
||||||
card.name = sanitize(card.name);
|
card.name = sanitize(card.name);
|
||||||
const fileName = getPngName(card.name, request.user.directories);
|
const fileName = getPngName(card.name, request.user.directories);
|
||||||
const result = await writeCharacterData(defaultAvatarPath, JSON.stringify(card), fileName, request);
|
const result = await writeCharacterData(avatar, JSON.stringify(card), fileName, request);
|
||||||
return result ? fileName : '';
|
return result ? fileName : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,6 +281,12 @@ router.post('/generate-image', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
const archiveBuffer = await generateResult.arrayBuffer();
|
const archiveBuffer = await generateResult.arrayBuffer();
|
||||||
const imageBuffer = await extractFileFromZipBuffer(archiveBuffer, '.png');
|
const imageBuffer = await extractFileFromZipBuffer(archiveBuffer, '.png');
|
||||||
|
|
||||||
|
if (!imageBuffer) {
|
||||||
|
console.warn('NovelAI generated an image, but the PNG file was not found.');
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
|
||||||
const originalBase64 = imageBuffer.toString('base64');
|
const originalBase64 = imageBuffer.toString('base64');
|
||||||
|
|
||||||
// No upscaling
|
// No upscaling
|
||||||
@ -311,6 +317,11 @@ router.post('/generate-image', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
const upscaledArchiveBuffer = await upscaleResult.arrayBuffer();
|
const upscaledArchiveBuffer = await upscaleResult.arrayBuffer();
|
||||||
const upscaledImageBuffer = await extractFileFromZipBuffer(upscaledArchiveBuffer, '.png');
|
const upscaledImageBuffer = await extractFileFromZipBuffer(upscaledArchiveBuffer, '.png');
|
||||||
|
|
||||||
|
if (!upscaledImageBuffer) {
|
||||||
|
throw new Error('NovelAI upscaled an image, but the PNG file was not found.');
|
||||||
|
}
|
||||||
|
|
||||||
const upscaledBase64 = upscaledImageBuffer.toString('base64');
|
const upscaledBase64 = upscaledImageBuffer.toString('base64');
|
||||||
|
|
||||||
return response.send(upscaledBase64);
|
return response.send(upscaledBase64);
|
||||||
|
@ -139,7 +139,7 @@ function getHexString(length) {
|
|||||||
* Extracts a file with given extension from an ArrayBuffer containing a ZIP archive.
|
* Extracts a file with given extension from an ArrayBuffer containing a ZIP archive.
|
||||||
* @param {ArrayBuffer} archiveBuffer Buffer containing a ZIP archive
|
* @param {ArrayBuffer} archiveBuffer Buffer containing a ZIP archive
|
||||||
* @param {string} fileExtension File extension to look for
|
* @param {string} fileExtension File extension to look for
|
||||||
* @returns {Promise<Buffer>} Buffer containing the extracted file
|
* @returns {Promise<Buffer|null>} Buffer containing the extracted file. Null if the file was not found.
|
||||||
*/
|
*/
|
||||||
async function extractFileFromZipBuffer(archiveBuffer, fileExtension) {
|
async function extractFileFromZipBuffer(archiveBuffer, fileExtension) {
|
||||||
return await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => {
|
return await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => {
|
||||||
@ -171,6 +171,7 @@ async function extractFileFromZipBuffer(archiveBuffer, fileExtension) {
|
|||||||
zipfile.readEntry();
|
zipfile.readEntry();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
zipfile.on('end', () => resolve(null));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user