feat:优化
This commit is contained in:
@@ -3,11 +3,12 @@ defineOptions({ name: 'DigitalVideoPage' })
|
||||
import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { InboxOutlined } from '@ant-design/icons-vue'
|
||||
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
||||
import { VoiceService } from '@/api/voice'
|
||||
import { MaterialService } from '@/api/material'
|
||||
import { createDigitalHumanTask, getDigitalHumanTask, cancelTask, retryTask } from '@/api/digitalHuman'
|
||||
|
||||
// 导入 voiceStore 用于获取用户音色
|
||||
import { useVoiceCopyStore } from '@/stores/voiceCopy'
|
||||
const voiceStore = useVoiceCopyStore()
|
||||
|
||||
// 状态管理
|
||||
@@ -35,7 +36,9 @@ const ttsText = ref('')
|
||||
const selectedTtsVoice = ref('')
|
||||
const speechRate = ref(1.0)
|
||||
const instruction = ref('neutral') // 指令参数,用于控制音色风格
|
||||
const voiceSource = ref('user')
|
||||
const emotion = ref('neutral') // 情感参数
|
||||
const emotionActive = ref(false) // 是否激活情感tab(false为指令,true为情感)
|
||||
const voiceSource = ref('system') // 音色来源:user 或 system
|
||||
|
||||
// 系统音色库(使用CosyVoice v3-flash模型)
|
||||
const SYSTEM_VOICES = [
|
||||
@@ -103,6 +106,10 @@ const setVoiceSource = (source) => {
|
||||
|
||||
const selectVoiceProfile = (voice) => {
|
||||
selectedTtsVoice.value = `${voice.source}-${voice.id}`
|
||||
// 选系统音色时也更新对应的instruction
|
||||
if (voice.source === 'system' && voice.defaultInstruction) {
|
||||
instruction.value = voice.defaultInstruction
|
||||
}
|
||||
}
|
||||
|
||||
// 音频播放
|
||||
@@ -155,8 +162,7 @@ const triggerVoicePreview = async (voice) => {
|
||||
|
||||
const buildPreviewParams = (voice) => {
|
||||
if (voice.source === 'user') {
|
||||
// 使用voiceConfigId,让后端查询数据库获取文件URL和transcriptionText
|
||||
// 用户音色不传instruction
|
||||
// 用户音色:使用voiceConfigId,不传instruction
|
||||
const configId = voice.rawId || extractIdFromString(voice.id)
|
||||
if (!configId) {
|
||||
message.error('配音配置无效')
|
||||
@@ -164,19 +170,29 @@ const buildPreviewParams = (voice) => {
|
||||
}
|
||||
return {
|
||||
voiceConfigId: configId,
|
||||
inputText: ttsText.value, // 传递用户输入的文本
|
||||
inputText: ttsText.value,
|
||||
speechRate: speechRate.value || 1.0,
|
||||
audioFormat: 'mp3'
|
||||
}
|
||||
} else {
|
||||
// 系统音色使用用户选择的instruction
|
||||
return {
|
||||
// 系统音色:根据是否选择instruction或emotion来决定传递哪个参数
|
||||
const params = {
|
||||
voiceId: voice.voiceId,
|
||||
inputText: ttsText.value, // 传递用户输入的文本
|
||||
instruction: instruction.value && instruction.value !== 'neutral' ? instruction.value : (voice.defaultInstruction || '请用自然流畅的语调朗读'),
|
||||
inputText: ttsText.value,
|
||||
speechRate: speechRate.value || 1.0,
|
||||
audioFormat: 'mp3'
|
||||
}
|
||||
|
||||
// instruction和emotion只能选一个传递
|
||||
if (instruction.value && instruction.value !== 'neutral') {
|
||||
params.instruction = instruction.value
|
||||
} else if (emotion.value && emotion.value !== 'neutral') {
|
||||
params.emotion = emotion.value
|
||||
} else if (voice.defaultInstruction) {
|
||||
params.instruction = voice.defaultInstruction
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,8 +216,8 @@ const handleSynthesizeVoice = async () => {
|
||||
audioFormat: 'mp3'
|
||||
}
|
||||
|
||||
// 如果是用户配音,使用voiceConfigId让后端查询,不传instruction
|
||||
if (voice.source === 'user') {
|
||||
// 用户音色:使用voiceConfigId,不传instruction
|
||||
const configId = voice.rawId || extractIdFromString(voice.id)
|
||||
if (!configId) {
|
||||
message.warning('音色配置无效')
|
||||
@@ -209,14 +225,22 @@ const handleSynthesizeVoice = async () => {
|
||||
}
|
||||
params.voiceConfigId = configId
|
||||
} else {
|
||||
// 使用系统音色voiceId和用户选择的instruction
|
||||
// 系统音色:使用voiceId,根据是否选择instruction或emotion来决定传递哪个参数
|
||||
const voiceId = voice.voiceId || voice.rawId
|
||||
if (!voiceId) {
|
||||
message.warning('音色配置无效')
|
||||
return
|
||||
}
|
||||
params.voiceId = voiceId
|
||||
params.instruction = instruction.value && instruction.value !== 'neutral' ? instruction.value : (voice.defaultInstruction || '请用自然流畅的语调朗读')
|
||||
|
||||
// instruction和emotion只能选一个传递
|
||||
if (instruction.value && instruction.value !== 'neutral') {
|
||||
params.instruction = instruction.value
|
||||
} else if (emotion.value && emotion.value !== 'neutral') {
|
||||
params.emotion = emotion.value
|
||||
} else if (voice.defaultInstruction) {
|
||||
params.instruction = voice.defaultInstruction
|
||||
}
|
||||
}
|
||||
|
||||
const res = await VoiceService.synthesize(params)
|
||||
@@ -317,19 +341,41 @@ const generateVideo = async () => {
|
||||
|
||||
message.destroy()
|
||||
|
||||
// 2. 创建数字人任务(简化:只使用voiceId,后端实时TTS)
|
||||
// 2. 创建数字人任务
|
||||
const taskData = {
|
||||
taskName: `数字人任务_${Date.now()}`,
|
||||
videoFileId: videoFileId,
|
||||
// 音频由后端实时合成,使用voiceId
|
||||
voiceId: voice.voiceId || voice.rawId,
|
||||
inputText: ttsText.value, // 文本内容(用于TTS合成)
|
||||
inputText: ttsText.value,
|
||||
speechRate: speechRate.value,
|
||||
instruction: voice.source === 'user' ? undefined : (instruction.value && instruction.value !== 'neutral' ? instruction.value : (voice.defaultInstruction || '请用自然流畅的语调朗读')),
|
||||
volume: 0,
|
||||
guidanceScale: 1,
|
||||
seed: 8888
|
||||
}
|
||||
|
||||
if (voice.source === 'user') {
|
||||
// 用户音色:使用voiceConfigId,不传instruction
|
||||
const configId = voice.rawId || extractIdFromString(voice.id)
|
||||
if (!configId) {
|
||||
message.warning('音色配置无效')
|
||||
return
|
||||
}
|
||||
taskData.voiceConfigId = configId
|
||||
} else {
|
||||
// 系统音色:使用voiceId,根据是否选择instruction或emotion来决定传递哪个参数
|
||||
taskData.voiceId = voice.voiceId
|
||||
|
||||
// instruction和emotion只能选一个传递
|
||||
if (instruction.value && instruction.value !== 'neutral') {
|
||||
taskData.instruction = instruction.value
|
||||
} else if (emotion.value && emotion.value !== 'neutral') {
|
||||
taskData.emotion = emotion.value
|
||||
} else if (voice.defaultInstruction) {
|
||||
taskData.instruction = voice.defaultInstruction
|
||||
} else {
|
||||
taskData.emotion = 'neutral'
|
||||
}
|
||||
}
|
||||
|
||||
message.loading('正在创建任务...', 0)
|
||||
const createRes = await createDigitalHumanTask(taskData)
|
||||
message.destroy()
|
||||
@@ -724,16 +770,55 @@ let previewObjectUrl = ''
|
||||
</div>
|
||||
|
||||
<div v-if="voiceSource === 'system'" class="control-group">
|
||||
<div class="control-label">指令</div>
|
||||
<div class="control-label">情感</div>
|
||||
<div class="control-tabs">
|
||||
<button
|
||||
:class="['tab-btn', { active: !emotionActive }]"
|
||||
@click="emotionActive = false"
|
||||
>
|
||||
语调
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-btn', { active: emotionActive }]"
|
||||
@click="emotionActive = true"
|
||||
>
|
||||
情感
|
||||
</button>
|
||||
</div>
|
||||
<div class="emotion-buttons">
|
||||
<button
|
||||
v-for="inst in ['neutral', '请用自然流畅的语调朗读', '请用温柔专业的语调朗读', '请用热情洋溢的语调朗读', '请用低沉磁性的语调朗读', '请用活泼生动的语调朗读']"
|
||||
:key="inst"
|
||||
v-if="!emotionActive"
|
||||
v-for="inst in [
|
||||
{ value: 'neutral', label: '中性' },
|
||||
{ value: '请用自然流畅的语调朗读', label: '自然' },
|
||||
{ value: '请用温柔专业的语调朗读', label: '温柔' },
|
||||
{ value: '请用热情洋溢的语调朗读', label: '热情' },
|
||||
{ value: '请用低沉磁性的语调朗读', label: '磁性' },
|
||||
{ value: '请用活泼生动的语调朗读', label: '活泼' }
|
||||
]"
|
||||
:key="inst.value"
|
||||
class="emotion-btn"
|
||||
:class="{ active: instruction === inst }"
|
||||
@click="instruction = inst"
|
||||
:class="{ active: instruction === inst.value }"
|
||||
@click="instruction = inst.value"
|
||||
>
|
||||
{{ inst === 'neutral' ? '中性' : inst }}
|
||||
{{ inst.label }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
v-for="emo in [
|
||||
{ value: 'neutral', label: '中性' },
|
||||
{ value: 'happy', label: '开心' },
|
||||
{ value: 'sad', label: '悲伤' },
|
||||
{ value: 'angry', label: '愤怒' },
|
||||
{ value: 'excited', label: '兴奋' },
|
||||
{ value: 'calm', label: '平静' }
|
||||
]"
|
||||
:key="emo.value"
|
||||
class="emotion-btn"
|
||||
:class="{ active: emotion === emo.value }"
|
||||
@click="emotion = emo.value"
|
||||
>
|
||||
{{ emo.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1091,6 +1176,28 @@ let previewObjectUrl = ''
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-tabs {
|
||||
display: inline-flex;
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 6px 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: rgba(59, 130, 246, 0.2);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.emotion-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid rgba(59, 130, 246, 0.2);
|
||||
|
||||
Reference in New Issue
Block a user