从 firebase 函数上传合成语音 node.js 服务器的 tmp 目录

Upload synthesized speech from firebase function node.js server's tmp directory

我正在尝试在 Firebase 函数中上传由 Google 的文本转语音 API 返回的音频,但无法将音频文件写入 Node.js 服务器的临时目录。我在函数日志中收到以下错误:

Write ERROR: { Error: ENOENT: no such file or directory, open '/tmp/synthesized/output.mp3' at Error (native) errno: -2, code: 'ENOENT', syscall: 'open', path: '/tmp/synthesized/output.mp3' }

这是我的进口商品:

    // Cloud Storage
    import * as Storage from '@google-cloud/storage';
    const gcs = new Storage();

    import { tmpdir } from 'os';
    import { join, dirname } from 'path';
    import * as fs from 'fs';
    import * as fse from 'fs-extra';

    // Cloud Text to Speech
    import * as textToSpeech from '@google-cloud/text-to-speech';
    const client = new textToSpeech.TextToSpeechClient();

...以及我遇到问题的函数部分:

    // Construct the text-to-speech request
    const request = {
        input: { text: text },
        voice: { languageCode: 'en-US', ssmlGender: 'NEUTRAL' },
        audioConfig: { audioEncoding: 'MP3' },
    };

    // Creat temp directory
    const workingDir = join(tmpdir(), 'synthesized');
    const tmpFilePath = join(workingDir, 'output.mp3');

    // Ensure temp directory exists
    await fse.ensureDir(workingDir);

    // Performs the Text-to-Speech request
    client.synthesizeSpeech(request)
        .then(responses => {
            const response = responses[0];
            // Write the binary audio content to a local file in temp directory
            fs.writeFile(tmpFilePath, response.audioContent, 'binary', writeErr => {
                if (writeErr) {
                    console.error('Write ERROR:', writeErr);
                    return;
                }
                // Upload audio to Firebase Storage
                gcs.bucket(fileBucket).upload(tmpFilePath, {
                    destination: join(bucketDir, pageName)
                })
                    .then(() => { console.log('audio uploaded successfully') })
                    .catch((error) => { console.log(error) });
            });
        })
        .catch(err => {
            console.error('Synthesize ERROR:', err);
        });

我的临时目录创建或 fs.writeFile() 功能有什么问题?

(为响应问题编辑而编辑的答案...)

在你原来的问题中,你调用了

client.synthesizeSpeech(request, (err, response) => {...})

遵循 Node 的 http 回调模式,其中回调函数可能在响应完成之前启动。您的后续代码调用假定响应内容的方法;如果响应仍然为空,fs.writeFile() 最初什么也不写,后续方法无法找到不存在的文件。 (因为 fs.writeFile() 遵循相同的回调模式,您甚至可能会在程序退出后发现 output.mp3 文件,因为 fs 将流式传输输入。但我敢打赌您的 Firebase 方法不会等待.)

解决方案是使用 Promises 或 async/await。查看Google TextToSpeechClient class docs,好像synthesizeSpeech方法支持这个:

Returns: Promise -> Array. The first element of the array is an object representing SynthesizeSpeechResponse.

Example:

client.synthesizeSpeech(request)
  .then(responses => {
      var response = responses[0];
      // doThingsWith(response)
  })
  .catch(err => {
      console.error(err);
  });

那应该可以解决 client.synthesizeSpeech 的问题,但不幸的是 fs.writeFile 仍然是同步的。如果您使用 Node >10,则可以使用原生 fsPromise.writeFile 方法,如果您使用 Node >8,则可以使用 util.promisify()fs.writeFile 转换为 promises。但是您在评论中指出您正在使用 Node 6,因此我们必须手动执行操作。从 this reference 偷窃:

const writeFilePromise = (file, data, option) => {
    return new Promise((resolve, reject) => {
        fs.writeFile(file, data, option, error => {
            if (error) reject(error);
            resolve("File created! Time for the next step!");
        });
    });
};

client.synthesizeSpeech(request)
    .then(responses => {
        const response = responses[0];
        return writeFilePromise(tmpFilePath, response.audioContent, 'binary');
    })
    .then(() => {
        return gcs.bucket(fileBucket).upload(tmpFilePath, {
            destination: join(bucketDir, pageName)
        });
    })
    .then(() => {
        console.log('audio uploaded successfully');
        return null;
    })
    .catch((error) => { console.log(error) });

我已经使用 .then 结构编写了所有这些内容,但当然,如果您愿意,也可以使用 async/await。我希望这能解决问题——它会强制您的 Firebase 代码等待 fs.writeFile 完成其工作。不幸的是,我还把所有的错误检查都塞进了一个最后的 .catch 块中。为了清楚起见,让事情变得有点冗长。我相信你可以做得更好。