lq1405 b5ed4e313d V 4.0.4
1. 新增预设库的批量删除
2. 添加两个高图文一致性推理出图提示词预设
3. 新增两个通用的高图文一致性推理出图和图转视频提示词预设
4. 新增 ComfyUI 图转视频功能(之前的工作流需要重新配置)
5. 优化 ComfyUI 设置
6. 新增导入图转视频提示词
7. 新增同步出图提示词到图转视频提示词
2025-11-05 19:39:42 +08:00

219 lines
7.9 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 { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService'
import { BookTaskService } from '@/define/db/service/book/bookTaskService'
import { OptionRealmService } from '@/define/db/service/optionService'
import { BookService } from '@/define/db/service/book/bookService'
import { TaskListService } from '@/define/db/service/book/taskListService'
import { TaskModal } from '@/define/model/task'
import { Book } from '@/define/model/book/book'
import { getProjectPath } from '../../option/optionCommonService'
import path from 'path'
import { isEmpty } from 'lodash'
import axios from 'axios'
import { define } from '@/define/define'
import { CheckFolderExistsOrCreate, CopyFileOrFolder } from '@/define/Tools/file'
import { DownloadFile } from '@/define/Tools/common'
import { MappingTaskTypeToVideoModel } from '@/define/enum/video'
import { BookBackTaskType } from '@/define/enum/bookEnum'
import { t } from '@/i18n'
import { PresetRealmService } from '@/define/db/service/presetService'
export class BookBasicHandle {
bookTaskDetailService!: BookTaskDetailService
bookTaskService!: BookTaskService
optionRealmService!: OptionRealmService
bookService!: BookService
taskListService!: TaskListService
presetRealmService!: PresetRealmService
constructor() {
// 初始化
}
async InitBookBasicHandle() {
// 如果 bookTaskDetailService 已经初始化,则直接返回
if (!this.bookTaskDetailService) {
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
}
if (!this.bookTaskService) {
this.bookTaskService = await BookTaskService.getInstance()
}
if (!this.optionRealmService) {
this.optionRealmService = await OptionRealmService.getInstance()
}
if (!this.bookService) {
this.bookService = await BookService.getInstance()
}
if (!this.taskListService) {
this.taskListService = await TaskListService.getInstance()
}
if (!this.presetRealmService) {
this.presetRealmService = await PresetRealmService.getInstance()
}
}
/**
* 检查所有的服务是否都已初始化
* @returns
*/
CheckInit() {
if (this.bookTaskDetailService
&& this.bookTaskService
&& this.optionRealmService
&& this.bookService
&& this.taskListService) {
return true
}
return false
}
/** 执行事务的方法 */
async transaction(callback: (realm: any) => void) {
this.CheckInit() || await this.InitBookBasicHandle()
this.bookService.transaction(() => {
callback(this.bookService.realm)
})
}
/**
* 下载视频文件并处理路径映射
*
* 此方法负责从远程URL下载视频文件到本地并处理文件的存储路径、转存服务等。
* 支持多种视频来源的处理包括MidJourney、可灵等不同平台的视频文件。
* 会自动处理文件转存除MJ官方CDN和可灵视频外并更新数据库中的路径信息。
*
* @param {string[]} videoUrls - 需要下载的视频URL列表
* @param {TaskModal.Task} task - 当前执行的任务对象,包含任务类型等信息
* @param {Book.SelectBookTaskDetail} bookTaskDetail - 小说任务详情对象,包含分镜信息
* @param {string} preffix - 文件名前缀,用于区分不同来源的视频文件
*
* @returns {Promise<{outVideoPath: string, subVideoPath: string[]}>} 返回下载结果
* - outVideoPath: 主输出视频的本地路径(第一个视频的副本)
* - subVideoPath: 所有子视频的路径信息数组JSON字符串格式
*
* @throws {Error} 当服务未初始化时
* @throws {Error} 当文件下载失败时
* @throws {Error} 当数据库操作失败时
*
* @example
* ```typescript
* const result = await this.DownloadVideoUrls(
* ['http://example.com/video1.mp4', 'http://example.com/video2.mp4'],
* task,
* bookTaskDetail,
* 'MJ'
* );
* console.log('主视频路径:', result.outVideoPath);
* console.log('所有视频路径:', result.subVideoPath);
* ```
*
* @description
* 处理流程:
* 1. 初始化服务并获取项目路径
* 2. 遍历每个视频URL进行下载
* 3. 根据任务类型决定是否使用转存服务
* 4. 创建本地存储目录并下载文件
* 5. 将第一个视频复制为主输出视频
* 6. 更新数据库中的路径信息
*
* @note
* - MidJourney官方CDN (cdn.midjourney.com) 的视频不支持转存
* - 可灵视频 (KLING_VIDEO, KLING_VIDEO_EXTEND) 不使用转存服务
* - 转存服务需要全局machineId配置
* - 视频文件按时间戳和索引命名以避免冲突
*/
async DownloadVideoUrls(videoUrls: string[], task: TaskModal.Task, bookTaskDetail: Book.SelectBookTaskDetail, preffix: string, videoIds?: string[]): Promise<{ outVideoPath: string, subVideoPath: string[] }> {
this.CheckInit() || await this.InitBookBasicHandle()
if (videoIds != undefined && videoIds.length != videoUrls.length) {
throw new Error(t("视频ID数量与视频链接数量不匹配"))
}
let bookTask = await this.bookTaskService.GetBookTaskDataById(
bookTaskDetail.bookTaskId as string,
true
)
let tempVideoUrls = bookTaskDetail.subVideoPath || []
let newVideoUrls: string[] = []
let outVideoPath: string = ''
const project_path = await getProjectPath()
// 开始下载所有视频
for (let i = 0; i < videoUrls.length; i++) {
const videoUrl = videoUrls[i]
// 处理文件地址和下载
let videoPath = path.join(
bookTask.imageFolder as string,
`video/subVideo/${bookTaskDetail.name}/${new Date().getTime()}_${i}.mp4`
)
let remoteUrl = videoUrl
// 开始处理下载 mj 官方的图片不支持转存
if (global.machineId
&& !isEmpty(global.machineId)
&& !videoUrl.startsWith('https://cdn.midjourney.com')
&& task.type != BookBackTaskType.KLING_VIDEO
&& task.type != BookBackTaskType.KLING_VIDEO_EXTEND
&& task.type != BookBackTaskType.HAILUO_TEXT_TO_VIDEO
&& task.type != BookBackTaskType.HAILUO_IMAGE_TO_VIDEO
&& task.type != BookBackTaskType.HAILUO_FIRST_LAST_FRAME
&& task.type != BookBackTaskType.COMFYUI_VIDEO
) {
// 转存一下视频文件
// 获取当前url的文件名
let fileName = preffix + "_" + path.basename(videoUrl)
let transferRes = await axios.post(define.lms_url + `/lms/FileUpload/UrlUpload/${global.machineId}`, {
url: videoUrl,
fileName: fileName
})
if (transferRes.status == 200 && transferRes.data.code == 1) {
remoteUrl = transferRes.data.data.url
}
}
if (isEmpty(remoteUrl)) {
remoteUrl = videoUrl
}
await CheckFolderExistsOrCreate(path.dirname(videoPath))
await DownloadFile(remoteUrl, videoPath)
// 处理返回数据信息
// 开始修改信息
// 将信息添加到里面
let a = {
localPath: path.relative(project_path, videoPath),
remotePath: remoteUrl,
taskId: bookTaskDetail.videoMessage?.taskId,
videoId: videoIds != undefined && videoIds[i] ? videoIds[i] : "",
index: i,
type: MappingTaskTypeToVideoModel(task.type as string)
}
newVideoUrls.push(JSON.stringify(a))
if (i == 0) {
outVideoPath = path.join(
bookTask.imageFolder as string,
'video',
bookTaskDetail.name + path.extname(videoPath)
)
await CopyFileOrFolder(videoPath, outVideoPath as string)
}
}
// 开始处理数据
// 将原有的视频路径合并到新数组中
newVideoUrls.push(...tempVideoUrls)
await this.bookTaskDetailService.ModifyBookTaskDetailById(bookTaskDetail.id as string, {
subVideoPath: newVideoUrls,
generateVideoPath: outVideoPath != '' ? outVideoPath : ''
})
return {
outVideoPath: outVideoPath,
subVideoPath: newVideoUrls
}
}
}