diff --git a/api/video_flow.ts b/api/video_flow.ts index 85092ba..d6980b6 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -880,3 +880,67 @@ export const generateCharacterDescription = async (request: { }>> => { return post("/character/generate_description", request); }; + +/** + * 批量更新视频片段状态和视频地址接口 + * @param request - 批量更新视频片段请求参数 + * @returns Promise> + */ +export const batchUpdateVideoSegments = async (request: { + /** 项目ID */ + project_id: string; + /** 要更新的视频片段列表 */ + segments: Array<{ + /** 视频片段ID */ + shot_id: string; + /** 新的视频地址列表 */ + video_urls: string[]; + /** 新的状态 0:视频加载中 1:任务已完成 2:任务失败 */ + status: 0 | 1 | 2; + /** 优化后的描述文本 */ + optimized_description?: string; + /** 关键词列表 */ + keywords?: string[]; + }>; +}): Promise; +}>> => { + return post("/movie/batch_update_video_segments", request); +}; + +/** + * 获取角色在项目中的场景列表接口 + * @param request - 获取角色场景请求参数 + * @returns Promise> + */ +export const getCharacterScenes = async (request: { + /** 项目ID */ + project_id: string; + /** 角色名称或ID */ + character_name: string; +}): Promise; + /** 总数量 */ + total_count: number; +}>> => { + return post("/character/get_character_scenes", request); +}; diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts index 6108e01..e6c2760 100644 --- a/app/service/Interaction/RoleShotService.ts +++ b/app/service/Interaction/RoleShotService.ts @@ -1,7 +1,7 @@ import { useState, useCallback, useMemo } from 'react'; import { VideoSegmentEntity } from '../domain/Entities'; import { RoleEditUseCase } from '../usecase/RoleEditUseCase'; -import { getRoleShots, applyRoleToShots } from '@/api/video_flow'; +import { getCharacterScenes } from '@/api/video_flow'; /** * 扩展的视频片段实体,包含选择状态 @@ -9,7 +9,7 @@ import { getRoleShots, applyRoleToShots } from '@/api/video_flow'; interface ExtendedVideoSegmentEntity extends VideoSegmentEntity { /** 是否已选中 */ selected?: boolean; - /** 是否已应用角色 */ + /** 是否已应用 */ applied?: boolean; } @@ -43,7 +43,7 @@ interface UseRoleShotService { * 角色视频片段服务Hook * 提供角色与视频片段交互的所有响应式功能和业务逻辑 */ -export const useRoleShotServiceHook = (): UseRoleShotService => { +export const useRoleShotServiceHook = (projectId: string): UseRoleShotService => { // 响应式状态 const [shotSelectionList, setShotSelectionList] = useState([]); const [selectedRoleId, setSelectedRoleId] = useState(null); @@ -77,14 +77,28 @@ export const useRoleShotServiceHook = (): UseRoleShotService => { */ const fetchRoleShots = useCallback(async (roleId: string) => { try { - const response = await getRoleShots({ roleId }); - if (response.successful) { - const { shots, appliedShotIds } = response.data; + // 调用新的真实接口获取角色在项目中的场景列表 + const response = await getCharacterScenes({ + project_id: projectId, + character_name: roleId + }); - const extendedShots: ExtendedVideoSegmentEntity[] = shots.map(shot => ({ - ...shot, + if (response.successful) { + const { scenes, total_count } = response.data; + + // 将scenes数据转换为ExtendedVideoSegmentEntity[]数据结构 + const extendedShots: ExtendedVideoSegmentEntity[] = scenes.map(scene => ({ + id: scene.video_id, + name: `视频片段_${scene.video_id}`, + sketchUrl: "", + videoUrl: scene.video_urls, // 保持为string[]类型 + status: 1, // 默认为已完成状态 + lens: [], + updatedAt: Date.now(), + loadingProgress: 100, + disableEdit: false, selected: false, - applied: appliedShotIds.includes(shot.id) + applied: true // 由于是通过角色查询到的,所以都是已应用的 })); setShotSelectionList(extendedShots); @@ -94,13 +108,13 @@ export const useRoleShotServiceHook = (): UseRoleShotService => { const newRoleEditUseCase = new RoleEditUseCase(); setRoleEditUseCase(newRoleEditUseCase); } else { - throw new Error(`获取角色分镜列表失败: ${response.message}`); + throw new Error(`获取角色场景列表失败: ${response.message}`); } } catch (error) { - console.error('获取角色分镜列表失败:', error); + console.error('获取角色场景列表失败:', error); throw error; } - }, []); + }, [projectId]); /** * 切换全选与全不选 diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 05aad39..a1d95f4 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase"; import { VideoSegmentEntity } from "../domain/Entities"; import { LensType, SimpleCharacter } from "../domain/valueObject"; @@ -57,6 +57,8 @@ export const useShotService = (): UseShotService => { const [selectedSegment, setSelectedSegment] = useState(null); const [projectId, setProjectId] = useState(""); const [simpleCharacter, setSimpleCharacter] = useState([]); + // 轮询任务ID + const [intervalId, setIntervalId] = useState(null); // UseCase实例 const [vidoEditUseCase] = useState( new VideoSegmentEditUseCase() @@ -76,6 +78,7 @@ export const useShotService = (): UseShotService => { console.log('segments', segments); setVideoSegments(segments); + setIntervalIdHandler(); } catch (error) { console.error("获取视频片段列表失败:", error); } finally { @@ -85,6 +88,59 @@ export const useShotService = (): UseShotService => { [vidoEditUseCase] ); + const setIntervalIdHandler = async (): Promise => { + // 每次执行前先清除之前的定时器,确保只存在一个定时器 + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + } + // 定义定时任务,每5秒执行一次 + const newIntervalId = setInterval(async () => { + try { + const segments = await vidoEditUseCase.getVideoSegmentList(projectId); + + setVideoSegments(prevSegments => { + const existingSegmentsMap = new Map(prevSegments.map(segment => [segment.id, segment])); + const segmentsToUpdate = segments.filter(segment => segment.id !== selectedSegment?.id); + + segmentsToUpdate.forEach(newSegment => { + const existingSegment = existingSegmentsMap.get(newSegment.id); + + if (existingSegment) { + existingSegmentsMap.set(newSegment.id, { + ...existingSegment, + videoUrl: newSegment.videoUrl, + status: newSegment.status, + sketchUrl: newSegment.sketchUrl, + lens: newSegment.lens, + updatedAt: newSegment.updatedAt, + loadingProgress: newSegment.loadingProgress + }); + } else { + existingSegmentsMap.set(newSegment.id, newSegment); + } + }); + + return Array.from(existingSegmentsMap.values()); + }); + } catch (error) { + console.error('定时获取视频片段列表失败:', error); + } + }, 5000); + + setIntervalId(newIntervalId); + }; + + // 组件卸载时清理定时器 + useEffect(() => { + return () => { + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + } + }; + }, [intervalId]); + /** * 重新生成视频片段 * @param shotPrompt 镜头描述数据 @@ -324,6 +380,6 @@ export const useShotService = (): UseShotService => { addNewLens, deleteLens, filterRole, - setSimpleCharacter + setSimpleCharacter, }; }; diff --git a/app/service/adapter/oldErrAdapter.ts b/app/service/adapter/oldErrAdapter.ts index 731b8da..8a48fbb 100644 --- a/app/service/adapter/oldErrAdapter.ts +++ b/app/service/adapter/oldErrAdapter.ts @@ -136,15 +136,18 @@ export class VideoSegmentEntityAdapter { videoUrls.push(...result.videos.map(video => video.video_url)); } - // // 根据task_status确定状态 + // 根据task_status和videoUrls内容确定状态 let status: 0 | 1 | 2 = 1; // 默认为已完成状态 - // if (data.task_status === "INIT" || data.task_status === "IN_PROGRESS") { - // status = 0; // 视频加载中(INIT和IN_PROGRESS都当成进行中) - // } else if (data.task_status === "COMPLETED") { - // status = 1; // 任务已完成 - // } else if (data.task_status === "FAILED") { - // status = 2; // 任务失败 - // } + if (data.task_status === "INIT" || data.task_status === "IN_PROGRESS") { + // 进行中:videoUrls[0]有内容为已完成,否则为处理中 + status = videoUrls[0] ? 1 : 0; + } else if (data.task_status === "COMPLETED") { + // 已完成:videoUrls[0]有内容为已完成,否则为失败 + status = videoUrls[0] ? 1 : 2; + } else if (data.task_status === "FAILED") { + // 失败:全部为失败 + status = 2; + } // 创建VideoSegmentEntity const entity: VideoSegmentEntity = { diff --git a/app/service/usecase/ShotEditUsecase.ts b/app/service/usecase/ShotEditUsecase.ts index 2296055..aa1f75b 100644 --- a/app/service/usecase/ShotEditUsecase.ts +++ b/app/service/usecase/ShotEditUsecase.ts @@ -9,6 +9,7 @@ import { updateShotPrompt, detailScriptEpisodeNew, faceRecognition, + batchUpdateVideoSegments, } from "@/api/video_flow"; /** @@ -228,6 +229,64 @@ export class VideoSegmentEditUseCase { } } + /** + * @description 批量更新除当前选中片段外的所有视频片段 + * @param projectId 项目ID + * @param currentSegmentId 当前选中的视频片段ID + * @param optimizedDescription 优化后的描述文本 + * @param keywords 关键词列表 + * @returns Promise 更新是否成功 + */ + async batchUpdateOtherVideoSegments( + projectId: string, + currentSegmentId: string, + optimizedDescription: string, + keywords: string[] + ): Promise { + try { + this.loading = true; + + // 获取当前项目的视频片段列表 + const segments = await this.getVideoSegmentList(projectId); + + // 过滤出除当前选中片段外的所有片段 + const otherSegments = segments.filter(segment => segment.id !== currentSegmentId); + + if (otherSegments.length === 0) { + console.log('没有其他视频片段需要更新'); + return true; + } + + // 准备更新数据 + const updateData = otherSegments.map(segment => ({ + shot_id: segment.id, + video_urls: segment.videoUrl || [], + status: segment.status, + optimized_description: optimizedDescription, + keywords: keywords + })); + + // 调用批量更新API + const response = await batchUpdateVideoSegments({ + project_id: projectId, + segments: updateData + }); + + if (!response.successful) { + throw new Error(response.message || '批量更新视频片段失败'); + } + + console.log(`成功更新 ${response.data.success_count} 个视频片段,失败 ${response.data.failed_count} 个`); + + return response.data.failed_count === 0; + } catch (error) { + console.error('批量更新视频片段失败:', error); + throw error; + } finally { + this.loading = false; + } + } + /** * @description 获取加载状态 * @returns boolean 是否正在加载