LaiTool/src/main/Task/basicReverse.js

568 lines
19 KiB
JavaScript
Raw Normal View History

2024-06-27 16:24:41 +08:00
import path from 'path'
import fs from 'fs'
const util = require('util')
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 './taskScheduler'
import { LoggerStatus, LoggerType, OtherData } from '../../define/enum/softwareEnum'
import { errorMessage, successMessage } from '../generalTools'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../define/Tools/file'
import { BookTaskDetailService } from '../../define/db/service/Book/bookTaskDetailService'
import { BookTaskService } from '../../define/db/service/Book/bookTaskService'
import { isEmpty, set } from 'lodash'
import ffmpeg from 'fluent-ffmpeg'
import { SetFfmpegPath } from '../setting/ffmpegSetting'
import { TimeStringToMilliseconds, MillisecondsToTimeString } from '../../define/Tools/time'
import { BookTaskStatus } from '../../define/enum/bookEnum'
SetFfmpegPath()
const fspromises = fs.promises
/**
* 后台执行的任务函数直接调用改函数即可抽帧分镜提取字幕等
*/
2024-06-24 13:11:19 +08:00
export class BasicReverse {
constructor() {
this.taskScheduler = new TaskScheduler()
}
2024-06-27 16:24:41 +08:00
//#region ffmpeg的一些操作
/**
* FFmpeg裁剪视频将一个视频将裁剪指定的时间内的片段
* @param {*} book 小说对象类
* @param {*} bookTask 小说批次任务对象类
* @param {*} startTime 开始时间
* @param {*} endTime 结束时间
* @param {*} videoPath 视频地址
* @param {*} outVideoFile 输出地址
* @returns
*/
async FfmpegCutVideo(book, bookTask, startTime, endTime, videoPath, outVideoFile) {
try {
// 判断视频地址是不是存在
let videoIsExist = CheckFileOrDirExist(videoPath)
if (!videoIsExist) {
throw new Error('视频地址对应的文件不存在')
}
// 判断开始时间和结束时间是不是合法
if (isEmpty(startTime) || isEmpty(endTime)) {
throw new Error('开始时间和结束时间不能为空')
}
// 判断输出文件夹是不是存在
let outputFolder = path.dirname(outVideoFile)
await CheckFolderExistsOrCreate(outputFolder)
// 将时间转换为字符串
startTimeString = MillisecondsToTimeString(startTime)
endTimeString = MillisecondsToTimeString(endTime)
// 设置视频编码器
let videoCodec = 'libx264' // 默认编码器
if (global.gpu.type === 'NVIDIA') {
videoCodec = 'h264_nvenc'
} else if (global.gpu.type === 'AMD') {
videoCodec = 'h264_amf'
}
// 判断分镜是不是和数据库中的数据匹配的上
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.setStartTime(startTimeString)
.setEndTime(endTimeString)
.videoCodec(videoCodec)
.addOption('-preset', 'fast')
.audioCodec('copy')
.output(outVideoFile)
.on('end', async function () {
let res_msg = `视频裁剪完成,输出地址:${outVideoFile}`
// 修改数据库中的输出地址
await this.taskScheduler.AddLogToDB(
book.id,
book.type,
res_msg,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
return successMessage(null, res_msg, 'BasicReverse_FfmpegCutVideo')
})
.on('error', async function (err) {
let res_msg = `视频裁剪失败,错误信息如下:${err.toString()}`
await this.taskScheduler.AddLogToDB(
book.id,
book.type,
res_msg,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
return errorMessage(res_msg, 'BasicReverse_FfmpegCutVideo')
})
.run()
})
// 开始裁剪视频
} catch (error) {
return errorMessage(
'裁剪视频失败,错误信息如下: ' + error.message,
'BasicReverse_FfmpegCutVideo'
)
}
}
/**
*
* @param {*} videoPath
* @param {*} audioPath
*/
async FfmpegExtractAudio(videoPath, outAudioPath) {
try {
// 判断视频地址是不是存在
let videoIsExist = CheckFileOrDirExist(videoPath)
if (!videoIsExist) {
throw new Error('视频地址对应的文件不存在')
}
// 开始提取音频
return new Promise((resolve, reject) => {
ffmpeg(videoPath)
.output(outAudioPath)
.audioCodec('libmp3lame')
.audioBitrate('128k')
.on('end', async function () {
let res_msg = `音频提取完成,输出地址:${outAudioPath}`
return successMessage(outAudioPath, res_msg, 'BasicReverse_FfmpegExtractAudio')
})
.on('error', async function (err) {
let res_msg = `音频提取失败,错误信息如下:${err.toString()}`
return errorMessage(res_msg, 'BasicReverse_FfmpegExtractAudio')
})
})
} catch (error) {
return errorMessage(
'提取音频失败,错误信息如下: ' + error.message,
'BasicReverse_FfmpegExtractAudio'
)
}
}
//#endregion
2024-06-24 13:11:19 +08:00
/**
* 分镜通过传入的bookId
* @param {*} bookId 传入的bookId
2024-06-27 16:24:41 +08:00
* @returns
2024-06-24 13:11:19 +08:00
*/
async GetFrameData(bookId) {
try {
2024-06-27 16:24:41 +08:00
let _bookService = await BookService.getInstance()
let _bookTaskDetailService = await BookTaskDetailService.getInstance()
let _bookTaskService = await BookTaskService.getInstance()
2024-06-24 13:11:19 +08:00
// 获取对应的小说小说数据,找到对应的小说视频地址
let bookQuery = {
bookId: bookId
}
2024-06-27 16:24:41 +08:00
let bookData = _bookService.GetBookData(bookQuery)
2024-06-24 13:11:19 +08:00
if (bookData.code == 0) {
return bookData
}
if (bookData.data.book_length <= 0 || bookData.data.res_book.length <= 0) {
2024-06-27 16:24:41 +08:00
throw new Error('没有找到对应的小说数据请检查bookId是否正确')
}
// 获取小说对应的批次任务数据,默认初始化为第一个
let bookTaskRes = await _bookTaskService.GetBookTaskData({
bookId: bookId,
name: 'output_00001'
})
if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) {
throw new Error('没有找到对应的小说批次任务数据请检查bookId是否正确')
2024-06-24 13:11:19 +08:00
}
// 获取小说的视频地址
let book = bookData.data.res_book[0]
2024-06-27 16:24:41 +08:00
let bookTask = bookTaskRes.data.bookTasks[0]
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.STORYBOARD)
// 分镜之前,删除之前的老数据
let deleteBookTaskRes = _bookTaskDetailService.DeleteBookTaskDetail({
bookId: bookId,
bookTaskId: bookTask.id
})
2024-06-24 13:11:19 +08:00
let oldVideoPath = book.oldVideoPath
let frameJson = oldVideoPath + '.json'
let sensitivity = 30
// 开始之前,推送日志
let log_content = `开始进行分镜操作,视频地址:${oldVideoPath},敏感度:${sensitivity},正在调用程序进行处理`
2024-06-27 16:24:41 +08:00
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
log_content,
OtherData.DEFAULT,
LoggerStatus.DOING
)
2024-06-24 13:11:19 +08:00
// 小说进行分镜python进行将结果写道一个json里面
// 使用异步的方法调用一个python程序然后写入到指定的json文件中k
2024-06-27 16:24:41 +08:00
let command = `"${path.join(
define.scripts_path,
'Lai.exe'
)}" "-ka" "${oldVideoPath}" "${frameJson}" "${sensitivity}"`
const output = await execAsync(command, {
maxBuffer: 1024 * 1024 * 10,
encoding: 'utf-8'
})
2024-06-24 13:11:19 +08:00
// 有错误输出
if (output.stderr != '') {
2024-06-27 16:24:41 +08:00
let error_msg = `分镜失败,错误信息如下:${output.stderr}`
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.STORYBOARD_FAIL,
error_msg
)
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
error_msg,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
throw new Error(output.stderr)
2024-06-24 13:11:19 +08:00
}
// 分镜成功,处理输出
let josnIsExist = CheckFileOrDirExist(frameJson)
if (!josnIsExist) {
let error_message = `分镜失败,没有找到对应的分镜输出文件:${frameJson}`
2024-06-27 16:24:41 +08:00
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.STORYBOARD_FAIL,
error_message
)
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
error_message,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
2024-06-24 13:11:19 +08:00
throw new Error(error_message)
}
let frameJsonData = JSON.parse(await fspromises.readFile(frameJson, 'utf-8'))
if (frameJsonData.length <= 0) {
2024-06-27 16:24:41 +08:00
let error_msg = `分镜失败,没有找到对应的分镜数据`
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.STORYBOARD_FAIL,
error_msg
)
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
error_msg,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
throw new Error(error_msg)
2024-06-24 13:11:19 +08:00
}
// 循环写入小说人物详细数据
for (let i = 0; i < frameJsonData.length; i++) {
2024-06-27 16:24:41 +08:00
let dataArray = frameJsonData[i]
2024-06-24 13:11:19 +08:00
let bookTaskDetail = {
bookId: bookId,
2024-06-27 16:24:41 +08:00
bookTaskId: bookTask.id
}
// 将字符串转换为number
bookTaskDetail.startTime = TimeStringToMilliseconds(dataArray[0])
bookTaskDetail.endTime = TimeStringToMilliseconds(dataArray[1])
let res = _bookTaskDetailService.AddBookTaskDetail(bookTaskDetail)
if (res.code == 0) {
throw new Error(res.message)
2024-06-24 13:11:19 +08:00
}
}
2024-06-27 16:24:41 +08:00
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.STORYBOARD_DONE)
// 分镜成功,推送日志
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`分镜成功,分镜数据如下:${frameJsonData}`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
2024-06-24 13:11:19 +08:00
2024-06-27 16:24:41 +08:00
return successMessage(null, `分镜成功,分镜信息在 ${frameJson}`, 'BasicReverse_GetFrameData')
} catch (error) {
return errorMessage(error.message, 'BasicReverse_GetFrameData')
}
}
2024-06-24 13:11:19 +08:00
2024-06-27 16:24:41 +08:00
/**
* 裁剪视频
* @param {*} bookId 小说ID
* @param {*} frameJson 存放分镜数据的json文件地址
*/
async CutVideoData(bookId) {
try {
if (isEmpty(bookId)) {
throw new Error('bookId不能为空')
}
2024-06-24 13:11:19 +08:00
2024-06-27 16:24:41 +08:00
// 判断小说是不是存在
let _bookService = await BookService.getInstance()
let _bookTaskService = await BookTaskService.getInstance()
let _bookTaskDetailService = await BookTaskDetailService.getInstance()
let book = _bookService.GetBookDataById(bookId)
2024-06-24 13:11:19 +08:00
2024-06-27 16:24:41 +08:00
if (book == null) {
throw new Error('没有找到对应的小说数据')
}
// 找到对应的小说ID和对应的小说批次任务ID判断是不是有分镜数据
let bookTaskRes = await _bookTaskService.GetBookTaskData({
bookId: bookId,
name: 'output_00001'
})
if (bookTaskRes.data.bookTasks.length <= 0 || bookTaskRes.data.total <= 0) {
throw new Error('没有找到对应的小说批次任务数据请检查bookId是否正确')
}
let bookTask = bookTaskRes.data.bookTasks[0]
let bookTaskDetail = _bookTaskDetailService.GetBookTaskData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetail.data.length <= 0) {
// 传入的分镜数据为空,需要重新获取
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`没有传入分镜数据,开始调用分镜方法`,
OtherData.DEFAULT,
LoggerStatus.DOING
)
let frameRes = this.GetFrameData(bookId)
if (frameRes.code == 0) {
throw new Error((await frameRes).message)
}
}
bookTaskDetail = _bookTaskDetailService.GetBookTaskData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetail.data.length <= 0) {
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.SPLIT_FAIL,
'重新调用分镜方法还是没有分镜数据,请检查'
)
throw new Error('重新调用分镜方法还是没有分镜数据,请检查')
}
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT)
// 有分镜数据,开始处理
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`成功获取分镜数据,开始裁剪视频`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
for (let i = 0; i < bookTaskDetail.length; i++) {
const element = bookTaskDetail[i]
let startTime = element.startTime
let endTime = element.endTime
if (startTime == null || endTime == null) {
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.SPLIT_FAIL,
'开始时间和结束时间不能为空'
)
throw new Error('开始时间和结束时间不能为空')
}
let outVideoFile = path.join(book.bookFolderPath, `data/frame/${element.name}.mp4`)
let res = await this.FfmpegCutVideo(
book,
startTime,
endTime,
book.oldVideoPath,
outVideoFile
)
if (res.code == 0) {
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT_FAIL, res.message)
throw new Error(res.message)
}
// 视频裁剪完成,要将裁剪后的视频地址写入到数据库中
_bookTaskDetailService.UpdateBookTaskDetail(element.id, {
videoPath: path.relative(define.project_path, outVideoFile)
})
}
// 小改小说批次的状态
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.SPLIT_DONE)
// 结束,分镜完毕,推送日志,返回成功
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`全部视频裁剪完成`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
return successMessage(null, '全部视频裁剪完成', 'BasicReverse_CutVideoData')
2024-06-24 13:11:19 +08:00
} catch (error) {
2024-06-27 16:24:41 +08:00
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
error.message,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
return errorMessage(
'裁剪视频失败,错误信息如下: ' + error.message,
'BasicReverse_CutVideoData'
)
}
}
/**
* 分离视频片段的音频
* 当没有传入bookTaskId分离默认的第一个
* 有传入的时候分离对应的bookTaskId的数据
* @param {*} bookId
* @param {*} bookTaskId
*/
async SplitAudioData(bookId, bookTaskId = null) {
try {
let _bookService = await BookService.getInstance()
let _bookTaskService = await BookTaskService.getInstance()
let _bookTaskDetailService = await BookTaskDetailService.getInstance()
let book = _bookService.GetBookDataById(bookId)
if (book == null) {
throw new Error('没有找到对应的小说数据')
}
let bookTask
if (bookTaskId != null) {
bookTaskId = _bookTaskService.GetBookTaskData({ id: bookTaskId })
} else {
bookTask = _bookTaskService.GetBookTaskData({
bookId: bookId,
name: 'output_00001'
})
}
if (bookTask.data.bookTasks.length <= 0 || bookTask.data.total <= 0) {
throw new Error('没有找到对应的小说批次任务数据请检查bookId是否正确')
}
bookTask = bookTask.data.bookTasks[0]
// 获取对应小说批次任务的分镜信息
let bookTaskDetails = _bookTaskDetailService.GetBookTaskData({
bookId: bookId,
bookTaskId: bookTask.id
})
if (bookTaskDetails.data.length <= 0) {
throw new Error('没有找到对应的小说批次任务数据请检查bookId是否正确或者手动执行')
}
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`开始分离音频`,
OtherData.DEFAULT,
LoggerStatus.DOING
)
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO)
for (let i = 0; i < bookTaskDetails.length; i++) {
const element = bookTaskDetails[i]
let videoPath = element.videoPath
let audioPath = path.join(book.bookFolderPath, `data/audio/${element.name}.mp3`)
await CheckFolderExistsOrCreate(path.dirname(audioPath))
// 开始分离音频
let audioRes = await this.FfmpegExtractAudio(videoPath, audioPath)
if (audioRes.code == 0) {
let errorMessage = `分离音频失败,错误信息如下:${audioRes.message}`
_bookTaskService.UpdateBookTaskStatus(
bookTask.id,
BookTaskStatus.AUDIO_FAIL,
errorMessage
)
throw new Error(audioRes.message)
}
_bookTaskDetailService.UpdateBookTaskDetail(element.id, {
audioPath: path.relative(define.project_path, audioPath)
})
// 推送成功消息
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`${element.name}分离音频成功,输出地址:${audioPath}`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
}
// 修改状态为分离音频成功
_bookTaskService.UpdateBookTaskStatus(bookTask.id, BookTaskStatus.AUDIO_DONE)
// 所有音频分离成功,推送日志
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
`${element.name}分离音频成功,输出地址:${audioPath}`,
OtherData.DEFAULT,
LoggerStatus.SUCCESS
)
return successMessage(null, '所有音频分离成功', 'BasicReverse_SplitAudioData')
} catch (error) {
let errorMessage = `分离音频失败,错误信息如下:${error.message}`
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
errorMessage,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
return errorMessage(errorMessage, 'BasicReverse_SplitAudioData')
}
}
/**
* 提取字幕
* @param {*} bookId
* @param {*} bookTaskId
* @returns
*/
async ExtractSubtitlesData(bookId, bookTaskId = null) {
try {
} catch (error) {
let errorMessage = `提取字幕失败,错误信息如下:${error.message}`
await this.taskScheduler.AddLogToDB(
bookId,
book.type,
errorMessage,
OtherData.DEFAULT,
LoggerStatus.FAIL
)
return errorMessage(errorMessage, 'BasicReverse_ExtractSubtitlesData')
2024-06-24 13:11:19 +08:00
}
}
2024-06-27 16:24:41 +08:00
}