如何使用ffmpeg-fluent npm包拼接不同扩展名的多个视频文件
报错原因
你遇到的ffmpeg报错是因为concat滤镜强制要求所有输入视频的分辨率、帧率、像素格式、音频采样率、声道数、编码规格完全一致,你拼接的是不同扩展名的视频,上述参数大概率存在差异,导致滤镜初始化失败。
需修复的代码问题
- 全局初始化
fluent_ffmpeg实例会导致多次请求复用同一个实例,产生不可预期的错误,需要将实例创建逻辑移入/merge路由处理函数内部 - 缺少音视频参数统一预处理步骤,不同参数的视频无法直接用
concat滤镜拼接 - 代码中
output_file变量未定义,执行到end回调时会额外报错
修复后代码
const express = require("express"); const multer = require("multer"); const fluent_ffmpeg = require("fluent-ffmpeg"); const app = express(); const fs = require("fs"); const path = require("path"); const ffmpeg = require('@ffmpeg-installer/ffmpeg').path; const ffprobe = require('@ffprobe-installer/ffprobe').path; // 全局只设置一次路径即可 fluent_ffmpeg.setFfmpegPath(ffmpeg); fluent_ffmpeg.setFfprobePath(ffprobe); const PORT = process.env.PORT || 3000; const output_file = "output.mp4"; var dir = "public"; var subDirectory = "public/uploads"; app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.static(path.join(__dirname,"public"))); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); fs.mkdirSync(subDirectory); } const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public/uploads"); }, filename: function (req, file, cb) { cb( null, file.fieldname + "-" + Date.now() + path.extname(file.originalname) ); } }); var upload = multer({ storage: storage }); app.post('/merge',upload.any(),(req,res) => { // 每次请求创建新的ffmpeg实例 const mergedVideo = fluent_ffmpeg(); let file_list = []; let filter_chains = []; let concat_inputs = ""; req.files.forEach((element, index) => { file_list.push("./"+element.path); // 先统一每个输入的视频参数:1920x1080分辨率,yuv420p像素格式,25fps filter_chains.push(`[${index}:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=25,format=yuv420p[v${index}]`); // 统一每个输入的音频参数:44100Hz采样率,双声道 filter_chains.push(`[${index}:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo[a${index}]`); concat_inputs += `[v${index}][a${index}]`; }); // 拼接concat滤镜 filter_chains.push(`${concat_inputs} concat=n=${file_list.length}:v=1:a=1 [outv] [outa]`); const output_option = ['-map [outv]','-map [outa]', '-c:v libx264', '-c:a aac']; let chainedInputs = file_list.reduce((result, inputItem) => result.addInput(inputItem), mergedVideo); chainedInputs .complexFilter(filter_chains) .outputOption(output_option) .on('error', function(err) { console.log("Error:",err.message); res.status(500).send("合并失败:" + err.message); }) .on('start', function() { console.log('Merging...'); }) .on('progress', function(progress) { console.log('合并进度:', progress.percent ? `${progress.percent.toFixed(2)}%` : '计算中'); }) .on('end', function() { console.log(output_file + ' merged.'); res.download(path.join(__dirname, output_file), (err) => { if (err) console.log("下载失败:", err); }); }) .save(output_file); }); app.get("/", (req, res) => { res.sendFile(__dirname + "/index.html"); }); app.listen(PORT, () => { console.log(`App is listening on Port ${PORT}`); });
可选优化方案
如果拼接的视频数量多,先统一转码再拼接的效率更高,你可以先把所有输入视频转成相同参数的中间ts文件,再用ffmpeg的concat demuxer直接拼接,不需要重新编码,速度更快。
内容的提问来源于stack exchange,提问作者Keval Sorathiya
相关产品推荐
相关产品推荐

