从 Firebase 函数节点(服务器的 tmp 目录)上传合成语音.js



我正在尝试在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块。为了清楚起见,事情变得有点冗长。我相信你可以做得更好。

最新更新