11
This commit is contained in:
@@ -157,6 +157,16 @@ function signOss(secret, str) {
|
||||
return crypto.createHmac("sha1", secret).update(str).digest("base64");
|
||||
}
|
||||
|
||||
/** 私有桶:ASR 需可公网 GET 的签名 URL(对齐 Electron generateSignedUrl) */
|
||||
function ossSignedGetUrl(cfg, objectKey, expiresSec = 3600) {
|
||||
const expireTime = Math.floor(Date.now() / 1000) + expiresSec;
|
||||
const resource = `/${cfg.bucket}/${objectKey}`;
|
||||
const stringToSign = `GET\n\n\n${expireTime}\n${resource}`;
|
||||
const signature = encodeURIComponent(signOss(cfg.accessKeySecret, stringToSign));
|
||||
const host = `${cfg.bucket}.${cfg.region}.aliyuncs.com`;
|
||||
return `https://${host}/${objectKey}?OSSAccessKeyId=${encodeURIComponent(cfg.accessKeyId)}&Expires=${expireTime}&Signature=${signature}`;
|
||||
}
|
||||
|
||||
async function aliyunOssUpload(localPath, cfg) {
|
||||
const ak = cfg.accessKeyId;
|
||||
const sk = cfg.accessKeySecret;
|
||||
@@ -196,7 +206,12 @@ async function aliyunOssUpload(localPath, cfg) {
|
||||
res.on("data", (c) => chunks.push(c));
|
||||
res.on("end", () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve({ success: true, url });
|
||||
const signedUrl = ossSignedGetUrl(
|
||||
{ accessKeyId: ak, accessKeySecret: sk, bucket, region },
|
||||
objectKey,
|
||||
3600,
|
||||
);
|
||||
resolve({ success: true, url: signedUrl });
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
@@ -211,6 +226,39 @@ async function aliyunOssUpload(localPath, cfg) {
|
||||
});
|
||||
}
|
||||
|
||||
/** filetrans: output.result;批量模型: output.results[0] */
|
||||
function extractTranscriptionUrl(output) {
|
||||
if (!output || typeof output !== "object") return null;
|
||||
if (output.result?.transcription_url) return output.result.transcription_url;
|
||||
const results = output.results;
|
||||
if (Array.isArray(results) && results[0]?.transcription_url) {
|
||||
return results[0].transcription_url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseTranscriptionText(trans) {
|
||||
let fullText = "";
|
||||
if (trans?.transcripts?.length) {
|
||||
for (const t of trans.transcripts) {
|
||||
if (t.sentences?.length) {
|
||||
for (const s of t.sentences) {
|
||||
if (s.text) {
|
||||
if (fullText) fullText += "\n";
|
||||
fullText += s.text;
|
||||
}
|
||||
}
|
||||
} else if (t.text) {
|
||||
if (fullText) fullText += "\n";
|
||||
fullText += t.text;
|
||||
}
|
||||
}
|
||||
} else if (trans?.text) {
|
||||
fullText = trans.text;
|
||||
}
|
||||
return fullText;
|
||||
}
|
||||
|
||||
async function fetchJson(url, options = {}) {
|
||||
const res = await fetch(url, options);
|
||||
const text = await res.text();
|
||||
@@ -235,6 +283,14 @@ async function aliyunAsrFiletrans(audioUrl, opts) {
|
||||
if (!apiKey) {
|
||||
return { success: false, error: "缺少 DashScope apiKey" };
|
||||
}
|
||||
const fileUrl = String(audioUrl || "").trim();
|
||||
if (!/^https?:\/\//i.test(fileUrl)) {
|
||||
return { success: false, error: "ASR 需要可访问的 http(s) 音频 URL(请检查 OSS 上传与签名)" };
|
||||
}
|
||||
const input =
|
||||
model === "qwen3-asr-flash-filetrans"
|
||||
? { file_url: fileUrl }
|
||||
: { file_urls: [fileUrl] };
|
||||
const submit = await fetchJson(
|
||||
"https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription",
|
||||
{
|
||||
@@ -246,13 +302,11 @@ async function aliyunAsrFiletrans(audioUrl, opts) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
input: { file_urls: [audioUrl] },
|
||||
input,
|
||||
parameters: {
|
||||
channel_id: [0],
|
||||
// 音频通道ID
|
||||
enable_itn: false
|
||||
// 是否启用逆文本归一化
|
||||
}
|
||||
enable_itn: false,
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
@@ -270,24 +324,27 @@ async function aliyunAsrFiletrans(audioUrl, opts) {
|
||||
const status = poll?.output?.task_status;
|
||||
log("info", "voice recognition status",status);
|
||||
if (status === "SUCCEEDED") {
|
||||
const transUrl = poll?.output?.results?.[0]?.transcription_url;
|
||||
const transUrl = extractTranscriptionUrl(poll?.output);
|
||||
if (!transUrl) {
|
||||
log("error", "voice recognition transUrl",transUrl);
|
||||
return { success: false, error: "ASR 成功但无 transcription_url" };
|
||||
log("error", "voice recognition output", poll?.output);
|
||||
return {
|
||||
success: false,
|
||||
error: "ASR 成功但无 transcription_url(期望 output.result 或 output.results[0])",
|
||||
};
|
||||
}
|
||||
const trans = await fetchJson(transUrl);
|
||||
let fullText = "";
|
||||
if (trans.transcripts && trans.transcripts.length) {
|
||||
fullText = trans.transcripts.map((t) => t.text || "").filter(Boolean).join("\n");
|
||||
} else if (trans.text) {
|
||||
fullText = trans.text;
|
||||
}
|
||||
const fullText = parseTranscriptionText(trans);
|
||||
return { success: true, text: fullText };
|
||||
}
|
||||
if (status === "FAILED" || status === "UNKNOWN") {
|
||||
const detail =
|
||||
poll?.output?.message ||
|
||||
poll?.output?.code ||
|
||||
poll?.message ||
|
||||
(poll?.output ? JSON.stringify(poll.output).slice(0, 300) : null);
|
||||
return {
|
||||
success: false,
|
||||
error: poll?.output?.message || "ASR 任务失败",
|
||||
error: detail || "ASR 任务失败",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user