Commit 4ab1b529 authored by Dazzle Wu's avatar Dazzle Wu

feat: 视频生成配置

parent a0b1ca29
......@@ -25,14 +25,19 @@ export function fetchBackgroundImage<T>() {
return request.post<T>('/bizDigitalHumanImageRest/getBackgroundImage.json')
}
// 根据人物名称分页获取人物信息
export function fetchInfoByImageName<T>(imageName: string) {
return request.post<T>(`/bizDigitalHumanImageRest/findByImageName.json?imageName=${imageName}`)
// 上传背景图片
export function uploadImageFile<T>(imageName: string, formData: FormData) {
return request.post<T>(`/baiduDigitalHumanFileRest/uploadImageFile.json?imageName=${imageName}`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 12000,
})
}
// 根据音色ID主键获取音色
export function fetchDigitalHumanTimbreById<T>() {
return request.post<T>('/bizDigitalHumanTimbreRest/getDigitalHumanTimbreById.json')
// 根据人物名称分页获取人物信息
export function fetchInfoByImageName<T>(imageName: string) {
return request.post<T>(`/bizDigitalHumanImageRest/findByImageName.json?imageName=${imageName}`, {
pagingInfo: { pageNo: 1, pageSize: 10 },
})
}
// 获取音色列表
......@@ -44,3 +49,11 @@ export function fetchDigitalHumanTimbreList<T>() {
export function fetchTimbreByExample<T>(condition: string) {
return request.post<T>(`/bizDigitalHumanTimbreRest/getByExample.json?condition=${condition}`)
}
// 基础数字人视频
export function createBaseVideoDigitalHumanTask<T>(callbackUrl: string, payload: object) {
return request.post<T>(
`/aiDigitalHumanServiceRest/createBaseVideoDigitalHumanTask.json?callbackUrl=${callbackUrl}`,
payload,
)
}
......@@ -31,7 +31,7 @@ const emit = defineEmits<Emits>()
</div>
<div
class="absolute h-8 w-20 rounded-full bg-blue-500 transition-transform duration-300"
:class="`translate-x-${value * 20}`"
:style="{ transform: `translateX(${value * 80}px)` }"
></div>
</div>
</template>
......@@ -3,7 +3,7 @@ import Creation from '@/views/creation/creation.vue'
export default [
{
path: '/fe/creation',
path: '/creation',
name: 'Creation',
meta: {
rank: 1001,
......
......@@ -3,49 +3,57 @@ import { DigitalTemplate } from '@/store/types/creation'
function defaultDigitalCreation(): DigitalTemplate {
return {
id: 1,
templateType: '产品营销',
videoName: '营销视频',
id: null,
coverUrl: null,
demonstrationGifUrl: null,
demonstrationVideoUrl: null,
templateType: null,
templateName: null,
taskType: 'BASE_VIDEO',
requestId: undefined,
inputImageUrl: undefined,
requestId: null,
inputImageUrl: null,
driveType: 'TEXT',
text: '嗨!我是挥手问候家',
text: null,
ttsParams: {
person: '5153',
person: null,
speed: '5',
volume: '5',
pitch: '5',
},
inputAudioUrl: undefined,
callbackUrl: undefined,
figureId: '',
inputAudioUrl: null,
callbackUrl: null,
figureId: null,
digitalImageUrl: null,
videoParams: {
width: 1920,
width: 0,
height: 0,
transparent: false,
},
dhParams: {
cameraId: undefined,
cameraId: null,
position: {
x: 100,
y: 111,
w: undefined,
h: undefined,
x: 0,
y: 0,
w: 0,
h: 0,
},
},
subtitleParams: {
subtitlePolicy: 'SRT',
enabled: true,
enabled: false,
},
backgroundImageUrl: undefined,
autoAnimoji: true,
backgroundImageUrl: null,
autoAnimoji: false,
enablePalindrome: false,
templateId: null,
title: undefined,
logoParams: null,
bgmParams: null,
materialUrl: undefined,
title: null,
logoParams: {
logoUrl: null,
},
bgmParams: {
bgmUrl: null,
},
materialUrl: null,
}
}
......@@ -57,14 +65,50 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
state: (): DigitalTemplate => getLocalState(),
actions: {
setFigureId(figureId: string | null) {
setFigureId(figureId: string) {
this.figureId = figureId
},
setDigitalImageUrl(digitalImageUrl: string) {
this.digitalImageUrl = digitalImageUrl
},
setPerson(person: string) {
this.ttsParams.person = person
},
setSpeed(speed: string) {
this.ttsParams.speed = speed
},
setPitch(pitch: string) {
this.ttsParams.pitch = pitch
},
setDigitalImagePositionX(x: number) {
this.dhParams.position.x = x
},
setDigitalImagePositionY(y: number) {
this.dhParams.position.y = y
},
setDigitalImagePositionW(w: number) {
this.dhParams.position.w = w
},
setDigitalImagePositionH(h: number) {
this.dhParams.position.h = h
},
setBackgroundImageUrl(backgroundImageUrl: string) {
this.backgroundImageUrl = backgroundImageUrl
},
setSubtitleEnabled(subtitleEnabled: boolean) {
this.subtitleParams.enabled = subtitleEnabled
},
updateDigitalCreation(digitalCreation: DigitalTemplate) {
this.$state = { ...this.$state, ...digitalCreation }
},
......
export interface DigitalTemplate {
id: number
templateType: string
videoName: string
id: number | null
coverUrl: string | null
demonstrationGifUrl: string | null
demonstrationVideoUrl: string | null
templateType: string | null
templateName: string | null
taskType: string
requestId?: string
inputImageUrl?: string
requestId: string | null
inputImageUrl: string | null
driveType: string
text: string
text: string | null
ttsParams: {
person: string
person: string | null
speed: string
volume: string
pitch: string
}
inputAudioUrl?: string
callbackUrl?: string
inputAudioUrl: string | null
callbackUrl: string | null
figureId: string | null
digitalImageUrl?: string | null
videoParams: {
width: number
height: number
width: number | null
height: number | null
transparent: boolean
}
dhParams: {
cameraId?: number
cameraId: number | null
position: {
x: number
y: number
w?: number
h?: number
w: number
h: number
}
}
subtitleParams: {
subtitlePolicy: string
subtitlePolicy: string | null
enabled: boolean
}
backgroundImageUrl?: string
backgroundImageUrl: string | null
autoAnimoji: boolean
enablePalindrome: boolean
templateId?: any
title?: string
logoParams?: any
bgmParams?: any
materialUrl?: string
templateId: string | null
title: string | null
logoParams: {
logoUrl: string | null
}
bgmParams: {
bgmUrl: string | null
}
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 ImageItem {
......@@ -52,9 +67,12 @@ export interface ImageItem {
imageUrl: string
}
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 TimbreItem {
id: number
name: string
timebreId: string
sex: string
style: string[]
applyScene: string[]
audioUrl: string
}
<script setup lang="ts">
import { fetchBackgroundImage } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
import { ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const imageList = ref<ImageItem[]>([])
const uploadLoading = ref(false)
const showCropModal = ref(false)
const loaded = ref(Array(imageList.value.length).fill(false))
getBackgroundImageList()
async function getBackgroundImageList() {
const res = await fetchBackgroundImage<ImageItem[]>()
if (res.code === 0) {
imageList.value = res.data.map((i) => ({ ...i, checked: i.imageUrl === digitalCreationStore.backgroundImageUrl }))
loaded.value = Array(res.data.length).fill(false)
}
}
function uploadImage() {
showCropModal.value = true
}
function handleClickImage(image: ImageItem) {
digitalCreationStore.setBackgroundImageUrl(image.imageUrl)
}
function handleDelete() {
window.$dialog.warning({
title: '刪除圖片',
content: '是否刪除該圖片?',
positiveText: '是',
negativeText: '否',
onPositiveClick: () => {
window.$message.success('刪除成功')
},
onNegativeClick: () => {
window.$message.error('刪除失敗')
},
})
}
function onImageLoaded(index: number) {
loaded.value[index] = true
}
</script>
<template>
<n-input round placeholder="搜索">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
</n-input>
<div class="h-4"></div>
<n-grid :x-gap="12" :y-gap="12" :cols="3">
<n-gi>
<n-spin :show="uploadLoading">
<label
class="h-22 w-22 hover:border-blue flex cursor-pointer flex-col items-center justify-center rounded-lg border border-gray-200"
for="upload"
>
<CustomIcon class="text-lg" icon="mingcute:add-line" />
</label>
<input id="upload" type="file" accept="image/*" class="hidden" @change="uploadImage" />
<template #description>上傳中</template>
</n-spin>
</n-gi>
<n-gi v-for="(image, index) in imageList" :key="index">
<n-spin :show="!loaded[index]">
<div
class="h-22 w-22 group relative cursor-pointer overflow-hidden rounded-lg border border-2"
:class="image.imageUrl === digitalCreationStore.backgroundImageUrl ? 'border-blue' : 'border-transparent'"
@click="handleClickImage(image)"
>
<img class="h-full w-full object-contain" :src="image.imageUrl" @load="onImageLoaded(index)" />
<div class="from-gray absolute bottom-0 h-5 w-full bg-gradient-to-t px-1 text-xs leading-5 text-white">
{{ image.imageName }}
</div>
<div
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"
>
<CustomIcon icon="mi:delete" class="text-lg text-white" />
</div>
</div>
</n-spin>
</n-gi>
</n-grid>
<n-modal v-model:show="showCropModal" preset="card" title="裁剪圖片"> 内容 </n-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const x = ref(0)
const y = ref(0)
const w = ref(0)
const h = ref(0)
const isLock = ref(false)
</script>
<template>
<div>背景位置</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="x">
<template #prefix>X</template>
</n-input-number>
<n-input-number v-model:value="y">
<template #prefix>Y</template>
</n-input-number>
</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="w">
<template #prefix>W</template>
</n-input-number>
<n-input-number v-model:value="h">
<template #prefix>H</template>
</n-input-number>
</div>
<div class="mt-4 flex items-center justify-between">
<span>鎖定背景</span>
<n-switch v-model:value="isLock" />
</div>
</template>
<script setup lang="ts">
import BackgroundImages from './background-images.vue'
// import BackgroundPosition from './background-position.vue'
import { fetchBackgroundImage, fetchInfoByImageName, uploadImageFile } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
import { ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const imageList = ref<ImageItem[]>([])
const searchName = ref('')
const uploadLoading = ref(false)
const loaded = ref(Array(imageList.value.length).fill(false))
getBackgroundImageList()
async function getBackgroundImageList() {
const res = await fetchBackgroundImage<ImageItem[]>()
if (res.code === 0) {
imageList.value = res.data.map((i) => ({ ...i, checked: i.imageUrl === digitalCreationStore.backgroundImageUrl }))
}
}
async function handleSearch(value: string) {
const res = await fetchInfoByImageName<ImageItem[]>(value)
if (res.code === 0) {
imageList.value = res.data
}
}
async function uploadImage(event: any) {
const e = window.event || event
const file = e.target.files[0]
const fileName = file.name.substring(0, file.name.lastIndexOf('.'))
const maxImageSize = 1024 * 1024 * 3
if (!['png', 'jpg', 'jpeg'].includes(file.type.split('/')[1])) {
window.$message.error('必须为png或者jpg格式')
return
}
if (file.size > maxImageSize) {
window.$message.error('图片不能超过3MB')
return
}
const URL = window.URL || window.webkitURL
const img = new Image()
img.src = URL.createObjectURL(file)
img.onload = async function () {
const formData = new FormData()
formData.append('file', file)
uploadLoading.value = true
const res = await uploadImageFile(fileName, formData).finally(() => (uploadLoading.value = false))
if (res.code === 0) {
getBackgroundImageList()
}
}
}
function handleClickImage(image: ImageItem) {
digitalCreationStore.setBackgroundImageUrl(image.imageUrl)
}
function handleDelete() {
window.$dialog.warning({
title: '刪除圖片',
content: '是否刪除該圖片?',
positiveText: '是',
negativeText: '否',
onPositiveClick: () => {
window.$message.success('刪除成功')
},
onNegativeClick: () => {
window.$message.error('刪除失敗')
},
})
}
function onImageLoaded(index: number) {
loaded.value[index] = true
}
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="images" tab="圖片">
<BackgroundImages />
<n-tabs type="line" animated class="h-full">
<n-tab-pane name="images" tab="圖片" class="h-full">
<div class="h-full overflow-y-auto px-4 py-2">
<n-input v-model:value="searchName" round placeholder="搜索" @input="handleSearch">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
</n-input>
<div class="h-4"></div>
<n-grid :x-gap="12" :y-gap="12" :cols="3">
<n-gi>
<n-spin :show="uploadLoading">
<label
class="h-22 w-22 hover:border-blue flex cursor-pointer flex-col items-center justify-center rounded-lg border border-gray-200"
for="upload"
>
<CustomIcon class="text-lg" icon="mingcute:add-line" />
</label>
<input id="upload" type="file" accept="image/*" class="hidden" @change="uploadImage" />
<template #description>上傳中</template>
</n-spin>
</n-gi>
<n-gi v-for="(image, index) in imageList" :key="index">
<n-spin :show="!loaded[index]">
<div
class="h-22 w-22 group relative cursor-pointer overflow-hidden rounded-lg border border-2 bg-gray-100"
:class="
image.imageUrl === digitalCreationStore.backgroundImageUrl ? 'border-blue' : 'border-transparent'
"
@click="handleClickImage(image)"
>
<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"
>
{{ image.imageName }}
</div>
<div
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"
>
<CustomIcon icon="mi:delete" class="text-lg text-white" />
</div>
</div>
</n-spin>
</n-gi>
</n-grid>
</div>
</n-tab-pane>
<!-- <n-tab-pane name="position" tab="位置">
<BackgroundPosition />
</n-tab-pane> -->
</n-tabs>
</template>
<style lang="scss" scoped>
:deep(.n-tabs-nav-scroll-wrapper) {
padding: 0 16px;
}
</style>
<script setup lang="ts">
import { ref } from 'vue'
const active = ref(false)
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="caption" tab="字幕">
<div class="flex items-center justify-between">
<span>是否開啓</span>
<n-switch v-model:value="active" />
</div>
</n-tab-pane>
</n-tabs>
</template>
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
import { TimbreItem } from '@/store/types/creation'
import { computed, ref } from 'vue'
interface Props {
value?: TimbreItem
showToggle?: boolean
}
interface Emits {
(e: 'click', value: TimbreItem): void
(e: 'toggle', value: boolean): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
const digitalCreationStore = useDigitalCreationStore()
const digitalAudio = ref<HTMLAudioElement>()
const person = computed(() => digitalCreationStore.ttsParams.person)
function playAudio() {
digitalAudio.value?.play()
}
</script>
<template>
<div
class="relative mb-4 flex items-center gap-2 rounded-2xl border p-2 hover:shadow"
: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-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-32 truncate">{{ value?.name }}</div>
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" @click.stop.prevent="playAudio" />
</div>
<div class="flex gap-2">
<n-tag v-for="(style, index) in value?.style" :key="index" type="warning" round>{{ style }}</n-tag>
</div>
</div>
<div v-if="showToggle" class="absolute right-2 top-2">
<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>
<script setup lang="ts">
import { ref } from 'vue'
import { fetchDigitalHumanTimbreList, fetchTimbreByExample } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { TimbreItem } from '@/store/types/creation'
import { computed, onMounted, ref, watch } from 'vue'
import DigitalAudioCard from './digital-audio-card.vue'
const pace = ref(1)
const intonation = ref(1)
const showAll = ref(false)
const tabValue = ref(0)
const tablist = [
{ label: '女性', key: 0 },
{ label: '男性', key: 1 },
const digitalCreationStore = useDigitalCreationStore()
const sexValue = ref(0)
const sexList = [
{ key: 0, label: '女性' },
{ key: 1, label: '男性' },
]
const digitalTimbreValue = ref<TimbreItem>()
const digitalTimbreList = ref<TimbreItem[]>([])
const digitalTimbreFemaleList = ref<TimbreItem[]>([])
const digitalTimbreMaleList = ref<TimbreItem[]>([])
const showAll = ref(false)
const searchName = ref('')
const speed = computed({
get() {
return Number(digitalCreationStore.ttsParams.speed)
},
set(value) {
digitalCreationStore.setSpeed(String(value))
},
})
const pitch = computed({
get() {
return Number(digitalCreationStore.ttsParams.pitch)
},
set(value) {
digitalCreationStore.setPitch(String(value))
},
})
watch(
() => [digitalCreationStore.ttsParams.person, digitalTimbreList.value.length],
([person, len]) => {
if (person && len) {
digitalTimbreValue.value = digitalTimbreList.value.find((i) => i.timebreId === person)
}
},
)
onMounted(() => {
getDigitalTimbreList()
})
async function getDigitalTimbreList() {
const res = await fetchDigitalHumanTimbreList<TimbreItem[]>()
if (res.code === 0) {
digitalTimbreList.value = res.data
digitalTimbreFemaleList.value = digitalTimbreList.value.filter((i) => i.sex === '女')
digitalTimbreMaleList.value = digitalTimbreList.value.filter((i) => i.sex === '男')
}
}
async function handleSearch(value: string) {
const res = await fetchTimbreByExample<TimbreItem[]>(value)
if (res.code === 0) {
digitalTimbreList.value = res.data
digitalTimbreFemaleList.value = digitalTimbreList.value.filter((i) => i.sex === '女')
digitalTimbreMaleList.value = digitalTimbreList.value.filter((i) => i.sex === '男')
}
}
function handleClickAudioCard(timbreItem: TimbreItem) {
digitalCreationStore.setPerson(timbreItem.timebreId)
}
</script>
<template>
<div class="h-full overflow-y-auto px-4 py-2">
<div v-if="!showAll">
<div class="relative flex items-center gap-2 rounded-2xl border p-2">
<div class="h-16 w-16 rounded-lg bg-gray-200"></div>
<div class="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-24 truncate">度清風</div>
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
</div>
<div class="flex gap-2">
<n-tag type="warning" round>知性大方</n-tag>
<n-tag type="success" round>客服助理</n-tag>
<n-tag type="success" round>知性大方</n-tag>
<n-tag type="warning" round>客服助理</n-tag>
</div>
</div>
<div class="absolute right-2 top-2">
<CustomIcon class="cursor-pointer text-lg" icon="ant-design:swap-outlined" @click="showAll = true" />
</div>
</div>
<DigitalAudioCard :value="digitalTimbreValue" show-toggle @toggle="showAll = true" />
<div class="mt-4 text-lg">聲音</div>
<div class="mt-4 flex items-center gap-2">
<div class="w-12">語速:</div>
<n-slider v-model:value="pace" class="flex-1" :max="1.5" :min="0.5" :step="0.25" />
<div class="w-10">{{ pace }}x</div>
<n-slider v-model:value="speed" class="flex-1" :max="15" :min="0" :step="1" />
<div class="w-10">{{ speed }}</div>
</div>
<div class="mt-4 flex items-center gap-2">
<div class="w-12">語調:</div>
<n-slider v-model:value="intonation" class="flex-1" :max="5" :min="1" :step="1" />
<div class="w-10">{{ intonation }}</div>
<n-slider v-model:value="pitch" class="flex-1" :max="15" :min="0" :step="1" />
<div class="w-10">{{ pitch }}</div>
</div>
</div>
......@@ -53,7 +98,7 @@ const tablist = [
</template>
返回
</n-button>
<n-input round placeholder="搜索">
<n-input v-model:value="searchName" round placeholder="搜索" @input="handleSearch">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
......@@ -61,24 +106,16 @@ const tablist = [
</div>
<div class="flex justify-end pb-3">
<HorizontalTabs v-model:value="tabValue" :list="tablist" />
<HorizontalTabs v-model:value="sexValue" :list="sexList" />
</div>
<div>
<div class="flex items-center gap-2 rounded-2xl border p-2">
<div class="h-16 w-16 rounded-lg bg-gray-200"></div>
<div class="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-24 truncate">度清風</div>
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
</div>
<div class="flex gap-2">
<n-tag type="warning" round>知性大方</n-tag>
<n-tag type="success" round>客服助理</n-tag>
<n-tag type="success" round>知性大方</n-tag>
<n-tag type="warning" round>客服助理</n-tag>
</div>
</div>
<DigitalAudioCard
v-for="(timbre, index) in sexValue ? digitalTimbreMaleList : digitalTimbreFemaleList"
:key="index"
:value="timbre"
@click="handleClickAudioCard"
/>
</div>
</div>
</div>
......
<script setup lang="ts">
import { ImageItem } from '@/store/types/creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
interface Props {
value: ImageItem
}
interface Emits {
(e: 'click', id: string | null): void
(e: 'click', value: ImageItem): void
}
defineProps<Props>()
......@@ -18,18 +18,18 @@ const digitalCreationStore = useDigitalCreationStore()
<template>
<div
class="hover:border-blue relative h-28 w-16 cursor-pointer overflow-hidden rounded border border-2"
class="hover:border-blue relative h-28 w-16 cursor-pointer rounded border border-2 bg-gray-100"
:class="digitalCreationStore.figureId === value.figureId ? 'border-blue' : 'border-transparent'"
@click="emit('click', value.figureId)"
@click="emit('click', value)"
>
<img :src="value.imageUrl" />
<div class="from-gray absolute bottom-0 h-5 w-full bg-gradient-to-t 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 }}
</div>
<!-- <CustomIcon
<CustomIcon
v-if="digitalCreationStore.figureId === value.figureId"
icon="teenyicons:tick-circle-solid"
class="text-blue absolute -left-2 -top-2 text-lg"
/> -->
icon="si-glyph:checked"
class="text-blue absolute left-0 top-0 text-xs"
/>
</div>
</template>
<script setup lang="ts">
import { fetch2DBoutiqueImageList, fetch2DFewShotImageList, fetch3DImageList } from '@/apis/digital-creation'
import {
fetch2DBoutiqueImageList,
fetch2DFewShotImageList,
fetch3DImageList,
fetchInfoByImageName,
} from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref } from 'vue'
import DigitalCard from './digital-card.vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
import DigitalCard from './digital-human-card.vue'
const digitalCreationStore = useDigitalCreationStore()
const threeDImageList = ref<ImageItem[]>([])
......@@ -11,6 +16,7 @@ const twoDBoutiqueImageList = ref<ImageItem[]>([])
const twoDFewShotImageList = ref<ImageItem[]>([])
const allImageList = ref<ImageItem[]>([])
const showAll = ref(false)
const searchName = ref('')
onMounted(() => {
getDigitalImageList()
......@@ -27,8 +33,16 @@ async function getDigitalImageList() {
res3.code === 0 && (twoDFewShotImageList.value = res3.data)
}
function handleClickDigitalImage(id: string | null) {
digitalCreationStore.setFigureId(id)
async function handleSearch(value: string) {
const res = await fetchInfoByImageName<ImageItem[]>(value)
if (res.code === 0) {
allImageList.value = res.data
}
}
function handleClickDigitalImage(digitalItem: ImageItem) {
digitalCreationStore.setFigureId(digitalItem.figureId!)
digitalCreationStore.setDigitalImageUrl(digitalItem.imageUrl)
}
function handleClickAll(imageType: ImageType) {
......@@ -48,9 +62,10 @@ function handleClickAll(imageType: ImageType) {
</script>
<template>
<div class="h-full overflow-y-auto px-4 py-2">
<div v-if="!showAll">
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<div class="flex items-center justify-between pb-3 leading-8">
<span>3D數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.THREE_D)">全部</span>
</div>
......@@ -65,7 +80,7 @@ function handleClickAll(imageType: ImageType) {
</div>
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<div class="flex items-center justify-between pb-3 leading-8">
<span>2D精品數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.TWO_D_BOUTIQUE)">全部</span>
</div>
......@@ -80,7 +95,7 @@ function handleClickAll(imageType: ImageType) {
</div>
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<div class="flex items-center justify-between pb-3 leading-8">
<span>2D小樣本數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.TWO_D_FEW_SHOT)">全部</span>
</div>
......@@ -103,7 +118,7 @@ function handleClickAll(imageType: ImageType) {
</template>
返回
</n-button>
<n-input round placeholder="搜索">
<n-input v-model:value="searchName" round placeholder="搜索" @input="handleSearch">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
......@@ -113,4 +128,5 @@ function handleClickAll(imageType: ImageType) {
<DigitalCard v-for="item in allImageList" :key="item.id" :value="item" @click="handleClickDigitalImage" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { computed } from 'vue'
const x = ref(0)
const y = ref(0)
const w = ref(0)
const h = ref(0)
const digitalCreationStore = useDigitalCreationStore()
const digitalImagePositionX = computed({
get() {
return digitalCreationStore.dhParams.position.x
},
set(value) {
digitalCreationStore.setDigitalImagePositionX(value)
},
})
const digitalImagePositionY = computed({
get() {
return digitalCreationStore.dhParams.position.y
},
set(value) {
digitalCreationStore.setDigitalImagePositionY(value)
},
})
const digitalImagePositionW = computed({
get() {
return digitalCreationStore.dhParams.position.w
},
set(width) {
const height = (width * 16) / 9
digitalCreationStore.setDigitalImagePositionW(width)
digitalCreationStore.setDigitalImagePositionH(parseInt(height + ''))
},
})
const digitalImagePositionH = computed({
get() {
return digitalCreationStore.dhParams.position.h
},
set(height) {
const width = (height * 9) / 16
digitalCreationStore.setDigitalImagePositionH(height)
digitalCreationStore.setDigitalImagePositionW(parseInt(width + ''))
},
})
</script>
<template>
<div class="h-full overflow-y-auto px-4 py-2">
<div>數字人位置</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="x">
<template #prefix>X</template>
<n-input-number v-model:value="digitalImagePositionX">
<template #prefix><div class="text-gray w-4 text-center text-xs">X</div></template>
</n-input-number>
<n-input-number v-model:value="y">
<template #prefix>Y</template>
<n-input-number v-model:value="digitalImagePositionY">
<template #prefix><div class="text-gray w-4 text-center text-xs">Y</div></template>
</n-input-number>
</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="w">
<template #prefix>W</template>
<div class="mt-4 flex">
<n-input-number v-model:value="digitalImagePositionW" class="flex-1">
<template #prefix><div class="text-gray w-4 text-center text-xs">W</div></template>
</n-input-number>
<n-input-number v-model:value="h">
<template #prefix>H</template>
<div class="flex w-4 items-center justify-center">
<CustomIcon class="text-gray text-xs" icon="fa6-solid:lock" />
</div>
<n-input-number v-model:value="digitalImagePositionH" class="flex-1">
<template #prefix><div class="text-gray w-4 text-center text-xs">H</div></template>
</n-input-number>
</div>
</div>
</template>
......@@ -5,15 +5,21 @@ import DigitalPosition from './digital-position.vue'
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="human" tab="選擇">
<n-tabs type="line" animated class="h-full">
<n-tab-pane name="human" tab="選擇" class="h-full">
<DigitalHuman />
</n-tab-pane>
<n-tab-pane name="position" tab="位置">
<n-tab-pane name="position" tab="位置" class="h-full">
<DigitalPosition />
</n-tab-pane>
<n-tab-pane name="audio" tab="聲音">
<n-tab-pane name="audio" tab="聲音" class="h-full">
<DigitalAudio />
</n-tab-pane>
</n-tabs>
</template>
<style lang="scss" scoped>
:deep(.n-tabs-nav-scroll-wrapper) {
padding: 0 16px;
}
</style>
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
import { computed, ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const previewContent = ref<HTMLElement>()
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)
</script>
<template>
<div class="flex flex-col overflow-hidden rounded-2xl">
<div class="flex-1 overflow-hidden bg-gray-200">
<div class="relative mx-auto aspect-[9/16] h-full bg-green-50">
<div ref="previewContent" class="relative mx-auto aspect-[9/16] h-full overflow-hidden bg-gray-100">
<img
v-show="digitalCreationStore.backgroundImageUrl"
:src="digitalCreationStore.backgroundImageUrl"
:src="digitalCreationStore.backgroundImageUrl!"
class="absolute h-full w-full object-cover"
/>
<img
v-show="digitalCreationStore.digitalImageUrl"
:src="digitalCreationStore.digitalImageUrl!"
class="absolute max-w-none object-fill"
:style="{
width: `${digitalHumanWidth}px`,
height: `${digitalHumanHeight}px`,
left: `${digitalHumanLeft}px`,
top: `${digitalHumanTop}px`,
}"
/>
</div>
</div>
<div class="flex bg-white p-4">
......
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
import { computed } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const subtitleEnabled = computed({
get() {
return digitalCreationStore.subtitleParams.enabled
},
set(value) {
digitalCreationStore.setSubtitleEnabled(value)
},
})
</script>
<template>
<n-tabs type="line" animated class="h-full">
<n-tab-pane name="subtitle" tab="字幕" class="h-full">
<div class="flex h-full items-center justify-between overflow-y-auto px-4 py-2">
<span>是否開啓</span>
<n-switch v-model:value="subtitleEnabled" />
</div>
</n-tab-pane>
</n-tabs>
</template>
<style lang="scss" scoped>
:deep(.n-tabs-nav-scroll-wrapper) {
padding: 0 16px;
}
</style>
<script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { DigitalTemplate } from '@/store/types/creation'
import { onMounted } from 'vue'
import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue'
import SideBar from './side-bar.vue'
import { onMounted } from 'vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
const digitalCreationStore = useDigitalCreationStore()
......@@ -16,7 +16,15 @@ onMounted(() => {
async function getDigitalImageList(id: number) {
const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
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)
}
}
</script>
......
<script setup lang="ts">
import { ref } from 'vue'
import BackgroundSetting from '../components/background/background-setting.vue'
import CaptionSetting from '../components/caption/caption-setting.vue'
import SubtitleSetting from '../components/subtitle/subtitle-setting.vue'
import DigitalSetting from '../components/digital/digital-setting.vue'
const value = ref('')
const value = ref('digital')
const barList = [
{
key: 'digital',
......@@ -17,7 +17,7 @@ const barList = [
icon: 'icon-park-outline:background-color',
},
{
key: 'caption',
key: 'subtitle',
label: '字幕',
icon: 'icon-park-outline:text-message',
},
......@@ -27,10 +27,10 @@ const barList = [
<template>
<section class="h-full pl-4">
<div class="flex h-full rounded-2xl bg-white">
<div class="flex-1 overflow-hidden px-4 py-2">
<div class="flex-1 overflow-hidden py-2">
<DigitalSetting v-if="value === 'digital'" />
<BackgroundSetting v-if="value === 'background'" />
<CaptionSetting v-if="value === 'caption'" />
<SubtitleSetting v-if="value === 'subtitle'" />
</div>
<VerticalTabs v-model:value="value" class="border-l" :list="barList"></VerticalTabs>
</div>
......
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