Next.js中fluent-ffmpeg转MP4至S3失败:输出格式不可用
解决fluent-ffmpeg流式输出MP4到AWS S3时的"Output format mp4 is not available"错误
错误根源
从FFmpeg日志里的关键报错可以定位问题:
[AVFormatContext @ 00000262cd0b2240] Unable to choose an output format for 'pipe:1'; use a standard extension for the filename or specify the format manually.
你代码里调用了.outputFormat("mp4"),但fluent-ffmpeg在使用.pipe()输出时,没有把格式参数传递给FFmpeg命令。看生成的FFmpeg命令行:
"C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffmpeg.exe" -i ./template1.mp4 -filter:v "drawtext=text=hi:fontsize=24:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:box=1:boxcolor=black@0.5:boxborderw=5:fontfile=/Windows/fonts/calibri.ttf" -report pipe:1
这里缺少-f mp4参数,导致FFmpeg无法识别pipe输出的格式。
另外你的S3上传逻辑还有时序问题:uploadToS3定义在ffmpeg调用之后,ffmpeg的end事件触发时函数还未定义;同时等待passthroughStream的end事件又在end里调用上传,逻辑冲突。
修复步骤
1. 强制传递FFmpeg格式参数
修改fluent-ffmpeg调用,直接用.outputOptions()添加-f mp4参数,确保FFmpeg能识别pipe输出格式:
ffmpeg({ source: "./template1.mp4" }) .on("end", () => { console.log("处理完成"); passthroughStream.end(); }) .on("error", (error) => { console.error("处理出错:", error); passthroughStream.destroy(error); throw new Error("视频处理失败"); }) .videoFilter({ filter: "drawtext", options: { text: "hi", fontsize: 24, fontcolor: "white", x: "(w-text_w)/2", y: "(h-text_h)/2", box: 1, boxcolor: "black@0.5", boxborderw: 5, fontfile: "C:\\Windows\\Fonts\\calibri.ttf", }, }) .videoCodec("libx264") .outputOptions([ "-f mp4", // 手动指定输出格式为MP4 "-movflags frag_keyframe+empty_moov" ]) .pipe(passthroughStream, { end: false }); // 不自动结束流,由ffmpeg的end事件控制
2. 修复S3上传时序
提前定义uploadToS3函数,启动上传Promise后再触发ffmpeg处理,避免函数未定义和流事件冲突:
// 提前定义上传函数 const uploadToS3 = async (stream: PassThrough) => { const key = generateFileName(); const upload = new Upload({ client: s3, params: { Bucket: process.env.AWS_BUCKET_NAME!, Key: key, Body: stream, ContentType: "video/mp4" // 显式指定内容类型,避免S3识别错误 }, }); await upload.done(); return key; }; const passthroughStream = new PassThrough(); // 先启动上传Promise const uploadPromise = uploadToS3(passthroughStream); // 再启动ffmpeg处理 ffmpeg(...) // 上面的ffmpeg配置代码 // 等待上传完成 const videoKey = await uploadPromise;
3. 其他优化点
- 手动指定ffmpeg路径,避免fluent-ffmpeg找不到可执行文件:
const ffmpeg = require("fluent-ffmpeg"); ffmpeg.setFfmpegPath("C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffmpeg.exe"); - 修复数据库存储的URL:直接用S3桶域名+文件key生成,不要依赖预签名URL(预签名URL有过期时间):
const videoUrl = `https://${process.env.AWS_BUCKET_NAME!}.s3.${process.env.AWS_BUCKET_REGION!}.amazonaws.com/${videoKey}`; - 修正Windows字体路径:用反斜杠或转义正斜杠,比如
"C:\\Windows\\Fonts\\calibri.ttf"
完整修复后的代码片段
createVideo: privateProcedure .input( z.object({ fileId: z.string(), }) ) .mutation(async ({ ctx, input }) => { const { getUser } = getKindeServerSession(); const user = await getUser(); if (!user || !user.id || !user.email) { throw new TRPCError({ code: "UNAUTHORIZED" }); } const dbUser = await db.user.findFirst({ where: { id: user.id, }, }); if (!dbUser) { throw new TRPCError({ code: "UNAUTHORIZED", message: "数据库中未找到用户.", }); } const s3 = new S3Client({ region: process.env.AWS_BUCKET_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }); const ffmpeg = require("fluent-ffmpeg"); ffmpeg.setFfmpegPath("C:\\ProgramData\\chocolatey\\lib\\ffmpeg-full\\tools\\ffmpeg\\bin\\ffmpeg.exe"); const passthroughStream = new PassThrough(); const uploadToS3 = async (stream: PassThrough) => { const key = generateFileName(); const upload = new Upload({ client: s3, params: { Bucket: process.env.AWS_BUCKET_NAME!, Key: key, Body: stream, ContentType: "video/mp4" }, }); await upload.done(); return key; }; const uploadPromise = uploadToS3(passthroughStream); ffmpeg({ source: "./template1.mp4" }) .on("end", () => { console.log("FFmpeg处理完成"); passthroughStream.end(); }) .on("error", (error: Error) => { console.error("FFmpeg错误:", error); passthroughStream.destroy(error); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "视频处理失败" }); }) .videoFilter({ filter: "drawtext", options: { text: "hi", fontsize: 24, fontcolor: "white", x: "(w-text_w)/2", y: "(h-text_h)/2", box: 1, boxcolor: "black@0.5", boxborderw: 5, fontfile: "C:\\Windows\\Fonts\\calibri.ttf", }, }) .videoCodec("libx264") .outputOptions([ "-f mp4", "-movflags frag_keyframe+empty_moov" ]) .pipe(passthroughStream, { end: false }); const videoKey = await uploadPromise; const videoUrl = `https://${process.env.AWS_BUCKET_NAME!}.s3.${process.env.AWS_BUCKET_REGION!}.amazonaws.com/${videoKey}`; const createdVideo = await db.video.create({ data: { name: "Test Name", url: videoUrl, key: videoKey, fileId: input.fileId, }, }); return createdVideo; }),
内容的提问来源于stack exchange,提问作者alpaca pwaa
相关产品推荐
相关产品推荐

