import { VideoFlowProjectResponse } from "@/api/allMovieType"; import { task_item, VideoSegmentEntityAdapter } from "../adapter/oldErrAdapter"; import { VideoSegmentEntity } from "../domain/Entities"; import { LensType } from "../domain/valueObject"; import { getShotList, regenerateShot, optimizeShotContent, updateShotPrompt, detailScriptEpisodeNew, faceRecognition, batchUpdateVideoSegments, } from "@/api/video_flow"; /** * 视频片段编辑用例 * 负责视频片段内容的初始化、修改和优化 */ export class VideoSegmentEditUseCase { private loading: boolean = false; /** * @description 获取视频片段列表 * @param projectId 项目ID * @returns Promise 视频片段列表 */ async getVideoSegmentList(projectId: string): Promise { try { this.loading = true; const response = await getShotList({ project_id: projectId }); if (!response.successful) { throw new Error(response.message || "获取视频片段列表失败"); } const Segments = VideoSegmentEntityAdapter.toVideoSegmentEntity(response.data) || []; const detail = await detailScriptEpisodeNew({ project_id: projectId }); if (!detail.successful || !detail.data) { throw new Error(detail.message || "获取视频片段列表失败"); } // 匹配视频片段ID return this.matchVideoSegmentsWithIds(Segments, detail.data); } catch (error) { console.error("获取视频片段列表失败:", error); throw error; } finally { this.loading = false; } } /** * @description 为视频片段匹配对应的video_id * @param segments 视频片段列表 * @param detail 项目详情数据 * @returns VideoSegmentEntity[] 匹配后的视频片段列表 * @throws Error 当有视频片段未匹配到video_id时 */ /** * @description 为视频片段匹配对应的video_id,并输出调试信息 * @param segments 视频片段列表 * @param detail 项目详情数据 * @returns VideoSegmentEntity[] 匹配后的视频片段列表 * @throws Error 当有视频片段未匹配到video_id时 */ private matchVideoSegmentsWithIds( segments: VideoSegmentEntity[], detail: VideoFlowProjectResponse ): VideoSegmentEntity[] { console.log('[matchVideoSegmentsWithIds] 输入segments:', segments); console.log('[matchVideoSegmentsWithIds] 输入detail:', detail); const videoData = detail?.data?.video?.data; if (!videoData || !Array.isArray(videoData)) { console.log('[matchVideoSegmentsWithIds] videoData无效,直接返回原segments'); return segments; } // 建立URL到VideoItem的映射表,提高查找效率 const urlToVideoMap = new Map(); videoData.forEach(videoItem => { if (videoItem.urls && Array.isArray(videoItem.urls)) { videoItem.urls.forEach((url: string) => { urlToVideoMap.set(url, videoItem); }); } }); console.log('[matchVideoSegmentsWithIds] urlToVideoMap keys:', Array.from(urlToVideoMap.keys())); // 为每个视频片段匹配video_id并重新创建实体 const updatedSegments: VideoSegmentEntity[] = []; segments.forEach(segment => { if (segment.videoUrl && Array.isArray(segment.videoUrl)) { // 查找匹配的视频项 const matchedVideo = segment.videoUrl.find(url => urlToVideoMap.has(url)); const videoItem = matchedVideo ? urlToVideoMap.get(matchedVideo) : null; if (videoItem) { // 创建新的实体实例,设置正确的id const updatedSegment: VideoSegmentEntity = { ...segment, id: videoItem.video_id }; updatedSegments.push(updatedSegment); console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" 匹配到 video_id:`, videoItem.video_id); } else { // 如果没有匹配到,保持原样 updatedSegments.push(segment); console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" 未匹配到video_id, 保持原样`); } } else { updatedSegments.push(segment); console.log(`[matchVideoSegmentsWithIds] segment "${segment.name}" videoUrl无效, 保持原样`); } }); // 检查是否所有视频片段都匹配上了id const unmatchedSegments = updatedSegments.filter(segment => segment.id.startsWith('video_mock_') || !segment.id ); if (unmatchedSegments.length > 0) { console.warn('[matchVideoSegmentsWithIds] 以下视频片段未匹配到video_id:', unmatchedSegments.map(s => ({ name: s.name, videoUrl: s.videoUrl }))); // throw new Error(`有 ${unmatchedSegments.length} 个视频片段未匹配到对应的video_id`); } console.log('[matchVideoSegmentsWithIds] 匹配后segments:', updatedSegments); return updatedSegments; } /** * @description 保存分镜提示词数据 * @param project_id 项目ID * @param shot_id 分镜ID * @param shot_descriptions 镜头描述数据 * @returns Promise 保存结果 */ async saveShotPrompt( project_id: string, shot_id: string, shot_descriptions: task_item ): Promise { try { this.loading = true; const response = await updateShotPrompt({ project_id, shot_id, shot_descriptions, }); if (!response.successful) { throw new Error(response.message || "保存分镜提示词数据失败"); } return response.data; } catch (error) { console.error("保存分镜提示词数据失败:", error); throw error; } finally { this.loading = false; } } /** * @description 通过视频镜头描述数据重新生成视频 * @param project_id 项目ID * @param shot_Lens 镜头描述数据 * @param shot_id 视频片段ID(可选,如果重新生成现有片段) * @returns Promise 重新生成的视频片段 */ async regenerateVideoSegment( project_id: string, shot_Lens: LensType[], shot_id?: string, // roleReplaceParams?: { oldId: string; newId: string }[], // sceneReplaceParams?: { oldId: string; newId: string }[] ): Promise { try { this.loading = true; const shot_descriptions = VideoSegmentEntityAdapter.lensTypeToTaskItem(shot_Lens); // 如果有shot_id,先保存分镜数据 if (shot_id) { await this.saveShotPrompt(project_id, shot_id, shot_descriptions); } const response = await regenerateShot({ project_id, shot_id, shot_descriptions, // roleReplaceParams, // sceneReplaceParams, }); if (!response.successful) { throw new Error(response.message || "重新生成视频片段失败"); } return response.data; } catch (error) { console.error("重新生成视频片段失败:", error); throw error; } finally { this.loading = false; } } /** * @description 通过AI优化镜头数据(包含对话内容) * @param shotId 视频片段ID * @param userRequirement 用户优化需求 * @param lensData 镜头数据数组 * @returns Promise 优化后的镜头数据 */ async optimizeVideoContent( shotId: string, userRequirement: string, lensData: LensType[] ): Promise { try { this.loading = true; // 调用AI优化接口 const response = await optimizeShotContent({ shotId, userRequirement, lensData, }); if (!response.successful) { throw new Error(response.message || "AI优化视频内容失败"); } return response.data; } catch (error) { console.error("AI优化视频内容失败:", error); throw error; } finally { this.loading = false; } } /** * @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 是否正在加载 */ isLoading(): boolean { return this.loading; } /** * @description 识别视频帧中的角色 * @param projectId 项目ID * @param videoId 视频ID * @param targetImageUrl 目标图片URL * @returns Promise 识别结果 */ async recognizeRoleFromImage( projectId: string, videoId: string, targetImageUrl: string ) { try { this.loading = true; const response = await faceRecognition({ project_id: projectId, video_id: videoId, target_image_url: targetImageUrl, }); if (!response.successful) { throw new Error(response.message || "人脸识别失败"); } // 打印返回结果 console.log('人脸识别接口返回结果:', response); return response.data; } catch (error) { console.error("人脸识别失败:", error); throw error; } finally { this.loading = false; } } } /** * 边界框坐标信息 * @description 描述检测到的人物在图片中的位置和尺寸 */ export interface BoundingBox { /** X坐标(像素) */ x: number; /** Y坐标(像素) */ y: number; /** 宽度(像素) */ width: number; /** 高度(像素) */ height: number; } /** * 匹配的人物信息 * @description 从图片中识别出的与已知角色匹配的人物信息 */ export interface MatchedPerson { /** 人物名 */ person_id: string; /**x坐标 小数百分比形式 */ x: number; /**y坐标 小数百分比形式 */ y: number; /**宽度 小数百分比形式 */ width: number; /**高度 小数百分比形式 */ height: number; } /** * 角色识别结果数据 * @description AI识别图片中人物后的详细结果 */ export interface RecognitionResultData { /** 目标图片URL */ target_image_url: string; /** 检测到的总人数 */ total_persons_detected: number; /** 匹配的人物列表 */ matched_persons: MatchedPerson[]; } /** * 角色识别响应结果 * @description API返回的角色识别完整响应 */ export interface RecognitionResult { /** 响应状态码 */ code: number; /** 响应消息 */ message: string; /** 识别结果数据 */ data: RecognitionResultData; } /** * 使用的角色信息 * @description 在识别过程中使用的已知角色信息 */ export interface CharacterUsed { /** 角色名称 */ character_name: string; /** 角色C-ID */ c_id: string; /** 角色图片路径 */ image_path: string; /** 角色头像 */ avatar: string; } /** * 角色识别API响应类型 * @description 完整的角色识别API响应结构 */ export interface RoleRecognitionResponse { /** 项目ID */ project_id: string; /** 视频ID */ video_id: string; /** 目标图片URL */ target_image_url: string; /** 已知人物数量 */ known_persons_count: number; /** 识别结果 */ recognition_result: RecognitionResult; /** 使用的角色列表 */ characters_used: CharacterUsed[]; } // export const roleRecognitionResponse:RoleRecognitionResponse = { // "project_id": "d0df7120-e27b-4f84-875c-e532f1bd318c", // "video_id": "984f3347-c81c-4af8-9145-49ead82becde", // "target_image_url": "https://cdn.qikongjian.com/videos/1754970412744_kqxplx.png", // "known_persons_count": 1, // "recognition_result": { // "code": 200, // "message": "识别完成", // "data": // { // "target_image_url": "https://cdn.qikongjian.com/videos/1754970412744_kqxplx.png", // "total_persons_detected": 1, // "matched_persons": [ // { // "person_id": "CH-01", // "bbox": { // "x": 269, // "y": 23, // "width": 585, // "height": 685 // }, // "confidence": 0.36905956268310547 // } // ] // } // }, // "characters_used": [ // { // "character_name": "CH-01", // "c_id": "无C-ID", // "image_path": "无image_path", // "avatar": "https://cdn.huiying.video/template/Whisk_9afb196368.jpg" // } // ] // }