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

feat: 视频保存联调

parent 74423a94
...@@ -22,7 +22,7 @@ export function fetch2DFewShotImageList<T>() { ...@@ -22,7 +22,7 @@ export function fetch2DFewShotImageList<T>() {
// 获取背景图 // 获取背景图
export function fetchBackgroundImage<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) { ...@@ -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) { export function fetchInfoByImageName<T>(imageName: string) {
return request.post<T>(`/bizDigitalHumanImageRest/findByImageName.json?imageName=${imageName}`, { 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) { ...@@ -50,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 saveDraftConfig<T>(payload: object) {
return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload)
}
// 基础数字人视频 // 基础数字人视频
export function createBaseVideoDigitalHumanTask<T>(callbackUrl: string, payload: object) { export function createBaseVideoDigitalHumanTask<T>(callbackUrl: string, payload: object) {
return request.post<T>( return request.post<T>(
......
import { DraftConfig, DriveType, TaskType } from '@/store/types/creation'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { DigitalTemplate } from '@/store/types/creation'
function defaultDigitalCreation(): DigitalTemplate { function defaultDigitalCreation(): DraftConfig {
return { return {
id: null, id: null,
coverUrl: null, coverUrl: null,
demonstrationGifUrl: null, draftName: '',
demonstrationVideoUrl: null, videoName: '',
templateType: null, taskType: TaskType.BASE_VIDEO,
templateName: null,
taskType: 'BASE_VIDEO',
requestId: null, requestId: null,
inputImageUrl: null, inputImageUrl: null,
driveType: 'TEXT', driveType: DriveType.TEXT,
text: null, text: '',
ttsParams: { person: null,
person: null, speed: '5',
speed: '5', volume: '5',
volume: '5', pitch: '5',
pitch: '5',
},
inputAudioUrl: null, inputAudioUrl: null,
callbackUrl: null, callbackUrl: null,
figureId: null, figureId: null,
digitalImageUrl: null, width: 720,
videoParams: { height: 1280,
width: 0, transparent: 'N',
height: 0, cameraId: null,
transparent: false, x: 0,
}, y: 0,
dhParams: { w: 0,
cameraId: null, h: 0,
position: { subtitlePolicy: 'SRT',
x: 0, enabled: 'N',
y: 0,
w: 0,
h: 0,
},
},
subtitleParams: {
subtitlePolicy: 'SRT',
enabled: false,
},
backgroundImageUrl: null, backgroundImageUrl: null,
autoAnimoji: false, autoAnimoji: 'N',
enablePalindrome: false, enablePalindrome: 'N',
templateId: null, templateId: null,
title: null, title: null,
logoParams: { logoUrl: null,
logoUrl: null, bgmUrl: null,
},
bgmParams: {
bgmUrl: null,
},
materialUrl: null, materialUrl: null,
} }
} }
function getLocalState(): DigitalTemplate { function getLocalState(): DraftConfig {
return defaultDigitalCreation() return defaultDigitalCreation()
} }
export const useDigitalCreationStore = defineStore('digital-creation-store', { export const useDigitalCreationStore = defineStore('digital-creation-store', {
state: (): DigitalTemplate => getLocalState(), state: (): DraftConfig => getLocalState(),
actions: { actions: {
setFigureId(figureId: string) { setFigureId(figureId: string) {
this.figureId = figureId this.figureId = figureId
}, },
setText(text: string) {
this.text = text
},
setDigitalImageUrl(digitalImageUrl: string) { setDigitalImageUrl(digitalImageUrl: string) {
this.digitalImageUrl = digitalImageUrl this.inputImageUrl = digitalImageUrl
}, },
setPerson(person: string) { setPerson(person: string) {
this.ttsParams.person = person this.person = person
}, },
setSpeed(speed: string) { setSpeed(speed: string) {
this.ttsParams.speed = speed this.speed = speed
}, },
setPitch(pitch: string) { setPitch(pitch: string) {
this.ttsParams.pitch = pitch this.pitch = pitch
}, },
setDigitalImagePositionX(x: number) { setDigitalImagePositionX(x: number) {
this.dhParams.position.x = x this.x = x
}, },
setDigitalImagePositionY(y: number) { setDigitalImagePositionY(y: number) {
this.dhParams.position.y = y this.y = y
}, },
setDigitalImagePositionW(w: number) { setDigitalImagePositionW(w: number) {
this.dhParams.position.w = w this.w = w
}, },
setDigitalImagePositionH(h: number) { setDigitalImagePositionH(h: number) {
this.dhParams.position.h = h this.h = h
}, },
setBackgroundImageUrl(backgroundImageUrl: string) { setBackgroundImageUrl(backgroundImageUrl: string) {
this.backgroundImageUrl = backgroundImageUrl this.backgroundImageUrl = backgroundImageUrl
}, },
setSubtitleEnabled(subtitleEnabled: boolean) { setSubtitleEnabled(subtitleEnabled: string) {
this.subtitleParams.enabled = subtitleEnabled this.enabled = subtitleEnabled
}, },
updateDigitalCreation(digitalCreation: DigitalTemplate) { updateDigitalCreation(digitalCreation: DraftConfig) {
this.$state = { ...this.$state, ...digitalCreation } 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 { export interface DigitalTemplate {
id: number | null id: number
coverUrl: string | null coverUrl: string | null
demonstrationGifUrl: string | null demonstrationGifUrl: string | null
demonstrationVideoUrl: string | null demonstrationVideoUrl: string | null
templateType: string | null templateType: string
templateName: string | null templateName: string
taskType: string taskType: TaskType
requestId: string | null requestId: string | null
inputImageUrl: string | null inputImageUrl: string | null
driveType: string driveType: DriveType
text: string | null text: string
ttsParams: { ttsParams: {
person: string | null person: string
speed: string speed: string
volume: string volume: string
pitch: string pitch: string
} }
inputAudioUrl: string | null inputAudioUrl: string | null
callbackUrl: string | null callbackUrl: string | null
figureId: string | null figureId: string
digitalImageUrl?: string | null
videoParams: { videoParams: {
width: number | null width: number
height: number | null height: number
transparent: boolean transparent: boolean
} }
dhParams: { dhParams: {
...@@ -35,7 +51,7 @@ export interface DigitalTemplate { ...@@ -35,7 +51,7 @@ export interface DigitalTemplate {
} }
} }
subtitleParams: { subtitleParams: {
subtitlePolicy: string | null subtitlePolicy: string
enabled: boolean enabled: boolean
} }
backgroundImageUrl: string | null backgroundImageUrl: string | null
...@@ -52,18 +68,18 @@ export interface DigitalTemplate { ...@@ -52,18 +68,18 @@ export interface DigitalTemplate {
materialUrl: string | null materialUrl: string | null
} }
export enum ImageType { export interface DigitalImageItem {
THREE_D = 'THREE_D', id: number
TWO_D_BOUTIQUE = 'TWO_D_BOUTIQUE', imageType: ImageType
TWO_D_FEW_SHOT = 'TWO_D_FEW_SHOT', imageName: string
BACKGROUND = 'BACKGROUND', figureId: string
imageUrl: string
} }
export interface ImageItem { export interface BackgroundImageItem {
id: number id: number
imageType: ImageType imageSource: string
imageName: string | null imageName: string
figureId: string | null
imageUrl: string imageUrl: string
} }
...@@ -75,4 +91,70 @@ export interface TimbreItem { ...@@ -75,4 +91,70 @@ export interface TimbreItem {
style: string[] style: string[]
applyScene: string[] applyScene: string[]
audioUrl: 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"> <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 { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation' import { BackgroundImageItem } from '@/store/types/creation'
import { ref } from 'vue' import { onMounted, ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const imageList = ref<ImageItem[]>([]) const imageList = ref<BackgroundImageItem[]>([])
const searchName = ref('') const searchName = ref('')
const uploadLoading = ref(false) const uploadLoading = ref(false)
const loaded = ref(Array(imageList.value.length).fill(false)) const loaded = ref(Array(imageList.value.length).fill(false))
getBackgroundImageList() onMounted(() => {
getBackgroundImageList()
})
async function getBackgroundImageList() { async function getBackgroundImageList() {
const res = await fetchBackgroundImage<ImageItem[]>() const res = await fetchBackgroundImage<BackgroundImageItem[]>()
if (res.code === 0) { 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) { 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) { if (res.code === 0) {
imageList.value = res.data imageList.value = res.data
} }
...@@ -60,21 +71,22 @@ async function uploadImage(event: any) { ...@@ -60,21 +71,22 @@ async function uploadImage(event: any) {
} }
} }
function handleClickImage(image: ImageItem) { function handleClickImage(image: BackgroundImageItem) {
digitalCreationStore.setBackgroundImageUrl(image.imageUrl) digitalCreationStore.setBackgroundImageUrl(image.imageUrl)
} }
function handleDelete() { function handleDelete(id: number) {
window.$dialog.warning({ window.$dialog.warning({
title: '刪除圖片', title: '刪除圖片',
content: '是否刪除該圖片?', content: '是否刪除該圖片?',
positiveText: '是', positiveText: '是',
negativeText: '否', negativeText: '否',
onPositiveClick: () => { onPositiveClick: async () => {
window.$message.success('刪除成功') const res = await deleteBackgroundImageById(id)
}, if (res.code === 0) {
onNegativeClick: () => { window.$message.success('刪除成功')
window.$message.error('刪除失敗') getBackgroundImageList()
}
}, },
}) })
} }
...@@ -118,13 +130,14 @@ function onImageLoaded(index: number) { ...@@ -118,13 +130,14 @@ function onImageLoaded(index: number) {
> >
<img class="h-full w-full object-contain" :src="image.imageUrl" @load="onImageLoaded(index)" /> <img class="h-full w-full object-contain" :src="image.imageUrl" @load="onImageLoaded(index)" />
<div <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 }} {{ image.imageName }}
</div> </div>
<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" 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" /> <CustomIcon icon="mi:delete" class="text-lg text-white" />
</div> </div>
......
...@@ -19,7 +19,7 @@ const emit = defineEmits<Emits>() ...@@ -19,7 +19,7 @@ const emit = defineEmits<Emits>()
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const digitalAudio = ref<HTMLAudioElement>() const digitalAudio = ref<HTMLAudioElement>()
const person = computed(() => digitalCreationStore.ttsParams.person) const person = computed(() => digitalCreationStore.person)
function playAudio() { function playAudio() {
digitalAudio.value?.play() digitalAudio.value?.play()
...@@ -32,7 +32,10 @@ function playAudio() { ...@@ -32,7 +32,10 @@ function playAudio() {
:class="!showToggle && value?.timebreId === person ? 'border-blue' : 'border-gray-200'" :class="!showToggle && value?.timebreId === person ? 'border-blue' : 'border-gray-200'"
@click="emit('click', value!)" @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="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2"> <div class="mb-2 flex items-center gap-2">
<div class="max-w-32 truncate">{{ value?.name }}</div> <div class="max-w-32 truncate">{{ value?.name }}</div>
...@@ -46,5 +49,6 @@ function playAudio() { ...@@ -46,5 +49,6 @@ function playAudio() {
<CustomIcon class="cursor-pointer text-lg" icon="ant-design:swap-outlined" @click="emit('toggle', true)" /> <CustomIcon class="cursor-pointer text-lg" icon="ant-design:swap-outlined" @click="emit('toggle', true)" />
</div> </div>
</div> </div>
<audio ref="digitalAudio" :src="value?.audioUrl"></audio> <audio ref="digitalAudio" :src="value?.audioUrl"></audio>
</template> </template>
...@@ -6,6 +6,11 @@ import { computed, onMounted, ref, watch } from 'vue' ...@@ -6,6 +6,11 @@ import { computed, onMounted, ref, watch } from 'vue'
import DigitalAudioCard from './digital-audio-card.vue' import DigitalAudioCard from './digital-audio-card.vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const lanValue = ref(0)
const lanList = ref([
{ key: 0, label: '粵語' },
{ key: 1, label: '普通話' },
])
const sexValue = ref(0) const sexValue = ref(0)
const sexList = [ const sexList = [
{ key: 0, label: '女性' }, { key: 0, label: '女性' },
...@@ -20,7 +25,7 @@ const searchName = ref('') ...@@ -20,7 +25,7 @@ const searchName = ref('')
const speed = computed({ const speed = computed({
get() { get() {
return Number(digitalCreationStore.ttsParams.speed) return Number(digitalCreationStore.speed)
}, },
set(value) { set(value) {
digitalCreationStore.setSpeed(String(value)) digitalCreationStore.setSpeed(String(value))
...@@ -29,7 +34,7 @@ const speed = computed({ ...@@ -29,7 +34,7 @@ const speed = computed({
const pitch = computed({ const pitch = computed({
get() { get() {
return Number(digitalCreationStore.ttsParams.pitch) return Number(digitalCreationStore.pitch)
}, },
set(value) { set(value) {
digitalCreationStore.setPitch(String(value)) digitalCreationStore.setPitch(String(value))
...@@ -37,7 +42,7 @@ const pitch = computed({ ...@@ -37,7 +42,7 @@ const pitch = computed({
}) })
watch( watch(
() => [digitalCreationStore.ttsParams.person, digitalTimbreList.value.length], () => [digitalCreationStore.person, digitalTimbreList.value.length],
([person, len]) => { ([person, len]) => {
if (person && len) { if (person && len) {
digitalTimbreValue.value = digitalTimbreList.value.find((i) => i.timebreId === person) digitalTimbreValue.value = digitalTimbreList.value.find((i) => i.timebreId === person)
...@@ -75,7 +80,11 @@ function handleClickAudioCard(timbreItem: TimbreItem) { ...@@ -75,7 +80,11 @@ function handleClickAudioCard(timbreItem: TimbreItem) {
<template> <template>
<div class="h-full overflow-y-auto px-4 py-2"> <div class="h-full overflow-y-auto px-4 py-2">
<div v-if="!showAll"> <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 text-lg">聲音</div>
<div class="mt-4 flex items-center gap-2"> <div class="mt-4 flex items-center gap-2">
......
<script setup lang="ts"> <script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation' import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation' import { DigitalImageItem } from '@/store/types/creation'
interface Props { interface Props {
value: ImageItem value: DigitalImageItem
} }
interface Emits { interface Emits {
(e: 'click', value: ImageItem): void (e: 'click', value: DigitalImageItem): void
} }
defineProps<Props>() defineProps<Props>()
...@@ -22,7 +22,7 @@ const digitalCreationStore = useDigitalCreationStore() ...@@ -22,7 +22,7 @@ const digitalCreationStore = useDigitalCreationStore()
:class="digitalCreationStore.figureId === value.figureId ? 'border-blue' : 'border-transparent'" :class="digitalCreationStore.figureId === value.figureId ? 'border-blue' : 'border-transparent'"
@click="emit('click', value)" @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"> <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 }} {{ value.imageName }}
</div> </div>
......
...@@ -6,41 +6,52 @@ import { ...@@ -6,41 +6,52 @@ import {
fetchInfoByImageName, fetchInfoByImageName,
} from '@/apis/digital-creation' } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation' import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem, ImageType } from '@/store/types/creation' import { DigitalImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref } from 'vue' import { onMounted, ref, watch } from 'vue'
import DigitalCard from './digital-human-card.vue' import DigitalCard from './digital-human-card.vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const threeDImageList = ref<ImageItem[]>([]) const threeDImageList = ref<DigitalImageItem[]>([])
const twoDBoutiqueImageList = ref<ImageItem[]>([]) const twoDBoutiqueImageList = ref<DigitalImageItem[]>([])
const twoDFewShotImageList = ref<ImageItem[]>([]) const twoDFewShotImageList = ref<DigitalImageItem[]>([])
const allImageList = ref<ImageItem[]>([]) const allImageList = ref<DigitalImageItem[]>([])
const showAll = ref(false) const showAll = ref(false)
const searchName = ref('') 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(() => { onMounted(() => {
getDigitalImageList() getDigitalImageList()
}) })
async function getDigitalImageList() { async function getDigitalImageList() {
const [res1, res2, res3] = await Promise.all([ const [res1, res2, res3] = await Promise.all([
fetch3DImageList<ImageItem[]>(), fetch3DImageList<DigitalImageItem[]>(),
fetch2DBoutiqueImageList<ImageItem[]>(), fetch2DBoutiqueImageList<DigitalImageItem[]>(),
fetch2DFewShotImageList<ImageItem[]>(), fetch2DFewShotImageList<DigitalImageItem[]>(),
]) ])
res1.code === 0 && (threeDImageList.value = res1.data) res1.code === 0 && (threeDImageList.value = res1.data)
res2.code === 0 && (twoDBoutiqueImageList.value = res2.data) res2.code === 0 && (twoDBoutiqueImageList.value = res2.data)
res3.code === 0 && (twoDFewShotImageList.value = res3.data) res3.code === 0 && (twoDFewShotImageList.value = res3.data)
allImageList.value = [...threeDImageList.value, ...twoDBoutiqueImageList.value, ...twoDFewShotImageList.value]
} }
async function handleSearch(value: string) { async function handleSearch(value: string) {
const res = await fetchInfoByImageName<ImageItem[]>(value) const res = await fetchInfoByImageName<DigitalImageItem[]>(value)
if (res.code === 0) { if (res.code === 0) {
allImageList.value = res.data allImageList.value = res.data
} }
} }
function handleClickDigitalImage(digitalItem: ImageItem) { function handleClickDigitalImage(digitalItem: DigitalImageItem) {
digitalCreationStore.setFigureId(digitalItem.figureId!) digitalCreationStore.setFigureId(digitalItem.figureId!)
digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl) digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl)
} }
......
...@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore() ...@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore()
const digitalImagePositionX = computed({ const digitalImagePositionX = computed({
get() { get() {
return digitalCreationStore.dhParams.position.x return digitalCreationStore.x
}, },
set(value) { set(value) {
digitalCreationStore.setDigitalImagePositionX(value) digitalCreationStore.setDigitalImagePositionX(value)
...@@ -15,7 +15,7 @@ const digitalImagePositionX = computed({ ...@@ -15,7 +15,7 @@ const digitalImagePositionX = computed({
const digitalImagePositionY = computed({ const digitalImagePositionY = computed({
get() { get() {
return digitalCreationStore.dhParams.position.y return digitalCreationStore.y
}, },
set(value) { set(value) {
digitalCreationStore.setDigitalImagePositionY(value) digitalCreationStore.setDigitalImagePositionY(value)
...@@ -24,7 +24,7 @@ const digitalImagePositionY = computed({ ...@@ -24,7 +24,7 @@ const digitalImagePositionY = computed({
const digitalImagePositionW = computed({ const digitalImagePositionW = computed({
get() { get() {
return digitalCreationStore.dhParams.position.w return digitalCreationStore.w
}, },
set(width) { set(width) {
const height = (width * 16) / 9 const height = (width * 16) / 9
...@@ -35,7 +35,7 @@ const digitalImagePositionW = computed({ ...@@ -35,7 +35,7 @@ const digitalImagePositionW = computed({
const digitalImagePositionH = computed({ const digitalImagePositionH = computed({
get() { get() {
return digitalCreationStore.dhParams.position.h return digitalCreationStore.h
}, },
set(height) { set(height) {
const width = (height * 9) / 16 const width = (height * 9) / 16
......
<script setup lang="ts"> <script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation' import { useDigitalCreationStore } from '@/store/modules/creation'
import { TextScript } from '@/store/types/creation'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
const previewContent = ref<HTMLElement>() const previewContent = ref<HTMLElement>()
const digitalAudio = ref<HTMLAudioElement>()
const previewContentWidth = computed(() => previewContent.value?.offsetWidth) const previewContentWidth = computed(() => previewContent.value?.offsetWidth)
const previewContentHeight = computed(() => previewContent.value?.offsetHeight) const previewContentHeight = computed(() => previewContent.value?.offsetHeight)
const digitalHumanPosition = computed(() => digitalCreationStore.dhParams.position) const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewContentWidth.value!) / 1080)
const digitalHumanWidth = computed(() => (digitalHumanPosition.value.w * previewContentWidth.value!) / 1080) const digitalHumanHeight = computed(() => (digitalCreationStore.h * previewContentHeight.value!) / 1920)
const digitalHumanHeight = computed(() => (digitalHumanPosition.value.h * previewContentHeight.value!) / 1920) const digitalHumanLeft = computed(() => (digitalCreationStore.x * previewContentWidth.value!) / 1080)
const digitalHumanLeft = computed(() => (digitalHumanPosition.value.x * previewContentWidth.value!) / 1080) const digitalHumanTop = computed(() => (digitalCreationStore.y * previewContentHeight.value!) / 1920)
const digitalHumanTop = computed(() => (digitalHumanPosition.value.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> </script>
<template> <template>
...@@ -24,8 +83,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo ...@@ -24,8 +83,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
class="absolute h-full w-full object-cover" class="absolute h-full w-full object-cover"
/> />
<img <img
v-show="digitalCreationStore.digitalImageUrl" v-if="digitalCreationStore.inputImageUrl"
:src="digitalCreationStore.digitalImageUrl!" :src="digitalCreationStore.inputImageUrl"
class="absolute max-w-none object-fill" class="absolute max-w-none object-fill"
:style="{ :style="{
width: `${digitalHumanWidth}px`, width: `${digitalHumanWidth}px`,
...@@ -39,7 +98,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo ...@@ -39,7 +98,8 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
<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-1 text-lg">00:11:22</div>
<div class="flex flex-1 justify-center"> <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>
<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" />
...@@ -47,4 +107,6 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo ...@@ -47,4 +107,6 @@ const digitalHumanTop = computed(() => (digitalHumanPosition.value.y * previewCo
</div> </div>
</div> </div>
</div> </div>
<audio ref="digitalAudio" :src="voiceUrl"></audio>
</template> </template>
<script setup lang="ts"> <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> </script>
<template> <template>
<div class="rounded-2xl bg-white p-6"> <div class="rounded-2xl bg-white p-6">
<div class="pb-2">脚本</div> <div class="pb-2">脚本</div>
<n-input <n-input
v-model:value="value" v-model:value="text"
type="textarea" type="textarea"
:maxlength="2000" :maxlength="2000"
show-count show-count
......
...@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore() ...@@ -6,7 +6,7 @@ const digitalCreationStore = useDigitalCreationStore()
const subtitleEnabled = computed({ const subtitleEnabled = computed({
get() { get() {
return digitalCreationStore.subtitleParams.enabled return digitalCreationStore.enabled
}, },
set(value) { set(value) {
digitalCreationStore.setSubtitleEnabled(value) digitalCreationStore.setSubtitleEnabled(value)
......
<script setup lang="ts"> <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 showExportModal = ref(false)
const exportForm = ref({ const exportForm = ref({
name: '', name: '',
ratio: '', ratio: 720,
range: '', transparent: false,
format: '', format: 'mp4',
}) })
const ratioList = [ const ratioList = [
{ value: '720', label: '720p' }, { value: 720, label: '720p' },
{ value: '1080', label: '1080p' }, { value: 1080, label: '1080p' },
] ]
const rangeList = [ const transparent = [
{ value: 'all', label: '全部' }, { value: false, label: '全部' },
{ value: 'digital', 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() { 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> </script>
...@@ -36,7 +90,7 @@ function confirmExport() { ...@@ -36,7 +90,7 @@ function confirmExport() {
<CustomIcon class="text-green" icon="ep:success-filled" /> <CustomIcon class="text-green" icon="ep:success-filled" />
<span>已自動保存</span> <span>已自動保存</span>
</div> </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> <n-button class="!rounded-md" type="info" @click="showExportModal = true"> 導出視頻 </n-button>
</div> </div>
</div> </div>
...@@ -65,16 +119,16 @@ function confirmExport() { ...@@ -65,16 +119,16 @@ function confirmExport() {
</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.range" name="range"> <n-radio-group v-model:value="exportForm.transparent" name="transparent">
<n-space> <n-space>
<n-radio v-for="range in rangeList" :key="range.value" :value="range.value"> <n-radio v-for="item in transparent" :key="item.value" :value="item.value">
{{ range.label }} {{ item.label }}
</n-radio> </n-radio>
</n-space> </n-space>
</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"> <n-radio-group v-model:value="exportForm.format" name="format" disabled>
<n-space> <n-space>
<n-radio v-for="format in formatList" :key="format.value" :value="format.value"> <n-radio v-for="format in formatList" :key="format.value" :value="format.value">
{{ format.label }} {{ format.label }}
......
<script setup lang="ts"> <script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation' import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/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 { onMounted } from 'vue'
import HeaderBar from './header-bar.vue' import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue' import MainContent from './main-content.vue'
...@@ -10,21 +10,50 @@ import SideBar from './side-bar.vue' ...@@ -10,21 +10,50 @@ import SideBar from './side-bar.vue'
const digitalCreationStore = useDigitalCreationStore() const digitalCreationStore = useDigitalCreationStore()
onMounted(() => { onMounted(() => {
getDigitalImageList(1) getDigitalTemplate(1)
}) })
async function getDigitalImageList(id: number) { async function getDigitalTemplate(id: number) {
const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id) const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id)
if (res.code === 0) { if (res.code === 0) {
const digitalTemplate = res.data const digitalTemplate = res.data
const { const draftConfig: DraftConfig = {
dhParams: { position }, id: null,
} = digitalTemplate coverUrl: digitalTemplate.coverUrl,
position.x = position.x || 0 draftName: '',
position.y = position.y || 0 videoName: '',
position.w = position.w || 1080 taskType: digitalTemplate.taskType,
position.h = position.h || 1920 requestId: digitalTemplate.requestId,
digitalCreationStore.updateDigitalCreation(digitalTemplate) 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> </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