From ad7ce3f0d1d1dc12d91d7b55672c7648e5417dd0 Mon Sep 17 00:00:00 2001 From: kwaroran Date: Mon, 3 Jun 2024 16:59:44 +0900 Subject: [PATCH] feat: add charx import --- src/ts/characterCards.ts | 41 +++++++++++++++++++- src/ts/process/processzip.ts | 73 +++++++++++++++++++++++++++++++++++- src/ts/util.ts | 11 ++++-- 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/src/ts/characterCards.ts b/src/ts/characterCards.ts index 7ae2b880..e7742107 100644 --- a/src/ts/characterCards.ts +++ b/src/ts/characterCards.ts @@ -12,13 +12,13 @@ import { CCardLib, type CharacterCardV3, type LorebookEntry } from '@risuai/ccar import { reencodeImage } from "./process/files/image" import { PngChunk } from "./pngChunk" import type { OnnxModelFiles } from "./process/transformers" -import { CharXWriter } from "./process/processzip" +import { CharXReader, CharXWriter } from "./process/processzip" export const hubURL = "https://sv.risuai.xyz" export async function importCharacter() { try { - const files = await selectFileByDom(['png', 'json'], 'multiple') + const files = await selectFileByDom(["*"], 'multiple') if(!files){ return } @@ -63,6 +63,36 @@ async function importCharacterProcess(f:{ return } } + + if(f.name.endsWith('charx')){ + console.log('reading charx') + const reader = new CharXReader() + alertStore.set({ + type: 'wait', + msg: 'Loading... (Reading)' + }) + await reader.read(f.data) + const cardData = reader.cardData + if(!cardData){ + alertError(language.errors.noData) + return + } + const card = JSON.parse(cardData) + if(CCardLib.character.check(card) !== 'v3'){ + alertError(language.errors.noData) + return + } + await importCharacterCardSpec(card, undefined, 'normal', reader.assets) + let db = get(DataBase) + return db.characters.length - 1 + } + + if(!f.name.endsWith('png')){ + alertError(language.errors.noData) + return + } + + alertStore.set({ type: 'wait', msg: 'Loading... (Reading)' @@ -486,6 +516,13 @@ async function importCharacterCardSpec(card:CharacterCardV2Risu|CharacterCardV3, else if(data.assets[i].uri === 'ccdefault:'){ imgp = im } + else if(data.assets[i].uri.startsWith('embeded://')){ + const key = data.assets[i].uri.replace('embeded://', '') + imgp = assetDict[key] + if(!imgp){ + throw new Error('Error while importing, asset ' + key + ' not found') + } + } else{ continue } diff --git a/src/ts/process/processzip.ts b/src/ts/process/processzip.ts index eaaa8a0f..8e5e0982 100644 --- a/src/ts/process/processzip.ts +++ b/src/ts/process/processzip.ts @@ -1,5 +1,6 @@ -import { AppendableBuffer, type LocalWriter, type VirtualWriter } from "../storage/globalApi"; +import { AppendableBuffer, saveAsset, type LocalWriter, type VirtualWriter } from "../storage/globalApi"; import * as fflate from "fflate"; +import { sleep } from "../util"; export async function processZip(dataArray: Uint8Array): Promise { const jszip = await import("jszip"); @@ -69,4 +70,74 @@ export class CharXWriter{ await this.writer.close() } } +} + +export class CharXReader{ + unzip:fflate.Unzip + assets:{[key:string]:string} = {} + assetBuffers:{[key:string]:AppendableBuffer} = {} + assetPromises:Promise[] = [] + excludedFiles:string[] = [] + cardData:string|undefined + constructor(){ + this.unzip = new fflate.Unzip() + this.unzip.register(fflate.UnzipInflate) + this.unzip.onfile = (file) => { + const assetIndex = file.name + this.assetBuffers[assetIndex] = new AppendableBuffer() + + file.ondata = (err, dat, final) => { + this.assetBuffers[assetIndex].append(dat) + if(final){ + const assetData = this.assetBuffers[assetIndex].buffer + if(assetData.byteLength > 50 * 1024 * 1024){ + this.excludedFiles.push(assetIndex) + } + else if(file.name === 'card.json'){ + this.cardData = new TextDecoder().decode(assetData) + } + else{ + this.assetPromises.push((async () => { + const assetId = await saveAsset(assetData) + this.assets[assetIndex] = assetId + })()) + } + } + } + + if(file.originalSize ?? 0 < 50 * 1024 * 1024){ + file.start() + } + } + } + + async read(data:Uint8Array|File|ReadableStream){ + if(data instanceof Uint8Array){ + this.unzip.push(data, true) + } + if(data instanceof File){ + const reader = data.stream().getReader() + while(true){ + const {done, value} = await reader.read() + if(done){ + break + } + this.unzip.push(value, false) + } + this.unzip.push(new Uint8Array(0), true) + } + if(data instanceof ReadableStream){ + const reader = data.getReader() + while(true){ + const {done, value} = await reader.read() + if(done){ + break + } + this.unzip.push(value, false) + } + this.unzip.push(new Uint8Array(0), true) + } + await sleep(500) + await Promise.all(this.assetPromises) + } } \ No newline at end of file diff --git a/src/ts/util.ts b/src/ts/util.ts index 1f10848a..03c90bac 100644 --- a/src/ts/util.ts +++ b/src/ts/util.ts @@ -125,12 +125,15 @@ export function selectFileByDom(allowedExtensions:string[], multiple:'multiple'| const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = multiple === 'multiple'; - - if(!(get(DataBase).allowAllExtentionFiles || checkIsIos())){ + const acceptAll = (get(DataBase).allowAllExtentionFiles || checkIsIos() || allowedExtensions[0] === '*') + if(!acceptAll){ if (allowedExtensions && allowedExtensions.length) { fileInput.accept = allowedExtensions.map(ext => `.${ext}`).join(','); } } + else{ + fileInput.accept = '*' + } fileInput.addEventListener('change', (event) => { @@ -139,10 +142,10 @@ export function selectFileByDom(allowedExtensions:string[], multiple:'multiple'| return; } - const files = Array.from(fileInput.files).filter(file => { + const files = acceptAll ? Array.from(fileInput.files) :(Array.from(fileInput.files).filter(file => { const fileExtension = file.name.split('.').pop().toLowerCase(); return !allowedExtensions || allowedExtensions.includes(fileExtension); - }); + })) fileInput.remove() resolve(files);