video-flow-b/app/service/usecase/ShotEditUsecase.ts

276 lines
7.8 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/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,
} 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时
*/
private matchVideoSegmentsWithIds(
segments: VideoSegmentEntity[],
detail: VideoFlowProjectResponse
): VideoSegmentEntity[] {
const projectData = detail.data as any;
const videoData = projectData?.data?.video?.data;
if (!videoData || !Array.isArray(videoData)) {
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);
});
}
});
// 为每个视频片段匹配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);
} else {
// 如果没有匹配到,保持原样
updatedSegments.push(segment);
}
} else {
updatedSegments.push(segment);
}
});
// 检查是否所有视频片段都匹配上了id
const unmatchedSegments = updatedSegments.filter(segment =>
segment.id.startsWith('video_mock_') || !segment.id
);
if (unmatchedSegments.length > 0) {
console.warn('以下视频片段未匹配到video_id:', unmatchedSegments.map(s => ({ name: s.name, videoUrl: s.videoUrl })));
throw new Error(`${unmatchedSegments.length} 个视频片段未匹配到对应的video_id`);
}
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,
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<VideoSegmentEntity> 重新生成的视频片段
*/
async regenerateVideoSegment(
project_id: string,
shot_Lens: LensType[],
shot_id?: string,
// roleReplaceParams?: { oldId: string; newId: string }[],
// sceneReplaceParams?: { oldId: string; newId: string }[]
): Promise<VideoSegmentEntity> {
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<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 获取加载状态
* @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
): Promise<any> {
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;
}
}
}