video-flow-b/app/service/usecase/ShotEditUsecase.ts
2025-08-13 20:39:02 +08:00

356 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { VideoFlowProjectResponse } from "@/api/DTO/movieEdit";
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<VideoSegmentEntity[]> 视频片段列表
*/
async getVideoSegmentList(projectId: string): Promise<VideoSegmentEntity[]> {
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<string, any>();
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<any> 保存结果
*/
async saveShotPrompt(
project_id: string,
shot_id: string,
shot_descriptions: task_item
): Promise<any> {
try {
this.loading = true;
const response = await updateShotPrompt({
project_id,
video_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 }[]
){
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 || "重新生成视频片段失败");
}
// 现在返回的是任务状态信息包含task_id、status等
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<LensType[]> 优化后的镜头数据
*/
async optimizeVideoContent(
shotId: string,
userRequirement: string,
lensData: LensType[]
): Promise<LensType[]> {
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<boolean> 更新是否成功
*/
async batchUpdateOtherVideoSegments(
projectId: string,
currentSegmentId: string,
optimizedDescription: string,
keywords: string[]
): Promise<boolean> {
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<any> 识别结果
*/
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;
}
}
}