我正在尝试在Firebase函数中上传Google的文本转语音API返回的音频,但在将音频文件写入Node.js服务器的临时目录中时遇到问题。我在函数日志中收到以下错误:
写入错误: { 错误: ENOENT: 没有这样的文件或目录, 打开 '/tmp/synthesized/output.mp3' 在错误(本机( errno: -2, 代码: 'ENOENT', syscall: 'open', 路径: '/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方法不会等待。
解决方案是使用 Promise 或 async/await。查看 GoogleTextToSpeechClient
类文档,看起来synthesizeSpeech
方法支持这一点:
返回:承诺 ->数组。数组的第一个元素是表示 SynthesizeSpeechResponse 的对象。
例:
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
转换为承诺。但是您在评论中指出您正在使用节点 6,因此我们必须手动执行操作。从这个参考中窃取:
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
块。为了清楚起见,事情变得有点冗长。我相信你可以做得更好。