在本文中,我們將使用 OpenAI 的 Whisper 以及 React、Node.js 和 FFmpeg 構(gòu)建一個(gè)語(yǔ)音轉(zhuǎn)文本應(yīng)用程序。該應(yīng)用程序?qū)@取用戶輸入,使用 OpenAI 的 Whisper API 將其合成為語(yǔ)音,并輸出結(jié)果文本。Whisper 提供了我用過(guò)的最準(zhǔn)確的語(yǔ)音到文本轉(zhuǎn)錄,即使對(duì)于非英語(yǔ)母語(yǔ)人士也是如此。
介紹
OpenAI解釋說(shuō),Whisper 是一種自動(dòng)語(yǔ)音識(shí)別 (ASR) 系統(tǒng),經(jīng)過(guò) 680,000 小時(shí)從網(wǎng)絡(luò)收集的多語(yǔ)言和多任務(wù)監(jiān)督數(shù)據(jù)的訓(xùn)練。
文本比音頻更容易搜索和存儲(chǔ)。然而,將音頻轉(zhuǎn)錄為文本可能非常費(fèi)力。像 Whisper 這樣的 ASR 可以檢測(cè)語(yǔ)音,并非常快速地將音頻轉(zhuǎn)錄為文本,非常準(zhǔn)確,這使其成為一種特別有用的工具。
先決條件
本文面向熟悉 JavaScript 并且對(duì) React 和 Express 有基本了解的開(kāi)發(fā)人員。
如果您想一起構(gòu)建,則需要 API 密鑰。您可以通過(guò)在 OpenAI 平臺(tái)上注冊(cè)帳戶來(lái)獲取。獲得 API 密鑰后,請(qǐng)確保其安全并且不要公開(kāi)共享。
技術(shù)堆棧
我們將使用 Create React App (CRA) 構(gòu)建此應(yīng)用程序的前端。我們?cè)谇岸艘龅木褪巧蟼魑募?、選擇時(shí)間邊界、發(fā)出網(wǎng)絡(luò)請(qǐng)求和管理一些狀態(tài)。為了簡(jiǎn)單起見(jiàn),我選擇了 CRA。隨意使用您喜歡的任何前端庫(kù),甚至是普通的舊 JS。代碼應(yīng)該大部分是可轉(zhuǎn)移的。
對(duì)于后端,我們將使用 Node.js 和 Express,這樣我們就可以堅(jiān)持使用此應(yīng)用程序的完整 JS 堆棧。您可以使用 Fastify 或任何其他替代方案來(lái)代替 Express,并且您仍然應(yīng)該能夠遵循。
注意:為了使本文重點(diǎn)關(guān)注主題,將鏈接到長(zhǎng)代碼塊,以便我們可以專注于手頭的實(shí)際任務(wù)。
設(shè)置項(xiàng)目
我們首先創(chuàng)建一個(gè)新文件夾,其中包含用于組織目的的項(xiàng)目的前端和后端。請(qǐng)隨意選擇您喜歡的任何其他結(jié)構(gòu):
mkdir speech-to-text-app
cd speech-to-text-app
接下來(lái),我們使用以下命令初始化一個(gè)新的 React 應(yīng)用程序create-react-app
:
npx create-react-app frontend
導(dǎo)航到新frontend
文件夾并安裝以使用以下代碼axios
發(fā)出網(wǎng)絡(luò)請(qǐng)求和文件上傳:react-dropzone
cd frontend
npm install axios react-dropzone react-select react-toastify
現(xiàn)在,讓我們切換回主文件夾并創(chuàng)建backend
文件夾:
cd ..
mkdir backend
cd backend
接下來(lái),我們?cè)?code>backend目錄中初始化一個(gè)新的 Node 應(yīng)用程序,同時(shí)安裝所需的庫(kù):
npm init -y
npm install express dotenv cors multer form-data axios fluent-ffmpeg ffmetadata ffmpeg-static
npm install --save-dev nodemon
在上面的代碼中,我們安裝了以下庫(kù):
-
dotenv
:有必要讓我們的 OpenAI API 密鑰遠(yuǎn)離源代碼。 -
cors
:?jiǎn)⒂每缬蛘?qǐng)求。 -
multer
:用于上傳音頻文件的中間件。它將一個(gè).file
or.files
對(duì)象添加到請(qǐng)求對(duì)象,然后我們將在路由處理程序中訪問(wèn)該對(duì)象。 -
form-data
:以編程方式創(chuàng)建帶有文件上傳和字段的表單并將其提交到服務(wù)器。 -
axios
:向 Whisper 端點(diǎn)發(fā)出網(wǎng)絡(luò)請(qǐng)求。
另外,由于我們將使用 FFmpeg 進(jìn)行音頻修剪,因此我們有這些庫(kù):
-
fluent-ffmpeg
:這提供了一個(gè)流暢的 API 來(lái)與 FFmpeg 工具配合使用,我們將使用它來(lái)進(jìn)行音頻修剪。 -
ffmetadata
:這用于讀取和寫(xiě)入媒體文件中的元數(shù)據(jù)。我們需要它來(lái)檢索音頻持續(xù)時(shí)間。 -
ffmpeg-static
:這為不同平臺(tái)提供靜態(tài) FFmpeg 二進(jìn)制文件,并簡(jiǎn)化了 FFmpeg 的部署。
Node.js 應(yīng)用程序的入口文件是index.js
.?在文件夾內(nèi)創(chuàng)建文件backend
并在代碼編輯器中打開(kāi)它。讓我們連接一個(gè)基本的 Express 服務(wù)器:
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
以包含啟動(dòng)和開(kāi)發(fā)腳本:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
}
上面的代碼只是注冊(cè)了一個(gè)簡(jiǎn)單的GET
路由。當(dāng)我們運(yùn)行npm run dev
并前往localhost:3001
或無(wú)論我們的端口是什么時(shí),我們應(yīng)該看到歡迎文本。
整合耳語(yǔ)
現(xiàn)在是時(shí)候添加秘制醬汁了!在本節(jié)中,我們將:
-
POST
接受路由上的文件上傳 - 將文件轉(zhuǎn)換為可讀流
- 非常重要的是,將文件發(fā)送到 Whisper 進(jìn)行轉(zhuǎn)錄
- 以 JSON 形式發(fā)送回響應(yīng)
現(xiàn)在讓我們.env
在文件夾的根目錄創(chuàng)建一個(gè)文件backend
來(lái)存儲(chǔ)我們的 API 密鑰,并記住將其添加到gitignore
:
OPENAI_API_KEY=YOUR_API_KEY_HERE
首先,讓我們導(dǎo)入一些更新文件上傳、網(wǎng)絡(luò)請(qǐng)求和流媒體所需的庫(kù):
const multer = require('multer')
const FormData = require('form-data');
const { Readable } = require('stream');
const axios = require('axios');
const upload = multer();
接下來(lái),我們將創(chuàng)建一個(gè)簡(jiǎn)單的實(shí)用程序函數(shù),將文件緩沖區(qū)轉(zhuǎn)換為可讀流,并將其發(fā)送到 Whisper:
const bufferToStream = (buffer) => {
return Readable.from(buffer);
}
我們將創(chuàng)建一個(gè)新路由 ,/api/transcribe
并使用 axios 向 OpenAI 發(fā)出請(qǐng)求。
首先,axios
在文件頂部導(dǎo)入app.js
:const axios = require('axios');
.
然后,創(chuàng)建新路線,如下所示:
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' });
}
});
在上面的代碼中,我們使用實(shí)用程序函數(shù)bufferToStream
將音頻文件緩沖區(qū)轉(zhuǎn)換為可讀流,然后通過(guò)網(wǎng)絡(luò)請(qǐng)求將其發(fā)送到 Whisper 和await
響應(yīng),然后將響應(yīng)作為響應(yīng)發(fā)回JSON
。
您可以查看文檔以了解有關(guān) Whisper 請(qǐng)求和響應(yīng)的更多信息。
安裝 FFmpeg
我們將在下面添加附加功能,以允許用戶轉(zhuǎn)錄部分音頻。為此,我們的 API 端點(diǎn)將接受startTime
和endTime
,之后我們將使用 修剪音頻ffmpeg
。
安裝適用于 Windows 的 FFmpeg
要安裝 Windows 版 FFmpeg,請(qǐng)按照以下簡(jiǎn)單步驟操作:
- 在這里訪問(wèn) FFmpeg 官方網(wǎng)站的下載頁(yè)面。
- Windows 圖標(biāo)下有幾個(gè)鏈接。選擇 gyan.dev 提供的“Windows Builds”鏈接。
- 下載與我們的系統(tǒng)(32 或 64 位)相對(duì)應(yīng)的版本。確保下載“靜態(tài)”版本以獲取包含的所有庫(kù)。
- 解壓縮下載的 ZIP 文件。我們可以將提取的文件夾放置在我們喜歡的任何位置。
- 要從命令行使用 FFmpeg 而無(wú)需導(dǎo)航到其文件夾,請(qǐng)將 FFmpeg
bin
文件夾添加到系統(tǒng) PATH。
為 macOS 安裝 FFmpeg
如果我們?cè)?macOS 上,我們可以使用 Homebrew 安裝 FFmpeg:
brew install ffmpeg
為 Linux 安裝 FFmpeg
如果我們?cè)?Linux 上,我們可以使用apt
、dnf
或來(lái)安裝 FFmpeg?pacman
,具體取決于我們的 Linux 發(fā)行版。這是安裝命令apt
:
sudo apt update
sudo apt install ffmpeg
修剪代碼中的音頻
為什么我們需要修剪音頻?假設(shè)用戶有一個(gè)長(zhǎng)達(dá)一小時(shí)的音頻文件,并且只想從 15 分鐘標(biāo)記轉(zhuǎn)錄到 45 分鐘標(biāo)記。使用 FFmpeg,我們可以修剪到精確的startTime
和endTime
,然后將修剪后的流發(fā)送到 Whisper 進(jìn)行轉(zhuǎn)錄。
首先,我們將導(dǎo)入以下庫(kù):
const ffmpeg = require('fluent-ffmpeg');
const ffmpegPath = require('ffmpeg-static');
const ffmetadata = require('ffmetadata');
const fs = require('fs');
ffmpeg.setFfmpegPath(ffmpegPath);
-
fluent-ffmpeg
是一個(gè) Node.js 模塊,提供與 FFmpeg 交互的流暢 API。 -
ffmetadata
將用于讀取音頻文件的元數(shù)據(jù) - 具體來(lái)說(shuō),duration
. -
ffmpeg.setFfmpegPath(ffmpegPath)
用于顯式設(shè)置 FFmpeg 二進(jìn)制文件的路徑。
接下來(lái),讓我們創(chuàng)建一個(gè)實(shí)用函數(shù),將傳遞的時(shí)間轉(zhuǎn)換為mm:ss
秒。這可以在我們的app.post
路線之外,就像bufferToStream
函數(shù)一樣:
/**
* 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;
}
接下來(lái),我們應(yīng)該更新我們的app.post
路線以執(zhí)行以下操作:
- 接受
startTime
和endTime
- 計(jì)算持續(xù)時(shí)間
- 處理基本的錯(cuò)誤處理
- 將音頻緩沖區(qū)轉(zhuǎn)換為流
- 使用 FFmpeg 修剪音頻
- 將修剪后的音頻發(fā)送至 OpenAI 進(jìn)行轉(zhuǎn)錄
該trimAudio
函數(shù)在指定的開(kāi)始時(shí)間和結(jié)束時(shí)間之間修剪音頻流,并返回一個(gè)使用修剪后的音頻數(shù)據(jù)進(jìn)行解析的承諾。如果在此過(guò)程中的任何一點(diǎn)發(fā)生錯(cuò)誤,則 Promise 將因該錯(cuò)誤而被拒絕。
讓我們逐步分解該功能。
-
定義修剪音頻功能。該
trimAudio
函數(shù)是異步的,接受audioStream
和endTime
作為參數(shù)。我們定義用于處理音頻的臨時(shí)文件名:const trimAudio = async (audioStream, endTime) => { const tempFileName = `temp-${Date.now()}.mp3`; const outputFileName = `output-${Date.now()}.mp3`;
-
將流寫(xiě)入臨時(shí)文件。我們使用 將傳入的音頻流寫(xiě)入臨時(shí)文件
fs.createWriteStream()
。如果出現(xiàn)錯(cuò)誤,則會(huì)Promise
被拒絕:return new Promise((resolve, reject) => { audioStream.pipe(fs.createWriteStream(tempFileName))
-
讀取元數(shù)據(jù)并設(shè)置 endTime。音頻流完成寫(xiě)入臨時(shí)文件后,我們使用 讀取文件的元數(shù)據(jù)
ffmetadata.read()
。如果提供的時(shí)間endTime
長(zhǎng)于音頻持續(xù)時(shí)間,我們將調(diào)整endTime
為音頻的持續(xù)時(shí)間:.on('finish', () => { ffmetadata.read(tempFileName, (err, metadata) => { if (err) reject(err); const duration = parseFloat(metadata.duration); if (endTime > duration) endTime = duration;
-
使用 FFmpeg 修剪音頻。我們利用 FFmpeg 根據(jù)
startSeconds
接收到的開(kāi)始時(shí)間 () 和timeDuration
之前計(jì)算的持續(xù)時(shí)間 () 來(lái)修剪音頻。修剪后的音頻將寫(xiě)入輸出文件:ffmpeg(tempFileName) .setStartTime(startSeconds) .setDuration(timeDuration) .output(outputFileName)
-
刪除臨時(shí)文件并解決承諾。修剪音頻后,我們刪除臨時(shí)文件并將修剪后的音頻讀入緩沖區(qū)。將輸出文件讀取到緩沖區(qū)后,我們還使用 Node.js 文件系統(tǒng)將其刪除。如果一切順利,問(wèn)題
Promise
就會(huì)得到解決trimmedAudioBuffer
。如果出現(xiàn)錯(cuò)誤,則會(huì)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();
端點(diǎn)的完整代碼可在此GitHub 存儲(chǔ)庫(kù)中找到。
前端
樣式將使用 Tailwind 完成,但我不會(huì)介紹如何設(shè)置 Tailwind。您可以在此處閱讀有關(guān)如何設(shè)置和使用 Tailwind 的信息。
創(chuàng)建 TimePicker 組件
由于我們的 API 接受startTime
和endTime
,所以讓我們使用TimePicker
來(lái)創(chuàng)建一個(gè)組件react-select
。
使用react-select
只是將其他功能添加到選擇菜單中,例如搜索選項(xiàng),但這對(duì)本文并不重要,可以跳過(guò)。
讓我們分解一下TimePicker
下面的 React 組件:
-
進(jìn)口和組件申報(bào)。首先,我們導(dǎo)入必要的包并聲明我們的
TimePicker
組件。該TimePicker
組件接受 props?id
、label
、value
、onChange
和maxDuration
:import React, { useState, useEffect, useCallback } from 'react'; import Select from 'react-select'; const TimePicker = ({ id, label, value, onChange, maxDuration }) => {
-
解析
value
prop。該value
prop 預(yù)計(jì)是一個(gè)時(shí)間字符串(格式HH:MM:SS
)。這里我們將時(shí)間分為小時(shí)、分鐘和秒:const [hours, minutes, seconds] = value.split(':').map((v) => parseInt(v, 10));
-
計(jì)算最大值。
maxDuration
是根據(jù)音頻持續(xù)時(shí)間可以選擇的最大時(shí)間(以秒為單位)。它被轉(zhuǎn)換為小時(shí)、分鐘和秒:const validMaxDuration = maxDuration === Infinity ? 0 : maxDuration const maxHours = Math.floor(validMaxDuration / 3600); const maxMinutes = Math.floor((validMaxDuration % 3600) / 60); const maxSeconds = Math.floor(validMaxDuration % 60);
-
時(shí)間選項(xiàng)選擇。我們?yōu)榭赡艿男r(shí)、分鐘和秒選項(xiàng)創(chuàng)建數(shù)組,并創(chuàng)建狀態(tài)掛鉤來(lái)管理分鐘和秒選項(xiàng):
const hoursOptions = Array.from({ length: Math.max(0, maxHours) + 1 }, (_, i) => i); const minutesSecondsOptions = Array.from({ length: 60 }, (_, i) => i); const [minuteOptions, setMinuteOptions] = useState(minutesSecondsOptions); const [secondOptions, setSecondOptions] = useState(minutesSecondsOptions);
-
更新值函數(shù)。
onChange
該函數(shù)通過(guò)調(diào)用作為 prop 傳入的函數(shù)來(lái)更新當(dāng)前值:const updateValue = (newHours, newMinutes, newSeconds) => { onChange(`${String(newHours).padStart(2, '0')}:${String(newMinutes).padStart(2, '0')}:${String(newSeconds).padStart(2, '0')}`); };
-
更新分秒選項(xiàng)功能。此功能根據(jù)所選的小時(shí)和分鐘更新分鐘和秒選項(xiàng):
const updateMinuteAndSecondOptions = useCallback((newHours, newMinutes) => { const minutesSecondsOptions = Array.from({ length: 60 }, (_, i) => i); let newMinuteOptions = minutesSecondsOptions; let newSecondOptions = minutesSecondsOptions; if (newHours === maxHours) { newMinuteOptions = Array.from({ length: Math.max(0, maxMinutes) + 1 }, (_, i) => i); if (newMinutes === maxMinutes) { newSecondOptions = Array.from({ length: Math.max(0, maxSeconds) + 1 }, (_, i) => i); } } setMinuteOptions(newMinuteOptions); setSecondOptions(newSecondOptions); }, [maxHours, maxMinutes, maxSeconds]);
-
效果掛鉤。這會(huì)調(diào)用
updateMinuteAndSecondOptions
何時(shí)hours
或minutes
更改:useEffect(() => { updateMinuteAndSecondOptions(hours, minutes); }, [hours, minutes, updateMinuteAndSecondOptions]);
-
輔助功能。這兩個(gè)輔助函數(shù)將時(shí)間整數(shù)轉(zhuǎn)換為選擇選項(xiàng),反之亦然:
const toOption = (value) => ({ value: value, label: String(value).padStart(2, '0'), }); const fromOption = (option) => option.value;
-
渲染。該
render
函數(shù)顯示時(shí)間選擇器,它由庫(kù)管理的三個(gè)下拉菜單(小時(shí)、分鐘、秒)組成react-select
。更改選擇框中的值將調(diào)用updateValue
和updateMinuteAndSecondOptions
,這已在上面進(jìn)行了解釋。
您可以在GitHub上找到 TimePicker 組件的完整源代碼。
主要成分
現(xiàn)在讓我們通過(guò)替換 來(lái)構(gòu)建主要的前端組件App.js
。
應(yīng)用程序組件將實(shí)現(xiàn)具有以下功能的轉(zhuǎn)錄頁(yè)面:
- 定義時(shí)間格式轉(zhuǎn)換的輔助函數(shù)。
- 更新
startTime
并endTime
基于TimePicker
組件的選擇。 - 定義一個(gè)
getAudioDuration
函數(shù)來(lái)檢索音頻文件的持續(xù)時(shí)間并更新audioDuration
狀態(tài)。 - 處理要轉(zhuǎn)錄的音頻文件的文件上傳。
- 定義一個(gè)
transcribeAudio
函數(shù),通過(guò)向我們的 API 發(fā)出 HTTP POST 請(qǐng)求來(lái)發(fā)送音頻文件。 - 渲染文件上傳的 UI。
-
TimePicker
用于選擇startTime
和 的渲染組件endTime
。 - 顯示通知消息。
- 顯示轉(zhuǎn)錄的文本。
讓我們將該組件分解為幾個(gè)較小的部分:
-
導(dǎo)入和輔助函數(shù)。導(dǎo)入必要的模塊并定義時(shí)間轉(zhuǎn)換的輔助函數(shù):
import React, { useState, useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; // for file upload import axios from 'axios'; // to make network request import TimePicker from './TimePicker'; // our custom TimePicker import { toast, ToastContainer } from 'react-toastify'; // for toast notification // Helper functions (timeToSeconds, secondsToTime, timeToMinutesAndSeconds)
-
組件聲明和狀態(tài)掛鉤。聲明
TranscriptionPage
組件并初始化狀態(tài)掛鉤:const TranscriptionPage = () => { const [uploading, setUploading] = useState(false); const [transcription, setTranscription] = useState(''); const [audioFile, setAudioFile] = useState(null); const [startTime, setStartTime] = useState('00:00:00'); const [endTime, setEndTime] = useState('00:10:00'); // 10 minutes default endtime const [audioDuration, setAudioDuration] = useState(null); // ...
-
事件處理程序。定義各種事件處理程序 - 用于處理開(kāi)始時(shí)間更改、獲取音頻持續(xù)時(shí)間、處理文件刪除和轉(zhuǎn)錄音頻:
const handleStartTimeChange = (newStartTime) => { //... }; const getAudioDuration = (file) => { //... }; const onDrop = useCallback((acceptedFiles) => { //... }, []); const transcribeAudio = async () => { // we'll explain this in detail shortly //... };
-
使用 Dropzone 掛鉤。使用庫(kù)
useDropzone
中的鉤子react-dropzone
來(lái)處理文件丟失:const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ onDrop, accept: 'audio/*', });
-
渲染。最后,渲染組件。這包括用于文件上傳的拖放區(qū)、
TimePicker
用于設(shè)置開(kāi)始和結(jié)束時(shí)間的組件、用于啟動(dòng)轉(zhuǎn)錄過(guò)程的按鈕以及用于顯示轉(zhuǎn)錄結(jié)果的顯示。
該transcribeAudio
函數(shù)是一個(gè)異步函數(shù),負(fù)責(zé)將音頻文件發(fā)送到服務(wù)器進(jìn)行轉(zhuǎn)錄。讓我們來(lái)分解一下:
const transcribeAudio = async () => {
setUploading(true);
try {
const formData = new FormData();
audioFile && formData.append('file', audioFile);
formData.append('startTime', timeToMinutesAndSeconds(startTime));
formData.append('endTime', timeToMinutesAndSeconds(endTime));
const response = await axios.post(`http://localhost:3001/api/transcribe`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
setTranscription(response.data.transcription);
toast.success('Transcription successful.')
} catch (error) {
toast.error('An error occurred during transcription.');
} finally {
setUploading(false);
}
};
下面是更詳細(xì)的介紹:
-
setUploading(true);
。此行將uploading
狀態(tài)設(shè)置為true
,我們用它來(lái)向用戶指示轉(zhuǎn)錄過(guò)程已經(jīng)開(kāi)始。 -
const formData = new FormData();
。FormData
是一個(gè) Web API,用于將表單數(shù)據(jù)發(fā)送到服務(wù)器。它允許我們發(fā)送鍵值對(duì),其中值可以是 Blob、文件或字符串。 - 如果 不為 null ( ),則
audioFile
會(huì)附加到對(duì)象。開(kāi)始時(shí)間和結(jié)束時(shí)間也會(huì)附加到對(duì)象中,但首先會(huì)轉(zhuǎn)換為格式。formData``audioFile && formData.append('file', audioFile);``formData``MM:SS
- 該
axios.post
方法用于將 發(fā)送formData
到服務(wù)器端點(diǎn) (?http://localhost:3001/api/transcribe
)。更改http://localhost:3001
為服務(wù)器地址。這是通過(guò)await
關(guān)鍵字完成的,這意味著該函數(shù)將暫停并等待 Promise 被解析或被拒絕。 - 如果請(qǐng)求成功,響應(yīng)對(duì)象將包含轉(zhuǎn)錄結(jié)果 (?
response.data.transcription
)。transcription
然后使用該函數(shù)將其設(shè)置為狀態(tài)setTranscription
。然后會(huì)顯示成功的 Toast 通知。 - 如果在此過(guò)程中發(fā)生錯(cuò)誤,則會(huì)顯示錯(cuò)誤 Toast 通知。
- 在該
finally
塊中,無(wú)論結(jié)果如何(成功或錯(cuò)誤),uploading
狀態(tài)都會(huì)被設(shè)置回false
以允許用戶重試。
本質(zhì)上,該transcribeAudio
函數(shù)負(fù)責(zé)協(xié)調(diào)整個(gè)轉(zhuǎn)錄過(guò)程,包括處理表單數(shù)據(jù)、發(fā)出服務(wù)器請(qǐng)求和處理服務(wù)器響應(yīng)。
您可以在GitHub上找到 App 組件的完整源代碼。
結(jié)論
我們已經(jīng)到了最后,現(xiàn)在有了一個(gè)完整的 Web 應(yīng)用程序,可以利用 Whisper 的強(qiáng)大功能將語(yǔ)音轉(zhuǎn)錄為文本。
我們絕對(duì)可以添加更多功能,但我會(huì)讓您自己構(gòu)建其余的功能。希望我們已經(jīng)為您提供了一個(gè)良好的開(kāi)端。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-545366.html
這是完整的源代碼:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-545366.html
- GitHub 上的后端存儲(chǔ)庫(kù)
- GitHub 上的前端存儲(chǔ)庫(kù)
到了這里,關(guān)于Whisper、React 和 Node 構(gòu)建語(yǔ)音轉(zhuǎn)文本 Web 應(yīng)用程序的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!