From c07a61c42410c89c59d80a11d53b86f4ec3b8b31 Mon Sep 17 00:00:00 2001 From: sion123 <450702724@qq.com> Date: Thu, 5 Mar 2026 21:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/app/web-gold/src/api/kling.js | 83 +- frontend/app/web-gold/src/api/material.js | 62 - .../web-gold/src/components/VideoSelector.vue | 2 +- .../web-gold/src/components/VoiceSelector.vue | 324 ++++- .../components/material/MaterialMixModal.vue | 2 +- .../app/web-gold/src/composables/useUpload.js | 2 +- .../web-gold/src/views/kling/IdentifyFace.vue | 1239 +++++++++-------- .../views/kling/components/GenerateStep.vue | 346 +++++ .../views/kling/components/StepNavigation.vue | 171 +++ .../views/kling/components/TimelinePanel.vue | 314 +++++ .../kling/components/VideoSelectStep.vue | 472 +++++++ .../kling/components/VoiceConfigStep.vue | 324 +++++ .../kling/stores/useDigitalHumanStore.ts | 638 ++++++--- .../src/views/kling/types/identify-face.ts | 29 +- .../web-gold/src/views/kling/utils/format.ts | 41 + .../src/views/material/MaterialListNew.vue | 4 +- .../app/web-gold/src/views/material/Mix.vue | 4 +- .../components/SceneSelectorModal.vue | 2 +- openspec/plans/points-oss-quota-system.md | 455 ------ .../file/service/TikUserFileServiceImpl.java | 8 +- .../tik/file/vo/app/AppTikUserFileRespVO.java | 4 +- 21 files changed, 3061 insertions(+), 1465 deletions(-) create mode 100644 frontend/app/web-gold/src/views/kling/components/GenerateStep.vue create mode 100644 frontend/app/web-gold/src/views/kling/components/StepNavigation.vue create mode 100644 frontend/app/web-gold/src/views/kling/components/TimelinePanel.vue create mode 100644 frontend/app/web-gold/src/views/kling/components/VideoSelectStep.vue create mode 100644 frontend/app/web-gold/src/views/kling/components/VoiceConfigStep.vue create mode 100644 frontend/app/web-gold/src/views/kling/utils/format.ts delete mode 100644 openspec/plans/points-oss-quota-system.md diff --git a/frontend/app/web-gold/src/api/kling.js b/frontend/app/web-gold/src/api/kling.js index 9758bfc589..ff4eb3abbb 100644 --- a/frontend/app/web-gold/src/api/kling.js +++ b/frontend/app/web-gold/src/api/kling.js @@ -2,51 +2,10 @@ * 可灵数字人 API */ import request from './http' -import { MaterialService } from './material' - -// ========== 辅助函数 ========== /** - * 执行人脸识别并返回结果 + * 人脸识别 */ -async function performFaceIdentification(videoUrl) { - const identifyRes = await identifyFace({ video_url: videoUrl }) - if (identifyRes.code !== 0) { - throw new Error(identifyRes.msg || '识别失败') - } - - const faceData = identifyRes.data.data?.face_data?.[0] - return { - sessionId: identifyRes.data.sessionId, - faceId: faceData?.face_id || null, - startTime: faceData?.start_time || 0, - endTime: faceData?.end_time || 0 - } -} - -/** - * 构建标准响应格式 - */ -function buildIdentifyResponse(fileId, videoUrl, identifyData, isUploadedFile = false) { - return { - success: true, - data: { - fileId, - videoUrl, - sessionId: identifyData.sessionId, - faceId: identifyData.faceId, - startTime: isUploadedFile - ? Math.round(identifyData.startTime * 1000) - : identifyData.startTime, - endTime: isUploadedFile - ? Math.round(identifyData.endTime * 1000) - : identifyData.endTime - } - } -} - -// ========== API 方法 ========== - export function identifyFace(data) { return request({ url: '/webApi/api/tik/kling/identify-face', @@ -55,6 +14,9 @@ export function identifyFace(data) { }) } +/** + * 创建口型同步任务 + */ export function createLipSyncTask(data) { return request({ url: '/webApi/api/tik/kling/task/create', @@ -63,43 +25,12 @@ export function createLipSyncTask(data) { }) } +/** + * 获取口型同步任务状态 + */ export function getLipSyncTask(taskId) { return request({ url: `/webApi/api/tik/kling/lip-sync/${taskId}`, method: 'get' }) } - -/** - * 识别已上传的视频 - */ -export async function identifyUploadedVideo(videoFile) { - const urlRes = await MaterialService.getVideoPlayUrl(videoFile.fileId) - if (urlRes.code !== 0 || !urlRes.data) { - throw new Error(urlRes.msg || '获取播放链接失败') - } - - const identifyData = await performFaceIdentification(urlRes.data) - return buildIdentifyResponse(videoFile.id, urlRes.data, identifyData, false) -} - -/** - * 上传视频并识别 - */ -export async function uploadAndIdentifyVideo(file) { - const uploadRes = await MaterialService.uploadFile(file, 'digital_human', null, null) - if (uploadRes.code !== 0) { - throw new Error(uploadRes.msg || '上传失败') - } - - const fileId = uploadRes.data - - const urlRes = await MaterialService.getVideoPlayUrl(fileId) - if (urlRes.code !== 0) { - throw new Error(urlRes.msg || '获取播放链接失败') - } - - const identifyData = await performFaceIdentification(urlRes.data) - return buildIdentifyResponse(fileId, urlRes.data, identifyData, true) -} - diff --git a/frontend/app/web-gold/src/api/material.js b/frontend/app/web-gold/src/api/material.js index 879a85d9a1..b4c440aceb 100644 --- a/frontend/app/web-gold/src/api/material.js +++ b/frontend/app/web-gold/src/api/material.js @@ -9,38 +9,6 @@ import { API_BASE } from '@gold/config/api' // 使用 webApi 前缀,确保能够被代理 const BASE_URL = `${API_BASE.APP_TIK}/file` -/** - * 获取视频时长(秒) - * @param {File} file - 视频文件对象 - * @returns {Promise} 时长(秒) - */ -function getVideoDuration(file) { - return new Promise((resolve, reject) => { - // 只处理视频文件 - if (!file.type.startsWith('video/')) { - resolve(null); - return; - } - - const video = document.createElement('video'); - video.preload = 'metadata'; - video.muted = true; // 静音,避免浏览器阻止自动播放 - - video.onloadedmetadata = function() { - const duration = Math.round(video.duration); - URL.revokeObjectURL(video.src); - resolve(duration); - }; - - video.onerror = function() { - URL.revokeObjectURL(video.src); - resolve(60); - }; - - video.src = URL.createObjectURL(file); - }); -} - /** * 素材库 API 服务 */ @@ -61,36 +29,6 @@ export const MaterialService = { return http.get(`${BASE_URL}/page`, { params }) }, - /** - * 上传文件 - * @param {File} file - 文件对象 - * @param {string} fileCategory - 文件分类(video/generate/audio/mix/voice) - * @param {number} duration - 视频时长(秒,可选,自动获取) - * @param {number} groupId - 分组编号(可选) - * @returns {Promise} - */ - async uploadFile(file, fileCategory, duration = null, groupId = null) { - if (duration === null && file.type.startsWith('video/')) { - duration = await getVideoDuration(file); - } - - const formData = new FormData() - formData.append('file', file) - formData.append('fileCategory', fileCategory) - - if (duration !== null) { - formData.append('duration', duration.toString()); - } - - if (groupId !== null) { - formData.append('groupId', groupId.toString()) - } - - return http.post(`${BASE_URL}/upload`, formData, { - timeout: 30 * 60 * 1000 - }) - }, - /** * 获取预签名URL(直传模式) * @param {Object} params - 请求参数 diff --git a/frontend/app/web-gold/src/components/VideoSelector.vue b/frontend/app/web-gold/src/components/VideoSelector.vue index f7a9c25ce5..58dbe76f95 100644 --- a/frontend/app/web-gold/src/components/VideoSelector.vue +++ b/frontend/app/web-gold/src/components/VideoSelector.vue @@ -294,7 +294,7 @@ const getVideoPreviewUrl = (video) => { ? video.coverBase64 : `data:image/jpeg;base64,${video.coverBase64}` } - return video.previewUrl || video.coverUrl || defaultCover + return video.imgUrl || video.coverUrl || defaultCover } const handleCancel = () => { diff --git a/frontend/app/web-gold/src/components/VoiceSelector.vue b/frontend/app/web-gold/src/components/VoiceSelector.vue index 4faec8a771..a0de85b083 100644 --- a/frontend/app/web-gold/src/components/VoiceSelector.vue +++ b/frontend/app/web-gold/src/components/VoiceSelector.vue @@ -5,50 +5,78 @@ -
- +
+ +
+
+
+ + + + + +
+ 音色选择 + {{ currentVoiceName }} +
- - - 合成 - +
+
+ + + +
+ + + + 合成试听 + +
+
+ + + +
+
+
+ + + 下载音频 + +
+
+
- - -
- - - - 下载音频 -
- diff --git a/frontend/app/web-gold/src/components/material/MaterialMixModal.vue b/frontend/app/web-gold/src/components/material/MaterialMixModal.vue index deb6db5196..f27486235e 100644 --- a/frontend/app/web-gold/src/components/material/MaterialMixModal.vue +++ b/frontend/app/web-gold/src/components/material/MaterialMixModal.vue @@ -157,7 +157,7 @@ const handleConfirm = async () => { // 提取视频URL const videoUrls = videoGroupFiles.value - .map(file => file?.fileUrl || file?.previewUrl) + .map(file => file?.fileUrl || file?.imgUrl) .filter(Boolean) if (videoUrls.length === 0) { diff --git a/frontend/app/web-gold/src/composables/useUpload.js b/frontend/app/web-gold/src/composables/useUpload.js index 1e05e56572..6cef49d188 100644 --- a/frontend/app/web-gold/src/composables/useUpload.js +++ b/frontend/app/web-gold/src/composables/useUpload.js @@ -198,7 +198,7 @@ export function useUpload() { state.status = 'success' state.progress = 100 - const fileId = completeData.data?.infraFileId || completeData.data?.userFileId + const fileId = completeData.data?.userFileId const fileUrl = presignedData.data.presignedUrl onSuccess?.(fileId, fileUrl) diff --git a/frontend/app/web-gold/src/views/kling/IdentifyFace.vue b/frontend/app/web-gold/src/views/kling/IdentifyFace.vue index 601af22cd1..ad94c0d52c 100644 --- a/frontend/app/web-gold/src/views/kling/IdentifyFace.vue +++ b/frontend/app/web-gold/src/views/kling/IdentifyFace.vue @@ -1,37 +1,146 @@