LaiTool_PRO/src/define/db/service/book/bookTaskService.ts

479 lines
17 KiB
TypeScript
Raw Normal View History

2025-08-19 14:33:59 +08:00
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'
2025-09-04 16:58:42 +08:00
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'
2025-08-19 14:33:59 +08:00
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
}
}
2025-09-04 16:58:42 +08:00
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
}
}
2025-08-19 14:33:59 +08:00
/**
*
* @param bookId ID
*/
2025-09-04 16:58:42 +08:00
GetMaxBookTaskNo(bookId: string): number {
2025-08-19 14:33:59 +08:00
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
}
}
}