Commit a5fc900c authored by Dazzle Wu's avatar Dazzle Wu

chore: 视频生成参数调整

parent 27a61770
......@@ -55,6 +55,11 @@ export function fetchTimbreByExample<T>(condition: string) {
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) {
return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload)
......
......@@ -56,8 +56,12 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
this.text = text
},
setDigitalImageUrl(digitalImageUrl: string) {
this.inputImageUrl = digitalImageUrl
setInputImageUrl(inputImageUrl: string) {
this.inputImageUrl = inputImageUrl
},
setInputAudioUrl(inputAudioUrl: string) {
this.inputAudioUrl = inputAudioUrl
},
setPerson(person: string) {
......@@ -72,19 +76,19 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
this.pitch = pitch
},
setDigitalImagePositionX(x: number) {
setX(x: number) {
this.x = x
},
setDigitalImagePositionY(y: number) {
setY(y: number) {
this.y = y
},
setDigitalImagePositionW(w: number) {
setW(w: number) {
this.w = w
},
setDigitalImagePositionH(h: number) {
setH(h: number) {
this.h = h
},
......
......@@ -28,7 +28,7 @@ export interface DigitalTemplate {
driveType: DriveType
text: string
ttsParams: {
person: string
person: string | null
speed: string
volume: string
pitch: string
......@@ -141,20 +141,11 @@ export interface DraftConfig {
}
export interface BaseVideoTask {
figureId: string | null
driveType: string | null
text: string | null
ttsParams: {
preson: string | null
speed?: string | null
pitch?: string | null
volume?: string | null
}
videoParams: {
width: number
height: number
transparent?: string | null
}
backgroundImageUrl: string | null
autoAnimoji: string | null
draftId: number
videoName: string
width: number
height: number
transparent: string
videoType: string
audioUrl: string
}
......@@ -23,7 +23,7 @@ watch(
([figureId, len]) => {
if (!digitalCreationStore.inputImageUrl && figureId && len) {
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) {
}
function handleClickDigitalImage(digitalItem: DigitalImageItem) {
digitalCreationStore.setFigureId(digitalItem.figureId!)
digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl)
digitalCreationStore.setFigureId(digitalItem.figureId)
digitalCreationStore.setInputImageUrl(digitalItem.imageUrl)
}
function handleClickAll(imageType: ImageType) {
......
......@@ -9,7 +9,7 @@ const digitalImagePositionX = computed({
return digitalCreationStore.x
},
set(value) {
digitalCreationStore.setDigitalImagePositionX(value)
digitalCreationStore.setX(value)
},
})
......@@ -18,7 +18,7 @@ const digitalImagePositionY = computed({
return digitalCreationStore.y
},
set(value) {
digitalCreationStore.setDigitalImagePositionY(value)
digitalCreationStore.setY(value)
},
})
......@@ -28,8 +28,8 @@ const digitalImagePositionW = computed({
},
set(width) {
const height = (width * 16) / 9
digitalCreationStore.setDigitalImagePositionW(width)
digitalCreationStore.setDigitalImagePositionH(parseInt(height + ''))
digitalCreationStore.setW(width)
digitalCreationStore.setH(parseInt(height + ''))
},
})
......@@ -39,8 +39,8 @@ const digitalImagePositionH = computed({
},
set(height) {
const width = (height * 9) / 16
digitalCreationStore.setDigitalImagePositionH(height)
digitalCreationStore.setDigitalImagePositionW(parseInt(width + ''))
digitalCreationStore.setH(height)
digitalCreationStore.setW(parseInt(width + ''))
},
})
</script>
......
......@@ -7,13 +7,13 @@ import DigitalPosition from './digital-position.vue'
<template>
<n-tabs type="line" animated 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 name="position" tab="位置" class="h-full">
<DigitalPosition />
<DigitalPosition style="min-height: calc(100vh - 158px)" />
</n-tab-pane>
<n-tab-pane name="audio" tab="聲音" class="h-full">
<DigitalAudio />
<DigitalAudio style="min-height: calc(100vh - 158px)" />
</n-tab-pane>
</n-tabs>
</template>
......
......@@ -13,11 +13,18 @@ const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewConten
const digitalHumanHeight = computed(() => (digitalCreationStore.h * previewContentHeight.value!) / 1920)
const digitalHumanLeft = computed(() => (digitalCreationStore.x * previewContentWidth.value!) / 1080)
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 isConnected = ref(false)
const audioData = ref('')
const voiceUrl = ref('')
let websocket: WebSocket
function connectWebSocket() {
......@@ -35,7 +42,7 @@ function connectWebSocket() {
if (event.data) {
const data = JSON.parse(event.data)
data.audio && (audioData.value += data.audio)
data.replyVoiceUrl && (voiceUrl.value = data.replyVoiceUrl)
data.replyVoiceUrl && (audioUrl.value = data.replyVoiceUrl)
data.final && disconnectWebSocket()
}
}
......@@ -46,7 +53,6 @@ function connectWebSocket() {
}
function disconnectWebSocket() {
console.log('audio', audioData.value)
if (websocket) {
websocket.close()
}
......@@ -58,7 +64,7 @@ function sendDataToWebSocket() {
sampleRate: 16000,
speed: Number(digitalCreationStore.speed),
volume: Number(digitalCreationStore.volume),
voiceType: 1018,
voiceType: 101019,
content: digitalCreationStore.text,
}
websocket.send(JSON.stringify(payload))
......@@ -96,17 +102,17 @@ function playAudio() {
</div>
</div>
<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">
<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" />
</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:fullscreen-line" />
</div>
</div> -->
</div>
</div>
<audio ref="digitalAudio" :src="voiceUrl"></audio>
<audio ref="digitalAudio" :src="audioUrl"></audio>
</template>
......@@ -2,38 +2,34 @@
import { createBaseVideoDigitalHumanTask, saveDraftConfig } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { BaseVideoTask, DraftConfig } from '@/store/types/creation'
import { ref, watch } from 'vue'
import { ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const showExportModal = ref(false)
const exportForm = ref({
name: '',
ratio: 720,
transparent: false,
format: 'mp4',
})
const ratioValue = ref(720)
const ratioList = [
{ value: 720, label: '720p' },
{ value: 1080, label: '1080p' },
]
const transparent = [
{ value: false, label: '全部' },
{ value: true, label: '僅數字人(透明背景)' },
]
const formatList = [
{ value: 'mp4', label: 'MP4' },
{ value: 'webm', label: 'WEBM' },
{ value: 'N', label: '全部' },
{ value: 'Y', label: '僅數字人(透明背景)' },
]
watch(
() => exportForm.value.transparent,
(val) => {
exportForm.value.format = val ? 'webm' : 'mp4'
},
)
// 保存为草稿
async function saveDraft() {
const payload: { draftConfigDto: DraftConfig } = {
draftConfigDto: digitalCreationStore.$state,
}
const res = await saveDraftConfig(payload)
if (res.code === 0) {
window.$message.success('保存成功')
}
}
// 导出视频
function confirmExport() {
if (!exportForm.value.name) {
if (!digitalCreationStore.videoName) {
window.$message.error('請輸入視頻名稱')
return false
}
......@@ -41,40 +37,29 @@ function confirmExport() {
}
async function createBaseVideoTask() {
if (!digitalCreationStore.id) {
window.$message.error('請先保存視頻為草稿')
return
}
if (!digitalCreationStore.inputAudioUrl) {
window.$message.error('請先生成預覽音頻')
return
}
const payload: BaseVideoTask = {
figureId: digitalCreationStore.figureId,
driveType: digitalCreationStore.driveType,
text: digitalCreationStore.text,
ttsParams: {
preson: digitalCreationStore.person,
speed: digitalCreationStore.speed,
pitch: digitalCreationStore.pitch,
volume: digitalCreationStore.volume,
},
videoParams: {
width: digitalCreationStore.width,
height: digitalCreationStore.height,
transparent: digitalCreationStore.transparent,
},
backgroundImageUrl: digitalCreationStore.backgroundImageUrl,
autoAnimoji: digitalCreationStore.autoAnimoji,
draftId: digitalCreationStore.id,
videoName: digitalCreationStore.videoName,
width: ratioValue.value === 720 ? 720 : 1080,
height: ratioValue.value === 720 ? 1280 : 1920,
transparent: digitalCreationStore.transparent,
videoType: 'mp4',
audioUrl: digitalCreationStore.inputAudioUrl,
}
const res = await createBaseVideoDigitalHumanTask('', payload)
const res = await createBaseVideoDigitalHumanTask('null', payload)
if (res.code === 0) {
window.$message.success('導出成功')
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>
<template>
......@@ -105,12 +90,12 @@ async function saveDraft() {
@positive-click="confirmExport"
@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-input v-model:value="exportForm.name" placeholder="請輸入視頻名稱" />
<n-input v-model:value="digitalCreationStore.videoName" placeholder="請輸入視頻名稱" />
</n-form-item>
<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-radio v-for="ratio in ratioList" :key="ratio.value" :value="ratio.value">
{{ ratio.label }}
......@@ -119,7 +104,7 @@ async function saveDraft() {
</n-radio-group>
</n-form-item>
<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-radio v-for="item in transparent" :key="item.value" :value="item.value">
{{ item.label }}
......@@ -128,13 +113,7 @@ async function saveDraft() {
</n-radio-group>
</n-form-item>
<n-form-item label="視頻格式" required>
<n-radio-group v-model:value="exportForm.format" name="format" disabled>
<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-radio value="mp4" checked> MP4 </n-radio>
</n-form-item>
</n-form>
</n-modal>
......
<script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation'
import { fetchDigitalHumanTemplateStatus, fetchDraftConfigById } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { DigitalTemplate, DraftConfig } from '@/store/types/creation'
import { onMounted } from 'vue'
import { useRoute } from 'vue-router'
import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue'
import SideBar from './side-bar.vue'
const route = useRoute()
const digitalCreationStore = useDigitalCreationStore()
onMounted(() => {
getDigitalTemplate(1)
if (route.params.draftId) {
getDraft(Number(route.params.draftId))
} else {
getDigitalTemplate(1)
}
})
async function getDigitalTemplate(id: number) {
......@@ -34,14 +40,14 @@ async function getDigitalTemplate(id: number) {
inputAudioUrl: digitalTemplate.inputAudioUrl,
callbackUrl: digitalTemplate.callbackUrl,
figureId: digitalTemplate.figureId,
width: digitalTemplate.videoParams.width,
height: digitalTemplate.videoParams.height,
width: digitalTemplate.videoParams.width || 0,
height: digitalTemplate.videoParams.height || 0,
transparent: digitalTemplate.videoParams.transparent ? 'Y' : 'N',
cameraId: digitalTemplate.dhParams.cameraId,
x: digitalTemplate.dhParams.position.x,
y: digitalTemplate.dhParams.position.y,
w: digitalTemplate.dhParams.position.w,
h: digitalTemplate.dhParams.position.h,
x: digitalTemplate.dhParams.position.x || 0,
y: digitalTemplate.dhParams.position.y || 0,
w: digitalTemplate.dhParams.position.w || 0,
h: digitalTemplate.dhParams.position.h || 0,
subtitlePolicy: digitalTemplate.subtitleParams.subtitlePolicy,
enabled: digitalTemplate.subtitleParams.enabled ? 'Y' : 'N',
backgroundImageUrl: digitalTemplate.backgroundImageUrl,
......@@ -49,13 +55,20 @@ async function getDigitalTemplate(id: number) {
enablePalindrome: digitalTemplate.enablePalindrome ? 'Y' : 'N',
templateId: String(digitalTemplate.id),
title: digitalTemplate.title,
logoUrl: digitalTemplate.logoParams.logoUrl,
bgmUrl: digitalTemplate.bgmParams.bgmUrl,
logoUrl: digitalTemplate.logoParams?.logoUrl || null,
bgmUrl: digitalTemplate.bgmParams?.bgmUrl || null,
materialUrl: digitalTemplate.materialUrl,
}
digitalCreationStore.updateDigitalCreation(draftConfig)
}
}
async function getDraft(id: number) {
const res = await fetchDraftConfigById<DraftConfig>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
}
}
</script>
<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