forked from 77media/video-flow
331 lines
12 KiB
TypeScript
331 lines
12 KiB
TypeScript
|
||
/**============因协同任务开发流程没有明确管理,导致的必要的适配=================**/
|
||
|
||
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 视频片段后端结构
|
||
*/
|
||
export class VideoSegmentEntityAdapter {
|
||
/** 原始文本 */
|
||
original_text: string = "";
|
||
/** 任务状态 */
|
||
task_status: string = "";
|
||
/** 任务结果 */
|
||
task_result: Array<{
|
||
/** 叙事目标 */
|
||
narrative_goal: string;
|
||
/** 镜头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;
|
||
/** 视频列表 */
|
||
videos: Array<{
|
||
/** 视频地址 */
|
||
video_url: string;
|
||
}>;
|
||
}> = [];
|
||
|
||
/**
|
||
* @description 解析shotContent,分离镜头描述和对话内容
|
||
* @param shotContent 原始shot内容
|
||
* @returns {description: string, dialogues: ContentItem[]} 解析后的描述和对话内容
|
||
*/
|
||
static parseShotContent(shotContent: string): { description: string; dialogues: ContentItem[] } {
|
||
const lines = shotContent.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
||
const dialogues: ContentItem[] = [];
|
||
let descriptionLines: string[] = [];
|
||
|
||
for (const line of lines) {
|
||
// 检查是否是对话行,格式:人物名称[CH-XX]: 对话内容
|
||
const dialogueMatch = line.match(/^(.+?)\s*\[CH-\d+\]:\s*(.+)$/);
|
||
if (dialogueMatch) {
|
||
const roleName = dialogueMatch[1].trim();
|
||
const content = dialogueMatch[2].trim();
|
||
dialogues.push({
|
||
roleName,
|
||
content
|
||
});
|
||
} else {
|
||
// 如果不是对话行,则认为是描述内容
|
||
descriptionLines.push(line);
|
||
}
|
||
}
|
||
|
||
const description = descriptionLines.join('\n');
|
||
return { description, dialogues };
|
||
}
|
||
|
||
/**
|
||
* @description 将后端数据结构转换为VideoSegmentEntity数组
|
||
* @param data 后端数据结构
|
||
* @returns VideoSegmentEntity[] 视频片段实体数组
|
||
*/
|
||
static toVideoSegmentEntity(data: VideoSegmentEntityAdapter): VideoSegmentEntity[] {
|
||
const entities: VideoSegmentEntity[] = [];
|
||
|
||
if (data.task_result && data.task_result.length > 0) {
|
||
// 遍历task_result中的每一项
|
||
data.task_result.forEach((result, index) => {
|
||
// 从task_result中提取镜头信息
|
||
const lens: LensType[] = [];
|
||
|
||
// 处理镜头1到镜头10
|
||
for (let i = 1; i <= 10; i++) {
|
||
const shotKey = `shot_${i}` as keyof typeof result;
|
||
const shotContent = (result as any)[shotKey] as string;
|
||
if (shotContent && shotContent.trim()) {
|
||
// 解析shotContent,分离镜头描述和对话内容
|
||
const { description, dialogues } = this.parseShotContent(shotContent);
|
||
|
||
// 创建镜头项,包含描述和对话内容
|
||
const lensItem = new LensType(`shot_${i}`, description, dialogues);
|
||
lens.push(lensItem);
|
||
}
|
||
}
|
||
|
||
// 如果没有任何镜头但有narrative_goal,将其作为镜头1
|
||
if (lens.length === 0 && result.narrative_goal) {
|
||
const narrativeLens = new LensType("镜头1", result.narrative_goal, []);
|
||
lens.push(narrativeLens);
|
||
}
|
||
|
||
// 提取视频URL列表
|
||
const videoUrls: string[] = [];
|
||
if (result.videos && result.videos.length > 0) {
|
||
videoUrls.push(...result.videos.map(video => video.video_url));
|
||
}
|
||
|
||
// 根据task_status确定状态
|
||
let status: 0 | 1 | 2 = 1; // 默认为已完成状态
|
||
if (data.task_status === "INIT" || data.task_status === "IN_PROGRESS") {
|
||
status = 0; // 视频加载中(INIT和IN_PROGRESS都当成进行中)
|
||
} else if (data.task_status === "COMPLETED") {
|
||
status = 1; // 任务已完成
|
||
} else if (data.task_status === "FAILED") {
|
||
status = 2; // 任务失败
|
||
}
|
||
|
||
// 创建VideoSegmentEntity
|
||
const entity: VideoSegmentEntity = {
|
||
id: `video_mock_${index}`, // 生成临时ID,包含索引
|
||
updatedAt: Date.now(),
|
||
loadingProgress: status === 1 ? 100 : status === 0 ? 50 : 0, // 已完成100%,进行中50%,失败0%
|
||
disableEdit: false,
|
||
name: `视频片段_${index}`, // 生成临时名称,包含索引
|
||
sketchUrl: "", // 后端数据中没有sketchUrl,设为空字符串
|
||
videoUrl: videoUrls,
|
||
status: status,
|
||
lens: lens
|
||
};
|
||
|
||
entities.push(entity);
|
||
});
|
||
}
|
||
|
||
return entities;
|
||
}
|
||
|
||
/**
|
||
* @description 将VideoSegmentEntity数组转换为后端数据结构
|
||
* @param entities 视频片段实体数组
|
||
* @returns VideoSegmentEntityAdapter 后端数据结构
|
||
*/
|
||
static fromVideoSegmentEntity(entities: VideoSegmentEntity[]): VideoSegmentEntityAdapter {
|
||
const taskResults: Array<{
|
||
narrative_goal: string;
|
||
shot_1: string;
|
||
shot_2: string;
|
||
shot_3: string;
|
||
shot_4: string;
|
||
shot_5: string;
|
||
shot_6: string;
|
||
shot_7: string;
|
||
shot_8: string;
|
||
shot_9: string;
|
||
shot_10: string;
|
||
videos: Array<{ video_url: string }>;
|
||
}> = [];
|
||
|
||
// 遍历每个实体,转换为task_result项
|
||
entities.forEach(entity => {
|
||
// 从lens中提取镜头描述(支持镜头1到镜头10)
|
||
const shots: { [key: string]: string } = {};
|
||
for (let i = 1; i <= 10; i++) {
|
||
const lensItem = entity.lens.find(lens => lens.name === `shot_${i}`);
|
||
if (lensItem) {
|
||
// 重新组合镜头描述和对话内容
|
||
let fullContent = lensItem.script;
|
||
|
||
// 如果有对话内容,添加到镜头描述后面
|
||
if (lensItem.content && lensItem.content.length > 0) {
|
||
const dialogueLines = lensItem.content.map(dialogue =>
|
||
`${dialogue.roleName} [CH-01]: ${dialogue.content}`
|
||
);
|
||
fullContent += '\n' + dialogueLines.join('\n');
|
||
}
|
||
|
||
shots[`shot_${i}`] = fullContent;
|
||
} else {
|
||
shots[`shot_${i}`] = "";
|
||
}
|
||
}
|
||
|
||
// 如果有更多镜头,可以合并到narrative_goal中
|
||
let narrative_goal = "";
|
||
const additionalLenses = entity.lens
|
||
.filter(lens => !lens.name.match(/^shot_[1-9]$|^shot_10$/))
|
||
.map(lens => `${lens.name}: ${lens.script}`)
|
||
.join("; ");
|
||
|
||
if (additionalLenses) {
|
||
narrative_goal = additionalLenses;
|
||
}
|
||
|
||
// 构建videos数组
|
||
const videos = entity.videoUrl.map(url => ({
|
||
video_url: url
|
||
}));
|
||
|
||
taskResults.push({
|
||
narrative_goal: narrative_goal,
|
||
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 || "",
|
||
videos: videos
|
||
});
|
||
});
|
||
|
||
// 根据第一个实体的status确定task_status(如果数组为空,默认为COMPLETED)
|
||
let task_status: string = "COMPLETED";
|
||
if (entities.length > 0) {
|
||
const firstEntity = entities[0];
|
||
if (firstEntity.status === 0) {
|
||
task_status = "IN_PROGRESS"; // 视频加载中映射为IN_PROGRESS
|
||
} else if (firstEntity.status === 1) {
|
||
task_status = "COMPLETED"; // 任务已完成
|
||
} else if (firstEntity.status === 2) {
|
||
task_status = "FAILED"; // 任务失败
|
||
}
|
||
}
|
||
|
||
// 创建VideoSegmentEntityAdapter
|
||
const adapter = new VideoSegmentEntityAdapter();
|
||
adapter.original_text = ""; // 实体中没有original_text,设为空字符串
|
||
adapter.task_status = task_status;
|
||
adapter.task_result = taskResults;
|
||
|
||
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
|
||
};
|
||
}
|
||
}
|
||
|
||
/**视频片段基础数据 */
|
||
export interface TaskSketch {
|
||
url: string;
|
||
video_id: string;
|
||
}
|