首页 → 博客 → Node.js 语音转文字:能直接用的代码和那些坑
Node.js 语音转文字:能直接用的代码和那些坑
在 Node.js 里,转录这一次调用本身微不足道。真正绊住人的是它周围的一切:文件句柄还是 Buffer、从浏览器过来的上传,以及一个会悄悄掐死长录音的默认超时。
安装与第一个请求
官方 SDK 原样可用——不同的只有基础地址和密钥:
npm install openai
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://voicesscribe.com/v1",
apiKey: process.env.VS_KEY,
});
const r = await client.audio.transcriptions.create({
model: "whisper-1",
file: fs.createReadStream("audio.mp3"),
});
console.log(r.text);
注意这里用的是 createReadStream 而不是 readFileSync:文件是流式传给服务端的,不会先整个读进内存。在同时处理多个请求的服务器上,这个差别就是整个内存曲线。
来自浏览器的上传
常见的情况不是磁盘上的文件,而是一次上传。用 Express 加 multer 时,需要把 Buffer 包一层,SDK 才能看到一个带文件名的文件:
import express from "express";
import multer from "multer";
import { toFile } from "openai/uploads";
const upload = multer({ limits: { fileSize: 25 * 1024 * 1024 } });
const app = express();
app.post("/transcribe", upload.single("audio"), async (req, res) => {
try {
const file = await toFile(req.file.buffer, req.file.originalname);
const r = await client.audio.transcriptions.create({
model: "whisper-1", file,
});
res.json({ text: r.text });
} catch (e) {
res.status(502).json({ error: String(e) });
}
});
multer 上的 fileSize 限制很重要:不设它,浏览器可以在任何东西拒绝之前把一个 GB 推进你的进程内存。接口上限是 25 MB,在上游对齐这个数字,失败得又早又便宜。
在浏览器里录音再上传
浏览器端 MediaRecorder 产出的是 webm,按原样接收,不需要转换:
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const rec = new MediaRecorder(stream);
const chunks = [];
rec.ondataavailable = (e) => chunks.push(e.data);
rec.onstop = async () => {
const blob = new Blob(chunks, { type: "audio/webm" });
const form = new FormData();
form.append("audio", blob, "voice.webm");
const res = await fetch("/transcribe", { method: "POST", body: form });
console.log((await res.json()).text);
};
rec.start();
setTimeout(() => rec.stop(), 5000);
永远不要把密钥放进浏览器代码。上传发到你自己的接口,密钥留在你的服务器上——这是唯一安全的安排。
超时与重试
默认的请求超时适合短片段,会悄无声息地掐死长录音。按你实际处理的音频长度来设,并把偶发故障交给 SDK 重试:
const client = new OpenAI({
baseURL: "https://voicesscribe.com/v1",
apiKey: process.env.VS_KEY,
timeout: 5 * 60 * 1000, // 五分钟:一小时的录音足够了
maxRetries: 2, // 5xx 与连接错误,由 SDK 处理
});
重试对 5xx 和连接错误是值得的。损坏文件上的 400 每次都会以同样方式失败,重试只是白白消耗时间。
格式与时间戳
字幕和分段时间轴来自同一个调用,只差一个参数:
// 现成的字幕文件字符串
const srt = await client.audio.transcriptions.create({
model: "whisper-1", file, response_format: "srt",
});
// 带时间轴的分段
const detailed = await client.audio.transcriptions.create({
model: "whisper-1", file, response_format: "verbose_json",
});
for (const s of detailed.segments) {
console.log(s.start.toFixed(1), s.text.trim());
}值得单独处理的错误
| 状态码 | 含义 | 怎么办 |
|---|---|---|
| 401 | 密钥错误或已被封禁 | 确认环境变量真的传进了进程 |
| 413 | 文件超过 25 MB | 重编码成 16 kHz 单声道,仍然超限再切分 |
| 429 | 触发限流或额度用尽 | 指数退避,不要立即重试 |
| 502 | 识别节点不可用 | 稍等片刻后重试 |
成功响应里 text 为空不是错误——它意味着这段录音里没有语音。把它当作正常结果处理,不要重试。
用你自己的录音试一试。 注册只需一分钟,免费额度足够判断识别质量。
免费获取 API 密钥常见问题
需要专门的 Node.js 客户端吗?
不需要,官方 openai 包原样可用。API 在协议层兼容,与 OpenAI 默认配置的区别只有 baseURL 和 apiKey。
Express 里上传的文件怎么传给 SDK?
用 openai/uploads 里的 toFile 把 Buffer 包一层,SDK 就能看到一个带文件名的正规文件,再作为 file 字段传入。
可以直接从浏览器调用 API 吗?
不可以——那等于把密钥暴露给任何打开页面的人。上传到你自己的接口,密钥保留在服务器上。
MediaRecorder 产生的 webm 能接收吗?
可以,webm 与 ogg、opus、mp3、wav、m4a 一样按原样接收,不需要任何转换步骤。
长录音为什么会超时失败?
客户端默认值是按短请求调的。把 timeout 调到与你处理的最长音频匹配即可,五分钟足以覆盖一小时的录音。
相关阅读
- Python 语音转文字:从一个文件到可用脚本 — 十分钟搭好 Python 语音转文字:安装、第一个请求、响应格式怎么选、错误处理与并发加速,以及最浪费时间的几个坑。
- 语音消息转文字:Telegram、WhatsApp 及其他即时通讯工具 — 如何自动把语音消息转成文字:处理 ogg/opus 格式、提升短录音准确率、应对静音,以及可直接使用的聊天机器人代码。
- 如何为音频存档搭建批量转录流水线 — 转录上万条录音而不丢失任何一条:任务队列、并发控制、重试策略、崩溃后续跑,以及如何把账单控制在预期之内。