feat(voice): 语音参数移入 config.js + 设置页男女声切换

- config: 新增 tts.female/male 两组参数(voiceType/volume/speed),
  直接改数字即可调音色,无需改逻辑代码
- storage: getSettings 兜底 voiceGender(旧用户迁移为女声)
- voice: 新增 _getTtsParams 读 config+性别;缓存 key 含完整参数,
  改音色/音量/性别自动重新合成,无需手动清缓存
- settings: 训练偏好加"语音音色"segmented(女声/男声)
- tts 云函数: _synthesize 用传入 opts 合成;_upload 路径含 voiceType
  (男/女不同文件);需重新部署

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 10:08:09 +08:00
parent dd9cd1859c
commit a7ed017267
7 changed files with 118 additions and 20 deletions
+9 -9
View File
@@ -30,7 +30,7 @@ const PROMPTS = {
const CACHE_DIR = 'tts-cache'
const _synthesize = async (text, promptKey) => {
const _synthesize = async (text, promptKey, opts) => {
let TtsClient
try {
TtsClient = require('tencentcloud-sdk-nodejs').tts.v20190823.Client
@@ -52,17 +52,17 @@ const _synthesize = async (text, promptKey) => {
const res = await client.TextToVoice({
Text: text,
SessionId: `${promptKey}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
VoiceType: 101001, // 智瑜,温柔女声
VoiceType: (opts && opts.voiceType) || 101001, // 智瑜,温柔女声
Codec: 'mp3',
SampleRate: 16000,
Speed: 0,
Volume: 5
Speed: (opts && opts.speed != null) ? opts.speed : 0,
Volume: (opts && opts.volume != null) ? opts.volume : 5
})
return Buffer.from(res.Audio, 'base64')
}
const _upload = async (key, buffer) => {
const cloudPath = `${CACHE_DIR}/${key}.mp3`
const _upload = async (key, buffer, voiceType) => {
const cloudPath = `${CACHE_DIR}/${key}-${voiceType || 'default'}.mp3`
// uploadFile returns the canonical fileID (e.g. "cloud://env.xxx/...").
// We must use THAT — not the relative cloudPath we passed in — when
// calling getTempFileURL. A self-constructed fileID is invalid.
@@ -71,7 +71,7 @@ const _upload = async (key, buffer) => {
}
exports.main = async (event) => {
const { promptKey, fileID } = event || {}
const { promptKey, fileID, voiceType, volume, speed } = event || {}
const text = PROMPTS[promptKey]
if (!text) {
return { success: false, error: `Unknown prompt key: ${promptKey}` }
@@ -93,8 +93,8 @@ exports.main = async (event) => {
// Slow path: synthesize + upload + return fresh fileID for caching
try {
const buffer = await _synthesize(text, promptKey)
const newFileID = await _upload(promptKey, buffer)
const buffer = await _synthesize(text, promptKey, { voiceType, volume, speed })
const newFileID = await _upload(promptKey, buffer, voiceType)
const urlRes = await cloud.getTempFileURL({ fileList: [newFileID] })
const url = urlRes.fileList[0].tempFileURL
if (!url) {