feat: 功能
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* @fileoverview useDigitalHumanGeneration Hook - 数字人生成逻辑封装
|
||||
* @author Claude Code
|
||||
* @fileoverview useDigitalHumanGeneration Hook - 数字人生成逻辑
|
||||
*/
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
@@ -14,18 +13,14 @@ import type {
|
||||
import { identifyUploadedVideo } from '@/api/kling'
|
||||
import { useUpload } from '@/composables/useUpload'
|
||||
|
||||
/**
|
||||
* 数字人生成 Hook
|
||||
* 独立管理所有状态,不依赖外部状态
|
||||
*/
|
||||
export function useDigitalHumanGeneration(): UseDigitalHumanGeneration {
|
||||
// ==================== 响应式状态 ====================
|
||||
|
||||
// ========== 状态 ==========
|
||||
const videoState = ref<VideoState>({
|
||||
uploadedVideo: '',
|
||||
videoFile: null,
|
||||
previewVideoUrl: '',
|
||||
selectedVideo: null,
|
||||
fileId: null,
|
||||
videoSource: null,
|
||||
selectorVisible: false,
|
||||
})
|
||||
@@ -40,24 +35,16 @@ export function useDigitalHumanGeneration(): UseDigitalHumanGeneration {
|
||||
videoFileId: null,
|
||||
})
|
||||
|
||||
// ==================== Upload Hook ====================
|
||||
const { upload } = useUpload()
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
|
||||
/**
|
||||
* 人脸出现时长
|
||||
*/
|
||||
const faceDuration = computed(() => {
|
||||
// ========== 计算属性 ==========
|
||||
const faceDuration = computed(function() {
|
||||
return identifyState.value.faceEndTime - identifyState.value.faceStartTime
|
||||
})
|
||||
|
||||
// ==================== 核心方法 ====================
|
||||
// ========== 方法 ==========
|
||||
|
||||
/**
|
||||
* 处理视频文件上传
|
||||
*/
|
||||
const handleFileUpload = async (file: File): Promise<void> => {
|
||||
async function handleFileUpload(file: File): Promise<void> {
|
||||
if (!file.name.match(/\.(mp4|mov)$/i)) {
|
||||
message.error('仅支持 MP4 和 MOV')
|
||||
return
|
||||
@@ -68,148 +55,101 @@ export function useDigitalHumanGeneration(): UseDigitalHumanGeneration {
|
||||
videoState.value.selectedVideo = null
|
||||
videoState.value.previewVideoUrl = ''
|
||||
videoState.value.videoSource = 'upload'
|
||||
|
||||
resetIdentifyState()
|
||||
|
||||
await performFaceRecognition()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理从素材库选择视频
|
||||
*/
|
||||
const handleVideoSelect = (video: Video): void => {
|
||||
async function handleVideoSelect(video: Video): Promise<void> {
|
||||
videoState.value.selectedVideo = video
|
||||
videoState.value.uploadedVideo = video.fileUrl
|
||||
videoState.value.videoFile = null
|
||||
videoState.value.videoSource = 'select'
|
||||
videoState.value.selectorVisible = false
|
||||
resetIdentifyState()
|
||||
identifyState.value.videoFileId = video.id
|
||||
identifyState.value.videoFileId = video.fileId
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行人脸识别
|
||||
*/
|
||||
const performFaceRecognition = async (): Promise<void> => {
|
||||
async function performFaceRecognition(): Promise<void> {
|
||||
const hasUploadFile = videoState.value.videoFile
|
||||
const hasSelectedVideo = videoState.value.selectedVideo
|
||||
|
||||
if (!hasUploadFile && !hasSelectedVideo) {
|
||||
return
|
||||
}
|
||||
if (!hasUploadFile && !hasSelectedVideo) return
|
||||
|
||||
identifyState.value.identifying = true
|
||||
|
||||
try {
|
||||
let res
|
||||
if (hasSelectedVideo) {
|
||||
res = await identifyUploadedVideo(hasSelectedVideo)
|
||||
identifyState.value.videoFileId = hasSelectedVideo.id
|
||||
const res = await identifyUploadedVideo(hasSelectedVideo) as { success: boolean; data: { sessionId: string; faceId: string | null; startTime: number; endTime: number } }
|
||||
identifyState.value.videoFileId = hasSelectedVideo.fileId
|
||||
|
||||
identifyState.value.sessionId = res.data.sessionId
|
||||
identifyState.value.faceId = res.data.faceId || ''
|
||||
identifyState.value.faceStartTime = res.data.startTime || 0
|
||||
identifyState.value.faceEndTime = res.data.endTime || 0
|
||||
} else {
|
||||
// 处理文件上传(提取封面)
|
||||
const file = hasUploadFile!
|
||||
let coverBase64 = null
|
||||
try {
|
||||
const { extractVideoCover } = await import('@/utils/video-cover')
|
||||
const cover = await extractVideoCover(file, {
|
||||
maxWidth: 800,
|
||||
quality: 0.8
|
||||
})
|
||||
const cover = await extractVideoCover(file, { maxWidth: 800, quality: 0.8 })
|
||||
coverBase64 = cover.base64
|
||||
} catch {
|
||||
// 封面提取失败不影响主流程
|
||||
}
|
||||
|
||||
// 使用useUpload Hook上传文件
|
||||
const fileId = await upload(file, {
|
||||
fileCategory: 'video',
|
||||
groupId: null, // 数字人模块不使用groupId
|
||||
groupId: null,
|
||||
coverBase64,
|
||||
onStart: () => {},
|
||||
onProgress: () => {},
|
||||
onSuccess: () => {
|
||||
message.success('文件上传成功')
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
onStart: function() {},
|
||||
onProgress: function() {},
|
||||
onSuccess: function() {},
|
||||
onError: function(err: Error) {
|
||||
message.error(err.message || '上传失败')
|
||||
}
|
||||
})
|
||||
|
||||
// 生成播放链接
|
||||
// TODO: 获取播放链接逻辑
|
||||
|
||||
res = {
|
||||
success: true,
|
||||
data: {
|
||||
fileId,
|
||||
videoUrl: '', // TODO: 需要获取实际URL
|
||||
sessionId: '', // TODO: 需要实际识别
|
||||
faceId: null,
|
||||
startTime: 0,
|
||||
endTime: 0
|
||||
}
|
||||
}
|
||||
identifyState.value.videoFileId = fileId
|
||||
identifyState.value.sessionId = ''
|
||||
identifyState.value.faceId = ''
|
||||
identifyState.value.faceStartTime = 0
|
||||
identifyState.value.faceEndTime = 0
|
||||
}
|
||||
|
||||
identifyState.value.sessionId = res.data.sessionId
|
||||
identifyState.value.faceId = res.data.faceId
|
||||
identifyState.value.faceStartTime = res.data.startTime || 0
|
||||
identifyState.value.faceEndTime = res.data.endTime || 0
|
||||
identifyState.value.identified = true
|
||||
|
||||
const durationSec = faceDuration.value / 1000
|
||||
const suggestedMaxChars = Math.floor(durationSec * 3.5)
|
||||
message.success(`识别完成!人脸出现时长约 ${durationSec.toFixed(1)} 秒,建议文案不超过 ${suggestedMaxChars} 字`)
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '识别失败')
|
||||
// 识别完成,不显示提示信息
|
||||
} catch (error: unknown) {
|
||||
const err = error as Error
|
||||
message.error(err.message || '识别失败')
|
||||
throw error
|
||||
} finally {
|
||||
identifyState.value.identifying = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重置视频状态
|
||||
*/
|
||||
const resetVideoState = (): void => {
|
||||
function resetVideoState(): void {
|
||||
videoState.value.uploadedVideo = ''
|
||||
videoState.value.videoFile = null
|
||||
videoState.value.selectedVideo = null
|
||||
videoState.value.fileId = null
|
||||
videoState.value.videoSource = null
|
||||
videoState.value.previewVideoUrl = ''
|
||||
videoState.value.selectorVisible = false
|
||||
|
||||
resetIdentifyState()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频预览 URL
|
||||
*/
|
||||
const getVideoPreviewUrl = (video: Video): string => {
|
||||
function getVideoPreviewUrl(video: Video): string {
|
||||
if (video.coverBase64) {
|
||||
if (!video.coverBase64.startsWith('data:')) {
|
||||
return `data:image/jpeg;base64,${video.coverBase64}`
|
||||
}
|
||||
return video.coverBase64
|
||||
return video.coverBase64.startsWith('data:')
|
||||
? video.coverBase64
|
||||
: `data:image/jpeg;base64,${video.coverBase64}`
|
||||
}
|
||||
|
||||
if (video.previewUrl) {
|
||||
return video.previewUrl
|
||||
}
|
||||
|
||||
if (video.coverUrl) {
|
||||
return video.coverUrl
|
||||
}
|
||||
|
||||
if (video.previewUrl) return video.previewUrl
|
||||
if (video.coverUrl) return video.coverUrl
|
||||
return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjExMCIgdmlld0JveD0iMCAwIDIwMCAxMTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTEwIiBmaWxsPSIjMzc0MTUxIi8+CjxwYXRoIGQ9Ik04NSA0NUwxMTUgNjVMMTA1IDg1TDc1IDc1TDg1IDQ1WiIgZmlsbD0iIzU3MjY1MSIvPgo8L3N2Zz4K'
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置识别状态
|
||||
*/
|
||||
const resetIdentifyState = (): void => {
|
||||
function resetIdentifyState(): void {
|
||||
identifyState.value.identified = false
|
||||
identifyState.value.sessionId = ''
|
||||
identifyState.value.faceId = ''
|
||||
@@ -217,14 +157,9 @@ export function useDigitalHumanGeneration(): UseDigitalHumanGeneration {
|
||||
}
|
||||
|
||||
return {
|
||||
// 响应式状态
|
||||
videoState,
|
||||
identifyState,
|
||||
|
||||
// 计算属性
|
||||
faceDuration,
|
||||
|
||||
// 方法
|
||||
handleFileUpload,
|
||||
handleVideoSelect,
|
||||
performFaceRecognition,
|
||||
|
||||
Reference in New Issue
Block a user