首页博客 → Node.js 语音转文字:能直接用的代码和那些坑

Node.js 语音转文字:能直接用的代码和那些坑

发布于 2026-08-18 · 6 分钟阅读

在 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 调到与你处理的最长音频匹配即可,五分钟足以覆盖一小时的录音。

相关阅读