You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:45:54