Commit 27a61770 authored by Dazzle Wu's avatar Dazzle Wu

feat: 视频保存联调

parent 74423a94
......@@ -22,7 +22,7 @@ export function fetch2DFewShotImageList<T>() {
// 获取背景图
export function fetchBackgroundImage<T>() {
return request.post<T>('/bizDigitalHumanImageRest/getBackgroundImage.json')
return request.post<T>('/aiDigitalHumanImageRest/getBackgroundImageList.json')
}
// 上传背景图片
......@@ -33,10 +33,15 @@ export function uploadImageFile<T>(imageName: string, formData: FormData) {
})
}
// 根据背景图id删除背景图
export function deleteBackgroundImageById<T>(id: number) {
return request.post<T>(`/bizDigitalHumanMemberImageRest/deletedById.json?id=${id}`)
}
// 根据人物名称分页获取人物信息
export function fetchInfoByImageName<T>(imageName: string) {
return request.post<T>(`/bizDigitalHumanImageRest/findByImageName.json?imageName=${imageName}`, {
pagingInfo: { pageNo: 1, pageSize: 10 },
pagingInfo: { pageNo: 1, pageSize: 9999 },
})
}
......@@ -50,6 +55,11 @@ export function fetchTimbreByExample<T>(condition: string) {
return request.post<T>(`/bizDigitalHumanTimbreRest/getByExample.json?condition=${condition}`)
}
// 保存当前用户的草稿配置
export function saveDraftConfig<T>(payload: object) {
return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload)
}
// 基础数字人视频
export function createBaseVideoDigitalHumanTask<T>(callbackUrl: string, payload: object) {
return request.post<T>(
......
import { DraftConfig, DriveType, TaskType } from '@/store/types/creation'
import { defineStore } from 'pinia'
import { DigitalTemplate } from '@/store/types/creation'
function defaultDigitalCreation(): DigitalTemplate {
function defaultDigitalCreation(): DraftConfig {
return {
id: null,
coverUrl: null,
demonstrationGifUrl: null,
demonstrationVideoUrl: null,
templateType: null,
templateName: null,
taskType: 'BASE_VIDEO',
draftName: '',
videoName: '',
taskType: TaskType.BASE_VIDEO,
requestId: null,
inputImageUrl: null,
driveType: 'TEXT',
text: null,
ttsParams: {
person: null,
speed: '5',
volume: '5',
pitch: '5',
},
driveType: DriveType.TEXT,
text: '',
person: null,
speed: '5',
volume: '5',
pitch: '5',
inputAudioUrl: null,
callbackUrl: null,
figureId: null,
digitalImageUrl: null,
videoParams: {
width: 0,
height: 0,
transparent: false,
},
dhParams: {
cameraId: null,
position: {
x: 0,
y: 0,
w: 0,
h: 0,
},
},
subtitleParams: {
subtitlePolicy: 'SRT',
enabled: false,
},
width: 720,
height: 1280,
transparent: 'N',
cameraId: null,
x: 0,
y: 0,
w: 0,
h: 0,
subtitlePolicy: 'SRT',
enabled: 'N',
backgroundImageUrl: null,
autoAnimoji: false,
enablePalindrome: false,
autoAnimoji: 'N',
enablePalindrome: 'N',
templateId: null,
title: null,
logoParams: {
logoUrl: null,
},
bgmParams: {
bgmUrl: null,
},
logoUrl: null,
bgmUrl: null,
materialUrl: null,
}
}
function getLocalState(): DigitalTemplate {
function getLocalState(): DraftConfig {
return defaultDigitalCreation()
}
export const useDigitalCreationStore = defineStore('digital-creation-store', {
state: (): DigitalTemplate => getLocalState(),
state: (): DraftConfig => getLocalState(),
actions: {
setFigureId(figureId: string) {
this.figureId = figureId
},
setText(text: string) {
this.text = text
},
setDigitalImageUrl(digitalImageUrl: string) {
this.digitalImageUrl = digitalImageUrl
this.inputImageUrl = digitalImageUrl
},
setPerson(person: string) {
this.ttsParams.person = person
this.person = person
},
setSpeed(speed: string) {
this.ttsParams.speed = speed
this.speed = speed
},
setPitch(pitch: string) {
this.ttsParams.pitch = pitch
this.pitch = pitch
},
setDigitalImagePositionX(x: number) {
this.dhParams.position.x = x
this.x = x
},
setDigitalImagePositionY(y: number) {
this.dhParams.position.y = y
this.y = y
},
setDigitalImagePositionW(w: number) {
this.dhParams.position.w = w
this.w = w
},
setDigitalImagePositionH(h: number) {
this.dhParams.position.h = h
this.h = h
},
setBackgroundImageUrl(backgroundImageUrl: string) {
this.backgroundImageUrl = backgroundImageUrl
},
setSubtitleEnabled(subtitleEnabled: boolean) {
this.subtitleParams.enabled = subtitleEnabled
setSubtitleEnabled(subtitleEnabled: string) {
this.enabled = subtitleEnabled
},
updateDigitalCreation(digitalCreation: DigitalTemplate) {
updateDigitalCreation(digitalCreation: DraftConfig) {
this.$state = { ...this.$state, ...digitalCreation }
},
......
export enum ImageType {
THREE_D = 'THREE_D',
TWO_D_BOUTIQUE = 'TWO_D_BOUTIQUE',
TWO_D_FEW_SHOT = 'TWO_D_FEW_SHOT',
}
export enum TaskType {
IMAGE_VIDEO = 'IMAGE_VIDEO',
BASE_VIDEO = 'BASE_VIDEO',
ADVANCED_VIDEO = 'ADVANCED_VIDEO',
}
export enum DriveType {
TEXT = 'TEXT',
VOICE = 'VOICE',
}
export interface DigitalTemplate {
id: number | null
id: number
coverUrl: string | null
demonstrationGifUrl: string | null
demonstrationVideoUrl: string | null
templateType: string | null
templateName: string | null
taskType: string
templateType: string
templateName: string
taskType: TaskType
requestId: string | null
inputImageUrl: string | null
driveType: string
text: string | null
driveType: DriveType
text: string
ttsParams: {
person: string | null
person: string
speed: string
volume: string
pitch: string
}
inputAudioUrl: string | null
callbackUrl: string | null
figureId: string | null
digitalImageUrl?: string | null
figureId: string
videoParams: {
width: number | null
height: number | null
width: number
height: number
transparent: boolean
}
dhParams: {
......@@ -35,7 +51,7 @@ export interface DigitalTemplate {
}
}
subtitleParams: {
subtitlePolicy: string | null
subtitlePolicy: string
enabled: boolean
}
backgroundImageUrl: string | null
......@@ -52,18 +68,18 @@ export interface DigitalTemplate {
materialUrl: string | null
}
export enum ImageType {
THREE_D = 'THREE_D',
TWO_D_BOUTIQUE = 'TWO_D_BOUTIQUE',
TWO_D_FEW_SHOT = 'TWO_D_FEW_SHOT',
BACKGROUND = 'BACKGROUND',
export interface DigitalImageItem {
id: number
imageType: ImageType
imageName: string
figureId: string
imageUrl: string
}
export interface ImageItem {
export interface BackgroundImageItem {
id: number
imageType: ImageType
imageName: string | null
figureId: string | null
imageSource: string
imageName: string
imageUrl: string
}
......@@ -75,4 +91,70 @@ export interface TimbreItem {
style: string[]
applyScene: string[]
audioUrl: string
iconUrl: string | null
}
export interface TextScript {
codec: string
sampleRate: number
speed: number
volume: number
voiceType: number
content: string
}
export interface DraftConfig {
id: number | null
coverUrl: string | null
draftName: string
videoName: string
taskType: TaskType
requestId: string | null
inputImageUrl: string | null
driveType: DriveType
text: string
person: string | null
speed: string
volume: string
pitch: string
inputAudioUrl: string | null
callbackUrl: string | null
figureId: string | null
width: number
height: number
transparent: string
cameraId: number | null
x: number
y: number
w: number
h: number
subtitlePolicy: string
enabled: string
backgroundImageUrl: string | null
autoAnimoji: string
enablePalindrome: string
templateId: string | null
title: string | null
logoUrl: string | null
bgmUrl: string | null
materialUrl: string | null
}
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
}
<script setup lang="ts">
import { fetchBackgroundImage, fetchInfoByImageName, uploadImageFile } from '@/apis/digital-creation'
import {
deleteBackgroundImageById,
fetchBackgroundImage,
fetchInfoByImageName,
uploadImageFile,
} from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
import { ref } from 'vue'
import { BackgroundImageItem } from '@/store/types/creation'
import { onMounted, ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const imageList = ref<ImageItem[]>([])
const imageList = ref<BackgroundImageItem[]>([])
const searchName = ref('')
const uploadLoading = ref(false)
const loaded = ref(Array(imageList.value.length).fill(false))
getBackgroundImageList()
onMounted(() => {
getBackgroundImageList()
})
async function getBackgroundImageList() {
const res = await fetchBackgroundImage<ImageItem[]>()
const res = await fetchBackgroundImage<BackgroundImageItem[]>()
if (res.code === 0) {
imageList.value = res.data.map((i) => ({ ...i, checked: i.imageUrl === digitalCreationStore.backgroundImageUrl }))
imageList.value = res.data
}
}
async function handleSearch(value: string) {
const res = await fetchInfoByImageName<ImageItem[]>(value)
if (!value) {
getBackgroundImageList()
return
}
const res = await fetchInfoByImageName<BackgroundImageItem[]>(value)
if (res.code === 0) {
imageList.value = res.data
}
......@@ -60,21 +71,22 @@ async function uploadImage(event: any) {
}
}
function handleClickImage(image: ImageItem) {
function handleClickImage(image: BackgroundImageItem) {
digitalCreationStore.setBackgroundImageUrl(image.imageUrl)
}
function handleDelete() {
function handleDelete(id: number) {
window.$dialog.warning({
title: '刪除圖片',
content: '是否刪除該圖片?',
positiveText: '是',
negativeText: '否',
onPositiveClick: () => {
window.$message.success('刪除成功')
},
onNegativeClick: () => {
window.$message.error('刪除失敗')
onPositiveClick: async () => {
const res = await deleteBackgroundImageById(id)
if (res.code === 0) {
window.$message.success('刪除成功')
getBackgroundImageList()
}
},
})
}
......@@ -118,13 +130,14 @@ function onImageLoaded(index: number) {
>
<img class="h-full w-full object-contain" :src="image.imageUrl" @load="onImageLoaded(index)" />
<div
class="absolute bottom-0 h-5 w-full bg-gradient-to-t from-gray-600 px-1 text-xs leading-5 text-white"
class="absolute bottom-0 h-5 w-full truncate bg-gradient-to-t from-gray-600 px-1 text-xs leading-5 text-white"
>
{{ image.imageName }}
</div>
<div
v-if="image.id"
class="absolute right-1 top-1 hidden h-7 w-7 cursor-pointer items-center justify-center rounded-md bg-black/40 p-1 group-hover:flex"
@click.stop="handleDelete"
@click.stop="handleDelete(image.id)"
>
<CustomIcon icon="mi:delete" class="text-lg text-white" />
</div>
......
......@@ -19,7 +19,7 @@ const emit = defineEmits<Emits>()
const digitalCreationStore = useDigitalCreationStore()
const digitalAudio = ref<HTMLAudioElement>()
const person = computed(() => digitalCreationStore.ttsParams.person)
const person = computed(() => digitalCreationStore.person)
function playAudio() {
digitalAudio.value?.play()
......@@ -32,7 +32,10 @@ function playAudio() {
:class="!showToggle && value?.timebreId === person ? 'border-blue' : 'border-gray-200'"
@click="emit('click', value!)"
>
<div class="h-16 w-16 rounded-lg bg-gray-200"></div>
<div class="flex h-16 w-16 items-center justify-center overflow-hidden rounded-lg bg-gray-200">
<img v-if="value?.iconUrl" :src="value.iconUrl" class="h-full w-full" />
<CustomIcon v-else class="text-gray text-4xl" :icon="value?.sex === '男' ? 'fontisto:male' : 'fontisto:famale'" />
</div>
<div class="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-32 truncate">{{ value?.name }}</div>
......@@ -46,5 +49,6 @@ function playAudio() {
<CustomIcon class="cursor-pointer text-lg" icon="ant-design:swap-outlined" @click="emit('toggle', true)" />
</div>
</div>
<audio ref="digitalAudio" :src="value?.audioUrl"></audio>
</template>
......@@ -6,6 +6,11 @@ import { computed, onMounted, ref, watch } from 'vue'
import DigitalAudioCard from './digital-audio-card.vue'
const digitalCreationStore = useDigitalCreationStore()
const lanValue = ref(0)
const lanList = ref([
{ key: 0, label: '粵語' },
{ key: 1, label: '普通話' },
])
const sexValue = ref(0)
const sexList = [
{ key: 0, label: '女性' },
......@@ -20,7 +25,7 @@ const searchName = ref('')
const speed = computed({
get() {
return Number(digitalCreationStore.ttsParams.speed)
return Number(digitalCreationStore.speed)
},
set(value) {
digitalCreationStore.setSpeed(String(value))
......@@ -29,7 +34,7 @@ const speed = computed({
const pitch = computed({
get() {
return Number(digitalCreationStore.ttsParams.pitch)
return Number(digitalCreationStore.pitch)
},
set(value) {
digitalCreationStore.setPitch(String(value))
......@@ -37,7 +42,7 @@ const pitch = computed({
})
watch(
() => [digitalCreationStore.ttsParams.person, digitalTimbreList.value.length],
() => [digitalCreationStore.person, digitalTimbreList.value.length],
([person, len]) => {
if (person && len) {
digitalTimbreValue.value = digitalTimbreList.value.find((i) => i.timebreId === person)
......@@ -75,7 +80,11 @@ function handleClickAudioCard(timbreItem: TimbreItem) {
<template>
<div class="h-full overflow-y-auto px-4 py-2">
<div v-if="!showAll">
<DigitalAudioCard :value="digitalTimbreValue" show-toggle @toggle="showAll = true" />
<div class="flex justify-end pb-3">
<HorizontalTabs v-model:value="lanValue" :list="lanList" />
</div>
<DigitalAudioCard v-if="lanValue" :value="digitalTimbreValue" show-toggle @toggle="showAll = true" />
<div class="mt-4 text-lg">聲音</div>
<div class="mt-4 flex items-center gap-2">
......
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
import { DigitalImageItem } from '@/store/types/creation'
interface Props {
value: ImageItem
value: DigitalImageItem
}
interface Emits {
(e: 'click', value: ImageItem): void
(e: 'click', value: DigitalImageItem): void
}
defineProps<Props>()
......@@ -22,7 +22,7 @@ const digitalCreationStore = useDigitalCreationStore()
:class="digitalCreationStore.figureId === value.figureId ? 'border-blue' : 'border-transparent'"
@click="emit('click', value)"
>
<img :src="value.imageUrl" />
<img :src="value.imageUrl" class="h-full w-full" />
<div class="absolute bottom-0 h-5 w-full bg-gradient-to-t from-gray-600 px-1 text-xs leading-5 text-white">
{{ value.imageName }}
</div>
......
......@@ -6,41 +6,52 @@ import {
fetchInfoByImageName,
} from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref } from 'vue'
import { DigitalImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref, watch } from 'vue'
import DigitalCard from './digital-human-card.vue'
const digitalCreationStore = useDigitalCreationStore()
const threeDImageList = ref<ImageItem[]>([])
const twoDBoutiqueImageList = ref<ImageItem[]>([])
const twoDFewShotImageList = ref<ImageItem[]>([])
const allImageList = ref<ImageItem[]>([])
const threeDImageList = ref<DigitalImageItem[]>([])
const twoDBoutiqueImageList = ref<DigitalImageItem[]>([])
const twoDFewShotImageList = ref<DigitalImageItem[]>([])
const allImageList = ref<DigitalImageItem[]>([])
const showAll = ref(false)
const searchName = ref('')
watch(
() => [digitalCreationStore.figureId, allImageList.value.length],
([figureId, len]) => {
if (!digitalCreationStore.inputImageUrl && figureId && len) {
const imageUrl = allImageList.value.find((i) => i.figureId === figureId)?.imageUrl
imageUrl && digitalCreationStore.setDigitalImageUrl(imageUrl)
}
},
)
onMounted(() => {
getDigitalImageList()
})
async function getDigitalImageList() {
const [res1, res2, res3] = await Promise.all([
fetch3DImageList<ImageItem[]>(),
fetch2DBoutiqueImageList<ImageItem[]>(),
fetch2DFewShotImageList<ImageItem[]>(),
fetch3DImageList<DigitalImageItem[]>(),
fetch2DBoutiqueImageList<DigitalImageItem[]>(),
fetch2DFewShotImageList<DigitalImageItem[]>(),
])
res1.code === 0 && (threeDImageList.value = res1.data)
res2.code === 0 && (twoDBoutiqueImageList.value = res2.data)
res3.code === 0 && (twoDFewShotImageList.value = res3.data)
allImageList.value = [...threeDImageList.value, ...twoDBoutiqueImageList.value, ...twoDFewShotImageList.value]
}
async function handleSearch(value: string) {
const res = await fetchInfoByImageName<ImageItem[]>(value)
const res = await fetchInfoByImageName<DigitalImageItem[]>(value)
if (res.code === 0) {
allImageList.value = res.data
}
}
function handleClickDigitalImage(digitalItem: ImageItem) {
function handleClickDigitalImage(digitalItem: DigitalImageItem) {
digitalCreationStore.setFigureId(digitalItem.figureId!)
digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl)
}
......
......@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore()
const digitalImagePositionX = computed({
get() {
return digitalCreationStore.dhParams.position.x
return digitalCreationStore.x
},
set(value) {
digitalCreationStore.setDigitalImagePositionX(value)
......@@ -15,7 +15,7 @@ const digitalImagePositionX = computed({
const digitalImagePositionY = computed({
get() {
return digitalCreationStore.dhParams.position.y
return digitalCreationStore.y
},
set(value) {
digitalCreationStore.setDigitalImagePositionY(value)
......@@ -24,7 +24,7 @@ const digitalImagePositionY = computed({
const digitalImagePositionW = computed({
get() {
return digitalCreationStore.dhParams.position.w
return digitalCreationStore.w
},
set(width) {
const height = (width * 16) / 9
......@@ -35,7 +35,7 @@ const digitalImagePositionW = computed({
const digitalImagePositionH = computed({
get() {
return digitalCreationStore.dhParams.position.h
return digitalCreationStore.h
},
set(height) {
const width = (height * 9) / 16
......
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
import { TextScript } from '@/store/types/creation'
import { computed, ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const previewContent = ref<HTMLElement>()
const digitalAudio = ref<HTMLAudioElement>()
const previewContentWidth = computed(() => previewContent.value?.offsetWidth)
const previewContentHeight = computed(() => previewContent.value?.offsetHeight)
const digitalHumanPosition = computed(() => digitalCreationStore.dhParams.position)
const digitalHumanWidth = computed(() => (digitalHumanPosition.value.w * previewContentWidth.value!) / 1080)
const digitalHumanHeight = computed(() => (digitalHumanPosition.value.h * previewContentHeight.value!) / 1920)
const digitalHumanLeft = computed(() => (digitalHumanPosition.value.x * previewContentWidth.value!) / 1080)
const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewContentHeight.value!) / 1920)
const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewContentWidth.value!) / 1080)
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 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() {
websocket = new WebSocket(url)
websocket.onopen = () => {
isConnected.value = true
sendDataToWebSocket()
}
websocket.onclose = () => {
isConnected.value = false
}
websocket.onmessage = (event) => {
if (event.data) {
const data = JSON.parse(event.data)
data.audio && (audioData.value += data.audio)
data.replyVoiceUrl && (voiceUrl.value = data.replyVoiceUrl)
data.final && disconnectWebSocket()
}
}
websocket.onerror = (error) => {
console.error('WebSocket error:', error)
}
}
function disconnectWebSocket() {
console.log('audio', audioData.value)
if (websocket) {
websocket.close()
}
}
function sendDataToWebSocket() {
const payload: TextScript = {
codec: 'mp3',
sampleRate: 16000,
speed: Number(digitalCreationStore.speed),
volume: Number(digitalCreationStore.volume),
voiceType: 1018,
content: digitalCreationStore.text,
}
websocket.send(JSON.stringify(payload))
}
function generatePreview() {
connectWebSocket()
}
function playAudio() {
digitalAudio.value?.play()
}
</script>
<template>
......@@ -24,8 +83,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
class="absolute h-full w-full object-cover"
/>
<img
v-show="digitalCreationStore.digitalImageUrl"
:src="digitalCreationStore.digitalImageUrl!"
v-if="digitalCreationStore.inputImageUrl"
:src="digitalCreationStore.inputImageUrl"
class="absolute max-w-none object-fill"
:style="{
width: `${digitalHumanWidth}px`,
......@@ -39,7 +98,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
<div class="flex bg-white p-4">
<div class="flex-1 text-lg">00:11:22</div>
<div class="flex flex-1 justify-center">
<CustomIcon class="cursor-pointer text-2xl" icon="ph:play" />
<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">
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
......@@ -47,4 +107,6 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
</div>
</div>
</div>
<audio ref="digitalAudio" :src="voiceUrl"></audio>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { computed } from 'vue'
const value = ref('')
const digitalCreationStore = useDigitalCreationStore()
const text = computed({
get() {
return digitalCreationStore.text
},
set(value) {
digitalCreationStore.setText(value)
},
})
</script>
<template>
<div class="rounded-2xl bg-white p-6">
<div class="pb-2">脚本</div>
<n-input
v-model:value="value"
v-model:value="text"
type="textarea"
:maxlength="2000"
show-count
......
......@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore()
const subtitleEnabled = computed({
get() {
return digitalCreationStore.subtitleParams.enabled
return digitalCreationStore.enabled
},
set(value) {
digitalCreationStore.setSubtitleEnabled(value)
......
<script setup lang="ts">
import { ref } from 'vue'
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'
const digitalCreationStore = useDigitalCreationStore()
const showExportModal = ref(false)
const exportForm = ref({
name: '',
ratio: '',
range: '',
format: '',
ratio: 720,
transparent: false,
format: 'mp4',
})
const ratioList = [
{ value: '720', label: '720p' },
{ value: '1080', label: '1080p' },
{ value: 720, label: '720p' },
{ value: 1080, label: '1080p' },
]
const rangeList = [
{ value: 'all', label: '全部' },
{ value: 'digital', label: '僅數字人(透明背景)' },
const transparent = [
{ value: false, label: '全部' },
{ value: true, label: '僅數字人(透明背景)' },
]
const formatList = [{ value: 'mp4', label: 'MP4' }]
const formatList = [
{ value: 'mp4', label: 'MP4' },
{ value: 'webm', label: 'WEBM' },
]
watch(
() => exportForm.value.transparent,
(val) => {
exportForm.value.format = val ? 'webm' : 'mp4'
},
)
function confirmExport() {
showExportModal.value = false
if (!exportForm.value.name) {
window.$message.error('請輸入視頻名稱')
return false
}
createBaseVideoTask()
}
async function createBaseVideoTask() {
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,
}
const res = await createBaseVideoDigitalHumanTask('', 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>
......@@ -36,7 +90,7 @@ function confirmExport() {
<CustomIcon class="text-green" icon="ep:success-filled" />
<span>已自動保存</span>
</div>
<n-button class="!rounded-md"> 保存爲草稿 </n-button>
<n-button class="!rounded-md" @click="saveDraft"> 保存爲草稿 </n-button>
<n-button class="!rounded-md" type="info" @click="showExportModal = true"> 導出視頻 </n-button>
</div>
</div>
......@@ -65,16 +119,16 @@ function confirmExport() {
</n-radio-group>
</n-form-item>
<n-form-item label="導出範圍" required>
<n-radio-group v-model:value="exportForm.range" name="range">
<n-radio-group v-model:value="exportForm.transparent" name="transparent">
<n-space>
<n-radio v-for="range in rangeList" :key="range.value" :value="range.value">
{{ range.label }}
<n-radio v-for="item in transparent" :key="item.value" :value="item.value">
{{ item.label }}
</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="視頻格式" required>
<n-radio-group v-model:value="exportForm.format" name="format">
<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 }}
......
<script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { DigitalTemplate } from '@/store/types/creation'
import { DigitalTemplate, DraftConfig } from '@/store/types/creation'
import { onMounted } from 'vue'
import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue'
......@@ -10,21 +10,50 @@ import SideBar from './side-bar.vue'
const digitalCreationStore = useDigitalCreationStore()
onMounted(() => {
getDigitalImageList(1)
getDigitalTemplate(1)
})
async function getDigitalImageList(id: number) {
async function getDigitalTemplate(id: number) {
const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id)
if (res.code === 0) {
const digitalTemplate = res.data
const {
dhParams: { position },
} = digitalTemplate
position.x = position.x || 0
position.y = position.y || 0
position.w = position.w || 1080
position.h = position.h || 1920
digitalCreationStore.updateDigitalCreation(digitalTemplate)
const draftConfig: DraftConfig = {
id: null,
coverUrl: digitalTemplate.coverUrl,
draftName: '',
videoName: '',
taskType: digitalTemplate.taskType,
requestId: digitalTemplate.requestId,
inputImageUrl: null,
driveType: digitalTemplate.driveType,
text: digitalTemplate.text,
person: digitalTemplate.ttsParams.person,
speed: digitalTemplate.ttsParams.speed,
volume: digitalTemplate.ttsParams.volume,
pitch: digitalTemplate.ttsParams.pitch,
inputAudioUrl: digitalTemplate.inputAudioUrl,
callbackUrl: digitalTemplate.callbackUrl,
figureId: digitalTemplate.figureId,
width: digitalTemplate.videoParams.width,
height: digitalTemplate.videoParams.height,
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,
subtitlePolicy: digitalTemplate.subtitleParams.subtitlePolicy,
enabled: digitalTemplate.subtitleParams.enabled ? 'Y' : 'N',
backgroundImageUrl: digitalTemplate.backgroundImageUrl,
autoAnimoji: digitalTemplate.autoAnimoji ? 'Y' : 'N',
enablePalindrome: digitalTemplate.enablePalindrome ? 'Y' : 'N',
templateId: String(digitalTemplate.id),
title: digitalTemplate.title,
logoUrl: digitalTemplate.logoParams.logoUrl,
bgmUrl: digitalTemplate.bgmParams.bgmUrl,
materialUrl: digitalTemplate.materialUrl,
}
digitalCreationStore.updateDigitalCreation(draftConfig)
}
}
</script>
......
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