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

477 lines
14 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,
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,
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 批量更新除当前选中片段外的所有视频片段
* @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
): 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;
}
}
}
/**
* 边界框坐标信息
* @description 描述检测到的人物在图片中的位置和尺寸
*/
export interface BoundingBox {
/** X坐标像素 */
x: number;
/** Y坐标像素 */
y: number;
/** 宽度(像素) */
width: number;
/** 高度(像素) */
height: number;
}
/**
* 匹配的人物信息
* @description 从图片中识别出的与已知角色匹配的人物信息
*/
export interface MatchedPerson {
/** 人物ID */
person_id: string;
/** 边界框信息 */
bbox: BoundingBox;
/** 匹配置信度0-1之间 */
confidence: 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[];
}
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"
}
]
}