feat: 将视频转写重构为纯文字异步语音识别
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import "dotenv/config";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { prisma } from "../lib/prisma";
|
||||
import { prepareAudio } from "../lib/stt/audio";
|
||||
import {
|
||||
downloadTranscript,
|
||||
getAsrConfig,
|
||||
queryTranscription,
|
||||
submitTranscription,
|
||||
} from "../lib/stt/dashscope";
|
||||
|
||||
async function main() {
|
||||
const videoId = process.argv[2];
|
||||
if (!videoId || !/^[A-Za-z0-9_-]+$/.test(videoId))
|
||||
throw new Error("用法:npx tsx scripts/stt-smoke.ts <视频 ID>");
|
||||
getAsrConfig();
|
||||
const video = await prisma.video.findUniqueOrThrow({
|
||||
where: { aweme_id: videoId },
|
||||
select: { duration_ms: true },
|
||||
});
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
videoId,
|
||||
durationSeconds: video.duration_ms / 1000,
|
||||
phase: "extracting_audio",
|
||||
}),
|
||||
);
|
||||
const audioURL = await prepareAudio(videoId);
|
||||
const resultPath = `/tmp/douyin-asr-${videoId}.json`;
|
||||
if (!audioURL) {
|
||||
await writeFile(
|
||||
resultPath,
|
||||
JSON.stringify({ videoId, transcript: [] }, null, 2),
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({ phase: "complete", transcript: [], resultPath }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const taskId = await submitTranscription(audioURL);
|
||||
await writeFile(
|
||||
resultPath,
|
||||
JSON.stringify({ videoId, taskId, status: "submitted" }, null, 2),
|
||||
);
|
||||
console.log(JSON.stringify({ phase: "submitted", taskId }));
|
||||
for (let attempt = 0; attempt < 90; attempt++) {
|
||||
await sleep(10_000);
|
||||
const task = await queryTranscription(taskId);
|
||||
if (task.status === "SUCCEEDED") {
|
||||
const transcript = await downloadTranscript(task.resultURL);
|
||||
await writeFile(
|
||||
resultPath,
|
||||
JSON.stringify({ videoId, taskId, transcript }, null, 2),
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({ phase: "complete", transcript, resultPath }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (attempt % 6 === 0) console.log(JSON.stringify({ phase: task.status }));
|
||||
}
|
||||
throw new Error(
|
||||
`15 分钟内未完成,任务 ID 已保存在 ${resultPath},请查询原任务而非重复提交`,
|
||||
);
|
||||
}
|
||||
main()
|
||||
.catch((error) => {
|
||||
// Do not print arbitrary SDK/network errors: they may contain signed URLs.
|
||||
console.error(
|
||||
error instanceof Error && !error.name.startsWith("Prisma")
|
||||
? error.message
|
||||
: "读取视频或转写失败",
|
||||
);
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
Reference in New Issue
Block a user