import Realm from 'realm' import { RealmBaseService } from '../base/realmBase' import { Book } from '@/define/model/book/book' import { BookTaskModel } from '../../model/bookTask' import { getProjectPath } from '@/main/service/option/optionCommonService' import { ImageCategory } from '@/define/data/imageData' import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from '@/define/Tools/file' import { BookTaskStatus, CopyImageType } from '@/define/enum/bookEnum' import path from 'path' import { ImageToVideoModels } from '@/define/enum/video' import { cloneDeep, isEmpty } from 'lodash' export class BookTaskService extends RealmBaseService { static instance: BookTaskService | null = null declare realm: Realm private constructor() { super() } /** * 获取当前实例对象,为空则创建一个新的 * @returns */ public static async getInstance() { if (BookTaskService.instance === null) { BookTaskService.instance = new BookTaskService() await super.getInstance() } await BookTaskService.instance.open() return BookTaskService.instance } /** * 查询满足条件的小说子任务信息 * @param bookTaskCondition 查询条件 id,bookId,name,no,page, pageSize */ async GetBookTaskDataByCondition( bookTaskCondition: Book.QueryBookTaskCondition ): Promise { try { // 获取所有的小说数据,并进行时间降序排序 let bookTasks = this.realm.objects('BookTask') // 开始开始筛选 if (bookTaskCondition.id) { // 查询对应的小说ID的数据 bookTasks = bookTasks.filtered('id = $0', bookTaskCondition.id) } if (bookTaskCondition.bookId) { // 查询对应的小说ID的数据 bookTasks = bookTasks.filtered('bookId = $0', bookTaskCondition.bookId) } if (bookTaskCondition.name) { // 查询对应的小说ID的数据 bookTasks = bookTasks.filtered('name = $0', bookTaskCondition.name) } if (bookTaskCondition.no) { // 查询对应的小说ID的数据 bookTasks = bookTasks.filtered('no = $0', bookTaskCondition.no) } let bookTask_length = bookTasks.length // bookTasks = bookTasks.sorted('updateTime', true) // 判断是不是有page和pageSize,有的话对查询返回的信息做分页 if (bookTaskCondition.page && bookTaskCondition.pageSize) { bookTasks = bookTasks.slice( (bookTaskCondition.page - 1) * bookTaskCondition.pageSize, bookTaskCondition.page * bookTaskCondition.pageSize ) as unknown as Realm.Results } let projectPath: string = await getProjectPath() // 做一下数据转换 // 将realm对象数组转换为普通对象数组 // 将realm对象数组转换为普通对象数组,并处理异步操作 let res_bookTasks = Array.from(bookTasks).map((bookTask) => { // 直接操作普通对象 return { ...bookTask, imageStyle: bookTask.imageStyle ? Array.from(bookTask.imageStyle) : [], customizeImageStyle: bookTask.customizeImageStyle ? Array.from(bookTask.customizeImageStyle) : [], generateVideoPath: JoinPath(projectPath, bookTask.generateVideoPath), srtPath: JoinPath(projectPath, bookTask.srtPath), audioPath: JoinPath(projectPath, bookTask.audioPath), imageFolder: JoinPath(projectPath, bookTask.imageFolder), cacheImageList: bookTask.cacheImageList ? Array.from(bookTask.cacheImageList).map((item) => JoinPath(projectPath, item)) : [], imageCategory: bookTask.imageCategory ? bookTask.imageCategory : ImageCategory.Midjourney // 默认使用MJ出图 } as Book.SelectBookTask }) return { bookTasks: JSON.parse(JSON.stringify(res_bookTasks)), bookTaskLength: bookTask_length } as Book.QueryBookTaskConditionResponse } catch (error) { throw error } } /** * 通过ID获取小说批次任务的数据 * @param bookTaskId */ async GetBookTaskDataById(bookTaskId: string): Promise { try { if (bookTaskId == null) { throw new Error('小说任务ID不能为空') } let bookTasks = await this.GetBookTaskDataByCondition({ id: bookTaskId }) if (bookTasks.bookTasks.length <= 0) { throw new Error('未找到对应的小说任务') } else { return bookTasks.bookTasks[0] } } catch (error) { throw error } } /** * 修改小说批次任务的状态 * @param bookTaskId 小说批次任务Id * @param status 目标状态 */ ModifyBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg?: string): void { try { this.transaction(() => { // 修改对应小说批次任务的状态 let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) if (bookTask == null) { throw new Error('未找到对应的小说任务') } bookTask.status = status bookTask.updateTime = new Date() if (errorMsg != null) { bookTask.errorMsg = errorMsg } }) } catch (error) { throw error } } /** * 修改小说批次任务数据 * @param bookTaskId 小说批次任务ID * @param data 要修改的数据 */ async ModifyBookTaskDataById( bookTaskId: string, data: Book.SelectBookTask ): Promise { try { this.transaction(() => { let updateData = this.realm.objectForPrimaryKey('BookTask', bookTaskId) if (updateData == null) { throw new Error('未找到对应的小说任务详细信息') } // 开始修改 for (let key in data) { updateData[key] = data[key] } }) let res = await this.GetBookTaskDataById(bookTaskId) return res } catch (error) { throw error } } // 添加一条数据 async AddBookTask(bookTask: Book.SelectBookTask): Promise { try { // 新增 if (bookTask.bookId == '' || bookTask.bookId == null) { throw new Error('小说ID不能为空') } bookTask.id = crypto.randomUUID() // 获取当前bookID对应的最大的no let maxNo = this.realm.objects('BookTask').filtered('bookId = $0', bookTask.bookId).max('no') bookTask.no = maxNo == null ? 1 : Number(maxNo) + 1 bookTask.name = 'output_0000' + bookTask.no bookTask.status = BookTaskStatus.WAIT bookTask.updateTime = new Date() bookTask.createTime = new Date() this.realm.write(() => { this.realm.create('BookTask', bookTask) }) // 处理完毕,返回结果 let res = await this.GetBookTaskDataById(bookTask.id) return res } catch (error) { throw error } } async CopyNewBookTask( sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType ) { try { let addBookTask = [] as Book.SelectBookTask[] let addBookTaskDetail = [] as Book.SelectBookTaskDetail[] let book = this.realm.objectForPrimaryKey('Book', sourceBookTask.bookId as string) if (book == null) { throw new Error('未找到对应的小说') } let projectPath = await getProjectPath() // 先处理文件夹的创建,包括小说任务的和小说任务分镜的 for (let i = 0; i < copyCount; i++) { let no = this.GetMaxBookTaskNo(sourceBookTask.bookId as string) + i let name = book.name + '_0000' + no let imageFolder = path.join(projectPath, `${sourceBookTask.bookId}/tmp/${name}`) await CheckFolderExistsOrCreate(imageFolder) // 创建对应的文件夹 let addOneBookTask = { id: crypto.randomUUID(), bookId: sourceBookTask.bookId, no: no, name: name, generateVideoPath: sourceBookTask.generateVideoPath, srtPath: sourceBookTask.srtPath, audioPath: sourceBookTask.audioPath, draftSrtStyle: sourceBookTask.draftSrtStyle, backgroundMusic: sourceBookTask.backgroundMusic, friendlyReminder: sourceBookTask.friendlyReminder, imageFolder: path.relative(projectPath, imageFolder), status: sourceBookTask.status, errorMsg: sourceBookTask.errorMsg, updateTime: new Date(), createTime: new Date(), isAuto: sourceBookTask.isAuto, imageStyle: sourceBookTask.imageStyle, autoAnalyzeCharacter: sourceBookTask.autoAnalyzeCharacter, customizeImageStyle: sourceBookTask.customizeImageStyle, videoConfig: sourceBookTask.videoConfig, prefixPrompt: sourceBookTask.prefixPrompt, suffixPrompt: sourceBookTask.suffixPrompt, version: sourceBookTask.version, imageCategory: sourceBookTask.imageCategory, videoCategory: sourceBookTask.videoCategory ?? ImageToVideoModels.MJ_VIDEO, openVideoGenerate: sourceBookTask.openVideoGenerate == null ? false : sourceBookTask.openVideoGenerate } as Book.SelectBookTask addBookTask.push(addOneBookTask) for (let j = 0; j < sourceBookTaskDetail.length; j++) { const element = sourceBookTaskDetail[j] let outImagePath: string | undefined let subImagePath: string[] | undefined if (element.outImagePath == null || isEmpty(element.outImagePath)) { throw new Error('部分分镜的输出图片路径为空') } if (copyImageType == CopyImageType.ALL) { // 直接全部复制 outImagePath = element.outImagePath subImagePath = element.subImagePath } else if (copyImageType == CopyImageType.ONE) { if (!element.subImagePath || element.subImagePath.length <= 1) { throw new Error('部分分镜的子图片路径数量不足或为空') } // 只复制对应的 let oldImage = element.subImagePath[i + 1] outImagePath = path.join(imageFolder, path.basename(element.outImagePath as string)) await CopyFileOrFolder(oldImage, outImagePath) subImagePath = [] } else if (copyImageType == CopyImageType.NONE) { outImagePath = undefined subImagePath = [] } else { throw new Error('无效的图片复制类型') } if (outImagePath) { // 单独处理一下显示的图片 let imageBaseName = path.basename(element.outImagePath) let newImageBaseName = path.join( projectPath, `${sourceBookTask.bookId}/tmp/${name}/${imageBaseName}` ) await CopyFileOrFolder(outImagePath, newImageBaseName) } // 处理SD设置 let sdConifg = undefined if (element.sdConifg) { let sdConifg = cloneDeep(element.sdConifg) if (sdConifg.webuiConfig) { let tempSdConfig = cloneDeep(sdConifg.webuiConfig) tempSdConfig.id = crypto.randomUUID() sdConifg.webuiConfig = tempSdConfig } } let reverseId = crypto.randomUUID() // 处理反推数据 let reverseMessage = [] as Book.ReversePrompt[] if (element.reversePrompt && element.reversePrompt.length > 0) { reverseMessage = cloneDeep(element.reversePrompt) for (let k = 0; k < reverseMessage.length; k++) { reverseMessage[k].id = crypto.randomUUID() reverseMessage[k].bookTaskDetailId = reverseId } } let addOneBookTaskDetail = {} as Book.SelectBookTaskDetail addOneBookTaskDetail.id = reverseId addOneBookTaskDetail.no = element.no addOneBookTaskDetail.name = element.name addOneBookTaskDetail.bookId = sourceBookTask.bookId addOneBookTaskDetail.bookTaskId = addOneBookTask.id addOneBookTaskDetail.videoPath = element.videoPath ? path.relative(projectPath, element.videoPath) : undefined addOneBookTaskDetail.word = element.word addOneBookTaskDetail.oldImage = element.oldImage ? path.relative(projectPath, element.oldImage) : undefined addOneBookTaskDetail.afterGpt = element.afterGpt addOneBookTaskDetail.startTime = element.startTime addOneBookTaskDetail.endTime = element.endTime addOneBookTaskDetail.timeLimit = element.timeLimit addOneBookTaskDetail.subValue = ( element.subValue && element.subValue.length > 0 ? JSON.stringify(element.subValue) : undefined ) as string addOneBookTaskDetail.characterTags = element.characterTags && element.characterTags.length > 0 ? cloneDeep(element.characterTags) : [] addOneBookTaskDetail.gptPrompt = element.gptPrompt addOneBookTaskDetail.outImagePath = outImagePath ? path.relative(projectPath, outImagePath) : undefined addOneBookTaskDetail.subImagePath = subImagePath || [] addOneBookTaskDetail.prompt = element.prompt addOneBookTaskDetail.adetailer = element.adetailer addOneBookTaskDetail.sdConifg = sdConifg addOneBookTaskDetail.createTime = new Date() addOneBookTaskDetail.updateTime = new Date() addOneBookTaskDetail.audioPath = element.audioPath addOneBookTaskDetail.subtitlePosition = element.subtitlePosition addOneBookTaskDetail.status = element.status addOneBookTaskDetail.reversePrompt = reverseMessage addOneBookTaskDetail.imageLock = false // 默认不锁定 addBookTaskDetail.push(addOneBookTaskDetail) } } } catch (error) { throw error } } /** * 获取最大的小说批次任务的编号 * @param bookId 小说ID */ GetMaxBookTaskNo(bookId: string): number { let maxNo = this.realm.objects('BookTask').filtered('bookId = $0', bookId).max('no') let no = maxNo == null ? 1 : Number(maxNo) + 1 return no } /** * 重置小说批次数据,清除小说的详细信息 * @param bookTaskId 小说批次ID */ async ResetBookTaskDataById( bookTaskId: string, resetBase: boolean = true ): Promise { try { // 开始重置数据,先重置小说批次数据,在重置其他 this.transaction(() => { this.ResetBookTaskDataInfo(bookTaskId, resetBase) }) let res = await this.GetBookTaskDataById(bookTaskId) return res } catch (error) { throw error } } /** * 删除对应的小说批次数据 * @param bookTaskId 要删除的批次的ID */ DeleteBookTaskDataById(bookTaskId: string): void { try { this.transaction(() => { // 先调用清除数据的方法 this.ResetBookTaskDataInfo(bookTaskId) // 删除批次数据 let bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) if (bookTask == null) { throw new Error('未找到对应的小说任务,无法执行删除操作') } this.realm.delete(bookTask) }) } catch (error) { throw error } } /** 重置小说批次任务数据 */ private ResetBookTaskDataInfo(bookTaskId: string, resetBase: boolean = true) { try { let modifyBookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId) if (modifyBookTask == null) { throw new Error('未找到对应的小说批次任务,无法执行重置操作') } let book = this.realm.objectForPrimaryKey('Book', modifyBookTask.bookId) if (book == null) { throw new Error('未找到对应的小说,无法执行重置操作') } if (resetBase) { // 基础数据的重置 modifyBookTask.errorMsg = '' modifyBookTask.updateTime = new Date() modifyBookTask.imageStyle = [] modifyBookTask.autoAnalyzeCharacter = undefined modifyBookTask.customizeImageStyle = [] modifyBookTask.videoConfig = undefined modifyBookTask.prefixPrompt = undefined modifyBookTask.suffixPrompt = undefined modifyBookTask.subImageFolder = [] modifyBookTask.srtPath = book.srtPath ?? undefined modifyBookTask.audioPath = book.audioPath ?? undefined } // 继承小说的srt和配音文件 modifyBookTask.imageCategory = ImageCategory.Midjourney // 默认使用MJ出图 modifyBookTask.status = BookTaskStatus.WAIT // 开始删除 分镜信息 let bookTaskDetails = this.realm .objects('BookTaskDetail') .filtered('bookTaskId = $0', bookTaskId) // 开始删除数据 for (const bookTaskDetail of bookTaskDetails) { // 删除MJMessage if (bookTaskDetail.mjMessage) { this.realm.delete(bookTaskDetail.mjMessage) } if (bookTaskDetail.reversePrompt) { ;(bookTaskDetail.reversePrompt as any[]).forEach((item) => { this.realm.delete(item) }) } if (bookTaskDetail.sdConifg) { this.realm.delete(bookTaskDetail.sdConifg) } this.realm.delete(bookTaskDetail) } } catch (error) { throw error } } }