基于OpenAI Whisper API的音视频转写应用报错求助:400与响应头错误
问题:基于OpenAI Whisper API的Web应用报错处理
我正在开发一款基于OpenAI Whisper API的简易Web应用,用于将音视频文件转换为文字转录内容。由于Whisper API仅支持小于25MB的文件,我对大文件进行了拆分处理,但运行时出现两个错误:
- getTranscript函数中出现AxiosError: Request failed with status code 400
- Error: Cannot set headers after they are sent to the client
以下是相关代码文件:
app.ts
import "dotenv/config"; import express, { NextFunction, Request, Response } from "express"; import scriptRoutes from "./routes/scripts"; import userRoutes from "./routes/users"; import uploadRoutes from "./routes/upload"; import morgan from "morgan"; import createHttpError, { isHttpError } from "http-errors"; import session from "express-session"; import env from "./util/validateEnv"; import MongoStore from "connect-mongo"; import bodyParser from "body-parser"; const app = express(); app.use(bodyParser.urlencoded({ extended: false })); app.use(express.json()); app.use(bodyParser.json()); app.use(morgan("dev")); app.use(express.json()); app.use(session({ secret: env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { maxAge: 60 * 60 * 1000, }, rolling: true, store: MongoStore.create({ mongoUrl: env.MONGO_CONNECTION_STRING }), })) app.use("/api/users", userRoutes); app.use("/api/scripts", scriptRoutes); app.use("/api/upload", uploadRoutes); // eslint-disable-next-line @typescript-eslint/no-unused-vars app.use((error: unknown, req: Request, res: Response, next: NextFunction) => { console.error(error); let errorMessage = "An unknown error occurred"; let statusCode = 500; if (isHttpError(error)){ statusCode = error.status; errorMessage = error.message; } res.status(statusCode).json({ error: errorMessage }); }); //use for reference and testing purposes app.use((req, res, next) => { next(createHttpError(404, "Endpoint not found")); }); export default app;
upload.ts
import "dotenv/config"; import express from "express"; import cors from 'cors'; import * as UploadController from "../controllers/uploadC"; import multer from 'multer'; const router = express.Router(); router.use(cors()); const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); router.post('/', upload.array('file'), UploadController.uploadFile); router.post('/chat', UploadController.chatWithUser); export default router;
uploadC.ts
import fs from "fs"; import path from "path"; import axios from "axios"; import FormData from "form-data"; import { NextFunction, Request, Response } from "express"; const MAX_CHUNK_SIZE = 25 * 1024 * 1024; export const uploadFile = async ( req: Request, res: Response, next: NextFunction ) => { const files = req.files as Express.Multer.File[]; if (!files || files.length === 0) { return res.status(400).json({ error: "No files uploaded." }); } try { let transcript = ""; for (let i = 0; i < files.length; i++) { const file = files[i]; const fileSize = file.size; console.log("size is: " + fileSize); let datasize: number = fileSize; if (fileSize > MAX_CHUNK_SIZE) { const chunks = Math.ceil(fileSize / MAX_CHUNK_SIZE); const chunkSize = Math.ceil(fileSize / chunks); const tempFilePaths: string[] = []; for (let j = 0; j < chunks; j++) { const start = j * chunkSize; const end = Math.min(start + chunkSize, fileSize); console.log("start: " + start + " end: " + end); const chunkData = file.buffer.subarray(start, end); console.log("chunk size: " + chunkData.length); datasize = datasize - chunkData.length; console.log("datasize left: " + datasize); const tempFilePath = path.join(__dirname, `../result/temp_audio_${i + 1}_${j + 1}.mp4`); fs.writeFileSync(tempFilePath, chunkData); tempFilePaths.push(tempFilePath); } for (const tempFilePath of tempFilePaths) { console.log("getTranscript called"); console.log("File path:", tempFilePath); const fileTranscript = await getTranscript(tempFilePath); transcript += fileTranscript; const transcriptFilePath = path.join(__dirname, "../result/transcript.txt"); fs.writeFileSync(transcriptFilePath, transcript); } for (const tempFilePath of tempFilePaths) { fs.unlinkSync(tempFilePath); } } else { const filePath = path.join(__dirname, `../result/temp_audio_${i + 1}.mp4`); fs.writeFileSync(filePath, file.buffer); const fileTranscript = await getTranscript(filePath); transcript += fileTranscript; } } const transcriptFilePath = path.join(__dirname, "../result/transcript.txt"); fs.writeFileSync(transcriptFilePath, transcript); const analysis = await getChatGPTAnalysis(transcript); res.status(200).json({ transcript, transcriptFilePath, analysis }); } catch (error) { next(error); res.status(500).json({ error: "Error processing files" }); } }; export const chatWithUser = async ( req: Request, res: Response, next: NextFunction) => { console.log("trying to chat") try{ const transcriptFilePath = path.join(__dirname, "../result/transcript.txt"); const transcript = fs.readFileSync(transcriptFilePath, "utf8"); const data = { model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "Please answer the question based on the transcripts, which is form video or audio:" }, { role: "user", content: transcript }, { role: "user", content: req.body.question }, ], }; const response = await axios.post( "https://api.openai.com/v1/chat/completions", data, { headers: { Authorization: `Bearer ${process.env.OPEN_AI_KEY}`, 'Content-Type': 'application/json', }, } ); const answer = response.data.choices[0].message.content; res.status(200).json({ answer }) } catch(error){ next(error); res.status(500).json({ error: "Error passing chat" }); } } async function getTranscript(audioFilePath: string) { try { const formData = new FormData(); formData.append("file", fs.createReadStream(audioFilePath)); formData.append("model", "whisper-1"); formData.append("response_format", "vtt"); const response = await axios.post( "https://api.openai.com/v1/audio/translations", formData, { headers: { Authorization: `Bearer ${process.env.OPEN_AI_KEY}`, ...formData.getHeaders(), }, } ); return response.data; } catch (error) { console.error("Error in getTranscript:", error); throw new Error("Error in getTranscript"); } } async function getChatGPTAnalysis(transcript: string) { try { const data = { model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "Please summarize the following transcript:" }, { role: "user", content: transcript }, ], }; const response = await axios.post( "https://api.openai.com/v1/chat/completions", data, { headers: { Authorization: `Bearer ${process.env.OPEN_AI_KEY}`, 'Content-Type': 'application/json', }, } ); const summary = response.data.choices[0].message.content; return summary; } catch (error) { console.error("Error in getChatGPTAnalysis:", error); throw new Error("Error in getChatGPTAnalysis"); } }
错误修复方案
1. AxiosError: Request failed with status code 400(Whisper API请求失败)
核心原因及修复:
- 错误的API端点:当前调用的是
/v1/audio/translations(翻译接口,用于将其他语言转为英文),如果你的文件是中文或需保留原语言转录,应该用/v1/audio/transcriptions(转录接口)。修改getTranscript中的请求URL:const response = await axios.post( "https://api.openai.com/v1/audio/transcriptions", // 替换为转录接口 formData, { headers: { Authorization: `Bearer ${process.env.OPEN_AI_KEY}`, ...formData.getHeaders(), }, } ); - 文件拆分方式错误:直接切割二进制文件会破坏音视频的编码结构,导致API无法识别。改用
fluent-ffmpeg这类专业媒体处理库分割文件:import ffmpeg from 'fluent-ffmpeg'; // 示例:按时长分割视频 const splitMedia = (inputPath: string, outputPrefix: string, chunkDuration: number) => { return new Promise((resolve) => { ffmpeg(inputPath) .outputOptions('-f', 'segment') .outputOptions('-segment_time', chunkDuration.toString()) .outputOptions('-c', 'copy') .output(`${outputPrefix}_%d.mp4`) .on('end', resolve) .run(); }); }; - 检查API密钥:确认
process.env.OPEN_AI_KEY已正确加载,且账户有足够额度调用Whisper API。
2. Error: Cannot set headers after they are sent to the client
核心原因及修复:
- 重复发送响应:在
uploadFile和chatWithUser的catch块中,同时调用了next(error)和res.status(500).json(...),导致Express尝试两次发送响应头。移除其中一种方式,推荐交给全局错误处理器处理:// 修改uploadFile的catch块 catch (error) { next(error); // 移除 res.status(500).json({ error: "Error processing files" }); } // 修改chatWithUser的catch块 catch(error){ next(error); // 移除 res.status(500).json({ error: "Error passing chat" }); } - 调整中间件顺序:将404中间件移到路由之后、全局错误处理器之前,避免响应头冲突:
// 路由定义 app.use("/api/users", userRoutes); app.use("/api/scripts", scriptRoutes); app.use("/api/upload", uploadRoutes); // 404中间件 app.use((req, res, next) => { next(createHttpError(404, "Endpoint not found")); }); // 全局错误处理器 app.use((error: unknown, req: Request, res: Response, next: NextFunction) => { console.error(error); let errorMessage = "An unknown error occurred"; let statusCode = 500; if (isHttpError(error)){ statusCode = error.status; errorMessage = error.message; } res.status(statusCode).json({ error: errorMessage }); });
内容的提问来源于stack exchange,提问作者940
相关产品推荐
相关产品推荐

