1. 移除软件包自带的本地 whisper(需单独安装)
2. 重构版本底层依赖,移除外部依赖
3. 修复 首页 暗黑模式不兼容的问题
4. 修复 SD 合并提示词报错
This commit is contained in:
2024-10-20 23:19:22 +08:00
parent efa8d3b2a2
commit f4d042f699
38 changed files with 2749 additions and 1568 deletions
+4 -4
View File
@@ -8,7 +8,7 @@ import { DEFINE_STRING } from "../../../define/define_string";
import path from 'path'
import { BasicReverse } from './basicReverse'
import { BookTaskDetailService } from '../../../define/db/service/Book/bookTaskDetailService'
import { TaskScheduler } from "../task/taskScheduler"
import { LogScheduler } from "../task/logScheduler"
import { Book } from '../../../model/book'
import { LoggerStatus, OtherData, ResponseMessageType } from '../../../define/enum/softwareEnum'
import { GeneralResponse } from '../../../model/generalResponse'
@@ -28,7 +28,7 @@ import { isEmpty } from 'lodash'
*/
export class ReverseBook {
basicReverse: BasicReverse
taskScheduler: TaskScheduler
logScheduler: LogScheduler
mjOpt: MJOpt = new MJOpt()
sdOpt: SDOpt = new SDOpt()
tagDefine: TagDefine
@@ -42,7 +42,7 @@ export class ReverseBook {
this.tagDefine = new TagDefine()
this.subtitle = new Subtitle()
this.watermark = new Watermark()
this.taskScheduler = new TaskScheduler()
this.logScheduler = new LogScheduler()
this.bookServiceBasic = new BookServiceBasic()
this.bookBasic = new BookBasic()
}
@@ -301,7 +301,7 @@ export class ReverseBook {
await this.bookServiceBasic.AddBookBackTask(book.id, task_type, TaskExecuteType.AUTO, bookTaskDetail.bookTaskId, bookTaskDetail.id, DEFINE_STRING.BOOK.REVERSE_PROMPT_RETURN
);
// 添加返回日志
await this.taskScheduler.AddLogToDB(book.id, book.type, `添加 ${task_type} 反推任务成功`, bookTaskDetail.bookTaskId, LoggerStatus.SUCCESS)
await this.logScheduler.AddLogToDB(book.id, book.type, `添加 ${task_type} 反推任务成功`, bookTaskDetail.bookTaskId, LoggerStatus.SUCCESS)
}
} catch (error) {
throw error
+24 -24
View File
@@ -5,7 +5,7 @@ const { exec } = require('child_process')
const execAsync = util.promisify(exec)
import { define } from '../../../define/define'
import { BookService } from '../../../define/db/service/Book/bookService'
import { TaskScheduler } from '../task/taskScheduler'
import { LogScheduler } from '../task/logScheduler'
import { LoggerStatus, LoggerType, OtherData } from '../../../define/enum/softwareEnum'
import { errorMessage, successMessage } from '../../Public/generalTools'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../../define/Tools/file'
@@ -35,11 +35,11 @@ export class BasicReverse {
bookTaskDetailService: BookTaskDetailService
bookBackTaskListService: BookBackTaskListService
taskScheduler: TaskScheduler
logScheduler: LogScheduler
ffmpegOptions: FfmpegOptions
constructor() {
this.taskScheduler = new TaskScheduler()
this.logScheduler = new LogScheduler()
this.ffmpegOptions = new FfmpegOptions()
}
@@ -109,7 +109,7 @@ export class BasicReverse {
if (taskRes.code == 0) {
throw new Error(taskRes.message)
}
this.taskScheduler.AddLogToDB(
this.logScheduler.AddLogToDB(
bookId,
book.type,
`添加分镜任务成功`,
@@ -149,7 +149,7 @@ export class BasicReverse {
let sensitivity = 30
// 开始之前,推送日志
let log_content = `开始进行分镜操作,视频地址:${oldVideoPath},敏感度:${sensitivity},正在调用程序进行处理`
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
log_content,
@@ -170,7 +170,7 @@ export class BasicReverse {
// 有错误输出
if (output.stderr != '') {
let error_msg = `分镜成功,但有警告提示:${output.stderr}`
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
error_msg,
@@ -187,7 +187,7 @@ export class BasicReverse {
BookTaskStatus.STORYBOARD_FAIL,
error_message
)
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
error_message,
@@ -205,7 +205,7 @@ export class BasicReverse {
BookTaskStatus.STORYBOARD_FAIL,
error_msg
)
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
error_msg,
@@ -237,7 +237,7 @@ export class BasicReverse {
this.bookTaskService.UpdateBookTaskStatus(bookTaskId, BookTaskStatus.STORYBOARD_DONE)
// 分镜成功,推送日志
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`分镜成功,分镜数据如下:${frameJsonData}`,
@@ -310,7 +310,7 @@ export class BasicReverse {
if (bookTaskDetail.data.length <= 0) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,开始调用分镜方法`,
@@ -339,7 +339,7 @@ export class BasicReverse {
this.bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT)
// 有分镜数据,开始处理
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`成功获取分镜数据,开始添加裁剪视频任务`,
@@ -363,7 +363,7 @@ export class BasicReverse {
}
}
// 添加日志
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`添加视频裁剪任务成功`,
@@ -424,7 +424,7 @@ export class BasicReverse {
// 小改小说批次的状态
this.bookTaskService.UpdateBookTaskStatus(bookTaskDetail.bookTaskId, BookTaskStatus.SPLIT_DONE)
// 结束,分镜完毕,推送日志,返回成功
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookTaskDetail.bookId,
book.type,
`${bookTaskDetail.name}_视频裁剪完成`,
@@ -490,7 +490,7 @@ export class BasicReverse {
})
}
if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) {
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`没有找到对应的小说批次任务数据,请检查bookId是否正确`,
@@ -508,7 +508,7 @@ export class BasicReverse {
bookTaskId: bookTask.id
})
if (bookTaskDetails.data.length <= 0) {
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`没有找到对应的小说批次任务数据,请检查bookId是否正确,或者手动执行`,
@@ -531,7 +531,7 @@ export class BasicReverse {
throw new Error(taskRes.message)
}
// 添加日志
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`添加音频 ${taskRes.data.name} 分离任务成功`,
@@ -588,7 +588,7 @@ export class BasicReverse {
})
// 推送成功消息
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
task.bookId,
book.type,
`${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`,
@@ -656,7 +656,7 @@ export class BasicReverse {
throw new Error(taskRes.message)
}
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`添加 ${taskRes.data.name} 抽帧任务成功`,
@@ -701,7 +701,7 @@ export class BasicReverse {
})
// 推送成功消息
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name}抽帧成功,输出地址:${outputFramePath}`,
@@ -797,7 +797,7 @@ export class BasicReverse {
}
}
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`添加提取字幕任务成功`,
@@ -821,9 +821,9 @@ export class BasicReverse {
// 判断是不是用本地的wisper服务
if (isWisper) {
// 开始调用wisper
// 使用异步的方法调用一个python程序,然后写入到指定的json文件中k
// 使用异步的方法调用一个python程序,然后写入到指定的json文件中
let out_dir = path.dirname(bookTaskDetail.videoPath)
// #TODO -t 被移除
let command = `"${path.join(define.scripts_path, 'Lai.exe')}" "-t" "${out_dir}" "${bookTaskDetail.audioPath
}" "${bookTaskDetail.name}"`
const output = await execAsync(command, {
@@ -833,7 +833,7 @@ export class BasicReverse {
// 有错误输出
if (output.stderr != '') {
let error_msg = `提取字幕成功,但有警告提示:${output.stderr}`
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
book.id,
book.type,
error_msg,
@@ -856,7 +856,7 @@ export class BasicReverse {
})
// 提取字幕成功,推送日志
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name} 提取字幕成功`,
+5 -6
View File
@@ -7,7 +7,7 @@ import { FfmpegOptions } from "../ffmpegOptions";
import { CheckFileOrDirExist, CopyFileOrFolder, DeleteFolderAllFile } from "../../../define/Tools/file";
import fs from 'fs';
import { Book } from "../../../model/book";
import { TaskScheduler } from '../task/taskScheduler';
import { LogScheduler } from '../task/logScheduler';
import { BookBasic } from "./BooKBasic";
import { LoggerStatus, OtherData } from "../../../define/enum/softwareEnum";
import { BasicReverse } from "./basicReverse";
@@ -15,18 +15,17 @@ import { BasicReverse } from "./basicReverse";
export class BookFrame {
bookServiceBasic: BookServiceBasic
ffmpegOptions: FfmpegOptions
taskScheduler: TaskScheduler
logScheduler: LogScheduler
basicReverse: BasicReverse
bookBasic: BookBasic
constructor() {
this.bookServiceBasic = new BookServiceBasic();
this.ffmpegOptions = new FfmpegOptions();
this.taskScheduler = new TaskScheduler()
this.logScheduler = new LogScheduler()
this.bookBasic = new BookBasic()
this.basicReverse = new BasicReverse()
}
/**
* 替换指定分镜的视频当前帧
* @param bookTaskDetailId 指定的小说分镜ID
@@ -138,7 +137,7 @@ export class BookFrame {
})
} catch (error) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,请先进行分镜计算`,
@@ -164,7 +163,7 @@ export class BookFrame {
}
let res = await this.basicReverse.FrameDataToCutVideoData(item, shortClipData[i]);
}
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
bookId,
book.type,
"所有的视频裁剪完成,开始抽帧",
+6
View File
@@ -173,6 +173,12 @@ export class BookVideo {
}
/**
* 添加剪映草稿
* @param id
* @param operateBookType
* @returns
*/
async AddJianyingDraft(id: string, operateBookType: OperateBookType): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
await this.InitService();
+4 -4
View File
@@ -13,7 +13,7 @@ import { MJSetting } from "../../../model/Setting/mjSetting";
import { GeneralResponse } from "../../../model/generalResponse"
import { LoggerStatus, ResponseMessageType } from "../../../define/enum/softwareEnum";
import { ImageStyle } from "../Book/imageStyle";
import { TaskScheduler } from "../task/taskScheduler";
import { LogScheduler } from "../task/logScheduler";
import { Tools } from "../../../main/tools"
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { PresetService } from "../presetService";
@@ -28,14 +28,14 @@ export class MJOpt {
mjApi: MJApi;
mjSetting: MJSetting.MjSetting
imageStyle: ImageStyle;
taskScheduler: TaskScheduler;
logScheduler: LogScheduler;
tools: Tools;
bookServiceBasic: BookServiceBasic
presetService: PresetService
softWareServiceBasic: SoftWareServiceBasic
constructor() {
this.imageStyle = new ImageStyle()
this.taskScheduler = new TaskScheduler()
this.logScheduler = new LogScheduler()
this.tools = new Tools()
this.bookServiceBasic = new BookServiceBasic();
this.presetService = new PresetService()
@@ -528,7 +528,7 @@ export class MJOpt {
let taskRes = await this.bookServiceBasic.AddBookBackTask(element.bookId, BookBackTaskType.MJ_IMAGE, TaskExecuteType.AUTO, element.bookTaskId, element.id, responseMessageName
);
// 添加返回日志
await this.taskScheduler.AddLogToDB(element.bookId, BookBackTaskType.MJ_IMAGE, `添加 ${element.name} MJ生成任务成功`, element.bookTaskId, LoggerStatus.SUCCESS)
await this.logScheduler.AddLogToDB(element.bookId, BookBackTaskType.MJ_IMAGE, `添加 ${element.name} MJ生成任务成功`, element.bookTaskId, LoggerStatus.SUCCESS)
}
// 全部完毕
return successMessage(null, "MJ添加生成图片任务成功", "MJOpt_AddGenerateImageTask")
+9 -5
View File
@@ -48,8 +48,10 @@ export class SDOpt {
const id = ids[i];
let scene = await this.presetService.GetScenePresetDetailById(id)
if (scene.code == 1) {
// 这边开始拼接
result += scene.data.prompt + ', '
if (scene.data) {
// 这边开始拼接
result += scene.data.prompt + ', '
}
} else {
throw new Error(scene.message)
}
@@ -68,9 +70,11 @@ export class SDOpt {
const id = ids[i];
let character = await this.presetService.GetCharacterPresetDetailById(id)
if (character.code == 1) {
result += character.data.prompt + ', '
if (character.data.lora && character.data.lora != '无' && character.data.loraWeight) {
result += `, <lora:${character.data.lora}:${character.data.lora_weight}>`
if (character.data) {
result += character.data.prompt + ', '
if (character.data.lora && character.data.lora != '无' && character.data.loraWeight) {
result += `, <lora:${character.data.lora}:${character.data.lora_weight}>`
}
}
} else {
throw new Error(character.message)
@@ -69,7 +69,7 @@ class BookServiceBasic {
GetBookTaskDetailDataById = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailDataById(bookTaskDetailId);
GetBookTaskDetailData = async (condition: Book.QueryBookTaskDetailCondition, returnEmpty: boolean = false) => await this.bookTaskDetailServiceBasic.GetBookTaskDetailData(condition, returnEmpty);
UpdateBookTaskDetail = async (bookTaskDetailId: string, data: Book.SelectBookTaskDetail) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, data);
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status);
UpdateBookTaskStatus = async (bookTaskDetailId: string, status: BookTaskStatus,errorMsg? : string) => await this.bookTaskDetailServiceBasic.UpdateBookTaskStatus(bookTaskDetailId, status,errorMsg);
DeleteBookTaskDetailReversePromptById = async (bookTaskDetailId: string, reversePromptId: string) => await this.bookTaskDetailServiceBasic.DeleteBookTaskDetailReversePromptById(bookTaskDetailId);
DeleteBoookTaskDetailGenerateImage = async (bookTaskDetailId: string) => await this.bookTaskDetailServiceBasic.DeleteBoookTaskDetailGenerateImage(bookTaskDetailId);
UpdateBookTaskDetailReversePrompt = async (bookTaskDetailId: string, reversePromptId: string, data: Book.ReversePrompt) => await this.bookTaskDetailServiceBasic.UpdateBookTaskDetailReversePrompt(bookTaskDetailId, reversePromptId, data);
+76 -28
View File
@@ -16,7 +16,7 @@ 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 '../task/taskScheduler'
import { LogScheduler } from '../task/logScheduler'
import { SubtitleModel } from '../../../model/subtitle'
import { BookTaskStatus, OperateBookType } from '../../../define/enum/bookEnum'
import axios from 'axios'
@@ -24,8 +24,9 @@ import { GptService } from '../GPT/gpt'
import FormData from 'form-data'
import { RetryWithBackoff } from '../../../define/Tools/common'
import { DEFINE_STRING } from '../../../define/define_string'
import { DraftTimeLineJson } from '../jianying/jianyingService'
const util = require('util')
const { exec } = require('child_process')
const { spawn, exec } = require('child_process')
const execAsync = util.promisify(exec)
const fspromises = fs.promises
@@ -36,12 +37,12 @@ const fspromises = fs.promises
export class Subtitle {
ffmpegOptions: FfmpegOptions
bookServiceBasic: BookServiceBasic
taskScheduler: TaskScheduler
logScheduler: LogScheduler
gptService: GptService
constructor() {
this.bookServiceBasic = new BookServiceBasic()
this.taskScheduler = new TaskScheduler()
this.logScheduler = new LogScheduler()
this.ffmpegOptions = new FfmpegOptions()
this.gptService = new GptService()
}
@@ -74,28 +75,6 @@ export class Subtitle {
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 获取的文案内容
@@ -123,7 +102,7 @@ export class Subtitle {
}, DEFINE_STRING.BOOK.GET_COPYWRITING_RETURN)
// 添加日志
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name} 识别文案成功`,
@@ -562,7 +541,7 @@ export class Subtitle {
})
// 推送成功消息
await this.taskScheduler.AddLogToDB(
await this.logScheduler.AddLogToDB(
book.id,
book.type,
`${bookTaskDetail.name}分离音频成功,输出地址:${audioPath}`,
@@ -667,4 +646,73 @@ export class Subtitle {
}
}
//#endregion
//#region 本地Whisper识别字幕相关操作
async GetTextByLocalWhisper(frameTimeList: DraftTimeLineJson[], outDir: string, mp3Dir: string, localWhisperPath?: string): Promise<void> {
try {
let localWhisperPathExePath = localWhisperPath
if (isEmpty(localWhisperPathExePath)) {
localWhisperPathExePath = path.join(define.scripts_path, 'localWhisper/local_whisper.exe')
}
return new Promise((resolve, reject) => {
let child = spawn(
localWhisperPathExePath,
['-ts', outDir, mp3Dir],
{ encoding: 'utf-8' }
);
child.on('error', (error) => {
console.log('error=', error)
this.logScheduler.ReturnLogger(errorMessage("使用localWhisper识别字幕失败输出,失败信息如下:" + error.message))
reject(new Error(error.message))
})
child.stdout.on('data', (data) => {
console.log(data.toString())
this.logScheduler.ReturnLogger(successMessage(data.toString(), "使用localWhisper识别字幕输出"))
})
child.stderr.on('data', (data) => {
console.log('stderr=', data.toString())
this.logScheduler.ReturnLogger(errorMessage("使用localWhisper识别字幕失败输出,失败信息如下:stderr = " + data.toString()))
reject(new Error(data.toString()))
})
child.on('close', async (data) => {
console.log('data=', data.toString())
this.logScheduler.ReturnLogger(successMessage(data.toString(), "使用localWhisper识别字幕完成"))
let textPath = path.join(outDir, '文案.txt')
if (!await CheckFileOrDirExist(textPath)) {
throw new Error('没有找到识别输出的文案文件')
}
let text = await fspromises.readFile(textPath, 'utf-8')
let textLines = text.split(/\r?\n/)
let lastLine = textLines[textLines.length - 1]
// 丢掉最后一行
textLines = textLines.slice(0, -1)
if (textLines.length != frameTimeList.length) {
throw new Error('分镜和识别文案数量不一致')
}
// 保存文案
for (let i = 0; i < textLines.length; i++) {
const element = textLines[i];
frameTimeList[i].text = element
}
// 写出
await fspromises.writeFile(path.join(global.config.project_path, '文案.txt'), textLines.join('\n'), 'utf-8')
if (data == 0) {
this.logScheduler.ReturnLogger(successMessage(null, "使用localWhisper识别字幕完成"))
} else {
this.logScheduler.ReturnLogger(errorMessage("使用localWhisper识别字幕失败,失败信息请查看日志"))
}
resolve();
})
})
} catch (error) {
this.logScheduler.ReturnLogger(errorMessage("使用localWhisper识别字幕失败,失败信息如下:" + error.message))
throw error
}
}
//#endregion
}
+2 -2
View File
@@ -11,7 +11,7 @@ import fs from 'fs'
import { CheckFileOrDirExist } from "../../../define/Tools/file";
import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic";
import { Subtitle } from "./subtitle";
import { TaskScheduler } from "../task/taskScheduler";
import { LogScheduler } from "../task/logScheduler";
import { BookTaskStatus, BookType, OperateBookType } from "../../../define/enum/bookEnum";
import { Book } from "../../../model/book";
import { TimeStringToMilliseconds } from "../../../define/Tools/time";
@@ -20,7 +20,7 @@ export class SubtitleService {
softWareServiceBasic: SoftWareServiceBasic
bookServiceBasic: BookServiceBasic
subtitle: Subtitle
taskScheduler: TaskScheduler
logScheduler: LogScheduler
constructor() {
this.softWareServiceBasic = new SoftWareServiceBasic();
this.bookServiceBasic = new BookServiceBasic();
+1 -3
View File
@@ -98,8 +98,6 @@ export class FfmpegOptions {
/**
* FFmpeg裁剪视频,将一个视频将裁剪指定的时间内的片段
* @param {*} book 小说对象类
* @param {*} bookTask 小说批次任务对象类
* @param {*} startTime 开始时间
* @param {*} endTime 结束时间
* @param {*} videoPath 视频地址
@@ -225,7 +223,7 @@ export class FfmpegOptions {
// 判断分镜是不是和数据库中的数据匹配的上
let res = await new Promise((resolve, reject) => {
Ffmpeg(videoPath)
.inputOptions([`-ss ${MillisecondsToTimeString(frameTime)}`])
.inputOptions([`-ss ${MillisecondsToTimeString(Math.ceil(frameTime))}`])
.output(outFramePath)
.frames(1)
.on('end', async function () {
@@ -0,0 +1,216 @@
import path from 'path';
import { CheckFileOrDirExist, DeleteFolderAllFile } from '../../../define/Tools/file';
import fs from "fs";
import { ValidateJson } from '../../../define/Tools/validate';
import { FfmpegOptions } from '../ffmpegOptions';
/**
* 存放剪映草稿的时间轴数据
*/
export type DraftTimeLineJson = {
name: string;
startTime: number;
endTime: number;
durationTime: number;
middleTime: number;
videoPath: string;
text: string;
framePath: string;
subVideoPath?: string;
audioPath?: string;
}
/**
* 剪映的一些服务
*/
class JianyingService {
draftTimeLine: DraftTimeLineJson[];
draftJson: any;
ffmpegOptions: FfmpegOptions
constructor() {
this.draftTimeLine = [];
this.ffmpegOptions = new FfmpegOptions();
}
/**
* 获取剪映草稿的关键帧和文本
* @param draftDir 草稿目录
* @param projectDir 项目目录
* @param packagePath 包路径
*/
async GetDraftFrameAndText(draftDir: string, projectDir: string, packagePath: string) {
try {
// 获取草稿文件路径
let draftJsonPath = path.resolve(draftDir, "draft_content.json");
if (!await CheckFileOrDirExist(draftJsonPath)) {
throw new Error("剪映草稿文件不存在,请先检查");
}
// 读取草稿文件内容
let draftJsonString = await fs.promises.readFile(draftJsonPath, "utf-8");
if (!ValidateJson(draftJsonString)) {
throw new Error("剪映草稿文件格式错误,请检查");
}
this.draftJson = JSON.parse(draftJsonString);
// 检查输出文件夹是否存在
let projectTmp = path.resolve(projectDir, "tmp");
if (await CheckFileOrDirExist(projectTmp)) {
// 删除文件夹
await DeleteFolderAllFile(projectTmp);
}
// 创建输出文件夹
let projectInput = path.resolve(projectTmp, "input_crop");
console.log("projectInput", projectInput);
// 获取剪映的轨道,并且判断是否包含一个video轨道和一个text轨道
let draftTracks = this.draftJson.tracks;
if (!draftTracks) {
throw new Error("剪映草稿文件格式错误,没有轨道,请检查");
}
let hasVideo = draftTracks.some((track: any) => track.type === "video");
let hasText = draftTracks.some((track: any) => track.type === "text");
if (!(this.draftJson.tracks && hasVideo && hasText)) {
throw new Error("没有检测到剪映草稿文件中的video和text轨道,请检查");
}
// 获取视频节点
let videoNodes = draftTracks.filter((track: any) => track.type === "video")[0].segments;
this.GetVideoTime(videoNodes);
// 获取文本节点
let textNodes = draftTracks.filter((track: any) => track.type === "text")[0].segments;
this.GetTextTime(textNodes);
console.log("场景数:", this.draftTimeLine.length);
// 将数据写入到文件中
let txtData = this.draftTimeLine.map((item) => item.text);
let txtPath = path.resolve(projectDir, "文案.txt");
await fs.promises.writeFile(txtPath, txtData.join("\n"), "utf-8");
// 开始抽取关键帧
await this.GetDraftFrame(projectInput);
// 将数据写入到json文件中
let jsonPath = path.resolve(projectDir, "draftFrameData.json");
await fs.promises.writeFile(jsonPath, JSON.stringify(this.draftTimeLine), "utf-8");
console.log("GetDraftFrameAndText", jsonPath);
} catch (error) {
throw error;
}
}
/**
* 在节点数组中查找指定类型和值的节点
* @param nodes 节点数组
* @param type 节点类型
* @param value 节点值
* @returns 找到的节点
* @throws 如果没有找到对应的节点则抛出错误
*/
private FindNode(nodes: any[], type: string, value: any) {
let res = nodes.filter((node: any) => node[type] === value);
if (res.length === 0) {
throw new Error("没有找到对应的节点");
}
return res[0];
}
/**
* 判断文本是否在时间轴内
* @param draftTimeObject 时间轴对象
* @param textStartTime 文本开始时间
* @param textEndTile 文本结束时间
* @returns 如果文本在时间轴内则返回 true,否则返回 false
*/
private TextIsInTimeLine(draftTimeObject: DraftTimeLineJson, textStartTime: number, textEndTile: number) {
return textStartTime >= draftTimeObject.startTime && textEndTile <= draftTimeObject.endTime;
}
/**
* 抽取剪映草稿的关键帧
* @param projectInput 项目输入目录
*/
private async GetDraftFrame(projectInput: string): Promise<void> {
for (let i = 0; i < this.draftTimeLine.length; i++) {
const element = this.draftTimeLine[i];
let outImagePath = path.resolve(projectInput, (i + 1).toString().padStart(5, "0") + ".png");
// 使用 ffmpeg 抽取关键帧
let frameRes = await this.ffmpegOptions.FfmpegGetFrame(element.middleTime / 1000, element.videoPath, outImagePath);
if (frameRes.code == 0) {
throw new Error(frameRes.message);
}
this.draftTimeLine[i].framePath = outImagePath;
console.log("已经抽取第", i + 1, "帧");
}
}
/**
* 获取文本时间
* @param textNodes 文本节点数组
*/
private GetTextTime(textNodes: any[]): void {
let tempText = "";
let count = 0;
for (let i = 0; i < textNodes.length; i++) {
const element = textNodes[i];
let textStartTime = element.target_timerange.start;
let textEndTime = textStartTime + element.target_timerange.duration;
let textMaterialId = element.material_id;
let textMaterialNode = this.FindNode(this.draftJson.materials.texts, "id", textMaterialId);
let textContent = textMaterialNode.content;
let textContentJson = JSON.parse(textContent);
let text = textContentJson.text + "。";
// 不在视频的时间轴内,丢弃
if (count > this.draftTimeLine.length - 1) {
break;
}
if (this.TextIsInTimeLine(this.draftTimeLine[count], textStartTime, textEndTime)) {
tempText += text;
if (i == textNodes.length - 1) {
this.draftTimeLine[count].text = tempText;
}
} else {
this.draftTimeLine[count].text = tempText;
tempText = text;
count += 1;
}
}
}
/**
* 获取视频时间
* @param videoNodes 视频节点数组
*/
private GetVideoTime(videoNodes: any[]): void {
for (let i = 0; i < videoNodes.length; i++) {
const element = videoNodes[i];
let startTime = element.target_timerange.start;
let endTime = startTime + element.target_timerange.duration;
let durationTime = element.target_timerange.duration;
let middleTime = startTime + ((endTime - startTime) / 2);
let videoId = element.material_id;
let materialNode = this.FindNode(this.draftJson.materials.videos, "id", videoId);
let videoPath = materialNode.path;
this.draftTimeLine.push({
name: (i + 1).toString().padStart(5, "0"),
startTime,
endTime,
durationTime,
middleTime,
videoPath,
text: "",
framePath: undefined
})
}
}
}
export default JianyingService;
@@ -4,7 +4,7 @@ import { LoggerStatus, OtherData } from '../../../define/enum/softwareEnum'
import { successMessage, errorMessage } from '../../Public/generalTools'
import { GeneralResponse } from '../../../model/generalResponse'
export class TaskScheduler {
export class LogScheduler {
constructor() { }
/**
*
@@ -20,7 +20,7 @@ export class TaskScheduler {
type: string,
content: string,
bookTaskId: string,
status = LoggerStatus.DOING
status: LoggerStatus = LoggerStatus.DOING
): Promise<GeneralResponse.ErrorItem | GeneralResponse.SuccessItem> {
try {
let log = {
@@ -38,7 +38,16 @@ export class TaskScheduler {
return res
} catch (error) {
return errorMessage(error.message, 'TaskScheduler_AddLogToDB')
return errorMessage(error.message, 'LogScheduler_AddLogToDB')
}
}
/**
*
* @param {*} log
* @returns
*/
ReturnLogger(log: GeneralResponse.ErrorItem | GeneralResponse.SuccessItem) {
global.newWindow[0].win.webContents.send(DEFINE_STRING.SYSTEM.RETURN_LOGGER, log)
}
}
@@ -0,0 +1,333 @@
import path from 'path';
import { CheckFileOrDirExist, DeleteFolderAllFile } from '../../../define/Tools/file';
import fs from 'fs';
import { LogScheduler } from '../task/logScheduler';
import { successMessage } from '../../Public/generalTools';
import { define } from '../../../define/define';
import util from 'util';
import { exec } from 'child_process';
import { ValidateJson } from '../../../define/Tools/validate';
import { DraftTimeLineJson } from '../jianying/jianyingService';
const execAsync = util.promisify(exec)
import { FfmpegOptions } from '../ffmpegOptions';
import { TimeStringToMilliseconds } from '../../../define/Tools/time';
import { isEmpty } from 'lodash';
import { Subtitle } from '../Subtitle/subtitle';
type VideoHandleShortVideoTimeLine = {
name: string,
startTime: number;
endTime: number;
videoPath: string,
duration: number
}
class VideoHandle {
logScheduler: LogScheduler;
ffmpegOptions: FfmpegOptions;
subtitle: Subtitle;
constructor() {
this.logScheduler = new LogScheduler()
this.ffmpegOptions = new FfmpegOptions();
this.subtitle = new Subtitle();
}
public async StartStoryboarding(videoPath: string, sensitivity: number) {
// 检查抽帧文件是不是存在
let framePath = path.resolve(global.config.project_path, "data/frame");
if (await CheckFileOrDirExist(framePath)) {
await DeleteFolderAllFile(framePath);
} else {
await fs.promises.mkdir(framePath, { recursive: true })
}
// 检查输入文件是不是存在
let inputPath = path.resolve(global.config.project_path, "tmp/input_crop");
if (await CheckFileOrDirExist(inputPath)) {
await DeleteFolderAllFile(inputPath);
} else {
await fs.promises.mkdir(inputPath, { recursive: true })
}
// 检查本事localwhisper是不是存在
let localwhisperPath = path.resolve(define.scripts_path, "localWhisper/local_whisper.exe");
if (!await CheckFileOrDirExist(localwhisperPath)) {
throw new Error('localwhisper 不存在,请查看文档安装localwhisper插件环境');
}
// 判断输出文件是不是存在,存在删除
let frameJson = path.resolve(global.config.project_path, "data/frameTimeLine.json");
if (await CheckFileOrDirExist(frameJson)) {
await fs.promises.unlink(frameJson);
}
// 开始计算分镜
let frameTimeList = await this.ComputedFrameTime(videoPath, frameJson, sensitivity);
// 开始对视频进行切割
// 先计算时间点
let shortVideo = [] as VideoHandleShortVideoTimeLine[];
shortVideo = await this.VideoShortClip(frameTimeList, videoPath, 0.5 * 60 * 1000);
// 检查长度
if (shortVideo.length != frameTimeList.length) {
throw new Error('分镜数据和切割视频数据不一致,请检查');
}
// 开始切割视频
console.log(shortVideo);
let subVideoPath = await this.CutViodeToShortClip(shortVideo, frameTimeList) as string[];
// 开始抽帧
await this.GetFrameFromCutVideo(shortVideo, inputPath, subVideoPath, frameTimeList);
// 开始分离音频
await this.SplitAudio(frameTimeList, framePath);
// 开始提取字幕
await this.subtitle.GetTextByLocalWhisper(frameTimeList, framePath, framePath, localwhisperPath);
console.log(frameTimeList);
await fs.promises.writeFile(frameJson, JSON.stringify(frameTimeList), 'utf-8');
}
/**
* 分离音频
* @param frameTimeList
* @param framePath
*/
async SplitAudio(frameTimeList: DraftTimeLineJson[], framePath: string) {
for (let i = 0; i < frameTimeList.length; i++) {
const element = frameTimeList[i];
if (isEmpty(element.subVideoPath)) {
throw new Error('没有找到待分离的视频数据,请检查');
}
if (!await CheckFileOrDirExist(element.subVideoPath)) {
throw new Error(`视频片段 ${element.subVideoPath} 不存在,请检查`);
}
let audioPath = path.resolve(framePath, `${element.name}.mp3`);
if (await CheckFileOrDirExist(audioPath)) {
await fs.promises.unlink(audioPath);
}
let res = await this.ffmpegOptions.FfmpegExtractAudio(element.subVideoPath, audioPath)
if (res.code == 0) {
throw new Error(res.message);
}
if (!await CheckFileOrDirExist(audioPath)) {
throw new Error(`分离音频 ${audioPath} 失败,没有找到分离后的音频文件,请检查`);
}
element.audioPath = audioPath;
}
}
async GetFrameFromCutVideo(shortVideo: VideoHandleShortVideoTimeLine[], inputPath: string, subVideoPath: string[], frameTimeList: DraftTimeLineJson[]) {
if (shortVideo.length != subVideoPath.length) {
throw new Error('视频片段和分镜数据不一致');
}
if (shortVideo.length != frameTimeList.length) {
throw new Error('视频片段和分镜数据不一致');
}
let imagePath = [] as string[];
for (let i = 0; i < shortVideo.length; i++) {
const element = shortVideo[i];
if (!frameTimeList[i]) {
throw new Error('分镜数据和切割视频数据不一致,请检查');
}
let middleTime = element.startTime + ((element.endTime - element.startTime) / 2);
let outImagePath = path.resolve(inputPath, `${element.name}.png`);
let res = await this.ffmpegOptions.FfmpegGetFrame(middleTime, element.videoPath, outImagePath);
if (res.code == 0) {
throw new Error(res.message);
}
imagePath.push(outImagePath);
// 检查图片是否存在
if (!await CheckFileOrDirExist(outImagePath)) {
throw new Error(`抽取的图片 ${outImagePath} 不存在,请检查`);
}
frameTimeList[i].framePath = outImagePath;
}
return imagePath;
}
/**
* 将长视频按照指定的视频长度,给分割为一个个的小视频片段
* @param shortVideo
*/
async CutViodeToShortClip(shortVideo: VideoHandleShortVideoTimeLine[], frameTimeList: DraftTimeLineJson[]): Promise<string[]> {
let subVideoPaths = [] as string[];
for (let i = 0; i < shortVideo.length; i++) {
const element = shortVideo[i];
if (!frameTimeList[i]) {
throw new Error('分镜数据和切割视频数据不一致,请检查');
}
let subVideoPath = path.resolve(global.config.project_path, `data/frame/${element.name}.mp4`);
// 开始截取视频
let res = await this.ffmpegOptions.FfmpegCutVideo(
element.startTime,
element.endTime,
element.videoPath,
subVideoPath
)
subVideoPaths.push(subVideoPath);
if (res.code == 0) {
throw new Error(res.message);
}
if (!await CheckFileOrDirExist(subVideoPath)) {
throw new Error(`截取视频片段 ${subVideoPath} 不存在,请检查`);
}
frameTimeList[i].subVideoPath = subVideoPath;
}
return subVideoPaths;
}
/**
* 预处理视频,将视频切割成小段,减少计算时间
* @param frameTimeList 要处理的时间线,是个json数组
* @param videoPath 要处理的视频地址
* @param duration 视频的持续时间
* @returns
*/
public async VideoShortClip(frameTimeList: any[], videoPath: string, duration: number): Promise<VideoHandleShortVideoTimeLine[]> {
let shortVideo = [] as VideoHandleShortVideoTimeLine[];
let durationTime = 0; // 小视频片段的持续时间
// let duration = 5 * 60 * 1000; // 5分钟
let tempCount = 0;
let shotVideoPath = path.resolve(global.config.project_path, `data/temp_frame_${tempCount}.mp4`); // 新的视频路径
let startTime = 0; // 开始时间
let endTime = 0; // 结束时间
let lastEndTime = 0; // 上一个结束时间
for (let i = 0; i < frameTimeList.length; i++) {
const item = frameTimeList[i];
let temRes = {
name: (i + 1).toString().padStart(5, "0"),
startTime: item.startTime - lastEndTime,
endTime: item.endTime - lastEndTime,
videoPath: shotVideoPath,
duration: item.endTime - item.startTime
}
endTime = item.endTime;
durationTime += item.endTime - item.startTime;
if (durationTime > duration) { // 判断条件切割视频
// 开始切割视频
let res = await this.ffmpegOptions.FfmpegCutVideo(
startTime,
endTime,
videoPath,
shotVideoPath
)
if (res.code == 0) {
throw new Error(res.message);
}
lastEndTime = item.endTime;
tempCount++;
durationTime = 0;
startTime = endTime;
endTime = 0;
shotVideoPath = path.resolve(global.config.project_path, `data/temp_frame_${tempCount}.mp4`);
}
shortVideo.push(temRes)
}
// 最后一个也要切割
if (durationTime > 0) {
let res = await this.ffmpegOptions.FfmpegCutVideo(
startTime,
endTime,
videoPath,
shotVideoPath
)
if (res.code == 0) {
throw new Error(res.message);
}
}
// 将数据写出
let shortVideoJson = path.resolve(global.config.project_path, "data/shortVideo.json");
await fs.promises.writeFile(shortVideoJson, JSON.stringify(shortVideo), 'utf-8');
return shortVideo;
}
/**
* 计算视频的帧时间。
*
* @param {string} videoPath - 视频文件的路径。
* @param {number} sensitivity - 分镜的敏感度。
* @throws {Error} 如果视频文件不存在或分镜失败,将抛出错误。
*
* @remarks
* 该方法首先检查视频文件是否存在,如果不存在则抛出错误。
* 然后检查输出文件是否存在,如果存在则删除。
* 接着调用外部程序进行分镜处理,并记录日志。
* 如果分镜成功但有警告信息,将记录警告日志。
* 最后检查分镜输出文件是否存在并读取数据,如果没有找到输出文件或数据为空,将抛出错误。
*/
public async ComputedFrameTime(videoPath: string, frameJson: string, sensitivity: number): Promise<DraftTimeLineJson[]> {
if (!await CheckFileOrDirExist(videoPath)) {
throw new Error('视频文件不存在,请检查');
}
this.logScheduler.ReturnLogger(successMessage(null, "前置检查结束,开始进行分镜", "VideoHandle_StartStoryboarding"));
// 开始调用分镜
let command = `"${path.join(
define.scripts_path,
'Lai.exe'
)}" "-ka" "${videoPath}" "${frameJson}" "${sensitivity}"`
const output = await execAsync(command, {
maxBuffer: 1024 * 1024 * 10,
encoding: 'utf-8'
})
// 有错误输出
if (output.stderr != '') {
let error_msg = `分镜成功,但有警告提示:${output.stderr}`
this.logScheduler.ReturnLogger(successMessage(null, error_msg, "VideoHandle_StartStoryboarding"));
}
// 分镜成功,处理输出
let josnIsExist = await CheckFileOrDirExist(frameJson);
if (!josnIsExist) {
let error_message = `分镜失败,没有找到对应的分镜输出文件:${frameJson}`
this.logScheduler.ReturnLogger(successMessage(null, error_message, "VideoHandle_StartStoryboarding"));
throw new Error(error_message);
}
let frameJsonDataString = await fs.promises.readFile(frameJson, 'utf-8');
let res = ValidateJson(frameJsonDataString);
if (!res) {
throw new Error('分镜数据不是有效的JSON格式,请检查');
}
let frameJsonData = JSON.parse(frameJsonDataString);
if (frameJsonData.length <= 0) {
let error_msg = `分镜失败,没有找到对应的分镜数据`
this.logScheduler.ReturnLogger(successMessage(null, error_msg, "VideoHandle_StartStoryboarding"));
throw new Error(error_msg);
}
let result = [] as DraftTimeLineJson[];
// 这边将分镜的数据进行一个处理
for (let i = 0; i < frameJsonData.length; i++) {
const element = frameJsonData[i];
let st = TimeStringToMilliseconds(element[0]);
let et = TimeStringToMilliseconds(element[1]);
let tempObject = {
name: (i + 1).toString().padStart(5, "0"),
startTime: st,
endTime: et,
middleTime: st + ((et - st) / 2),
videoPath: videoPath,
framePath: '',
text: "",
durationTime: et - st
} as DraftTimeLineJson;
result.push(tempObject);
}
return result;
}
}
export default VideoHandle;
+5 -5
View File
@@ -13,7 +13,7 @@ import { define } from '../../define/define'
import { LOGGER_DEFINE } from '../../define/logger_define'
import axios from 'axios'
import { Base64ToFile, GetImageBase64 } from '../../define/Tools/image'
import { TaskScheduler } from './task/taskScheduler';
import { LogScheduler } from './task/logScheduler';
import { LoggerStatus, OtherData, ResponseMessageType } from '../../define/enum/softwareEnum';
import { basicApi } from '../../api/apiBasic';
import { FfmpegOptions } from './ffmpegOptions';
@@ -28,7 +28,7 @@ import { BookTaskService } from '../../define/db/service/Book/bookTaskService';
export class Watermark {
softwareService: SoftwareService
taskScheduler: TaskScheduler;
logScheduler: LogScheduler;
bookService: BookService
bookTaskDetailService: BookTaskDetailService
bookTaskService: BookTaskService
@@ -39,8 +39,8 @@ export class Watermark {
if (!this.softwareService) {
this.softwareService = await SoftwareService.getInstance()
}
if (!this.taskScheduler) {
this.taskScheduler = new TaskScheduler()
if (!this.logScheduler) {
this.logScheduler = new LogScheduler()
}
if (!this.bookService) {
this.bookService = await BookService.getInstance()
@@ -449,7 +449,7 @@ export class Watermark {
}, DEFINE_STRING.BOOK.REMOVE_WATERMARK_RETURN)
this.taskScheduler.AddLogToDB(book.id, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS)
this.logScheduler.AddLogToDB(book.id, book.type, `${element.name} 去除水印完成`, element.bookTaskId, LoggerStatus.SUCCESS)
}
// 全部完毕
if (operateBookType == OperateBookType.BOOKTASKDETAIL) {
+870 -870
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -73,9 +73,8 @@ const api = {
},
// 分镜语音识别消息
StartStoryboarding: async (value) => {
let res = await ipcRenderer.invoke(DEFINE_STRING.START_STORY_BOARDING, value)
},
StartStoryboarding: async (value) =>
await ipcRenderer.invoke(DEFINE_STRING.START_STORY_BOARDING, value),
// 获取设置的初始数据
getSettingDafultData: async (callback) =>
+132 -152
View File
@@ -37,7 +37,6 @@
label-placement="left"
inline
:model="frameValue"
:rules="rules"
size="medium"
>
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px">
@@ -68,7 +67,7 @@
<n-code style="padding: 0; font-size: small" :code="code" language="js" word-wrap />
</template>
<script>
<script setup>
import { defineComponent, ref, onMounted, toRaw } from 'vue'
import {
NSelect,
@@ -83,161 +82,142 @@ import {
NForm,
NFormItem,
NSlider,
NInput
NInput,
useDialog
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string'
export default defineComponent({
components: {
NSelect,
NButton,
NSpin,
NDivider,
NCode,
NTabs,
NTabPane,
NSpace,
NForm,
NFormItem,
NSlider,
NInput
},
setup() {
let options = []
let out_dir = ref(null)
let selectedValue = ref(null)
let show = ref(false)
let code = ref('')
const message = useMessage()
let storyLoading = ref(false)
let frameValue = ref({
sensitivity: 30,
video_path: null
})
let options = []
let out_dir = ref(null)
let selectedValue = ref(null)
let show = ref(false)
let code = ref('')
const message = useMessage()
let storyLoading = ref(false)
let dialog = useDialog()
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach((element) => {
let obj = {
label: element,
value: element
}
options.push(obj)
})
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message)
code.value = code.value + '\n' + value.data
return
}
// 完成
if (value.type == 0) {
storyLoading.value = false
message.success('分镜抽帧完成')
}
code.value = code.value + '\n' + value.data
})
})
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error('请选择剪映草稿和输出草稿')
return
}
show.value = true
// 抽帧
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error('抽帧失败')
code.value = value.message
return
}
message.success('抽帧成功')
code.value = value.data
show.value = false
})
}
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error('必须选择输出文件夹位置')
return
}
out_dir.value = value[0]
})
}
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
frameValue.value.video_path = value.value
})
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true
if (frameValue.value.video_path == null) {
message.error('选择分镜的视频地址')
return
}
if (
toRaw(frameValue.value)
.video_path.split('.')
[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != 'MP4'
) {
message.error('目前只支持MP4格式')
return
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl(
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
)
}
function openExportFolder() {
window.system.OpenFolder({
folderPath: 'tmp/input_crop',
baseProject: true
})
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading,
openExportFolder
}
}
let frameValue = ref({
sensitivity: 30,
video_path: null
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach((element) => {
let obj = {
label: element,
value: element
}
options.push(obj)
})
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message)
code.value = code.value + '\n' + value.data
return
}
// 完成
if (value.type == 0) {
storyLoading.value = false
message.success('分镜抽帧完成')
}
code.value = code.value + '\n' + value.data
})
})
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error('请选择剪映草稿和输出草稿')
return
}
show.value = true
// 抽帧
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error('抽帧失败')
code.value = value.message
return
}
message.success('抽帧成功')
code.value = value.data
show.value = false
})
}
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error('必须选择输出文件夹位置')
return
}
out_dir.value = value[0]
})
}
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
frameValue.value.video_path = value.value
})
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true
if (frameValue.value.video_path == null) {
message.error('选择分镜的视频地址')
return
}
if (
toRaw(frameValue.value)
.video_path.split('.')
[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != 'MP4'
) {
message.error('目前只支持MP4格式')
return
}
let res = await window.api.StartStoryboarding(toRaw(frameValue.value))
if (res.code == 0) {
dialog.error({
title: '抽帧错误输出',
content: res.message
})
return
} else {
dialog.success({
title: '抽帧成功',
content: '视频分镜,抽帧,问题提取都已完成'
})
}
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl(
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
)
}
function openExportFolder() {
window.system.OpenFolder({
folderPath: 'tmp/input_crop',
baseProject: true
})
}
</script>
<style>
@@ -47,7 +47,6 @@ export default defineComponent({
onMounted(async () => {
//
window.api.setEventListen([DEFINE_STRING.SYSTEM.RETURN_LOGGER], (value) => {
debuger
if (value.code == 0) {
message.error('添加日志输出失败,但是不影响使用')
logger.value += value.message + '\n'
@@ -1,9 +1,8 @@
<template>
<div style="width: 100%; height: 100%">
<div
id="showmessage"
style="font-size: 15px; display: flex; justify-content: center; width: 100%; height: 100%"
></div>
<div style="font-size: 15px; display: flex; justify-content: center; width: 100%; height: 100%">
<div id="showmessage"></div>
</div>
</div>
</template>
@@ -34,6 +33,7 @@ async function GetRemoteSystemInformation() {
iframe.style.padding = '0'
iframe.style.height = '98vh' // Adjust the height as needed
showMessageDiv.innerHTML = ''
showMessageDiv.style.width = '100%'
showMessageDiv.appendChild(iframe)
} else {
showMessageDiv.innerHTML = remoteHomePage