1. 新增 图/文转视频 菜单界面,专注实现图/文转视频(目前只集成了 MJ VIDEO) 1. 全新的界面排列,小说列表和批次任务更加分明 2. 添加转视频进度,在主界面即可看到转视频的比例 3. 单独的界面去处理图转视频,避免表格数据过多繁琐 4. 新增分页显示,界面加载更快,也可切换不分页,需要更多的事件等待加载 5. 单独操作面板,参数修改处理更加清晰,支持多种模式显示,右侧固定或抽屉模式 6. 批量设置转视频配置,可以批量修改分类 7. 友好的选择视频界面 2. 重写 软件导出剪映,修复若干草稿导出问题 1. 修复导出剪映文案和图片对齐会有些许对不上,时长越长越明显 2. 修复导出草稿关键帧部分问题 3. 导出的文案通过分镜自动导入,不再需要手动选择SRT 3. 美化 生成草稿界面 弹窗,优化部分逻辑 1. 删除选择SRT文件,SRT根据聚合推文中导入的SRT自动生成草稿 2. 只需选择配音文件即可,配音文件和导入的SRT请自行对应 3. 背景音乐不在内部设置,自行选择文件夹或者是MP3、WAV文件 4. 背景音乐选择文件夹则读取文件夹,随机获取一个 5. 背景音乐选择指定的音乐文件则使用选择的
338 lines
11 KiB
TypeScript
338 lines
11 KiB
TypeScript
import Realm, { UpdateMode } from 'realm'
|
|
import { BookModel } from '../../model/Book/book.js'
|
|
import path from 'path'
|
|
import { define } from '../../../define'
|
|
import { BookImageCategory, BookTaskStatus, BookType } from '../../../enum/bookEnum.js'
|
|
import { successMessage } from '../../../../main/Public/generalTools'
|
|
import { CheckFolderExistsOrCreate, CopyFileOrFolder } from '../../../Tools/file'
|
|
const { v4: uuidv4 } = require('uuid')
|
|
import { BaseRealmService } from './bookBasic.js'
|
|
import { isEmpty } from 'lodash'
|
|
import { FfmpegOptions } from '../../../../main/Service/ffmpegOptions.js'
|
|
import { version } from '../../../../../package.json'
|
|
import { Book } from '../../../../model/book/book.js'
|
|
import { GeneralResponse } from '../../../../model/generalResponse.js'
|
|
import { ImageToVideoModels } from '@/define/enum/video.js'
|
|
|
|
export class BookService extends BaseRealmService {
|
|
static instance: BookService | null = null
|
|
realm: Realm
|
|
|
|
private constructor() {
|
|
super()
|
|
}
|
|
|
|
/**
|
|
* 获取当前实例对象,为空则创建一个新的
|
|
* @returns
|
|
*/
|
|
public static async getInstance() {
|
|
if (BookService.instance === null) {
|
|
BookService.instance = new BookService()
|
|
await super.getInstance()
|
|
}
|
|
await BookService.instance.open()
|
|
return BookService.instance
|
|
}
|
|
|
|
/**
|
|
* 获取小说信息,没有找到返回null
|
|
* @param bookId
|
|
*/
|
|
GetBookDataById(bookId: string): Book.SelectBook | null {
|
|
try {
|
|
if (isEmpty(bookId)) {
|
|
throw new Error('获取小说信息失败,缺少小说ID')
|
|
}
|
|
let books = this.realm.objects<BookModel>('Book').filtered('id = $0', bookId)
|
|
if (books.length <= 0) {
|
|
return null
|
|
} else {
|
|
// 对返回的数据进行处理
|
|
let resBooks = Array.from(books).map((book) => {
|
|
// 这里可以直接操作普通对象
|
|
let bookObj = {
|
|
...book,
|
|
bookFolderPath: path.resolve(
|
|
define.project_path,
|
|
book.bookFolderPath.replace(/\\/g, '/')
|
|
),
|
|
oldVideoPath: book.oldVideoPath
|
|
? path.resolve(define.project_path, book.oldVideoPath.replace(/\\/g, '/'))
|
|
: '',
|
|
imageFolder: book.imageFolder
|
|
? path.resolve(define.project_path, book.imageFolder.replace(/\\/g, '/'))
|
|
: ''
|
|
}
|
|
return bookObj
|
|
})
|
|
return resBooks[0]
|
|
}
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取小说信息,通过参数查询
|
|
* @returns
|
|
*/
|
|
GetBookData(bookQuery) {
|
|
try {
|
|
// 获取所有的小说数据,并进行时间降序排序
|
|
let books = this.realm.objects<BookModel>('Book')
|
|
let book_length = books.length
|
|
|
|
// 开始开始筛选
|
|
if (bookQuery.bookId) {
|
|
// 查询对应的小说ID的数据
|
|
books = books.filtered('id = $0', bookQuery.bookId)
|
|
}
|
|
|
|
books = books.sorted('updateTime', true)
|
|
// 判断是不是有page和pageSize,有的话对查询返回的信息做分页
|
|
if (bookQuery.page && bookQuery.pageSize) {
|
|
books = books.slice(
|
|
(bookQuery.page - 1) * bookQuery.pageSize,
|
|
bookQuery.page * bookQuery.pageSize
|
|
) as unknown as Realm.Results<BookModel>
|
|
}
|
|
if (books.length <= 0) {
|
|
return successMessage(
|
|
{
|
|
res_book: [],
|
|
book_length: 0
|
|
},
|
|
'没有数据',
|
|
'ReverseBook_GetBookData'
|
|
)
|
|
}
|
|
|
|
// 将realm对象数组转换为普通对象数组
|
|
let res_book = Array.from(books).map((book) => {
|
|
// 这里可以直接操作普通对象
|
|
let bookObj = {
|
|
...book,
|
|
bookFolderPath: path.resolve(
|
|
define.project_path,
|
|
book.bookFolderPath.replace(/\\/g, '/')
|
|
),
|
|
oldVideoPath: book.oldVideoPath
|
|
? path.resolve(define.project_path, book.oldVideoPath.replace(/\\/g, '/'))
|
|
: '',
|
|
imageFolder: book.imageFolder
|
|
? path.resolve(define.project_path, book.imageFolder.replace(/\\/g, '/'))
|
|
: '',
|
|
imageStyle: book.imageStyle ? Array.from(book.imageStyle) : [],
|
|
customizeImageStyle: book.customizeImageStyle ? Array.from(book.imageStyle) : [],
|
|
} as Book.SelectBook
|
|
return bookObj
|
|
})
|
|
|
|
return successMessage(
|
|
{
|
|
res_book,
|
|
book_length
|
|
},
|
|
'获取成功',
|
|
'ReverseBook_GetBookData'
|
|
)
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 新增或者是修小说数据
|
|
* @param {*} book 小说信息
|
|
* @returns
|
|
*/
|
|
async AddOrModifyBook(book) {
|
|
try {
|
|
if (book == null) {
|
|
throw new Error('小说数据为空,无法修改')
|
|
}
|
|
|
|
// 当小说的类型是反推的时候,必须传入视频
|
|
if (book.type == BookType.MJ_REVERSE || book.type == BookType.SD_REVERSE) {
|
|
// 判断视频是否存在
|
|
if (book.oldVideoPath == null || book.oldVideoPath == '') {
|
|
throw new Error('反推必须传入视频')
|
|
}
|
|
}
|
|
|
|
if (book.id == null) {
|
|
// 新增
|
|
// 判断指定的名字在数据库中是否存在
|
|
let books = this.realm.objects('Book').filtered('name = $0', book.name)
|
|
if (books.length > 0) {
|
|
throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`)
|
|
}
|
|
console.log(this)
|
|
// 新增数据
|
|
book.id = uuidv4()
|
|
book.createTime = new Date()
|
|
book.updateTime = new Date()
|
|
// 检查传入的视频文件是不是存在
|
|
// 获取当前最大的no
|
|
let maxNo = this.realm.objects('Book').max('no')
|
|
book.no = maxNo == null ? 1 : Number(maxNo) + 1
|
|
// 拼接项目文件夹
|
|
book.bookFolderPath = book.id
|
|
book.imageFolder = `${book.id}/tmp`
|
|
let bookFolderPath = path.resolve(define.project_path, book.id)
|
|
let imageFolder = path.resolve(define.project_path, `${book.id}/tmp`)
|
|
let oldVideoPath = path.resolve(define.project_path, `${book.id}/data/${book.id}.mp4`)
|
|
let bookTaskImageFolder = path.resolve(imageFolder, 'output_00001')
|
|
|
|
// 将视频拷贝一个到项目文件下面
|
|
if (book.oldVideoPath) {
|
|
await CopyFileOrFolder(book.oldVideoPath, oldVideoPath)
|
|
}
|
|
|
|
// 创建对应的文件夹
|
|
await CheckFolderExistsOrCreate(bookFolderPath)
|
|
await CheckFolderExistsOrCreate(imageFolder)
|
|
await CheckFolderExistsOrCreate(bookTaskImageFolder) // 创建默认的任务文件夹
|
|
// 修改数据
|
|
book.oldVideoPath = path.relative(define.project_path, oldVideoPath)
|
|
if (book.type == BookType.ORIGINAL) {
|
|
book.oldVideoPath = null
|
|
}
|
|
|
|
let imageCategory = BookImageCategory.MJ
|
|
if (book.type == BookType.SD_REVERSE) {
|
|
imageCategory = BookImageCategory.SD
|
|
} else if (book.type == BookType.MJ_REVERSE) {
|
|
imageCategory = BookImageCategory.MJ
|
|
} else if (book.type == BookType.ORIGINAL) {
|
|
imageCategory = global.config.defaultImageMode ?? BookImageCategory.MJ
|
|
} else {
|
|
throw new Error('未知的小说类型')
|
|
}
|
|
|
|
let videoCategory = ImageToVideoModels.MJ_VIDEO;
|
|
|
|
this.realm.write(() => {
|
|
book.version = version
|
|
this.realm.create('Book', book)
|
|
|
|
// 添加一个任务
|
|
let bookTask = {
|
|
id: uuidv4(),
|
|
bookId: book.id,
|
|
no: 1,
|
|
name: 'output_00001',
|
|
generateVideoPath: null,
|
|
srtPath: null,
|
|
audioPath: null,
|
|
imageFolder: path.relative(define.project_path, bookTaskImageFolder), // 获取文件输出对于项目的相对路径
|
|
styleList: [],
|
|
prefix: null,
|
|
status: BookTaskStatus.WAIT,
|
|
errorMsg: null,
|
|
isAuto: false,
|
|
updateTime: new Date(),
|
|
createTime: new Date(),
|
|
version: version,
|
|
imageCategory: imageCategory,
|
|
videoCategory: videoCategory,
|
|
openVideoGenerate: false
|
|
}
|
|
|
|
// 添加任务
|
|
this.realm.create('BookTask', bookTask)
|
|
})
|
|
|
|
// 保存成功,返回数据,但是要做处理
|
|
book.bookFolderPath = bookFolderPath
|
|
book.imageFolder = imageFolder
|
|
book.oldVideoPath = oldVideoPath
|
|
|
|
return successMessage(book, '新增成功', 'BookBasic_AddOrModifyBook')
|
|
} else {
|
|
// 修改
|
|
// 判断指定的名字在数据库中是否存在(不算自己)
|
|
let books = this.realm
|
|
.objects('Book')
|
|
.filtered('name = $0 AND id != $1', book.name, book.id)
|
|
if (books.length > 0) {
|
|
throw new Error(`小说名字 ${book.name} 已经存在,请更换小说名字`)
|
|
}
|
|
// 两个文件夹地址不能改,删除两个属性
|
|
delete book.bookFolderPath
|
|
delete book.imageFolder
|
|
// 修改数据
|
|
book.updateTime = new Date()
|
|
this.transaction(() => {
|
|
this.realm.create('Book', book, UpdateMode.Modified)
|
|
})
|
|
|
|
// 保存成功,返回数据,但是要做处理
|
|
return successMessage(null, '修改成功', 'BookBasic_AddOrModifyBook')
|
|
}
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 修改小说数据
|
|
* @param bookId 小说的ID
|
|
* @param bookData 要修改的小说数据
|
|
*/
|
|
UpdateBookData(bookId: string, bookData: Book.SelectBook): Book.SelectBook {
|
|
try {
|
|
if (bookId == null) {
|
|
throw new Error('修改小说数据失败,缺少小说ID')
|
|
}
|
|
if (bookData == null) {
|
|
throw new Error('修改小说数据失败,缺少小说数据')
|
|
}
|
|
|
|
// 检查小说ID对应的数据是不是存在
|
|
let bookRes = this.GetBookDataById(bookId)
|
|
if (bookRes == 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 == null) {
|
|
throw new Error('获取修改后的小说数据失败,小说ID对应的数据不存在')
|
|
}
|
|
// return successMessage(bookRes, '修改小说数据成功', 'ReverseBook_UpdateBookData')
|
|
return bookRes;
|
|
} catch (error) {
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除指定的小说任务
|
|
* @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
|
|
}
|
|
}
|
|
}
|