自用剪切视频小工具,使用 Node.js 操作 ffmpeg ,支持将大视频分割为多个视频片段、合并多个视频片段为一个视频。
功能介绍
- 分割视频:只需要给出每个剪辑点的时分秒信息和对应片段标题,就会自动切割出对应多个视频。
- 合并视频:将多个视频依次合成一个大视频。
分割视频
用法
进入目录,打开 action-cutting.mjs
,能看到可以设置的内容,进行配置:
- 将视频文件放到 input 目录,更名为 input.mp4 ,或者更改视频文件名变量
- 按格式填写片段数组 segments 数组
配置后,执行 node action-cutting.mjs
,生成的视频将出现在 output 目录下
import { cut } from "./core/cut.mjs";
const inputVideo = 'input.mp4';
const segments = [
['00:00:00', '第一段'],
['00:02:30', '第二段'],
['00:05:10', '第三段'],
];
cut(inputVideo, segments, 1);
实现代码
逻辑代码在:
管理批量视频剪裁:
- 先获取视频总时长,结合计算每段片段的开始时间和持续时长
- 为保证成功率和降低能耗,每个片段逐一调用 fluent-ffmpeg 剪切视频
/**
* 开始执行视频剪裁
* @param {string} inputVideo 视频文件名
* @param {string[][]} segments 片段信息
* @param {number} startNumber 首个视频开始的序号
* @param {number} numberLength 视频序号位数,不足补0
*/
export async function cut(inputVideo, segments, startNumber = 1, numberLength = 2) {
// 先获取视频总时长,结合计算每段片段的开始时间和持续时长
ffmpeg.ffprobe(`input/${inputVideo}`, async (err, metadata) => {
if (err) {
console.error('读取视频元数据出错:', err);
return;
}
await ensureOutputDirExists()
const videoDuration = metadata.format.duration;
const segmentCount = segments.length;
for (let index = 0; index < segments.length; index++) {
const segment = segments[index];
const start = segment[0];
const end = (index + 1 < segmentCount) ? segments[index + 1][0] : secondsToTimeStr(videoDuration);
const outputFileName = `output/${String(startNumber + index).padStart(numberLength, '0')}. ${segment[1]}.mp4`;
// 为保证成功率和降低能耗,每个片段逐一调用 fluent-ffmpeg 剪切视频
await cutSingle(inputVideo, start, end, outputFileName)
}
});
}
实现单个视频剪裁:
/**
* 剪切单个视频
* @param {string} inputVideo 视频文件名
* @param {string} start 片段开始时间
* @param {string} end 片段结束时间
* @param {string} outputFileName 视频文件名
* @returns
*/
function cutSingle(inputVideo, start, end, outputFileName) {
const duration = getDuration(start, end)
return new Promise((resolve, reject) => {
ffmpeg(`input/${inputVideo}`)
.setStartTime(start)
.setDuration(duration)
// 视频和音频都不重新进行编码
.videoCodec('copy')
.audioCodec('copy')
.output(outputFileName)
.on('end', () => {
console.log(`生成视频成功: ${outputFileName},位置 ${start}-${end} ,视频共 ${duration} 秒。`);
resolve()
})
.on('error', err => {
console.error(`生成视频失败: ${outputFileName},错误信息: ${err.message}。`);
reject()
})
.run();
})
}
合并视频
用法
进入目录,打开 action-merging
,能看到可以设置的内容,进行配置:
- 将视频文件放到 input 目录,在 inputVideos 变量按顺序写入视频文件名
- 编写输出文件名 outputVide
配置后,执行 node action-merging
,生成的视频将出现在 output 目录下
import { merge } from './core/merge.mjs'
const inputVideos = [
'1.mp4',
'2.mp4',
'3.mp4'
];
const outputVide = 'merged.mp4';
merge(inputVideos, outputVide);
实现代码
逻辑代码在:
- 创建一个临时文件列表,按指定的格式输入多个文件名
- 执行合并视频后删除临时文件列表
export async function merge(inputVideos, outputVideo) {
await ensureOutputDirExists()
// 创建一个临时文件列表
const fileList = 'filelist.txt';
const fileContent = inputVideos.map(video => `file 'input/${video}'`).join('\n');
await fs.writeFile(fileList, fileContent);
// 执行合并视频
ffmpeg()
.input(fileList)
.inputOptions(['-f concat', '-safe 0'])
.outputOptions(['-c:v copy', '-c:a copy'])
.output(`output/${outputVideo}`)
.on('end', async () => {
console.log(`合并文件成功: ${outputVideo}`);
await fs.unlink(fileList);
})
.on('error', async err => {
console.error(`合并文件失败: ${err.message}`);
await fs.unlink(fileList);
})
.run();
}