diff --git a/api/allMovieType.ts b/api/allMovieType.ts new file mode 100644 index 0000000..ab50584 --- /dev/null +++ b/api/allMovieType.ts @@ -0,0 +1,503 @@ +/** + * 视频流程项目相关的TypeScript类型定义 + * 包含角色、场景、服装、音乐等完整的数据结构 + */ + +/** + * 角色性别枚举 + */ +export enum Gender { + MALE = 'male', + FEMALE = 'female' + } + + /** + * 角色种族枚举 + */ + export enum Race { + WESTERN = 'Western', + ASIAN = 'Asian', + AFRICAN = 'African', + LATINO = 'Latino', + MIDDLE_EASTERN = 'Middle Eastern', + MIXED = 'Mixed' + } + + /** + * 角色类型枚举 + */ + export enum RoleType { + PROTAGONIST = 'Protagonist', + ANTAGONIST = 'Antagonist', + SUPPORTING = 'Supporting', + BACKGROUND = 'Background' + } + + /** + * 项目状态枚举 + */ + export enum ProjectStatus { + COMPLETED = 'COMPLETED', + IN_PROGRESS = 'IN_PROGRESS', + PENDING = 'PENDING', + FAILED = 'FAILED' + } + + /** + * 项目步骤枚举 + */ + export enum ProjectStep { + INIT = 'INIT', + SKETCH = 'SKETCH', + CHARACTER = 'CHARACTER', + SHOT_SKETCH = 'SHOT_SKETCH', + VIDEO = 'VIDEO', + FINAL = 'FINAL' + } + + /** + * 项目模式枚举 + */ + export enum ProjectMode { + AUTOMATIC = 'automatic', + MANUAL = 'manual', + HYBRID = 'hybrid' + } + + /** + * 分辨率枚举 + */ + export enum Resolution { + P720 = '720p', + P1080 = '1080p', + P4K = '4K' + } + + /** + * 语言枚举 + */ + export enum Language { + EN = 'en', + ZH = 'zh', + JA = 'ja', + KO = 'ko' + } + + /** + * 角色信息接口 + */ + export interface Character { + /** 角色唯一标识符 */ + id: string; + /** 角色名称 */ + name: string; + /** 角色描述,包含外貌、性格等详细信息 */ + description: string; + /** 角色类型 */ + role: RoleType; + /** 角色年龄 */ + age: number; + /** 角色性别 */ + gender: Gender; + /** 角色种族 */ + race: Race; + /** 角色简介 */ + brief?: string; + /** 角色体型描述 */ + physique?: string; + /** 角色发型描述 */ + hairstyle?: string; + /** 角色默认举止描述 */ + default_demeanor?: string; + } + + /** + * 服装信息接口 + */ + export interface Wardrobe { + /** 服装所属角色ID */ + belongs_to: string; + /** 服装详细描述 */ + description: string; + } + + /** + * 场景信息接口 + */ + export interface Scene { + /** 场景名称 */ + name: string; + /** 场景详细描述 */ + description: string; + } + + /** + * 项目风格信息接口 + */ + export interface ProjectStyle { + /** 地理设置和种族信息 */ + geographic_setting_ethnicity: string; + /** 格式要求 */ + format: string; + /** 美学风格描述 */ + aesthetics: string; + /** 技术蓝图 */ + technical_blueprint: string; + } + + /** + * 提示JSON结构接口 + */ + export interface PromptJson { + /** 导演指令 */ + directors_directive: string; + /** 叙事提示 */ + narrative_prompt: string; + /** 对话语言 */ + dialogue_language: string; + /** 整体项目风格 */ + overall_project_style: string; + /** 主要角色列表 */ + master_character_list: MasterCharacter[]; + /** 主要服装列表 */ + master_wardrobe_list: MasterWardrobe[]; + /** 主要场景列表 */ + master_scene_list: MasterScene[]; + /** 核心氛围 */ + core_atmosphere: string; + } + + /** + * 场景草图项 + */ + export interface SketchItem { + /** 草图名称 */ + sketch_name: string; + /** 图片ID */ + image_id: string; + /** 提示词 */ + prompt: string; + /** 提示词JSON */ + prompt_json: PromptJson; + /** 草图名称前缀 */ + sketch_name_prefix: string; + /** 图片URL列表 */ + urls: string[]; + } + + /** + * 镜头草图项 + */ + export interface ShotSketchItem { + /** 镜头草图名称 */ + shot_sketch_name: string; + /** 图片ID */ + image_id: string; + /** 提示词 */ + prompt: string; + /** 提示词JSON */ + prompt_json: PromptJson; + /** 镜头草图名称前缀 */ + shot_sketch_name_prefix: string; + /** 图片URL列表 */ + urls: string[]; + } + + /** + * 视频项 + */ + export interface VideoItem { + /** 视频ID */ + video_id: string; + /** 描述 */ + description: string; + /** 提示词JSON */ + prompt_json: PromptJson; + /** 视频名称前缀 */ + video_name_prefix: string; + /** 视频URL列表 */ + urls: string[]; + } + + /** + * 主要角色 + */ + export interface MasterCharacter { + /** 角色ID */ + id: string; + /** 角色名称 */ + name: string; + /** 角色描述 */ + description: string; + } + + /** + * 主要服装 + */ + export interface MasterWardrobe { + /** 所属角色 */ + belongs_to: string; + /** 服装描述 */ + description: string; + } + + /** + * 主要场景 + */ + export interface MasterScene { + /** 场景名称 */ + name: string; + /** 场景描述 */ + description: string; + } + + /** + * 视频数据 + */ + export interface VideoData { + /** 视频列表 */ + data: VideoItem[]; + /** 总数 */ + total_count: number; + /** 全局ID */ + guid: string; + /** 项目ID */ + project_id: string; + } + + /** + * 音乐数据 + */ + export interface MusicData { + [key: string]: any; + } + /** + * 视频信息接口 + */ + export interface Video { + /** 视频唯一标识符 */ + video_id: string; + /** 视频描述 */ + description: string; + /** 提示JSON数据 */ + prompt_json: PromptJson; + /** 视频名称前缀 */ + video_name_prefix: string; + /** 视频URL列表 */ + urls: string[]; + } + + /** + * 音乐信息接口 + */ + export interface Music { + /** 音乐相关数据,当前为空对象 */ + [key: string]: any; + } + + /** + * 最终视频信息接口 + */ + export interface FinalVideo { + /** 最终视频URL */ + video: string; + } + + /** + * 多语言视频信息接口 + */ + export interface MultilingualVideo { + /** 多语言视频相关数据,当前为空对象 */ + [key: string]: any; + } + + /** + * 项目设置接口 + */ + export interface ProjectSettings { + /** 视频质量设置 */ + quality: string; + /** 帧率设置 */ + frame_rate: string; + /** 分辨率设置 */ + resolution: string; + /** 音频设置 */ + audio: { + /** 音频质量 */ + quality: string; + /** 音频格式 */ + format: string; + }; + } + + /** + * 项目统计信息接口 + */ + export interface ProjectStatistics { + /** 总视频数 */ + total_videos: number; + /** 总角色数 */ + total_characters: number; + /** 总场景数 */ + total_scenes: number; + /** 总服装数 */ + total_wardrobes: number; + /** 总音乐数 */ + total_music: number; + /** 总时长 */ + total_duration: number; + /** 总大小 */ + total_size: number; + } + + /** + * 项目元数据接口 + */ + export interface ProjectMetadata { + /** 项目版本 */ + version: string; + /** 项目语言 */ + language: string; + /** 项目分类 */ + category: string; + /** 项目难度 */ + difficulty: string; + /** 项目时长 */ + duration: string; + /** 项目预算 */ + budget: string; + /** 项目团队 */ + team: string[]; + /** 项目设备 */ + equipment: string[]; + /** 项目软件 */ + software: string[]; + /** 项目许可证 */ + license: string; + /** 项目版权 */ + copyright: string; + /** 项目评分 */ + rating: number; + /** 项目评论数 */ + review_count: number; + /** 项目下载数 */ + download_count: number; + /** 项目分享数 */ + share_count: number; + /** 项目收藏数 */ + favorite_count: number; + /** 项目观看数 */ + view_count: number; + /** 项目完成度 */ + completion_rate: number; + /** 项目进度 */ + progress: number; + /** 项目阶段 */ + stage: string; + /** 项目里程碑 */ + milestones: string[]; + /** 项目任务 */ + tasks: string[]; + /** 项目文件 */ + files: string[]; + /** 项目链接 */ + links: string[]; + /** 项目备注 */ + notes: string; + /** 项目历史 */ + history: string[]; + /** 项目日志 */ + logs: string[]; + /** 项目统计 */ + statistics: ProjectStatistics; + } + + /** + * 项目内容接口 + */ + export interface ProjectContent { + /** 草图数据 */ + sketch: SketchItem[]; + /** 角色数据 */ + character: Character[]; + /** 镜头草图数据 */ + shot_sketch: ShotSketchItem[]; + /** 视频数据 */ + video: VideoData; + /** 音乐数据 */ + music: MusicData; + /** 最终视频 */ + final_video: FinalVideo; + /** 多语言视频 */ + multilingual_video: MultilingualVideo; + } + + /** + * 视频流程项目接口 + */ + export interface VideoFlowProject { + /** 项目名称 */ + name: string; + /** 项目描述 */ + description: string; + /** 项目类型 */ + type: string; + /** 项目状态 */ + status: string; + /** 项目创建时间 */ + created_at: string; + /** 项目更新时间 */ + updated_at: string; + /** 项目所有者ID */ + owner_id: string; + /** 项目标签列表 */ + tags: string[]; + /** 项目设置 */ + settings: ProjectSettings; + /** 项目元数据 */ + metadata: ProjectMetadata; + /** 项目内容 */ + content: ProjectContent; + /** 音乐信息 */ + music: Music; + /** 最终视频 */ + final_video: FinalVideo; + /** 多语言视频 */ + multilingual_video: MultilingualVideo; + } + + /** + * 视频流程项目 - 完整的接口返回值类型 + */ + export interface VideoFlowProjectResponse { + /** 项目名称 */ + name: string; + /** 项目ID */ + project_id: string; + /** 项目模式 */ + mode: ProjectMode; + /** 分辨率 */ + resolution: Resolution; + /** 语言 */ + language: Language; + /** 原始文本 */ + original_text: string; + /** 生成的脚本 */ + generated_script: string; + /** 项目状态 */ + status: ProjectStatus; + /** 项目标题 */ + title: string; + /** 项目类型 */ + genre: string; + /** 项目标签 */ + tags: string[]; + /** 项目步骤 */ + step: ProjectStep; + /** 最后消息 */ + last_message: string; + /** 项目内容 */ + data: ProjectContent; + } + + diff --git a/api/video_flow.ts b/api/video_flow.ts index f14f296..9b800db 100644 --- a/api/video_flow.ts +++ b/api/video_flow.ts @@ -14,7 +14,8 @@ import { LensType, ScriptSlice, } from "@/app/service/domain/valueObject"; -import { VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; +import { task_item, VideoSegmentEntityAdapter } from "@/app/service/adapter/oldErrAdapter"; +import { VideoFlowProjectResponse } from "./allMovieType"; // API 响应类型 interface BaseApiResponse { @@ -199,8 +200,8 @@ export const convertVideoToScene = async ( // 新-获取剧集详情 export const detailScriptEpisodeNew = async (data: { project_id: string; -}): Promise> => { - return post>("/movie/get_movie_project_detail", data); +}): Promise> => { + return post("/movie/get_movie_project_detail", data); }; // 获取 title 接口 @@ -511,11 +512,11 @@ export const regenerateShot = async (request: { /** 分镜ID */ shot_id?: string; /** 镜头描述 */ - shot_descriptions?: LensType[]; - /** 角色ID替换参数,格式为{oldId:string,newId:string}[] */ - roleReplaceParams?: { oldId: string; newId: string }[]; - /** 场景ID替换参数,格式为{oldId:string,newId:string}[] */ - sceneReplaceParams?: { oldId: string; newId: string }[]; + shot_descriptions?: task_item; + // /** 角色ID替换参数,格式为{oldId:string,newId:string}[] */ + // roleReplaceParams?: { oldId: string; newId: string }[]; + // /** 场景ID替换参数,格式为{oldId:string,newId:string}[] */ + // sceneReplaceParams?: { oldId: string; newId: string }[]; }): Promise> => { return post>("/movie/regenerate_shot_video", request); }; @@ -810,7 +811,7 @@ export const updateShotPrompt = async (request: { /** 分镜ID */ shot_id: string; /** 镜头描述 */ - shot_descriptions: LensType[]; + shot_descriptions: task_item; }): Promise> => { return post("/movie/update_shot_prompt", request); }; diff --git a/app/service/Interaction/ShotService.ts b/app/service/Interaction/ShotService.ts index 83f8035..27f4cbe 100644 --- a/app/service/Interaction/ShotService.ts +++ b/app/service/Interaction/ShotService.ts @@ -22,8 +22,8 @@ export interface UseShotService { getVideoSegmentList: (projectId: string) => Promise; /** 重新生成视频片段 */ regenerateVideoSegment: ( - roleReplaceParams?: { oldId: string; newId: string }[], - sceneReplaceParams?: { oldId: string; newId: string }[] + // roleReplaceParams?: { oldId: string; newId: string }[], + // sceneReplaceParams?: { oldId: string; newId: string }[] ) => Promise; /** AI优化视频内容 */ optimizeVideoContent: ( @@ -95,8 +95,8 @@ export const useShotService = (): UseShotService => { */ const regenerateVideoSegment = useCallback( async ( - roleReplaceParams?: { oldId: string; newId: string }[], - sceneReplaceParams?: { oldId: string; newId: string }[] + // roleReplaceParams?: { oldId: string; newId: string }[], + // sceneReplaceParams?: { oldId: string; newId: string }[] ): Promise => { try { setLoading(true); @@ -105,8 +105,8 @@ export const useShotService = (): UseShotService => { projectId, selectedSegment!.lens, selectedSegment!.id, - roleReplaceParams, - sceneReplaceParams + // roleReplaceParams, + // sceneReplaceParams ); // 如果重新生成的是现有片段,更新列表中的对应项 diff --git a/app/service/adapter/oldErrAdapter.ts b/app/service/adapter/oldErrAdapter.ts index 3c624a5..7371132 100644 --- a/app/service/adapter/oldErrAdapter.ts +++ b/app/service/adapter/oldErrAdapter.ts @@ -4,6 +4,29 @@ import { VideoSegmentEntity } from "../domain/Entities"; import { LensType, ContentItem } from "../domain/valueObject"; + +export type task_item = { + /** 镜头1描述 */ + shot_1: string; + /** 镜头2描述 */ + shot_2: string; + /** 镜头3描述 */ + shot_3: string; + /** 镜头4描述 */ + shot_4: string; + /** 镜头5描述 */ + shot_5: string; + /** 镜头6描述 */ + shot_6: string; + /** 镜头7描述 */ + shot_7: string; + /** 镜头8描述 */ + shot_8: string; + /** 镜头9描述 */ + shot_9: string; + /** 镜头10描述 */ + shot_10: string; +} /** * @description 视频片段后端结构 */ @@ -241,6 +264,63 @@ export class VideoSegmentEntityAdapter { return adapter; } + + /** + * @description 将LensType数组转换为task_item类型 + * @param lensTypes 镜头类型数组 + * @returns task_item 任务项 + */ + static lensTypeToTaskItem(lensTypes: LensType[]): task_item { + // 初始化镜头对象,所有字段设为空字符串 + const shots: { [key: string]: string } = { + shot_1: "", + shot_2: "", + shot_3: "", + shot_4: "", + shot_5: "", + shot_6: "", + shot_7: "", + shot_8: "", + shot_9: "", + shot_10: "" + }; + + // 遍历所有LensType,处理镜头1到镜头10 + lensTypes.forEach(lensType => { + const shotMatch = lensType.name.match(/^shot_(\d+)$/); + if (shotMatch) { + const shotNumber = shotMatch[1]; + const shotKey = `shot_${shotNumber}` as keyof typeof shots; + + // 重新组合镜头描述和对话内容 + let fullContent = lensType.script; + + // 如果有对话内容,添加到镜头描述后面 + if (lensType.content && lensType.content.length > 0) { + const dialogueLines = lensType.content.map(dialogue => + `${dialogue.roleName} [CH-01]: ${dialogue.content}` + ); + fullContent += '\n' + dialogueLines.join('\n'); + } + + shots[shotKey] = fullContent; + } + }); + + // 返回完整的task_item对象 + return { + shot_1: shots.shot_1, + shot_2: shots.shot_2, + shot_3: shots.shot_3, + shot_4: shots.shot_4, + shot_5: shots.shot_5, + shot_6: shots.shot_6, + shot_7: shots.shot_7, + shot_8: shots.shot_8, + shot_9: shots.shot_9, + shot_10: shots.shot_10 + }; + } } /**视频片段基础数据 */ diff --git a/app/service/usecase/ShotEditUsecase.ts b/app/service/usecase/ShotEditUsecase.ts index bf6f12a..0e2d03f 100644 --- a/app/service/usecase/ShotEditUsecase.ts +++ b/app/service/usecase/ShotEditUsecase.ts @@ -1,4 +1,5 @@ -import { VideoSegmentEntityAdapter } from "../adapter/oldErrAdapter"; +import { VideoFlowProjectResponse } from "@/api/allMovieType"; +import { task_item, VideoSegmentEntityAdapter } from "../adapter/oldErrAdapter"; import { VideoSegmentEntity } from "../domain/Entities"; import { LensType } from "../domain/valueObject"; import { @@ -6,6 +7,7 @@ import { regenerateShot, optimizeShotContent, updateShotPrompt, + detailScriptEpisodeNew, } from "@/api/video_flow"; /** @@ -30,7 +32,13 @@ export class VideoSegmentEditUseCase { throw new Error(response.message || "获取视频片段列表失败"); } - return VideoSegmentEntityAdapter.toVideoSegmentEntity(response.data) || []; + 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; @@ -39,6 +47,74 @@ export class VideoSegmentEditUseCase { } } + /** + * @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(); + 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 @@ -49,7 +125,7 @@ export class VideoSegmentEditUseCase { async saveShotPrompt( project_id: string, shot_id: string, - shot_descriptions: LensType[] + shot_descriptions: task_item ): Promise { try { this.loading = true; @@ -76,22 +152,20 @@ export class VideoSegmentEditUseCase { /** * @description 通过视频镜头描述数据重新生成视频 * @param project_id 项目ID - * @param shot_descriptions 镜头描述数据 + * @param shot_Lens 镜头描述数据 * @param shot_id 视频片段ID(可选,如果重新生成现有片段) - * @param roleReplaceParams 角色替换参数(可选) - * @param sceneReplaceParams 场景替换参数(可选) * @returns Promise 重新生成的视频片段 */ async regenerateVideoSegment( project_id: string, - shot_descriptions: LensType[], + shot_Lens: LensType[], shot_id?: string, - roleReplaceParams?: { oldId: string; newId: string }[], - sceneReplaceParams?: { oldId: string; newId: 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); @@ -101,8 +175,8 @@ export class VideoSegmentEditUseCase { project_id, shot_id, shot_descriptions, - roleReplaceParams, - sceneReplaceParams, + // roleReplaceParams, + // sceneReplaceParams, }); if (!response.successful) {