使用 Node.js 操作 ffmpeg ,实现分割视频与合并视频小工具

12 阅读2分钟

自用剪切视频小工具,使用 Node.js 操作 ffmpeg ,支持将大视频分割为多个视频片段、合并多个视频片段为一个视频。

github.com/heibaimeng/…

功能介绍

  • 分割视频:只需要给出每个剪辑点的时分秒信息和对应片段标题,就会自动切割出对应多个视频。
  • 合并视频:将多个视频依次合成一个大视频。

分割视频

用法

进入目录,打开 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);

实现代码

逻辑代码在:

github.com/heibaimeng/…

管理批量视频剪裁:

  • 先获取视频总时长,结合计算每段片段的开始时间和持续时长
  • 为保证成功率和降低能耗,每个片段逐一调用 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);

实现代码

逻辑代码在:

github.com/heibaimeng/…

  • 创建一个临时文件列表,按指定的格式输入多个文件名
  • 执行合并视频后删除临时文件列表
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();
}