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

node-fluent-ffmpeg转MP3无end/finish事件触发且上传失败

问题

使用ytdl-core下载视频后,通过node-fluent-ffmpeg转换为MP3时遇到两个问题:

  • 上传Google Cloud Storage的代码在转换完成前就执行,抛出文件不存在的ENOENT错误
  • ffmpeg的end/finish事件均未触发,且报错退出码1,提示「Output file #0 does not contain any stream」

代码示例

ytdl(url?.toString() as string)
    .once('data', (data) => {
      console.log('start', data);
    })
    .on('progress', async function (progress, downloaded, total) {
      console.log(parseInt(((downloaded / total) * 100).toString()) + '%');
      
    })
    .on('finish', async function () {
      console.log('Download finished...');
      
      console.log('using ffmpeg to convert into mp3');
      Ffmpeg({ source: `/Users/user/project/server/output/${videoId}.mp4` })
        .setFfmpegPath(ffmpeg.path)
        .toFormat('mp3')
        .saveToFile(`/Users/user/project/server/output/${videoId}.mp3`)
        .on('finish', () => {
          console.log('finished conversion');
        });
      console.log('after ffmpeg mp3 conversion');

      const storage = new Storage({
        keyFilename: `./key.json`,
      });

      const bucketName = 'bucketname';
      const bucket = storage.bucket(bucketName);
      let mp3_url;
      console.log(
        fs.existsSync(`/Users/user/project/server/output/${videoId}.mp3`)
      ); // returns false
      bucket.upload(
        `/Users/user/project/server/output/${videoId}.mp3`,
// Error uploading: Error: ENOENT: no such file or directory, open '/Users/me/project/server/output/RMvenf7E-Dg.mp3'
        {
          destination: `${userId}/${videoId}.mp3`,
        },
        function (err, file) {
          if (err) {
            console.error(`Error uploading: ${err}`);
          } else {
            console.log(`mp3 uploaded to ${bucketName}.`);
            console.log(file?.publicUrl());
          }
        }
      );

      
    .pipe(
      fs.createWriteStream(`/Users/me/project/server/output/${videoId}.mp4`)
    );

  console.log('after download and conversion');

  req.on('close', async () => {
    res.end();
  });

错误信息

Error uploading: Error: ENOENT: no such file or directory, open '/Users/polo/makeklips/server/output/RMvenf7E-Dg.mp3'
[1] /Users/me/project/server/node_modules/.pnpm/fluent-ffmpeg@2.1.2/node_modules/fluent-ffmpeg/lib/processor.js:182
[1]           handleExit(new Error('ffmpeg exited with code ' + code));
[1]                      ^
[1] Error: ffmpeg exited with code 1: Output #0, mp3, to '/Users/me/project/server/output/RMvenf7E-Dg.mp3':
[1] Output file #0 does not contain any stream

解决方案

问题根源分析

  1. 路径不一致:ffmpeg读取的源视频路径是/Users/user/...,但实际写入的路径是/Users/me/...,导致ffmpeg找不到源文件,转换失败后输出文件无流。
  2. 异步流程错误:
    • ytdl的finish事件仅表示下载完成,但通过pipe写入磁盘的流可能还未完成,此时读取文件会导致内容不完整或文件不存在。
    • 上传代码未等待ffmpeg转换完成就执行,自然找不到未生成的MP3文件。

修正步骤及代码

  1. 统一文件路径:确保源视频和输出MP3的路径一致,避免路径错误。
  2. 监听正确的流事件:监听writeStream的finish事件,确保视频文件完全写入磁盘后再启动转换。
  3. 调整异步执行顺序:将GCS上传逻辑放到ffmpeg的finish事件内,同时添加error事件监听处理转换失败的情况。

修正后的代码:

const videoOutputPath = `/Users/me/project/server/output/${videoId}.mp4`;
const mp3OutputPath = `/Users/me/project/server/output/${videoId}.mp3`;

const writeStream = fs.createWriteStream(videoOutputPath);

ytdl(url?.toString() as string)
  .once('data', (data) => {
    console.log('start', data);
  })
  .on('progress', (progress, downloaded, total) => {
    console.log(parseInt(((downloaded / total) * 100).toString()) + '%');
  })
  .pipe(writeStream);

// 监听写入流的finish事件,确保视频完全写入磁盘
writeStream.on('finish', async () => {
  console.log('Download finished...');
  console.log('using ffmpeg to convert into mp3');

  Ffmpeg({ source: videoOutputPath })
    .setFfmpegPath(ffmpeg.path)
    .toFormat('mp3')
    .saveToFile(mp3OutputPath)
    .on('finish', async () => {
      console.log('finished conversion');
      // 转换完成后执行GCS上传
      const storage = new Storage({
        keyFilename: `./key.json`,
      });
      const bucketName = 'bucketname';
      const bucket = storage.bucket(bucketName);

      console.log(fs.existsSync(mp3OutputPath)); // 现在会返回true
      bucket.upload(
        mp3OutputPath,
        {
          destination: `${userId}/${videoId}.mp3`,
        },
        function (err, file) {
          if (err) {
            console.error(`Error uploading: ${err}`);
          } else {
            console.log(`mp3 uploaded to ${bucketName}.`);
            console.log(file?.publicUrl());
          }
        }
      );
    })
    .on('error', (err) => {
      console.error('ffmpeg转换失败:', err);
    });
});

console.log('开始下载和转换流程');

req.on('close', async () => {
  res.end();
});

关键修改说明

  • 提取路径为变量,避免硬编码导致的路径不一致问题。
  • 将ytdl的finish事件替换为writeStream的finish事件,确保视频文件完全写入磁盘后再启动转换。
  • 将GCS上传逻辑移到ffmpeg的finish事件回调内,保证转换完成后再执行上传。
  • 添加ffmpeg的error事件监听,便于排查转换失败问题。

内容的提问来源于stack exchange,提问作者hey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:17:08