feat: 新增音频路径音频文本欄位,提供給本地tts選取音色

https://github.com/xinnan-tech/xiaozhi-esp32-server/issues/1503#issuecomment-2954952028
This commit is contained in:
shane0411
2025-06-09 19:18:58 +08:00
parent be6fba5963
commit 2eb5539c9b
11 changed files with 83 additions and 45 deletions
@@ -67,6 +67,7 @@ public class ConfigServiceImpl implements ConfigService {
null, null,
null, null,
null, null,
null,
agent.getVadModelId(), agent.getVadModelId(),
agent.getAsrModelId(), agent.getAsrModelId(),
null, null,
@@ -103,11 +104,13 @@ public class ConfigServiceImpl implements ConfigService {
} }
// 获取音色信息 // 获取音色信息
String voice = null; String voice = null;
String voiceRemark = null; String referenceAudio = null;
String referenceText = null;
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId()); TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
if (timbre != null) { if (timbre != null) {
voice = timbre.getTtsVoice(); voice = timbre.getTtsVoice();
voiceRemark = timbre.getRemark(); referenceAudio = timbre.getReferenceAudio();
referenceText = timbre.getReferenceText();
} }
// 构建返回数据 // 构建返回数据
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
@@ -141,7 +144,8 @@ public class ConfigServiceImpl implements ConfigService {
agent.getSystemPrompt(), agent.getSystemPrompt(),
agent.getSummaryMemory(), agent.getSummaryMemory(),
voice, voice,
voiceRemark, referenceAudio,
referenceText,
agent.getVadModelId(), agent.getVadModelId(),
agent.getAsrModelId(), agent.getAsrModelId(),
agent.getLlmModelId(), agent.getLlmModelId(),
@@ -231,7 +235,8 @@ public class ConfigServiceImpl implements ConfigService {
* *
* @param prompt 提示词 * @param prompt 提示词
* @param voice 音色 * @param voice 音色
* @param voiceRemark 備註 * @param referenceAudio 参考音频路径
* @param referenceText 参考文本
* @param vadModelId VAD模型ID * @param vadModelId VAD模型ID
* @param asrModelId ASR模型ID * @param asrModelId ASR模型ID
* @param llmModelId LLM模型ID * @param llmModelId LLM模型ID
@@ -245,7 +250,8 @@ public class ConfigServiceImpl implements ConfigService {
String prompt, String prompt,
String summaryMemory, String summaryMemory,
String voice, String voice,
String voiceRemark, String referenceAudio,
String referenceText,
String vadModelId, String vadModelId,
String asrModelId, String asrModelId,
String llmModelId, String llmModelId,
@@ -273,7 +279,8 @@ public class ConfigServiceImpl implements ConfigService {
// 如果是TTS类型,添加private_voice属性 // 如果是TTS类型,添加private_voice属性
if ("TTS".equals(modelTypes[i])){ if ("TTS".equals(modelTypes[i])){
if (voice != null) ((Map<String, Object>) model.getConfigJson()).put("private_voice", voice); if (voice != null) ((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
if (voiceRemark != null) ((Map<String, Object>) model.getConfigJson()).put("voice_remark", voiceRemark); if (referenceAudio != null) ((Map<String, Object>) model.getConfigJson()).put("ref_audio", referenceAudio);
if (referenceText != null) ((Map<String, Object>) model.getConfigJson()).put("ref_text", referenceText);
} }
// 如果是Intent类型,且type=intent_llm,则给他添加附加模型 // 如果是Intent类型,且type=intent_llm,则给他添加附加模型
if ("Intent".equals(modelTypes[i])) { if ("Intent".equals(modelTypes[i])) {
@@ -26,6 +26,12 @@ public class TimbreDataDTO {
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
@Schema(description = "参考音频路径")
private String referenceAudio;
@Schema(description = "參考文本")
private String referenceText;
@Schema(description = "排序") @Schema(description = "排序")
@Min(value = 0, message = "{sort.number}") @Min(value = 0, message = "{sort.number}")
private long sort; private long sort;
@@ -34,6 +34,12 @@ public class TimbreEntity {
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
@Schema(description = "参考音频路径")
private String referenceAudio;
@Schema(description = "參考文本")
private String referenceText;
@Schema(description = "排序") @Schema(description = "排序")
private long sort; private long sort;
@@ -25,6 +25,12 @@ public class TimbreDetailsVO implements Serializable {
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
@Schema(description = "参考音频路径")
private String referenceAudio;
@Schema(description = "參考文本")
private String referenceText;
@Schema(description = "排序") @Schema(description = "排序")
private long sort; private long sort;
@@ -0,0 +1,3 @@
ALTER TABLE `ai_tts_voice`
ADD COLUMN `reference_audio` VARCHAR(500) DEFAULT NULL COMMENT '参考音频路径' AFTER `remark`,
ADD COLUMN `reference_text` VARCHAR(500) DEFAULT NULL COMMENT '参考文本' AFTER `reference_audio`;
@@ -205,3 +205,10 @@ databaseChangeLog:
- sqlFile: - sqlFile:
encoding: utf8 encoding: utf8
path: classpath:db/changelog/202506080955.sql path: classpath:db/changelog/202506080955.sql
- changeSet:
id: 202506091720
author: shane0411
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202506091720.sql
@@ -34,6 +34,8 @@ export default {
languages: params.languageType, languages: params.languageType,
name: params.voiceName, name: params.voiceName,
remark: params.remark, remark: params.remark,
referenceAudio: params.referenceAudio,
referenceText: params.referenceText,
sort: params.sort, sort: params.sort,
ttsModelId: params.ttsModelId, ttsModelId: params.ttsModelId,
ttsVoice: params.voiceCode, ttsVoice: params.voiceCode,
@@ -75,6 +77,8 @@ export default {
languages: params.languageType, languages: params.languageType,
name: params.voiceName, name: params.voiceName,
remark: params.remark, remark: params.remark,
referenceAudio: params.referenceAudio,
referenceText: params.referenceText,
ttsModelId: params.ttsModelId, ttsModelId: params.ttsModelId,
ttsVoice: params.voiceCode, ttsVoice: params.voiceCode,
voiceDemo: params.voiceDemo || '' voiceDemo: params.voiceDemo || ''
@@ -74,6 +74,18 @@
<span v-else>{{ scope.row.remark }}</span> <span v-else>{{ scope.row.remark }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="音频路径" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.referenceAudio" placeholder="这里是参考音频路径"></el-input>
<span v-else>{{ scope.row.referenceAudio }}</span>
</template>
</el-table-column>
<el-table-column label="音频文本" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.referenceText"></el-input>
<span v-else>{{ scope.row.referenceText }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150"> <el-table-column label="操作" align="center" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<template v-if="!scope.row.editing"> <template v-if="!scope.row.editing">
@@ -206,6 +218,8 @@ export default {
voiceName: item.name || '未命名音色', voiceName: item.name || '未命名音色',
languageType: item.languages || '', languageType: item.languages || '',
remark: item.remark || '', remark: item.remark || '',
referenceAudio: item.referenceAudio || '',
referenceText: item.referenceText || '',
voiceDemo: item.voiceDemo || '', voiceDemo: item.voiceDemo || '',
selected: false, selected: false,
editing: false, editing: false,
@@ -351,6 +365,8 @@ export default {
voiceName: row.voiceName, voiceName: row.voiceName,
languageType: row.languageType, languageType: row.languageType,
remark: row.remark, remark: row.remark,
referenceAudio: row.referenceAudio,
referenceText: row.referenceText,
ttsModelId: this.ttsModelId, ttsModelId: this.ttsModelId,
voiceDemo: row.voiceDemo || '', voiceDemo: row.voiceDemo || '',
sort: row.sort sort: row.sort
@@ -432,6 +448,8 @@ export default {
languageType: '中文', languageType: '中文',
voiceDemo: '', voiceDemo: '',
remark: '', remark: '',
referenceAudio: '',
referenceText: '',
selected: false, selected: false,
editing: true, editing: true,
sort: maxSort + 1 sort: maxSort + 1
@@ -85,8 +85,12 @@ class TTSProvider(TTSProviderBase):
self.reference_id = ( self.reference_id = (
None if not config.get("reference_id") else config.get("reference_id") None if not config.get("reference_id") else config.get("reference_id")
) )
self.reference_audio = parse_string_to_list(config.get("reference_audio")) self.reference_audio = parse_string_to_list(
self.reference_text = parse_string_to_list(config.get("reference_text")) config.get('ref_audio')if config.get('ref_audio') else config.get("reference_audio")
)
self.reference_text = parse_string_to_list(
config.get('ref_text')if config.get('ref_text') else config.get("reference_text")
)
self.format = config.get("response_format", "wav") self.format = config.get("response_format", "wav")
self.audio_file_type = config.get("response_format", "wav") self.audio_file_type = config.get("response_format", "wav")
self.api_key = config.get("api_key", "YOUR_API_KEY") self.api_key = config.get("api_key", "YOUR_API_KEY")
@@ -129,13 +133,6 @@ class TTSProvider(TTSProviderBase):
self.use_memory_cache = config.get("use_memory_cache", "on") self.use_memory_cache = config.get("use_memory_cache", "on")
self.seed = int(config.get("seed")) if config.get("seed") else None self.seed = int(config.get("seed")) if config.get("seed") else None
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts") self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
self.get_voice_data(config)
def get_voice_data(self, config: dict):
if not config.get('private_voice', '') and not config.get('voice_remark', ''):
return
self.reference_audio = config.get('private_voice')
self.reference_text = config.get('voice_remark')
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
# Prepare reference data # Prepare reference data
@@ -12,8 +12,8 @@ class TTSProvider(TTSProviderBase):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.url = config.get("url") self.url = config.get("url")
self.text_lang = config.get("text_lang", "zh") self.text_lang = config.get("text_lang", "zh")
self.ref_audio_path = config.get("ref_audio_path") self.ref_audio_path = config.get('ref_audio') if config.get('ref_audio') else config.get("ref_audio_path")
self.prompt_text = config.get("prompt_text") self.prompt_text = config.get('ref_text') if config.get('ref_text') else config.get("prompt_text")
self.prompt_lang = config.get("prompt_lang", "zh") self.prompt_lang = config.get("prompt_lang", "zh")
# 处理空字符串的情况 # 处理空字符串的情况
@@ -67,14 +67,6 @@ class TTSProvider(TTSProviderBase):
) )
self.audio_file_type = config.get("format", "wav") self.audio_file_type = config.get("format", "wav")
self.get_voice_data(config)
def get_voice_data(self, config: dict):
if not config.get('private_voice', '') and not config.get('voice_remark', ''):
return
self.ref_audio_path = config.get('private_voice')
self.prompt_text = config.get('voice_remark')
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
request_json = { request_json = {
"text": text, "text": text,
@@ -11,8 +11,8 @@ class TTSProvider(TTSProviderBase):
def __init__(self, config, delete_audio_file): def __init__(self, config, delete_audio_file):
super().__init__(config, delete_audio_file) super().__init__(config, delete_audio_file)
self.url = config.get("url") self.url = config.get("url")
self.refer_wav_path = config.get("refer_wav_path") self.refer_wav_path = config.get('ref_audio')if config.get('ref_audio') else config.get("refer_wav_path")
self.prompt_text = config.get("prompt_text") self.prompt_text = config.get('ref_text')if config.get('ref_text') else config.get("prompt_text")
self.prompt_language = config.get("prompt_language") self.prompt_language = config.get("prompt_language")
self.text_language = config.get("text_language", "audo") self.text_language = config.get("text_language", "audo")
@@ -34,14 +34,6 @@ class TTSProvider(TTSProviderBase):
self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes") self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes")
self.audio_file_type = config.get("format", "wav") self.audio_file_type = config.get("format", "wav")
self.get_voice_data(config)
def get_voice_data(self, config: dict):
if not config.get('private_voice', '') and not config.get('voice_remark', ''):
return
self.refer_wav_path = config.get('private_voice')
self.prompt_text = config.get('voice_remark')
async def text_to_speak(self, text, output_file): async def text_to_speak(self, text, output_file):
request_params = { request_params = {
"refer_wav_path": self.refer_wav_path, "refer_wav_path": self.refer_wav_path,