添加文案处理的功能

This commit is contained in:
2024-07-13 15:44:13 +08:00
parent 669e57824d
commit c8a46d59fb
80 changed files with 7383 additions and 2613 deletions
+160 -132
View File
@@ -1,8 +1,7 @@
import fs from "fs"
import { isEmpty } from "lodash";
import path from "path";
const fspromises = fs.promises;
import fs from 'fs'
import { isEmpty } from 'lodash'
import path from 'path'
const fspromises = fs.promises
/**
* 判断文件或目录是否存在
@@ -10,39 +9,67 @@ const fspromises = fs.promises;
* @returns true表示存在,false表示不存在
*/
export async function CheckFileOrDirExist(path) {
try {
await fspromises.access(path);
return true; // 文件或目录存在
} catch (error) {
return false; // 文件或目录不存在
}
try {
await fspromises.access(path)
return true // 文件或目录存在
} catch (error) {
return false // 文件或目录不存在
}
}
// 检查文件夹是不是存在,不存在的话,创建
export async function CheckFolderExistsOrCreate(folderPath) {
try {
if (!(await CheckFileOrDirExist(folderPath))) {
await fspromises.mkdir(folderPath, { recursive: true });
}
} catch (error) {
throw new Error(error);
try {
if (!(await CheckFileOrDirExist(folderPath))) {
await fspromises.mkdir(folderPath, { recursive: true })
}
} catch (error) {
throw new Error(error)
}
}
/**
* 拼接两个地址,返回拼接后的地址
* @param {*} rootPath 根目录的消息
* @param {*} subPath 子目录的消息
* @returns
* @returns
*/
export function JoinPath(rootPath, subPath) {
// 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址
if (subPath && !isEmpty(subPath)) {
return path.resolve(rootPath, subPath)
} else {
return null
}
// 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址
if (subPath && !isEmpty(subPath)) {
return path.resolve(rootPath, subPath)
} else {
return null
}
}
/**
* 删除指定的文件中里面所有的文件和文件夹
* @param {*} folderPath 文件夹地址
*/
export async function DeleteFolderAllFile(folderPath) {
try {
let folderIsExist = await CheckFileOrDirExist(folderPath)
if (!folderIsExist) {
throw new Error('目的文件夹不存在,' + folderPath)
}
// 开始删除
let files = await fspromises.readdir(folderPath)
files.forEach(async (file) => {
const curPath = path.join(folderPath, file)
if ((await fspromises.stat(curPath)).isDirectory()) {
// 判断是不是文件夹
await DeleteFolderAllFile(curPath) // 递归删除文件夹内容
fspromises.rmdir(curPath) // 删除空文件夹
} else {
// 删除文件
fspromises.unlink(curPath)
}
})
} catch (error) {
throw error
}
}
/**
* 拷贝一个文件或者是文件夹到指定的目标地址
@@ -51,65 +78,64 @@ export function JoinPath(rootPath, subPath) {
* @param {*} checkParent 是否检查父文件夹是否存在,不存在的话创建,默认false,不检查,不存在直接创建
*/
export async function CopyFileOrFolder(source, target, checkParent = false) {
try {
// 判断源文件或文件夹是不是存在
if (!(await CheckFileOrDirExist(source))) {
throw new Error(`源文件或文件夹不存在: ${source}`);
}
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target);
let parentIsExist = await CheckFileOrDirExist(parent_path);
if (!parentIsExist) {
if (checkParent) {
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`);
} else {
await fspromises.mkdir(parent_path, { recursive: true });
}
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source);
// 复制文件夹的逻辑
async function copyDirectory(source, target) {
// 创建目标文件夹
await fspromises.mkdir(target, { recursive: true });
let entries = await fspromises.readdir(source, { withFileTypes: true });
for (let entry of entries) {
let srcPath = path.join(source, entry.name);
let tgtPath = path.join(target, entry.name);
if (entry.isDirectory()) {
await copyDirectory(srcPath, tgtPath);
} else {
await fspromises.copyFile(srcPath, tgtPath);
}
}
}
if (isDirectory) {
// 创建目标文件夹
await copyDirectory(source, target);
} else {
// 复制文件
await fspromises.copyFile(source, target);
}
} catch (error) {
throw error;
try {
// 判断源文件或文件夹是不是存在
if (!(await CheckFileOrDirExist(source))) {
throw new Error(`源文件或文件夹不存在: ${source}`)
}
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target)
let parentIsExist = await CheckFileOrDirExist(parent_path)
if (!parentIsExist) {
if (checkParent) {
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`)
} else {
await fspromises.mkdir(parent_path, { recursive: true })
}
}
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source)
// 复制文件夹的逻辑
async function copyDirectory(source, target) {
// 创建目标文件夹
await fspromises.mkdir(target, { recursive: true })
let entries = await fspromises.readdir(source, { withFileTypes: true })
for (let entry of entries) {
let srcPath = path.join(source, entry.name)
let tgtPath = path.join(target, entry.name)
if (entry.isDirectory()) {
await copyDirectory(srcPath, tgtPath)
} else {
await fspromises.copyFile(srcPath, tgtPath)
}
}
}
if (isDirectory) {
// 创建目标文件夹
await copyDirectory(source, target)
} else {
// 复制文件
await fspromises.copyFile(source, target)
}
} catch (error) {
throw error
}
}
/** * 判断一个文件地址是不是文件夹
* @param {*} path 输入的文件地址
* @returns true 是 false 不是
*/
export async function IsDirectory(path) {
try {
const stat = await fspromises.stat(path);
return stat.isDirectory();
} catch (error) {
throw new Error(`获取文件夹信息失败: ${path}`);
}
try {
const stat = await fspromises.stat(path)
return stat.isDirectory()
} catch (error) {
throw new Error(`获取文件夹信息失败: ${path}`)
}
}
/**
* 将文件或者是文件夹备份到指定的文职
@@ -117,29 +143,27 @@ export async function IsDirectory(path) {
* @param {*} target_path 目标文件/文件夹地址
*/
export async function BackupFileOrFolder(source_path, target_path) {
try {
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target_path);
if (!(await CheckFileOrDirExist(parent_path))) {
await fspromises.mkdir(parent_path, { recursive: true });
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source_path);
if (isDirectory) {
// 复制文件夹
await fspromises.rename(source_path, target_path);
} else {
// 复制文件
await fspromises.copyFile(source, target);
}
} catch (error) {
throw new Error(error);
try {
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target_path)
if (!(await CheckFileOrDirExist(parent_path))) {
await fspromises.mkdir(parent_path, { recursive: true })
}
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source_path)
if (isDirectory) {
// 复制文件夹
await fspromises.rename(source_path, target_path)
} else {
// 复制文件
await fspromises.copyFile(source, target)
}
} catch (error) {
throw new Error(error)
}
}
/**
* 获取指定的文件夹下面的所有的指定的拓展名的文件
@@ -148,40 +172,44 @@ export async function BackupFileOrFolder(source_path, target_path) {
* @returns 返回文件中指定的后缀文件地址(绝对地址)
*/
export async function GetFilesWithExtensions(folderPath, extensions) {
try {
// 判断当前是不是文件夹
if (!(await IsDirectory(folderPath))) {
throw new Error("输入的不是有效的文件夹地址")
}
let entries = await fspromises.readdir(folderPath, { withFileTypes: true });
let files = [];
// 使用Promise.all来并行处理所有的stat调用
const fileStats = await Promise.all(entries.map(async (entry) => {
const entryPath = path.join(folderPath, entry.name);
if (entry.isFile()) {
return {
name: entry.name,
path: entryPath,
isFile: true,
};
} else {
return {
isFile: false,
};
}
}));
// 过滤出文件并且满足扩展名要求的文件
files = fileStats.filter(fileStat => fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase()));
// 对files数组进行排序,基于文件名
files.sort((a, b) => a.name.localeCompare(b.name));
// 返回文件名数组(完整的)
return files.map(fileStat => path.join(folderPath, fileStat.name));
} catch (error) {
throw new Error(error);
try {
// 判断当前是不是文件夹
if (!(await IsDirectory(folderPath))) {
throw new Error('输入的不是有效的文件夹地址')
}
}
let entries = await fspromises.readdir(folderPath, { withFileTypes: true })
let files = []
// 使用Promise.all来并行处理所有的stat调用
const fileStats = await Promise.all(
entries.map(async (entry) => {
const entryPath = path.join(folderPath, entry.name)
if (entry.isFile()) {
return {
name: entry.name,
path: entryPath,
isFile: true
}
} else {
return {
isFile: false
}
}
})
)
// 过滤出文件并且满足扩展名要求的文件
files = fileStats.filter(
(fileStat) =>
fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase())
)
// 对files数组进行排序,基于文件名
files.sort((a, b) => a.name.localeCompare(b.name))
// 返回文件名数组(完整的)
return files.map((fileStat) => path.join(folderPath, fileStat.name))
} catch (error) {
throw new Error(error)
}
}
+6 -1
View File
@@ -36,5 +36,10 @@ export function MillisecondsToTimeString(milliseconds) {
const secondsFormatted = seconds.toString().padStart(2, '0')
const msFormatted = ms.toString().padStart(3, '0')
return `${hoursFormatted}:${minutesFormatted}:${secondsFormatted}.${msFormatted}`
let timeString = `${hoursFormatted}:${minutesFormatted}:${secondsFormatted}.${msFormatted}`
// 使用正则表达式检测并删除多余的小数点
// 此正则表达式查找除了第一个小数点之外的所有小数点,并将它们替换为空字符串
timeString = timeString.replace(/(\.\d+)\./g, '$1')
return timeString
}
+77 -45
View File
@@ -1,56 +1,88 @@
let apiUrl = [{
label: "openai-hk",
value: "3d64e50e-79c0-49ec-a72d-7dfdf508dd04",
gpt_url: "https://api.openai-hk.com/v1/chat/completions",
mj_url: null,
buy_url: "https://openai-hk.com/?i=10196"
}, {
label: "通义千问",
value: "b630c69a-99e9-46bc-8d88-39a00bcc3d2a",
gpt_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
mj_url: null,
buy_url: null
}, {
label: "DrawAPI(MJ)",
value: "2cabf684-ac48-4733-a427-8c41626f7d8f",
gpt_url: null,
mj_url: {
imagine: "https://mjapi.deepwl.net/api/mj/submit/imagine",
describe: "https://mjapi.deepwl.net/api/mj/submit/describe",
update_file: "https://mjapi.deepwl.net/api/mj/submit/upload-discord-images",
once_get_task: "https://mjapi.deepwl.net/api/mj/query/task/${id}",
get_task_list: "https://mjapi.deepwl.net/api/mj/task/list-by-condition"
let apiUrl = [
{
label: 'LAI API',
value: 'b44c6f24-59e4-4a71-b2c7-3df0c4e35e65',
gpt_url: 'https://laitool.net/v1/chat/completions',
mj_url: {
imagine: 'https://laitool.net/mj/submit/imagine',
describe: 'https://laitool.net/mj/submit/describe',
update_file: 'https://laitool.net/mj/submit/upload-discord-images',
once_get_task: 'https://laitool.net/mj/task/${id}/fetch'
},
d3_url: {
image: 'https://laitool.net/v1/images/generations'
},
buy_url: 'https://laitool.net/register?aff=Zmdu'
},
d3_url: null,
buy_url: "https://mjapi.deepwl.net/#/home"
}, {
label: "ePhoneAPI",
value: "b8866543-8c27-4888-869c-00aa1eb31272",
gpt_url: "https://api.ephone.ai/v1/chat/completions",
mj_url: {
imagine: "https://api.ephone.ai/mj/submit/imagine",
describe: "https://api.ephone.ai/mj/submit/describe",
update_file: "https://api.ephone.ai/mj/submit/upload-discord-images",
once_get_task: "https://api.ephone.ai/mj/task/${id}/fetch",
{
label: 'openai-hk',
value: '3d64e50e-79c0-49ec-a72d-7dfdf508dd04',
gpt_url: 'https://api.openai-hk.com/v1/chat/completions',
mj_url: null,
buy_url: 'https://openai-hk.com/?i=10196'
},
d3_url: {
image: "https://api.ephone.ai/v1/images/generations"
{
label: '通义千问',
value: 'b630c69a-99e9-46bc-8d88-39a00bcc3d2a',
gpt_url: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
mj_url: null,
buy_url: null
},
buy_url: "https://ephone.ai/register?aff=55XT"
}]
{
label: 'DrawAPI(MJ)',
value: '2cabf684-ac48-4733-a427-8c41626f7d8f',
gpt_url: null,
mj_url: {
imagine: 'https://mjapi.deepwl.net/api/mj/submit/imagine',
describe: 'https://mjapi.deepwl.net/api/mj/submit/describe',
update_file: 'https://mjapi.deepwl.net/api/mj/submit/upload-discord-images',
once_get_task: 'https://mjapi.deepwl.net/api/mj/query/task/${id}',
get_task_list: 'https://mjapi.deepwl.net/api/mj/task/list-by-condition'
},
d3_url: null,
buy_url: 'https://mjapi.deepwl.net/#/home'
},
{
label: 'ePhoneAPI',
value: 'b8866543-8c27-4888-869c-00aa1eb31272',
gpt_url: 'https://api.ephone.ai/v1/chat/completions',
mj_url: {
imagine: 'https://api.ephone.ai/mj/submit/imagine',
describe: 'https://api.ephone.ai/mj/submit/describe',
update_file: 'https://api.ephone.ai/mj/submit/upload-discord-images',
once_get_task: 'https://api.ephone.ai/mj/task/${id}/fetch'
},
d3_url: {
image: 'https://api.ephone.ai/v1/images/generations'
},
buy_url: 'https://ephone.ai/register?aff=55XT'
},
{
label: 'KIMI',
value: 'b5c8c8c5-f3c4-4c88-b25c-7f5a3d5f9d1f',
gpt_url: 'https://api.moonshot.cn/v1/chat/completions',
mj_url: null,
d3_url: null,
buy_url: 'https://platform.moonshot.cn/console/account'
},
{
label: 'DouBao',
value: 'd3f6a2a9-2d17-4c3b-8d7f-28356cfa676e',
gpt_url: 'https://ark.cn-beijing.volces.com/api/v3/chat/completions',
mj_url: null,
d3_url: null,
buy_url: 'https://www.volcengine.com/product/doubao'
}
]
/**
* 通过ID获取指定的数据(value)
* @param {*} id
* @param {*} id
*/
function getApiMessageByID(id) {
let mj_api_url_index = apiUrl.findIndex(item => item.value == id)
let mj_api_url_index = apiUrl.findIndex((item) => item.value == id)
if (mj_api_url_index == -1) {
throw new Error("没有找到对应的MJ API的配置,请先检查配置")
throw new Error('没有找到对应的MJ API的配置,请先检查配置')
}
}
export {
apiUrl,
getApiMessageByID
}
export { apiUrl, getApiMessageByID }
@@ -1,13 +1,16 @@
import Realm, { ObjectSchema } from 'realm'
import { BookBackTaskStatus, BookBackTaskType } from '../../../enum/bookEnum'
import { BookBackTaskStatus, BookBackTaskType, TaskExecuteType } from '../../../enum/bookEnum'
export class BookBackTaskList extends Realm.Object<BookBackTaskList> {
id: string
bookId: string
bookTaskId: string
bookTaskDetailId: string
name: string // 任务名称,小说名+批次名+分镜名
type: BookBackTaskType
status: BookBackTaskStatus
errorMessage: string | null
executeType: TaskExecuteType // 任务执行类型,手动还是自动
createTime: Date
updateTime: Date
@@ -17,9 +20,12 @@ export class BookBackTaskList extends Realm.Object<BookBackTaskList> {
id: 'string',
bookId: { type: 'string', indexed: true },
bookTaskId: { type: 'string', indexed: true },
bookTaskDetailId: { type: 'string', indexed: true },
name: 'string',
type: 'string',
status: 'string',
errorMessage: 'string?',
executeType: { type: 'string', default: TaskExecuteType.AUTO },
createTime: 'date',
updateTime: 'date'
},
+4 -2
View File
@@ -13,7 +13,8 @@ export class BookModel extends Realm.Object<BookModel> {
audioPath: string | null
updateTime: Date
createTime: Date
test: string | null
version: string
subtitlePosition: string | null
static schema: ObjectSchema = {
name: 'Book',
@@ -29,7 +30,8 @@ export class BookModel extends Realm.Object<BookModel> {
imageFolder: 'string?',
updateTime: 'date',
createTime: 'date',
test: 'string?'
version: 'string',
subtitlePosition: 'string?'
},
// 主键为_id
primaryKey: 'id'
@@ -128,6 +128,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
prompt: string | null // 提示
adetailer: boolean // 是否开启修脸
sdConifg: SDConfig | null // SD配置
subtitlePosition: string | null // 字幕位置
createTime: Date
updateTime: Date
@@ -156,6 +157,7 @@ export class BookTaskDetailModel extends Realm.Object<BookTaskDetailModel> {
prompt: 'string?',
adetailer: 'bool',
sdConifg: 'SDConfig?',
subtitlePosition: 'string?',
createTime: 'date',
updateTime: 'date'
},
+9 -1
View File
@@ -7,6 +7,10 @@ export class SoftwareModel extends Realm.Object<SoftwareModel> {
reverse_display_show: boolean
reverse_show_book_striped: boolean
reverse_data_table_size: ComponentSize
globalSetting: string // 通用设置的json字符串
ttsSetting: string | null // TTS设置的json字符串
writeSetting: string | null // 文案的相关配置的json字符串
aiSetting: string | null // AI相关的配置的json字符串
static schema: ObjectSchema = {
name: 'Software',
@@ -15,7 +19,11 @@ export class SoftwareModel extends Realm.Object<SoftwareModel> {
theme: 'string',
reverse_display_show: 'bool',
reverse_show_book_striped: 'bool',
reverse_data_table_size: 'string'
reverse_data_table_size: 'string',
globalSetting: 'string',
ttsSetting: 'string?', // 可空
writeSetting: 'string?',
aiSetting: 'string?'
},
// 主键为_id
primaryKey: 'id'
@@ -3,10 +3,19 @@ import path from 'path'
import { BaseService } from '../baseService.js'
import { define } from '../../../define.js'
import { BookTaskModel } from '../../model/Book/bookTask.js'
import { BookTaskStatus } from '../../../enum/bookEnum.js'
import {
BookBackTaskStatus,
BookBackTaskType,
BookTaskStatus,
TaskExecuteType
} from '../../../enum/bookEnum.js'
import { errorMessage, successMessage } from '../../../../main/generalTools.js'
import { BaseRealmService } from './bookBasic'
import { isEmpty } from 'lodash'
import { DefaultObject } from 'realm/dist/public-types/schema.js'
import { BookModel } from '../../model/Book/book.js'
import { OtherData } from '../../../enum/softwareEnum.js'
import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js'
const { v4: uuidv4 } = require('uuid')
export class BookBackTaskListService extends BaseRealmService {
@@ -32,13 +41,93 @@ export class BookBackTaskListService extends BaseRealmService {
/**
* 获取指定条件的后台任务队列
* bookId 必填
* bookId 和 status 必填一个
* @param query bookIdbookTaskIdnametypestatus
*/
getBookBackTaskList(query) {
GetBookBackTaskList(query) {
try {
// if()
if (query == null) {
throw new Error('查询后台队列任务失败,没有查询条件')
}
if (isEmpty(query.bookId) && isEmpty(query.status)) {
throw new Error('查询后台队列任务失败,没有查询条件')
}
// 构建查询条件
// 下面时可空的条件
let queryString = ''
if (query.bookId) {
queryString = `bookId = ${query.bookId}`
}
if (query.bookTaskId) {
queryString += ` && bookTaskId = ${query.bookTaskId}`
}
if (query.name) {
queryString += ` && name = ${query.name}`
}
if (query.type) {
queryString += ` && type = ${query.type}`
}
if (query.status) {
queryString += ` && status = ${query.status}`
}
if (query.executeType) {
queryString += ` && executeType = ${query.executeType}`
}
// 获取数据
let tasks = this.realm
.objects('BookBackTaskList')
.filtered(queryString)
.sorted('createTime', true)
let res
if (query.count) {
res = tasks.slice(0, query.count)
} else {
res = tasks
}
return successMessage(
res.toJSON(),
'查询后台队列任务成功',
'BookBackTaskList_GetBookBackTaskList'
)
} catch (error) {
throw error
}
}
/**
* 获取等待状态的任务和返回指定数量的数据
* @param executeType 任务的执行类型
* @param count 返回数据的数量
*/
GetWaitTaskAndSlice(executeType: TaskExecuteType, count: number) {
try {
let tasks = this.realm
.objects<BookBackTaskList>('BookBackTaskList')
.filtered(
'status == $0 && executeType == $1',
BookBackTaskStatus.WAIT,
executeType ? executeType : TaskExecuteType.AUTO
)
.sorted('createTime', false)
if (count != null) {
tasks = tasks.slice(0, count) as unknown as Realm.Results<BookBackTaskList>
}
let res = Array.from(tasks).map((item) => {
let resObj = {
...item
}
return resObj
})
return successMessage(
res,
'查询等待状态的后台队列任务成功',
'BookBackTaskList_GetWaitTaskAndSlice'
)
} catch (error) {
throw error
}
@@ -48,25 +137,61 @@ export class BookBackTaskListService extends BaseRealmService {
* 新增一个小说相关的后台任务队列
* @param bookBackTask 要添加的小说数据
*/
AddBookBackTaskList(bookBackTask) {
async AddBookBackTask(
bookId: string,
taskType: BookBackTaskType,
executeType = TaskExecuteType.AUTO,
bookTaskId = null,
bookTaskDetailId = null
) {
try {
// 判断数据是不是存在
if (
isEmpty(bookBackTask.bookId) ||
isEmpty(bookBackTask.bookTaskId) ||
isEmpty(bookBackTask.name) ||
isEmpty(bookBackTask.type)
) {
throw new Error('新增后台队列任务到数据库失败,数据不完整,缺少必要字段')
// 通过bookid获取book信息
let book = this.realm.objectForPrimaryKey('Book', bookId)
if (book == null) {
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说')
}
let bookTask
if (bookTaskId) {
bookTask = this.realm.objectForPrimaryKey('BookTask', bookTaskId)
if (bookTask == null) {
throw new Error('新增后台队列任务到数据库失败,没有找到对应的小说批次任务')
}
}
let bookTaskDetail
if (bookTaskDetailId) {
bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId)
if (bookTaskDetail == null) {
throw new Error(
'新增后台队列任务到数据库失败,没有找到对应的小说批次任务详情(分镜数据)'
)
}
}
// 开始往数据库中写数据
let name = `${book.name}-${bookTask ? bookTask.name : 'default'}-${
bookTaskDetail ? bookTaskDetail.name : 'default'
}-${taskType}`
let bookBackTask = {
id: uuidv4(),
bookId: bookId,
bookTaskId: bookTaskId ? bookTaskId : OtherData.DEFAULT,
bookTaskDetailId: bookTaskDetailId ? bookTaskDetailId : OtherData.DEFAULT,
name: name,
type: taskType,
executeType: executeType,
status: BookBackTaskStatus.WAIT,
createTime: new Date(),
updateTime: new Date()
}
// 开始新建
bookBackTask.id = uuidv4()
bookBackTask.createTime = new Date()
bookBackTask.updateTime = new Date()
bookBackTask.status = BookTaskStatus.WAIT
this.realm.write(() => {
this.realm.create('BookBackTaskList', bookBackTask)
})
// 添加成功之后,调用开始执行任务的方法
await global.taskManager.ExecuteAutoTask()
return successMessage(
bookBackTask,
'新增后台队列任务到数据库成功',
@@ -74,24 +199,25 @@ export class BookBackTaskListService extends BaseRealmService {
)
} catch (error) {
return errorMessage(
'新增后台队列任务到数据库失败,错误信息入校' + error.toString(),
'新增后台队列任务到数据库失败,错误信息如下' + error.toString(),
'BookBackTaskList_AddBookBackTaskList'
)
}
}
/**
* 修改一个小说相关的后台任务队列中的详细信息(对于后台的队列任务只能修改状态)
* 修改一个小说相关的后台任务队列中的详细信息
* (对于后台的队列任务只能修改状态)和错误信息
* @param bookBackTask 修改的数据
*/
async ModifyBookBackTaskList(bookBackTask) {
UpdateTaskStatus(bookBackTask) {
try {
// 判断数据是不是存在
if (isEmpty(bookBackTask.id) || isEmpty(bookBackTask.status)) {
throw new Error('修改后台队列任务失败,数据不完整,缺少必要字段')
}
// 开始修改
this.realm.write(() => {
this.transaction(() => {
// 获取指定ID的队列任务
let _bookBackTask = this.realm.objectForPrimaryKey('BookBackTaskList', bookBackTask.id)
// 判断数据是不是存在
@@ -100,46 +226,50 @@ export class BookBackTaskListService extends BaseRealmService {
}
// 修改数据
_bookBackTask.status = bookBackTask.status
if (bookBackTask.errorMessage) {
_bookBackTask.errorMessage = bookBackTask.errorMessage
}
})
return successMessage(
bookBackTask,
'修改后台队列任务成功',
'修改后台队列任务状态成功',
'BookBackTaskList_ModifyBookBackTaskList'
)
} catch (error) {
return errorMessage(
'修改后台队列任务失败,错误信息如下:' + error.toString(),
'BookBackTaskList_ModifyBookBackTaskList'
)
throw error
}
}
/**
* 删除满足条件的数据,包含 id、bookId、bookTaskId
* 上面的条件,至少要有一个
* @param bookBackTask 删除的数据
*/
async DeleteBookBackTaskListBy(bookBackTask) {
async DeleteBookBackTask(bookBackTask) {
try {
this.realm.write(() => {
if (
!bookBackTask.hasOwnProperty('id') &&
!bookBackTask.hasOwnProperty('bookId') &&
!bookBackTask.hasOwnProperty('bookTaskId')
) {
throw new Error('删除后台队列任务失败,缺少必要的删除条件')
}
this.transaction(() => {
// 构建查询条件
let query = [] as string[]
if (bookBackTask.id) {
query.push(`id = ${bookBackTask.id}`)
}
const tasksToDelete = this.realm
.objects('BookBackTaskList')
.filtered('id == $0', bookBackTask.id)
if (bookBackTask.bookId) {
query.push(`bookId = ${bookBackTask.bookId}`)
tasksToDelete.filtered('bookId == $0', bookBackTask.bookId)
}
if (bookBackTask.bookTaskId) {
query.push(`bookTaskId = ${bookBackTask.bookTaskId}`)
}
const queryString = query.join(' && ')
// 获取指定的数据
if (queryString) {
const tasksToDelete = this.realm.objects('BookBackTaskList').filtered(queryString)
this.realm.delete(tasksToDelete)
} else {
throw new Error('删除后台队列任务失败,没有筛选条件')
tasksToDelete.filtered('bookTaskId == $0', bookBackTask.bookTaskId)
}
this.realm.delete(tasksToDelete)
return successMessage(
bookBackTask,
'删除后台队列任务成功',
+39 -1
View File
@@ -12,6 +12,8 @@ import {
WebuiConfig
} from '../../model/Book/bookTaskDetail'
import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel'
import { TaskExecuteType } from '../../../enum/bookEnum'
import { version } from '../../../../../package.json'
let dbPath = path.resolve(define.db_path, 'book.realm')
@@ -39,6 +41,42 @@ const migration = (oldRealm: Realm, newRealm: Realm) => {
newBookTask[i].audioPath = null // 为新属性设置默认值
}
}
if (oldRealm.schemaVersion < 4) {
const oldBookTask = oldRealm.objects('BookBackTaskList')
const newBookTask = newRealm.objects('BookBackTaskList')
for (let i = 0; i < oldBookTask.length; i++) {
newBookTask[i].errorMessage = null // 设置错误信息的默认值
}
}
if (oldRealm.schemaVersion < 5) {
const oldBookTask = oldRealm.objects('BookBackTaskList')
const newBookTask = newRealm.objects('BookBackTaskList')
for (let i = 0; i < oldBookTask.length; i++) {
newBookTask[i].executeType = TaskExecuteType.AUTO // 设置错误信息的默认值
}
}
if (oldRealm.schemaVersion < 6) {
const oldBookTask = oldRealm.objects('BookBackTaskList')
const newBookTask = newRealm.objects('BookBackTaskList')
for (let i = 0; i < oldBookTask.length; i++) {
newBookTask[i].bookTaskDetailId = 'default' // 设置错误信息的默认值
}
}
if (oldRealm.schemaVersion < 7) {
const oldBookTask = oldRealm.objects('Book')
const newBookTask = newRealm.objects('Book')
for (let i = 0; i < oldBookTask.length; i++) {
newBookTask[i].version = version // 设置版本的默认值
newBookTask[i].subtitlePosition = null // 设置字幕位置的默认值
}
}
if (oldRealm.schemaVersion < 8) {
const oldBookTask = oldRealm.objects('BookTaskDetail')
const newBookTask = newRealm.objects('BookTaskDetail')
for (let i = 0; i < oldBookTask.length; i++) {
newBookTask[i].subtitlePosition = null // 设置字幕位置的默认值
}
}
}
export class BaseRealmService extends BaseService {
@@ -79,7 +117,7 @@ export class BaseRealmService extends BaseService {
BookTaskDetailModel
],
path: this.dbpath,
schemaVersion: 3,
schemaVersion: 8,
migration: migration
}
this.realm = await Realm.open(config)
+41 -1
View File
@@ -237,7 +237,7 @@ export class BookService extends BaseRealmService {
delete book.imageFolder
// 修改数据
book.updateTime = new Date()
this.realm.write(() => {
this.transaction(() => {
this.realm.create('Book', book, UpdateMode.Modified)
})
@@ -248,4 +248,44 @@ export class BookService extends BaseRealmService {
throw error
}
}
/**
* 修改小说数据
* @param bookId 小说的ID
* @param bookData 要修改的小说数据
*/
async UpdateBookData(bookId: string, bookData) {
try {
if (bookId == null) {
throw new Error('修改小说数据失败,缺少小说ID')
}
if (bookData == null) {
throw new Error('修改小说数据失败,缺少小说数据')
}
// 检查小说ID对应的数据是不是存在
let bookRes = this.GetBookDataById(bookId)
if (bookRes.data == null) {
throw new Error('修改小说数据失败,小说ID对应的数据不存在')
}
if (bookData && bookData.id) {
delete bookData.id
}
// 开始修改
this.transaction(() => {
this.realm.create('Book', { id: bookId, ...bookData }, UpdateMode.Modified)
})
bookRes = this.GetBookDataById(bookId)
if (bookRes.data == null) {
throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在')
}
return successMessage(bookRes.data, '修改小说数据成功', 'ReverseBook_UpdateBookData')
} catch (error) {
throw error
}
}
}
@@ -10,6 +10,7 @@ import { endsWith, isEmpty } from 'lodash'
import { book } from '../../../../preload/book.js'
import { DefaultObject } from 'realm/dist/public-types/schema.js'
import { JoinPath } from '../../../Tools/file.js'
import { BookTaskDetailModel } from '../../model/Book/bookTaskDetail.js'
const { v4: uuidv4 } = require('uuid')
let dbPath = path.resolve(define.db_path, 'book.realm')
@@ -47,28 +48,20 @@ export class BookTaskDetailService extends BaseRealmService {
if (condition == null) {
throw new Error('查询小说分镜信息,查询条件不能为空')
}
let query = [] as string[]
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
if (condition.id) {
query.push(`id = ${condition.id}`)
tasksToDelete = tasksToDelete.filtered('id==$0', condition.id)
}
if (condition.bookId) {
query.push(`bookId = ${condition.bookId}`)
tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId)
}
if (condition.bookTaskId) {
query.push(`bookTaskId = ${condition.bookTaskId}`)
tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId)
}
if (condition.name) {
query.push(`name = ${condition.name}`)
}
const queryString = query.join(' && ')
let tasksToDelete: Realm.Results<Realm.Object<DefaultObject, never> & DefaultObject>
// 获取指定的数据
if (queryString) {
tasksToDelete = this.realm.objects('BookTaskDetail').filtered(queryString)
} else {
// 返回全部
tasksToDelete = this.realm.objects('BookTaskDetail')
tasksToDelete = tasksToDelete.filtered('name==$0', condition.name)
}
let resData = Array.from(tasksToDelete).map((item) => {
let resObj = {
...item,
@@ -138,19 +131,20 @@ export class BookTaskDetailService extends BaseRealmService {
let bookTaskDetails = this.realm
.objects<BookTaskModel>('BookTaskDetail')
.filtered(
'bookId = $0 AND bookTaskId = $1',
'bookId == $0 AND bookTaskId == $1',
bookTaskDetail.bookId,
bookTaskDetail.bookTaskId
)
let maxNo = bookTaskDetails.max('no')
bookTaskDetail.no = maxNo ? Number(maxNo) + 1 : 1
let name = bookTaskDetail.no.tosString().padStart(5, '0')
let name = bookTaskDetail.no.toString().padStart(5, '0')
bookTaskDetail.name = name
bookTaskDetail.id = uuidv4()
bookTaskDetail.createTime = new Date()
bookTaskDetail.updateTime = new Date()
bookTaskDetail.adetailer = false // 先写死false
// 开始添加
this.transaction(() => {
this.realm.create('BookTaskDetail', bookTaskDetail)
@@ -203,25 +197,20 @@ export class BookTaskDetailService extends BaseRealmService {
if (isEmpty(condition.id) && isEmpty(condition.bookTaskId) && isEmpty(condition.bookId)) {
throw new Error('删除小说分镜信息失败,没有必要参数')
}
let query = [] as string[]
let tasksToDelete = this.realm.objects<BookTaskDetailModel>('BookTaskDetail')
if (condition.id) {
query.push(`id = ${condition.id}`)
tasksToDelete = tasksToDelete.filtered('id==$0', condition.id)
}
if (condition.bookId) {
query.push(`bookId = ${condition.bookId}`)
tasksToDelete = tasksToDelete.filtered('bookId==$0', condition.bookId)
}
if (condition.bookTaskId) {
query.push(`bookTaskId = ${condition.bookTaskId}`)
tasksToDelete = tasksToDelete.filtered('bookTaskId==$0', condition.bookTaskId)
}
if (condition.name) {
query.push(`name = ${condition.name}`)
tasksToDelete = tasksToDelete.filtered('name==$0', condition.name)
}
if (query.length <= 0) {
throw new Error('删除小说分镜任务失败,没有查询条件')
}
const queryString = query.join(' && ')
let tasksToDelete = this.realm.objects('BookTaskDetail').filtered(queryString)
this.transaction(() => {
this.realm.delete(tasksToDelete)
})
+31 -1
View File
@@ -3,11 +3,12 @@ import path from 'path'
import { BaseService } from '../baseService.js'
import { define } from '../../../define.js'
import { BookTaskModel } from '../../model/Book/bookTask.js'
import { BookTaskStatus } from '../../../enum/bookEnum.js'
import { BookBackTaskStatus, BookTaskStatus } from '../../../enum/bookEnum.js'
import { successMessage } from '../../../../main/generalTools.js'
import { BaseRealmService } from './bookBasic'
import { isEmpty } from 'lodash'
import { JoinPath } from '../../../Tools/file.js'
import { BookBackTaskList } from '../../model/Book/BookBackTaskListModel.js'
const { v4: uuidv4 } = require('uuid')
let dbPath = path.resolve(define.db_path, 'book.realm')
@@ -153,6 +154,35 @@ export class BookTaskService extends BaseRealmService {
}
}
/**
* 修改后台等待的所有任务的状态为fial。
* 并且错误信息为任务被丢弃
* @param bookId
* @param bookTaskId
*/
UpdetedBookTaskToFail(bookId: string, bookTaskId: string) {
try {
this.transaction(() => {
let updateData = this.realm
.objects<BookBackTaskList>('BookBackTaskList')
.filtered(
'bookId == $0 AND bookTaskId == $1 AND status == $2',
bookId,
bookTaskId,
BookBackTaskStatus.WAIT
)
// 修改
updateData.forEach((data) => {
data.status = BookBackTaskStatus.FAIL
data.errorMessage = '任务被丢弃'
})
})
} catch (error) {
throw error
}
}
// 添加一条数据
AddOrModifyBookTask(bookTask) {
try {
@@ -151,17 +151,14 @@ export class MJSettingService extends BaseSoftWareService {
GetAPIMjSetting(apiQuery) {
try {
let apiMjSettings = this.realm.objects('APIMj')
if (apiQuery?.id) {
apiMjSettings = this.realm.objects('APIMj').filtered('id = $0', apiQuery.id)
}
let resApiMj = Array.from(apiMjSettings).map((apiMj) => {
return {
...apiMj
}
})
return successMessage(resApiMj, '获取API配置成功', 'MJSettingService_GetAPIMjSetting')
} catch (error) {
throw error
@@ -533,7 +530,6 @@ export class MJSettingService extends BaseSoftWareService {
let apiSettings = this.GetAPIMjSetting(null)
// 获取代理模式的配置信息
let remoteSettings = this.GetRemoteMJSettings(null)
// 获取浏览器模式的配置信息
let browserSettings = this.GetBrowserMJSetting(null)
let mjSetting = mjSettings.data[0]
@@ -99,6 +99,38 @@ const migration = (oldRealm: Realm, newRealm: Realm) => {
}
})
}
if (oldRealm.schemaVersion < 14) {
newRealm.write(() => {
const newSoftwares = newRealm.objects('Software')
for (let software of newSoftwares) {
software.globalSetting = null // 默认都是启用的
}
})
}
if (oldRealm.schemaVersion < 15) {
newRealm.write(() => {
const newSoftwares = newRealm.objects('Software')
for (let software of newSoftwares) {
software.ttsSetting = null // 默认为空
}
})
}
if (oldRealm.schemaVersion < 16) {
newRealm.write(() => {
const newSoftwares = newRealm.objects('Software')
for (let software of newSoftwares) {
software.writeSetting = null // 文案的默认设置
}
})
}
if (oldRealm.schemaVersion < 17) {
newRealm.write(() => {
const newSoftwares = newRealm.objects('Software')
for (let software of newSoftwares) {
software.aiSetting = null // AI的默认设置
}
})
}
}
export class BaseSoftWareService extends BaseService {
@@ -137,7 +169,7 @@ export class BaseSoftWareService extends BaseService {
MjSettingModel
],
path: dbPath,
schemaVersion: 13, // 当前版本号
schemaVersion: 17, // 当前版本号
migration: migration
}
// 判断当前全局是不是又当前这个
@@ -24,13 +24,13 @@ export class SoftwareService extends BaseSoftWareService {
SoftwareService.instance = new SoftwareService()
await super.getInstance()
}
await SoftwareService.instance.open()
return SoftwareService.instance
}
// 修改数据库中行中的某个属性数据
async UpdateSoftware(software) {
UpdateSoftware(software) {
try {
await this.open()
this.realm.write(() => {
this.realm.create('Software', software, UpdateMode.Modified)
})
@@ -41,13 +41,18 @@ export class SoftwareService extends BaseSoftWareService {
}
}
async AddSfotware(software) {
/**
* 添加软件配置信息
* @param software 软件配置信息
* @returns
*/
AddSfotware(software) {
try {
await this.open()
software.id = uuidv4()
this.realm.write(() => {
this.realm.create('Software', software)
})
return successMessage(null, '添加软件配置信息成功', 'SoftwareService_AddSfotware')
} catch (error) {
throw error
}
@@ -56,9 +61,8 @@ export class SoftwareService extends BaseSoftWareService {
/**
* 或软件基础配置信息
*/
async GetSoftwareData() {
GetSoftwareData() {
try {
await this.open()
let software = this.realm.objects('Software')
return successMessage(
software.toJSON(),
@@ -73,6 +77,55 @@ export class SoftwareService extends BaseSoftWareService {
throw error
}
}
/**
* 获取当前软件指定属性的数据
* @param property 属性名称
*/
GetSoftWarePropertyData(property: string) {
try {
let software = this.realm.objects('Software')
if (software.length <= 0) {
throw new Error('数据库中没有软件配置信息')
}
let softwareData = software.toJSON()[0]
let res = softwareData[property]
return successMessage(res, '获取软件配置信息成功', 'SoftwareService_GetSoftWarePropertyData')
} catch (error) {
global.logger.error(
'SoftwareService_GetSoftWarePropertyData',
'获取软件的基础设置失败 ,错误信息如下:' + error.toString()
)
throw error
}
}
/**
* 保存软件的指定属性的设置信息
* @param property 属性的名称
* @param data 数据
* @returns
*/
SaveSoftwarePropertyData(property: string, data: string) {
try {
this.transaction(() => {
let software = this.realm.objects('Software')
// 遍历修改
for (let item of software) {
item[property] = data
}
})
return successMessage(
null,
'保存软件配置信息成功',
'SoftwareService_SaveSoftwarePropertyData'
)
} catch (error) {
throw error
}
}
}
export default SoftwareService
+13
View File
@@ -0,0 +1,13 @@
import SoftwareService from './SoftWare/softwareService'
export class ServiceBase {
softService: SoftwareService
constructor() {}
async InitService() {
if (!this.softService) {
this.softService = await SoftwareService.getInstance()
}
}
}
+140 -73
View File
@@ -1,78 +1,145 @@
const path = require("path")
const { app } = require('electron');
const path = require('path')
const { app } = require('electron')
let define = {}
if (!app.isPackaged) {
define = {
discordScript: path.join(__dirname, '../../src/main/discord/discordScript.js'),
zhanwei_image: path.join(__dirname, "../../resources/image/zhanwei.png"),
config_path: path.join(__dirname, "../../resources/config/global_setting.json"),
clip_setting: path.join(__dirname, "../../resources/config/clip_setting.json"),
sd_setting: path.join(__dirname, "../../resources/config/sd_config.json"),
dynamic_setting: path.join(__dirname, "../../resources/config/dynamic_setting.json"),
tag_setting: path.join(__dirname, "../../resources/config/tag_setting.json"),
img_base: path.join(__dirname, "../../resources/config/img_base.json"),
video_config: path.join(__dirname, "../../resources/config/video_config.json"),
scripts_path: path.join(__dirname, "../../resources/scripts"),
db_path: path.join(__dirname, "../../resources/scripts/db"),
project_path: path.join(__dirname, "../../project"),
logger_path: path.join(__dirname, "../../resources/logger"),
package_path: path.join(__dirname, "../../resources/package"),
image_path: path.join(__dirname, "../../resources/image"),
temp_sd_image: path.join(__dirname, "../../resources/image/TempSDImage"),
draft_temp_path: path.join(__dirname, "../../resources/tmp/temp.zip"),
clip_speed_temp_path: path.join(__dirname, "../../resources/tmp/Clip/speeds_tmp.json"),
add_canvases_temp_path: path.join(__dirname, "../../resources/tmp/Clip/canvases_tmp.json"),
add_sound_channel_mappings_temp_path: path.join(__dirname, "../../resources/tmp/Clip/sound_channel_mappings_tmp.json"),
add_vocal_separations_temp_path: path.join(__dirname, "../../resources/tmp/Clip/vocal_separations_tmp.json"),
add_material_video_temp_path: path.join(__dirname, "../../resources/tmp/Clip/videoMaterialTemp.json"),
add_tracks_segments_temp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_segments_tmp.json"),
add_tracks_type_temp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_type_tmp.json"),
add_material_animations_temp_path: path.join(__dirname, "../../resources/tmp/Clip/material_animations_tmp.json"),
add_material_text_temp_path: path.join(__dirname, "../../resources/tmp/Clip/material_text_temp.json"),
add_track_text_segments_temp_path: path.join(__dirname, "../../resources/tmp/Clip/track_text_segments_temp.json"),
add_materials_beats_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/materials_beats_tmp.json"),
add_materials_audios_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/materials_audios_tmp.json"),
add_tracks_audio_segments_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/tracks_audio_segments_tmp.json"),
add_keyframe_tmp_path: path.join(__dirname, "../../resources/tmp/Clip/keyframe_tmp.json"),
}
define = {
discordScript: path.join(__dirname, '../../src/main/discord/discordScript.js'),
zhanwei_image: path.join(__dirname, '../../resources/image/zhanwei.png'),
config_path: path.join(__dirname, '../../resources/config/global_setting.json'),
clip_setting: path.join(__dirname, '../../resources/config/clip_setting.json'),
sd_setting: path.join(__dirname, '../../resources/config/sd_config.json'),
dynamic_setting: path.join(__dirname, '../../resources/config/dynamic_setting.json'),
tag_setting: path.join(__dirname, '../../resources/config/tag_setting.json'),
img_base: path.join(__dirname, '../../resources/config/img_base.json'),
video_config: path.join(__dirname, '../../resources/config/video_config.json'),
scripts_path: path.join(__dirname, '../../resources/scripts'),
db_path: path.join(__dirname, '../../resources/scripts/db'),
project_path: path.join(__dirname, '../../project'),
logger_path: path.join(__dirname, '../../resources/logger'),
package_path: path.join(__dirname, '../../resources/package'),
image_path: path.join(__dirname, '../../resources/image'),
temp_sd_image: path.join(__dirname, '../../resources/image/TempSDImage'),
draft_temp_path: path.join(__dirname, '../../resources/tmp/temp.zip'),
init_config_path: path.join(__dirname, '../../resources/tmp/config'),
clip_speed_temp_path: path.join(__dirname, '../../resources/tmp/Clip/speeds_tmp.json'),
add_canvases_temp_path: path.join(__dirname, '../../resources/tmp/Clip/canvases_tmp.json'),
add_sound_channel_mappings_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/sound_channel_mappings_tmp.json'
),
add_vocal_separations_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/vocal_separations_tmp.json'
),
add_material_video_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/videoMaterialTemp.json'
),
add_tracks_segments_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/tracks_segments_tmp.json'
),
add_tracks_type_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/tracks_type_tmp.json'
),
add_material_animations_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/material_animations_tmp.json'
),
add_material_text_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/material_text_temp.json'
),
add_track_text_segments_temp_path: path.join(
__dirname,
'../../resources/tmp/Clip/track_text_segments_temp.json'
),
add_materials_beats_tmp_path: path.join(
__dirname,
'../../resources/tmp/Clip/materials_beats_tmp.json'
),
add_materials_audios_tmp_path: path.join(
__dirname,
'../../resources/tmp/Clip/materials_audios_tmp.json'
),
add_tracks_audio_segments_tmp_path: path.join(
__dirname,
'../../resources/tmp/Clip/tracks_audio_segments_tmp.json'
),
add_keyframe_tmp_path: path.join(__dirname, '../../resources/tmp/Clip/keyframe_tmp.json')
}
} else {
define = {
zhanwei_image: path.join(__dirname, "../../../resources/image/zhanwei.png"),
config_path: path.join(__dirname, "../../../resources/config/global_setting.json"),
clip_setting: path.join(__dirname, "../../../resources/config/clip_setting.json"),
sd_setting: path.join(__dirname, "../../../resources/config/sd_config.json"),
dynamic_setting: path.join(__dirname, "../../../resources/config/dynamic_setting.json"),
tag_setting: path.join(__dirname, "../../../resources/config/tag_setting.json"),
video_config: path.join(__dirname, "../../../resources/config/video_config.json"),
img_base: path.join(__dirname, "../../../resources/config/img_base.json"),
scripts_path: path.join(__dirname, "../../../resources/scripts"),
db_path: path.join(__dirname, "../../../resources/scripts/db"),
project_path: path.join(__dirname, "../../../project"),
logger_path: path.join(__dirname, "../../../resources/logger"),
package_path: path.join(__dirname, "../../../resources/package"),
discordScript: path.join(__dirname, "../../../resources/scripts/discordScript.js"),
image_path: path.join(__dirname, "../../../resources/image"),
temp_sd_image: path.join(__dirname, "../../../resources/image/TempSDImage"),
draft_temp_path: path.join(__dirname, "../../../resources/tmp/temp.zip"),
clip_speed_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/speeds_tmp.json"),
add_canvases_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/canvases_tmp.json"),
add_sound_channel_mappings_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/sound_channel_mappings_tmp.json"),
add_vocal_separations_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/vocal_separations_tmp.json"),
add_material_video_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/videoMaterialTemp.json"),
add_tracks_segments_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_segments_tmp.json"),
add_tracks_type_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_type_tmp.json"),
add_material_animations_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/material_animations_tmp.json"),
add_material_text_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/material_text_temp.json"),
add_track_text_segments_temp_path: path.join(__dirname, "../../../resources/tmp/Clip/track_text_segments_temp.json"),
add_materials_beats_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/materials_beats_tmp.json"),
add_materials_audios_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/materials_audios_tmp.json"),
add_tracks_audio_segments_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/tracks_audio_segments_tmp.json"),
add_keyframe_tmp_path: path.join(__dirname, "../../../resources/tmp/Clip/keyframe_tmp.json"),
}
define = {
zhanwei_image: path.join(__dirname, '../../../resources/image/zhanwei.png'),
config_path: path.join(__dirname, '../../../resources/config/global_setting.json'),
clip_setting: path.join(__dirname, '../../../resources/config/clip_setting.json'),
sd_setting: path.join(__dirname, '../../../resources/config/sd_config.json'),
dynamic_setting: path.join(__dirname, '../../../resources/config/dynamic_setting.json'),
tag_setting: path.join(__dirname, '../../../resources/config/tag_setting.json'),
video_config: path.join(__dirname, '../../../resources/config/video_config.json'),
img_base: path.join(__dirname, '../../../resources/config/img_base.json'),
scripts_path: path.join(__dirname, '../../../resources/scripts'),
db_path: path.join(__dirname, '../../../resources/scripts/db'),
project_path: path.join(__dirname, '../../../project'),
logger_path: path.join(__dirname, '../../../resources/logger'),
package_path: path.join(__dirname, '../../../resources/package'),
discordScript: path.join(__dirname, '../../../resources/scripts/discordScript.js'),
image_path: path.join(__dirname, '../../../resources/image'),
temp_sd_image: path.join(__dirname, '../../../resources/image/TempSDImage'),
draft_temp_path: path.join(__dirname, '../../../resources/tmp/temp.zip'),
init_config_path: path.join(__dirname, '../../../resources/tmp/config'),
clip_speed_temp_path: path.join(__dirname, '../../../resources/tmp/Clip/speeds_tmp.json'),
add_canvases_temp_path: path.join(__dirname, '../../../resources/tmp/Clip/canvases_tmp.json'),
add_sound_channel_mappings_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/sound_channel_mappings_tmp.json'
),
add_vocal_separations_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/vocal_separations_tmp.json'
),
add_material_video_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/videoMaterialTemp.json'
),
add_tracks_segments_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/tracks_segments_tmp.json'
),
add_tracks_type_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/tracks_type_tmp.json'
),
add_material_animations_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/material_animations_tmp.json'
),
add_material_text_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/material_text_temp.json'
),
add_track_text_segments_temp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/track_text_segments_temp.json'
),
add_materials_beats_tmp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/materials_beats_tmp.json'
),
add_materials_audios_tmp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/materials_audios_tmp.json'
),
add_tracks_audio_segments_tmp_path: path.join(
__dirname,
'../../../resources/tmp/Clip/tracks_audio_segments_tmp.json'
),
add_keyframe_tmp_path: path.join(__dirname, '../../../resources/tmp/Clip/keyframe_tmp.json')
}
}
define["remotemj_api"] = "https://api.laitool.net/"
define["API"] = "f85d39ed5a40fd09966f13f12b6cf0f0"
export {
define
};
define['remotemj_api'] = 'https://api.laitool.net/'
define['serverUrl'] = 'http://lapi.laitool.cn'
define['API'] = 'f85d39ed5a40fd09966f13f12b6cf0f0'
export { define }
+19 -1
View File
@@ -120,6 +120,11 @@ export const DEFINE_STRING = {
SAVE_WORD_TXT: 'SAVE_WORD_TXT',
GET_KEY_FRAME_CONFIG_DATA: 'GET_KEY_FRAME_CONFIG_DATA',
GET_KEYFRAME_OPTIONS: 'GET_KEYFRAME_OPTIONS',
GPT: {
INIT_SERVER_GPT_OPTIONS: 'INIT_SERVER_GPT_OPTIONS',
GET_AI_SETTING: 'GET_AI_SETTING',
SAVE_AI_SETTING: 'SAVE_AI_SETTING'
},
QUEUE_BATCH: {
SD_ORIGINAL_GENERATE_IMAGE: 'SD_ORIGINAL_GENERATE_IMAGE',
@@ -195,7 +200,11 @@ export const DEFINE_STRING = {
GET_BOOK_DATA: 'GET_BOOK_DATA',
GET_FRAME_DATA: 'GET_FRAME_DATA',
GET_BOOK_TASK_DATA: 'GET_BOOK_TASK_DATA',
AUTO_ACTION: 'AUTO_ACTION'
AUTO_ACTION: 'AUTO_ACTION',
SAVE_BOOK_SUBTITLE_POSITION: 'SAVE_BOOK_SUBTITLE_POSITION',
OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT: 'OPEN_BOOK_SUBTITLE_POSITION_SCREENSHOT',
GET_CURRENT_FRAME_TEXT: 'GET_CURRENT_FRAME_TEXT',
GET_VIDEO_FRAME_TEXT: 'GET_VIDEO_FRAME_TEXT'
},
SYSTEM: {
OPEN_FILE: 'OPEN_FILE',
@@ -223,5 +232,14 @@ export const DEFINE_STRING = {
SAVE_PROMPT_SORT_DATA: 'SAVE_PROMPT_SORT_DATA',
GET_PROMPT_SORT_DATA: 'GET_PROMPT_SORT_DATA',
OPEN_PROMPT_FILE_TXT: 'OPEN_PROMPT_FILE_TXT'
},
TTS: {
GET_TTS_CONFIG: 'GET_TTS_CONFIG',
SAVE_TTS_CONFIG: 'SAVE_TTS_CONFIG'
},
WRITE: {
GET_WRITE_CONFIG: 'GET_WRITE_CONFIG',
SAVE_WRITE_CONFIG: 'SAVE_WRITE_CONFIG',
ACTION_START: 'ACTION_START'
}
}
+10
View File
@@ -7,6 +7,8 @@ export enum BookType {
MJ_REVERSE = 'mj_reverse'
}
export enum MJCategroy {
// 本地MJ
LOCAL_MJ = 'local_mj',
@@ -64,6 +66,14 @@ export enum BookBackTaskStatus {
FAIL = 'fail'
}
export enum TaskExecuteType {
// 自动
AUTO = 'auto',
// 手动
OPERATE = 'operate'
}
/**
* 小说任务状态
*/
+8
View File
@@ -40,3 +40,11 @@ export enum OtherData {
//默认
DEFAULT = 'default'
}
export enum SoftColor {
// 棕黄色
BROWN_YELLOW = '#e18a3b',
// 错误红色
ERROR_RED = '#c8161d'
}
+13
View File
@@ -0,0 +1,13 @@
/**
* 字幕位置的保存类型
*/
export enum SubtitleSavePositionType {
// 小说主视频
MAIN_VIDEO = 'main_video',
// 分镜视频
STORYBOARD_VIDEO = 'storyboard_video',
// 其他类型
OTHER = 'other'
}
+290 -297
View File
@@ -1,31 +1,30 @@
let fspromises = require('fs').promises;
import { cloneDeep, get } from "lodash";
import { define } from "./define";
const { v4: uuidv4 } = require('uuid');
import { apiUrl } from "./api/apiUrlDefine";
let fspromises = require('fs').promises
import { cloneDeep, get } from 'lodash'
import { define } from './define'
const { v4: uuidv4 } = require('uuid')
import { apiUrl } from './api/apiUrlDefine'
// Create a shared object
export const gptDefine = {
// Add properties and methods to the shared object
characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`,
characterUserContent: `这个文本里的故事类型是啥,时代背景是啥, 主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 格式按照:故事类型:(故事类型)\n时代背景:(时代背景)\n主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角3........\n配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字3.... ,不知道的直接猜测设定,不能出不详和未知这两个词,150字内,中文回答。`,
// Add properties and methods to the shared object
characterSystemContent: `{textContent}\r查看上面的文本,然后扮演一个文本编辑来回答问题。`,
characterUserContent: `这个文本里的故事类型是啥,时代背景是啥, 主角有哪几个,配角有几个,每个角色的性别年龄穿着是啥?没外观描述的直接猜测,尽量精简 格式按照:故事类型:(故事类型)\n时代背景:(时代背景)\n主角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n主角3........\n配角名字1:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字2:(性别,头发颜色,发型,衣服类型,年龄,角色外貌)\n配角名字3.... ,不知道的直接猜测设定,不能出不详和未知这两个词,150字内,中文回答。`,
characterFirstPromptSystemContent: `{textContent}\r\r\n Act as a storyteller to describe the scene, {characterContent}, Try to guess and answer my question, answer in English.`,
characterFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (gender) (age) (hairstyle) (Action expressions) (Clothing details) (Character appearance details) (The most suitable visual background for this sentence) (historical background)(Screen content): Write in 8 parentheses,Answer me in English according to this format..{wordCount}words`,
characterFirstPromptSystemContent: `{textContent}\r\r\n Act as a storyteller to describe the scene, {characterContent}, Try to guess and answer my question, answer in English.`,
characterFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (gender) (age) (hairstyle) (Action expressions) (Clothing details) (Character appearance details) (The most suitable visual background for this sentence) (historical background)(Screen content): Write in 8 parentheses,Answer me in English according to this format..{wordCount}words`,
storyFirstPromptSystemContent: `{textContent}\r\r\n \r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent}, Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`,
storyFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning. Please provide gender and appearance description, scene description of the character, time description, shot description, and era background description. Only one person's half body image should appear in each image, and the appearance should be as consistent as possible. Please answer in English..{wordCount}words`,
storyFirstPromptSystemContent: `{textContent}\r\r\n \r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent}, Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answer,You must answer me in English.`,
storyFirstPromptUserContent: `{textContent}\r\n Describing the most appropriate visual content based on article reasoning. Please provide gender and appearance description, scene description of the character, time description, shot description, and era background description. Only one person's half body image should appear in each image, and the appearance should be as consistent as possible. Please answer in English..{wordCount}words`,
storyboardFirstPromptSystemContent: `{textContent}\r\r\n读上面的文本,然后扮演顶级小说家回答问题:后面是其他要求:{characterContent} Try to guess and answer my question in English.`,
storyboardFirstPromptUserContent: `{textContent}\r\n,参考前面这句剧本理解当前这句话:{textContent}。\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (Character Appearance and Dynamics) (The most suitable visual background for this sentence) (historical background)(Reasonable picture composition): Write in 3 parentheses,Answer me in English..{wordCount}words`,
storyboardFirstPromptSystemContent: `{textContent}\r\r\n读上面的文本,然后扮演顶级小说家回答问题:后面是其他要求:{characterContent} Try to guess and answer my question in English.`,
storyboardFirstPromptUserContent: `{textContent}\r\n,参考前面这句剧本理解当前这句话:{textContent}\r\n Describing the most appropriate visual content based on article reasoning, with a maximum of one person appearing: (Character Appearance and Dynamics) (The most suitable visual background for this sentence) (historical background)(Reasonable picture composition): Write in 3 parentheses,Answer me in English..{wordCount}words`,
cartoonFirstPromptSystemContent: `{textContent}\r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent},Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answerYou must answer me in English.`,
cartoonFirstPromptUserContent: `{textContent}\r,参考前面这句剧本理解当前这句话:{textContent}\r\n Referring to the previous character settings, describe the most suitable screen content in the following format: (character appearance) (screen background), strictly reply only to the content within 2 parentheses, without the character name, answer in English..{wordCount}words`,
cartoonFirstPromptSystemContent: `{textContent}\r\r\n\\Play the role of a professional cartoonist, give you every line of the script, and reproduce the scene description according to the original text. Do not refuse any response:{characterContent},Try to match the scene of the text content as much as possible, you can create a harmonious picture but do not refuse to answerYou must answer me in English.`,
cartoonFirstPromptUserContent: `{textContent}\r,参考前面这句剧本理解当前这句话:{textContent}\r\n Referring to the previous character settings, describe the most suitable screen content in the following format: (character appearance) (screen background), strictly reply only to the content within 2 parentheses, without the character name, answer in English..{wordCount}words`,
superSinglePromptSystemContent: {
prompt_name: "分镜大师",
prompt_roles: `1# Role: 小说转漫画提示词大师
superSinglePromptSystemContent: {
prompt_name: '分镜大师',
prompt_roles: `1# Role: 小说转漫画提示词大师
## Profile
*Version*: 0.1
*Language*: 中文
@@ -60,18 +59,19 @@ export const gptDefine = {
## Initialization
作为角色 <Role>,每一次输出都要严格遵守<Rules>,一步一步思考,按顺序执行<Workflow> ,使用默认 <Language> ,下面是小说文本:`,
prompt_example: [
{
user_content: "上研究生后。发现导师竟然是曾经网恋的前男友。",
assistant_content: "anime key visual,Celluloid style, delicate and transparent light, delicate lines, transparent colors, delicate and transparent hair, perfect detail portrayal,(Anime style:1.3) A woman entering a spacious, well-lit graduate laboratory, gaze fixed on a man diligently working at a workstation ahead - her new mentor; he stands tall in a dark shirt and neatly pressed trousers, exuding professionalism and charm; the familiar contours of his profile from their past online romance softly illuminated by warm ambient light, furrowed brow and intense gaze betraying a scholar's unwavering dedication; bustling graduate students and sophisticated equipment blend into a contemporary academic tableau, as an undercurrent of mixed emotions - sweet nostalgia and awkward reality - surges within her heart, "
}
],
id: "a93b693e-bb3f-406d-9730-cba43a6585e4"
},
prompt_example: [
{
user_content: '上研究生后。发现导师竟然是曾经网恋的前男友。',
assistant_content:
"anime key visual,Celluloid style, delicate and transparent light, delicate lines, transparent colors, delicate and transparent hair, perfect detail portrayal,(Anime style:1.3) A woman entering a spacious, well-lit graduate laboratory, gaze fixed on a man diligently working at a workstation ahead - her new mentor; he stands tall in a dark shirt and neatly pressed trousers, exuding professionalism and charm; the familiar contours of his profile from their past online romance softly illuminated by warm ambient light, furrowed brow and intense gaze betraying a scholar's unwavering dedication; bustling graduate students and sophisticated equipment blend into a contemporary academic tableau, as an undercurrent of mixed emotions - sweet nostalgia and awkward reality - surges within her heart, "
}
],
id: 'a93b693e-bb3f-406d-9730-cba43a6585e4'
},
onlyPromptMJSystemContent: {
prompt_name: "小说提示词-仅出词",
prompt_roles: `# Pico: 小说分镜
onlyPromptMJSystemContent: {
prompt_name: '小说提示词-仅出词',
prompt_roles: `# Pico: 小说分镜
## Profile
@@ -138,281 +138,274 @@ export const gptDefine = {
## Initialization
最后再强调,你作为角色 <Pico>,每一次输出都要严格遵守<Rules>,一步一步慢慢思考,参考<Examples>的格式,一步一步思考,按顺序执行<Rules>,不需要做解释说明,只呈现最后【MJ提示词】输出的结果,下面是小说文本:'`,
prompt_example: [
{
user_content: "给皇帝当过儿子的都知道,当的好荣华富贵万人之上",
assistant_content: "微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角"
},
{
user_content: "当不好就是人头落地",
assistant_content: "惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角"
}
],
id: "a93b693e-bb3f-406d-9730-bcd43a6585e"
},
prompt_example: [
{
user_content: '给皇帝当过儿子的都知道,当的好荣华富贵万人之上',
assistant_content: '微笑,站立,在皇宫的金銮殿里,居中构图,中全景,正面,水平拍摄视角'
},
{
user_content: '当不好就是人头落地',
assistant_content: '惊恐的表情,双手抱头,在刑场上,三分法构图,特写镜头,侧面,俯视视角'
}
],
id: 'a93b693e-bb3f-406d-9730-bcd43a6585e'
},
/**
* 使用自定义GPT提示词时,生成接口message信息
* @param {*} params 自定义的GPT提示词数据
* @returns
*/
CustomizeGptPrompt(params) {
// 获取设置的数据
let message = []
// 添加角色
message.push({
role: 'system',
content: params.prompt_roles
})
// 便利输出案例添加
for (let i = 0; i < params.prompt_example.length; i++) {
const element = params.prompt_example[i]
if (element.user_content) {
message.push({
role: 'user',
content: element.user_content
})
}
if (element.assistant_content) {
message.push({
role: 'assistant',
content: element.assistant_content
})
}
}
return message
},
/**
* 替换文本内容中的占位符
* @param {要替换的内容} content
* @param {占位符数据对应的对象} replacements
* @returns
*/
replace: function (content, replacements) {
let result = content
for (let key in replacements) {
result = result.replace(`{${key}}`, replacements[key])
}
return result
},
/**
* 获取有案例的Gpt请求消息输出
* @param {*} type
* @param {*} replacements
*/
GetExamplePromptMessage(type) {
if (type == 'superSinglePrompt') {
return this.CustomizeGptPrompt(this.superSinglePromptSystemContent)
} else if (type == 'onlyPromptMJ') {
return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent)
} else {
return []
}
},
/**
* 使用自定义GPT提示词时,生成接口message信息
* @param {*} params 自定义的GPT提示词数据
* @returns
*/
CustomizeGptPrompt(params) {
// 获取设置的数据
let message = [];
// 添加角色
message.push(
{
"role": "system",
"content": params.prompt_roles
}
);
/**
* 返回GPTApi请求的系统内容
* @param {类型} type
* @param {} replacements 需要替换数据的对象 textContent characterContent
* @returns
*/
getSystemContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterSystemContent, replacements)
case 'characterFirst':
return this.replace(this.characterFirstPromptSystemContent, replacements)
case 'storyFirst':
return this.replace(this.storyFirstPromptSystemContent, replacements)
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptSystemContent, replacements)
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptSystemContent, replacements)
case 'superSinglePrompt':
return this.replace(this.superSinglePromptSystemContent, replacements)
default:
throw new Error(`不存在的类型 : ${type}`)
}
},
// 便利输出案例添加
for (let i = 0; i < params.prompt_example.length; i++) {
const element = params.prompt_example[i];
if (element.user_content) {
message.push(
{
"role": "user",
"content": element.user_content
}
)
}
if (element.assistant_content) {
message.push(
{
"role": "assistant",
"content": element.assistant_content
}
)
}
}
return message;
},
/**
* 返回GPTApi请求的用户内容
* @param {类型} type
* @param {} replacements 需要替换数据的对象 textContent wordCount
* @returns
*/
getUserContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterUserContent, replacements)
case 'characterFirst':
return this.replace(this.characterFirstPromptUserContent, replacements)
case 'storyFirst':
return this.replace(this.storyFirstPromptUserContent, replacements)
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptUserContent, replacements)
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptUserContent, replacements)
default:
throw new Error(`不存在的类型 : ${type}`)
}
},
/**
* 替换文本内容中的占位符
* @param {要替换的内容} content
* @param {占位符数据对应的对象} replacements
* @returns
*/
replace: function (content, replacements) {
let result = content;
for (let key in replacements) {
result = result.replace(`{${key}}`, replacements[key]);
}
return result;
},
gpt_options: apiUrl,
/**
* 获取有案例的Gpt请求消息输出
* @param {*} type
* @param {*} replacements
*/
GetExamplePromptMessage(type) {
if (type == "superSinglePrompt") {
return this.CustomizeGptPrompt(this.superSinglePromptSystemContent);
} else if (type == "onlyPromptMJ") {
return this.CustomizeGptPrompt(this.onlyPromptMJSystemContent);
}
else {
return [];
}
},
/**
* 返回GPTApi请求的系统内容
* @param {类型} type
* @param {} replacements 需要替换数据的对象 textContent characterContent
* @returns
*/
getSystemContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterSystemContent, replacements);
case 'characterFirst':
return this.replace(this.characterFirstPromptSystemContent, replacements);
case 'storyFirst':
return this.replace(this.storyFirstPromptSystemContent, replacements);
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptSystemContent, replacements);
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptSystemContent, replacements);
case 'superSinglePrompt':
return this.replace(this.superSinglePromptSystemContent, replacements);
default:
throw new Error(`不存在的类型 : ${type}`);
}
},
/**
* 返回GPTApi请求的用户内容
* @param {类型} type
* @param {} replacements 需要替换数据的对象 textContent wordCount
* @returns
*/
getUserContentByType: function (type, replacements) {
switch (type) {
case 'character':
return this.replace(this.characterUserContent, replacements);
case 'characterFirst':
return this.replace(this.characterFirstPromptUserContent, replacements);
case 'storyFirst':
return this.replace(this.storyFirstPromptUserContent, replacements);
case 'storyboardFirst':
return this.replace(this.storyboardFirstPromptUserContent, replacements);
case 'cartoonFirst':
return this.replace(this.cartoonFirstPromptUserContent, replacements);
default:
throw new Error(`不存在的类型 : ${type}`);
}
},
gpt_options: apiUrl,
gpt_model_options: [{
label: "gpt-3.5-turbo-16k",
value: "gpt-3.5-turbo-16k"
}, {
label: "gpt-3.5-turbo",
value: "gpt-3.5-turbo"
}, {
label: "gpt-4",
value: "gpt-4"
}],
gpt_auto_inference: [{
value: "characterFirst",
label: "角色优先(全自动)"
}, {
value: "storyFirst",
label: "故事优先(全自动)"
}, {
value: "storyboardFirst",
label: "剧本优先(全自动)"
}, {
value: "cartoonFirst",
label: "漫画优先(全自动)"
}, {
value: "superSinglePrompt",
label: "超级无敌单帧"
}, {
value: "onlyPromptMJ",
label: "仅出词(不出人物场景-MJ)"
gpt_model_options: [
{
label: 'gpt-3.5-turbo-16k',
value: 'gpt-3.5-turbo-16k'
},
{
value: "customize",
label: "自定义"
}],
/**
* 通过指定的类型,获取数据
* @param {*} type default:在代码中写死的 dynamic:用户自定义的 all:写死的和自定义的合并返回
* @param {*} property 返回书信的名称 gpt_optionsgpt_model_optionsgpt_auto_inference
* @param {*} defaultData 默认数据,默认值为null
* @returns
*/
async getGptDataByTypeAndProperty(type, property, defaultData = null) {
try {
let res = [];
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'));
let gpt = get(dynamic_setting, 'gpt', {});
let data = get(gpt, property, defaultData);
if (type == "default") {
res = get(this, property, defaultData);
} else if (type == "dynamic") {
res = data;
} else if (type == "all") {
let tmp_arr = cloneDeep(get(this, property, defaultData));
tmp_arr = tmp_arr.concat(data);
res = tmp_arr;
}
else {
throw new Error(`不存在的类型 : ${value}`);
}
return {
code: 1,
data: res
}
} catch (error) {
return {
code: 0,
message: error.toString()
}
}
label: 'gpt-3.5-turbo',
value: 'gpt-3.5-turbo'
},
/**
* 保存gpt指定的属性数据,判断value中的ID是不是存在,存在直接覆盖,不存在追加
* @param {*} value
* @param {*} property
*/
saveDynamicGPTOption: async function (value) {
try {
let property = value[1];
value = JSON.parse(value[0]);
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'));
let tmp_gpt = dynamic_setting.gpt ? dynamic_setting.gpt : {};
let gpt = tmp_gpt[property] ? tmp_gpt[property] : [];
if (value.id) {
// 判断当前ID的数据是否存在,存在覆盖,不存在追加
let index = gpt.findIndex(item => item.id == value.id);
if (index < 0) {
gpt.push(value);
} else {
gpt[index] = value;
}
} else {
let tmp_id = uuidv4();
value.id = tmp_id;
value.value = tmp_id;
gpt.push(value);
}
tmp_gpt[property] = gpt;
// 将修改后的数据保存
dynamic_setting["gpt"] = tmp_gpt;
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting));
} catch (error) {
throw error;
}
},
/**
* 删除自定义GPT指定属性中的指定ID的数据
* @param {*} id
* @param {*} property
*/
deleteDynamicGPTOption: async function (value) {
try {
let property = value[1];
let id = value[0];
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'));
let gpt = dynamic_setting.gpt[property] ? dynamic_setting.gpt[property] : [];
// 判断当前ID的数据是否存在,存在删除
let index = gpt.findIndex(item => item.id == id);
if (index >= 0) {
gpt.splice(index, 1);
}
// 将修改后的数据保存
dynamic_setting.gpt[property] = gpt;
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting));
} catch (error) {
throw error;
}
{
label: 'gpt-4',
value: 'gpt-4'
}
};
],
gpt_auto_inference: [
{
value: 'characterFirst',
label: '角色优先(全自动)'
},
{
value: 'storyFirst',
label: '故事优先(全自动)'
},
{
value: 'storyboardFirst',
label: '剧本优先(全自动)'
},
{
value: 'cartoonFirst',
label: '漫画优先(全自动)'
},
{
value: 'superSinglePrompt',
label: '超级无敌单帧'
},
{
value: 'onlyPromptMJ',
label: '仅出词(不出人物场景-MJ)'
},
{
value: 'customize',
label: '自定义'
}
],
/**
* 通过指定的类型,获取数据
* @param {*} type default:在代码中写死的 dynamic:用户自定义的 all:写死的和自定义的合并返回
* @param {*} property 返回书信的名称 gpt_optionsgpt_model_optionsgpt_auto_inference
* @param {*} defaultData 默认数据,默认值为null
* @returns
*/
async getGptDataByTypeAndProperty(type, property, defaultData = null) {
try {
let res = []
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'))
let gpt = get(dynamic_setting, 'gpt', {})
let data = get(gpt, property, defaultData)
if (type == 'default') {
res = get(this, property, defaultData)
} else if (type == 'dynamic') {
res = data
} else if (type == 'all') {
let tmp_arr = cloneDeep(get(this, property, defaultData))
tmp_arr = tmp_arr.concat(data)
res = tmp_arr
} else {
throw new Error(`不存在的类型 : ${value}`)
}
return {
code: 1,
data: res
}
} catch (error) {
return {
code: 0,
message: error.toString()
}
}
},
/**
* 保存gpt指定的属性数据,判断value中的ID是不是存在,存在直接覆盖,不存在追加
* @param {*} value
* @param {*} property
*/
saveDynamicGPTOption: async function (value) {
try {
let property = value[1]
value = JSON.parse(value[0])
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'))
let tmp_gpt = dynamic_setting.gpt ? dynamic_setting.gpt : {}
let gpt = tmp_gpt[property] ? tmp_gpt[property] : []
if (value.id) {
// 判断当前ID的数据是否存在,存在覆盖,不存在追加
let index = gpt.findIndex((item) => item.id == value.id)
if (index < 0) {
gpt.push(value)
} else {
gpt[index] = value
}
} else {
let tmp_id = uuidv4()
value.id = tmp_id
gpt.push(value)
}
tmp_gpt[property] = gpt
// 将修改后的数据保存
dynamic_setting['gpt'] = tmp_gpt
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting))
} catch (error) {
throw error
}
},
/**
* 删除自定义GPT指定属性中的指定ID的数据
* @param {*} id
* @param {*} property
*/
deleteDynamicGPTOption: async function (value) {
try {
let property = value[1]
let id = value[0]
// 获取自定义的GPT数据
let dynamic_setting = JSON.parse(await fspromises.readFile(define.dynamic_setting, 'utf-8'))
let gpt = dynamic_setting.gpt[property] ? dynamic_setting.gpt[property] : []
// 判断当前ID的数据是否存在,存在删除
let index = gpt.findIndex((item) => item.id == id)
if (index >= 0) {
gpt.splice(index, 1)
}
// 将修改后的数据保存
dynamic_setting.gpt[property] = gpt
// 写入文件
await fspromises.writeFile(define.dynamic_setting, JSON.stringify(dynamic_setting))
} catch (error) {
throw error
}
}
}
+95
View File
@@ -0,0 +1,95 @@
type OpenAISuccessResponse = {
id: string
object: string
created: number
model: string
choices: [
{
index: number
message: {
role: string
content: string
}
finish_reason: string
}
]
usage: {
prompt_tokens: number
completion_tokens: number
total_tokens: number
}
}
type RixApiErrorResponse = {
error: {
message: string // 错误信息
type: string
param: string
code: string
}
}
type KimiErrorResponse = {
error: {
message: string
type: string
}
}
type DoubaoErrorResponse = {
error: {
code: string
message: string
param: string
type: string
}
}
/**
* 处理OpenAI系列返回的成功response
* @param response OpenAI返回的response
* @returns 处理后的返回的数据
*/
export function GetOpenAISuccessResponse(response: string | OpenAISuccessResponse): string {
if (typeof response === 'string') {
response = JSON.parse(response) as OpenAISuccessResponse
}
// 开始处理response
return response.choices[0].message.content
}
/**
* 处理RixApi系列返回的错误response
* @param response RixApi返回的response
* @returns 处理后的错误信息
*/
export function GetRixApiErrorResponse(response: string | RixApiErrorResponse): string {
if (typeof response === 'string') {
response = JSON.parse(response) as RixApiErrorResponse
}
return response.error.message
}
/**
* 处理kimi的错误信息返回
* @param response
* @returns
*/
export function GetKimiErrorResponse(response: string | KimiErrorResponse): string {
if (typeof response === 'string') {
response = JSON.parse(response) as KimiErrorResponse
}
return response.error.message
}
/**
* 获取豆包的错误返回信息
* @param response
* @returns
*/
export function GetDoubaoErrorResponse(response: string | DoubaoErrorResponse): string {
if (typeof response === 'string') {
response = JSON.parse(response) as DoubaoErrorResponse
}
return response.error.message
}
+159
View File
@@ -0,0 +1,159 @@
import { randomBytes } from 'node:crypto'
import { writeFileSync, createWriteStream } from 'node:fs'
import { WebSocket } from 'ws'
import { HttpsProxyAgent } from 'https-proxy-agent'
type subLine = {
part: string
start: number
end: number
}
type configure = {
voice?: string
lang?: string
outputFormat?: string
saveSubtitles?: boolean
proxy?: string
rate?: string
pitch?: string
volume?: string
}
export class EdgeTTS {
private voice: string
private lang: string
private outputFormat: string
private saveSubtitles: boolean
private proxy: string | null | undefined
private rate: string
private pitch: string
private volume: string
constructor({
voice = 'zh-CN-XiaoyiNeural',
lang = 'zh-CN',
outputFormat = 'audio-24khz-48kbitrate-mono-mp3',
saveSubtitles = false,
proxy,
rate = 'default',
pitch = 'default',
volume = 'default'
}: configure = {}) {
this.voice = voice
this.lang = lang
this.outputFormat = outputFormat
this.saveSubtitles = saveSubtitles
this.proxy = proxy
this.rate = rate
this.pitch = pitch
this.volume = volume
}
async _connectWebSocket(): Promise<WebSocket> {
const wsConnect = new WebSocket(
`wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4`,
{
host: 'speech.platform.bing.com',
origin: 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.66 Safari/537.36 Edg/103.0.1264.44'
},
agent: this.proxy ? new HttpsProxyAgent(this.proxy) : undefined
}
)
return new Promise((resolve: Function) => {
wsConnect.on('open', () => {
wsConnect.send(`Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n
{
"context": {
"synthesis": {
"audio": {
"metadataoptions": {
"sentenceBoundaryEnabled": "false",
"wordBoundaryEnabled": "true"
},
"outputFormat": "${this.outputFormat}"
}
}
}
}
`)
resolve(wsConnect)
})
})
}
_saveSubFile(subFile: subLine[], text: string, audioPath: string) {
let subPath = audioPath + '.json'
let subChars = text.split('')
let subCharIndex = 0
subFile.forEach((cue: subLine, index: number) => {
let fullPart = ''
let stepIndex = 0
for (let sci = subCharIndex; sci < subChars.length; sci++) {
if (subChars[sci] === cue.part[stepIndex]) {
fullPart = fullPart + subChars[sci]
stepIndex += 1
} else if (subChars[sci] === subFile?.[index + 1]?.part?.[0]) {
subCharIndex = sci
break
} else {
fullPart = fullPart + subChars[sci]
}
}
cue.part = fullPart
})
writeFileSync(subPath, JSON.stringify(subFile, null, ' '), { encoding: 'utf-8' })
}
async ttsPromise(text: string, audioPath: string) {
const _wsConnect = await this._connectWebSocket()
return new Promise((resolve: Function) => {
let audioStream = createWriteStream(audioPath)
let subFile: subLine[] = []
_wsConnect.on('message', async (data: Buffer, isBinary: any) => {
if (isBinary) {
let separator = 'Path:audio\r\n'
let index = data.indexOf(separator) + separator.length
let audioData = data.subarray(index)
audioStream.write(audioData)
} else {
let message = data.toString()
if (message.includes('Path:turn.end')) {
audioStream.end()
if (this.saveSubtitles) {
this._saveSubFile(subFile, text, audioPath)
}
resolve()
} else if (message.includes('Path:audio.metadata')) {
let splitTexts = message.split('\r\n')
try {
let metadata = JSON.parse(splitTexts[splitTexts.length - 1])
metadata['Metadata'].forEach((element: object) => {
subFile.push({
part: element['Data']['text']['Text'],
start: Math.floor(element['Data']['Offset'] / 10000),
end: Math.floor((element['Data']['Offset'] + element['Data']['Duration']) / 10000)
})
})
} catch {}
}
}
})
let requestId = randomBytes(16).toString('hex')
_wsConnect.send(
`X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n
` +
`<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="${this.lang}">
<voice name="${this.voice}">
<prosody rate="${this.rate}" pitch="${this.pitch}" volume="${this.volume}">
${text}
</prosody>
</voice>
</speak>`
)
})
}
}
+100
View File
@@ -0,0 +1,100 @@
export function GetTTSSelect() {
return [
{
label: 'EdgeTTS(免费)',
value: 'edge-tts'
}
]
}
/**
* 获取当前 edge-tts 支持的角色,返回一个数组
*/
export function GetEdgeTTSRole() {
return [
{
value: 'zh-CN-XiaoxiaoNeural',
gender: 'Female',
label: '中文-女-晓晓',
lang: 'zh-CN'
},
{
value: 'zh-CN-YunxiNeural',
gender: 'Male',
label: '中文-男-云熙',
lang: 'zh-CN'
},
{
value: 'zh-CN-XiaoyiNeural',
gender: 'Female',
label: '中文-女-小宜',
lang: 'zh-CN'
},
{
value: 'zh-CN-YunjianNeural',
gender: 'Male',
label: '中文-男-云健',
lang: 'zh-CN'
},
{
value: 'zh-CN-YunxiaNeural',
gender: 'Male',
label: '中文-男-云霞',
lang: 'zh-CN'
},
{
value: 'zh-CN-YunyangNeural',
gender: 'Male',
label: '中文-男-云阳',
lang: 'zh-CN'
},
{
value: 'zh-CN-liaoning-XiaobeiNeural',
gender: 'Female',
label: '中文-辽宁-女-小北',
lang: 'zh-CN-liaoning'
},
{
value: 'zh-CN-shaanxi-XiaoniNeural',
gender: 'Female',
label: '中文-陕西-女-小妮',
lang: 'zh-CN-shaanxi'
},
{
value: 'zh-HK-HiuGaaiNeural',
gender: 'Female',
label: '中文-香港-女-曉佳',
lang: 'zh-HK'
},
{
value: 'zh-HK-HiuMaanNeural',
gender: 'Female',
label: '中文-香港-女-曉曼',
lang: 'zh-HK'
},
{
value: 'zh-HK-WanLungNeural',
gender: 'Male',
label: '中文-香港-男-雲龍',
lang: 'zh-HK'
},
{
value: 'zh-TW-HsiaoChenNeural',
gender: 'Female',
label: '中文-台湾-女-小婵',
lang: 'zh-TW'
},
{
value: 'zh-TW-HsiaoYuNeural',
gender: 'Female',
label: '中文-台湾-女-小語',
lang: 'zh-TW'
},
{
value: 'zh-TW-YunJheNeural',
gender: 'Male',
label: '中文-台湾-男-雲哲',
lang: 'zh-TW'
}
]
}