用JavaScript五分钟开发一个文本转智能语音的应用

avatar
掘金前首席打杂官

在详细讲解之前,先看看最终的效果:

这个简单的应用只需要几分钟时间,你也可以搞定。

语音合成服务

语音合成服务有多种选择,比如阿里云、百度AI开放平台,AWS、七牛云等等,这里我选择的是七牛云,因为它价格便宜,而且API简单。

首先我们注册并登录七牛云,然后进入【个人中心>密钥管理】,生成一对密钥。

复制其中的AK(AccessKey)和SK(SecretKey),我们后续鉴权要使用。

接着我们可以自己写服务部署或者选择一个FaaS云服务。这里我选择AirCode,这是一个可以在线快速开发和部署云服务的平台。

然后我们创建一个AirCode项目:

image.png

在这个项目中,默认有一个hello.js,它是一个云函数,内容如下:

// @see https://docs.aircode.io/guide/functions/
const aircode = require('aircode');

module.exports = async function (params, context) {
  console.log('Received params:', params);
  return {
    message: 'Hi, AirCode.',
  };
};

我们把文件名修改成index.js(或者不改也行,只是影响部署后的path),然后改写内容。

首先我们需要安装一些依赖,我们在IED右下角的Dependencies下,安装node-fetch和qiniu这两个库。

image.png

然后我们配置七牛云的AK和SK。在IDE右上角的Environments中添加两个配置,将前面复制出来的七牛云密钥填入。

image.png

接下来,我们开始实现具体代码:

// @see https://docs.aircode.io/guide/functions/
const qiniu = require('qiniu');
const fetch = require('node-fetch');

const serviceURL = 'https://ap-gate-z0.qiniuapi.com/voice/v2/tts';

module.exports = async (params, context) => {
  const {spkid, content} = params;
  const mac = {accessKey: process.env.AK, secretKey: process.env.SK};
  const body = JSON.stringify({spkid, content});
  
  const authToken = qiniu.util.generateAccessTokenV2(mac, serviceURL, 'POST', 'application/json', body);

  const res = await (await fetch(serviceURL, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'authorization': authToken,
    },
    body,
  })).json();
  
  return res;
};

上面的代码非常简单,七牛云的语音合成API的服务地址是https://ap-gate-z0.qiniuapi.com/voice/v2/tts,具体用法在它的文档中心可以查到:

developer.qiniu.com/dora/8091/s…

首先我们需要鉴权,鉴权方法在七牛云文档中:

developer.qiniu.com/kodo/3702/Q…

不过我们不必那么麻烦,我们前面已经安装了qiniu库,这是官方的SDK,因此我们只需要调用qiniu.util.generateAccessTokenV2,就可以自动帮我们完成鉴权签名。

然后我们只需要带着签名请求头,请求serviceURL就可以获得我们想要的数据。

这时我们可以在IDE右侧调试区域进行调试,首先我们在Params中输入JSON数据:

{
    "skpid":"7",
    "content":"hi aircode"
}

然后点击右边的Debug按钮,等待片刻,就能收到返回的数据,其中的audioUrl就是合成后的音频信息,你可以将URL复制到浏览器地址栏播放。

image.png

缓存音频数据

因为接口返回的数据是http协议,在很多场景中受到限制,所以我们可以用AirCode自带的文件存储服务将它转存。

另外,如果请求参数相同,我们可以不再重复生成音频,而是事先将音频数据缓存在数据库中,把缓存的数据返回给用户,因为七牛云毕竟是按照合成服务次数收费的,缓存音频数据能省下一些费用。

具体做法也非常简单,我们修改一下实现代码:

// @see https://docs.aircode.io/guide/functions/
const {parse} = require('node:url');
const path = require('node:path');
const {db, files} = require('aircode');

const qiniu = require('qiniu');
const fetch = require('node-fetch');

const serviceURL = 'https://ap-gate-z0.qiniuapi.com/voice/v2/tts';

module.exports = async (params, context) => {
  const table = db.table('_files');
  const {spkid, content} = params;
  
  const body = JSON.stringify({spkid, content});

  const cached = await table.where({body}).findOne();
  if(cached) {
    return {code: "0", result: {audioUrl: cached.url}, msg: "from cache"};
  }

  const mac = {accessKey: process.env.AK, secretKey: process.env.SK};
  
  const authToken = qiniu.util.generateAccessTokenV2(mac, serviceURL, 'POST', 'application/json', body);

  const res = await (await fetch(serviceURL, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      'authorization': authToken,
    },
    body,
  })).json();

  if(res.code === "0") {
    let url = res.result.audioUrl;
    const filename = path.basename(parse(url).pathname);
    const file = await files.upload({ url }, filename, {
      additions: {
        body,
      }
    });
    url = file.url;
    res.result.audioUrl = url;
  }
  
  return res;
};

在AirCode中,提供了默认的文件存储API,我们只需要调用files.upload方法,就可以把指定url的文件存储起来。AirCode会把文件信息存储在数据库的_files表中。

image.png

我们在IDE下方的Database页下,可以看到_files表。注意到,我们用additions: {body},将body信息,即请求的参数JSON字符串,也存储在这个数据表中。这样我们可以通过const cached = await table.where({body}).findOne();来获取缓存的数据。

这样我们再运行Debug两次,就可以看到返回的数据是https协议的资源,而且是被缓存的。

image.png

部署应用

到这里,我们的服务端程序就写完了,我们可以部署应用。用AirCode部署应用特别简单,直接点击IDE上方的Deploy按钮,等待部署完成后,直接访问文件下方的URL就可以。

image.png

前端界面

到目前为止我们已经实现了服务端代码,实现前端也很简单,首先是HTML:

<textarea id="content">Lodash 是一个流行的 JavaScript 实用工具库,提供了许多常用的函数和工具,能够方便地处理集合、字符串、数值、函数等多种数据类型,减少编写重复代码的时间和精力。Lodash 的 API 设计与 ES6 的新特性相似,同时兼容了更早的浏览器版本。Lodash 支持模块化加载,可以通过 npm 或在浏览器中直接引入来使用。
</textarea>
<div>
  <button id="submitBtn">submit</button>
  <select id="spkid">
    <option value=7>女声:柔美</option>
    <option value=8>女声:西安方言</option>
    <option value=9>女声:东北方言</option>
    <option value=10>男声:播音员</option>
    <option value=11>男孩:活泼</option>
    <option value=12>男声:配音</option>
    <option value=13>男声:新闻联播</option>
    <option value=14>女声:少女</option>
  </select><span> <a target="blank" href="https://aircode.cool/pk7gjhdtat">参考代码</a></span>
  <div id="notice"></div>
</div>

然后是CSS:

textarea {
  width: 600px;
  height: 300px;
}

最后是JS:

// See https://aircode.cool/pk7gjhdtat
const url = 'https://1263b3ejfu.us.aircode.run/index';

submitBtn.addEventListener('click', async (evt) => {
  if(content.value) {
    const data = {content: content.value, spkid: spkid.value};
    notice.innerHTML = '音频合成中...';
    const res = await (await fetch(url, {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify(data),
    })).json();
    console.log(res);
    notice.innerHTML = `音频合成成功!<a href="${res.result.audioUrl}" target="_blank">${res.result.audioUrl}</a>`
  }
});

你也可以直接Fork码上掘金的代码 code.juejin.cn/pen/7255992…const url = 'https://1263b3ejfu.us.aircode.run/index' 的地址改成你自己部署生成的应用的地址。

这样就实现了文字转语音的应用,七牛云的收费标准是合成千次3元钱,还是非常便宜的,你可以用它来生成各种语音,再将它合成到视频中或者作为分享音频,还是很有用的。

你学会了吗?可以自己动手尝试一下。关于这部分内容有任何问题,欢迎留言讨论。