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

 ̄綄美尐妖づ 提交于 2019-12-08 03:45:20

问题


I am trying to upload the audio returned by Google's Text-to-Speech API in a Firebase Function and having trouble writing the audio file to the Node.js server's temp directory. I receive the following error in my functions log:

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' }

Here's my imports:

    // 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();

...and the part of my function I'm having trouble with:

    // 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);
        });

What is wrong with my temp directory creation or fs.writeFile() function?


回答1:


(Answer edited in response to question edit...)

In your original question, you invoked

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

following Node's http callback pattern, in which the callback function may initiate before the response is complete. Your subsequent code calls methods that assume response content; if the response is still empty, fs.writeFile() writes nothing initially, and subsequent methods cannot find the non-existent file. (Because fs.writeFile() follows the same callback pattern, you might even discover that output.mp3 file after the program exits, because fs will stream the input. But I bet your Firebase methods aren't waiting.)

The solution is to use Promises or async/await. Looking at the Google TextToSpeechClient class docs, it looks like the synthesizeSpeech method supports this:

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);
  });

That should solve the problem with client.synthesizeSpeech, but unfortunately fs.writeFile is still synchronous. If you were using Node >10 you could use a native fsPromise.writeFile method, and if you were using Node >8 you could use util.promisify() to convert fs.writeFile to promises. But you've indicated in comments that you are using Node 6, so we'll have to do things manually. Thieving from 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) });

I've written all of this using .then constructs, but naturally, you could also use async/await if you would rather do that. I hope this fixes things--it will force your Firebase code to wait until fs.writeFile has completed its job. I have also, unfortunately, smooshed all of the error checking into one final .catch block. And made things a bit verbose for clarity. I'm sure you can do better.



来源:https://stackoverflow.com/questions/52506550/upload-synthesized-speech-from-firebase-function-node-js-servers-tmp-directory

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!