Whisper、React 和 Node 构建语音转文本 Web 应用程序(一)

简介: Whisper、React 和 Node 构建语音转文本 Web 应用程序(一)

在本文中,我们将使用 OpenAI 的 Whisper 以及 React、Node.js 和 FFmpeg 构建一个语音转文本应用程序。该应用程序将获取用户输入,使用 OpenAI 的 Whisper API 将其合成为语音,并输出结果文本。Whisper 提供了我用过的最准确的语音到文本转录,即使对于非英语母语人士也是如此。

介绍

OpenAI解释说,Whisper 是一种自动语音识别 (ASR) 系统,经过 680,000 小时从网络收集的多语言和多任务监督数据的训练。


文本比音频更容易搜索和存储。然而,将音频转录为文本可能非常费力。像 Whisper 这样的 ASR 可以检测语音,并非常快速地将音频转录为文本,非常准确,这使其成为一种特别有用的工具。

先决条件

本文面向熟悉 JavaScript 并且对 React 和 Express 有基本了解的开发人员。

如果您想一起构建,则需要 API 密钥。您可以通过在 OpenAI 平台上注册帐户来获取。获得 API 密钥后,请确保其安全并且不要公开共享。

技术堆栈

我们将使用 Create React App (CRA) 构建此应用程序的前端。我们在前端要做的就是上传文件、选择时间边界、发出网络请求和管理一些状态。为了简单起见,我选择了 CRA。随意使用您喜欢的任何前端库,甚至是普通的旧 JS。代码应该大部分是可转移的。


对于后端,我们将使用 Node.js 和 Express,这样我们就可以坚持使用此应用程序的完整 JS 堆栈。您可以使用 Fastify 或任何其他替代方案来代替 Express,并且您仍然应该能够遵循。


注意:为了使本文重点关注主题,将链接到长代码块,以便我们可以专注于手头的实际任务。

设置项目

我们首先创建一个新文件夹,其中包含用于组织目的的项目的前端和后端。请随意选择您喜欢的任何其他结构:

mkdir speech-to-text-app
cd speech-to-text-app

接下来,我们使用以下命令初始化一个新的 React 应用程序create-react-app

npx create-react-app frontend

导航到新frontend文件夹并安装以使用以下代码axios发出网络请求和文件上传:react-dropzone

cd frontend
npm install axios react-dropzone react-select react-toastify

现在,让我们切换回主文件夹并创建backend文件夹:

cd ..
mkdir backend
cd backend

接下来,我们在backend目录中初始化一个新的 Node 应用程序,同时安装所需的库:

npm init -y
npm install express dotenv cors multer form-data axios fluent-ffmpeg ffmetadata ffmpeg-static
npm install --save-dev nodemon

在上面的代码中,我们安装了以下库:


  • dotenv:有必要让我们的 OpenAI API 密钥远离源代码。
  • cors:启用跨域请求。
  • multer:用于上传音频文件的中间件。它将一个.fileor.files对象添加到请求对象,然后我们将在路由处理程序中访问该对象。
  • form-data:以编程方式创建带有文件上传和字段的表单并将其提交到服务器。
  • axios:向 Whisper 端点发出网络请求。

另外,由于我们将使用 FFmpeg 进行音频修剪,因此我们有这些库:


  • fluent-ffmpeg:这提供了一个流畅的 API 来与 FFmpeg 工具配合使用,我们将使用它来进行音频修剪。
  • ffmetadata:这用于读取和写入媒体文件中的元数据。我们需要它来检索音频持续时间。
  • ffmpeg-static:这为不同平台提供静态 FFmpeg 二进制文件,并简化了 FFmpeg 的部署。

Node.js 应用程序的入口文件是index.js. 在文件夹内创建文件backend并在代码编辑器中打开它。让我们连接一个基本的 Express 服务器:

const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
app.get('/', (req, res) => {
  res.send('Welcome to the Speech-to-Text API!');
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

更新package.json文件夹backend以包含启动和开发脚本:

"scripts": {
  "start": "node index.js",
  "dev": "nodemon index.js",
}

上面的代码只是注册了一个简单的GET路由。当我们运行npm run dev并前往localhost:3001或无论我们的端口是什么时,我们应该看到欢迎文本。

整合耳语

现在是时候添加秘制酱汁了!在本节中,我们将:


  • POST接受路由上的文件上传
  • 将文件转换为可读流
  • 非常重要的是,将文件发送到 Whisper 进行转录
  • 以 JSON 形式发送回响应

现在让我们.env在文件夹的根目录创建一个文件backend来存储我们的 API 密钥,并记住将其添加到gitignore:

OPENAI_API_KEY=YOUR_API_KEY_HERE

首先,让我们导入一些更新文件上传、网络请求和流媒体所需的库:

const  multer  =  require('multer')
const  FormData  =  require('form-data');
const { Readable } =  require('stream');
const  axios  =  require('axios');
const  upload  =  multer();

接下来,我们将创建一个简单的实用程序函数,将文件缓冲区转换为可读流,并将其发送到 Whisper:

const  bufferToStream  = (buffer) => {
  return  Readable.from(buffer);
}

我们将创建一个新路由 ,/api/transcribe并使用 axios 向 OpenAI 发出请求。

首先,axios在文件顶部导入app.jsconst axios = require('axios');.

然后,创建新路线,如下所示:

app.post('/api/transcribe', upload.single('file'), async (req, res) => {
  try {
    const  audioFile  = req.file;
    if (!audioFile) {
      return res.status(400).json({ error: 'No audio file provided' });
    }
    const  formData  =  new  FormData();
    const  audioStream  =  bufferToStream(audioFile.buffer);
    formData.append('file', audioStream, { filename: 'audio.mp3', contentType: audioFile.mimetype });
    formData.append('model', 'whisper-1');
    formData.append('response_format', 'json');
    const  config  = {
      headers: {
        "Content-Type": `multipart/form-data; boundary=${formData._boundary}`,
        "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
      },
    };
    // Call the OpenAI Whisper API to transcribe the audio
    const  response  =  await axios.post('https://api.openai.com/v1/audio/transcriptions', formData, config);
    const  transcription  = response.data.text;
    res.json({ transcription });
  } catch (error) {
    res.status(500).json({ error: 'Error transcribing audio' });
  }
});

在上面的代码中,我们使用实用程序函数bufferToStream将音频文件缓冲区转换为可读流,然后通过网络请求将其发送到 Whisper 和await响应,然后将响应作为响应发回JSON

您可以查看文档以了解有关 Whisper 请求和响应的更多信息。

安装 FFmpeg

我们将在下面添加附加功能,以允许用户转录部分音频。为此,我们的 API 端点将接受startTimeendTime,之后我们将使用 修剪音频ffmpeg

安装适用于 Windows 的 FFmpeg

要安装 Windows 版 FFmpeg,请按照以下简单步骤操作:

  1. 在这里问 FFmpeg 官方网站的下载页面。
  2. Windows 图标下有几个链接。选择 gyan.dev 提供的“Windows Builds”链接。
  3. 下载与我们的系统(32 或 64 位)相对应的版本。确保下载“静态”版本以获取包含的所有库。
  4. 解压缩下载的 ZIP 文件。我们可以将提取的文件夹放置在我们喜欢的任何位置。
  5. 要从命令行使用 FFmpeg 而无需导航到其文件夹,请将 FFmpegbin文件夹添加到系统 PATH。

为 macOS 安装 FFmpeg

如果我们在 macOS 上,我们可以使用 Homebrew 安装 FFmpeg:

brew install ffmpeg

为 Linux 安装 FFmpeg

如果我们在 Linux 上,我们可以使用aptdnf或来安装 FFmpeg pacman,具体取决于我们的 Linux 发行版。这是安装命令apt

sudo apt update
sudo apt install ffmpeg

修剪代码中的音频

为什么我们需要修剪音频?假设用户有一个长达一小时的音频文件,并且只想从 15 分钟标记转录到 45 分钟标记。使用 FFmpeg,我们可以修剪到精确的startTimeendTime,然后将修剪后的流发送到 Whisper 进行转录。

首先,我们将导入以下库:

const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('ffmpeg-static');
const ffmetadata = require('ffmetadata');
const fs  =  require('fs');
ffmpeg.setFfmpegPath(ffmpegPath);
  • fluent-ffmpeg是一个 Node.js 模块,提供与 FFmpeg 交互的流畅 API。
  • ffmetadata将用于读取音频文件的元数据 - 具体来说,duration.
  • ffmpeg.setFfmpegPath(ffmpegPath)用于显式设置 FFmpeg 二进制文件的路径。

接下来,让我们创建一个实用函数,将传递的时间转换为mm:ss秒。这可以在我们的app.post路线之外,就像bufferToStream函数一样:

/**
 * Convert time string of the format 'mm:ss' into seconds.
 * @param {string} timeString - Time string in the format 'mm:ss'.
 * @return {number} - The time in seconds.
 */
const parseTimeStringToSeconds = timeString => {
    const [minutes, seconds] = timeString.split(':').map(tm => parseInt(tm));
    return minutes * 60 + seconds;
}

接下来,我们应该更新我们的app.post路线以执行以下操作:

  • 接受startTimeendTime
  • 计算持续时间
  • 处理基本的错误处理
  • 将音频缓冲区转换为流
  • 使用 FFmpeg 修剪音频
  • 将修剪后的音频发送至 OpenAI 进行转录

trimAudio函数在指定的开始时间和结束时间之间修剪音频流,并返回一个使用修剪后的音频数据进行解析的承诺。如果在此过程中的任何一点发生错误,则 Promise 将因该错误而被拒绝。

让我们逐步分解该功能。

  1. 定义修剪音频功能。该trimAudio函数是异步的,接受audioStreamendTime作为参数。我们定义用于处理音频的临时文件名:
const trimAudio = async (audioStream, endTime) => {
    const tempFileName = `temp-${Date.now()}.mp3`;
    const outputFileName = `output-${Date.now()}.mp3`;

将流写入临时文件。我们使用 将传入的音频流写入临时文件fs.createWriteStream()。如果出现错误,则会Promise被拒绝:

return new Promise((resolve, reject) => {
    audioStream.pipe(fs.createWriteStream(tempFileName))

读取元数据并设置 endTime。音频流完成写入临时文件后,我们使用 读取文件的元数据ffmetadata.read()。如果提供的时间endTime长于音频持续时间,我们将调整endTime为音频的持续时间:

.on('finish', () => {
    ffmetadata.read(tempFileName, (err, metadata) => {
        if (err) reject(err);
        const duration = parseFloat(metadata.duration);
        if (endTime > duration) endTime = duration;

使用 FFmpeg 修剪音频。我们利用 FFmpeg 根据startSeconds接收到的开始时间 () 和timeDuration之前计算的持续时间 () 来修剪音频。修剪后的音频将写入输出文件:

ffmpeg(tempFileName)
    .setStartTime(startSeconds)
    .setDuration(timeDuration)
    .output(outputFileName)

删除临时文件并解决承诺。修剪音频后,我们删除临时文件并将修剪后的音频读入缓冲区。将输出文件读取到缓冲区后,我们还使用 Node.js 文件系统将其删除。如果一切顺利,问题Promise就会得到解决trimmedAudioBuffer。如果出现错误,则会Promise被拒绝:

.on('end', () => {
    fs.unlink(tempFileName, (err) => {
        if (err) console.error('Error deleting temp file:', err);
    });const trimmedAudioBuffer = fs.readFileSync(outputFileName);
fs.unlink(outputFileName, (err) => {
    if (err) console.error('Error deleting output file:', err);
});
resolve(trimmedAudioBuffer);
})
.on('error', reject)
.run();

端点的完整代码可在此GitHub 存储库中找到。

相关文章
|
4月前
|
存储 JavaScript 前端开发
掌握现代Web开发的基石:深入理解React与Redux
【10月更文挑战第14天】掌握现代Web开发的基石:深入理解React与Redux
70 0
|
4月前
|
移动开发 前端开发 JavaScript
前端开发实战:利用Web Speech API之speechSynthesis实现文字转语音功能
前端开发实战:利用Web Speech API之speechSynthesis实现文字转语音功能
548 0
|
4月前
|
前端开发 JavaScript UED
构建现代Web应用:使用React框架打造单页面应用
【10月更文挑战第9天】构建现代Web应用:使用React框架打造单页面应用
|
4月前
|
前端开发 JavaScript 测试技术
构建响应式Web应用程序:React实战指南
【10月更文挑战第9天】构建响应式Web应用程序:React实战指南
|
4月前
|
前端开发 JavaScript 开发者
探索现代Web前端技术:React框架入门
【10月更文挑战第9天】 探索现代Web前端技术:React框架入门
|
4月前
|
人工智能 搜索推荐 API
用于企业AI搜索的Bocha Web Search API,给LLM提供联网搜索能力和长文本上下文
博查Web Search API是由博查提供的企业级互联网网页搜索API接口,允许开发者通过编程访问博查搜索引擎的搜索结果和相关信息,实现在应用程序或网站中集成搜索功能。该API支持近亿级网页内容搜索,适用于各类AI应用、RAG应用和AI Agent智能体的开发,解决数据安全、价格高昂和内容合规等问题。通过注册博查开发者账户、获取API KEY并调用API,开发者可以轻松集成搜索功能。
|
4月前
|
存储 JavaScript 前端开发
如何使用React和Redux构建现代化Web应用程序
【10月更文挑战第4天】如何使用React和Redux构建现代化Web应用程序
|
5月前
|
前端开发
【前端web入门第三天】02 CSS字体和文本
本文详细介绍了CSS中字体和文本的相关属性。字体部分涵盖字体大小、粗细、样式、行高、字体族及`font`复合属性,通过具体示例展示了如何设置和使用这些属性。文本部分则讲解了文本缩进、对齐方式、修饰线及文字颜色等属性,并提供了实用的代码示例。此外,还简要介绍了调试工具中的一些细节,如错误属性标识和属性生效状态的控制。
94 28
|
6月前
|
开发者 缓存 数据库
【性能奇迹】Wicket应用的极速重生:揭秘那些让开发者心跳加速的调优秘技!
【8月更文挑战第31天】在软件开发中,性能优化是确保应用快速响应和高效运行的关键。本书《性能调优:Apache Wicket应用的速度提升秘籍》详细介绍了如何优化Apache Wicket应用,包括代码优化、资源管理、数据库查询优化、缓存策略及服务器配置等方面。通过减少不必要的组件渲染、优化SQL查询、使用缓存和调整服务器设置等方法,本书帮助开发者显著提升Wicket应用的性能,确保其在高并发和数据密集型场景下的稳定性和响应速度。
55 0
|
6月前
|
Java 前端开发 Spring
技术融合新潮流!Vaadin携手Spring Boot、React、Angular,引领Web开发变革,你准备好了吗?
【8月更文挑战第31天】本文探讨了Vaadin与Spring Boot、React及Angular等主流技术栈的最佳融合实践。Vaadin作为现代Java Web框架,与其他技术栈结合能更好地满足复杂应用需求。文中通过示例代码展示了如何在Spring Boot项目中集成Vaadin,以及如何在Vaadin项目中使用React和Angular组件,充分发挥各技术栈的优势,提升开发效率和用户体验。开发者可根据具体需求选择合适的技术组合。
130 0