Commit a5fc900c authored by Dazzle Wu's avatar Dazzle Wu

chore: 视频生成参数调整

parent 27a61770
...@@ -55,6 +55,11 @@ export function fetchTimbreByExample<T>(condition: string) { ...@@ -55,6 +55,11 @@ export function fetchTimbreByExample<T>(condition: string) {
return request.post<T>(`/bizDigitalHumanTimbreRest/getByExample.json?condition=${condition}`) return request.post<T>(`/bizDigitalHumanTimbreRest/getByExample.json?condition=${condition}`)
} }
// 保存当前用户的草稿配置
export function fetchDraftConfigById<T>(id: number) {
return request.post<T>(`/bizDigitalHumanMemberDraftConfigRest/getById.json?id=${id}`)
}
// 保存当前用户的草稿配置 // 保存当前用户的草稿配置
export function saveDraftConfig<T>(payload: object) { export function saveDraftConfig<T>(payload: object) {
return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload) return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload)
......
...@@ -56,8 +56,12 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', { ...@@ -56,8 +56,12 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
this.text = text this.text = text
}, },
setDigitalImageUrl(digitalImageUrl: string) { setInputImageUrl(inputImageUrl: string) {
this.inputImageUrl = digitalImageUrl this.inputImageUrl = inputImageUrl
},
setInputAudioUrl(inputAudioUrl: string) {
this.inputAudioUrl = inputAudioUrl
}, },
setPerson(person: string) { setPerson(person: string) {
...@@ -72,19 +76,19 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', { ...@@ -72,19 +76,19 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
this.pitch = pitch this.pitch = pitch
}, },
setDigitalImagePositionX(x: number) { setX(x: number) {
this.x = x this.x = x
}, },
setDigitalImagePositionY(y: number) { setY(y: number) {
this.y = y this.y = y
}, },
setDigitalImagePositionW(w: number) { setW(w: number) {
this.w = w this.w = w
}, },
setDigitalImagePositionH(h: number) { setH(h: number) {
this.h = h this.h = h
}, },
......
...@@ -28,7 +28,7 @@ export interface DigitalTemplate { ...@@ -28,7 +28,7 @@ export interface DigitalTemplate {
driveType: DriveType driveType: DriveType
text: string text: string
ttsParams: { ttsParams: {
person: string person: string | null
speed: string speed: string
volume: string volume: string
pitch: string pitch: string
...@@ -141,20 +141,11 @@ export interface DraftConfig { ...@@ -141,20 +141,11 @@ export interface DraftConfig {
} }
export interface BaseVideoTask { export interface BaseVideoTask {
figureId: string | null draftId: number
driveType: string | null videoName: string
text: string | null width: number
ttsParams: { height: number
preson: string | null transparent: string
speed?: string | null videoType: string
pitch?: string | null audioUrl: string
volume?: string | null
}
videoParams: {
width: number
height: number
transparent?: string | null
}
backgroundImageUrl: string | null
autoAnimoji: string | null
} }
...@@ -23,7 +23,7 @@ watch( ...@@ -23,7 +23,7 @@ watch(
([figureId, len]) => { ([figureId, len]) => {
if (!digitalCreationStore.inputImageUrl && figureId && len) { if (!digitalCreationStore.inputImageUrl && figureId && len) {
const imageUrl = allImageList.value.find((i) => i.figureId === figureId)?.imageUrl const imageUrl = allImageList.value.find((i) => i.figureId === figureId)?.imageUrl
imageUrl && digitalCreationStore.setDigitalImageUrl(imageUrl) imageUrl && digitalCreationStore.setInputImageUrl(imageUrl)
} }
}, },
) )
...@@ -52,8 +52,8 @@ async function handleSearch(value: string) { ...@@ -52,8 +52,8 @@ async function handleSearch(value: string) {
} }
function handleClickDigitalImage(digitalItem: DigitalImageItem) { function handleClickDigitalImage(digitalItem: DigitalImageItem) {
digitalCreationStore.setFigureId(digitalItem.figureId!) digitalCreationStore.setFigureId(digitalItem.figureId)
digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl) digitalCreationStore.setInputImageUrl(digitalItem.imageUrl)
} }
function handleClickAll(imageType: ImageType) { function handleClickAll(imageType: ImageType) {
......
...@@ -9,7 +9,7 @@ const digitalImagePositionX = computed({ ...@@ -9,7 +9,7 @@ const digitalImagePositionX = computed({
return digitalCreationStore.x return digitalCreationStore.x
}, },
set(value) { set(value) {
digitalCreationStore.setDigitalImagePositionX(value) digitalCreationStore.setX(value)
}, },
}) })
...@@ -18,7 +18,7 @@ const digitalImagePositionY = computed({ ...@@ -18,7 +18,7 @@ const digitalImagePositionY = computed({
return digitalCreationStore.y return digitalCreationStore.y
}, },
set(value) { set(value) {
digitalCreationStore.setDigitalImagePositionY(value) digitalCreationStore.setY(value)
}, },
}) })
...@@ -28,8 +28,8 @@ const digitalImagePositionW = computed({ ...@@ -28,8 +28,8 @@ const digitalImagePositionW = computed({
}, },
set(width) { set(width) {
const height = (width * 16) / 9 const height = (width * 16) / 9
digitalCreationStore.setDigitalImagePositionW(width) digitalCreationStore.setW(width)
digitalCreationStore.setDigitalImagePositionH(parseInt(height + '')) digitalCreationStore.setH(parseInt(height + ''))
}, },
}) })
...@@ -39,8 +39,8 @@ const digitalImagePositionH = computed({ ...@@ -39,8 +39,8 @@ const digitalImagePositionH = computed({
}, },
set(height) { set(height) {
const width = (height * 9) / 16 const width = (height * 9) / 16
digitalCreationStore.setDigitalImagePositionH(height) digitalCreationStore.setH(height)
digitalCreationStore.setDigitalImagePositionW(parseInt(width + '')) digitalCreationStore.setW(parseInt(width + ''))
}, },
}) })
</script> </script>
......
...@@ -7,13 +7,13 @@ import DigitalPosition from './digital-position.vue' ...@@ -7,13 +7,13 @@ import DigitalPosition from './digital-position.vue'
<template> <template>
<n-tabs type="line" animated class="h-full"> <n-tabs type="line" animated class="h-full">
<n-tab-pane name="human" tab="選擇" class="h-full"> <n-tab-pane name="human" tab="選擇" class="h-full">
<DigitalHuman /> <DigitalHuman style="min-height: calc(100vh - 158px)" />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="position" tab="位置" class="h-full"> <n-tab-pane name="position" tab="位置" class="h-full">
<DigitalPosition /> <DigitalPosition style="min-height: calc(100vh - 158px)" />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="audio" tab="聲音" class="h-full"> <n-tab-pane name="audio" tab="聲音" class="h-full">
<DigitalAudio /> <DigitalAudio style="min-height: calc(100vh - 158px)" />
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</template> </template>
......
...@@ -13,11 +13,18 @@ const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewConten ...@@ -13,11 +13,18 @@ const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewConten
const digitalHumanHeight = computed(() => (digitalCreationStore.h * previewContentHeight.value!) / 1920) const digitalHumanHeight = computed(() => (digitalCreationStore.h * previewContentHeight.value!) / 1920)
const digitalHumanLeft = computed(() => (digitalCreationStore.x * previewContentWidth.value!) / 1080) const digitalHumanLeft = computed(() => (digitalCreationStore.x * previewContentWidth.value!) / 1080)
const digitalHumanTop = computed(() => (digitalCreationStore.y * previewContentHeight.value!) / 1920) const digitalHumanTop = computed(() => (digitalCreationStore.y * previewContentHeight.value!) / 1920)
const audioUrl = computed({
get() {
return digitalCreationStore.inputAudioUrl || ''
},
set(value) {
digitalCreationStore.setInputAudioUrl(value)
},
})
const url = 'wss://ai-api-sit.gsstcloud.com/websocket/textToSpeechTC.ws' const url = 'wss://ai-api-sit.gsstcloud.com/websocket/textToSpeechTC.ws'
const isConnected = ref(false) const isConnected = ref(false)
const audioData = ref('') const audioData = ref('')
const voiceUrl = ref('')
let websocket: WebSocket let websocket: WebSocket
function connectWebSocket() { function connectWebSocket() {
...@@ -35,7 +42,7 @@ function connectWebSocket() { ...@@ -35,7 +42,7 @@ function connectWebSocket() {
if (event.data) { if (event.data) {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
data.audio && (audioData.value += data.audio) data.audio && (audioData.value += data.audio)
data.replyVoiceUrl && (voiceUrl.value = data.replyVoiceUrl) data.replyVoiceUrl && (audioUrl.value = data.replyVoiceUrl)
data.final && disconnectWebSocket() data.final && disconnectWebSocket()
} }
} }
...@@ -46,7 +53,6 @@ function connectWebSocket() { ...@@ -46,7 +53,6 @@ function connectWebSocket() {
} }
function disconnectWebSocket() { function disconnectWebSocket() {
console.log('audio', audioData.value)
if (websocket) { if (websocket) {
websocket.close() websocket.close()
} }
...@@ -58,7 +64,7 @@ function sendDataToWebSocket() { ...@@ -58,7 +64,7 @@ function sendDataToWebSocket() {
sampleRate: 16000, sampleRate: 16000,
speed: Number(digitalCreationStore.speed), speed: Number(digitalCreationStore.speed),
volume: Number(digitalCreationStore.volume), volume: Number(digitalCreationStore.volume),
voiceType: 1018, voiceType: 101019,
content: digitalCreationStore.text, content: digitalCreationStore.text,
} }
websocket.send(JSON.stringify(payload)) websocket.send(JSON.stringify(payload))
...@@ -96,17 +102,17 @@ function playAudio() { ...@@ -96,17 +102,17 @@ function playAudio() {
</div> </div>
</div> </div>
<div class="flex bg-white p-4"> <div class="flex bg-white p-4">
<div class="flex-1 text-lg">00:11:22</div> <!-- <div class="flex flex-1 items-center text-lg">00:11:22</div> -->
<div class="flex flex-1 justify-center"> <div class="flex flex-1 justify-center">
<n-button v-if="!audioData" type="info" :loading="isConnected" @click="generatePreview">生成预览</n-button> <n-button v-if="!audioData" type="info" :loading="isConnected" @click="generatePreview">生成预览</n-button>
<CustomIcon v-else class="cursor-pointer text-2xl" icon="ph:play" @click="playAudio" /> <CustomIcon v-else class="cursor-pointer text-2xl" icon="ph:play" @click="playAudio" />
</div> </div>
<div class="flex flex-1 items-center justify-end gap-4"> <!-- <div class="flex flex-1 items-center justify-end gap-4">
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" /> <CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:fullscreen-line" /> <CustomIcon class="cursor-pointer text-lg" icon="mingcute:fullscreen-line" />
</div> </div> -->
</div> </div>
</div> </div>
<audio ref="digitalAudio" :src="voiceUrl"></audio> <audio ref="digitalAudio" :src="audioUrl"></audio>
</template> </template>
...@@ -2,38 +2,34 @@ ...@@ -2,38 +2,34 @@
import { createBaseVideoDigitalHumanTask, saveDraftConfig } from '@/apis/digital-creation' import { createBaseVideoDigitalHumanTask, saveDraftConfig } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation' import { useDigitalCreationStore } from '@/store/modules/creation'
import { BaseVideoTask, DraftConfig } from '@/store/types/creation' import { BaseVideoTask, DraftConfig } from '@/store/types/creation'
import { ref, watch } from 'vue' import { ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const showExportModal = ref(false) const showExportModal = ref(false)
const exportForm = ref({ const ratioValue = ref(720)
name: '',
ratio: 720,
transparent: false,
format: 'mp4',
})
const ratioList = [ const ratioList = [
{ value: 720, label: '720p' }, { value: 720, label: '720p' },
{ value: 1080, label: '1080p' }, { value: 1080, label: '1080p' },
] ]
const transparent = [ const transparent = [
{ value: false, label: '全部' }, { value: 'N', label: '全部' },
{ value: true, label: '僅數字人(透明背景)' }, { value: 'Y', label: '僅數字人(透明背景)' },
]
const formatList = [
{ value: 'mp4', label: 'MP4' },
{ value: 'webm', label: 'WEBM' },
] ]
watch( // 保存为草稿
() => exportForm.value.transparent, async function saveDraft() {
(val) => { const payload: { draftConfigDto: DraftConfig } = {
exportForm.value.format = val ? 'webm' : 'mp4' draftConfigDto: digitalCreationStore.$state,
}, }
) const res = await saveDraftConfig(payload)
if (res.code === 0) {
window.$message.success('保存成功')
}
}
// 导出视频
function confirmExport() { function confirmExport() {
if (!exportForm.value.name) { if (!digitalCreationStore.videoName) {
window.$message.error('請輸入視頻名稱') window.$message.error('請輸入視頻名稱')
return false return false
} }
...@@ -41,40 +37,29 @@ function confirmExport() { ...@@ -41,40 +37,29 @@ function confirmExport() {
} }
async function createBaseVideoTask() { async function createBaseVideoTask() {
if (!digitalCreationStore.id) {
window.$message.error('請先保存視頻為草稿')
return
}
if (!digitalCreationStore.inputAudioUrl) {
window.$message.error('請先生成預覽音頻')
return
}
const payload: BaseVideoTask = { const payload: BaseVideoTask = {
figureId: digitalCreationStore.figureId, draftId: digitalCreationStore.id,
driveType: digitalCreationStore.driveType, videoName: digitalCreationStore.videoName,
text: digitalCreationStore.text, width: ratioValue.value === 720 ? 720 : 1080,
ttsParams: { height: ratioValue.value === 720 ? 1280 : 1920,
preson: digitalCreationStore.person, transparent: digitalCreationStore.transparent,
speed: digitalCreationStore.speed, videoType: 'mp4',
pitch: digitalCreationStore.pitch, audioUrl: digitalCreationStore.inputAudioUrl,
volume: digitalCreationStore.volume,
},
videoParams: {
width: digitalCreationStore.width,
height: digitalCreationStore.height,
transparent: digitalCreationStore.transparent,
},
backgroundImageUrl: digitalCreationStore.backgroundImageUrl,
autoAnimoji: digitalCreationStore.autoAnimoji,
} }
const res = await createBaseVideoDigitalHumanTask('', payload) const res = await createBaseVideoDigitalHumanTask('null', payload)
if (res.code === 0) { if (res.code === 0) {
window.$message.success('導出成功') window.$message.success('導出成功')
showExportModal.value = false showExportModal.value = false
} }
} }
async function saveDraft() {
const payload: { draftConfigDto: DraftConfig } = {
draftConfigDto: digitalCreationStore.$state,
}
const res = await saveDraftConfig(payload)
if (res.code === 0) {
window.$message.success('保存成功')
}
}
</script> </script>
<template> <template>
...@@ -105,12 +90,12 @@ async function saveDraft() { ...@@ -105,12 +90,12 @@ async function saveDraft() {
@positive-click="confirmExport" @positive-click="confirmExport"
@negative-click="showExportModal = false" @negative-click="showExportModal = false"
> >
<n-form ref="formRef" :label-width="120" :model="exportForm" label-placement="left"> <n-form ref="formRef" :label-width="120" label-placement="left">
<n-form-item label="視頻名稱" required> <n-form-item label="視頻名稱" required>
<n-input v-model:value="exportForm.name" placeholder="請輸入視頻名稱" /> <n-input v-model:value="digitalCreationStore.videoName" placeholder="請輸入視頻名稱" />
</n-form-item> </n-form-item>
<n-form-item label="視頻分辨率" required> <n-form-item label="視頻分辨率" required>
<n-radio-group v-model:value="exportForm.ratio" name="ratio"> <n-radio-group v-model:value="ratioValue" name="ratio">
<n-space> <n-space>
<n-radio v-for="ratio in ratioList" :key="ratio.value" :value="ratio.value"> <n-radio v-for="ratio in ratioList" :key="ratio.value" :value="ratio.value">
{{ ratio.label }} {{ ratio.label }}
...@@ -119,7 +104,7 @@ async function saveDraft() { ...@@ -119,7 +104,7 @@ async function saveDraft() {
</n-radio-group> </n-radio-group>
</n-form-item> </n-form-item>
<n-form-item label="導出範圍" required> <n-form-item label="導出範圍" required>
<n-radio-group v-model:value="exportForm.transparent" name="transparent"> <n-radio-group v-model:value="digitalCreationStore.transparent" name="transparent">
<n-space> <n-space>
<n-radio v-for="item in transparent" :key="item.value" :value="item.value"> <n-radio v-for="item in transparent" :key="item.value" :value="item.value">
{{ item.label }} {{ item.label }}
...@@ -128,13 +113,7 @@ async function saveDraft() { ...@@ -128,13 +113,7 @@ async function saveDraft() {
</n-radio-group> </n-radio-group>
</n-form-item> </n-form-item>
<n-form-item label="視頻格式" required> <n-form-item label="視頻格式" required>
<n-radio-group v-model:value="exportForm.format" name="format" disabled> <n-radio value="mp4" checked> MP4 </n-radio>
<n-space>
<n-radio v-for="format in formatList" :key="format.value" :value="format.value">
{{ format.label }}
</n-radio>
</n-space>
</n-radio-group>
</n-form-item> </n-form-item>
</n-form> </n-form>
</n-modal> </n-modal>
......
<script setup lang="ts"> <script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation' import { fetchDigitalHumanTemplateStatus, fetchDraftConfigById } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation' import { useDigitalCreationStore } from '@/store/modules/creation'
import { DigitalTemplate, DraftConfig } from '@/store/types/creation' import { DigitalTemplate, DraftConfig } from '@/store/types/creation'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useRoute } from 'vue-router'
import HeaderBar from './header-bar.vue' import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue' import MainContent from './main-content.vue'
import SideBar from './side-bar.vue' import SideBar from './side-bar.vue'
const route = useRoute()
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
onMounted(() => { onMounted(() => {
getDigitalTemplate(1) if (route.params.draftId) {
getDraft(Number(route.params.draftId))
} else {
getDigitalTemplate(1)
}
}) })
async function getDigitalTemplate(id: number) { async function getDigitalTemplate(id: number) {
...@@ -34,14 +40,14 @@ async function getDigitalTemplate(id: number) { ...@@ -34,14 +40,14 @@ async function getDigitalTemplate(id: number) {
inputAudioUrl: digitalTemplate.inputAudioUrl, inputAudioUrl: digitalTemplate.inputAudioUrl,
callbackUrl: digitalTemplate.callbackUrl, callbackUrl: digitalTemplate.callbackUrl,
figureId: digitalTemplate.figureId, figureId: digitalTemplate.figureId,
width: digitalTemplate.videoParams.width, width: digitalTemplate.videoParams.width || 0,
height: digitalTemplate.videoParams.height, height: digitalTemplate.videoParams.height || 0,
transparent: digitalTemplate.videoParams.transparent ? 'Y' : 'N', transparent: digitalTemplate.videoParams.transparent ? 'Y' : 'N',
cameraId: digitalTemplate.dhParams.cameraId, cameraId: digitalTemplate.dhParams.cameraId,
x: digitalTemplate.dhParams.position.x, x: digitalTemplate.dhParams.position.x || 0,
y: digitalTemplate.dhParams.position.y, y: digitalTemplate.dhParams.position.y || 0,
w: digitalTemplate.dhParams.position.w, w: digitalTemplate.dhParams.position.w || 0,
h: digitalTemplate.dhParams.position.h, h: digitalTemplate.dhParams.position.h || 0,
subtitlePolicy: digitalTemplate.subtitleParams.subtitlePolicy, subtitlePolicy: digitalTemplate.subtitleParams.subtitlePolicy,
enabled: digitalTemplate.subtitleParams.enabled ? 'Y' : 'N', enabled: digitalTemplate.subtitleParams.enabled ? 'Y' : 'N',
backgroundImageUrl: digitalTemplate.backgroundImageUrl, backgroundImageUrl: digitalTemplate.backgroundImageUrl,
...@@ -49,13 +55,20 @@ async function getDigitalTemplate(id: number) { ...@@ -49,13 +55,20 @@ async function getDigitalTemplate(id: number) {
enablePalindrome: digitalTemplate.enablePalindrome ? 'Y' : 'N', enablePalindrome: digitalTemplate.enablePalindrome ? 'Y' : 'N',
templateId: String(digitalTemplate.id), templateId: String(digitalTemplate.id),
title: digitalTemplate.title, title: digitalTemplate.title,
logoUrl: digitalTemplate.logoParams.logoUrl, logoUrl: digitalTemplate.logoParams?.logoUrl || null,
bgmUrl: digitalTemplate.bgmParams.bgmUrl, bgmUrl: digitalTemplate.bgmParams?.bgmUrl || null,
materialUrl: digitalTemplate.materialUrl, materialUrl: digitalTemplate.materialUrl,
} }
digitalCreationStore.updateDigitalCreation(draftConfig) digitalCreationStore.updateDigitalCreation(draftConfig)
} }
} }
async function getDraft(id: number) {
const res = await fetchDraftConfigById<DraftConfig>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
}
}
</script> </script>
<template> <template>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment