为什么 Busboy 在解析 FLAC 文件时会产生不一致的结果?

Why does Busboy yield inconsistent results when parsing FLAC files?

我有一个 Express 服务器接收 FormData 和附加的 FLAC 音频文件。对于大小不同 (10 - 70MB) 的几个文件,代码按预期工作,但其中一些文件卡在 'file' 事件中,我无法弄清楚为什么会发生这种情况。更奇怪的是,以前没有触发 file.on('close', => {}) 事件的文件(如 Busboy 的文档中所示)突然触发,文件已成功上传。

对我来说,这似乎完全是随机的,因为我已经对十几个不同大小和内容类型的文件进行了尝试 (audio/flac & audio/x-flac),但结果并不一致。然而,有些文件根本不起作用,即使我尝试多次解析它们也是如此。然而,如果尝试足够多,可以解析和上传某些文件?

'file' 事件中是否存在我无法处理的错误?我确实尝试收听 file.on('error', => {}) 事件,但没有发现任何错误。 Other answers 建议必须使用 file 流才能使 'close' 事件继续进行,但我认为 file.pipe(fs.createWriteStream(fileObject.filePath)); 是这样做的,对吗?

如果我忘记在问题中包含一些重要信息,请告诉我。这已经困扰我大约一个星期了,所以我很乐意提供任何相关信息来帮助我克服这个障碍。

app.post('/upload', (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');
  const bb = busboy({ headers: request.headers });

  const fields = {};
  const fileObject = {};

  bb.on('file', (_name, file, info) => {
    const { filename, mimeType } = info;

    fileObject['mimeType'] = mimeType;
    fileObject['filePath'] = path.join(os.tmpdir(), filename);
    file.pipe(fs.createWriteStream(fileObject.filePath));

    file.on('close', () => {
      console.log('Finished parsing of file');
    });
  });

  bb.on('field', (name, value) => {
    fields[name] = value;
  });

  bb.on('close', () => {
    bucket.upload(
      fileObject.filePath,
      {
        uploadType: 'resumable',
        metadata: {
          metadata: {
            contentType: fileObject.mimeType,
            firebaseStorageDownloadToken: fields.id
          }
        }
      },
      (error, uploadedFile) => {
        if (error) {
          console.log(error);
        } else {
          db.collection('tracks')
            .doc(fields.id)
            .set({
              identifier: fields.id,
              artist: fields.artist,
              title: fields.title,
              imageUrl: fields.imageUrl,
              fileUrl: `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${uploadedFile.name}?alt=media&token=${fields.id}`
            });

          response.send(`File uploaded: ${fields.id}`);
        }
      }
    );
  });

  request.pipe(bb);
});

更新:1

我决定用 file.on('data', (data) => {}) 测量每次上传时传输的字节数,只是为了看看问题是否总是相同的,事实证明这也是完全随机的。

let bytes = 0;

file.on('data', (data) => {
  bytes += data.length;

  console.log(`Loaded ${(bytes / 1000000).toFixed(2)}MB`);
});

第一个测试用例:Fenomenon - Buxton 的沉睡草地

来源:https://fenomenon.bandcamp.com/track/sleepy-meadows-of-buxton

三次尝试的结果:

  1. 加载了 18.74MB,然后卡住了
  2. 加载了 5.05MB,然后卡住了
  3. 加载了 21.23MB,然后卡住了

第二个测试用例:Almunia - New Moon

来源:https://almunia.bandcamp.com/track/new-moon

三次尝试的结果:

  1. 加载了 12.78MB,然后卡住了
  2. 加载38.65,上传成功!
  3. 加载38.65,上传成功!

如您所见,至少可以说这种行为是不可预测的。此外,这两个成功的上传确实从 Firebase 存储无缝回放,因此它确实按预期工作。我不明白的是为什么它不能总是工作,或者至少在大多数时候不能工作,排除任何与网络相关的故障。

更新:2

我无可救药地试图弄清楚这个问题,所以我现在创建了一个与我的实际项目非常相似的场景,并将代码上传到 GitHub。它非常小,但我确实添加了一些额外的库以使前端使用起来愉快。

除了用于后端的 Express 服务器和用于前端的简单 Vue 应用程序之外,它没有太多内容。在 files 文件夹中,有两个 FLAC 文件;其中一个只有 4.42MB,证明代码有时确实有效。另一个文件要大得多,有 38.1MB,可以可靠地说明问题。随意尝试任何其他文件。

请注意,必须修改前端以允许 FLAC 文件以外的文件。我选择只接受 FLAC 文件,因为这是我在实际项目中使用的文件。

当 BusBoy 发出文件事件时,您需要直接写入文件。

如果您依赖 BusBoy,似乎存在竞争条件,阻止文件加载完成。如果您将它加载到 file 事件处理程序中,那么它可以正常工作。

app.post('/upload', (request, response) => {
  response.set('Access-Control-Allow-Origin', '*');
  const bb = busboy({
    headers: request.headers
  });
  const fileObject = {};
  let bytes = 0;
  bb.on('file', (name, file, info) => {
    const {
      filename,
      mimeType
    } = info;
    fileObject['mimeType'] = mimeType;
    fileObject['filePath'] = path.join(os.tmpdir(), filename);
    const saveTo = path.join(os.tmpdir(), filename);
    const writeStream = fs.createWriteStream(saveTo);
    file.on('data', (data) => {
      writeStream.write(data);
      console.log(`Received: ${((bytes += data.length) / 1000000).toFixed(2)}MB`);
    });
    file.on('end', () => {
      console.log('closing writeStream');
      writeStream.close()
    });
  });
  bb.on('close', () => {
    console.log(`Actual size is ${(fs.statSync(fileObject.filePath).size / 1000000).toFixed(2)}MB`);
    console.log('This is where the file would be uploaded to some cloud storage server...');
    response.send('File was uploaded');
  });
  bb.on('error', (error) => {
    console.log(error);
  });
  request.pipe(bb);
});