forked from 77media/video-flow
387 lines
14 KiB
TypeScript
387 lines
14 KiB
TypeScript
import { useState, useCallback, useMemo } from "react";
|
||
import {
|
||
VideoSegmentEntity,
|
||
RoleEntity,
|
||
SceneEntity,
|
||
} from "../domain/Entities";
|
||
import { ContentItem, ScriptSlice, ScriptValueObject, LensType } from "../domain/valueObject";
|
||
import { VideoSegmentItem } from "../domain/Item";
|
||
import { VideoSegmentEditUseCase } from "../usecase/ShotEditUsecase";
|
||
import {
|
||
getShotList,
|
||
// getShotDetail,
|
||
updateShotContent,
|
||
getUserRoleLibrary,
|
||
replaceShotRole,
|
||
getShotVideoScript,
|
||
} from "@/api/video_flow";
|
||
|
||
/**
|
||
* 视频片段服务Hook接口
|
||
* 定义视频片段服务Hook的所有状态和操作方法
|
||
*/
|
||
export interface UseVideoSegmentService {
|
||
// 响应式状态
|
||
/** 视频片段列表 */
|
||
videoSegmentList: VideoSegmentEntity[];
|
||
/** 当前选中的视频片段 */
|
||
selectedVideoSegment: VideoSegmentItem | null;
|
||
/** 当前视频片段的草图数据URL */
|
||
videoSegmentSketchData: string | null;
|
||
/** 当前视频片段的视频数据URL */
|
||
videoSegmentVideoData: string[] | null;
|
||
|
||
/** 用户角色库 */
|
||
userRoleLibrary: RoleEntity[];
|
||
/** 当前视频片段的视频剧本片段 */
|
||
videoSegmentVideoScript: ScriptSlice[];
|
||
/** 加载状态 */
|
||
loading: boolean;
|
||
/** 错误信息 */
|
||
error: string | null;
|
||
|
||
// 操作方法
|
||
/** 获取视频片段列表 */
|
||
fetchVideoSegmentList: (projectId: string) => Promise<void>;
|
||
/** 选择视频片段并获取详情 */
|
||
selectVideoSegment: (videoSegmentId: string) => Promise<void>;
|
||
/** 修改视频片段对话内容 */
|
||
updateVideoSegmentContent: (
|
||
newContent: Array<{ roleId: string; content: string }>
|
||
) => Promise<void>;
|
||
/** 获取用户角色库 */
|
||
fetchUserRoleLibrary: () => Promise<void>;
|
||
/** 替换视频片段角色 */
|
||
replaceVideoSegmentRole: (oldRoleId: string, newRoleId: string) => Promise<void>;
|
||
/** 获取视频片段视频剧本内容 */
|
||
fetchVideoSegmentVideoScript: () => Promise<void>;
|
||
/** 获取视频片段关联的角色信息 */
|
||
getVideoSegmentRoles: () => Promise<RoleEntity[]>;
|
||
/** 获取视频片段关联的场景信息 */
|
||
getVideoSegmentScenes: () => Promise<SceneEntity[]>;
|
||
|
||
/** 重新生成视频片段 */
|
||
regenerateVideoSegment: (
|
||
shotPrompt: LensType[],
|
||
dialogueContent: ContentItem[],
|
||
roleReplaceParams: { oldId: string; newId: string }[],
|
||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||
) => Promise<void>;
|
||
}
|
||
|
||
/**
|
||
* 视频片段服务Hook
|
||
* 提供视频片段相关的所有状态管理和操作方法
|
||
* 包括视频片段列表管理、视频片段选择、数据获取、内容修改等功能
|
||
*/
|
||
export const useVideoSegmentService = (): UseVideoSegmentService => {
|
||
// 响应式状态
|
||
const [videoSegmentList, setVideoSegmentList] = useState<VideoSegmentEntity[]>([]);
|
||
const [selectedVideoSegment, setSelectedVideoSegment] = useState<VideoSegmentItem | null>(null);
|
||
const [videoSegmentSketchData, setVideoSegmentSketchData] = useState<string | null>(null);
|
||
const [videoSegmentVideoData, setVideoSegmentVideoData] = useState<string[] | null>(null);
|
||
const [userRoleLibrary, setUserRoleLibrary] = useState<RoleEntity[]>([]);
|
||
const [videoSegmentVideoScript, setVideoSegmentVideoScript] = useState<ScriptSlice[]>([]);
|
||
const [loading, setLoading] = useState<boolean>(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [projectId, setProjectId] = useState<string>("");
|
||
// UseCase实例
|
||
const [videoSegmentEditUseCase, setVideoSegmentEditUseCase] =
|
||
useState<VideoSegmentEditUseCase | null>(null);
|
||
|
||
/**
|
||
* 获取视频片段列表
|
||
* @description 根据项目ID获取所有视频片段列表
|
||
* @param projectId 项目ID
|
||
*/
|
||
const fetchVideoSegmentList = useCallback(async (projectId: string) => {
|
||
setProjectId(projectId);
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getShotList({ projectId });
|
||
if (response.successful) {
|
||
setVideoSegmentList(response.data);
|
||
} else {
|
||
setError(`获取视频片段列表失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取视频片段列表失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 选择视频片段并获取详情
|
||
* @description 根据视频片段ID获取视频片段详情,并初始化相关的UseCase和数据
|
||
* @param videoSegmentId 视频片段ID
|
||
*/
|
||
const selectVideoSegment = useCallback(async (videoSegmentId: string) => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
// 获取视频片段详情
|
||
await fetchVideoSegmentList(projectId);
|
||
const videoSegmentEntity = videoSegmentList.find(
|
||
(videoSegment: VideoSegmentEntity) => videoSegment.id === videoSegmentId
|
||
);
|
||
if (!videoSegmentEntity) {
|
||
setError(`视频片段不存在: ${videoSegmentId}`);
|
||
return;
|
||
}
|
||
const videoSegmentItem = new VideoSegmentItem(videoSegmentEntity);
|
||
setSelectedVideoSegment(videoSegmentItem);
|
||
|
||
// 初始化UseCase
|
||
const newVideoSegmentEditUseCase = new VideoSegmentEditUseCase(videoSegmentItem);
|
||
setVideoSegmentEditUseCase(newVideoSegmentEditUseCase);
|
||
|
||
// 从视频片段实体中获取草图数据和视频数据
|
||
setVideoSegmentSketchData(videoSegmentEntity.sketchUrl || null);
|
||
setVideoSegmentVideoData(videoSegmentEntity.videoUrl || null);
|
||
} catch (err) {
|
||
setError(
|
||
`选择视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 修改视频片段对话内容
|
||
* @description 更新视频片段的对话内容,ContentItem数量和ID顺序不能变,只能修改content字段
|
||
* @param newContent 新的对话内容数组
|
||
*/
|
||
const updateVideoSegmentContentHandler = useCallback(
|
||
async (newContent: Array<{ roleId: string; content: string }>) => {
|
||
if (!videoSegmentEditUseCase) {
|
||
setError("视频片段编辑用例未初始化");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const updatedVideoSegment = await videoSegmentEditUseCase.updateVideoSegmentContent(newContent);
|
||
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||
} catch (err) {
|
||
setError(
|
||
`修改视频片段对话内容失败: ${
|
||
err instanceof Error ? err.message : "未知错误"
|
||
}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[videoSegmentEditUseCase]
|
||
);
|
||
|
||
|
||
/**
|
||
* 获取用户角色库
|
||
* @description 获取当前用户的所有角色库数据
|
||
*/
|
||
const fetchUserRoleLibrary = useCallback(async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getUserRoleLibrary();
|
||
if (response.successful) {
|
||
setUserRoleLibrary(response.data);
|
||
} else {
|
||
setError(`获取用户角色库失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取用户角色库失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
/**
|
||
* 替换视频片段角色
|
||
* @description 将视频片段中的某个角色替换为角色库中的另一个角色
|
||
* @param oldRoleId 旧角色ID
|
||
* @param newRoleId 新角色ID
|
||
*/
|
||
const replaceVideoSegmentRoleHandler = useCallback(
|
||
async (oldRoleId: string, newRoleId: string) => {
|
||
if (!selectedVideoSegment) {
|
||
setError("未选择视频片段");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await replaceShotRole({
|
||
shotId: selectedVideoSegment.entity.id,
|
||
oldRoleId,
|
||
newRoleId,
|
||
});
|
||
if (response.successful) {
|
||
// 重新获取视频片段详情
|
||
await selectVideoSegment(selectedVideoSegment.entity.id);
|
||
} else {
|
||
setError(`替换视频片段角色失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`替换视频片段角色失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[selectedVideoSegment, selectVideoSegment]
|
||
);
|
||
|
||
/**
|
||
* 获取视频片段视频剧本内容
|
||
* @description 获取视频片段视频的剧本内容,通过接口返回多个ScriptSliceEntity片段
|
||
*/
|
||
const fetchVideoSegmentVideoScript = useCallback(async () => {
|
||
if (!selectedVideoSegment) {
|
||
setError("未选择视频片段");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const response = await getShotVideoScript({
|
||
shotId: selectedVideoSegment.entity.id,
|
||
});
|
||
if (response.successful) {
|
||
const script = new ScriptValueObject(response.data);
|
||
setVideoSegmentVideoScript([...script.scriptSlices]);
|
||
} else {
|
||
setError(`获取视频片段视频剧本失败: ${response.message}`);
|
||
}
|
||
} catch (err) {
|
||
setError(
|
||
`获取视频片段视频剧本失败: ${
|
||
err instanceof Error ? err.message : "未知错误"
|
||
}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [selectedVideoSegment]);
|
||
|
||
/**
|
||
* 获取视频片段关联的角色信息
|
||
* @description 获取当前视频片段可以使用的角色列表
|
||
* @returns Promise<RoleEntity[]> 角色信息列表
|
||
*/
|
||
const getVideoSegmentRoles = useCallback(async (): Promise<RoleEntity[]> => {
|
||
if (!videoSegmentEditUseCase) {
|
||
throw new Error("视频片段编辑用例未初始化");
|
||
}
|
||
return await videoSegmentEditUseCase.getVideoSegmentRoles();
|
||
}, [videoSegmentEditUseCase]);
|
||
|
||
/**
|
||
* 获取视频片段关联的场景信息
|
||
* @description 获取当前视频片段可以使用的场景列表
|
||
* @returns Promise<SceneEntity[]> 场景信息列表
|
||
*/
|
||
const getVideoSegmentScenes = useCallback(async (): Promise<SceneEntity[]> => {
|
||
if (!videoSegmentEditUseCase) {
|
||
throw new Error("视频片段编辑用例未初始化");
|
||
}
|
||
return await videoSegmentEditUseCase.getVideoSegmentScenes();
|
||
}, [videoSegmentEditUseCase]);
|
||
|
||
/**
|
||
* 重新生成视频片段
|
||
* @description 使用镜头、对话内容、角色ID替换参数、场景ID替换参数重新生成视频片段
|
||
* @param shotPrompt 镜头描述
|
||
* @param dialogueContent 对话内容
|
||
* @param roleReplaceParams 角色ID替换参数,格式为{oldId:string,newId:string}[]
|
||
* @param sceneReplaceParams 场景ID替换参数,格式为{oldId:string,newId:string}[]
|
||
*/
|
||
const regenerateVideoSegment = useCallback(
|
||
async (
|
||
shotPrompt: LensType[],
|
||
dialogueContent: ContentItem[],
|
||
roleReplaceParams: { oldId: string; newId: string }[],
|
||
sceneReplaceParams: { oldId: string; newId: string }[]
|
||
) => {
|
||
if (!videoSegmentEditUseCase) {
|
||
setError("视频片段编辑用例未初始化");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const updatedVideoSegment = await videoSegmentEditUseCase.regenerateVideoSegment(
|
||
shotPrompt,
|
||
dialogueContent,
|
||
roleReplaceParams,
|
||
sceneReplaceParams
|
||
);
|
||
setSelectedVideoSegment(new VideoSegmentItem(updatedVideoSegment));
|
||
} catch (err) {
|
||
setError(
|
||
`重新生成视频片段失败: ${err instanceof Error ? err.message : "未知错误"}`
|
||
);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
},
|
||
[videoSegmentEditUseCase]
|
||
);
|
||
|
||
return {
|
||
// 响应式状态 - 用于UI组件订阅和渲染
|
||
/** 视频片段列表 - 当前项目的所有视频片段数据 */
|
||
videoSegmentList,
|
||
/** 当前选中的视频片段 - 包含视频片段实体和编辑状态 */
|
||
selectedVideoSegment,
|
||
/** 当前视频片段的草图数据URL - 从视频片段实体中获取的草图图片链接 */
|
||
videoSegmentSketchData,
|
||
/** 当前视频片段的视频数据URL - 从视频片段实体中获取的视频链接 */
|
||
videoSegmentVideoData,
|
||
|
||
/** 用户角色库 - 当前用户可用的所有角色数据 */
|
||
userRoleLibrary,
|
||
/** 当前视频片段的视频剧本片段 - 通过接口获取的剧本内容 */
|
||
videoSegmentVideoScript,
|
||
/** 加载状态 - 标识当前是否有异步操作正在进行 */
|
||
loading,
|
||
/** 错误信息 - 记录最近一次操作的错误信息 */
|
||
error,
|
||
|
||
// 操作方法 - 提供给UI组件调用的业务逻辑方法
|
||
/** 获取视频片段列表 - 根据项目ID获取所有视频片段数据 */
|
||
fetchVideoSegmentList,
|
||
/** 选择视频片段并获取详情 - 选择指定视频片段并初始化相关数据 */
|
||
selectVideoSegment,
|
||
/** 修改视频片段对话内容 - 更新视频片段的对话内容,保持ContentItem结构不变 */
|
||
updateVideoSegmentContent: updateVideoSegmentContentHandler,
|
||
|
||
/** 获取用户角色库 - 获取当前用户的所有角色数据 */
|
||
fetchUserRoleLibrary,
|
||
/** 替换视频片段角色 - 将视频片段中的角色替换为角色库中的另一个角色 */
|
||
replaceVideoSegmentRole: replaceVideoSegmentRoleHandler,
|
||
/** 获取视频片段视频剧本内容 - 通过接口获取视频剧本片段 */
|
||
fetchVideoSegmentVideoScript,
|
||
/** 获取视频片段关联的角色信息 - 获取当前视频片段可用的角色列表 */
|
||
getVideoSegmentRoles,
|
||
/** 获取视频片段关联的场景信息 - 获取当前视频片段可用的场景列表 */
|
||
getVideoSegmentScenes,
|
||
/** 重新生成视频片段 - 使用新参数重新生成视频片段内容 */
|
||
regenerateVideoSegment,
|
||
};
|
||
};
|