问题
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