LaiTool_PRO/src/define/db/service/book/bookTaskService.ts
2025-09-04 16:58:42 +08:00

479 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 查询条件 idbookIdnamenopage, pageSize
*/
async GetBookTaskDataByCondition(
bookTaskCondition: Book.QueryBookTaskCondition
): Promise<Book.QueryBookTaskConditionResponse> {
try {
// 获取所有的小说数据,并进行时间降序排序
let bookTasks = this.realm.objects<BookTaskModel>('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<BookTaskModel>
}
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<Book.SelectBookTask> {
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<Book.SelectBookTask> {
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<Book.SelectBookTask> {
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<Book.SelectBookTask> {
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
}
}
}