LaiTool/src/main/Task/ffmpegOptions.js

308 lines
9.8 KiB
JavaScript
Raw Normal View History

2024-07-13 15:44:13 +08:00
import path from 'path'
import { TaskScheduler } from './taskScheduler'
import { errorMessage, successMessage } from '../generalTools'
import { CheckFileOrDirExist, CheckFolderExistsOrCreate } from '../../define/Tools/file'
import { MillisecondsToTimeString } from '../../define/Tools/time'
import Ffmpeg from 'fluent-ffmpeg'
import { SetFfmpegPath } from '../setting/ffmpegSetting'
import fs from 'fs'
const fspromises = fs.promises
SetFfmpegPath()
/**
* FFmpeg 封装的一些操作
*/
export class FfmpegOptions {
constructor() {
this.taskScheduler = new TaskScheduler()
}
InitCodec() {
let videoCodec = 'libx264' // 默认编码器
if (global.gpu.type === 'NVIDIA') {
videoCodec = 'h264_nvenc'
} else if (global.gpu.type === 'AMD') {
videoCodec = 'h264_amf'
}
this.ecode = videoCodec
let videoDcodec = 'libx264' // 默认解码器
if (global.gpu.type === 'NVIDIA') {
videoDcodec = 'h264_cuvid '
} else if (global.gpu.type === 'AMD') {
videoDcodec = 'h264_amf'
}
}
/**
* FFmpeg裁剪视频将一个视频将裁剪指定的时间内的片段
* @param {*} book 小说对象类
* @param {*} bookTask 小说批次任务对象类
* @param {*} startTime 开始时间
* @param {*} endTime 结束时间
* @param {*} videoPath 视频地址
* @param {*} outVideoFile 输出地址
* @returns
*/
async FfmpegCutVideo(startTime, endTime, videoPath, outVideoFile) {
try {
// 判断视频地址是不是存在
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (!videoIsExist) {
throw new Error('视频地址对应的文件不存在')
}
// 判断开始时间和结束时间是不是合法
if (startTime == null || endTime == null) {
throw new Error('开始时间和结束时间不能为空')
}
// 判断输出文件夹是不是存在
let outputFolder = path.dirname(outVideoFile)
await CheckFolderExistsOrCreate(outputFolder)
// 将时间转换为字符串
let startTimeString = MillisecondsToTimeString(startTime)
let endTimeString = MillisecondsToTimeString(endTime)
// 设置视频编码器
let videoCodec = 'libx264' // 默认编码器
if (global.gpu.type === 'NVIDIA') {
videoCodec = 'h264_nvenc'
} else if (global.gpu.type === 'AMD') {
videoCodec = 'h264_amf'
}
// 判断分镜是不是和数据库中的数据匹配的上
let res = await new Promise((resolve, reject) => {
Ffmpeg(videoPath)
.outputOptions([
`-ss ${startTimeString}`,
`-to ${endTimeString}`,
'-preset fast',
'-c:v ' + videoCodec,
'-c:a copy'
])
.output(outVideoFile)
.on('end', async function () {
resolve(outVideoFile)
})
.on('error', async function (err) {
reject(new Error(`视频裁剪失败,错误信息如下:${err.toString()}`))
})
.run()
})
let res_msg = `视频裁剪完成,输出地址:${outVideoFile}`
return successMessage(res_msg, '视频裁剪成功', 'BasicReverse_FfmpegCutVideo')
// 开始裁剪视频
} catch (error) {
return errorMessage(
'裁剪视频失败,错误信息如下: ' + error.message,
'BasicReverse_FfmpegCutVideo'
)
}
}
/**
* Ffmpeg提取音频
* @param {*} videoPath 视频地址
* @param {*} outAudioPath 输出音频地址
* @returns
*/
async FfmpegExtractAudio(videoPath, outAudioPath) {
try {
// 判断视频地址是不是存在
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (!videoIsExist) {
throw new Error('视频地址对应的文件不存在')
}
// 开始提取音频
let res = await new Promise((resolve, reject) => {
Ffmpeg(videoPath)
.output(outAudioPath)
.audioCodec('libmp3lame')
.audioBitrate('128k')
.on('end', async function () {
resolve(outAudioPath)
})
.on('error', async function (err) {
let res_msg = `音频提取失败,错误信息如下:${err.toString()}`
reject(new Error(res_msg))
})
.run()
})
let res_msg = `音频提取完成,输出地址:${res}`
return successMessage(res, res_msg, 'BasicReverse_FfmpegExtractAudio')
} catch (error) {
return errorMessage(
'提取音频失败,错误信息如下: ' + error.message,
'BasicReverse_FfmpegExtractAudio'
)
}
}
/**
* Ffmpeg提取视频帧只提取一帧
* 根据point判断提取什么位置的帧
* @param {*} frameTime 视频的时间点
* @param {*} videoPath 视频地址
* @param {*} outFramePath 输出帧地址
*/
async FfmpegGetFrame(frameTime, videoPath, outFramePath) {
try {
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (videoIsExist == false) {
throw new Error('视频地址对应的文件不存在')
}
// 判断输出文件夹是不是存在
let outputFolder = path.dirname(outFramePath)
await CheckFolderExistsOrCreate(outputFolder)
// 开始抽帧
// 判断分镜是不是和数据库中的数据匹配的上
let res = await new Promise((resolve, reject) => {
Ffmpeg(videoPath)
.inputOptions([`-ss ${MillisecondsToTimeString(frameTime)}`])
.output(outFramePath)
.frames(1)
.on('end', async function () {
resolve(outFramePath)
})
.on('error', async function (err) {
reject(new Error(err.toString()))
})
.run()
})
let res_msg = `视频抽帧完成,输出地址:${res}`
return successMessage(res, '视频抽帧成功', 'BasicReverse_FfmpegGetFrame')
} catch (error) {
return errorMessage(error.message, 'BasicReverse_FfmpegGetFrame')
}
}
/**
* 获取视频文件的宽高
* @param {*} videoPath 视频文件地址
* @returns
*/
async FfmpegGetVideoSize(videoPath) {
try {
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (videoIsExist == false) {
throw new Error('视频地址对应的文件不存在')
}
let res = await new Promise((resolve, reject) => {
Ffmpeg.ffprobe(videoPath, function (err, metadata) {
if (err) {
reject(new Error(err.toString()))
}
const { width, height } = metadata.streams.find((s) => s.codec_type === 'video')
resolve({ width, height })
})
})
return successMessage(res, '获取视频的宽高成功', 'BasicReverse_GetVideoSize')
} catch (error) {
return errorMessage(
'获取视频的宽高失败,失败信息如下:' + error.message,
'BasicReverse_GetVideoSize'
)
}
}
/**
* 通过FFmpeg获取指定视频的时长
* @param {*} videoPath 视频地址
* @returns 返回视频的时长
*/
async FfmpegGetVideoDuration(videoPath) {
try {
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (videoIsExist == false) {
throw new Error('视频地址对应的文件不存在')
}
let res = await new Promise((resolve, reject) => {
Ffmpeg.ffprobe(videoPath, function (err, metadata) {
if (err) {
reject(new Error(err.toString()))
}
const duration = metadata.format.duration
resolve(duration * 1000)
})
})
return successMessage(res, '获取视频的时长成功', 'BasicReverse_GetVideoDuration')
} catch (error) {
return errorMessage(
'获取视频的时长,失败信息如下:' + error.message,
'BasicReverse_GetVideoDuration'
)
}
}
/**
* 获取视频的指定时间点的一帧然后再裁剪
* @param {*} videoPath
* @param {*} currentTime
* @param {*} outImagePath
* @param {*} clipRanges
*/
async FfmpegGetVideoFramdAndClip(videoPath, currentTime, outImagePath, clipRanges) {
try {
let videoIsExist = await CheckFileOrDirExist(videoPath)
if (videoIsExist == false) {
throw new Error('视频地址对应的文件不存在')
}
// 判断输出文件夹是不是存在
let outputFolder = path.dirname(outImagePath)
await CheckFolderExistsOrCreate(outputFolder)
let frameRes = await this.FfmpegGetFrame(currentTime, videoPath, outImagePath)
if (frameRes.code == 0) {
throw new Error(frameRes.message)
}
let outImagePaths = []
// 这边可以会裁剪多个,所以需要循环
for (let i = 0; i < clipRanges.length; i++) {
const element = clipRanges[i]
let outCropImagePath = outImagePath.replace('.png', `_${i}.png`)
outImagePaths.push(outCropImagePath)
// 开始裁剪
let res = await new Promise((resolve, reject) => {
Ffmpeg(outImagePath)
.outputOptions([
`-vf crop=${element.width}:${element.height}:${element.startX}:${element.startY}`
])
.output(outCropImagePath)
.on('end', async function () {
resolve(outCropImagePath)
})
.on('error', async function (err) {
reject(new Error(err.toString()))
})
.run()
})
}
// 删除文件
await fspromises.unlink(outImagePath)
return successMessage(
outImagePaths,
'获取指定位置的帧和裁剪成功',
'WatermarkAndSubtitle_FfmpegGetVideoFramdAndClip'
)
} catch (error) {
return errorMessage(
'获取指定位置的帧失败和裁剪失败,失败信息如下:' + error.toString(),
'WatermarkAndSubtitle_FfmpegGetVideoFramdAndClip'
)
}
}
}