feat: 将视频转写重构为纯文字异步语音识别

This commit is contained in:
2026-10-03 21:58:06 +08:00
parent 040273586b
commit 6f0e38054b
34 changed files with 2283 additions and 1771 deletions
+16 -6
View File
@@ -111,17 +111,17 @@ export async function getVideoDuration(
}
/**
* 使用 ffmpeg 将视频二进制转换为音频,返回音频 Buffer
* 使用 ffmpeg 将视频 Buffer 或本地文件转换为音频,返回音频 Buffer
*
* 默认输出 mp3,可通过 opts.format 指定 'mp3' | 'aac' | 'wav'
* - mp3: 使用 libmp3lame,contentType 为 'audio/mpeg'
* - mp3: 使用 libmp3lame,16 kHz 单声道,contentType 为 'audio/mpeg'
* - aac: 输出 m4a 容器(AAC 编码),contentType 为 'audio/mp4'
* - wav: PCM 16-bit,44100Hz,双声道,contentType 为 'audio/wav'
*
* 若系统未安装 ffmpeg 或未在 PATH 中,返回 null 并给出提示。
*/
export async function extractAudio(
videoBuffer: Buffer,
videoBuffer: Buffer | string,
opts?: { format?: "mp3" | "aac" | "wav"; bitrateKbps?: number },
): Promise<{ buffer: Buffer; contentType: string; ext: string } | null> {
const ffmpegCmd = process.env.FFMPEG_PATH || "ffmpeg";
@@ -129,6 +129,7 @@ export async function extractAudio(
const base = `dy_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const inPath = path.join(tmpDir, `${base}.mp4`);
const inputPath = typeof videoBuffer === "string" ? videoBuffer : inPath;
const format = opts?.format ?? "mp3";
const bitrate = Math.max(32, Math.min(512, opts?.bitrateKbps ?? 192)); // 安全范围 32~512 kbps
@@ -174,19 +175,28 @@ export async function extractAudio(
}
try {
await fs.writeFile(inPath, videoBuffer);
if (typeof videoBuffer !== "string") await fs.writeFile(inPath, videoBuffer);
const args = [
"-hide_banner",
"-loglevel",
"error",
"-i",
inPath,
inputPath,
"-map",
"0:a:0",
"-ac",
"1",
"-ar",
"16000",
"-vn", // 丢弃视频流
...codecArgs,
"-y",
outPath,
];
await execFileAsync(ffmpegCmd, args, { windowsHide: true });
await execFileAsync(ffmpegCmd, args, {
windowsHide: true,
timeout: 60 * 60 * 1000,
});
const audio = await fs.readFile(outPath);
return { buffer: audio, contentType, ext };
} catch (e: any) {