LaiTool V3.0.1-preview.7

This commit is contained in:
lq1405 2024-08-18 16:22:19 +08:00
parent 07f99708da
commit 223678f761
78 changed files with 2827 additions and 917 deletions

2
.gitignore vendored
View File

@ -28,3 +28,5 @@ resources/config*
*Lai_1.exe*
.DS_Store
*.log*
resources/scripts/db/book.realm.lock
resources/scripts/db/software.realm.lock

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "laitool",
"version": "3.0.1-preview.6",
"version": "3.0.1-preview.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "laitool",
"version": "3.0.1-preview.6",
"version": "3.0.1-preview.7",
"hasInstallScript": true,
"dependencies": {
"@alicloud/alimt20181012": "^1.2.0",

View File

@ -1,6 +1,6 @@
{
"name": "laitool",
"version": "3.0.1-preview.6",
"version": "3.0.1-preview.7",
"description": "An AI tool for image processing, video processing, and other functions.",
"main": "./out/main/index.js",
"author": "laitool.cn",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -236,3 +236,20 @@ export async function GetFileSize(filePath: string): Promise<number> {
throw error
}
}
/**
*
* @param folderPath
* @returns
*/
export async function GetSubdirectories(folderPath: string): Promise<string[]> {
try {
const files = await fs.promises.readdir(folderPath, { withFileTypes: true });
const directories = files
.filter((fileStat) => fileStat.isDirectory())
.map((fileStat) => path.join(folderPath, fileStat.name));
return directories;
} catch (error) {
throw new Error(error);
}
}

View File

@ -13,6 +13,7 @@ export class SoftwareModel extends Realm.Object<SoftwareModel> {
aiSetting: string | null // AI相关的配置的json字符串
watermarkSetting: string | null // 水印相关的配置的json字符串
translationSetting: string | null // 翻译相关的配置的json字符串
subtitleSetting: string | null // 字幕相关的配置的json字符串
static schema: ObjectSchema = {
name: 'Software',
@ -27,7 +28,8 @@ export class SoftwareModel extends Realm.Object<SoftwareModel> {
writeSetting: 'string?',
aiSetting: 'string?',
watermarkSetting: 'string?',
translationSetting: 'string?'
translationSetting: 'string?',
subtitleSetting: "string?"
},
// 主键为_id
primaryKey: 'id'

View File

@ -135,13 +135,13 @@ export class BookBackTaskListService extends BaseRealmService {
*
* @param bookBackTask
*/
async AddBookBackTask(
AddBookBackTask(
bookId: string,
taskType: BookBackTaskType,
executeType = TaskExecuteType.AUTO,
bookTaskId = null,
bookTaskDetailId = null
) {
): GeneralResponse.SuccessItem | GeneralResponse.ErrorItem {
try {
// 通过bookid获取book信息
let book = this.realm.objectForPrimaryKey('Book', bookId)

View File

@ -11,6 +11,7 @@ import { isEmpty } from 'lodash'
import { FfmpegOptions } from '../../../../main/Service/ffmpegOptions.js'
import { version } from '../../../../../package.json'
import { Book } from '../../../../model/book.js'
import { GeneralResponse } from '../../../../model/generalResponse.js'
export class BookService extends BaseRealmService {
static instance: BookService | null = null
@ -37,7 +38,7 @@ export class BookService extends BaseRealmService {
* null
* @param bookId
*/
GetBookDataById(bookId): Book.SelectBook | null {
GetBookDataById(bookId: string): Book.SelectBook | null {
try {
if (isEmpty(bookId)) {
throw new Error('获取小说信息失败缺少小说ID')
@ -275,7 +276,7 @@ export class BookService extends BaseRealmService {
* @param bookId ID
* @param bookData
*/
async UpdateBookData(bookId: string, bookData: Book.SelectBook) {
UpdateBookData(bookId: string, bookData: Book.SelectBook): Book.SelectBook {
try {
if (bookId == null) {
throw new Error('修改小说数据失败缺少小说ID')
@ -303,8 +304,27 @@ export class BookService extends BaseRealmService {
if (bookRes == null) {
throw new Error('获取修改后的小说数据失败小说ID对应的数据不存在')
}
// return successMessage(bookRes, '修改小说数据成功', 'ReverseBook_UpdateBookData')
return bookRes;
} catch (error) {
throw error
}
}
return successMessage(bookRes, '修改小说数据成功', 'ReverseBook_UpdateBookData')
/**
*
* @param bookId ID
*/
DeleteBookData(bookId: string): void {
try {
this.transaction(() => {
let book = this.realm.objectForPrimaryKey('Book', bookId);
if (book == null) {
throw new Error('未找到对应的小说')
}
// 删除对应的小说
this.realm.delete(book)
})
} catch (error) {
throw error
}

View File

@ -72,7 +72,7 @@ export class BookTaskDetailService extends BaseRealmService {
return JoinPath(define.project_path, subImage)
}),
characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : null,
subValue: item.subValue,
subValue: isEmpty(item.subValue) ? null : JSON.parse(item.subValue),
reversePrompt: item.reversePrompt.map((reversePrompt) => {
return {
...reversePrompt
@ -101,7 +101,6 @@ export class BookTaskDetailService extends BaseRealmService {
if (bookTaskDetailId == null) {
throw new Error('获取小说任务详细信息失败缺少ID')
}
let bookTaskDetails = this.GetBookTaskData({ id: bookTaskDetailId })
if (bookTaskDetails.data.length <= 0) {
return null;
@ -165,7 +164,7 @@ export class BookTaskDetailService extends BaseRealmService {
* @param bookTaskDetailId
* @param updateData
*/
UpdateBookTaskDetail(bookTaskDetailId: string, updateData: Book.SelectBookTaskDetail) {
UpdateBookTaskDetail(bookTaskDetailId: string, updateData: Book.SelectBookTaskDetail): Book.SelectBookTaskDetail {
try {
this.transaction(() => {
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
@ -178,18 +177,21 @@ export class BookTaskDetailService extends BaseRealmService {
}
bookTaskDetail.updateTime = new Date()
})
return successMessage(
null,
'修改小说任务详细信息成功',
'BookTaskDetailService_UpdateBookTaskDetail'
)
let res = this.GetBookTaskDetailDataById(bookTaskDetailId)
return res;
} catch (error) {
throw error
}
}
/**
* ID的反推提示词数据
* @param bookTaskDetailId
* @param mjMessage
*/
UpdateBookTaskDetailMjMessage(bookTaskDetailId: string, mjMessage: Book.MJMessage): void {
try {
console.log('UpdateBookTaskDetailMjMessage', bookTaskDetailId, mjMessage)
this.transaction(() => {
let mjMessageRes = this.realm.objectForPrimaryKey('MJMessage', bookTaskDetailId)
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
@ -276,12 +278,20 @@ export class BookTaskDetailService extends BaseRealmService {
* @param bookTaskDetailId ID
*/
DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): void {
let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId);
if (bookTaskDetail == null) {
throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息')
}
this.transaction(() => {
this.realm.delete(bookTaskDetail.reversePrompt)
let bookTaskDetails = this.realm.objects<BookTaskDetailModel>('BookTaskDetail').filtered('id = $0', bookTaskDetailId);
if (bookTaskDetails.length <= 0) {
throw new Error('删除小说任务详细信息的反推提示词失败,未找到对应的分镜信息')
}
let bookTaskDetail = bookTaskDetails[0];
bookTaskDetail.gptPrompt = undefined;
// 删除所有的反推提示词
if (bookTaskDetail.reversePrompt) {
bookTaskDetail.reversePrompt.forEach(item => {
this.realm.delete(item)
})
}
})
}
}

View File

@ -132,7 +132,7 @@ export class BookTaskService extends BaseRealmService {
* @param bookTaskId Id
* @param status
*/
UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null) {
UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null): GeneralResponse.SuccessItem {
try {
this.transaction(() => {
// 修改对应小说批次任务的状态

View File

@ -147,6 +147,14 @@ const migration = (oldRealm: Realm, newRealm: Realm) => {
}
})
}
if (oldRealm.schemaVersion < 20) {
newRealm.write(() => {
const newSoftwares = newRealm.objects('Software')
for (let software of newSoftwares) {
software.subtitleSetting = null // 字幕的默认设置
}
})
}
}
export class BaseSoftWareService extends BaseService {
@ -185,7 +193,7 @@ export class BaseSoftWareService extends BaseService {
MjSettingModel
],
path: dbPath,
schemaVersion: 19, // 当前版本号
schemaVersion: 21, // 当前版本号
migration: migration
}
// 判断当前全局是不是又当前这个

View File

@ -1,6 +1,7 @@
import Realm, { UpdateMode } from 'realm'
import { successMessage } from '../../../../main/Public/generalTools'
import { BaseSoftWareService } from './softwareBasic.js'
import { GeneralResponse } from '../../../../model/generalResponse'
const { v4: uuidv4 } = require('uuid')
export class SoftwareService extends BaseSoftWareService {
@ -24,7 +25,7 @@ export class SoftwareService extends BaseSoftWareService {
}
// 修改数据库中行中的某个属性数据
UpdateSoftware(software) {
UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): GeneralResponse.SuccessItem {
try {
this.realm.write(() => {
this.realm.create('Software', software, UpdateMode.Modified)
@ -41,7 +42,7 @@ export class SoftwareService extends BaseSoftWareService {
* @param software
* @returns
*/
AddSfotware(software) {
AddSfotware(software: SoftwareSettingModel.SoftwareSetting): GeneralResponse.SuccessItem {
try {
software.id = uuidv4()
this.realm.write(() => {
@ -56,7 +57,7 @@ export class SoftwareService extends BaseSoftWareService {
/**
*
*/
GetSoftwareData() {
GetSoftwareData(): GeneralResponse.SuccessItem {
try {
let softwares = this.realm.objects('Software')

View File

@ -123,7 +123,8 @@ export const DEFINE_STRING = {
GPT: {
INIT_SERVER_GPT_OPTIONS: 'INIT_SERVER_GPT_OPTIONS',
GET_AI_SETTING: 'GET_AI_SETTING',
SAVE_AI_SETTING: 'SAVE_AI_SETTING'
SAVE_AI_SETTING: 'SAVE_AI_SETTING',
SYNC_GPT_KEY: "SYNC_GPT_KEY"
},
QUEUE_BATCH: {
@ -153,7 +154,6 @@ export const DEFINE_STRING = {
SD: {
LOAD_SD_SERVICE_DATA: 'LOAD_SD_SERVICE_DATA',
TXT2IMG: 'TXT2IMG',
SD_MERGE_PROMPT: "SD_MERGE_PROMPT"
},
MJ: {
SAVE_WORD_SRT: 'SAVE_WORD_SRT',
@ -175,7 +175,6 @@ export const DEFINE_STRING = {
GET_MJ_IMAGE_ROBOT_MODEL: 'GET_MJ_IMAGE_ROBOT_MODEL',
MACTH_USER_RETURN: 'MACTH_USER_RETURN',
AUTO_MATCH_USER: 'AUTO_MATCH_USER',
MJ_MERGE_PROMPT: "MJ_MERGE_PROMPT",
ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK: "ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK",
MJ_IMAGE: "MJ_IMAGE"
},
@ -208,6 +207,7 @@ export const DEFINE_STRING = {
BOOK: {
MAIN_DATA_RETURN: 'MAIN_DATA_RETURN', // 监听任务返回
REPLACE_VIDEO_CURRENT_FRAME: 'REPLACE_VIDEO_CURRENT_FRAME',
GET_BOOK_TYPE: 'GET_BOOK_TYPE',
ADD_OR_MODIFY_BOOK: 'ADD_OR_MODIFY_BOOK',
GET_BOOK_DATA: 'GET_BOOK_DATA',
@ -232,6 +232,10 @@ export const DEFINE_STRING = {
HD_IMAGE: "HD_IMAGE",
USE_BOOK_VIDEO_DATA_TO_BOOK_TASK: "USE_BOOK_VIDEO_DATA_TO_BOOK_TASK",
ADD_JIANYING_DRAFT: "ADD_JIANYING_DRAFT",
EXPORT_COPYWRITING: "EXPORT_COPYWRITING",
MERGE_PROMPT: "MERGE_PROMPT",
RESET_BOOK_DATA: "RESET_BOOK_DATA",
DELETE_BOOK_DATA: "DELETE_BOOK_DATA",
COMPUTE_STORYBOARD: 'COMPUTE_STORYBOARD',
@ -291,7 +295,10 @@ export const DEFINE_STRING = {
WRITE: {
GET_WRITE_CONFIG: 'GET_WRITE_CONFIG',
SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG',
ACTION_START: 'ACTION_START'
ACTION_START: 'ACTION_START',
GET_SUBTITLE_SETTING: "GET_SUBTITLE_SETTING",
RESET_SUBTITLE_SETTING: "RESET_SUBTITLE_SETTING",
SAVE_SUBTITLE_SETTING: "SAVE_SUBTITLE_SETTING",
},
DB: {
UPDATE_BOOK_TASK_DATA: "UPDATE_BOOK_TASK_DATA",

View File

@ -207,10 +207,6 @@ export enum TagDefineType {
SCENE_MAIN = "scene_main",
}
export enum MergeType {
BOOKTASK = 'bookTask', // 整个小说批次分镜合并
BOOKTASKDETAIL = 'bookTaskDetail' // 单个分镜合并
}
export enum OperateBookType {
BOOK = 'book', // 这个小说的所有批次
@ -227,3 +223,14 @@ export enum CopyImageType {
// 不包含图
NONE = 'none'
}
export enum PromptMergeType {
// mj 合并
MJ_MERGE = 'mj_merge',
// SD 合并
SD_MERGE = 'sd_merge',
// D3 合并
D3_MERGE = 'd3_merge'
}

View File

@ -60,7 +60,8 @@ export enum ResponseMessageType {
GET_TEXT = 'getText', // 获取文案
REMOVE_WATERMARK = "REMOVE_WATERMARK",// 删除水印
MJ_REVERSE = 'MJ_REVERSE',// MJ反推返回反推结果
PROMPT_TRANSLATE = 'PROMPT_TRANSLATE',// 提示词翻译
REVERSE_PROMPT_TRANSLATE = 'REVERSE_PROMPT_TRANSLATE',// 反推提示词翻译
GPT_PROMPT_TRANSLATE = 'GPT_PROMPT_TRANSLATE', // GPT提示词翻译
MJ_IMAGE = 'MJ_IMAGE',// MJ 生成图片
HD_IMAGE = 'HD_IMAGE',// HD 生成图片
}
@ -72,4 +73,11 @@ export enum LaiAPIType {
HK_PROXY = "hk-proxy",
// 备用站点
BAK_MAIN = 'bak-main'
}
// 同步GPTkey的分类
export enum SyncGptKeyType {
// 字幕设置
SUBTITLE_SETTING = 'subtitle_setting',
}

View File

@ -2,7 +2,6 @@
export enum TranslateType {
// 反推提示词翻译
REVERSE_PROMPT_TRANSLATE = 'reverse_prompt_translate',
// GPT提示词翻译
GPT_PROMPT_TRANSLATE = 'gpt_prompt_translate',
}

View File

@ -27,4 +27,14 @@ export enum WaterMarkResponseDateType {
export enum RemoveWatermarkType {
LOCAL_LAMA = 'local_lama',
IOPAINT = 'iopaint'
}
// 获取字幕的方法类型
export enum GetSubtitleType {
// 本地OCR
LOCAL_OCR = 'local_ocr',
// 本地Whisper
LOCAL_WHISPER = 'local_whisper',
// LAI WHISPER
LAI_WHISPER = 'lai_whisper',
}

View File

@ -2,7 +2,7 @@ import { ipcMain } from 'electron'
import { DEFINE_STRING } from '../../define/define_string'
import { ReverseBook } from '../Service/Book/ReverseBook'
import { BasicReverse } from '../Service/Book/basicReverse'
import { Subtitle } from '../Service/subtitle'
import { Subtitle } from '../Service/Subtitle/subtitle'
import { BookBasic } from '../Service/Book/BooKBasic'
import { MJOpt } from '../Service/MJ/mj'
import { BookImage } from '../Service/Book/bookImage'
@ -10,6 +10,8 @@ import { ImageStyle } from '../Service/Book/imageStyle'
import { BookTask } from '../Service/Book/bookTask'
import { BookVideo } from '../Service/Book/bookVideo'
import { Watermark } from '../Service/watermark'
import { SubtitleService } from '../Service/Subtitle/subtitleService'
import { BookFrame } from '../Service/Book/bookFrame'
let reverseBook = new ReverseBook()
let basicReverse = new BasicReverse()
let subtitle = new Subtitle()
@ -20,14 +22,16 @@ let imageStyle = new ImageStyle()
let bookTask = new BookTask()
let bookVideo = new BookVideo()
let watermark = new Watermark()
let subtitleService = new SubtitleService()
let bookFrame = new BookFrame()
export function BookIpc() {
// 获取样式图片的子列表
ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => reverseBook.GetBookType())
ipcMain.handle(DEFINE_STRING.BOOK.GET_BOOK_TYPE, async (event) => bookBasic.GetBookType())
// 新增或者是修改小说数据
ipcMain.handle(DEFINE_STRING.BOOK.ADD_OR_MODIFY_BOOK, async (event, book) =>
reverseBook.AddOrModifyBook(book)
bookBasic.AddOrModifyBook(book)
)
// 获取小说数据(通过传递的参数进行筛选)
@ -83,8 +87,7 @@ export function BookIpc() {
//#endregion
//#region 一键反推的单个任务
//#region 分镜相关
// 开始计算分镜
ipcMain.handle(
DEFINE_STRING.BOOK.COMPUTE_STORYBOARD,
@ -97,23 +100,46 @@ export function BookIpc() {
async (event, bookId) => await reverseBook.Framing(bookId)
)
// 开始执行分镜任务
// 替换分镜视频的当前帧
ipcMain.handle(DEFINE_STRING.BOOK.REPLACE_VIDEO_CURRENT_FRAME, async (event, bookTaskDetailId, currentTime) =>
await bookFrame.ReplaceVideoCurrentFrame(bookTaskDetailId, currentTime)
)
//#endregion
//#region 提示词相关
// 合并提示词
ipcMain.handle(DEFINE_STRING.BOOK.MERGE_PROMPT, async (event, id, type, operateBookType) => await reverseBook.MergePrompt(id, type, operateBookType))
//#endregion
//#region 一键反推的单个任务
// 开始执行获取小说文案的方法
ipcMain.handle(
DEFINE_STRING.BOOK.GET_COPYWRITING,
async (event, bookId, bookTaskId) => await reverseBook.GetCopywriting(bookId, bookTaskId)
async (event, bookId, bookTaskId, operateBookType) => await subtitleService.GetCopywriting(bookId, bookTaskId, operateBookType)
)
// 获取小说的文案数据,然后保存到对应的文件中
ipcMain.handle(
DEFINE_STRING.BOOK.EXPORT_COPYWRITING,
async (event, bookTaskId) => await subtitleService.ExportCopywriting(bookTaskId)
)
// 执行去除水印
ipcMain.handle(
DEFINE_STRING.BOOK.REMOVE_WATERMARK,
async (event, id,operateBookType) => await watermark.RemoveWatermark(id,operateBookType)
async (event, id, operateBookType) => await watermark.RemoveWatermark(id, operateBookType)
)
// 添加反推任务到任务列表
ipcMain.handle(
DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT,
async (event, bookTaskDetailIds, type) =>
await reverseBook.AddReversePromptTask(bookTaskDetailIds, type)
async (event, bookTaskDetailIds, operateBookType, type) =>
await reverseBook.AddReversePrompt(bookTaskDetailIds, operateBookType, type)
)
// 将反推出来的提示词写入到GPT提示词里面
@ -154,9 +180,19 @@ export function BookIpc() {
// 一拆四,将一个任务拆分成四个任务,并且复制对应的图片
ipcMain.handle(
DEFINE_STRING.BOOK.ONE_TO_FOUR_BOOK_TASK,
async (event, bookTaskDetailId) => await bookBasic.OneToFourBookTask(bookTaskDetailId)
async (event, bookTaskDetailId) => await bookTask.OneToFourBookTask(bookTaskDetailId)
)
//#region 小说相关
// 重置小说数据
ipcMain.handle(DEFINE_STRING.BOOK.RESET_BOOK_DATA, async (event, bookId) => await bookBasic.ResetBookData(bookId))
// 删除小说数据
ipcMain.handle(DEFINE_STRING.BOOK.DELETE_BOOK_DATA, async (event, bookId) => await bookBasic.DeleteBookData(bookId))
//#endregion
//#region 小说批次任务相关
// 重置小说批次数据

View File

@ -83,6 +83,12 @@ function GptIpc() {
DEFINE_STRING.GPT.SAVE_AI_SETTING,
async (event, value) => await gptSetting.SaveAISetting(value)
)
// 同步GPT Key 到指定的设置
ipcMain.handle(
DEFINE_STRING.GPT.SYNC_GPT_KEY,
async (event, syncType) => await gptSetting.SyncGptKey(syncType)
)
}
export { GptIpc }

View File

@ -12,7 +12,7 @@ import { MainIpc } from './mainIpc.js'
import { GlobalIpc } from './globalIpc.js'
import { ImageIpc } from './imageIpc.js'
import { SystemIpc } from './systemIpc.js'
import { BookIpc } from './bookIpc.js'
import { BookIpc } from './bookIpc'
import { TTSIpc } from './ttsIpc.js'
import { DBIpc } from './dbIpc'

View File

@ -138,12 +138,6 @@ function MjIpc() {
async (event, value) => await discordSimple.DiscordDeleteMessage(value)
)
// MJ合并提示词命令
ipcMain.handle(
DEFINE_STRING.MJ.MJ_MERGE_PROMPT,
async (event, id, mergeType) => await mjOpt.MergePrompt(id, mergeType)
)
// MJ出单张图
ipcMain.handle(
DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK,

View File

@ -29,11 +29,5 @@ function SdIpc() {
// 文生图,单张
ipcMain.handle(DEFINE_STRING.SD.TXT2IMG, async (event, value) => await sd.txt2img(value))
// SD合并提示词
ipcMain.handle(
DEFINE_STRING.SD.SD_MERGE_PROMPT,
async (event, id, mergeType) => await sdOpt.MergePrompt(id, mergeType)
)
}
export { SdIpc }

View File

@ -4,6 +4,8 @@ import { Writing } from '../Service/writing'
let writing = new Writing(global)
import { WritingSetting } from '../setting/writeSetting'
let writingSetting = new WritingSetting()
import { SubtitleService } from '../Service/Subtitle/subtitleService'
let subtitleService = new SubtitleService()
function WritingIpc() {
// 监听分镜时间的保存
@ -36,7 +38,7 @@ function WritingIpc() {
async (event, value) => await writing.ImportSrtAndGetTime(value)
)
// 获取文案相关的配置(数据库)
// 获取文案格式化相关的配置(数据库)
ipcMain.handle(
DEFINE_STRING.WRITE.GET_WRITE_CONFIG,
async (event) => await writingSetting.GetWritingConfig()
@ -48,6 +50,23 @@ function WritingIpc() {
async (event, value) => await writingSetting.SaveWriteConfig(value)
)
// 获取提取文案相关的配置(数据库)
ipcMain.handle(
DEFINE_STRING.WRITE.GET_SUBTITLE_SETTING,
async (event) => await subtitleService.GetSubtitleSetting()
)
// 重置提取文案相关的配置(数据库)
ipcMain.handle(
DEFINE_STRING.WRITE.RESET_SUBTITLE_SETTING,
async (event) => await subtitleService.ResetSubtitleSetting()
)
ipcMain.handle(
DEFINE_STRING.WRITE.SAVE_SUBTITLE_SETTING,
async (event, subtitleSetting) => await subtitleService.SaveSubtitleSetting(subtitleSetting)
)
ipcMain.handle(
DEFINE_STRING.WRITE.ACTION_START,
async (event, aiSetting, word) => await writing.ActionStart(aiSetting, word)

View File

@ -1,29 +1,19 @@
import { BookType, OperateBookType, TagDefineType } from '../../../define/enum/bookEnum'
import { errorMessage, successMessage } from '../../Public/generalTools'
import { BookService } from '../../../define/db/service/Book/bookService'
import { BookTaskService } from '../../../define/db/service/Book/bookTaskService'
import { BookTaskDetailService } from '../../../define/db/service/Book/bookTaskDetailService'
import { CopyImageType } from '../../../define/enum/bookEnum'
const { v4: uuidv4 } = require('uuid')
import { define } from '../../../define/define'
import path from 'path'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder } from '../../../define/Tools/file'
import { Book } from '../../../model/book'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, DeleteFolderAllFile, GetSubdirectories } from '../../../define/Tools/file'
import { GeneralResponse } from '../../../model/generalResponse'
import { cloneDeep, isEmpty } from 'lodash'
import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic'
import { BookTask } from './bookTask'
import fs from 'fs'
export class BookBasic {
constructor() { }
bookTaskService: BookTaskService
bookTaskDetailService: BookTaskDetailService
async InitService() {
if (!this.bookTaskService) {
this.bookTaskService = await BookTaskService.getInstance()
}
if (!this.bookTaskDetailService) {
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
}
bookServiceBasic: BookServiceBasic
bookTask: BookTask
constructor() {
this.bookServiceBasic = new BookServiceBasic();
this.bookTask = new BookTask()
}
//#region 小说相关操作
@ -73,209 +63,112 @@ export class BookBasic {
//#endregion
//#region 小说批次任务相关操作
//#region 小说相关操作
async OneToFourBookTask(bookTaskId: string) {
/**
*
* @param bookId ID
* @returns
*/
async ResetBookData(bookId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
console.log(bookTaskId)
await this.InitService();
let copyCount = 100
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId)
if (bookTask == null) {
throw new Error("没有找到对应的数小说任务,请检查数据")
}
// 获取所有的出图中最少的
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookTaskId: bookTaskId
}).data as Book.SelectBookTaskDetail[]
if (bookTaskDetail == null || bookTaskDetail.length <= 0) {
throw new Error("没有对应的小说分镜任务,请先添加分镜任务")
let book = await this.bookServiceBasic.GetBookDataById(bookId)
// 获取所有的小说批次
let bookTasks = (await this.bookServiceBasic.GetBookTaskData({
bookId: bookId
})).bookTasks;
// 重置批次任务
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
// 第一个重置,后面的删除
if (i == 0) {
let resetBookTaskData = await this.bookTask.ReSetBookTask(element.id);
if (resetBookTaskData.code == 0) {
throw new Error(resetBookTaskData.message)
}
} else {
let deleteBookTaskData = await this.bookTask.DeleteBookTask(element.id);
if (deleteBookTaskData.code == 0) {
throw new Error(deleteBookTaskData.message)
}
}
}
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
if (isEmpty(element.subImagePath)) {
throw new Error("检测到图片没有出完,请先检查出图")
}
if (element.subImagePath == null || element.subImagePath.length <= 0) {
throw new Error("检测到图片没有出完,请先检查出图")
}
if (element.subImagePath.length < copyCount) {
copyCount = element.subImagePath.length
}
// 开始重置小说数据
await this.bookServiceBasic.UpdateBookData(bookId, {
srtPath: undefined,
audioPath: undefined,
subtitlePosition: undefined,
imageStyle: undefined,
autoAnalyzeCharacter: undefined,
customizeImageStyle: undefined,
videoConfig: undefined,
prefixPrompt: undefined,
suffixPrompt: undefined,
draftSrtStyle: undefined,
backgroundMusic: undefined,
friendlyReminder: undefined,
watermarkPosition: undefined,
})
// 文件重置获取data下面的所有的子文件夹删除所有的文件夹
let dirs = await GetSubdirectories(path.join(book.bookFolderPath, 'data'))
for (let i = 0; i < dirs.length; i++) {
const element = dirs[i];
await DeleteFolderAllFile(element, true)
}
if (copyCount <= 0) {
throw new Error("批次设置错误,无法进行一拆四")
let scriptPath = path.join(book.bookFolderPath, 'script')
if (await CheckFileOrDirExist(scriptPath)) {
await DeleteFolderAllFile(scriptPath, true)
}
// 开始复制
let res = await this.CopyNewBookTask(bookTask, bookTaskDetail, copyCount - 1, CopyImageType.ONE)
if (res.code == 0) {
throw new Error(res.message)
// 删掉输入的备份文件和input文件
let bakPath = path.join(book.bookFolderPath, 'tmp/bak');
if (await CheckFileOrDirExist(bakPath)) {
await DeleteFolderAllFile(bakPath, true)
}
return successMessage(res.data, "一拆四成功", "BookBasic_OneToFourBookTask")
let inputPath = path.join(book.bookFolderPath, 'tmp/input');
if (await CheckFileOrDirExist(inputPath)) {
await DeleteFolderAllFile(inputPath, true)
}
// 重置完毕,开始返回
return successMessage('重置小说数据成功', 'BookBasic_ResetBookData');
} catch (error) {
return errorMessage("一拆四失败,失败信息如下:" + error.message, "BookBasic_OneToFourBookTask")
return errorMessage('重置小说数据失败,失败信息如下:' + error.message, 'BookBasic_ResetBookData');
}
}
/**
*
* @param oldBookTaskId
* @param copyCount
* @param isCopyImage
*
* @param bookId ID
*/
async CopyNewBookTask(sourceBookTask: Book.SelectBookTask, sourceBookTaskDetail: Book.SelectBookTaskDetail[], copyCount: number, copyImageType: CopyImageType) {
async DeleteBookData(bookId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
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 name = 'output_0000' + no
let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`)
await CheckFolderExistsOrCreate(imageFolder)
// 创建对应的文件夹
let addOneBookTask = {
id: uuidv4(),
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(define.project_path, 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,
} as Book.SelectBookTask
addBookTask.push(addOneBookTask)
for (let j = 0; j < sourceBookTaskDetail.length; j++) {
const element = sourceBookTaskDetail[j];
let outImagePath = undefined as string
let subImagePath = [] as string[]
if (copyImageType == CopyImageType.ALL) { // 直接全部复制
outImagePath = element.outImagePath
subImagePath = element.subImagePath
} else if (copyImageType == CopyImageType.ONE) { // 只复制对应的
let oldImage = element.subImagePath[i + 1]
outImagePath = path.join(imageFolder, path.basename(element.outImagePath))
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(define.project_path, `${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 = uuidv4()
sdConifg.webuiConfig = tempSdConfig
}
}
let reverseId = uuidv4()
// 处理反推数据
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 = reverseId
}
}
let addOneBookTaskDetail = {
id: reverseId,
no: element.no,
name: element.name,
bookId: sourceBookTask.bookId,
bookTaskId: addOneBookTask.id,
videoPath: path.relative(define.project_path, element.videoPath),
word: element.word,
oldImage: path.relative(define.project_path, element.oldImage),
afterGpt: element.afterGpt,
startTime: element.startTime,
endTime: element.endTime,
timeLimit: element.timeLimit,
subValue: element.subValue,
characterTags: element.characterTags && element.characterTags.length > 0 ? cloneDeep(element.characterTags) : [],
gptPrompt: element.gptPrompt,
outImagePath: path.relative(define.project_path, outImagePath),
subImagePath: subImagePath || [],
prompt: element.prompt,
adetailer: element.adetailer,
sdConifg: sdConifg,
createTime: new Date(),
updateTime: new Date(),
audioPath: element.audioPath,
subtitlePosition: element.subtitlePosition,
status: element.status,
reversePrompt: reverseMessage,
imageLock: element.imageLock
} as Book.SelectBookTaskDetail
addBookTaskDetail.push(addOneBookTaskDetail)
}
let book = await this.bookServiceBasic.GetBookDataById(bookId);
// 先将所有的数据重置
let resetRes = await this.ResetBookData(bookId);
if (resetRes.code == 0) {
throw new Error(resetRes.message)
}
let bookTasks = (await this.bookServiceBasic.GetBookTaskData({
bookId: bookId
})).bookTasks;
// 删除遗留重置的小说批次任务
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
await this.bookServiceBasic.DeleteBookTaskData(element.id);
}
// 数据处理完毕,开始新增数据
// 将所有的复制才做,全部放在一个事务中
this.bookTaskService.transaction(() => {
for (let i = 0; i < addBookTask.length; i++) {
const element = addBookTask[i];
this.bookTaskService.realm.create('BookTask', element)
}
for (let i = 0; i < addBookTaskDetail.length; i++) {
const element = addBookTaskDetail[i];
this.bookTaskDetailService.realm.create('BookTaskDetail', element)
}
})
// 全部创建完成
// 查找到数据,然后全部返回
let returnBookTask = this.bookTaskService.GetBookTaskData({
bookId: sourceBookTask.bookId
}).data as Book.SelectBookTask[]
// 开始删除数据
await this.bookServiceBasic.DeleteBookData(bookId);
return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask")
// 开始删除文件
let bookPath = book.bookFolderPath;
if (await CheckFileOrDirExist(bookPath)) {
await DeleteFolderAllFile(bookPath, true)
}
return successMessage(null, '删除小说数据成功', 'BookBasic_DeleteBookData');
} catch (error) {
console.log(error)
throw error
return errorMessage('删除小说数据失败,失败信息如下:' + error.message, 'BookBasic_DeleteBookData');
}
}

View File

@ -12,65 +12,36 @@ import { TaskScheduler } from "../../Service/taskScheduler"
import { Book } from '../../../model/book'
import { LoggerStatus, OtherData, ResponseMessageType } from '../../../define/enum/softwareEnum'
import { GeneralResponse } from '../../../model/generalResponse'
import { cloneDeep, isEmpty } from 'lodash'
import { Subtitle } from '../../Service/subtitle'
import { Subtitle } from '../Subtitle/subtitle'
import { Watermark } from '../watermark'
import { SubtitleSavePositionType } from '../../../define/enum/waterMarkAndSubtitle'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate, CopyFileOrFolder, GetFilesWithExtensions } from '../../../define/Tools/file'
import { ValidateJson } from '../../../define/Tools/validate'
import { GetImageBase64, ProcessImage } from '../../../define/Tools/image'
import { BookBackTaskType, BookType, TagDefineType, TaskExecuteType } from '../../../define/enum/bookEnum'
import { BookBackTaskListService } from '../../../define/db/service/Book/bookBackTaskListService'
import { BookBackTaskType, BookType, OperateBookType, PromptMergeType, TagDefineType, TaskExecuteType } from '../../../define/enum/bookEnum'
import { MJOpt } from '../MJ/mj'
import { TagDefine } from '../../../define/tagDefine'
import { ImageStyleDefine } from '../../../define/iamgeStyleDefine'
import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic'
import { SDOpt } from '../SD/sd'
/**
*
*/
export class ReverseBook extends BookBasic {
export class ReverseBook {
basicReverse: BasicReverse
bookTaskService: BookTaskService
taskScheduler: TaskScheduler
bookService: BookService
bookTaskDetailService: BookTaskDetailService
bookBackTaskListService: BookBackTaskListService
mjOpt: MJOpt = new MJOpt()
sdOpt: SDOpt = new SDOpt()
tagDefine: TagDefine
subtitle: Subtitle
watermark: Watermark
bookServiceBasic: BookServiceBasic
constructor() {
super()
this.basicReverse = new BasicReverse()
this.tagDefine = new TagDefine()
this.subtitle = new Subtitle()
this.watermark = new Watermark()
this.taskScheduler = new TaskScheduler()
this.bookServiceBasic = new BookServiceBasic()
}
async InitService() {
if (!this.bookTaskService) {
this.bookTaskService = await BookTaskService.getInstance()
}
if (!this.taskScheduler) {
this.taskScheduler = new TaskScheduler()
}
if (!this.bookService) {
this.bookService = await BookService.getInstance()
}
if (!this.bookTaskDetailService) {
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
}
if (!this.subtitle) {
this.subtitle = new Subtitle()
}
if (!this.watermark) {
this.watermark = new Watermark()
}
if (!this.bookBackTaskListService) {
this.bookBackTaskListService = await BookBackTaskListService.getInstance()
}
}
// 主动返回前端的消息
sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) {
global.newWindow[0].win.webContents.send(message_name, data)
@ -104,15 +75,9 @@ export class ReverseBook extends BookBasic {
*
* @param {*} bookTaskCondition
*/
async GetBookTaskData(bookTaskCondition): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let _bookTaskService = await BookTaskService.getInstance()
let res = _bookTaskService.GetBookTaskData(bookTaskCondition)
if (res.code == 0) {
throw new Error(res.message)
}
let res = await this.bookServiceBasic.GetBookTaskData(bookTaskCondition)
// //TODO 这个后面是不是将所有的数据都是用数据库
// // 这边加载自定义风格
// let styleLists = await this.tagDefine.getTagDataByTypeAndProperty('dynamic', "style_tags");
@ -142,10 +107,10 @@ export class ReverseBook extends BookBasic {
// }
// res.data.bookTasks[index].styleList = styleList;
// }
return successMessage(res.data, '获取小说任务成功', 'ReverseBook_GetBookTaskData')
return successMessage(res, '获取小说任务成功', 'ReverseBook_GetBookTaskData')
} catch (error) {
return errorMessage(
'获取小说对应批次错误,错误信息入' + error.message,
'获取小说对应批次错误,错误信息入' + error.message,
'ReverseBook_GetBookTaskData'
)
}
@ -153,20 +118,15 @@ export class ReverseBook extends BookBasic {
/**
*
* @param bookTaskId
* @param bookTaskId ID
*/
async GetBookTaskDetail(bookTaskId: string) {
async GetBookTaskDetail(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let _bookTaskDetailService = await BookTaskDetailService.getInstance()
let res = _bookTaskDetailService.GetBookTaskData({ bookTaskId: bookTaskId })
if (res.code == 0) {
throw new Error(res.message)
}
return res
let res = await this.bookServiceBasic.GetBookTaskDetailData({ bookTaskId: bookTaskId })
return successMessage(res, '获取小说任务详情成功', 'ReverseBook_GetBookTaskDetail')
} catch (error) {
return errorMessage(
'获取小说对应批次错误,错误信息入' + error.message,
'获取小说对应批次错误,错误信息入下:' + error.message,
'ReverseBook_GetBookTaskData'
)
}
@ -236,10 +196,7 @@ export class ReverseBook extends BookBasic {
* @returns
*/
async GetBookAndTask(bookId: string, bookTaskName: string) {
let book = this.bookService.GetBookDataById(bookId)
if (book == null) {
throw new Error("查找小说数据失败");
}
let book = await this.bookServiceBasic.GetBookDataById(bookId)
// 获取小说对应的批次任务数据,默认初始化为第一个
let condition = {
bookId: bookId
@ -249,20 +206,14 @@ export class ReverseBook extends BookBasic {
} else {
condition["id"] = bookTaskName
}
let bookTaskRes = await this.bookTaskService.GetBookTaskData(condition)
if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) {
let msg = "没有找到对应的小说批次任务数据"
this.taskScheduler.AddLogToDB(bookId, book.type, msg, OtherData.DEFAULT, LoggerStatus.FAIL)
throw new Error(msg)
}
return { book: book as Book.SelectBook, bookTask: bookTaskRes.data.bookTasks[0] as Book.SelectBookTask }
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<any> {
try {
await this.InitService()
let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001')
let res = await this.basicReverse.ComputeStoryboardFunc(bookId, bookTask.id);
// 分镜成功直接返回
@ -279,15 +230,15 @@ export class ReverseBook extends BookBasic {
*/
async Framing(bookId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let { book, bookTask } = await this.GetBookAndTask(bookId, 'output_00001')
// 获取所有的分镜数据
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetail.data.length <= 0) {
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[]
try {
// 获取所有的分镜数据
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
} catch (error) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
bookId,
@ -298,9 +249,7 @@ export class ReverseBook extends BookBasic {
)
throw new Error("没有传入分镜数据,请先进行分镜计算");
}
let bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[]
let bookTaskDetails = bookTaskDetail;
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
@ -314,12 +263,12 @@ export class ReverseBook extends BookBasic {
LoggerStatus.SUCCESS
)
bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[]
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);
@ -331,96 +280,70 @@ export class ReverseBook extends BookBasic {
return errorMessage("开始切割视频并抽帧失败,失败信息如下:" + error.message, 'ReverseBook_Framing')
}
}
/**
*
*/
async GetCopywriting(bookId: string, bookTaskId: string = null): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let { book, bookTask } = await this.GetBookAndTask(bookId, bookTaskId ? bookTaskId : 'output_00001')
if (isEmpty(book.subtitlePosition)) {
throw new Error("请先设置小说的字幕位置")
}
// 获取所有的分镜数据
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetail.data.length <= 0) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,请先进行分镜计算`,
OtherData.DEFAULT,
LoggerStatus.DOING
)
throw new Error("没有传入分镜数据,请先进行分镜计算");
}
let bookTaskDetails = bookTaskDetail.data as Book.SelectBookTaskDetail[]
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let res = await this.subtitle.GetVideoFrameText({
id: item.id,
videoPath: item.videoPath,
type: SubtitleSavePositionType.STORYBOARD_VIDEO,
subtitlePosition: book.subtitlePosition
})
if (res.code == 0) {
throw new Error(res.message)
}
// 修改数据
this.bookTaskDetailService.UpdateBookTaskDetail(item.id, {
word: res.data,
afterGpt: res.data
});
// let res = await this.basicReverse.GetCopywritingFunc(book, item);
// 将当前的数据实时返回,前端进行修改
this.sendReturnMessage({
code: 1,
id: item.id,
type: ResponseMessageType.GET_TEXT,
data: res.data // 返回识别到的文案
})
// 添加日志
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`${item.name} 识别文案成功`,
bookTask.id,
LoggerStatus.SUCCESS
)
}
return successMessage(null, "识别是所有文案成功", "ReverseBook_GetCopywriting")
} catch (error) {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting')
}
}
//#endregion
//#region 反推相关任务
/**
*
* @param id IDIDID
* @param operateBookType
* @param type
* @returns
*/
async AddReversePrompt(id: string, operateBookType: OperateBookType, type: BookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTaskDetailIds: string[] = []
let bookTaskDetails = [] as Book.SelectBookTaskDetail[]
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: id
})
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
bookTaskDetails = [bookTaskDetail]
} else if (operateBookType == OperateBookType.UNDERBOOKTASK) {
// 下生图
let tempBooktaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
let tempBooktaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookTaskId: tempBooktaskDetail.bookTaskId
});
for (let i = 0; i < tempBooktaskDetails.length; i++) {
const element = tempBooktaskDetails[i];
if (tempBooktaskDetail.no <= element.no) {
bookTaskDetails.push(element)
}
}
} else {
throw new Error("未知的操作模式,请检查");
}
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
bookTaskDetailIds.push(element.id)
}
if (bookTaskDetailIds.length <= 0) {
throw new Error("没有需要反推的数据,请检查")
}
await this.AddReversePromptTask(bookTaskDetailIds, type);
return successMessage(null, "添加反推任务成功", 'ReverseBook_AddReversePrompt')
} catch (error) {
return errorMessage("添加反推任务失败,错误信息如下:" + error.message, "ReverseBook_AddReversePrompt")
}
}
/**
*
* @param bookTaskDetailId ID
* @param type
* @returns
*/
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async AddReversePromptTask(bookTaskDetailIds: string[], type: BookType): Promise<void> {
try {
await this.InitService()
for (let index = 0; index < bookTaskDetailIds.length; index++) {
const bookTaskDetailId = bookTaskDetailIds[index];
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
if (bookTaskDetail == null) {
throw new Error("没有找到对应的分镜数据")
}
let book = this.bookService.GetBookDataById(bookTaskDetail.bookId)
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId)
let book = await this.bookServiceBasic.GetBookDataById(bookTaskDetail.bookId)
// 是不是又额外的类型,没有的话试用小说的类型
let task_type = undefined as BookBackTaskType
if (!type) {
@ -436,17 +359,14 @@ export class ReverseBook extends BookBasic {
default:
throw new Error("暂不支持的推理类型")
}
let taskRes = await this.bookBackTaskListService.AddBookBackTask(book.id, task_type, TaskExecuteType.AUTO, bookTaskDetail.bookTaskId, bookTaskDetail.id
// 添加后台任务
await this.bookServiceBasic.AddBookBackTask(book.id, task_type, TaskExecuteType.AUTO, bookTaskDetail.bookTaskId, bookTaskDetail.id
);
if (taskRes.code == 0) {
throw new Error(taskRes.message)
}
// 添加返回日志
await this.taskScheduler.AddLogToDB(book.id, book.type, `添加 ${task_type} 反推任务成功`, bookTaskDetail.bookTaskId, LoggerStatus.SUCCESS)
}
return successMessage(null, "添加反推任务成功", "ReverseBook_AddReversePromptTask")
} catch (error) {
return errorMessage("添加单个反推失败,错误信息如下:" + error.message, "ReverseBook_SingleReversePrompt")
throw error
}
}
@ -481,14 +401,12 @@ export class ReverseBook extends BookBasic {
}
}
/**
*
* @param bookTaskDetailIds ID
*/
async RemoveReverseData(bookTaskDetailIds: string[]) {
try {
await this.InitService()
// 开始删除
if (bookTaskDetailIds.length <= 0) {
throw new Error("没有传入要删除的数据")
@ -496,7 +414,7 @@ export class ReverseBook extends BookBasic {
for (let i = 0; i < bookTaskDetailIds.length; i++) {
const element = bookTaskDetailIds[i];
this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(element)
await this.bookServiceBasic.DeleteBookTaskDetailReversePromptById(element);
}
// 全部删除完毕
@ -505,6 +423,30 @@ export class ReverseBook extends BookBasic {
return errorMessage("删除反推数据失败,错误信息如下:" + error.message, "ReverseBook_RemoveReverseData")
}
}
//#endregion
//#region 提示词相关操作
/**
*
* @param id IDIDID
* @param operateBookType
* @param type MJSD D3
* @returns
*/
async MergePrompt(id: string, type: PromptMergeType, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
if (type == PromptMergeType.MJ_MERGE) {
return await this.mjOpt.MergePrompt(id, operateBookType);
} else if (type == PromptMergeType.SD_MERGE) {
return await this.sdOpt.MergePrompt(id, operateBookType)
} else {
throw new Error("未知的合并模式,请检查")
}
} catch (error) {
return errorMessage("合并提示词失败,错误信息如下:" + error.message, "ReverseBook_MergePrompt")
}
}
//#endregion
}

View File

@ -0,0 +1,59 @@
import { isEmpty } from "lodash";
import { GeneralResponse } from "../../../model/generalResponse";
import { errorMessage, successMessage } from "../../Public/generalTools";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import path from 'path';
import { FfmpegOptions } from "../ffmpegOptions";
import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file";
import fs from 'fs';
export class BookFrame {
bookServiceBasic: BookServiceBasic
ffmpegOptions: FfmpegOptions
constructor() {
this.bookServiceBasic = new BookServiceBasic();
this.ffmpegOptions = new FfmpegOptions();
}
/**
*
* @param bookTaskDetailId ID
* @param current
*/
async ReplaceVideoCurrentFrame(bookTaskDetailId: string, current: number): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId);
let videoPath = bookTaskDetail.videoPath;
if (isEmpty(videoPath)) {
throw new Error('未找到对应的视频路径,请检查');
}
let oldImagePath = bookTaskDetail.oldImage;
let tempOldImagePath = path.join(path.dirname(oldImagePath), `temp_${bookTaskDetail.name}.png`);
// 删除之前的图片
if (await CheckFileOrDirExist(tempOldImagePath)) {
await fs.promises.unlink(tempOldImagePath);
}
// 开始裁剪
let res = await this.ffmpegOptions.FfmpegGetFrame(current, videoPath, tempOldImagePath);
if (res.code == 0) {
// 抽帧失败
global.logger.error('BookFrame_ReplaceVideoCurrentFrame', '抽取视频指定帧失败,请检查');
throw new Error('抽取视频指定帧失败,请检查');
}
// 成功。开始替换
let fileExist = await CheckFileOrDirExist(tempOldImagePath);
if (!fileExist) {
throw new Error('抽帧出来的图片不存在,请检查');
}
await fs.promises.unlink(oldImagePath);
// 重命名
await CopyFileOrFolder(tempOldImagePath, oldImagePath);
await fs.promises.unlink(tempOldImagePath);
return successMessage(oldImagePath + '?t=' + new Date().getTime(), '视频指定的视频帧成功', 'BookFrame_ReplaceVideoCurrentFrame');
} catch (error) {
return errorMessage('替换指定分镜的视频当前帧失败,失败信息如下:' + error.toString(), 'BookFrame_ReplaceVideoCurrentFrame');
}
}
}

View File

@ -1,10 +1,15 @@
import { CheckFolderExistsOrCreate, DeleteFolderAllFile } from "../../../define/Tools/file";
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, OperateBookType } from "../../../define/enum/bookEnum";
import { 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 { define } from '../../../define/define'
import { v4 as uuidv4 } from 'uuid'
import { GeneralResponse } from "../../../model/generalResponse";
/**
*
@ -33,7 +38,7 @@ export class BookTask {
*
* @param bookTaskId ID
*/
async ReSetBookTask(bookTaskId: string) {
async ReSetBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
console.log(bookTaskId)
await this.InitService()
@ -57,7 +62,7 @@ export class BookTask {
*
* @param bookTaskId ID
*/
async DeleteBookTask(bookTaskId: string) {
async DeleteBookTask(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService();
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId);
@ -75,4 +80,212 @@ export class BookTask {
}
}
/**
*
* @param bookTaskId ID
* @returns
*/
async OneToFourBookTask(bookTaskId: string) {
try {
console.log(bookTaskId)
await this.InitService();
let copyCount = 100
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId)
if (bookTask == null) {
throw new Error("没有找到对应的数小说任务,请检查数据")
}
// 获取所有的出图中最少的
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookTaskId: bookTaskId
}).data as Book.SelectBookTaskDetail[]
if (bookTaskDetail == null || bookTaskDetail.length <= 0) {
throw new Error("没有对应的小说分镜任务,请先添加分镜任务")
}
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
if (isEmpty(element.subImagePath)) {
throw new Error("检测到图片没有出完,请先检查出图")
}
if (element.subImagePath == null || element.subImagePath.length <= 0) {
throw new Error("检测到图片没有出完,请先检查出图")
}
if (element.subImagePath.length < copyCount) {
copyCount = element.subImagePath.length
}
}
if (copyCount <= 0) {
throw new Error("批次设置错误,无法进行一拆四")
}
// 开始复制
let res = await this.CopyNewBookTask(bookTask, bookTaskDetail, copyCount - 1, CopyImageType.ONE)
if (res.code == 0) {
throw new Error(res.message)
}
return successMessage(res.data, "一拆四成功", "BookBasic_OneToFourBookTask")
} catch (error) {
return errorMessage("一拆四失败,失败信息如下:" + error.message, "BookBasic_OneToFourBookTask")
}
}
/**
*
* @param oldBookTaskId
* @param copyCount
* @param isCopyImage
*/
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 name = 'output_0000' + no
let imageFolder = path.join(define.project_path, `${sourceBookTask.bookId}/tmp/${name}`)
await CheckFolderExistsOrCreate(imageFolder)
// 创建对应的文件夹
let addOneBookTask = {
id: uuidv4(),
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(define.project_path, 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,
} as Book.SelectBookTask
addBookTask.push(addOneBookTask)
for (let j = 0; j < sourceBookTaskDetail.length; j++) {
const element = sourceBookTaskDetail[j];
let outImagePath = undefined as string
let subImagePath = [] as string[]
if (copyImageType == CopyImageType.ALL) { // 直接全部复制
outImagePath = element.outImagePath
subImagePath = element.subImagePath
} else if (copyImageType == CopyImageType.ONE) { // 只复制对应的
let oldImage = element.subImagePath[i + 1]
outImagePath = path.join(imageFolder, path.basename(element.outImagePath))
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(define.project_path, `${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 = uuidv4()
sdConifg.webuiConfig = tempSdConfig
}
}
let reverseId = uuidv4()
// 处理反推数据
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 = reverseId
}
}
let addOneBookTaskDetail = {
id: reverseId,
no: element.no,
name: element.name,
bookId: sourceBookTask.bookId,
bookTaskId: addOneBookTask.id,
videoPath: path.relative(define.project_path, element.videoPath),
word: element.word,
oldImage: path.relative(define.project_path, element.oldImage),
afterGpt: element.afterGpt,
startTime: element.startTime,
endTime: element.endTime,
timeLimit: element.timeLimit,
subValue: element.subValue,
characterTags: element.characterTags && element.characterTags.length > 0 ? cloneDeep(element.characterTags) : [],
gptPrompt: element.gptPrompt,
outImagePath: path.relative(define.project_path, outImagePath),
subImagePath: subImagePath || [],
prompt: element.prompt,
adetailer: element.adetailer,
sdConifg: sdConifg,
createTime: new Date(),
updateTime: new Date(),
audioPath: element.audioPath,
subtitlePosition: element.subtitlePosition,
status: element.status,
reversePrompt: reverseMessage,
imageLock: element.imageLock
} as Book.SelectBookTaskDetail
addBookTaskDetail.push(addOneBookTaskDetail)
}
}
// 数据处理完毕,开始新增数据
// 将所有的复制才做,全部放在一个事务中
this.bookTaskService.transaction(() => {
for (let i = 0; i < addBookTask.length; i++) {
const element = addBookTask[i];
this.bookTaskService.realm.create('BookTask', element)
}
for (let i = 0; i < addBookTaskDetail.length; i++) {
const element = addBookTaskDetail[i];
this.bookTaskDetailService.realm.create('BookTaskDetail', element)
}
})
// 全部创建完成
// 查找到数据,然后全部返回
let returnBookTask = this.bookTaskService.GetBookTaskData({
bookId: sourceBookTask.bookId
}).data as Book.SelectBookTask[]
return successMessage(returnBookTask, "复制小说任务成功", "BookBasic_CopyNewBookTask")
} catch (error) {
console.log(error)
throw error
}
}
}

View File

@ -10,14 +10,17 @@ import { CheckFolderExistsOrCreate } from "../../../define/Tools/file";
import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService";
import fs from 'fs'
import { ClipDraft } from '../../Public/clipDraft'
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
export class BookVideo {
bookService: BookService
bookTaskService: BookTaskService
setting: Setting
bookTaskDetailService: BookTaskDetailService
bookServiceBasic: BookServiceBasic
constructor() {
this.setting = new Setting(global)
this.bookServiceBasic = new BookServiceBasic()
}
async InitService() {
@ -71,17 +74,16 @@ export class BookVideo {
}
// 将修改数据放在一个事务中
this.bookService.transaction(() => {
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
let modifyBookTask = this.bookService.realm.objectForPrimaryKey('BookTask', element.id);
modifyBookTask.backgroundMusic = book.backgroundMusic;
modifyBookTask.friendlyReminder = book.friendlyReminder;
modifyBookTask.draftSrtStyle = book.draftSrtStyle;
modifyBookTask.srtPath = book.srtPath;
modifyBookTask.audioPath = book.audioPath;
}
})
for (let i = 0; i < bookTasks.length; i++) {
const element = bookTasks[i];
this.bookServiceBasic.UpdetedBookTaskData(element.id, {
backgroundMusic: book.backgroundMusic,
friendlyReminder: book.friendlyReminder,
draftSrtStyle: book.draftSrtStyle,
srtPath: book.srtPath,
audioPath: book.audioPath,
})
}
return successMessage({
backgroundMusic: book.backgroundMusic,
friendlyReminder: book.friendlyReminder,

178
src/main/Service/GPT/gpt.ts Normal file
View File

@ -0,0 +1,178 @@
import { isEmpty } from "lodash";
import { gptDefine } from "../../../define/gptDefine";
import axios from "axios";
/**
* GPT相关的服务都在这边
*/
export class GptService {
gptUrl: string = undefined
gptModel: string = undefined
gptApiKey: string = undefined
//#region GPT 设置
/**
* GPT的所有的服务商
* @param type all
* @param callback
* @returns
*/
private async GetGPTBusinessOption(type: string, callback: Function = null): Promise<any> {
let res = await gptDefine.getGptDataByTypeAndProperty(type, "gpt_options", []);
if (res.code == 0) {
throw new Error(res.message)
} else {
if (callback) {
callback(res.data)
}
return res.data
}
}
async RefreshGptSetting() {
let all_options = await this.GetGPTBusinessOption("all", (value) => value.gpt_url);
let index = all_options.findIndex(item => item.value == global.config.gpt_business && item.gpt_url)
if (index < 0) {
throw new Error("没有找到指定的GPT服务商的配置请检查")
}
this.gptUrl = all_options[index].gpt_url;
this.gptApiKey = global.config.gpt_key;
this.gptModel = global.config.gpt_model;
}
/**
* GPT的设置
*/
async InitGptSetting(refresh = false) {
if (refresh) {
await this.RefreshGptSetting()
} else {
// 判断是不是存在必要信息
if (isEmpty(this.gptUrl) || isEmpty(this.gptModel) || isEmpty(this.gptApiKey)) {
await this.RefreshGptSetting();
}
}
}
/**
*
* @param data
* @param gpt_url
* @returns
*/
ModifyData(data: any, gpt_url: string = null) {
let res = data;
if (!gpt_url) {
gpt_url = this.gptUrl
}
if (gpt_url.includes("dashscope.aliyuncs.com")) {
res = {
"model": data.model,
"input": {
"messages": data.messages,
},
"parameters": {
"result_format": "message"
}
}
}
return res;
}
/**
*
* @param res
* @param gpt_url URL
* @returns
*/
GetResponseContent(res: any, gpt_url: string = null) {
let content = "";
if (!gpt_url) {
gpt_url = this.gptUrl
}
if (gpt_url.includes("dashscope.aliyuncs.com")) {
content = res.data.output.choices[0].message.content;
} else {
content = res.data.choices[0].message.content;
}
return content;
}
//#endregion
/**
* GPT请求
* @param {*} message
* @param {*} gpt_url gpt的urlglobal中取
* @param {*} gpt_key gpt的keyglobal中取
* @param {*} gpt_model gpt的modelglobal中取
* @returns
*/
async FetchGpt(message: any, gpt_model: string = null, gpt_key: string = null, gpt_url: string = null): Promise<string> {
try {
await this.InitGptSetting();
let data = {
"model": gpt_model ? gpt_model : this.gptModel,
"messages": message
};
data = this.ModifyData(data, gpt_url);
let config = {
method: 'post',
maxBodyLength: Infinity,
url: gpt_url ? gpt_url : this.gptUrl,
headers: {
'Authorization': `Bearer ${gpt_key ? gpt_key : this.gptApiKey}`,
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
};
let res = await axios.request(config);
let content = this.GetResponseContent(res, this.gptUrl);
return content;
} catch (error) {
throw error;
}
}
//#region 繁体中文 -> 简体中文
/**
*
* @param traditionalText
* @param apiKey Lai API的 Key
* @param baseUrl baseurl
* @returns
*/
async ChineseTraditionalToSimplified(traditionalText: string, apiKey: string, baseUrl: string = null): Promise<string> {
try {
let message = [
{
"role": "system",
"content": '我想让你充当中文繁体转简体专家用简体中文100%还原繁体中文,不要加其他的联想,只把原有的繁体中文转换为简体中文,请检查所有信息是否准确,并在回答时保持简活,不需要任何其他反馈。'
}, {
"role": "user",
"content": '上研究生後,發現導師竟然是曾經網戀的前男友。'
}, {
"role": "assistant",
"content": '上研究生后,发现导师竟然是曾经网恋的前男友。'
}, {
"role": "user",
"content": traditionalText
}
]
let baseSubUrl = baseUrl ? (baseUrl.endsWith('/') ? baseUrl + 'v1/chat/completions' : baseUrl + '/v1/chat/completions') : null;
let url = baseSubUrl ? baseSubUrl : "https://api.laitool.cc/v1/chat/completions"
// 开始请求这个默认是使用的是LAI API的gpt-4o-mini
let content = await this.FetchGpt(message, 'gpt-4o-mini', apiKey, url)
return content
} catch (error) {
throw error
}
}
//#endregion
}

View File

@ -7,7 +7,7 @@ import { CheckFolderExistsOrCreate, CopyFileOrFolder, JoinPath } from "../../../
import { define } from "../../../define/define"
import { GetImageBase64, ImageSplit } from "../../../define/Tools/image";
import MJApi from "./mjApi"
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, MergeType, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum";
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus, BookType, DialogType, MJAction, OperateBookType, TaskExecuteType } from "../../../define/enum/bookEnum";
import { DEFINE_STRING } from "../../../define/define_string";
import { MJ } from "../../../model/mj";
import { MJRespoonseType } from "../../../define/enum/mjEnum";
@ -21,6 +21,7 @@ import { TaskScheduler } from "../taskScheduler";
import { BookService } from "../../../define/db/service/Book/bookService";
import { Tools } from "../../../main/tools"
import { MJSettingService } from "../../../define/db/service/SoftWare/mjSettingService";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import path from "path"
const { v4: uuidv4 } = require('uuid')
@ -39,10 +40,12 @@ export class MJOpt {
bookService: BookService
tools: Tools;
mjSettingService: MJSettingService;
bookServiceBasic: BookServiceBasic
constructor() {
this.imageStyle = new ImageStyle()
this.taskScheduler = new TaskScheduler()
this.tools = new Tools()
this.bookServiceBasic = new BookServiceBasic();
}
async InitService() {
if (!this.reverseBook) {
@ -208,7 +211,8 @@ export class MJOpt {
this.bookTaskDetail.UpdateBookTaskDetail(task.bookTaskDetailId, {
status: BookTaskStatus.REVERSE_DONE,
reversePrompt: reversePrompt
reversePrompt: reversePrompt,
gptPrompt: undefined
});
task_res.prompt = JSON.stringify(reversePrompt);
@ -331,20 +335,35 @@ export class MJOpt {
* @param id ID
* @param mergeType
*/
async MergePrompt(id: string, mergeType: MergeType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async MergePrompt(id: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[];
let bookTask = undefined as Book.SelectBookTask;
if (mergeType == MergeType.BOOKTASK) {
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetail = this.bookTaskDetail.GetBookTaskData({
bookTaskId: id
}).data
bookTask = this.bookTaskService.GetBookTaskDataById(id);
} else if (mergeType == MergeType.BOOKTASKDETAIL) {
bookTaskDetail = [this.bookTaskDetail.GetBookTaskDetailDataById(id)]
// 判断是不是有为空的
let emptyName = [] as string[]
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
if (isEmpty(element.gptPrompt)) {
emptyName.push(element.name)
}
}
if (emptyName.length > 0) {
throw new Error("有空的提示词,请先推理")
}
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
throw new Error("当前分镜没有推理提示词,请先生成")
}
bookTaskDetail = [tempBookTaskDetail]
bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId);
} else {
throw new Error("未知的合并类型")
@ -371,7 +390,6 @@ export class MJOpt {
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
let promptStr = '';
for (let i = 0; i < promptSort.length; i++) {
const element = promptSort[i];

View File

@ -1,4 +1,3 @@
import { MergeType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
import { GeneralResponse } from "../../../model/generalResponse";
import { checkStringValueAddSuffix, errorMessage, successMessage } from "../../Public/generalTools";
@ -8,14 +7,19 @@ 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";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
const fspromise = fs.promises
export class SDOpt {
bookTaskDetailService: BookTaskDetailService
bookTaskService: BookTaskService
imageStyle: ImageStyle
constructor() {
bookServiceBasic: BookServiceBasic
constructor() {
this.bookServiceBasic = new BookServiceBasic()
}
@ -34,22 +38,37 @@ export class SDOpt {
/**
* SD的提示词合并
* @param id ID
* @param mergeType
* @param operateBookType
* @returns
*/
async MergePrompt(id: string, mergeType: MergeType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
async MergePrompt(id: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let bookTaskDetail = undefined as Book.SelectBookTaskDetail[];
let bookTask = undefined as Book.SelectBookTask;
if (mergeType == MergeType.BOOKTASK) {
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetail = this.bookTaskDetailService.GetBookTaskData({
bookTaskId: id
}).data
bookTask = this.bookTaskService.GetBookTaskDataById(id);
} else if (mergeType == MergeType.BOOKTASKDETAIL) {
bookTaskDetail = [this.bookTaskDetailService.GetBookTaskDetailDataById(id)];
// 判断是不是有为空的
let emptyName = [] as string[]
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i];
if (isEmpty(element.gptPrompt)) {
emptyName.push(element.name)
}
}
if (emptyName.length > 0) {
throw new Error("有空的提示词,请先推理")
}
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(id);
if (isEmpty(tempBookTaskDetail.gptPrompt)) {
throw new Error("当前分镜没有推理提示词,请先生成")
}
bookTaskDetail = [tempBookTaskDetail];
bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail[0].bookTaskId);
} else {
throw new Error("未知的合并类型")

View File

@ -0,0 +1,256 @@
import { DEFINE_STRING } from "../../../define/define_string";
import { GeneralResponse } from "../../../model/generalResponse";
import { BookService } from "../../../define/db/service/Book/bookService";
import { Book } from "../../../model/book";
import { BookTaskService } from "../../../define/db/service/Book/bookTaskService";
import { TaskScheduler } from "../taskScheduler";
import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum";
import { BookTaskDetailService } from "../../../define/db/service/Book/bookTaskDetailService";
import { BookBackTaskListService } from "../../../define/db/service/Book/bookBackTaskListService";
import { BookBackTaskType, BookTaskStatus, TaskExecuteType } from "../../../define/enum/bookEnum";
/**
*
* 便
*/
export class BookServiceBasic {
bookService: BookService
bookTaskService: BookTaskService;
taskScheduler: TaskScheduler
bookTaskDetailService: BookTaskDetailService
bookBackTaskListService: BookBackTaskListService
constructor() {
this.taskScheduler = new TaskScheduler()
}
async InitService() {
if (!this.bookService) {
this.bookService = await BookService.getInstance()
}
if (!this.bookTaskService) {
this.bookTaskService = await BookTaskService.getInstance()
}
if (!this.bookTaskDetailService) {
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
}
if (!this.bookBackTaskListService) {
this.bookBackTaskListService = await BookBackTaskListService.getInstance()
}
}
// 主动返回前端的消息
sendReturnMessage(data: GeneralResponse.MessageResponse, message_name = DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN) {
global.newWindow[0].win.webContents.send(message_name, data)
}
//#region 小说相关的基础服务
/**
* ID获取小说数据
* @param bookId ID
* @returns
*/
async GetBookDataById(bookId: string): Promise<Book.SelectBook> {
await this.InitService();
let book = this.bookService.GetBookDataById(bookId);
if (book == null) {
let msg = '未找到对应的小说数据,请检查'
throw new Error(msg);
}
return book
}
/**
* ID的数据
* @param bookId ID
* @param data
*/
async UpdateBookData(bookId: string, data: Book.SelectBook): Promise<Book.SelectBook> {
await this.InitService();
let res = this.bookService.UpdateBookData(bookId, data)
return res
}
/**
*
* @param bookId ID
*/
async DeleteBookData(bookId: string): Promise<void> {
await this.InitService();
this.bookService.DeleteBookData(bookId)
}
//#endregion
//#region 小说批次任务相关的基础服务
/**
* ID获取小说批次任务数据
* @param bookTaskId ID
* @returns
*/
async GetBookTaskDataId(bookTaskId: string): Promise<Book.SelectBookTask> {
await this.InitService();
let bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskId);
if (bookTask == null) {
let msg = '未找到对应的小说批次任务数据,请检查';
throw new Error(msg);
}
return bookTask
}
/**
*
* @param bookTaskCondition
*/
async GetBookTaskData(bookTaskCondition: Book.QueryBookTaskCondition): Promise<{ bookTasks: Book.SelectBookTask[], total: number }> {
await this.InitService();
let bookTasks = this.bookTaskService.GetBookTaskData(bookTaskCondition)
if (bookTasks.data.bookTasks.length <= 0 || bookTasks.data.total <= 0) {
throw new Error("未找到对应的小说批次任务数据,请检查")
}
return bookTasks.data
}
/**
*
* @param bookTaskId ID
* @param data
*/
async UpdetedBookTaskData(bookTaskId: string, data: Book.SelectBookTask): Promise<void> {
await this.InitService();
this.bookTaskService.UpdetedBookTaskData(bookTaskId, data)
}
/**
*
* @param bookTaskId ID
*/
async DeleteBookTaskData(bookTaskId: string): Promise<void> {
await this.InitService();
this.bookTaskService.DeleteBookTask(bookTaskId)
}
//#endregion
//#region 小说批次任务对应的分镜的相关的基础服务
/**
* ID
* @param bookTaskDetailId ID
* @returns
*/
async GetBookTaskDetailDataById(bookTaskDetailId: string): Promise<Book.SelectBookTaskDetail> {
await this.InitService();
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId)
if (bookTaskDetail == null) {
let msg = "未找到对应的小说批次任务分镜数据,请检查"
global.logger.error('BookServiceBasic_GetBookTaskDetailDataById', msg);
throw new Error("未找到对应的小说批次任务分镜数据,请检查")
}
return bookTaskDetail
}
/**
*
* @param condition
*/
async GetBookTaskDetailData(condition: Book.QueryBookTaskDetailCondition): Promise<Book.SelectBookTaskDetail[]> {
await this.InitService();
let bookTaskDetails = this.bookTaskDetailService.GetBookTaskData(condition)
if (bookTaskDetails.data.length <= 0) {
let msg = "未找到对应的小说批次任务分镜数据,请检查";
throw new Error(msg)
}
return bookTaskDetails.data
}
/**
* ID
* @param bookTaskDetailId ID
* @param data
*/
async UpdateBookTaskDetail(bookTaskDetailId: string, data: Book.SelectBookTaskDetail): Promise<Book.SelectBookTaskDetail> {
await this.InitService();
let res = this.bookTaskDetailService.UpdateBookTaskDetail(bookTaskDetailId, data)
return res
}
/**
*
* @param bookTaskId ID
* @param status
* @param errorMsg
*/
async UpdateBookTaskStatus(bookTaskId: string, status: BookTaskStatus, errorMsg: string | null = null): Promise<void> {
await this.InitService();
this.bookTaskService.UpdateBookTaskStatus(bookTaskId, status, errorMsg);
}
/**
*
* @param bookTaskDetail
*/
async DeleteBookTaskDetailReversePromptById(bookTaskDetailId: string): Promise<void> {
await this.InitService();
this.bookTaskDetailService.DeleteBookTaskDetailReversePromptById(bookTaskDetailId)
}
//#endregion
//#region 小说后台任务相关操作
/**
*
* @param bookId ID
* @param taskType
* @param executeType
* @param bookTaskId ID
* @param bookTaskDetailId ID
*/
async AddBookBackTask(bookId: string,
taskType: BookBackTaskType,
executeType = TaskExecuteType.AUTO,
bookTaskId = null,
bookTaskDetailId = null): Promise<TaskModal.Task> {
await this.InitService();
let res = this.bookBackTaskListService.AddBookBackTask(bookId, taskType, executeType, bookTaskId, bookTaskDetailId)
if (res.code == 0) {
throw new Error(res.message)
}
return res.data as TaskModal.Task
}
//#endregion
/**
* ID和小说批次任务ID获取小说和小说批次任务数据
* @param bookId ID
* @param bookTaskName ID
* @returns
*/
async GetBookAndTask(bookId: string, bookTaskName: string) {
await this.InitService();
let book = this.bookService.GetBookDataById(bookId)
if (book == null) {
throw new Error("查找小说数据失败");
}
// 获取小说对应的批次任务数据,默认初始化为第一个
let condition = {
bookId: bookId
} as Book.QueryBookBackTaskCondition
if (bookTaskName == "output_00001") {
condition["name"] = bookTaskName
} else {
condition["id"] = bookTaskName
}
let bookTaskRes = this.bookTaskService.GetBookTaskData(condition)
if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) {
let msg = "没有找到对应的小说批次任务数据"
this.taskScheduler.AddLogToDB(bookId, book.type, msg, OtherData.DEFAULT, LoggerStatus.FAIL)
throw new Error(msg)
}
return { book: book as Book.SelectBook, bookTask: bookTaskRes.data.bookTasks[0] as Book.SelectBookTask }
}
}

View File

@ -0,0 +1,76 @@
import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService';
export class SoftWareServiceBasic {
softwareService: SoftwareService
constructor() { }
async InitService() {
if (!this.softwareService) {
this.softwareService = await SoftwareService.getInstance()
}
}
//#region software相关的基础服务
/**
*
* @param software
*/
async UpdateSoftware(software: SoftwareSettingModel.SoftwareSetting): Promise<void> {
await this.InitService();
this.softwareService.UpdateSoftware(software)
}
/**
*
* @param software
*/
async AddSfotware(software: SoftwareSettingModel.SoftwareSetting): Promise<void> {
await this.InitService();
this.softwareService.AddSfotware(software);
}
/**
*
*
*/
async GetSoftwareData(): Promise<SoftwareSettingModel.SoftwareSetting> {
await this.InitService();
let softwares = this.softwareService.GetSoftwareData();
if (softwares.data.length <= 0) {
let msg = "未找到软件配置信息,请检查";
global.logger.error(
'SoftWareServiceBasic_GetSoftwareData',
'获取软件的基础设置失败 ,错误信息如下:' + msg
)
throw new Error(msg)
}
return softwares.data[0]
}
/**
*
* @param property
* @returns
*/
async GetSoftWarePropertyData(property: string): Promise<string> {
await this.InitService();
let res = this.softwareService.GetSoftWarePropertyData(property)
return res
}
/**
*
* @param property
* @param data
*/
async SaveSoftwarePropertyData(property: string, data: string): Promise<void> {
await this.InitService();
this.softwareService.SaveSoftwarePropertyData(property, data)
}
//#endregion
}

View File

@ -1,45 +1,47 @@
import { isEmpty } from 'lodash'
import { BookService } from '../../define/db/service/Book/bookService'
import { errorMessage, successMessage } from '../Public/generalTools'
import { FfmpegOptions } from './ffmpegOptions'
import { SubtitleSavePositionType } from '../../define/enum/waterMarkAndSubtitle'
import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService'
import { define } from '../../define/define'
import { errorMessage, successMessage } from '../../Public/generalTools'
import { FfmpegOptions } from '../ffmpegOptions'
import { SubtitleSavePositionType } from '../../../define/enum/waterMarkAndSubtitle'
import { define } from '../../../define/define'
import path from 'path'
import {
CheckFileOrDirExist,
CheckFolderExistsOrCreate,
DeleteFolderAllFile,
GetFilesWithExtensions
} from '../../define/Tools/file'
} from '../../../define/Tools/file'
import { shell } from 'electron'
import { Book } from '../../model/book'
import { Book } from '../../../model/book'
import fs from 'fs'
import { GeneralResponse } from '../../../model/generalResponse'
import { BookServiceBasic } from '../ServiceBasic/bookServiceBasic'
import { LoggerStatus, OtherData, ResponseMessageType } from '../../../define/enum/softwareEnum'
import { TaskScheduler } from '../taskScheduler'
import { SubtitleModel } from '../../../model/subtitle'
import { BookTaskStatus, OperateBookType } from '../../../define/enum/bookEnum'
import axios from 'axios'
import { GptService } from '../GPT/gpt'
import FormData from 'form-data'
const util = require('util')
const { exec } = require('child_process')
const execAsync = util.promisify(exec)
const fspromises = fs.promises
/**
*
*/
export class Subtitle {
bookService: BookService
bookTaskDetailService: BookTaskDetailService
ffmpegOptions: FfmpegOptions
bookServiceBasic: BookServiceBasic
taskScheduler: TaskScheduler
gptService: GptService
constructor() { }
async InitService() {
if (!this.bookService) {
this.bookService = await BookService.getInstance()
}
if (!this.bookTaskDetailService) {
this.bookTaskDetailService = await BookTaskDetailService.getInstance()
}
if (!this.ffmpegOptions) {
this.ffmpegOptions = new FfmpegOptions()
}
constructor() {
this.bookServiceBasic = new BookServiceBasic()
this.taskScheduler = new TaskScheduler()
this.ffmpegOptions = new FfmpegOptions()
this.gptService = new GptService()
}
//#region 通用方法
@ -50,7 +52,7 @@ export class Subtitle {
* @param {*} framesPerSecond
* @returns
*/
GenerateFrameTimes(videoDurationMs, framesPerSecond) {
GenerateFrameTimes(videoDurationMs: number, framesPerSecond: number): number[] {
// 直接使用视频总时长(毫秒),不进行向下取整
const videoDurationSec = videoDurationMs / 1000
@ -67,117 +69,70 @@ export class Subtitle {
let timePoint = Math.min(Math.round(interval * i), videoDurationMs)
frameTimes.push(timePoint)
}
return frameTimes
}
/**
*
* @param bookId ID
* @param bookTaskId ID
* @returns
*/
async GetBookAllData(bookId: string, bookTaskId: string = null): Promise<{ book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[] }> {
let { book, bookTask } = await this.bookServiceBasic.GetBookAndTask(bookId, bookTaskId ? bookTaskId : 'output_00001')
if (isEmpty(book.subtitlePosition)) {
throw new Error("请先设置小说的字幕位置")
}
// 获取所有的分镜数据
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetails.length <= 0) {
throw new Error("没有找到对应的分镜数据,请先执行对应的操作")
}
return { book, bookTask, bookTaskDetails }
}
/**
*
* @param content
* @param book
* @param bookTask
* @param bookTaskDetail
*/
async GetSubtitleLoggerAndResponse(content: string, progress: GeneralResponse.ProgressResponse, book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail): Promise<void> {
// 修改数据
await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, {
word: content,
afterGpt: content
});
// let res = await this.basicReverse.GetCopywritingFunc(book, item);
// 将当前的数据实时返回,前端进行修改
this.bookServiceBasic.sendReturnMessage({
code: 1,
id: bookTaskDetail.id,
type: ResponseMessageType.GET_TEXT,
data: {
content: content,
progress: progress
} as GeneralResponse.SubtitleProgressResponse // 返回识别到的文案
})
// 添加日志
await this.taskScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name} 识别文案成功`,
bookTask.id,
LoggerStatus.SUCCESS
)
}
//#endregion
/**
*
* @param {*} value
* @param {*} value.id ID/ID/null
* @param {*} value.type //
* @param {*} value.videoPath
* @param {*} value.subtitlePosition
*/
async GetVideoFrameText(value: Book.GetVideoFrameTextParams) {
try {
await this.InitService()
let videoPath
let tempImageFolder
let position
if (value.type == SubtitleSavePositionType.MAIN_VIDEO) {
let bookRes = this.bookService.GetBookDataById(value.id)
if (bookRes == null) {
throw new Error('没有找到小说对应的的视频地址')
}
let book = bookRes
tempImageFolder = path.join(define.project_path, `${book.id}/data/subtitle/${book.id}/temp`)
if (isEmpty(book.subtitlePosition)) {
throw new Error('请先保存位置信息')
}
position = JSON.parse(book.subtitlePosition)
videoPath = book.oldVideoPath
} else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) {
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(value.id);
if (bookTaskDetail == null) {
throw new Error("没有找到小说分镜详细信息")
}
tempImageFolder = path.join(define.project_path, `${bookTaskDetail.bookId}/data/subtitle/${bookTaskDetail.name}_${bookTaskDetail.id}/temp`)
if (isEmpty(value.subtitlePosition)) {
throw new Error('请先保存位置信息')
}
position = JSON.parse(value.subtitlePosition)
videoPath = bookTaskDetail.videoPath
} else {
throw new Error("不支持的操作");
}
await CheckFolderExistsOrCreate(tempImageFolder)
// 判断文件夹是不是存在,存在的话,将里面的所有文件删除
await DeleteFolderAllFile(tempImageFolder)
// 将视频进行抽帧目前是每秒1帧时间小于一秒抽一帧
let getDurationRes = await this.ffmpegOptions.FfmpegGetVideoDuration(videoPath)
if (getDurationRes.code == 0) {
throw new Error(getDurationRes.message)
}
let videoDuration = getDurationRes.data
let frameTime = this.GenerateFrameTimes(videoDuration, 1)
for (let i = 0; i < frameTime.length; i++) {
const item = frameTime[i];
let name = i.toString().padStart(6, '0')
let imagePath = path.join(tempImageFolder, `frame_${name}.png`)
// 开始抽帧
let res = await this.ffmpegOptions.FfmpegGetVideoFramdAndClip(
videoPath,
item,
imagePath,
position
)
if (res.code == 0) {
throw new Error(res.message)
}
}
// 开始识别
let textRes = await this.GetCurrentFrameText({
id: value.id,
type: value.type,
imageFolder: tempImageFolder
})
let allTextData = [] as string[]
// 开始获取所有的数据
let jsonPaths = await GetFilesWithExtensions(tempImageFolder, ['.json'])
for (let i = 0; i < jsonPaths.length; i++) {
const element = jsonPaths[i]
// 开始拼接
let texts = JSON.parse(await fspromises.readFile(element, 'utf-8'))
for (let j = 0; j < texts.length; j++) {
const text = texts[j][1][0]
allTextData.includes(text) ? null : allTextData.push(text)
}
}
console.log(allTextData.join(''))
// 这边计算相似度,返回过于相似的数据
// let res = await RemoveSimilarTexts(allTextData)
return successMessage(
allTextData.join(''),
'获取视频的的文案信息成功',
'WatermarkAndSubtitle_GetVideoFrameText'
)
} catch (error) {
return errorMessage(
'提取视频的的文案信息失败,错误消息如下:' + error.toString(),
'WatermarkAndSubtitle_GetCurrentFrameText'
)
}
}
//#region 获取字幕位置信息相关操作
/**
*
@ -185,9 +140,8 @@ export class Subtitle {
* @param {*} value.id ID/ID/null
* @param {*} value.type //
*/
async GetCurrentFrameText(value) {
async GetCurrentFrameText(value: { id: any; type?: SubtitleSavePositionType; imageFolder: any }): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService()
let iamgePaths = []
let imageFolder = value.imageFolder
? value.imageFolder
@ -249,7 +203,7 @@ export class Subtitle {
* @param {*} value.id ID/ID/null
* @param {*} value.type //
*/
async OpenBookSubtitlePositionScreenshot(value) {
async OpenBookSubtitlePositionScreenshot(value: { type: SubtitleSavePositionType; id: any }) {
try {
let folder
if (
@ -291,9 +245,8 @@ export class Subtitle {
* @param {*} value.type //
* @returns
*/
async SaveBookSubtitlePosition(value) {
async SaveBookSubtitlePosition(value: { type: SubtitleSavePositionType; id: string; bookSubtitlePosition: string | any[]; currentTime: number }) {
try {
await this.InitService()
let saveData = []
let videoPath
let outImagePath
@ -307,7 +260,7 @@ export class Subtitle {
throw new Error('小说ID不能为空')
}
// 获取指定的小说
let bookRes = this.bookService.GetBookDataById(value.id)
let bookRes = await this.bookServiceBasic.GetBookDataById(value.id)
if (bookRes == null) {
throw new Error('没有找到小说信息')
}
@ -353,19 +306,16 @@ export class Subtitle {
}
// 数据保存
let saveRes = await this.bookService.UpdateBookData(value.id, {
let saveRes = await this.bookServiceBasic.UpdateBookData(value.id, {
subtitlePosition: JSON.stringify(saveData)
})
if (saveRes.code == 0) {
throw new Error(saveRes.message)
}
} else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) {
// 小说分镜详细信息保存
if (value.id == null) {
throw new Error('小说分镜详细信息ID不能为空')
}
// 获取指定的小说分镜详细信息
let bookStoryboard = this.bookTaskDetailService.GetBookTaskDetailDataById(value.id)
let bookStoryboard = await this.bookServiceBasic.GetBookTaskDetailDataById(value.id)
if (bookStoryboard == null) {
throw new Error('没有找到小说分镜信息')
}
@ -414,12 +364,9 @@ export class Subtitle {
})
}
// 数据保存
let saveRes = this.bookTaskDetailService.UpdateBookTaskDetail(bookStoryboard.id, {
let saveRes = this.bookServiceBasic.UpdateBookTaskDetail(bookStoryboard.id, {
subtitlePosition: JSON.stringify(saveData)
})
if (saveRes.code == 0) {
throw new Error(saveRes.message)
}
}
// 开始设置裁剪出来的图片位置
@ -445,4 +392,272 @@ export class Subtitle {
)
}
}
//#endregion
//#region 本地OCR识别字幕相关操作
/**
*
* @param {*} value
* @param {*} value.id ID/ID/null
* @param {*} value.type //
* @param {*} value.videoPath
* @param {*} value.subtitlePosition
*/
async GetVideoFrameText(value: Book.GetVideoFrameTextParams): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let videoPath = undefined
let tempImageFolder = undefined
let position = undefined
if (value.type == SubtitleSavePositionType.MAIN_VIDEO) {
let bookRes = await this.bookServiceBasic.GetBookDataById(value.id)
if (bookRes == null) {
throw new Error('没有找到小说对应的的视频地址')
}
let book = bookRes
tempImageFolder = path.join(define.project_path, `${book.id}/data/subtitle/${book.id}/temp`)
if (isEmpty(book.subtitlePosition)) {
throw new Error('请先保存位置信息')
}
position = JSON.parse(book.subtitlePosition)
videoPath = book.oldVideoPath
} else if (value.type == SubtitleSavePositionType.STORYBOARD_VIDEO) {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(value.id);
if (bookTaskDetail == null) {
throw new Error("没有找到小说分镜详细信息")
}
tempImageFolder = path.join(define.project_path, `${bookTaskDetail.bookId}/data/subtitle/${bookTaskDetail.name}_${bookTaskDetail.id}/temp`)
if (isEmpty(value.subtitlePosition)) {
throw new Error('请先保存位置信息')
}
position = JSON.parse(value.subtitlePosition)
videoPath = bookTaskDetail.videoPath
} else {
throw new Error("不支持的操作");
}
await CheckFolderExistsOrCreate(tempImageFolder)
// 判断文件夹是不是存在,存在的话,将里面的所有文件删除
await DeleteFolderAllFile(tempImageFolder)
// 将视频进行抽帧目前是每秒1帧时间小于一秒抽一帧
let getDurationRes = await this.ffmpegOptions.FfmpegGetVideoDuration(videoPath)
if (getDurationRes.code == 0) {
throw new Error(getDurationRes.message)
}
let videoDuration = getDurationRes.data
let frameTime = this.GenerateFrameTimes(videoDuration, 1)
for (let i = 0; i < frameTime.length; i++) {
const item = frameTime[i];
let name = i.toString().padStart(6, '0')
let imagePath = path.join(tempImageFolder, `frame_${name}.png`)
// 开始抽帧
let res = await this.ffmpegOptions.FfmpegGetVideoFramdAndClip(
videoPath,
item,
imagePath,
position
)
if (res.code == 0) {
throw new Error(res.message)
}
}
// 开始识别
let textRes = await this.GetCurrentFrameText({
id: value.id,
type: value.type,
imageFolder: tempImageFolder
})
let allTextData = [] as string[]
// 开始获取所有的数据
let jsonPaths = await GetFilesWithExtensions(tempImageFolder, ['.json'])
for (let i = 0; i < jsonPaths.length; i++) {
const element = jsonPaths[i]
// 开始拼接
let texts = JSON.parse(await fspromises.readFile(element, 'utf-8'))
for (let j = 0; j < texts.length; j++) {
const text = texts[j][1][0]
allTextData.includes(text) ? null : allTextData.push(text)
}
}
// 这边计算相似度,返回过于相似的数据
// let res = await RemoveSimilarTexts(allTextData)
return successMessage(
allTextData.join(''),
'获取视频的的文案信息成功',
'WatermarkAndSubtitle_GetVideoFrameText'
)
} catch (error) {
return errorMessage(
'提取视频的的文案信息失败,错误消息如下:' + error.toString(),
'WatermarkAndSubtitle_GetCurrentFrameText'
)
}
}
/**
* 使OCR识别字幕文案
* @param bookId ID
* @param bookTaskId ID
* @returns
*/
async GetCopywritingByLocalOcr(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[]): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
for (let i = 0; i < bookTaskDetails.length; i++) {
const item = bookTaskDetails[i];
let res = await this.GetVideoFrameText({
id: item.id,
videoPath: item.videoPath,
type: SubtitleSavePositionType.STORYBOARD_VIDEO,
subtitlePosition: book.subtitlePosition
})
if (res.code == 0) {
throw new Error(res.message)
}
// 修改数据,并返回
await this.GetSubtitleLoggerAndResponse(res.data, {
total: bookTaskDetails.length,
current: i + 1
}, book, bookTask, item)
}
return successMessage(null, "识别是所有文案成功", "Subtitle_GetCopywriting")
} catch (error) {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'Subtitle_GetCopywriting')
}
}
//#endregion
//#region Lai_WHISPER识别字幕相关操作
/**
*
* @param book
* @param bookTask
* @param bookTaskDetail
* @returns
*/
async SplitAudio(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetail: Book.SelectBookTaskDetail) {
// 开始分离音频
let videoPath = bookTaskDetail.videoPath
let audioPath = path.join(path.dirname(videoPath), bookTaskDetail.name + '.mp3');
let audioRes = await this.ffmpegOptions.FfmpegExtractAudio(bookTaskDetail.videoPath, audioPath)
if (audioRes.code == 0) {
let errorMessage = `分离音频失败,错误信息如下:${audioRes.message}`
await this.bookServiceBasic.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.AUDIO_FAIL,
errorMessage
)
throw new Error(audioRes.message)
}
this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, {
audioPath: path.relative(define.project_path, audioPath)
})
// 推送成功消息
await this.taskScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
// 修改状态为分离音频成功
this.bookServiceBasic.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO_DONE)
return audioPath;
}
/**
* 使LAI Whisper
* @param audioPath
* @param subtitleSetting
*/
async LaiWhisperApi(audioPath: string, subtitleSetting: SubtitleModel.subtitleSettingModel): Promise<string> {
// 开始调用LAI API识别
let formdata = new FormData()
formdata.append("file", fs.createReadStream(audioPath)); // 如果是Node.js环境可以使用fs.createReadStream方法
formdata.append("model", "whisper-1");
formdata.append("response_format", "srt");
formdata.append("temperature", "0");
formdata.append("language", "zh");
formdata.append("prompt", isEmpty(subtitleSetting.laiWhisper.prompt) ? "eiusmod nulla" : subtitleSetting.laiWhisper.prompt);
let url = subtitleSetting.laiWhisper.url
if (!url.endsWith('/')) {
url = url + '/'
}
const config = {
method: 'post',
url: url + 'v1/audio/transcriptions',
headers: {
'Accept': 'application/json',
'Authorization': subtitleSetting.laiWhisper.apiKey,
'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
'Content-Type': 'multipart/form-data',
...formdata.getHeaders() // 在Node.js环境中需要添加这一行
},
data: formdata
};
let res = await axios(config)
let text = res.data.text;
// 但是这边是繁体,需要转化为简体
let simpleText = await this.gptService.ChineseTraditionalToSimplified(text, subtitleSetting.laiWhisper.apiKey, url);
console.log(res.data)
return simpleText;
}
/**
* 使LAI Whisper识别字幕
* @param bookId ID
* @param bookTaskId ID
* @param subtitleSetting
* @returns
*/
async GetCopywritingByLaiWhisper(book: Book.SelectBook, bookTask: Book.SelectBookTask, bookTaskDetails: Book.SelectBookTaskDetail[], subtitleSetting: SubtitleModel.subtitleSettingModel): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let emptyVideoPaths = [] as string[]
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
// 将所有的分镜视频音频分开
if (isEmpty(element.videoPath)) {
emptyVideoPaths.push(element.name)
}
}
if (emptyVideoPaths.length > 0) {
throw new Error(`以下分镜视频没有找到对应的视频路径:${emptyVideoPaths.join("")} \n 请先计算分镜`)
}
// 拆分音频和视频
for (let i = 0; i < bookTaskDetails.length; i++) {
const bookTaskDetail = bookTaskDetails[i];
// 开始分离音频
let audioPath = await this.SplitAudio(book, bookTask, bookTaskDetail)
let fileExist = await CheckFileOrDirExist(audioPath)
if (!fileExist) {
throw new Error('没有找到对应的音频文件');
}
// 开始调用LAI API识别
let content = await this.LaiWhisperApi(audioPath, subtitleSetting);
// 向前端发送数据
await this.GetSubtitleLoggerAndResponse(content, {
total: bookTaskDetails.length,
current: i + 1
}, book, bookTask, bookTaskDetail)
}
return successMessage(
null,
`所有音频识别成功`,
'Subtitle_GetCopywritingByLaiWhisper'
)
} catch (error) {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'Subtitle_GetCopywritingByLaiWhisper')
}
}
//#endregion
}

View File

@ -0,0 +1,227 @@
import { isEmpty } from "lodash";
import { GetSubtitleType, SubtitleSavePositionType } from "../../../define/enum/waterMarkAndSubtitle"
import { errorMessage, successMessage } from "../../Public/generalTools"
import { SoftWareServiceBasic } from "../ServiceBasic/softwareServiceBasic"
import { ValidateJson } from "../../../define/Tools/validate";
import { GeneralResponse } from "../../../model/generalResponse";
import { SubtitleModel } from "../../../model/subtitle";
import { define } from '../../../define/define'
import path from 'path'
import fs from 'fs'
import { CheckFileOrDirExist } from "../../../define/Tools/file";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { Subtitle } from "./subtitle";
import { LoggerStatus, ResponseMessageType } from "../../../define/enum/softwareEnum";
import { TaskScheduler } from "../taskScheduler";
import { OperationType } from "realm/dist/public-types/internal";
import { OperateBookType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
export class SubtitleService {
softWareServiceBasic: SoftWareServiceBasic
bookServiceBasic: BookServiceBasic
subtitle: Subtitle
taskScheduler: TaskScheduler
constructor() {
this.softWareServiceBasic = new SoftWareServiceBasic();
this.bookServiceBasic = new BookServiceBasic();
this.subtitle = new Subtitle();
}
//#region 设置相关的方法
/**
*
*/
async InitSubtitleSetting(): Promise<SubtitleModel.subtitleSettingModel> {
let defauleSetting = {
selectModel: GetSubtitleType.LAI_WHISPER,
laiWhisper: {
url: 'https://api.laitool.cc/',
apiKey: '你的LAI API KEY',
syncGPTAPIKey: false,
prompt: undefined
}
} as SubtitleModel.subtitleSettingModel
await this.softWareServiceBasic.SaveSoftwarePropertyData("subtitleSetting", JSON.stringify(defauleSetting));
return defauleSetting
}
/**
*
*/
async GetSubtitleSetting(): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let subtitleSetting = undefined as SubtitleModel.subtitleSettingModel
let subtitleSettingString = await this.softWareServiceBasic.GetSoftWarePropertyData('subtitleSetting');
if (isEmpty(subtitleSettingString)) {
// 初始化
subtitleSetting = await this.InitSubtitleSetting();
} else {
if (ValidateJson(subtitleSettingString)) {
subtitleSetting = JSON.parse(subtitleSettingString)
} else {
throw new Error("提起字幕设置解析失败,请重置后重新配置")
}
}
return successMessage(subtitleSetting, '获取提取字幕设置成功', "SubtitleService_GetSubtitleSetting")
} catch (error) {
return errorMessage("获取字幕设置失败,失败信息如下:" + error.message, "SubtitleService_GetSubtitleSetting")
}
}
/**
*
*/
async ResetSubtitleSetting(): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let subtitleSetting = await this.InitSubtitleSetting();
return successMessage(subtitleSetting, "重置字幕设置成功", "SubtitleService_ResetSubtitleSetting")
} catch (error) {
return errorMessage("重置字幕设置失败,失败信息如下:" + error.message, "SubtitleService_ResetSubtitleSetting")
}
}
/**
*
* @param subtitleSetting
*/
async SaveSubtitleSetting(subtitleSetting: SubtitleModel.subtitleSettingModel): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
// 判断模式,通过不同的模式判断是不是又必要检查
if (subtitleSetting.selectModel == GetSubtitleType.LOCAL_OCR) {
let localOcrPath = path.join(define.scripts_path, 'LaiOcr/LaiOcr.exe');
let fileIsExists = await CheckFileOrDirExist(localOcrPath);
if (!fileIsExists) {
throw new Error("当前模式未本地OCR但是没有检查到对应的执行文件请查看教程安装对应的拓展");
}
} else if (subtitleSetting.selectModel == GetSubtitleType.LOCAL_WHISPER) {
// let localWhisper = path.join(define.scripts_path,'')
// 这个好像没有什么可以检查的
} else if (subtitleSetting.selectModel == GetSubtitleType.LAI_WHISPER) {
// 判断是不是laitool的不是的话报错
if (!subtitleSetting.laiWhisper.url.includes('laitool')) {
throw new Error('该模式只能试用LAI API的接口请求');
}
if (isEmpty(subtitleSetting.laiWhisper.apiKey)) {
throw new Error("当前模式为LAI API的接口请求请输入LAI API KEY")
}
if (isEmpty(subtitleSetting.laiWhisper.url)) {
throw new Error("当前模式为LAI API的接口请求请输入LAI API URL")
}
} else {
throw new Error("未知的识别字幕模式")
}
// 检查做完,开始保存数据
await this.softWareServiceBasic.SaveSoftwarePropertyData('subtitleSetting', JSON.stringify(subtitleSetting))
return successMessage(null, "保存提取文案设置成功", "SubtitleService_SaveSubtitleSetting");
} catch (error) {
return errorMessage("保存提取文案设置失败,失败信息如下:" + error.message, "SubtitleService_SaveSubtitleSetting")
}
}
//#endregion
//#region 语音转文案或者是字幕识别
/**
*
*/
async GetCopywriting(bookId: string, bookTaskId: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let subtitleSettingRes = await this.GetSubtitleSetting();
if (subtitleSettingRes.code == 0) {
throw new Error(subtitleSettingRes.message)
}
let subtitleSetting = subtitleSettingRes.data as SubtitleModel.subtitleSettingModel;
let res = undefined as GeneralResponse.ErrorItem | GeneralResponse.SuccessItem
let bookTaskDetails = undefined as Book.SelectBookTaskDetail[]
let tempBookTaskId = bookTaskId
if (operateBookType == OperateBookType.BOOKTASK) {
bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: bookId,
bookTaskId: bookTaskId
})
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let tempBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskId)
tempBookTaskId = tempBookTaskDetail.bookTaskId
bookTaskDetails = [tempBookTaskDetail]
} else {
throw new Error("未知的操作类型")
}
if (bookTaskDetails.length <= 0) {
throw new Error("分镜信息不存在");
}
let { book, bookTask } = await this.bookServiceBasic.GetBookAndTask(bookId, tempBookTaskId)
switch (subtitleSetting.selectModel) {
case GetSubtitleType.LOCAL_OCR:
res = await this.subtitle.GetCopywritingByLocalOcr(book, bookTask, bookTaskDetails)
break;
case GetSubtitleType.LOCAL_WHISPER:
throw new Error("本地Whisper暂时不支持")
break;
case GetSubtitleType.LAI_WHISPER:
res = await this.subtitle.GetCopywritingByLaiWhisper(book, bookTask, bookTaskDetails, subtitleSetting)
break;
default:
throw new Error("未知的识别字幕模式")
}
if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(bookTaskId)
return successMessage(bookTaskDetail.afterGpt, "获取文案成功", "ReverseBook_GetCopywriting")
} else {
return res
}
} catch (error) {
return errorMessage("获取分镜数据失败,失败信息如下:" + error.message, 'ReverseBook_GetCopywriting')
}
}
/**
*
* @param bookTaskId ID
* @returns
*/
async ExportCopywriting(bookTaskId: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let bookTask = await this.bookServiceBasic.GetBookTaskDataId(bookTaskId)
let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId)
let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({
bookId: book.id,
bookTaskId: bookTaskId
})
let emptyList = []
let content = []
// 检查是不是所有的里面都有文案
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i];
if (isEmpty(element.afterGpt)) {
emptyList.push(element.name)
} else {
content.push(element.afterGpt)
}
}
if (emptyList.length > 0) {
throw new Error(`以下分镜没有文案:${emptyList.join("\n")}`);
}
// 写出文案
let contentStr = content.join("。\n");
contentStr = contentStr + '。'
let wordPath = path.join(book.bookFolderPath, "文案.txt")
await fs.promises.writeFile(wordPath, contentStr, 'utf-8')
return successMessage(wordPath, "导出文案成功", "ReverseBook_ExportCopywriting")
} catch (error) {
return errorMessage("导出文案失败,失败信息如下:" + error.message, 'ReverseBook_ExportCopywriting')
}
}
//#endregion
}

View File

@ -10,6 +10,7 @@ import { ResponseMessageType } from "../../../define/enum/softwareEnum";
import { SoftwareService } from '../../../define/db/service/SoftWare/softwareService'
import { isEmpty } from "lodash";
import { ValidateJson } from "../../../define/Tools/validate";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
/**
*
@ -18,8 +19,10 @@ export class TranslateService {
translate: Translate
bookTaskDetail: BookTaskDetailService;
softwareService: SoftwareService
bookServiceBasic: BookServiceBasic
constructor() {
this.bookServiceBasic = new BookServiceBasic();
}
async InitService() {
@ -107,7 +110,7 @@ export class TranslateService {
// 解析
let tryParse = ValidateJson(translateSettingString)
if (!tryParse) {
throw new Error("翻译设置数据解析失败")
throw new Error("翻译设置数据解析失败,请重置后重新配置")
}
translateSetting = JSON.parse(translateSettingString);
}
@ -175,7 +178,7 @@ export class TranslateService {
/**
*
*
* @param bookTaskDetailId ID
* @param reversePromptId ID
* @param to
@ -200,6 +203,12 @@ export class TranslateService {
case TranslateType.REVERSE_PROMPT_TRANSLATE:
await this.TranslateProcessReversePrompt(value.bookTaskDetailId, value.reversePromptId, to, dstString)
break;
case TranslateType.GPT_PROMPT_TRANSLATE:
// 这个直接改就行
await this.bookServiceBasic.UpdateBookTaskDetail(value.bookTaskDetailId, {
gptPrompt: dstString
})
break;
default:
throw new Error("未知的翻译类型");
}
@ -228,11 +237,15 @@ export class TranslateService {
// 写回数据库
await this.TranslateReturnProcess(element, data.to, srcString);
let responseType = ResponseMessageType.REVERSE_PROMPT_TRANSLATE;
if (element.type == TranslateType.GPT_PROMPT_TRANSLATE) {
responseType = ResponseMessageType.GPT_PROMPT_TRANSLATE
}
// 做个返回数据
this.sendTranslateReturn(element.windowId, {
code: 1,
id: element.bookTaskDetailId,
type: ResponseMessageType.PROMPT_TRANSLATE,
type: responseType,
data: {
progress: i + 1,
total: value.length,

15
src/main/Service/d3.ts Normal file
View File

@ -0,0 +1,15 @@
export class D3 {
constructor() { }
//#region D3进行画图的基础方法
//#endregion
//#region 软件相关的方法
//endregion
}

View File

@ -172,7 +172,7 @@ export class FfmpegOptions {
* @param {*} outAudioPath
* @returns
*/
async FfmpegExtractAudio(videoPath, outAudioPath) {
async FfmpegExtractAudio(videoPath: string, outAudioPath: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
// 判断视频地址是不是存在
let videoIsExist = await CheckFileOrDirExist(videoPath)
@ -205,13 +205,12 @@ export class FfmpegOptions {
}
/**
* Ffmpeg提取视频帧
* point判断提取什么位置的帧
* Ffmpeg提取视频指定时间的帧
* @param {*} frameTime
* @param {*} videoPath
* @param {*} outFramePath
*/
async FfmpegGetFrame(frameTime, videoPath, outFramePath) {
async FfmpegGetFrame(frameTime: number, videoPath: string, outFramePath: string): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (videoIsExist == false) {
@ -238,7 +237,7 @@ export class FfmpegOptions {
.run()
})
let res_msg = `视频抽帧完成,输出地址:${res}`
return successMessage(res, '视频抽帧成功', 'BasicReverse_FfmpegGetFrame')
return successMessage(res, res_msg, 'BasicReverse_FfmpegGetFrame')
} catch (error) {
return errorMessage(error.message, 'BasicReverse_FfmpegGetFrame')
}
@ -328,7 +327,7 @@ export class FfmpegOptions {
throw new Error(frameRes.message)
}
let outImagePaths = []
if(await CheckFileOrDirExist(outImagePath) == false){
if (await CheckFileOrDirExist(outImagePath) == false) {
return successMessage(
outImagePaths,
'获取指定位置的帧和裁剪成功',

View File

@ -124,6 +124,12 @@ export class TaskManager {
for (let index = 0; index < tasks.data.length; index++) {
const element = tasks.data[index];
if (element.type == BookBackTaskType.MJ_IMAGE || element.type == BookBackTaskType.MJ_REVERSE) {
// 判断任务数量是不是又修改
let taskNumber = global.mjQueue.getConcurrencyLimit();
if (taskNumber != this.mjOpt.mjSetting.taskCount) {
global.mjQueue.concurrencyLimit = this.mjOpt.mjSetting.taskCount // 重置并发执行的数量
}
if (global.mjQueue.getWaitingQueue() > 10) {
console.log('MJ等待中的任务太多等待中的任务数量', global.mjQueue.getWaitingQueue());
this.spaceTime = 20000;
@ -275,11 +281,6 @@ export class TaskManager {
* @param task
*/
async AddImageMJImage(task: TaskModal.Task) {
// 判断任务数量是不是又修改
let taskNumber = global.mjQueue.getConcurrencyLimit();
if (taskNumber != this.mjOpt.mjSetting.taskCount) {
global.mjQueue.concurrencyLimit = this.mjOpt.mjSetting.taskCount // 重置并发执行的数量
}
// 判断是不是MJ的任务
let batch = DEFINE_STRING.MJ.MJ_IMAGE;
global.mjQueue.enqueue(async () => {

View File

@ -352,7 +352,11 @@ export class Watermark {
bookId: bookTask.bookId
}).data as Book.SelectBookTaskDetail[]
} else if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
bookTask = this.bookTaskService.GetBookTaskDataById(id);
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(id)
if (bookTaskDetail == null) {
throw new Error("指定的小说任务分镜信息不存在,请检查")
}
bookTask = this.bookTaskService.GetBookTaskDataById(bookTaskDetail.bookTaskId);
if (bookTask == null) {
throw new Error('指定的小说任务数据不存在')
}
@ -360,10 +364,7 @@ export class Watermark {
if (book == null) {
throw new Error("小说数据不存在,请检查")
}
let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(id)
if (bookTaskDetail == null) {
throw new Error("指定的小说任务分镜信息不存在,请检查")
}
bookTaskDetails = [bookTaskDetail];
} else {
throw new Error("未知的操作类型")
@ -451,7 +452,11 @@ export class Watermark {
this.taskScheduler.AddLogToDB(book.id, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS)
}
// 全部完毕
return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark")
if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
return successMessage(bookTaskDetails[0].oldImage + '?t=' + new Date().getTime(), "去除水印完成", "ReverseBook_RemoveWatermark")
} else {
return successMessage(null, "全部图片去除水印完成", "ReverseBook_RemoveWatermark")
}
} catch (error) {
return errorMessage("去除水印失败,错误信息如下:" + error.message, "ReverseBook_RemoveWatermark")
}

View File

@ -6,11 +6,20 @@ import axios from 'axios'
import { ServiceBase } from '../../define/db/service/serviceBase'
import { isEmpty } from 'lodash'
import { ValidateJson } from '../../define/Tools/validate'
import { SyncGptKeyType } from '../../define/enum/softwareEnum'
import { GeneralResponse } from '../../model/generalResponse'
import { SoftWareServiceBasic } from '../Service/ServiceBasic/softwareServiceBasic'
import { SubtitleService } from '../Service/Subtitle/subtitleService'
import { SubtitleModel } from '../../model/subtitle'
export class GptSetting extends ServiceBase {
softWareServiceBasic: SoftWareServiceBasic
subtitleService: SubtitleService
constructor() {
super()
axios.defaults.baseURL = define.serverUrl
this.softWareServiceBasic = new SoftWareServiceBasic();
this.subtitleService = new SubtitleService()
}
/**
@ -105,4 +114,19 @@ export class GptSetting extends ServiceBase {
)
}
}
/**
* GPT的Key到其他的设置中
* @param syncTpye
*/
async SyncGptKey(syncTpye: SyncGptKeyType): Promise<GeneralResponse.SuccessItem | GeneralResponse.ErrorItem> {
try {
let globalGptKey = global.config.gpt_key;
console.log(globalGptKey);
return successMessage(globalGptKey, '获取全局配置成功', 'GptSetting_SyncGptKey')
} catch (error) {
return errorMessage("同步GPT的Key到其他的设置中失败失败信息如下" + error.toString(), "GptSetting_SyncGptKey")
}
}
}

17
src/model/Setting/softwareSetting.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
declare namespace SoftwareSettingModel {
type SoftwareSetting = {
id?: string
theme?: SoftwareThemeType
reverse_display_show?: boolean
reverse_show_book_striped?: boolean
reverse_data_table_size?: ComponentSize
globalSetting?: string
ttsSetting?: string
writeSetting?: string
aiSetting?: string
watermarkSetting?: string
translationSetting?: string
subtitleSetting?: string
}
}

16
src/model/book.d.ts vendored
View File

@ -30,6 +30,22 @@ declare namespace Book {
suffixPrompt?: string | null // 后缀
}
type BookBackTaskList = {
id?: string
bookId?: string
bookTaskId?: string
bookTaskDetailId?: string
name?: string // 任务名称,小说名+批次名+分镜名
type?: BookBackTaskType
status?: BookBackTaskStatus
errorMessage?: string
executeType?: TaskExecuteType // 任务执行类型,手动还是自动
createTime?: Date
updateTime?: Date
startTime?: number
endTime?: number
}
type SelectBookTask = {
no?: number,
id?: string,

View File

@ -21,6 +21,11 @@ declare namespace GeneralResponse {
current: number, // 当前进度
}
type SubtitleProgressResponse = {
content: string, // 文案内容
progress: ProgressResponse
}
// 主线程主动返回前端的消息类
type MessageResponse = {
code: number,
@ -28,6 +33,6 @@ declare namespace GeneralResponse {
type: ResponseMessageType,
dialogType?: DialogType = DialogType.MESSAGE,
message?: string,
data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse
data?: MJ.MJResponseToFront | Buffer | string | TranslateModel.TranslateResponseMessageModel | ProgressResponse | SubtitleProgressResponse
}
}

13
src/model/subtitle.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { GetSubtitleType } from "../define/enum/waterMarkAndSubtitle"
declare namespace SubtitleModel {
type subtitleSettingModel = {
selectModel: GetSubtitleType,
laiWhisper: {
url: string,
apiKey: string,
syncGPTAPIKey: boolean,
prompt: string
}
}
}

View File

@ -51,7 +51,7 @@ const book = {
//#endregion
//#region 一键反推的单个任务
//#region 分镜
// 开始计算分镜数据
ComputeStoryboard: async (bookId) =>
@ -60,17 +60,57 @@ const book = {
// 开始执行分镜,切分视频
Framing: async (bookId) => await ipcRenderer.invoke(DEFINE_STRING.BOOK.FRAMING, bookId),
// 替换指定的分镜的视频的当前帧
ReplaceVideoCurrentFrame: async (bookTaskDetailId, currentTime) =>
await ipcRenderer.invoke(
DEFINE_STRING.BOOK.REPLACE_VIDEO_CURRENT_FRAME,
bookTaskDetailId,
currentTime
),
//#endregion
//#region 文案相关信息
// 获取文案信息
GetCopywriting: async (bookId, bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_COPYWRITING, bookId, bookTaskId),
GetCopywriting: async (bookId, bookTaskId, operateBookType) =>
await ipcRenderer.invoke(
DEFINE_STRING.BOOK.GET_COPYWRITING,
bookId,
bookTaskId,
operateBookType
),
// 将文案信息导出,方便修改
ExportCopywriting: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.EXPORT_COPYWRITING, bookTaskId),
//#endregion
//#region 水印
// 去除所有水印
RemoveWatermark: async (id,operateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, id,operateBookType),
RemoveWatermark: async (id, operateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.REMOVE_WATERMARK, id, operateBookType),
// 添加单句推理的
AddReversePrompt: async (bookTaskDetailIds, type) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT, bookTaskDetailIds, type),
//#endregion
//#region 提示词
MergePrompt: async (id, type, operateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.MERGE_PROMPT, id, type, operateBookType),
//#endregion
//#region 一键反推的单个任务
// 添加单句反推的
AddReversePrompt: async (bookTaskDetailIds, operateBookType, type) =>
await ipcRenderer.invoke(
DEFINE_STRING.BOOK.ADD_REVERSE_PROMPT,
bookTaskDetailIds,
operateBookType,
type
),
// 将反推的数据指定的位置的提示词写入到GPT提示词中
ReversePromptToGptPrompt: async (bookId, bookTaskId, index) =>
@ -115,6 +155,18 @@ const book = {
OneToFourBookTask: async (bookTaskId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.ONE_TO_FOUR_BOOK_TASK, bookTaskId),
//#region 小说相关
// 重置小说数据
ResetBookData: async (bookId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.RESET_BOOK_DATA, bookId),
// 删除小说数据
DeleteBookData: async (bookId) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.DELETE_BOOK_DATA, bookId),
//#endregion
//#region 小说批次任务相关
// 重置小说批次数据
@ -142,7 +194,7 @@ const book = {
HDImage: async (id, scale, operateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.BOOK.HD_IMAGE, id, scale, operateBookType),
// 小说视频相关的设置添加到小说任务批次
// 小说视频相关的设置添加到小说任务批次
UseBookVideoDataToBookTask: async (id, operateBookType) =>
await ipcRenderer.invoke(
DEFINE_STRING.BOOK.USE_BOOK_VIDEO_DATA_TO_BOOK_TASK,

View File

@ -7,7 +7,6 @@ const gpt = {
return await ipcRenderer.invoke(DEFINE_STRING.GPT.INIT_SERVER_GPT_OPTIONS)
},
//#region GPT 设置相关
// 获取软件设置里面的GPT设置
GetAISetting: async () => {
@ -17,6 +16,11 @@ const gpt = {
// 保存软件设置里面的GPT设置
SaveAISetting: async (data) => {
return await ipcRenderer.invoke(DEFINE_STRING.GPT.SAVE_AI_SETTING, data)
},
// 同步GPT Key 到指定的设置
SyncGptKey: async (syncType) => {
return await ipcRenderer.invoke(DEFINE_STRING.GPT.SYNC_GPT_KEY, syncType)
}
//#endregion

View File

@ -69,10 +69,6 @@ const mj = {
AutoMatchUser: async (value, callback) =>
callback(await ipcRenderer.invoke(DEFINE_STRING.MJ.AUTO_MATCH_USER, value)),
// 合并提示词
MergePrompt: async (id, mergeType) =>
await ipcRenderer.invoke(DEFINE_STRING.MJ.MJ_MERGE_PROMPT, id, mergeType),
// 单个出图
AddMJGenerateImageTask: async (id, operateBookType) =>
await ipcRenderer.invoke(DEFINE_STRING.MJ.ADD_MJ_GENADD_MJ_GENERATE_IMAGE_TASK, id, operateBookType)

View File

@ -9,9 +9,5 @@ const sd = {
// 文生图,单张
txt2img: async (value, callback) =>
callback(await ipcRenderer.invoke(DEFINE_STRING.SD.TXT2IMG, value)),
// 合并提示词
MergePrompt: async (id, mergeType) =>
await ipcRenderer.invoke(DEFINE_STRING.SD.SD_MERGE_PROMPT, id, mergeType)
}
export { sd }

View File

@ -11,6 +11,18 @@ const write = {
SaveWriteConfig: async (data) =>
await ipcRenderer.invoke(DEFINE_STRING.WRITE.SAVE_WRITE_CONFIG, data),
// 获取当前的识别字幕设置的数据
GetSubtitleSetting: async () =>
await ipcRenderer.invoke(DEFINE_STRING.WRITE.GET_SUBTITLE_SETTING),
// 重置识别字幕设置的数据
ResetSubtitleSetting: async () =>
await ipcRenderer.invoke(DEFINE_STRING.WRITE.RESET_SUBTITLE_SETTING),
// 保存识别字幕设置的数据
SaveSubtitleSetting: async (subtitleSetting) =>
await ipcRenderer.invoke(DEFINE_STRING.WRITE.SAVE_SUBTITLE_SETTING, subtitleSetting),
//#endregion
//#region AI相关的任务

View File

@ -1,19 +1,19 @@
<template>
<n-config-provider
:hljs="hljs"
:theme="softwareStore.softWare.theme == 'dark' ? darkTheme : null"
>
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<n-spin style="z-index: 1000;" :show="softwareStore.spin.spinning">
<n-spin style="z-index: 1000 !important;" :show="softwareStore.spin.spinning">
<template #description> {{ softwareStore.spin.tip }} </template>
<n-config-provider
:hljs="hljs"
:theme="softwareStore.softWare.theme == 'dark' ? darkTheme : null"
>
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<RouterView></RouterView>
<template #description> {{ softwareStore.spin.tip }} </template>
</n-spin>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
</n-config-provider>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
</n-config-provider>
</n-spin>
</template>
<script>

View File

@ -7,21 +7,13 @@
@click="EditBook"
>编辑</n-button
>
<n-button
:size="softwareStore.softWare.reverse_data_table_size"
strong
secondary
style="margin-left: 5px"
type="info"
>任务</n-button
>
<n-button
:size="softwareStore.softWare.reverse_data_table_size"
strong
secondary
style="margin-left: 5px"
type="warning"
@click="EditBook"
@click="ResetBookData"
>重置</n-button
>
<n-button
@ -30,58 +22,108 @@
strong
secondary
type="error"
@click="DeleteBookData"
>删除
</n-button>
</template>
<script>
import { ref, onMounted, defineComponent, h, onUnmounted, toRaw, watch } from 'vue'
import { useMessage, NButton, useDialog } from 'naive-ui'
<script setup>
import { ref, h, toRaw } from 'vue'
import { useMessage, useDialog, NButton } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
import { useSoftwareStore } from '../../../../../stores/software'
import { BookType } from '../../../../../define/enum/bookEnum'
import AddBook from '../Components/AddBook.vue'
export default defineComponent({
components: {
NButton
},
props: ['book'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
let book = ref(props.book)
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
onMounted(async () => {})
/**
* 编辑小说数据
*/
async function EditBook(e) {
e.stopPropagation()
//
reverseManageStore.SetSelectBook(toRaw(book.value))
//
let dialogWidth = 600
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
title: '编辑小说',
content: () => h(AddBook, { type: 'edit' }),
style: `width : ${dialogWidth}px;`,
maskClosable: false
})
}
return {
book,
reverseManageStore,
softwareStore,
EditBook
}
}
let props = defineProps({
book: undefined
})
let message = useMessage()
let dialog = useDialog()
let book = ref(props.book)
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
/**
* 编辑小说数据
*/
async function EditBook(e) {
e.stopPropagation()
//
reverseManageStore.SetSelectBook(toRaw(book.value))
//
let dialogWidth = 600
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
title: '编辑小说',
content: () => h(AddBook, { type: 'edit' }),
style: `width : ${dialogWidth}px;`,
maskClosable: false
})
}
async function ResetBookData(e) {
e.stopPropagation()
message.info('重置小说数据 ' + book.value.id)
let da = dialog.warning({
title: '重置小说数据警告',
content:
'是否重置小说数据,重置后数据将恢复至新建状态,所有的批次数据和文件数据都会被删除,请确认是否继续!',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在重置小说数据。。。'
let res = await window.book.ResetBookData(book.value.id)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
return
}
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在重新加载小说批次任务数据。。。'
// //
let bookTaskRes = await reverseManageStore.GetBookTaskDataFromDB({
bookId: book.value.id
})
softwareStore.spin.spinning = false
if (bookTaskRes.code == 0) {
message.error(bookTaskRes.message)
return
}
message.success('重置小说数据成功')
}
})
}
async function DeleteBookData(e) {
e.stopPropagation()
message.info('删除小说数据 ' + book.value.id)
let da = dialog.warning({
title: '删除小说数据警告',
content:
'是否删除小说数据,删除后数据将被永久删除,所有的批次数据和文件数据都会被删除,请确认是否继续!',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
debugger
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在删除小说数据。。。'
let res = await window.book.DeleteBookData(book.value.id)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
return
}
debugger
//
reverseManageStore.bookData = reverseManageStore.bookData.filter(
(item) => item.id != book.value.id
)
message.success('删除小说数据成功')
}
})
}
</script>

View File

@ -34,14 +34,11 @@
</template>
<script>
import { ref, onMounted, defineComponent, h, onUnmounted, toRaw, watch } from 'vue'
import { ref, onMounted, defineComponent, h } from 'vue'
import { useMessage, NButton, useDialog } from 'naive-ui'
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
import { useSoftwareStore } from '../../../../../stores/software'
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
import AddBook from '../Components/AddBook.vue'
import { DEFINE_STRING } from '../../../../../define/define_string'
import { ResponseMessageType } from '../../../../../define/enum/softwareEnum'
import ManageBookTaskGenerateInformation from './ManageBookTaskGenerateInformation.vue'
export default defineComponent({
@ -51,9 +48,11 @@ export default defineComponent({
props: ['bookTask'],
setup(props) {
debugger
let message = useMessage()
let dialog = useDialog()
let bookTask = ref(props.bookTask)
console.log('bookTask', bookTask.value)
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
@ -172,6 +171,7 @@ export default defineComponent({
*/
async function ClipDraft(e) {
e.stopPropagation()
debugger
dialog.info({
closeOnEsc: false,
title: '生成草稿前检查',
@ -186,7 +186,7 @@ export default defineComponent({
*/
async function GenerateVideo(e) {
e.stopPropagation()
alert('合成视频')
message.error('该功能暂不可用')
}
return {

View File

@ -32,6 +32,7 @@ export default defineComponent({
props: ['initData', 'index'],
setup(props) {
let data = ref(props.initData)
console.log('subValue', data.value)
onMounted(async () => {})
let show = computed(() => {
debugger

View File

@ -1,5 +1,5 @@
<template>
<div style="position: absolute; top: 0">
<div>
<n-dropdown trigger="hover" :options="reverseOptions" @select="ReverseSelect">
<n-button size="tiny" :color="softwareStore.SoftColor.BROWN_YELLOW" @click="AddReversePrompt">
单句反推
@ -15,7 +15,6 @@
英文 2 中文
</n-button>
</n-dropdown>
<n-button
style="margin-left: 5px"
size="tiny"
@ -35,6 +34,7 @@ import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { TranslateType } from '../../../../../define/enum/translate'
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
import ReSelectReversePrompt from '../MJReverse/ReSelectReversePrompt.vue'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
import { isEmpty } from 'lodash'
export default defineComponent({
@ -53,7 +53,11 @@ export default defineComponent({
* 单句反推提示词
*/
async function AddReversePrompt() {
let res = await window.book.AddReversePrompt([data.value.id], null)
let res = await window.book.AddReversePrompt(
data.value.id,
OperateBookType.BOOKTASKDETAIL,
null
)
if (res.code == 0) {
message.error(res.message)
} else {
@ -63,7 +67,8 @@ export default defineComponent({
async function AddNextReversePrompt() {
let res = await window.book.AddReversePrompt(
[data.value.id],
data.value.id,
OperateBookType.UNDERBOOKTASK,
reverseManageStore.selectBook.type
)
if (res.code == 0) {
@ -84,6 +89,7 @@ export default defineComponent({
if (res.code == 0) {
message.error(res.message)
} else {
reverseManageStore.selectBookTaskDetail[props.index].gptPrompt = undefined
reverseManageStore.selectBookTaskDetail[props.index].reversePrompt = undefined
message.success('删除反推数据成功')
}
@ -110,16 +116,16 @@ export default defineComponent({
//
function TranslateOneParams(from, to) {
let translateData = []
// if (!isEmpty(data.value.prompt)) {
// translateData.push({
// text: item.prompt,
// from: from,
// to: to,
// type: TranslateType.PROMPT_TRANSLATE,
// isSplit: false, // true
// bookTaskDetailId: data.value.id
// })
// } else {
if (!isEmpty(data.value.gptPrompt)) {
translateData.push({
text: data.value.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: data.value.id
})
} else {
let reversePrompt = data.value.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
@ -143,7 +149,7 @@ export default defineComponent({
translateData.push(temp_obj)
})
}
// }
}
return translateData
}
@ -152,27 +158,39 @@ export default defineComponent({
let translateData = []
for (let i = index.value; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
let reversePrompt = element.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
let temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
reversePromptId: item.id
}
translateData.push(temp_obj)
// GPTGPT
if (!isEmpty(data.value.gptPrompt)) {
translateData.push({
text: element.gptPrompt,
from: from,
to: to,
type: TranslateType.GPT_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id
})
} else {
let reversePrompt = element.reversePrompt
if (reversePrompt) {
reversePrompt.forEach((item) => {
if (from == 'zh' && to == 'en') {
//
if (!ContainsChineseOrPunctuation(item.promptCN ? item.promptCN : item.prompt)) {
return
}
}
let temp_obj = {
text: item.prompt,
from: from,
to: to,
type: TranslateType.REVERSE_PROMPT_TRANSLATE,
isSplit: false, // true
bookTaskDetailId: element.id,
reversePromptId: item.id
}
translateData.push(temp_obj)
})
}
}
}
return translateData

View File

@ -30,7 +30,7 @@ import { useSoftwareStore } from '../../../../../stores/software'
import DynamicTagsSelect from '../../Components/DynamicTagsSelect.vue'
import { usePromptStore } from '../../../../../stores/prompt'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { BookImageCategory, BookType, MergeType } from '../../../../../define/enum/bookEnum'
import { BookImageCategory, OperateBookType } from '../../../../../define/enum/bookEnum'
export default defineComponent({
components: { NDropdown, NPopover, NIcon, NButton, Construct },
@ -110,20 +110,15 @@ export default defineComponent({
}
}
//
let res = undefined
//
softwareStore.spin.spinning = true
softwareStore.spin.tip = '合并命令中。。。'
if (type == 'mj_merge') {
res = await window.mj.MergePrompt(reverseManageStore.selectBookTask.id, MergeType.BOOKTASK)
} else if (type == 'sd_merge') {
res = await window.sd.MergePrompt(reverseManageStore.selectBookTask.id, MergeType.BOOKTASK)
} else {
message.error('未知的合并模式')
softwareStore.spin.spinning = false
return
}
let res = await window.book.MergePrompt(
reverseManageStore.selectBookTask.id,
type,
OperateBookType.BOOKTASK
)
//
if (res.code == 0) {

View File

@ -1,5 +1,5 @@
<template>
<div style="position: absolute; top: 0">
<div>
<n-dropdown trigger="hover" :options="mergeOptions" @select="MergePrompt">
<n-button
size="tiny"
@ -37,7 +37,6 @@
</n-button>
</div>
<n-input
style="margin-top: 5px"
type="textarea"
size="tiny"
@input="InputDebounced"
@ -51,7 +50,7 @@ import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'v
import { useMessage, useDialog, NInput, NButton, NDropdown } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { MergeType, OperateBookType } from '../../../../../define/enum/bookEnum'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
import { BookImageCategory } from '../../../../../define/enum/bookEnum'
import { debounce } from 'lodash'
import InputDialogContent from '../../Original/Components/InputDialogContent.vue'
@ -81,25 +80,14 @@ export default defineComponent({
}
}
//
let res = undefined
//
softwareStore.spin.spinning = true
softwareStore.spin.tip = '合并命令中。。。'
if (type == 'mj_merge') {
res = await window.mj.MergePrompt(data.value.id, MergeType.BOOKTASKDETAIL)
} else if (type == 'sd_merge') {
res = await window.sd.MergePrompt(data.value.id, MergeType.BOOKTASKDETAIL)
} else {
message.error('未知的合并模式')
softwareStore.spin.spinning = false
return
}
let res = await window.book.MergePrompt(data.value.id, type, OperateBookType.BOOKTASKDETAIL)
softwareStore.spin.spinning = false
//
if (res.code == 0) {
message.error(res.message)
softwareStore.spin.spinning = false
return
}
for (let i = 0; i < res.data.length; i++) {
@ -110,7 +98,6 @@ export default defineComponent({
reverseManageStore.selectBookTaskDetail[selectBookDetailIndex].prompt = element.prompt
}
message.success(res.message)
softwareStore.spin.spinning = false
}
//
@ -169,7 +156,7 @@ export default defineComponent({
//
let dialogWidth = 400
let dialogHeight = 150
dialog.create({
let da = dialog.create({
title: '添加图片链接/本地地址',
showIcon: false,
closeOnEsc: false,
@ -182,9 +169,12 @@ export default defineComponent({
style: `width : ${dialogWidth}px; min-height : ${dialogHeight}px`,
maskClosable: false,
onClose: async () => {
da?.destroy()
let row_image_url = image_url_ref.value.data
alert(row_image_url)
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在下载图片/分割图片中。。。'
let res = await window.book.DownloadImageUrlAndSplit(data.value.id, row_image_url)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
return
@ -192,24 +182,6 @@ export default defineComponent({
data.value.outImagePath = res.data.outImagePath
data.value.subImagePath = res.data.subImagePath
message.success(res.message)
//
// await window.mj.DownloadImageUrlAndSplit(
// JSON.stringify([toRaw(row.value), row_image_url]),
// (value) => {
// if (value.code == 0) {
// message.error(value.message)
// return
// }
// //
// row.value.outImagePath = value.data.outImagePath
// row.value.subImagePath = value.data.subImagePath
// row.value.mj_message = value.data.mj_message ? value.data.mj_message : {}
// row.value.mj_message.image_click = value.data.image_click
// row.value.mj_message.image_path = value.data.image_path
// row.value.mj_message.progress = 100
// }
// )
}
})
}

View File

@ -0,0 +1,70 @@
<template>
<div class="artplayer-app" style="height: 100%">
<Artplayer ref="art" id="art-player" @get-instance="getInstance" :option="option" :style="palyarStyle" />
<div style="margin-top: 20px; display: flex; justify-content: center">
<n-button :loading="loading" type="info" @click="ReplaceFrame"> 替换为当前帧 </n-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useMessage, useDialog, NButton } from 'naive-ui'
import Artplayer from '../../Components/Artplayer.vue'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../../stores/software'
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let props = defineProps({
videoPath: undefined,
type: undefined,
id: undefined
})
let videoPath = ref(props.videoPath)
let id = ref(props.id)
let art = ref(null)
let loading = ref(false)
function getInstance(art) {}
async function ReplaceFrame() {
let artInstance = art.value.instance
message.success(artInstance.currentTime)
let currentTime = artInstance.currentTime
loading.value = true
let res = await window.book.ReplaceVideoCurrentFrame(id.value, currentTime * 1000)
loading.value = false
if (res.code == 0) {
message.error(res.message)
} else {
//
let index = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == id.value)
if (index < 0) {
return
}
reverseManageStore.selectBookTaskDetail[index].oldImage = res.data
message.success('替换视频帧成功')
}
}
function test() {}
let palyarStyle = {
width: '600px',
height: '600px',
margin: '0 auto'
}
let option = {
container: '.artplayer-app',
id: id.value,
url: videoPath.value,
playbackRate: true,
aspectRatio: true,
mutex: true,
setting: true,
hotkey: true,
autoSize: true,
fullscreen: true
}
</script>

View File

@ -134,10 +134,12 @@ export default defineComponent({
code.value = code.value + '\n' + value.data
return
}
//
softwareStore.spin.tip = `正在提取文案,当前进度 ${value.data.progress.current} / ${value.data.progress.total} 。。。`
let index = reverseManageStore.selectBookTaskDetail.findIndex((item) => item.id == value.id)
if (index >= 0) {
reverseManageStore.selectBookTaskDetail[index].word = value.data
reverseManageStore.selectBookTaskDetail[index].afterGpt = value.data
reverseManageStore.selectBookTaskDetail[index].word = value.data.content
reverseManageStore.selectBookTaskDetail[index].afterGpt = value.data.content
}
})
@ -251,18 +253,39 @@ export default defineComponent({
return
}
let copywriting_res = await window.book.GetCopywriting(reverseManageStore.selectBook.id)
if (copywriting_res.code == 0) {
message.error(copywriting_res.message)
let subtitleSettingRes = await window.write.GetSubtitleSetting()
if (subtitleSettingRes.code == 0) {
message.error(subtitleSettingRes.message)
return
}
message.success('获取文案成功')
let da = dialog.warning({
title: '开始提取文案提示',
content: `即将进行文案提取,当前的文案提取模式为 ${subtitleSettingRes.data.selectModel} ,是否继续?`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = `正在使用 ${subtitleSettingRes.data.selectModel} 提取文案,正在准备中。。。`
let copywriting_res = await window.book.GetCopywriting(
reverseManageStore.selectBook.id,
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
if (copywriting_res.code == 0) {
message.error(copywriting_res.message)
return
}
// dialog.success({})
message.success('获取全部文案成功')
}
})
}
//
async function RemoveWatermark() {
// softwareStore.spin.spinning = true
// softwareStore.spin.tip = ''
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
@ -270,12 +293,23 @@ export default defineComponent({
})
return
}
let res_frame = await window.book.RemoveWatermark(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
let da = dialog.warning({
title: '去除水印提示',
content: '即将去除全部水印,是否继续?',
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
da?.destroy()
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在去除水印中。。。'
let res_frame = await window.book.RemoveWatermark(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
}
})
}
//
@ -297,6 +331,28 @@ export default defineComponent({
})
}
//
async function ExportCopywriting() {
debugger
softwareStore.spin.spinning = true
softwareStore.spin.tip = '正在导出文案中...'
let res = await window.book.ExportCopywriting(reverseManageStore.selectBookTask.id)
if (res.code == 0) {
message.error(res.message)
} else {
dialog.success({
title: '导出文案成功',
content: '导出文案成功,是否打开导出的字幕文件',
positiveText: '打开',
negativeText: '取消',
onPositiveClick: () => {
window.system.OpenFile(res.data)
}
})
}
softwareStore.spin.spinning = false
}
/**
* 获取水印位置
*/
@ -329,6 +385,9 @@ export default defineComponent({
case 'recognizing_setting': //
await GetCopywritingSetting()
break
case 'export_recognizing': //
await ExportCopywriting()
break
case 'watermark_position': //
await GetWatermarkPosition()
break
@ -376,7 +435,6 @@ export default defineComponent({
* @param type 反推类型
*/
async function ImageReversePrompt(type = undefined) {
debugger
if (isEmpty(reverseManageStore.selectBookTask.id)) {
window.api.showGlobalMessageDialog({
code: 0,
@ -384,32 +442,36 @@ export default defineComponent({
})
return
}
dialog.warning({
title: '反推提示',
content: `即将进行反推操作,反推方式为 ${
type ? type : reverseManageStore.selectBook.type
} 是否继续`,
positiveText: '继续',
negativeText: '取消',
onPositiveClick: async () => {
if (!type) {
let bookType = reverseManageStore.selectBook.type
type = bookType
}
if (!type) {
let bookType = reverseManageStore.selectBook.type
type = bookType
}
if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) {
message.error(`该类型 ${bookType} 的小说不支持反推`)
return
}
let reverseIds = []
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (!element.reversePrompt || element.reversePrompt.length <= 0) {
reverseIds.push(element.id)
if (type != BookType.MJ_REVERSE && type != BookType.SD_REVERSE) {
message.error(`该类型 ${bookType} 的小说不支持反推`)
return
}
//
let res = await window.book.AddReversePrompt(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK,
type
)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加所有反推任务成功')
}
}
}
//
let res = await window.book.AddReversePrompt(reverseIds, type)
if (res.code == 0) {
message.error(res.message)
} else {
message.success('添加所有反推任务成功')
}
})
}
/**
@ -417,13 +479,16 @@ export default defineComponent({
*/
async function RemoveReverseData(id) {
let deleteIds = []
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (element.reversePrompt && element.reversePrompt.length > 0) {
deleteIds.push(element.id)
if (id == undefined) {
//
for (let i = 0; i < reverseManageStore.selectBookTaskDetail.length; i++) {
const element = reverseManageStore.selectBookTaskDetail[i]
if (element.reversePrompt && element.reversePrompt.length > 0) {
deleteIds.push(element.id)
}
}
} else {
deleteIds.push(id)
}
let res = await window.book.RemoveReverseData(deleteIds)
@ -643,6 +708,10 @@ export default defineComponent({
label: '提取文案位置',
key: 'recognizing_setting'
},
{
label: '导出文案',
key: 'export_recognizing'
},
{
label: '停止提取',
key: 'stop_recognizing'
@ -665,7 +734,8 @@ export default defineComponent({
},
{
label: 'SD反推',
key: 'sd_reverse'
key: 'sd_reverse',
disabled: reverseManageStore.selectBook.type != BookType.SD_REVERSE
},
{
label: '清除反推数据',

View File

@ -1,28 +1,107 @@
<template>
<div style="position: absolute; top: 0">
<!-- <n-button size="tiny">123</n-button> -->
<div style="margin-bottom: 2px">
<n-button :color="softwareStore.SoftColor.BROWN_YELLOW" size="tiny" @click="OpenGetVideoFrame"
>抽帧</n-button
>
<n-button
style="margin-left: 2px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
size="tiny"
@click="GetVideoSubtitle"
>文案</n-button
>
<n-button
style="margin-left: 2px"
:color="softwareStore.SoftColor.BROWN_YELLOW"
size="tiny"
@click="RemoveImageWatermark"
>水印</n-button
>
</div>
<n-image v-if="data.oldImage" width="110" :src="data.oldImage"> </n-image>
<video v-else-if="data.data.videoPath" width="110" controls>
<n-image v-if="data.oldImage" width="112" :src="data.oldImage"> </n-image>
<video v-else-if="data.videoPath" width="110" controls>
<source :src="data.videoPath" type="video/mp4" />
</video>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage, NImage, NButton } from 'naive-ui'
<script setup>
import { ref, onMounted, h } from 'vue'
import { useMessage, useDialog, NImage, NButton } from 'naive-ui'
import { useSoftwareStore } from '../../../../../stores/software'
import { useReverseManageStore } from '../../../../../stores/reverseManage'
import { OperateBookType } from '../../../../../define/enum/bookEnum'
import GetVideoFrame from './GetVideoFrame.vue'
let message = useMessage()
let dialog = useDialog()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
export default defineComponent({
components: { NImage, NButton },
props: ['initData', 'index'],
setup(props) {
let message = useMessage()
let data = ref(props.initData)
onMounted(async () => {})
return {
data
}
}
let props = defineProps({
initData: undefined,
index: undefined
})
let data = ref(props.initData)
let index = ref(props.index)
onMounted(async () => {})
//
async function OpenGetVideoFrame() {
//
if (!data.value.videoPath) {
message.error('当前分镜没有视频')
return
}
dialog.create({
title: '获取视频的帧',
style: 'width : 800px;',
content: () =>
h(GetVideoFrame, {
videoPath: data.value.videoPath,
id: data.value.id,
type: OperateBookType.BOOKTASKDETAIL
})
})
}
//
async function GetVideoSubtitle() {
// alert(data.value.id)
softwareStore.spin.spinning = true
softwareStore.spin.tip = `获取分镜 ${data.value.name} 文案中。。。`
let res = await window.book.GetCopywriting(
reverseManageStore.selectBook.id,
data.value.id,
OperateBookType.BOOKTASKDETAIL
)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
} else {
//
reverseManageStore.selectBookTaskDetail[index.value].afterGpt = res.data
reverseManageStore.selectBookTaskDetail[index.value].word = res.data
message.success('获取视频文案成功')
}
}
//
async function RemoveImageWatermark() {
softwareStore.spin.spinning = true
softwareStore.spin.tip = `去除分镜 ${data.value.name} 水印中。。。`
let res = await window.book.RemoveWatermark(data.value.id, OperateBookType.BOOKTASKDETAIL)
softwareStore.spin.spinning = false
if (res.code == 0) {
message.error(res.message)
} else {
//
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == data.value.id
)
if (index < 0) {
return
}
reverseManageStore.selectBookTaskDetail[index].oldImage = res.data
message.success(`${data.value.name} 去除水印成功`)
}
}
</script>

View File

@ -48,7 +48,7 @@
</n-form-item>
<n-form-item path="backgroundMusic">
<n-button :disabled="type == 'book'" type="info" @click="UseBookVideoDataToBookTask">{{
type == 'bookTsk' ? '应用主小说相关数据' : '当前就是用的主小说的数据'
type == bookTask ? '应用主小说相关数据' : '当前就是用的主小说的数据'
}}</n-button>
<n-button type="info" style="margin-left: 10px" @click="SaveVideoData">保存数据</n-button>
</n-form-item>
@ -73,6 +73,7 @@ export default defineComponent({
props: ['bookTask', 'type'],
setup(props) {
let bookTask = ref(props.bookTask)
debugger
let type = ref(props.type)
let backgroundMusicOptions = ref([])
let message = useMessage()

View File

@ -1,5 +1,5 @@
<template>
<div style="margin-bottom: 7px">
<div>
<DatatableGptPromptButton :initData="data" :index="index" />
</div>
<div v-if="data.gptPrompt">
@ -15,11 +15,22 @@
>
</n-input>
</div>
<div v-else class="show-gpt-prompt">
<div v-else-if="data.reversePrompt && data.reversePrompt.length > 0" class="show-gpt-prompt">
<div v-for="(item, index) in showReversePrompt" :key="index" style="margin-bottom: 5px">
<n-input v-model:value="item.prompt" type="text" size="tiny"> </n-input>
</div>
</div>
<n-input
v-else
type="textarea"
size="tiny"
v-model:value="data.gptPrompt"
:autosize="{
minRows: 6,
maxRows: 6
}"
@input="InputDebounced"
></n-input>
</template>
<script>
@ -42,7 +53,7 @@ export default defineComponent({
let showReversePrompt = computed(() => {
debugger
let res = []
for (let i = 0; i < data.value.reversePrompt.length; i++) {
for (let i = 0; i < data.value.reversePrompt?.length; i++) {
let item = data.value.reversePrompt[i]
let res_obj = {
...item

View File

@ -106,6 +106,7 @@ export default defineComponent({
return h(DatatableHeaderImage)
},
width: 300,
className: 'space-row',
render(row, index) {
return h(DataTableShowGenerateImage, {
image_generate_category: 'mj',
@ -126,7 +127,6 @@ export default defineComponent({
let div = document.getElementsByClassName('space-row')
for (let i = 0; i < div.length; i++) {
div[i].style.padding = '0px 5px'
div[i].style.position = 'relative'
}
}, 100)

View File

@ -6,7 +6,7 @@
<n-data-table
:columns="columns"
:size="softwareStore.softWare.reverse_data_table_size"
:data="data"
:data="reverseManageStore.bookData"
:pagination="pagination"
:bordered="false"
:remote="true"
@ -39,7 +39,6 @@ export default defineComponent({
let message = useMessage()
let softwareStore = useSoftwareStore()
let reverseManageStore = useReverseManageStore()
let data = computed(() => reverseManageStore.GetBookData())
let maxHeight = ref(0)
//
const paginationReactive = (() => {
@ -161,7 +160,7 @@ export default defineComponent({
{
title: '操作',
key: 'action',
width: 310,
width: 240,
fixed: 'right',
render: (row) => {
return h(BookListAction, { book: row })
@ -185,7 +184,6 @@ export default defineComponent({
return {
softwareStore,
reverseManageStore,
data,
pagination,
maxHeight,
columns: createColumns(),

View File

@ -66,11 +66,14 @@ export default defineComponent({
//
switch (value.type) {
case ResponseMessageType.MJ_REVERSE:
case ResponseMessageType.MJ_REVERSE: // MJ
MJReverseResponse(value)
break
case ResponseMessageType.PROMPT_TRANSLATE:
PromptTranslateResponse(value)
case ResponseMessageType.REVERSE_PROMPT_TRANSLATE: //
ReversePromptTranslateResponse(value)
break
case ResponseMessageType.GPT_PROMPT_TRANSLATE: // GPT
GptPromptTranslateResponse(value)
break
case ResponseMessageType.MJ_IMAGE:
MJImageResponse(value)
@ -111,7 +114,7 @@ export default defineComponent({
* prompt: string //
* promptCN: string //
*/
function PromptTranslateResponse(value) {
function ReversePromptTranslateResponse(value) {
if (!value.data) {
return
}
@ -143,15 +146,32 @@ export default defineComponent({
}
}
function GptPromptTranslateResponse(value) {
console.log('GptPromptTranslateResponse', value)
if (!value.data) {
return
}
let index = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.bookTaskDetailId
)
if (index < 0) {
return
}
reverseManageStore.selectBookTaskDetail[index].gptPrompt = value.data.prompt
}
/**
* MJ反推返回数据
*/
function MJReverseResponse(value) {
console.log('MJReverseResponse', value)
if (value.data && value.data.progress == 100) {
debugger
let dataIndex = reverseManageStore.selectBookTaskDetail.findIndex(
(item) => item.id == value.data.id
)
if (dataIndex >= 0) {
reverseManageStore.selectBookTaskDetail[dataIndex].gptPrompt = undefined
reverseManageStore.selectBookTaskDetail[dataIndex].reversePrompt = JSON.parse(
value.data.prompt
)

View File

@ -32,7 +32,7 @@
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch, h } from 'vue'
import { useMessage, useDialog, NButton, NDataTable, NIcon } from 'naive-ui'
import { useMessage, useDialog, NButton, NDataTable, NIcon, NWatermark } from 'naive-ui'
import { useReverseManageStore } from '../../../../stores/reverseManage'
import { useSoftwareStore } from '../../../../stores/software'
import { AddSharp } from '@vicons/ionicons5'
@ -46,7 +46,8 @@ export default defineComponent({
components: {
NButton,
NDataTable,
BookTaskListAction
BookTaskListAction,
NWatermark
},
setup() {
@ -103,6 +104,7 @@ export default defineComponent({
width: 215,
fixed: 'right',
render(row, index) {
debugger
return h(BookTaskListAction, {
bookTask: row
})

View File

@ -89,7 +89,11 @@ export default defineComponent({
let message = useMessage()
let type = ref(props.type)
let row = ref(props.row)
let translate_options = ref([{ label: '翻译中文', key: 'chinese' }])
let translate_options = ref([
{ label: '翻译中文', key: 'chinese' },
{ label: '下翻译 英文 2 中文', key: 'down_english_chinese' },
{ label: '下翻译 中文 2 英文', key: 'down_chinese_english' }
])
//
watch(
@ -142,7 +146,6 @@ export default defineComponent({
message.error('当前翻译的数据里面没有中文')
}
} else if (key == 'chinese') {
let t = [[toRaw(row.value)], 'en', 'zh', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
@ -151,6 +154,14 @@ export default defineComponent({
}
message.success('添加翻译任务成功')
})
} else if (key == 'down_english_chinese') {
//
props.func.translateAll(key, props.index)
} else if (key == 'down_chinese_english') {
//
props.func.translateAll(key, props.index)
} else {
message.error('未知的翻译类型')
}
}

View File

@ -81,8 +81,8 @@
class="g-image-class generate-image-show"
:src="outImagePath ? outImagePath : space_image"
fit="cover"
width="120"
height="120"
:width="120"
:height="120"
lazy
/>
<n-button
@ -107,7 +107,7 @@
subImage="true"
class="g-image-class"
draggable="true"
:width="width"
:width="56"
style="margin: 1px"
v-for="(image, index) in images"
:key="image.id"
@ -330,7 +330,7 @@ export default defineComponent({
return
}
let lock = !data.value.imageLock
if (
reverseManageStore.selectBookTaskDetail &&
reverseManageStore.selectBookTaskDetail.length > 0

View File

@ -250,6 +250,7 @@ export default defineComponent({
row: row,
index: index,
func: {
translateAll: TranslateAll,
nextGptPrompt: NextGptPrompt,
singlePrompt: SinglePrompt
}
@ -848,8 +849,9 @@ export default defineComponent({
* 翻译传入的数据
* @param {*} key 翻译的类型
*/
async function TranslateAll(key) {
async function TranslateAll(key, index = -1) {
//
debugger
if (key == 'english') {
let tmp_d = []
for (let i = 0; i < data.value.length; i++) {
@ -877,6 +879,45 @@ export default defineComponent({
}
message.success('添加翻译任务成功')
})
} else if (key == 'down_english_chinese') {
//
// ,
let tempData = []
for (let i = 0; i < data.value.length; i++) {
const element = cloneDeep(data.value[i])
if (index <= i) {
tempData.push(element)
}
}
let t = [tempData, 'en', 'zh', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
} else if (key == 'down_chinese_english') {
//
//
let tempData = []
for (let i = 0; i < data.value.length; i++) {
const element = cloneDeep(data.value[i])
if (index <= i) {
tempData.push(element)
}
}
let t = [tempData, 'zh', 'en', false, false]
await window.mj.TranslateReturnNowTask(JSON.stringify(t), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加翻译任务成功')
})
} else {
message.error('未知的翻译类型')
}
}

View File

@ -74,7 +74,6 @@ export default defineComponent({
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i]
await window.api.GetPromptJson(item.name, (value) => {
console.log(value)
if (value.code == 0 && promptError) {
message.error(value.message)
promptError = false

View File

@ -35,8 +35,8 @@
v-model:value="mjSetting.imageScale"
></n-select>
</n-form-item>
<n-form-item label="命令后缀" style="width: 160px; margin-left: 10px" path="image_suffix">
<n-input v-model:value="image_suffix" placeholder="请输入后缀命令"></n-input>
<n-form-item label="命令后缀" style="width: 160px; margin-left: 10px" path="imageSuffix">
<n-input v-model:value="mjSetting.imageSuffix" placeholder="请输入后缀命令"></n-input>
</n-form-item>
<n-form-item label="生图任务量" style="width: 100px; margin-left: 10px" path="taskCount">
<n-input-number
@ -91,7 +91,7 @@
<n-input
type="password"
placeholder="请输入密钥"
show-password-on="mousedown"
show-password-on="mousedown"
v-model:value="mjSetting.apiSetting.apiKey"
></n-input>
</n-form-item>
@ -272,7 +272,7 @@ export default defineComponent({
let request_model_options = ref([])
let mj_api_options = ref([])
let image_suffix = computed(() => {
let computedSuffix = () => {
let text = image_model_options.value.findIndex((item) => {
return item.value == mjSetting.value.imageModel
})
@ -285,7 +285,7 @@ export default defineComponent({
let dd = ` --${image_model_options.value[text].text} --ar ${image_scale_options.value[sc].text}`
mjSetting.value.imageSuffix = dd
return dd
})
}
//
async function InitData() {
@ -419,7 +419,7 @@ export default defineComponent({
message.error('请检查必填字段')
return
}
mjSetting.value.imageSuffix = image_suffix.value
// mjSetting.value.imageSuffix = image_suffix.value
//
let request_model = mjSetting.value.requestModel
@ -500,6 +500,9 @@ export default defineComponent({
mjSetting.value.imageModel =
image_model_options.value.length > 0 ? image_model_options.value[0].value : null
}
//
mjSetting.value.imageSuffix = computedSuffix()
}
/**
@ -546,7 +549,6 @@ export default defineComponent({
image_scale_options,
image_model_options,
UpdateSelectRobot,
image_suffix,
request_model_options,
mj_api_options,
mj_speed_options,

View File

@ -1,18 +1,182 @@
<template>
<div>识别文案设置</div>
<div style="display: flex; align-items: center; margin-bottom: 10px; margin-top: 10px">
<div>选择识别字幕的方式</div>
<n-select
style="margin-left: 10px; width: 200px"
:options="subtitleOptions"
v-model:value="subtitleSetting.selectModel"
placeholder="选择识别文案方式"
/>
<n-button style="margin-left: 10px" :type="urlDisabled ? 'warning' : 'info'" @click="UnBlock">{{
urlDisabled ? '解锁' : '上锁'
}}</n-button>
<n-button style="margin-left: 10px" type="info" @click="ResetSubtitleSetting"
>重置数据</n-button
>
<n-button style="margin-left: 10px" type="info" @click="SaveSubtitleSetting">保存</n-button>
</div>
<div>
<n-card title="本地OCR | 本地Whisper">
<span class="url_class" @click="OpenTeach('local_ocr')" style="margin-left: 5px"
>本地OCR安装教程</span
>
<span class="url_class" @click="OpenTeach('local_whisper')" style="margin-left: 25px"
>本地Whisper安装教程</span
>
</n-card>
<n-card title="LAI Whisper" style="margin-top: 5px">
<n-form
ref="formRef"
label-placement="left"
inline
:label-width="auto"
:model="formValue"
:rules="rules"
:size="size"
>
<n-form-item label="API URL" path="subtitleSetting.laiWhisper.url">
<n-input
:disabled="urlDisabled"
style="width: 220px"
v-model:value="subtitleSetting.laiWhisper.url"
></n-input>
</n-form-item>
<n-form-item label="API KEY" path="subtitleSetting.laiWhisper.apiKey">
<n-input
type="password"
style="width: 220px"
show-password-on="mousedown"
v-model:value="subtitleSetting.laiWhisper.apiKey"
></n-input>
</n-form-item>
<n-form-item label="prompt" path="subtitleSetting.laiWhisper.prompt">
<n-input
type="text"
style="width: 220px"
placeholder="请输入识别文案的提示"
v-model:value="subtitleSetting.laiWhisper.prompt"
></n-input>
</n-form-item>
<n-form-item path="subtitleSetting.laiWhisper.syncGPTAPIKey">
<n-button type="info" @click="SyncGptSetting">同步GPT设置</n-button>
</n-form-item>
</n-form>
</n-card>
</div>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
components: {},
setup() {
onMounted(async () => {})
return {}
<script setup>
import { onMounted, ref, toRaw } from 'vue'
import { useMessage, useDialog, NCard, NSelect, NForm, NFormItem, NInput, NButton } from 'naive-ui'
import { GetSubtitleType } from '../../../../define/enum/waterMarkAndSubtitle'
import { SyncGptKeyType } from '../../../../define/enum/softwareEnum'
let message = useMessage()
let dialog = useDialog()
let urlDisabled = ref(true)
let subtitleSetting = ref({
selectModel: GetSubtitleType.LAI_WHISPER,
laiWhisper: {
url: 'https://api.laitool.cc/',
apiKey: '你的LAI API KEY',
syncGPTAPIKey: false
}
})
onMounted(async () => {
let res = await window.write.GetSubtitleSetting()
if (res.code == 0) {
message.error(res.message)
} else {
subtitleSetting.value = res.data
}
})
// GPTAPI KEY
async function SyncGptSetting() {
dialog.warning({
title: '同步提示',
content:
'该功能会将通用设置中的GPT Key直接复制到这边请确定在GPT中用的是LAI API不然后续对应的操作可能会出现报错是否继续操作',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
//
let res = await window.gpt.SyncGptKey(SyncGptKeyType.SUBTITLE_SETTING)
if (res.code == 0) {
message.error(res.message)
} else {
subtitleSetting.value.laiWhisper.apiKey = res.data
message.success('获取全局GPT Key成功')
}
}
})
}
//
function UnBlock() {
urlDisabled.value = !urlDisabled.value
}
//
async function ResetSubtitleSetting() {
dialog.warning({
title: '重置识别文案设置',
content: '注意,该操作不可逆,是否重置识别文案设置?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
let res = await window.write.ResetSubtitleSetting()
if (res.code == 0) {
message.error(res.message)
} else {
subtitleSetting.value = res.data
message.success('重置字幕设置成功')
}
}
})
}
//
async function SaveSubtitleSetting() {
//
let res = await window.write.SaveSubtitleSetting(toRaw(subtitleSetting.value))
if (res.code == 0) {
message.error(res.message)
} else {
message.success('保存提取文案设置成功')
}
}
//
function OpenTeach(type) {
switch (type) {
case 'local_ocr':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RSj1dIJvpooKCGxHxhgcDhsqnSh?from=from_copylink'
)
break
case 'local_whisper':
window.api.OpenUrl(
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
)
default:
break
}
}
let subtitleOptions = ref([
{ label: '本地OCR', value: GetSubtitleType.LOCAL_OCR },
{ label: '本地Whisper', value: GetSubtitleType.LOCAL_WHISPER },
{ label: 'LAI Whisper', value: GetSubtitleType.LAI_WHISPER }
])
</script>
<style>
.url_class {
color: blue;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>

View File

@ -39,8 +39,14 @@
</n-form-item>
<n-form-item label="APP ID/应用ID">
<n-input
:type="item.name == 'laitool' ? 'text' : 'password'"
:show-password-on="mousedown"
v-if="item.name == 'laitool'"
type="text"
v-model:value="item.translation_app_id"
/>
<n-input
v-else
type="password"
show-password-on="mousedown"
v-model:value="item.translation_app_id"
/>
</n-form-item>
@ -93,6 +99,7 @@ export default defineComponent({
return
}
translateSetting.value = translateSettingRes.data
console.log(translateSetting.value)
})
function GetTranslationName(name) {

View File

@ -448,7 +448,7 @@ export default defineComponent({
async function SaveMask() {
debugger
//
if (canvasHistory.length <= 1) {
if (canvasHistory.length <= 0) {
message.error('请先选择去水印的区域')
return
}

View File

@ -81,6 +81,8 @@ export const useReverseManageStore = defineStore('reverseManage', {
// 获取小说任务数据
async GetBookTaskDataFromDB(condition) {
try {
debugger
this.bookTaskData = []
//@ts-ignore
let res = await window.book.GetBookTaskData(condition)
if (res.code == 0) {
@ -90,7 +92,6 @@ export const useReverseManageStore = defineStore('reverseManage', {
this.bookTaskData = res.data.bookTasks
this.selectBookTask = res.data.bookTasks[0]
} else {
this.bookTaskData = []
this.selectBookTask = {
no: null,
id: null,