This commit is contained in:
fengchuanhn@gmail.com
2026-05-18 10:18:49 +08:00
parent beb5eaeb42
commit 6cc305f5e8
5 changed files with 253 additions and 5 deletions

View File

@@ -0,0 +1,85 @@
import { invoke, isTauri } from "@tauri-apps/api/core";
import { ensureAppConfigReady } from "../config/videoPipeline.js";
import { resolveAliyunVoiceId } from "./soundPromptStorage.js";
const VOICE_PREVIEW_SCRIPT = "voice_preview.js";
/** @type {Map<string, string>} cacheKey → data:audio URL */
const previewUrlCache = new Map();
function mimeFromPath(filePath) {
const lower = String(filePath || "").toLowerCase();
if (lower.endsWith(".mp3")) return "audio/mpeg";
if (lower.endsWith(".ogg")) return "audio/ogg";
return "audio/wav";
}
/**
* 将 Node 写入的本地音频转为可在 WebView 播放的 URL避免 asset.localhost 不可用)
* @param {string} filePath
*/
async function localAudioToPlayableUrl(filePath) {
if (!isTauri()) {
throw new Error("请在 Tauri 桌面端使用试听功能");
}
const base64 = await invoke("read_local_file_base64", { path: filePath });
const mime = mimeFromPath(filePath);
return `data:${mime};base64,${base64}`;
}
/**
* 音色试听
* @param {{ listKey: string, title: string, aliyunVoiceId?: string, selectedVoiceId?: string }} opts
*/
export async function previewVoice(opts) {
const cfg = await ensureAppConfigReady();
if (!cfg.ok) {
return { ok: false, message: cfg.message };
}
const voice = String(
opts.aliyunVoiceId ||
resolveAliyunVoiceId(opts.selectedVoiceId || ""),
).trim();
if (!voice) {
return { ok: false, message: "音色 ID 无效" };
}
const cacheKey = opts.listKey || "";
const memCached = cacheKey ? previewUrlCache.get(cacheKey) : null;
if (memCached) {
return { ok: true, audioSrc: memCached, cached: true };
}
try {
const result = await invoke("run_nodejs_script", {
scriptName: VOICE_PREVIEW_SCRIPT,
params: {
voice,
title: opts.title || "",
cacheKey: cacheKey || undefined,
},
});
if (!result?.success || !result?.audioPath) {
return {
ok: false,
message: String(result?.error || "试听失败,请重试"),
};
}
const audioSrc = await localAudioToPlayableUrl(result.audioPath);
if (cacheKey) {
previewUrlCache.set(cacheKey, audioSrc);
}
return {
ok: true,
audioSrc,
audioPath: result.audioPath,
cached: Boolean(result.cached),
};
} catch (err) {
const msg = err instanceof Error ? err.message : String(err || "未知错误");
return { ok: false, message: `试听失败: ${msg}` };
}
}