diff --git a/package-lock.json b/package-lock.json index b0a5bf7..26b2b6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "3.0.3", + "version": "3.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "3.0.3", + "version": "3.0.4", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", @@ -21,7 +21,6 @@ "axios": "^1.6.5", "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", - "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "electron-store": "^9.0.0", "electron-updater": "^6.1.7", @@ -3048,11 +3047,6 @@ "bluebird": "^3.5.5" } }, - "node_modules/blueimp-canvas-to-blob": { - "version": "3.29.0", - "resolved": "https://registry.npmmirror.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", - "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" - }, "node_modules/boolbase": { "version": "1.0.0", "dev": true, @@ -3669,15 +3663,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/compressorjs": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/compressorjs/-/compressorjs-1.2.1.tgz", - "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", - "dependencies": { - "blueimp-canvas-to-blob": "^3.29.0", - "is-blob": "^2.1.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -5899,17 +5884,6 @@ "version": "0.3.2", "license": "MIT" }, - "node_modules/is-blob": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-blob/-/is-blob-2.1.0.tgz", - "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-ci": { "version": "3.0.1", "dev": true, diff --git a/package.json b/package.json index 1f1929d..f6280de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.0.3", + "version": "3.0.4", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", @@ -29,7 +29,6 @@ "axios": "^1.6.5", "blob-to-buffer": "^1.2.9", "compressing": "^1.10.0", - "compressorjs": "^1.2.1", "crypto-js": "^4.2.0", "electron-store": "^9.0.0", "electron-updater": "^6.1.7", diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index fdf514a..2be42e7 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm b/resources/scripts/db/software.realm index 4702f0f..5ec3fa8 100644 Binary files a/resources/scripts/db/software.realm and b/resources/scripts/db/software.realm differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index 52188d9..cd65c2d 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/src/define/Tools/image.ts b/src/define/Tools/image.ts index 237a70d..79e12e7 100644 --- a/src/define/Tools/image.ts +++ b/src/define/Tools/image.ts @@ -3,7 +3,6 @@ import sharp from 'sharp' import { CheckFileOrDirExist } from './file' import fs from 'fs' import https from 'https' -import Compressor from 'compressorjs'; /** * 将指定的图片的尺寸修改,返回修改后的图片数据(base64或buffer) @@ -130,40 +129,28 @@ export function GetImageBase64(url: string): Promise { /** * 压缩图片到指定的大小 - * @param file 图片的Blob对象 + * @param base64 图片的Blob对象 * @param maxSizeInBytes 最大文件大小,单位字节 * @returns 返回一个Promise,解析为压缩后的Blob对象 */ -export function CompressImageToSize(base64: string, maxSizeInBytes: number): Promise { - let mimeType = this.getMimeType(base64); - let byteCharacters = atob(base64.split(',')[1]); - let byteNumbers = new Array(byteCharacters.length); - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); - } - let byteArray = new Uint8Array(byteNumbers); - let imageBlob = new Blob([byteArray], { type: mimeType }); +export async function CompressImageToSize(filePath: string, maxSizeInBytes: number): Promise { + let quality = 100; // 初始质量设置 + let outputBuffer; - return new Promise((resolve, reject) => { - const compress = (quality: number) => { - new Compressor(imageBlob as Blob, { - quality, - success(result) { - if (result.size <= maxSizeInBytes || quality <= 0.1) { - resolve(result); - } else { - // 递归降低质量 - compress(quality - 0.1); - } - }, - error(err) { - reject(err); - }, - }); - }; - // 从较高的质量开始 - compress(0.9); - }); + const image = sharp(filePath); + // 输出图片的大小 + const metadata = await image.metadata(); + + // 迭代压缩过程 + while (true) { + outputBuffer = await image.jpeg({ quality }).toBuffer(); + if (outputBuffer.length <= maxSizeInBytes || quality === 20) { + break; + } + quality -= 5; // 每次迭代降低质量 + } + + return outputBuffer; } /** diff --git a/src/define/api/apiUrlDefine.js b/src/define/api/apiUrlDefine.js index f83d6bb..6e8b88b 100644 --- a/src/define/api/apiUrlDefine.js +++ b/src/define/api/apiUrlDefine.js @@ -1,6 +1,6 @@ let apiUrl = [ { - label: 'LAI API', + label: 'LAI API - 香港', value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65', gpt_url: 'https://api.laitool.cc/v1/chat/completions', mj_url: { diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 5ab9172..34f016c 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -189,9 +189,6 @@ export class BookService extends BaseRealmService { await CopyFileOrFolder(book.oldVideoPath, oldVideoPath) } - let ffmpegOptions = new FfmpegOptions(); - let res = await ffmpegOptions.FfmpegCompressVideo(oldVideoPath, 800, "2000k") - // 创建对应的文件夹 await CheckFolderExistsOrCreate(bookFolderPath) await CheckFolderExistsOrCreate(imageFolder) diff --git a/src/define/define_string.ts b/src/define/define_string.ts index 440448f..fa27741 100644 --- a/src/define/define_string.ts +++ b/src/define/define_string.ts @@ -233,6 +233,7 @@ export const DEFINE_STRING = { USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK", ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT", EXPORT_COPYWRITING: "EXPORT_COPYWRITING", + IMPORT_COPYWRITING: 'IMPORT_COPYWRITING', MERGE_PROMPT: "MERGE_PROMPT", RESET_BOOK_DATA: "RESET_BOOK_DATA", DELETE_BOOK_DATA: "DELETE_BOOK_DATA", @@ -240,6 +241,8 @@ export const DEFINE_STRING = { RESET_GPT_REVERSE_DATA: "RESET_GPT_REVERSE_DATA", REMOVE_MERGE_PROMPT_DATA: "REMOVE_MERGE_PROMPT_DATA", REMOVE_GENERATE_IMAGE: 'REMOVE_GENERATE_IMAGE', + ADD_NEW_BOOK_TASK: "ADD_NEW_BOOK_TASK", + REPLACE_BOOK_DATA: "REPLACE_BOOK_DATA", COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD', diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index 9b7bf88..c3dba87 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -17,6 +17,16 @@ export enum BookImageCategory { D3 = 'd3' } +export enum AddBookTaskCopyData { + AFTER_GPT = 'after_gpt', // 文案 + OLD_IMAGE = 'old_image', // 抽帧/视频 + GPT_PROMPT = 'gpt_prompt', // 反推/GPT提示词 + CHARACTER = 'character', // 角色 + IMAGE_STYLE = 'image_style', // 风格 + PROMPT = 'prompt', // 生图提示词 + IMAGE = 'image', // 生图 +} + export enum MJCategroy { @@ -234,3 +244,15 @@ export enum PromptMergeType { // D3 合并 D3_MERGE = 'd3_merge' } + +/** + * 小说数据替换类型 + */ +export enum BookRepalceDataType { + // 文案 + AFTER_GPT = 'after_gpt', + // GPT提示词 + GPT_PROMPT = 'gpt_prompt', + // 提示词 + PROMPT = 'prompt', +} diff --git a/src/define/gptDefine.js b/src/define/gptDefine.js index 988122f..ab6efe0 100644 --- a/src/define/gptDefine.js +++ b/src/define/gptDefine.js @@ -1,5 +1,5 @@ let fspromises = require('fs').promises -import { cloneDeep, get } from 'lodash' +import { cloneDeep, get, values } from 'lodash' import { define } from './define' const { v4: uuidv4 } = require('uuid') import { apiUrl } from './api/apiUrlDefine' @@ -69,6 +69,61 @@ export const gptDefine = { id: 'a93b693e-bb3f-406d-9730-cba43a6585e4' }, + superSinglePromptChineseSystemContent: { + prompt_name: '超级无敌单帧-中文版', + prompt_roles: `# Role: 小说转漫画提示词大师 + ## Profile + + *Author*: laolu + *Version*: 0.1 + *Language*: 中文 + *Description*: 这个角色会将用户输入的小说文本转化为一个生动的画面描写,最后生成对应的SD提示词。 + + ## Features + + 1. 文本转化为画面描写:创作引人入胜、生动有趣的画面描写,善于创意想象并使用各种形容词,以第三人称视角转化文本为画面描写。 + + ## Rules + + 1. 一个文本就是一副画面,不跳过任何一个句子,不能编造 + 2. 【画面描写】删除人物姓名 + 3. 【画面描写】删除人物对话 + 4. 【画面描写】每一句都要有人物的外形和动作的描写,场景的具体描写,多使用形容词 + + ## Examples + + 用户: + 在那个梦里,我整整学了七年炒饭。 + AI: + 一个身材高大的帅气男人站在梦境之门前,手中紧握炒饭的铲子。身后是一排代表七年的日历页,梦境之门的另一边,隐约可见一个厨房的轮廓。 + + ## Workflow + + 根据文本生成对应的画面描写,直接使用中文数据,不要又过多的说明。 + + ## Initialization + + 作为角色 ,每一次输出都要严格遵守,一步一步思考,按顺序执行 ,使用默认 ,下面是小说文本:`, + prompt_example: [ + { + user_content: '上研究生后。发现导师竟然是曾经网恋的前男友。', + assistant_content: + '一位女士走进一间宽敞、光线充足的研究生实验室,她的目光定格在前方工作台前一位正在辛勤工作的男士身上--他是她的新导师;他身着深色衬衫和熨烫整齐的长裤,身材高大,散发着专业精神和魅力; 温暖的环境光柔和地照亮了他们过去网恋时熟悉的轮廓,皱起的眉头和专注的目光透露出学者坚定不移的奉献精神;熙熙攘攘的研究生和精密的设备融为一体,构成了一幅现代学术画卷,她的内心涌动着甜蜜的怀旧和尴尬的现实这两种复杂的情绪' + }, + { + user_content: '我欲哭无泪', + assistant_content: + '一位女士坐在昏暗的房间角落,双手紧握,眼神空洞,脸上流露出无法言说的悲伤;她的周围是散乱的家具和昏黄的灯光,营造出一种压抑和孤寂的氛围;她的身体微微前倾,仿佛承受着巨大的心理压力,却无法找到宣泄的出口;整个场景透露出一种深深的无奈和心碎,她的表情和姿态都在无声地诉说着“我欲哭无泪”的绝望。' + }, + { + user_content: '被窝里他搂着我的腰亲我,再丢掉我,就把你锁起来。', + assistant_content: + '在昏暗的卧室中,一对情侣亲密地依偎在温暖的被窝里,男子温柔地搂着女子的腰,轻吻她的脸颊,气氛温馨而浪漫;然而,随着话语的转变,男子的动作突然变得愤怒,他用力地推开女子,眼神中闪过一丝威胁;女子的表情由幸福转为惊恐,她紧紧抓住被单,试图保护自己;整个场景充满了紧张和不安,昏暗的灯光和凌乱的床铺加剧了这种氛围,仿佛预示着即将发生的冲突和束缚' + } + ], + id: 'a93b693e-bb3f-406d-9730-cba43a6585e7' + }, + onlyPromptMJSystemContent: { prompt_name: '小说提示词-仅出词', prompt_roles: `# Pico: 小说分镜 @@ -208,6 +263,8 @@ export const gptDefine = { return this.CustomizeGptPrompt(this.superSinglePromptSystemContent) } else if (type == 'onlyPromptMJ') { return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent) + } else if (type == 'superSinglePromptChinese') { + return this.CustomizeGptPrompt(this.superSinglePromptChineseSystemContent) } else { return [] } @@ -233,6 +290,8 @@ export const gptDefine = { return this.replace(this.cartoonFirstPromptSystemContent, replacements) case 'superSinglePrompt': return this.replace(this.superSinglePromptSystemContent, replacements) + case 'superSinglePromptChinese': + return this.replace(this.superSinglePromptChineseSystemContent, replacements) default: throw new Error(`不存在的类型 : ${type}`) } @@ -307,6 +366,10 @@ export const gptDefine = { value: 'superSinglePrompt', label: '超级无敌单帧' }, + { + value: 'superSinglePromptChinese', + label: '超级无敌单帧-中文版' + }, { value: 'onlyPromptMJ', label: '仅出词(不出人物场景-MJ)' diff --git a/src/main/IPCEvent/bookIpc.ts b/src/main/IPCEvent/bookIpc.ts index 5af27bd..9f947be 100644 --- a/src/main/IPCEvent/bookIpc.ts +++ b/src/main/IPCEvent/bookIpc.ts @@ -13,6 +13,7 @@ import { Watermark } from '../Service/watermark' import { SubtitleService } from '../Service/Subtitle/subtitleService' import { BookFrame } from '../Service/Book/bookFrame' import { BookPrompt } from '../Service/Book/bookPrompt' +import { BookGeneral } from '../Service/Book/bookGeneral' let reverseBook = new ReverseBook() let basicReverse = new BasicReverse() let subtitle = new Subtitle() @@ -26,6 +27,7 @@ let watermark = new Watermark() let subtitleService = new SubtitleService() let bookFrame = new BookFrame() let bookPrompt = new BookPrompt(); +let bookGeneral = new BookGeneral() export function BookIpc() { // 获取样式图片的子列表 @@ -89,6 +91,12 @@ export function BookIpc() { //#endregion + //#region 小说通用操作 + + ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_BOOK_DATA, async (event, bookTaskId, replaceData) => await bookGeneral.ReplaceBookData(bookTaskId, replaceData)) + + //#endregion + //#region 分镜相关 // 开始计算分镜 ipcMain.handle( @@ -99,7 +107,7 @@ export function BookIpc() { // 开始执行分镜,切分视频 ipcMain.handle( DEFINE_STRING.BOOK.FRAMING, - async (event, bookId) => await reverseBook.Framing(bookId) + async (event, bookId) => await bookFrame.Framing(bookId) ) // 替换分镜视频的当前帧 @@ -149,6 +157,13 @@ export function BookIpc() { async (event, bookTaskId) => await subtitleService.ExportCopywriting(bookTaskId) ) + // 将文案文件导入到小说中 + ipcMain.handle( + DEFINE_STRING.BOOK.IMPORT_COPYWRITING, + async (event, bookId, bookTaskId, txtPath) => await subtitleService.ImportCopywriting(bookId, bookTaskId, txtPath) + + ) + // 清除导入对齐的文案 ipcMain.handle(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, async (event, bookTaskId) => await subtitleService.ClearImportWord(bookTaskId)) @@ -243,6 +258,9 @@ export function BookIpc() { //#region 小说批次任务相关 + // 新建小说批次任务 + ipcMain.handle(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, async (event, addBookTaskData) => await bookTask.AddNewBookTask(addBookTaskData)) + // 重置小说批次数据 ipcMain.handle( DEFINE_STRING.BOOK.RESET_BOOK_TASK, diff --git a/src/main/Public/GPT.js b/src/main/Public/GPT.js index 5717075..812c2ed 100644 --- a/src/main/Public/GPT.js +++ b/src/main/Public/GPT.js @@ -106,7 +106,11 @@ export class GPT { let single_word = element.after_gpt // 判断当前的格式 - if (['superSinglePrompt', 'onlyPromptMJ'].includes(this.global.config.gpt_auto_inference)) { + if ( + ['superSinglePrompt', 'onlyPromptMJ', 'superSinglePromptChinese'].includes( + this.global.config.gpt_auto_inference + ) + ) { // 有返回案例的 message = gptDefine.GetExamplePromptMessage(this.global.config.gpt_auto_inference) // 加当前提问的 diff --git a/src/main/Service/Book/BooKBasic.ts b/src/main/Service/Book/BooKBasic.ts index 8535708..d6034c6 100644 --- a/src/main/Service/Book/BooKBasic.ts +++ b/src/main/Service/Book/BooKBasic.ts @@ -7,6 +7,7 @@ import { GeneralResponse } from '../../../model/generalResponse' import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' import { BookTask } from './bookTask' import fs from 'fs' +import { Book } from '../../../model/book' export class BookBasic { bookServiceBasic: BookServiceBasic @@ -34,7 +35,7 @@ export class BookBasic { return res } catch (error) { return errorMessage( - '修改数据错误,错误信息如下:' + error.message, + '修改数据错误,错误信息如下:' + error.toString(), 'BookBasic_AddOrModifyBook' ) } @@ -63,6 +64,32 @@ export class BookBasic { //#endregion + //#region 小说批次基础操作 + + /** + * 通过小说ID和小说批次任务ID获取小说和小说批次任务数据 + * @param bookId 小说ID + * @param bookTaskName 小说批次的名字,或者是小说批次任务的ID + * @returns + */ + async GetBookAndTask(bookId: string, bookTaskName: string) { + let book = await this.bookServiceBasic.GetBookDataById(bookId) + // 获取小说对应的批次任务数据,默认初始化为第一个 + let condition = { + bookId: bookId + } as Book.QueryBookBackTaskCondition + if (bookTaskName == "output_00001") { + condition["name"] = bookTaskName + } else { + condition["id"] = bookTaskName + } + let bookTaskRes = await this.bookServiceBasic.GetBookTaskData(condition) + return { book: book as Book.SelectBook, bookTask: bookTaskRes.bookTasks[0] as Book.SelectBookTask } + } + + //#endregion + + //#region 小说相关操作 /** diff --git a/src/main/Service/Book/ReverseBook.ts b/src/main/Service/Book/ReverseBook.ts index b7172a8..a93ce8b 100644 --- a/src/main/Service/Book/ReverseBook.ts +++ b/src/main/Service/Book/ReverseBook.ts @@ -21,6 +21,7 @@ import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic' import { SDOpt } from '../SD/sd' + /** * 一键反推的相关操作 */ @@ -33,6 +34,7 @@ export class ReverseBook { subtitle: Subtitle watermark: Watermark bookServiceBasic: BookServiceBasic + bookBasic: BookBasic constructor() { this.basicReverse = new BasicReverse() @@ -41,6 +43,7 @@ export class ReverseBook { this.watermark = new Watermark() this.taskScheduler = new TaskScheduler() this.bookServiceBasic = new BookServiceBasic() + this.bookBasic = new BookBasic() } // 主动返回前端的消息 sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) { @@ -189,32 +192,12 @@ export class ReverseBook { } } - /** - * 通过小说ID和小说批次任务ID获取小说和小说批次任务数据 - * @param bookId 小说ID - * @param bookTaskName 小说批次的名字,或者是小说批次任务的ID - * @returns - */ - async GetBookAndTask(bookId: string, bookTaskName: string) { - let book = await this.bookServiceBasic.GetBookDataById(bookId) - // 获取小说对应的批次任务数据,默认初始化为第一个 - let condition = { - bookId: bookId - } as Book.QueryBookBackTaskCondition - if (bookTaskName == "output_00001") { - condition["name"] = bookTaskName - } else { - condition["id"] = bookTaskName - } - let bookTaskRes = await this.bookServiceBasic.GetBookTaskData(condition) - return { book: book as Book.SelectBook, bookTask: bookTaskRes.bookTasks[0] as Book.SelectBookTask } - } /** * 开始分镜任务 */ async ComputeStoryboard(bookId: string): Promise { try { - let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') + let { book, bookTask } = await this.bookBasic.GetBookAndTask(bookId, 'output_00001') let res = await this.basicReverse.ComputeStoryboardFunc(bookId, bookTask.id); // 分镜成功直接返回 return successMessage(null, res, "ReverseBook_ComputeStoryboard") @@ -224,62 +207,6 @@ export class ReverseBook { } } - /** - * 开始进行分镜,切割视频并抽帧 - * @param bookId - */ - async Framing(bookId: string): Promise { - try { - let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001') - let bookTaskDetail = undefined as Book.SelectBookTaskDetail[] - try { - // 获取所有的分镜数据 - bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ - bookId: bookId, - bookTaskId: bookTask.id - }) - } catch (error) { - // 传入的分镜数据为空,需要重新获取 - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - `没有传入分镜数据,请先进行分镜计算`, - OtherData.DEFAULT, - LoggerStatus.DOING - ) - throw new Error("没有传入分镜数据,请先进行分镜计算"); - } - let bookTaskDetails = bookTaskDetail; - - for (let i = 0; i < bookTaskDetails.length; i++) { - const item = bookTaskDetails[i]; - let res = await this.basicReverse.FrameDataToCutVideoData(item); - } - await this.taskScheduler.AddLogToDB( - bookId, - book.type, - "所有的视频裁剪完成,开始抽帧", - bookTask.id, - LoggerStatus.SUCCESS - ) - - bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ - bookId: bookId, - bookTaskId: bookTask.id - }) - - bookTaskDetails = bookTaskDetail as Book.SelectBookTaskDetail[] - for (let i = 0; i < bookTaskDetails.length; i++) { - const item = bookTaskDetails[i]; - let res = await this.basicReverse.FrameFunc(book, item); - } - // 分镜成功直接返回 - return successMessage(null, "所有的视频裁剪,抽帧完成", "ReverseBook_Framing") - - } catch (error) { - return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing') - } - } //#endregion //#region 反推相关任务 diff --git a/src/main/Service/Book/basicReverse.ts b/src/main/Service/Book/basicReverse.ts index 44df9b1..8c30db1 100644 --- a/src/main/Service/Book/basicReverse.ts +++ b/src/main/Service/Book/basicReverse.ts @@ -294,7 +294,7 @@ export class BasicReverse { } // 找到对应的小说ID和对应的小说批次任务ID,判断是不是有分镜数据 - let bookTaskRes = await this.bookTaskService.GetBookTaskData({ + let bookTaskRes = this.bookTaskService.GetBookTaskData({ bookId: bookId, name: 'output_00001' }) @@ -351,7 +351,7 @@ export class BasicReverse { for (let i = 0; i < bookTaskDetail.data.length; i++) { const element = bookTaskDetail.data[i] // 创建后台的视频分割任务 - let taskRes = await this.bookBackTaskListService.AddBookBackTask( + let taskRes = this.bookBackTaskListService.AddBookBackTask( bookId, BookBackTaskType.SPLIT, TaskExecuteType.AUTO, @@ -381,7 +381,7 @@ export class BasicReverse { * @param bookTaskDetailId 小说详细的分镜ID * @returns */ - async FrameDataToCutVideoData(bookTaskDetail: Book.SelectBookTaskDetail): Promise { + async FrameDataToCutVideoData(bookTaskDetail: Book.SelectBookTaskDetail, frameShortClipData: Book.BookFrameShortClip): Promise { await this.InitService() let startTime = bookTaskDetail.startTime let endTime = bookTaskDetail.endTime @@ -402,9 +402,9 @@ export class BasicReverse { let outVideoFile = path.join(book.bookFolderPath, `data/frame/${bookTaskDetail.name}.mp4`) let res = await this.ffmpegOptions.FfmpegCutVideo( - startTime, - endTime, - book.oldVideoPath, + frameShortClipData.startTime, + frameShortClipData.endTime, + frameShortClipData.videoPath, outVideoFile ) if (res.code == 0) { @@ -454,7 +454,7 @@ export class BasicReverse { throw new Error('没有找到对应的分镜数据') } - let cur_res = await this.FrameDataToCutVideoData(bookTaskDetail); + // let cur_res = await this.FrameDataToCutVideoData(bookTaskDetail, null); return successMessage(null, `${task.name}_视频裁剪完成`, "BasicReverse_CutVideoData"); } catch (error) { diff --git a/src/main/Service/Book/bookFrame.ts b/src/main/Service/Book/bookFrame.ts index bd88638..27ce5a3 100644 --- a/src/main/Service/Book/bookFrame.ts +++ b/src/main/Service/Book/bookFrame.ts @@ -6,13 +6,24 @@ import path from 'path'; import { FfmpegOptions } from "../ffmpegOptions"; import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; import fs from 'fs'; +import { Book } from "../../../model/book"; +import { TaskScheduler } from '../taskScheduler'; +import { BookBasic } from "./BooKBasic"; +import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum"; +import { BasicReverse } from "./basicReverse"; export class BookFrame { bookServiceBasic: BookServiceBasic ffmpegOptions: FfmpegOptions + taskScheduler: TaskScheduler + basicReverse: BasicReverse + bookBasic: BookBasic constructor() { this.bookServiceBasic = new BookServiceBasic(); this.ffmpegOptions = new FfmpegOptions(); + this.taskScheduler = new TaskScheduler() + this.bookBasic = new BookBasic() + this.basicReverse = new BasicReverse() } @@ -56,4 +67,130 @@ export class BookFrame { } } + //#region 进行分镜截取的一些操作 + + /** + * 预处理视频,将视频切割成小段,减少计算时间 + * @param book 小说数据 + * @param bookTaskDetails 分镜数据 + */ + async FrameShortClip(book: Book.SelectBook, bookTaskDetails: Book.SelectBookTaskDetail[], duration: number): Promise { + let result = [] as Book.BookFrameShortClip[] // 返回的数据 + let durationTime = 0; // 小视频片段的持续时间 + let tempCount = 0; + let videoPath = book.oldVideoPath + `_${tempCount}.mp4`; // 新的视频路径 + let startTime = 0; // 开始时间 + let endTime = 0; // 结束时间 + let lastEndTime = 0; // 上一个结束时间 + for (let i = 0; i < bookTaskDetails.length; i++) { + const item = bookTaskDetails[i]; + let temRes = { + startTime: item.startTime - lastEndTime, + endTime: item.endTime - lastEndTime, + videoPath: videoPath, + duration: item.endTime - item.startTime + } + endTime = item.endTime; + durationTime += item.endTime - item.startTime; + if (durationTime > duration) { // 判断条件切割视频 + // 开始切割视频 + await this.ffmpegOptions.FfmpegCutVideo( + startTime, + endTime, + book.oldVideoPath, + videoPath + ) + lastEndTime = item.endTime; + tempCount++; + durationTime = 0; + startTime = endTime; + endTime = 0; + videoPath = book.oldVideoPath + `_${tempCount}.mp4`; + } + result.push(temRes) + } + // 最后一个也要切割 + if (durationTime > 0) { + await this.ffmpegOptions.FfmpegCutVideo( + startTime, + endTime, + book.oldVideoPath, + videoPath + ) + } + return result; + } + + + /** + * 开始进行分镜,切割视频并抽帧 + * @param bookId + */ + async Framing(bookId: string): Promise { + try { + let { book, bookTask } = await this.bookBasic.GetBookAndTask(bookId, 'output_00001') + let bookTaskDetail = undefined as Book.SelectBookTaskDetail[] + try { + // 获取所有的分镜数据 + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + } catch (error) { + // 传入的分镜数据为空,需要重新获取 + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + `没有传入分镜数据,请先进行分镜计算`, + OtherData.DEFAULT, + LoggerStatus.DOING + ) + throw new Error("没有传入分镜数据,请先进行分镜计算"); + } + let bookTaskDetails = bookTaskDetail; + + // 这边重新开始计算,再次切割视频(预切割,减少长视频的计算时间) + let shortClipData = await this.FrameShortClip(book, bookTaskDetails, 180000); + console.log(shortClipData) + + if (shortClipData.length != bookTaskDetails.length) { + throw new Error('切割短视频和分镜数据不一致,请检查') + } + // 开始切割视频 + for (let i = 0; i < bookTaskDetails.length; i++) { + const item = bookTaskDetails[i]; + if (!shortClipData[i]) { + throw new Error('切割短视频和分镜数据不一致,请检查') + } + let res = await this.basicReverse.FrameDataToCutVideoData(item, shortClipData[i]); + } + await this.taskScheduler.AddLogToDB( + bookId, + book.type, + "所有的视频裁剪完成,开始抽帧", + bookTask.id, + LoggerStatus.SUCCESS + ) + + bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ + bookId: bookId, + bookTaskId: bookTask.id + }) + + bookTaskDetails = bookTaskDetail as Book.SelectBookTaskDetail[] + for (let i = 0; i < bookTaskDetails.length; i++) { + const item = bookTaskDetails[i]; + let res = await this.basicReverse.FrameFunc(book, item); + } + // 分镜成功直接返回 + return successMessage(null, "所有的视频裁剪,抽帧完成", "ReverseBook_Framing") + + } catch (error) { + return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing') + } + } + + + //#endregion + } \ No newline at end of file diff --git a/src/main/Service/Book/bookGeneral.ts b/src/main/Service/Book/bookGeneral.ts new file mode 100644 index 0000000..8671a27 --- /dev/null +++ b/src/main/Service/Book/bookGeneral.ts @@ -0,0 +1,198 @@ +import { isEmpty } from "lodash"; +import { BookRepalceDataType } from "../../../define/enum/bookEnum"; +import { Book } from "../../../model/book"; +import { GeneralResponse } from "../../../model/generalResponse"; +import { errorMessage, successMessage } from "../../Public/generalTools"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; + +/** + * 小说通用操作的类 + */ +export class BookGeneral { + bookServiceBasic: BookServiceBasic + constructor() { + this.bookServiceBasic = new BookServiceBasic() + } + + //#region 批量替换数据的操作 + + /** + * 替换after_gpt 和 word 中的数据 + * @param bookTask + * @param bookTaskDetail + * @param replaceData + */ + ReplaceAfterGpt(bookTask: Book.SelectBook, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] { + let result = []; + // 修改放在一个事务中 + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < bookTaskDetail.length; i++) { + let element = bookTaskDetail[i]; + let afterGpt = element.afterGpt + // 判断是否存在before的数据 + if (!afterGpt.includes(replaceData.before)) { + continue + } + let newAfterGpt = afterGpt.replaceAll(replaceData.before, replaceData.after); + let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id) + btd.afterGpt = newAfterGpt + result.push({ + bookTaskDetailId: element.id, + newData: newAfterGpt + } as Book.ReplaceDataRes) + } + }) + return result + } + + /** + * 替换反推提示词中的数据 + * @param realm realm对象 + * @param bookTaskDetail 小说分镜数据 + * @param replaceData 替换的数据 + * @returns + */ + ReplaceRversePrompt(realm: any, bookTaskDetail: Book.SelectBookTaskDetail, replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] { + let result = []; + if (bookTaskDetail.reversePrompt && bookTaskDetail.reversePrompt.length > 0) { + for (let k = 0; k < bookTaskDetail.reversePrompt.length; k++) { + const item = bookTaskDetail.reversePrompt[k]; + if (!isEmpty(item.prompt) && item.prompt.includes(replaceData.before)) { + let newPrompt = item.prompt.replaceAll(replaceData.before, replaceData.after) + let rsp = realm.objectForPrimaryKey("ReversePrompt", item.id) + rsp.prompt = newPrompt + result.push({ + bookTaskDetailId: bookTaskDetail.id, + newData: newPrompt, + type: 'reversePrompt', + reversePromptType: 'prompt', + reversePromptId: item.id + } as Book.ReplaceDataRes) + } + if (!isEmpty(item.promptCN) && item.promptCN.includes(replaceData.before)) { + let newPrompt = item.promptCN.replaceAll(replaceData.before, replaceData.after) + let rsp = realm.objectForPrimaryKey("ReversePrompt", item.id) + rsp.promptCN = newPrompt + result.push({ + bookTaskDetailId: bookTaskDetail.id, + newData: newPrompt, + type: 'reversePrompt', + reversePromptType: 'promptCN', + reversePromptId: item.id + } as Book.ReplaceDataRes) + } + } + } + return result; + } + + /** + * 替换反推提示词词中的数据 + * @param bookTask 小说任务 + * @param bookTaskDetail 小说详细任务 + * @param replaceData 替换的数据 + * @returns + */ + ReplaceGPTPrompt(bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] { + let result = []; + // 修改放在一个事务中 + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < bookTaskDetail.length; i++) { + let element = bookTaskDetail[i]; + // 替换GPT提示词 + if (!isEmpty(element.gptPrompt) && element.gptPrompt.includes(replaceData.before)) { + let newGptPrompt = element.gptPrompt.replaceAll(replaceData.before, replaceData.after) + let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id) + btd.gptPrompt = newGptPrompt + result.push({ + bookTaskDetailId: element.id, + newData: newGptPrompt, + type: 'gptPrompt' + } as Book.ReplaceDataRes) + } + + if (replaceData.replaceAll) { + // 替换反推提示词,有的话 + let res = this.ReplaceRversePrompt(realm, element, replaceData); + result.push(...res) + } else { + if (!isEmpty(element.reversePrompt)) { + continue; + } + // 这边处理反推提示词,有的话 + let res = this.ReplaceRversePrompt(realm, element, replaceData); + result.push(...res) + } + + } + }) + return result + } + + /** + * 替换生图提示词里面的数据 + * @param bookTask 小说批次任务 + * @param bookTaskDetail 小说分镜任务信息 + * @param replaceData 替换的数据信息 + */ + ReplacePromptData(bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail[], replaceData: Book.BookReplaceData): Book.ReplaceDataRes[] { + let res = [] + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + let prompt = element.prompt + if (isEmpty(prompt) || !prompt.includes(replaceData.before)) { + continue + } + let newPrompt = prompt.replaceAll(replaceData.before, replaceData.after); + let btd = realm.objectForPrimaryKey('BookTaskDetail', element.id) + btd.prompt = newPrompt + res.push({ + bookTaskDetailId: element.id, + newData: newPrompt, + type: 'prompt' + } as Book.ReplaceDataRes) + } + }) + return res + } + + /** + * 批量替换小说中的一些数据,这个方法会根据传递的数据进行替换 + * @param bookTaskId 小说任务ID + * @param replaceData 替换的数据 + */ + async ReplaceBookData(bookTaskId: string, replaceData: Book.BookReplaceData): Promise { + try { + console.log(bookTaskId, replaceData) + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId) + let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: bookTaskId + }) + if (bookTaskDetails.length <= 0) { + throw new Error("未找到对应的小说任务批次任务数据"); + } + let res = [] + // 根据type调用对应的方法 + switch (replaceData.type) { + case BookRepalceDataType.AFTER_GPT: + res = this.ReplaceAfterGpt(bookTask, bookTaskDetails, replaceData) + break; + case BookRepalceDataType.GPT_PROMPT: + res = this.ReplaceGPTPrompt(bookTask, bookTaskDetails, replaceData) + break; + case BookRepalceDataType.PROMPT: + res = this.ReplacePromptData(bookTask, bookTaskDetails, replaceData) + break; + default: + throw new Error("未找到对应的替换类型"); + + } + return successMessage(res, '替换所有的提示词数据成功', 'BookGeneral_ReplaceBookData') + } catch (error) { + return errorMessage('替换失败,错误信息如下:' + error.toString(), 'BookGeneral_ReplaceBookData') + } + } + + //#endregion +} \ No newline at end of file diff --git a/src/main/Service/Book/bookTask.ts b/src/main/Service/Book/bookTask.ts index 78ac24c..8a81ab6 100644 --- a/src/main/Service/Book/bookTask.ts +++ b/src/main/Service/Book/bookTask.ts @@ -1,38 +1,232 @@ -import { CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; -import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; -import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; -import { BookService } from "../../../define/db/service/Book/bookService"; -import { BookTaskStatus, CopyImageType, OperateBookType } from "../../../define/enum/bookEnum"; +import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file"; +import { AddBookTaskCopyData, BookImageCategory, BookTaskStatus, CopyImageType, OperateBookType } from "../../../define/enum/bookEnum"; import { errorMessage, successMessage } from "../../Public/generalTools"; import { Book } from "../../../model/book"; import path from 'path' -import { cloneDeep, isEmpty } from "lodash"; +import { add, cloneDeep, isEmpty } from "lodash"; import { define } from '../../../define/define' import { v4 as uuidv4 } from 'uuid' import { GeneralResponse } from "../../../model/generalResponse"; +import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; +import { ValidateJson } from "../../../define/Tools/validate"; +import fs from 'fs' +import { TimeStringToMilliseconds } from "../../../define/Tools/time"; /** * 小说批次相关的操作 */ export class BookTask { - bookTaskService: BookTaskService - bookTaskDetailService: BookTaskDetailService - bookService: BookService + bookServiceBasic: BookServiceBasic constructor() { + this.bookServiceBasic = new BookServiceBasic() + } + + //#region 添加小说批次任务 + + /** + * 复制小说批次任务的基础数据 + * @param oldBookTaskDetail 需要需要复制的小说批次任务 + */ + async CopyBookTaskDetailBaseData(bookTask: Book.SelectBookTask, newBookTask: Book.SelectBookTask, oldBookTaskDetail: Book.SelectBookTaskDetail[], addNewBookTask: Book.AddBookTask): Promise { + let bookTaskDetail = [] as Book.SelectBookTaskDetail[] + let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId) + let originalTimePath = path.join(book.bookFolderPath, `data/${book.id}.mp4.json`); + let originalTime = [] as any[]; + if (await CheckFileOrDirExist(originalTimePath)) { + let originalTimeString = await fs.promises.readFile(originalTimePath, 'utf-8'); + if (ValidateJson(originalTimeString)) { + originalTime = JSON.parse(originalTimeString) + } + } + // 判断分镜数据和批次数据是不是相同的 + if (originalTime.length != oldBookTaskDetail.length) { + originalTime = [] + } + for (let i = 0; i < oldBookTaskDetail.length; i++) { + const element = oldBookTaskDetail[i]; + let addOneBookTaskDetail = { + id: uuidv4(), + no: element.no, + name: element.name, + bookId: element.bookId, + bookTaskId: newBookTask.id, + videoPath: path.relative(define.project_path, element.videoPath), + oldImage: path.relative(define.project_path, element.oldImage), + adetailer: element.adetailer, + sdConifg: element.sdConifg, + createTime: new Date(), + updateTime: new Date(), + audioPath: element.audioPath, + subtitlePosition: element.subtitlePosition, + status: BookTaskStatus.WAIT, + imageLock: false, + } as Book.SelectBookTaskDetail + + // 开始添加数据 + if (bookTask && addNewBookTask.selectTaskDataCategory && addNewBookTask.selectTaskDataCategory.length > 0) { + // 有选择的数据,这边要调用各种方法 + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.AFTER_GPT)) { + // 是不是要复制文案 + addOneBookTaskDetail.word = element.word + addOneBookTaskDetail.afterGpt = element.afterGpt + addOneBookTaskDetail.subValue = element.subValue ? JSON.stringify(element.subValue) : undefined + addOneBookTaskDetail.oldImage = element.oldImage + addOneBookTaskDetail.startTime = element.startTime + addOneBookTaskDetail.endTime = element.endTime + addOneBookTaskDetail.timeLimit = element.timeLimit; + } else { + // 初始化时间信息 + if (originalTime[i]) { + addOneBookTaskDetail.startTime = TimeStringToMilliseconds(originalTime[i][0]) + addOneBookTaskDetail.endTime = TimeStringToMilliseconds(originalTime[i][1]) + addOneBookTaskDetail.timeLimit = undefined; + } + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.GPT_PROMPT)) { + // 是不是要复制GPT提示词 + addOneBookTaskDetail.gptPrompt = element.gptPrompt + // 复制反推提示词 + // 处理反推数据 + 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 = uuidv4() + reverseMessage[k].bookTaskDetailId = element.id + } + } + addOneBookTaskDetail.reversePrompt = reverseMessage + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.CHARACTER)) { + // 是不是要复制角色 + addOneBookTaskDetail.characterTags = element.characterTags + } + if (addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.PROMPT)) { + // 是不是要复制提示 + addOneBookTaskDetail.prompt = element.prompt + } + + } + bookTaskDetail.push(addOneBookTaskDetail) + } + return bookTaskDetail + } + + /** + * 复制一份小说任务的基础数据 + * @param bookTask 小说任务 + * @param addNewBookTask 新增的小说数据 + * @param no 当前的编号 + * @returns + */ + CopyBookTaskBaseData(bookTask: Book.SelectBookTask, addNewBookTask: Book.AddBookTask, no: number): Book.SelectBookTask { + let name = 'output_' + no.toString().padStart(5, '0'); + let imageFolder = path.join(define.project_path, `${bookTask.bookId}/tmp/${name}`); + let newBookTask = { + id: uuidv4(), + bookId: bookTask.bookId, + no: no, + name: name, + generateVideoPath: undefined, + srtPath: bookTask.srtPath, + audioPath: bookTask.audioPath, + imageFolder: path.relative(define.project_path, imageFolder), + status: BookTaskStatus.WAIT, + errorMsg: undefined, + updateTime: new Date(), + createTime: new Date(), + isAuto: false, + autoAnalyzeCharacter: undefined, + imageStyle: [], + customizeImageStyle: [], + videoConfig: bookTask.videoConfig ??= undefined, + prefixPrompt: addNewBookTask.prefixPrompt ??= undefined, + suffixPrompt: addNewBookTask.suffixPrompt ?? undefined, + imageCategory: bookTask.imageCategory ??= BookImageCategory.MJ, + subImageFolder: [], + draftSrtStyle: undefined, + backgroundMusic: bookTask.backgroundMusic ??= undefined, + friendlyReminder: bookTask.friendlyReminder ??= undefined, + } as Book.SelectBookTask + if (addNewBookTask.selectTaskDataCategory && addNewBookTask.selectTaskDataCategory.includes(AddBookTaskCopyData.IMAGE_STYLE)) { + newBookTask.imageStyle = bookTask.imageStyle ??= [] + newBookTask.customizeImageStyle = bookTask.customizeImageStyle ??= [] + } + return newBookTask + } + + CopyCopywrittingData() { + + } + + /** + * 添加一个小说批次任务 + * @param bookTask 小说任务 + * @param bookTaskDetails 小说任务分镜信息 + * @param addNewBookTask 添加数据的基础数据信息 + * @returns + */ + async AddOneBookTask(bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[], addNewBookTask: Book.AddBookTask, no: number): Promise<{ newBookTask: Book.SelectBookTask, newBookTaskDetails: Book.SelectBookTaskDetail[] }> { + let newBookTask = this.CopyBookTaskBaseData(bookTask, addNewBookTask, no) + let newBookTaskDetails = await this.CopyBookTaskDetailBaseData(bookTask, newBookTask, bookTaskDetails, addNewBookTask) + return { + newBookTask: newBookTask, + newBookTaskDetails: newBookTaskDetails + } } - async InitService() { - if (!this.bookTaskService) { - this.bookTaskService = await BookTaskService.getInstance() - } - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } if (!this.bookService) { - this.bookService = await BookService.getInstance() + + /** + * 添加个新的小说批次任务 + * @param AddNewBookTask 添加的小说任务数据 + */ + async AddNewBookTask(addNewBookTask: Book.AddBookTask): Promise { + try { + console.log(addNewBookTask) + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(addNewBookTask.selectBookTask) + let oldBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ + bookTaskId: addNewBookTask.selectBookTask + }) + let bookTasks = [] as Book.SelectBookTask[] + let bookTaskDetail = [] as Book.SelectBookTaskDetail[] + + let maxNo = await this.bookServiceBasic.GetMaxBookTaskNo(bookTask.bookId) + for (let i = 0; i < addNewBookTask.count; i++) { + if (addNewBookTask.copyBookTask) { + let { newBookTask, newBookTaskDetails } = await this.AddOneBookTask(bookTask, oldBookTaskDetail, addNewBookTask, maxNo + i) + bookTasks.push(newBookTask) + bookTaskDetail.push(...newBookTaskDetails) + } else { + let newBookTask = this.CopyBookTaskBaseData({ bookId: bookTask.bookId }, addNewBookTask, maxNo + i) + bookTasks.push(newBookTask); + } + } + // 先要创建文件夹 + for (let i = 0; i < bookTasks.length; i++) { + let imageFolder = path.join(define.project_path, bookTasks[i].imageFolder) + await CheckFolderExistsOrCreate(imageFolder) + } + + // 这边开始添加数据 + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < bookTasks.length; i++) { + const element = bookTasks[i]; + realm.create('BookTask', element) + } + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + realm.create('BookTaskDetail', element) + } + }) + return successMessage(null, "添加小说批次任务成功", "BookTask_AddNewBookTask") + } catch (error) { + return errorMessage('添加小说批次任务失败,错误信息如下' + error.toString(), "BookTask_AddNewBookTask"); } } + //#endregion + /** * 重置小说任务数据 @@ -40,13 +234,8 @@ export class BookTask { */ async ReSetBookTask(bookTaskId: string): Promise { try { - console.log(bookTaskId) - await this.InitService() - let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId); - if (bookTask == null) { - throw new Error('未找到对应的小说任务') - } - this.bookTaskService.ResetBookTask(bookTaskId); + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId); + await this.bookServiceBasic.ResetBookTask(bookTaskId); // 数据库删除完毕,看是删除对应的文件(主要是图片) let imageFolder = bookTask.imageFolder; // 整个删掉在重建 @@ -64,14 +253,10 @@ export class BookTask { */ async DeleteBookTask(bookTaskId: string): Promise { try { - await this.InitService(); - let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId); - if (bookTask == null) { - throw new Error('未找到对应的小说任务,无法执行删除操作') - } + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId); let imageFolder = bookTask.imageFolder; // 先删除每个批次对应的数据,然后删除批次 - this.bookTaskService.DeleteBookTask(bookTaskId); + await this.bookServiceBasic.DeleteBookTaskData(bookTaskId); // 删除成功,直接把对应的出图文件夹删掉 await DeleteFolderAllFile(imageFolder, true) return successMessage(null, "删除小说批次数据成功", "BookTask_DeleteBookTask") @@ -87,17 +272,15 @@ export class BookTask { */ async OneToFourBookTask(bookTaskId: string) { try { - console.log(bookTaskId) - await this.InitService(); let copyCount = 100 - let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId) + let bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskId) if (bookTask == null) { throw new Error("没有找到对应的数小说任务,请检查数据") } // 获取所有的出图中最少的 - let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ + let bookTaskDetail = (await this.bookServiceBasic.GetBookTaskData({ bookTaskId: bookTaskId - }).data as Book.SelectBookTaskDetail[] + })).bookTasks as Book.SelectBookTaskDetail[] if (bookTaskDetail == null || bookTaskDetail.length <= 0) { throw new Error("没有对应的小说分镜任务,请先添加分镜任务") } @@ -136,17 +319,12 @@ export class BookTask { */ async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) { try { - await this.InitService(); let addBookTask = [] as Book.SelectBookTask[] let addBookTaskDetail = [] as Book.SelectBookTaskDetail[] // 先处理文件夹的创建,包括小说任务的和小说任务分镜的 for (let i = 0; i < copyCount; i++) { - let maxNo = this.bookTaskService.realm - .objects('BookTask') - .filtered('bookId = $0', sourceBookTask.bookId) - .max('no') - let no = maxNo == null ? 1 : Number(maxNo) + 1 + i + let no = await this.bookServiceBasic.GetMaxBookTaskNo(sourceBookTask.bookId) let name = 'output_0000' + no let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`) await CheckFolderExistsOrCreate(imageFolder) @@ -265,21 +443,21 @@ export class BookTask { // 数据处理完毕,开始新增数据 // 将所有的复制才做,全部放在一个事务中 - this.bookTaskService.transaction(() => { + this.bookServiceBasic.transaction((realm) => { for (let i = 0; i < addBookTask.length; i++) { const element = addBookTask[i]; - this.bookTaskService.realm.create('BookTask', element) + realm.create('BookTask', element) } for (let i = 0; i < addBookTaskDetail.length; i++) { const element = addBookTaskDetail[i]; - this.bookTaskDetailService.realm.create('BookTaskDetail', element) + realm.create('BookTaskDetail', element) } }) // 全部创建完成 // 查找到数据,然后全部返回 - let returnBookTask = this.bookTaskService.GetBookTaskData({ + let returnBookTask = (await this.bookServiceBasic.GetBookTaskData({ bookId: sourceBookTask.bookId - }).data as Book.SelectBookTask[] + })).bookTasks as Book.SelectBookTask[] return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask") } catch (error) { diff --git a/src/main/Service/MJ/mj.ts b/src/main/Service/MJ/mj.ts index be626d5..8bd4f9a 100644 --- a/src/main/Service/MJ/mj.ts +++ b/src/main/Service/MJ/mj.ts @@ -5,7 +5,7 @@ import { BookBackTaskListService } from "../../../define/db/service/Book/bookBac import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../define/Tools/file"; import { define } from "../../../define/define" -import { GetImageBase64, ImageSplit } from "../../../define/Tools/image"; +import { CompressImageToSize, GetImageBase64, ImageSplit } from "../../../define/Tools/image"; import MJApi from "./mjApi" import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum"; import { DEFINE_STRING } from "../../../define/define_string"; @@ -214,10 +214,16 @@ export class MJOpt { throw new Error(`${bookTaskDetail.name} 没有需要反推的图片`); } oldImagePath = JoinPath(define.project_path, oldImagePath) - let imageBase64 = await GetImageBase64(oldImagePath) + // let imageBase64 = await GetImageBase64(oldImagePath) + + // 每次执行前压缩图片 + let newImageBase64 = await CompressImageToSize(oldImagePath, 500000); + // 将buffer转换为base64 + let imageBase64 = newImageBase64.toString('base64'); + // 这个就是任务ID let reqRes = await this.mjApi.SubmitMJDescribe({ - image: imageBase64, + image: 'data:image/png;base64,' + imageBase64, taskId: task.id }) if (reqRes == '23') { @@ -394,8 +400,8 @@ export class MJOpt { if (bookTask.prefixPrompt) { promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr } - if (bookTask.prefixPrompt) { - promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.prefixPrompt + if (bookTask.suffixPrompt) { + promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt } promptStr = ' ' + promptStr; promptStr += ` ${cref_url} ${style_url}${suffixParam}` diff --git a/src/main/Service/SD/sd.ts b/src/main/Service/SD/sd.ts index 848fe60..8687b81 100644 --- a/src/main/Service/SD/sd.ts +++ b/src/main/Service/SD/sd.ts @@ -1,11 +1,8 @@ import { Book } from "../../../model/book"; import { GeneralResponse } from "../../../model/generalResponse"; import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools"; -import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService"; -import { BookTaskService } from "../../../define/db/service/Book/bookTaskService"; import { define } from '../../../define/define' import fs from "fs"; -import { ImageStyleDefine } from "../../../define/iamgeStyleDefine"; import { ImageStyle } from "../Book/imageStyle"; import { OperateBookType } from "../../../define/enum/bookEnum"; import { isEmpty } from "lodash"; @@ -13,26 +10,22 @@ import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; const fspromise = fs.promises export class SDOpt { - bookTaskDetailService: BookTaskDetailService - bookTaskService: BookTaskService imageStyle: ImageStyle bookServiceBasic: BookServiceBasic constructor() { this.bookServiceBasic = new BookServiceBasic() + this.imageStyle = new ImageStyle() } - async InitService() { - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } - if (!this.bookTaskService) { - this.bookTaskService = await BookTaskService.getInstance() - } - if (!this.imageStyle) { - this.imageStyle = new ImageStyle() - } + // TODO 这边的设置应该改为数据库 + /** + * 获取SD的设置,之后要删掉,改为数据库 + */ + private async GetSDSetting() { + let sdSetting = JSON.parse(await fspromise.readFile(define.sd_setting, 'utf-8')) + return sdSetting } /** @@ -43,15 +36,16 @@ export class SDOpt { */ async MergePrompt(id: string, operateBookType: OperateBookType): Promise { try { - await this.InitService() let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]; let bookTask = undefined as Book.SelectBookTask; + let sd_setting = await this.GetSDSetting(); + let style_weight = sd_setting.setting.style_weight ? sd_setting.setting.style_weight : 1 if (operateBookType == OperateBookType.BOOKTASK) { - bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({ + bookTaskDetail = (await this.bookServiceBasic.GetBookTaskData({ bookTaskId: id - }).data - bookTask = this.bookTaskService.GetBookTaskDataById(id); + })).bookTasks; + bookTask = await this.bookServiceBasic.GetBookTaskDataById(id); // 判断是不是有为空的 let emptyName = [] as string[] for (let i = 0; i < bookTaskDetail.length; i++) { @@ -69,7 +63,7 @@ export class SDOpt { throw new Error("当前分镜没有推理提示词,请先生成") } bookTaskDetail = [tempBookTaskDetail]; - bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); + bookTask = await this.bookServiceBasic.GetBookTaskDataById(bookTaskDetail[0].bookTaskId); } else { throw new Error("未知的合并类型") } @@ -86,12 +80,12 @@ export class SDOpt { for (let i = 0; i < styleArr.length; i++) { const element = styleArr[i] if (element.type == 'style_main') { - styleString += element.prompt + ', ' + styleString += `(${element.prompt}:${style_weight})` + ', ' if (element.lora && element.lora != '无' && element.lora_weight) { styleString += `, ` } } else { - styleString += element.english_style + ',' + styleString += `(${element.english_style}:${style_weight})` + ',' } } @@ -126,8 +120,8 @@ export class SDOpt { if (bookTask.prefixPrompt) { promptStr = checkStringValueAddSuffix(bookTask.prefixPrompt, ',') + promptStr } - if (bookTask.prefixPrompt) { - promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.prefixPrompt + if (bookTask.suffixPrompt) { + promptStr = checkStringValueAddSuffix(promptStr, ',') + bookTask.suffixPrompt } if (sdGlobalPrompt) { promptStr = checkStringValueAddSuffix(sdGlobalPrompt, ',') + promptStr @@ -136,7 +130,7 @@ export class SDOpt { promptStr = ' ' + promptStr; console.log(promptStr) // 修改数据库数据 - this.bookTaskDetailService.UpdateBookTaskDetail(element.id, { + await this.bookServiceBasic.UpdateBookTaskDetail(element.id, { prompt: promptStr }) // 写回数据 @@ -147,7 +141,6 @@ export class SDOpt { } return successMessage(result, "SD和并提示词数据成功", "SDOpt_MergePrompt") - } catch (error) { return errorMessage("SD合并提示词,错误信息如下:" + error.toString(), "SDOpt_MergePrompt") } diff --git a/src/main/Service/ServiceBasic/bookServiceBasic.ts b/src/main/Service/ServiceBasic/bookServiceBasic.ts index 82230f1..0e28bbc 100644 --- a/src/main/Service/ServiceBasic/bookServiceBasic.ts +++ b/src/main/Service/ServiceBasic/bookServiceBasic.ts @@ -42,6 +42,16 @@ export class BookServiceBasic { global.newWindow[0].win.webContents.send(message_name, data) } + + //#region 事务操作 + transaction(callback: (realm: any) => void) { + this.bookService.transaction(() => { + callback(this.bookService.realm) + }) + } + + //#endregion + //#region 小说相关的基础服务 /** @@ -111,6 +121,20 @@ export class BookServiceBasic { return bookTasks.data } + /** + * 获取最大的小说批次任务的编号 + * @param bookId 小说ID + */ + async GetMaxBookTaskNo(bookId: string): Promise { + await this.InitService(); + let maxNo = this.bookTaskService.realm + .objects('BookTask') + .filtered('bookId = $0', bookId) + .max('no') + let no = maxNo == null ? 1 : Number(maxNo) + 1 + return no + } + /** * 更新小说批次任务的数据 * @param bookTaskId 小说批次任务ID @@ -121,6 +145,15 @@ export class BookServiceBasic { this.bookTaskService.UpdetedBookTaskData(bookTaskId, data) } + /** + * 重置小说批次任务的数据 + * @param bookTaskId 指定的重置的小说批次任务的ID + */ + async ResetBookTask(bookTaskId: string): Promise { + await this.InitService(); + this.bookTaskService.ResetBookTask(bookTaskId) + } + /** * 删除指定的小说批次任务的数据 * @param bookTaskId 需要删除的指定的小说批次任务ID diff --git a/src/main/Service/Subtitle/subtitleService.ts b/src/main/Service/Subtitle/subtitleService.ts index 8023c9e..6f3cd91 100644 --- a/src/main/Service/Subtitle/subtitleService.ts +++ b/src/main/Service/Subtitle/subtitleService.ts @@ -12,7 +12,7 @@ import { CheckFileOrDirExist } from "../../../define/Tools/file"; import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import { Subtitle } from "./subtitle"; import { TaskScheduler } from "../taskScheduler"; -import { OperateBookType } from "../../../define/enum/bookEnum"; +import { BookType, OperateBookType } from "../../../define/enum/bookEnum"; import { Book } from "../../../model/book"; import { TimeStringToMilliseconds } from "../../../define/Tools/time"; @@ -189,8 +189,9 @@ export class SubtitleService { return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting') } } + //#endregion - + //#region 文案相关的操作 /** * 导出指定导出指定小说的文案 * @param bookTaskId 小说批次任务ID @@ -231,7 +232,57 @@ export class SubtitleService { } } - //#region 文案相关的操作 + /** + * 将 + * @param bookId + * @param bookTaskId + * @param txtPath + * @returns + */ + async ImportCopywriting(bookId: string, bookTaskId: string, txtPath: string): Promise { + try { + let word = await fs.promises.readFile(txtPath, 'utf-8'); + let wordLines = word.split('\n').map(line => line.trim()).filter(line => line.length > 0); + // 判断小说类型,是反推的话,文案和分镜数据要对应 + let book = await this.bookServiceBasic.GetBookDataById(bookId) + let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({ + bookId: bookId, + bookTaskId: bookTaskId + }) + + if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) { + if (wordLines.length != bookTaskDetail.length) { + throw new Error("文案行数和分镜数据不对应,请检查") + } + } else if (book.type == BookType.ORIGINAL) { + // 原创这边也要做些判断,待定 + throw new Error("原创小说暂时不支持导入文案"); + } else { + throw new Error("未知的小说类型,请检查") + } + + let result = [] + // 这边开始导入文案,这边使用事务 + this.bookServiceBasic.transaction((realm) => { + for (let i = 0; i < bookTaskDetail.length; i++) { + const element = bookTaskDetail[i]; + let btd = realm.objectForPrimaryKey("BookTaskDetail", element.id); + let ag = wordLines[i] ? wordLines[i] : '' + + btd.afterGpt = ag + result.push({ + bookTaskDetailId: element.id, + afterGpt: ag + }); + } + }) + + return successMessage(result, "导入文案成功", "ReverseBook_ImportCopywriting") + } catch (error) { + return errorMessage("导入文案失败,失败信息如下:" + error.message, 'ReverseBook_ImportCopywriting') + } + } + /** * 清除导入的对齐后的文案 @@ -287,6 +338,4 @@ export class SubtitleService { } //#endregion - - //#endregion } \ No newline at end of file diff --git a/src/main/Service/ffmpegOptions.ts b/src/main/Service/ffmpegOptions.ts index 1959af6..c7645f9 100644 --- a/src/main/Service/ffmpegOptions.ts +++ b/src/main/Service/ffmpegOptions.ts @@ -80,7 +80,7 @@ export class FfmpegOptions { return new Promise((resolve, reject) => { Ffmpeg(tempVideo) .outputOptions([ - `-vf scale=${width}:${height}`, // 调整视频尺寸到 1280x720 + `-vf scale=${Math.floor(width)}:${Math.floor(height)}`, // 调整视频尺寸到 1280x720 `-b:v ${crf}` // 设置视频比特率为 1000kbps ]) .on('end', async () => { diff --git a/src/main/index.js b/src/main/index.js index dd9fd39..4c581df 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -66,12 +66,12 @@ async function createWindow(hash = 'ShowMessage', data, url = null) { webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: false, - nodeIntegration: hash == 'discord' ? false : true, // 在网页中集成Node + nodeIntegration: true, // 在网页中集成Node nodeIntegrationInWorker: true, webSecurity: false, partition: 'persist:my-partition', session: ses, - webviewTag : true, + webviewTag: true } }) diff --git a/src/model/book.d.ts b/src/model/book.d.ts index 2a578cd..092bc50 100644 --- a/src/model/book.d.ts +++ b/src/model/book.d.ts @@ -1,4 +1,4 @@ -import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType } from "../define/enum/bookEnum" +import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, TaskExecuteType, BookRepalceDataType } from "../define/enum/bookEnum" import { MJAction } from "../define/enum/bookEnum" import { MJImageType } from "../define/enum/mjEnum" @@ -76,6 +76,16 @@ declare namespace Book { subImageFolder?: string[] | null // 子图片文件夹地址,多个 } + // 添加批次任务 + type AddBookTask = { + copyBookTask: boolean // 是否复制旧的小说任务 + count: number // 批次数量 + prefixPrompt?: string // 前缀提示 + suffixPrompt?: string // 后缀提示词 + selectBookTask?: string // 选择的旧的小说任务 + selectTaskDataCategory?: string[] // 选择复制的数据类型 + } + // 字幕相关 type Subtitle = { startTime: number @@ -246,4 +256,33 @@ declare namespace Book { id?: string image?: string } + + /** + * 裁剪小视频的返回数据类型 + */ + type BookFrameShortClip = { + startTime: number + endTime: number + videoPath: string + duration: number + } + + //#region 替换小说数据 + + type BookReplaceData = { + before: string, + after: string, + replaceAll?: boolean, + type: BookRepalceDataType + } + + type ReplaceDataRes = { + bookTaskDetailId: string, + newData: string, + type?: 'reversePrompt' | 'gptPrompt' | 'afterGpt' | 'prompt', + reversePromptType?: 'prompt' | 'promptCN' + reversePromptId?: string + } + + //#endregion } diff --git a/src/preload/book.js b/src/preload/book.ts similarity index 91% rename from src/preload/book.js rename to src/preload/book.ts index e9a31e6..9fa4a15 100644 --- a/src/preload/book.js +++ b/src/preload/book.ts @@ -1,5 +1,6 @@ import { ipcRenderer } from 'electron' import { DEFINE_STRING } from '../define/define_string' +import { Book } from '../model/book' const book = { // 获取小说操作类型(原创/SD反推/MJ反推) @@ -51,6 +52,18 @@ const book = { //#endregion + //#region 小说通用操作 + + // 替换文案数据 + ReplaceBookData: async (bookTaskId: string, replaceData: Book.BookReplaceData) => + await ipcRenderer.invoke( + DEFINE_STRING.BOOK.REPLACE_BOOK_DATA, + bookTaskId, + replaceData + ), + + //#endregion + //#region 分镜 // 开始计算分镜数据 @@ -85,6 +98,10 @@ const book = { ExportCopywriting: async (bookTaskId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId), + // 导入文案,修改过后的 + ImportCopywriting: async (bookId: string, bookTaskId: string, txtPath: string) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.IMPORT_COPYWRITING, bookId, bookTaskId, txtPath), + // 清除导入对齐的文案 ClearImportWord: async (bookTaskId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.CLEAR_IMPORT_WORD, bookTaskId), @@ -183,6 +200,10 @@ const book = { //#region 小说批次任务相关 + // 添加新的小说批次任务 + AddNewBookTask: async (addBookTaskData) => + await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_NEW_BOOK_TASK, addBookTaskData), + // 重置小说批次数据 ReSetBookTask: async (bookTaskId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_TASK, bookTaskId), diff --git a/src/preload/index.js b/src/preload/index.js index d3773a8..bd0db60 100644 --- a/src/preload/index.js +++ b/src/preload/index.js @@ -8,7 +8,7 @@ import { img } from './img.js' import { system } from './system.js' import { setting } from './setting.js' import { prompt } from './prompt.js' -import { book } from './book.js' +import { book } from './book' import { tts } from './tts.js' import { write } from './write.js' import { gpt } from './gpt.js' diff --git a/src/renderer/src/components/APIService/ApiService.vue b/src/renderer/src/components/APIService/ApiService.vue index 1386fb6..be8da6c 100644 --- a/src/renderer/src/components/APIService/ApiService.vue +++ b/src/renderer/src/components/APIService/ApiService.vue @@ -1,15 +1,42 @@ diff --git a/src/renderer/src/components/Book/Components/DatatableHeaderAfterGptSelectAndReplace.vue b/src/renderer/src/components/Book/Components/DatatableHeaderAfterGptSelectAndReplace.vue new file mode 100644 index 0000000..80f2963 --- /dev/null +++ b/src/renderer/src/components/Book/Components/DatatableHeaderAfterGptSelectAndReplace.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/renderer/src/components/Book/Components/DatatableHeaderGptPrompt.vue b/src/renderer/src/components/Book/Components/DatatableHeaderGptPrompt.vue index 18db8e6..c7d98a2 100644 --- a/src/renderer/src/components/Book/Components/DatatableHeaderGptPrompt.vue +++ b/src/renderer/src/components/Book/Components/DatatableHeaderGptPrompt.vue @@ -1,37 +1,58 @@ diff --git a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue index a64067a..02885f6 100644 --- a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue +++ b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue @@ -362,7 +362,6 @@ export default defineComponent({ // 导出文案 async function ExportCopywriting() { - debugger softwareStore.spin.spinning = true softwareStore.spin.tip = '正在导出文案中...' let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id) @@ -382,6 +381,36 @@ export default defineComponent({ softwareStore.spin.spinning = false } + // 导入文案 + async function ImportCopywriting() { + window.api.SelectFile(['txt'], async (value) => { + if (value.code == 0) { + message.error(value.message) + return + } + let txtPath = value.value + // 这边开始导入 + let res = await window.book.ImportCopywriting( + reverseManageStore.selectBook.id, + reverseManageStore.selectBookTask.id, + txtPath + ) + if (res.code == 1) { + for (let i = 0; i < res.data.length; i++) { + const element = res.data[i] + let index = reverseManageStore.selectBookTaskDetail.findIndex( + (x) => x.id == element.bookTaskDetailId + ) + if (index != -1) { + reverseManageStore.selectBookTaskDetail[index].afterGpt = element.afterGpt + } + } + } else { + window.api.showGlobalMessageDialog(res) + } + }) + } + /** * 获取水印位置 */ @@ -420,6 +449,9 @@ export default defineComponent({ case 'export_recognizing': // 导出文案 await ExportCopywriting() break + case 'import_recognizing': // 导入文案 + await ImportCopywriting() + break case 'watermark_position': // 水印位置设置 await GetWatermarkPosition() break @@ -853,6 +885,10 @@ export default defineComponent({ label: '导出文案', key: 'export_recognizing' }, + { + label: '导入文案', + key: 'import_recognizing' + }, { label: '停止提取', key: 'stop_recognizing' diff --git a/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue b/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue index d936644..a77f7a5 100644 --- a/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue +++ b/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue @@ -19,6 +19,7 @@ import ManageBookOldImage from '../Components/ManageBookOldImage.vue' import { BookType } from '../../../../../define/enum/bookEnum' import MJReversePrompt from './MJReversePrompt.vue' import DatatableHeaderGptPrompt from '../Components/DatatableHeaderGptPrompt.vue' +import DatatableHeaderAfterGpt from '../Components/DatatableHeaderAfterGpt.vue' import DatatableHeaderPrompt from '../Components/DatatableHeaderPrompt.vue' import DatatablePrompt from '../Components/DatatablePrompt.vue' import DatatableHeaderImage from '../Components/DatatableHeaderImage.vue' @@ -41,7 +42,9 @@ export default defineComponent({ fixed: 'left' }, { - title: '文案', + title(row) { + return h(DatatableHeaderAfterGpt) + }, key: 'afterGpt', width: 200, fixed: 'left', @@ -87,10 +90,7 @@ export default defineComponent({ }, { title(row) { - return h('div', { style: 'display: flex; align-items: center' }, [ - h('span', { size: 'tiny', style: 'margin-left: 5px' }, '生图提示词'), - h(DatatableHeaderPrompt) - ]) + return h(DatatableHeaderPrompt) }, className: 'space-row', key: 'row1', diff --git a/src/renderer/src/components/Book/ManageBookTask.vue b/src/renderer/src/components/Book/ManageBookTask.vue index b94f232..46906e1 100644 --- a/src/renderer/src/components/Book/ManageBookTask.vue +++ b/src/renderer/src/components/Book/ManageBookTask.vue @@ -41,6 +41,7 @@ import { useRouter } from 'vue-router' import { OperateBookType } from '../../../../define/enum/bookEnum' import { DEFINE_STRING } from '../../../../define/define_string' import { ResponseMessageType } from '../../../../define/enum/softwareEnum' +import AddBookTask from './Components/ManageBook/AddBookTask.vue' export default defineComponent({ components: { @@ -167,6 +168,16 @@ export default defineComponent({ } } + async function AddBookDialog() { + message.info('新增' + reverseManageStore.selectBook.id) + dialog.create({ + title: '新增小说批次任务', + showIcon: false, + content: () => h(AddBookTask), + style: { width: '600px' } + }) + } + /** * 一键生成草稿 */ @@ -197,6 +208,7 @@ export default defineComponent({ return { reverseManageStore, + AddBookDialog, HDImageAll, DraftAll, VideoAll, diff --git a/src/renderer/src/components/Book/ReverseManage.vue b/src/renderer/src/components/Book/ReverseManage.vue index 2565ce7..90f080e 100644 --- a/src/renderer/src/components/Book/ReverseManage.vue +++ b/src/renderer/src/components/Book/ReverseManage.vue @@ -7,14 +7,6 @@ - - - - - - - - diff --git a/src/renderer/src/components/Components/BackTask/BackTask.vue b/src/renderer/src/components/Components/BackTask/BackTask.vue new file mode 100644 index 0000000..d5d27f2 --- /dev/null +++ b/src/renderer/src/components/Components/BackTask/BackTask.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/renderer/src/components/Components/BackTask/QueryButton.vue b/src/renderer/src/components/Components/BackTask/QueryButton.vue new file mode 100644 index 0000000..56de7db --- /dev/null +++ b/src/renderer/src/components/Components/BackTask/QueryButton.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/renderer/src/components/Components/BackTask/TaskDataTable.vue b/src/renderer/src/components/Components/BackTask/TaskDataTable.vue new file mode 100644 index 0000000..bbee35c --- /dev/null +++ b/src/renderer/src/components/Components/BackTask/TaskDataTable.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/renderer/src/components/Home/Home.vue b/src/renderer/src/components/Home/Home.vue index 6089213..f770a30 100644 --- a/src/renderer/src/components/Home/Home.vue +++ b/src/renderer/src/components/Home/Home.vue @@ -33,18 +33,21 @@