From 3d5307c8e4d749f4fbf3a939954c83741d4e40f8 Mon Sep 17 00:00:00 2001 From: lq1405 <2769838458@qq.com> Date: Thu, 25 Sep 2025 17:21:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B5=B7=E8=9E=BA=E8=A7=86?= =?UTF-8?q?=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/define/db/model/bookTaskDetail.ts | 6 + src/define/db/service/base/realmBase.ts | 2 +- src/define/enum/bookEnum.ts | 8 +- src/define/enum/softwareEnum.ts | 1 + src/define/enum/video.ts | 491 ++++++++++ .../subDefineString/bookDefineString.ts | 3 + src/define/model/book/bookTaskDetail.d.ts | 145 ++- src/i18n/locales/en.ts | 52 +- src/i18n/locales/zh-cn.ts | 62 +- .../book/subBookHandle/bookBasicHandle.ts | 8 + .../subBookHandle/bookVideoServiceHandle.ts | 52 +- src/main/service/mj/mjBasic.ts | 56 +- src/main/service/task/taskManage.ts | 3 + src/main/service/video/hailuoVideo.ts | 886 ++++++++++++++++++ src/main/service/video/index.ts | 16 + src/main/service/video/klingVideo.ts | 22 +- src/main/service/video/mjVideo.ts | 4 +- src/main/service/video/videoBasic.ts | 110 +++ src/renderer/components.d.ts | 4 + .../MediaToVideoInfoConfig.vue | 5 + .../HailuoFirstLastFrameInfo.vue | 522 +++++++++++ .../HailuoImageToVideoInfo.vue | 460 +++++++++ .../HailuoTextToVideoInfo.vue | 419 +++++++++ .../MediaToVideoInfoHaiLuoVideoInfo.vue | 387 ++++++++ .../MediaToVideoInfoHaiLuo/README.md | 185 ++++ .../MediaToVideo/MediaToVideoInfoHome.vue | 33 +- 26 files changed, 3840 insertions(+), 102 deletions(-) create mode 100644 src/main/service/video/hailuoVideo.ts create mode 100644 src/main/service/video/videoBasic.ts create mode 100644 src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoFirstLastFrameInfo.vue create mode 100644 src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoImageToVideoInfo.vue create mode 100644 src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoTextToVideoInfo.vue create mode 100644 src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue create mode 100644 src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/README.md diff --git a/src/define/db/model/bookTaskDetail.ts b/src/define/db/model/bookTaskDetail.ts index ea7dfae..1a7b43e 100644 --- a/src/define/db/model/bookTaskDetail.ts +++ b/src/define/db/model/bookTaskDetail.ts @@ -64,6 +64,9 @@ export class VideoMessage extends Realm.Object { lumaOptions!: string | null // 生成视频的一些设置 klingOptions!: string | null // 生成视频的一些设置 mjVideoOptions!: string | null // MJ生成视频的一些设置 + hailuoTextToVideoOptions?: string + hailuoFirstFrameOnlyOptions?: string + hailuoFirstLastFrameOptions?: string messageData!: string | null static schema: ObjectSchema = { name: 'VideoMessage', @@ -83,6 +86,9 @@ export class VideoMessage extends Realm.Object { lumaOptions: 'string?', klingOptions: 'string?', mjVideoOptions: 'string?', + hailuoTextToVideoOptions: "string?", + hailuoFirstFrameOnlyOptions: "string?", + hailuoFirstLastFrameOptions: "string?", messageData: 'string?' }, primaryKey: 'id' diff --git a/src/define/db/service/base/realmBase.ts b/src/define/db/service/base/realmBase.ts index a577ceb..111637c 100644 --- a/src/define/db/service/base/realmBase.ts +++ b/src/define/db/service/base/realmBase.ts @@ -80,7 +80,7 @@ export class RealmBaseService extends BaseService { PresetModel ], path: this.dbpath, - schemaVersion: 21, // 数据库版本号,修改时需要增加 + schemaVersion: 22, // 数据库版本号,修改时需要增加 migration: migration } this.realm = await Realm.open(config) diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index 9d28471..ff97cfa 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -120,7 +120,13 @@ export enum BookBackTaskType { // MJ Video MJ_VIDEO = 'mj_video', // MJ VIDEO EXTEND 视频拓展 - MJ_VIDEO_EXTEND = 'mj_video_extend' + MJ_VIDEO_EXTEND = 'mj_video_extend', + // 海螺文生视频 + HAILUO_TEXT_TO_VIDEO = 'hailuo_text_to_video', + // 海螺图生视频 + HAILUO_IMAGE_TO_VIDEO = 'hailuo_image_to_video', + // 海螺视频首尾帧 + HAILUO_FIRST_LAST_FRAME = 'hailuo_first_last_frame' } export enum BookBackTaskStatus { diff --git a/src/define/enum/softwareEnum.ts b/src/define/enum/softwareEnum.ts index 7d576a1..76ac68f 100644 --- a/src/define/enum/softwareEnum.ts +++ b/src/define/enum/softwareEnum.ts @@ -71,6 +71,7 @@ export enum ResponseMessageType { KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND', // Kling生成视频拓展 MJ_VIDEO = 'MJ_VIDEO', // MJ生成视频 MJ_VIDEO_EXTEND = 'MJ_VIDEO_EXTEND', // MJ生成视频拓展 + HAI_LUO_VIDEO = 'HAI_LUO_VIDEO', // 海螺生成视频 VIDEO_SUCESS = 'VIDEO_SUCESS' //视频生成成功 } diff --git a/src/define/enum/video.ts b/src/define/enum/video.ts index 6218254..69906f6 100644 --- a/src/define/enum/video.ts +++ b/src/define/enum/video.ts @@ -3,6 +3,8 @@ import { t } from '@/i18n' import { BookBackTaskType } from './bookEnum' +export type ToVIdeoType = 'textToVideo' | 'imageToVideo' | "firstLastFrame" + /** 图片转视频的方式 */ export enum ImageToVideoModels { /** runway 生成视频 */ @@ -15,6 +17,8 @@ export enum ImageToVideoModels { KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND', /** Pika 生成视频 */ PIKA = 'PIKA', + /** 海螺生成视频 */ + HAILUO = 'HAILUO', /** MJ 图转视频 */ MJ_VIDEO = 'MJ_VIDEO', /** MJ 视频拓展 */ @@ -22,10 +26,36 @@ export enum ImageToVideoModels { } +/** + * 根据任务类型映射到对应的图片转视频模型 + * + * 该方法用于将后端任务类型(BookBackTaskType)或字符串类型, + * 映射为前端使用的 ImageToVideoModels 枚举值。 + * 主要用于统一不同任务类型与视频模型的对应关系,便于后续视频处理逻辑。 + * + * @param type 后端任务类型(BookBackTaskType)或字符串 + * @returns 对应的 ImageToVideoModels 枚举值,未匹配时返回 'UNKNOWN' + * + * @description + * - 支持多种后端任务类型与视频模型的映射 + * - 未知类型返回 'UNKNOWN',便于异常处理 + * - 常用于视频生成、任务分发等场景 + * + * @example + * ```typescript + * const model = MappingTaskTypeToVideoModel(BookBackTaskType.LUMA_VIDEO); + * // model === ImageToVideoModels.LUMA + * ``` + * + * @see BookBackTaskType - 后端任务类型枚举 + * @see ImageToVideoModels - 图片转视频模型枚举 + */ export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) => { switch (type) { case BookBackTaskType.LUMA_VIDEO: return ImageToVideoModels.LUMA + case BookBackTaskType.HAILUO_TEXT_TO_VIDEO: + return ImageToVideoModels.HAILUO case BookBackTaskType.RUNWAY_VIDEO: return ImageToVideoModels.RUNWAY case BookBackTaskType.KLING_VIDEO: @@ -56,6 +86,8 @@ export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) = return t('可灵') case ImageToVideoModels.PIKA: return 'Pika' + case ImageToVideoModels.HAILUO: + return t('海螺') case ImageToVideoModels.MJ_VIDEO: case ImageToVideoModels.MJ_VIDEO_EXTEND: return t('MJ视频') @@ -78,6 +110,10 @@ export const GetImageToVideoModelsOptions = () => { { label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO), value: ImageToVideoModels.MJ_VIDEO + }, { + + label: GetImageToVideoModelsLabel(ImageToVideoModels.HAILUO), + value: ImageToVideoModels.HAILUO }, { label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY), @@ -492,3 +528,458 @@ export function GetMJVideoBatchSizeOptions() { } //#endregion + +//#region 海螺视频相关 + +/** + * 海螺视频模型名称 + */ +export enum HailuoModel { + /** MiniMax-Hailuo-02 模型 */ + MINIMAX_HAILUO_02 = 'MiniMax-Hailuo-02', + /** I2V-01-Director 模型 */ + I2V_01_DIRECTOR = 'I2V-01-Director', + /** I2V-01-live 模型 */ + I2V_01_LIVE = 'I2V-01-live', + /** I2V-01 模型 */ + I2V_01 = 'I2V-01', + /** T2V-01-Director 模型 */ + T2V_01_DIRECTOR = "T2V-01-Director", + /** T2V-01 模型 */ + T2V_01 = 'T2V-01' +} + +/** + * 海螺视频分辨率 + */ +export enum HailuoResolution { + /** 512P 分辨率 */ + P512 = '512P', + /** 720P 分辨率 */ + P720 = '720P', + /** 768P 分辨率 */ + P768 = '768P', + /** 1080P 分辨率 */ + P1080 = '1080P' +} + +/** + * 海螺视频时长 + */ +export enum HailuoDuration { + /** 6秒 */ + SIX = 6, + /** 10秒 */ + TEN = 10 +} + +/** + * 获取海螺视频模型名称的标签 + * + * @param model 海螺视频模型枚举值或字符串 + * @returns 返回对应的中文标签 + */ +export function GetHailuoModelLabel(model: HailuoModel | string) { + switch (model) { + case HailuoModel.MINIMAX_HAILUO_02: + return t('MiniMax-Hailuo-02') + case HailuoModel.I2V_01_DIRECTOR: + return t('I2V-01-Director') + case HailuoModel.I2V_01_LIVE: + return t('I2V-01-live') + case HailuoModel.I2V_01: + return t('I2V-01') + case HailuoModel.T2V_01_DIRECTOR: + return t('T2V-01-Director') + case HailuoModel.T2V_01: + return t('T2V-01') + default: + return t('未知') + } +} + +/** + * 获取海螺视频模型选项列表 + * + * @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件 + */ +export function GetHailuoModelOptions(type: ToVIdeoType) { + if (type == "textToVideo") { + return [ + { + label: GetHailuoModelLabel(HailuoModel.MINIMAX_HAILUO_02), + value: HailuoModel.MINIMAX_HAILUO_02 + }, + { + label: GetHailuoModelLabel(HailuoModel.T2V_01_DIRECTOR), + value: HailuoModel.T2V_01_DIRECTOR + }, + { + label: GetHailuoModelLabel(HailuoModel.T2V_01), + value: HailuoModel.T2V_01 + } + ] + } else if (type == "imageToVideo") { + return [ + { + label: GetHailuoModelLabel(HailuoModel.MINIMAX_HAILUO_02), + value: HailuoModel.MINIMAX_HAILUO_02 + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01_DIRECTOR), + value: HailuoModel.I2V_01_DIRECTOR + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01_LIVE), + value: HailuoModel.I2V_01_LIVE + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01), + value: HailuoModel.I2V_01 + } + ] + } else if (type == "firstLastFrame") { + return [ + { + label: GetHailuoModelLabel(HailuoModel.MINIMAX_HAILUO_02), + value: HailuoModel.MINIMAX_HAILUO_02 + }] + } + + return [ + [ + { + label: GetHailuoModelLabel(HailuoModel.MINIMAX_HAILUO_02), + value: HailuoModel.MINIMAX_HAILUO_02 + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01_DIRECTOR), + value: HailuoModel.I2V_01_DIRECTOR + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01_LIVE), + value: HailuoModel.I2V_01_LIVE + }, + { + label: GetHailuoModelLabel(HailuoModel.I2V_01), + value: HailuoModel.I2V_01 + }, + { + label: GetHailuoModelLabel(HailuoModel.T2V_01_DIRECTOR), + value: HailuoModel.T2V_01_DIRECTOR + }, + { + label: GetHailuoModelLabel(HailuoModel.T2V_01), + value: HailuoModel.T2V_01 + } + ] + ] +} + +/** + * 判断海螺视频模型是否支持运镜指令 + * + * @param model 海螺视频模型枚举值或字符串 + * @returns 返回是否支持运镜指令 + */ +export function IsHailuoModelSupportDirectorCommands(model: HailuoModel | string): boolean { + return model === HailuoModel.MINIMAX_HAILUO_02 || model === HailuoModel.I2V_01_DIRECTOR +} + +/** + * 获取海螺视频模型支持的分辨率选项 + * + * @param type 视频生成类型: 'textToVideo' (文生视频), 'imageToVideo' (图生视频), 'firstLastFrame' (首尾帧) + * @param model 海螺视频模型枚举值或字符串 + * @param duration 视频时长 + * @returns 返回支持的分辨率选项数组 + */ +export function GetHailuoModelSupportedResolutions(type: ToVIdeoType, model: HailuoModel | string, duration: HailuoDuration | number = HailuoDuration.SIX) { + if (model === HailuoModel.MINIMAX_HAILUO_02) { + // MiniMax-Hailuo-02 模型根据类型和时长的不同支持 + if (type === 'textToVideo') { + // 文生视频 + if (duration === HailuoDuration.SIX) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 }, // 默认 + { label: GetHailuoResolutionLabel(HailuoResolution.P1080), value: HailuoResolution.P1080 } + ] + } else if (duration === HailuoDuration.TEN) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 } // 默认 + ] + } + } else if (type === 'imageToVideo') { + // 图生视频 + if (duration === HailuoDuration.SIX) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P512), value: HailuoResolution.P512 }, + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 }, // 默认 + { label: GetHailuoResolutionLabel(HailuoResolution.P1080), value: HailuoResolution.P1080 } + ] + } else if (duration === HailuoDuration.TEN) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P512), value: HailuoResolution.P512 }, + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 } // 默认 + ] + } + } else if (type === 'firstLastFrame') { + // 首尾帧视频 + if (duration === HailuoDuration.SIX) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 }, // 默认 + { label: GetHailuoResolutionLabel(HailuoResolution.P1080), value: HailuoResolution.P1080 } + ] + } else if (duration === HailuoDuration.TEN) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P768), value: HailuoResolution.P768 } + ] + } + } + } else { + // 其他模型 (I2V-01-Director, I2V-01-live, I2V-01) + if (duration === HailuoDuration.SIX) { + return [ + { label: GetHailuoResolutionLabel(HailuoResolution.P720), value: HailuoResolution.P720 } // 默认 + ] + } + // 其他模型不支持10秒时长 + } + + return [] +} + +/** + * 验证海螺视频分辨率是否有效 + * + * 根据视频生成类型、模型和时长,验证指定的分辨率是否被支持。 + * 不同的海螺视频模型在不同类型和时长下支持的分辨率范围不同。 + * + * @param type 视频生成类型 - 'textToVideo' (文生视频), 'imageToVideo' (图生视频), 'firstLastFrame' (首尾帧) + * @param model 海螺视频模型 - 不同模型支持的分辨率范围不同 + * @param duration 视频时长 - 6秒或10秒,影响支持的分辨率选项 + * @param resolution 要验证的分辨率 - 512P, 720P, 768P, 1080P + * + * @returns {boolean} 如果分辨率被支持则返回true,否则返回false + * + * @description + * - MiniMax-Hailuo-02 模型支持多种分辨率,但具体支持范围依赖于类型和时长 + * - I2V系列模型通常只支持720P分辨率 + * - 1080P分辨率通常只在6秒时长下被支持 + * + * @example + * ```typescript + * // 验证文生视频在MiniMax-Hailuo-02模型下6秒时长是否支持768P + * const isValid = IsValidResolution('textToVideo', HailuoModel.MINIMAX_HAILUO_02, HailuoDuration.SIX, HailuoResolution.P768); + * // isValid === true + * + * // 验证图生视频在I2V-01模型下6秒时长是否支持1080P + * const isValid2 = IsValidResolution('imageToVideo', HailuoModel.I2V_01, HailuoDuration.SIX, HailuoResolution.P1080); + * // isValid2 === false + * ``` + * + * @see GetHailuoModelSupportedResolutions - 获取模型支持的分辨率列表 + */ +export function IsValidResolution(type: ToVIdeoType, model: HailuoModel, duration: HailuoDuration, resolution: HailuoResolution): boolean { + let spportedResolutions = GetHailuoModelSupportedResolutions(type, model, duration) + + // 检查传入的分辨率是否在支持的列表中 + return spportedResolutions.some(r => r.value === resolution) +} + +/** + * 获取海螺视频模型支持的时长选项 + * + * @param type 视频生成类型: 'textToVideo' (文生视频), 'imageToVideo' (图生视频), 'firstLastFrame' (首尾帧) + * @param model 海螺视频模型枚举值或字符串 + * @param resolution 视频分辨率 + * @returns 返回支持的时长选项数组 + */ +export function GetHailuoModelSupportedDurations(type: ToVIdeoType, model: HailuoModel | string, resolution?: HailuoResolution | string) { + if (model === HailuoModel.MINIMAX_HAILUO_02) { + if (type === 'textToVideo') { + // 文生视频 - MiniMax-Hailuo-02 支持 6s 和 10s,但 1080P 只支持 6s + if (resolution === HailuoResolution.P1080) { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX } + ] + } else { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX }, + { label: GetHailuoDurationLabel(HailuoDuration.TEN), value: HailuoDuration.TEN } + ] + } + } else if (type === 'imageToVideo') { + // 图生视频 - MiniMax-Hailuo-02 支持 6s 和 10s,1080P 只支持 6s + if (resolution === HailuoResolution.P1080) { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX } + ] + } else { + // 512P, 768P 都支持 6s 和 10s + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX }, + { label: GetHailuoDurationLabel(HailuoDuration.TEN), value: HailuoDuration.TEN } + ] + } + } else if (type === 'firstLastFrame') { + // 首尾帧视频 - MiniMax-Hailuo-02 支持 6s 和 10s,但 1080P 只支持 6s + if (resolution === HailuoResolution.P1080) { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX } + ] + } else { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX }, + { label: GetHailuoDurationLabel(HailuoDuration.TEN), value: HailuoDuration.TEN } + ] + } + } else { + // 兼容旧版本调用,默认逻辑 + if (resolution === HailuoResolution.P1080) { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX } + ] + } else { + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX }, + { label: GetHailuoDurationLabel(HailuoDuration.TEN), value: HailuoDuration.TEN } + ] + } + } + } else { + // 其他模型 (I2V-01-Director, I2V-01-live, I2V-01) 只支持6秒 + return [ + { label: GetHailuoDurationLabel(HailuoDuration.SIX), value: HailuoDuration.SIX } + ] + } +} + +/** + * 验证海螺视频时长是否有效 + * + * 根据视频生成类型、模型和分辨率,验证指定的时长是否被支持。 + * 不同的海螺视频模型在不同类型和分辨率下支持的时长范围不同。 + * + * @param type 视频生成类型 - 'textToVideo' (文生视频), 'imageToVideo' (图生视频), 'firstLastFrame' (首尾帧) + * @param model 海螺视频模型 - 不同模型支持的时长范围不同 + * @param resolution 视频分辨率 - 512P, 720P, 768P, 1080P,影响支持的时长选项 + * @param duration 要验证的时长 - 6秒或10秒 + * + * @returns {boolean} 如果时长被支持则返回true,否则返回false + * + * @description + * - MiniMax-Hailuo-02 模型通常支持6秒和10秒,但1080P分辨率只支持6秒 + * - I2V系列模型 (I2V-01-Director, I2V-01-live, I2V-01) 只支持6秒时长 + * - 高分辨率(如1080P)通常限制为较短时长以保证生成质量 + * + * @example + * ```typescript + * // 验证文生视频在MiniMax-Hailuo-02模型下768P分辨率是否支持10秒 + * const isValid = IsValidDuratio('textToVideo', HailuoModel.MINIMAX_HAILUO_02, HailuoResolution.P768, HailuoDuration.TEN); + * // isValid === true + * + * // 验证图生视频在MiniMax-Hailuo-02模型下1080P分辨率是否支持10秒 + * const isValid2 = IsValidDuratio('imageToVideo', HailuoModel.MINIMAX_HAILUO_02, HailuoResolution.P1080, HailuoDuration.TEN); + * // isValid2 === false (1080P只支持6秒) + * + * // 验证I2V-01模型是否支持10秒时长 + * const isValid3 = IsValidDuratio('imageToVideo', HailuoModel.I2V_01, HailuoResolution.P720, HailuoDuration.TEN); + * // isValid3 === false (I2V系列只支持6秒) + * ``` + * + * @see GetHailuoModelSupportedDurations - 获取模型支持的时长列表 + */ +export function IsValidDuratio(type: ToVIdeoType, model: HailuoModel, resolution: HailuoResolution, duration: HailuoDuration): boolean { + // 获取当前模型、类型和分辨率组合下支持的所有时长选项 + let spportedResolutions = GetHailuoModelSupportedDurations(type, model, resolution) + + // 检查传入的时长是否在支持的列表中 + return spportedResolutions.some(r => r.value === duration) +} + + + +/** + * 获取海螺视频分辨率的标签 + * + * @param resolution 海螺视频分辨率枚举值或字符串 + * @returns 返回对应的中文标签 + */ +export function GetHailuoResolutionLabel(resolution: HailuoResolution | string) { + switch (resolution) { + case HailuoResolution.P512: + return t('512P') + case HailuoResolution.P720: + return t('720P') + case HailuoResolution.P768: + return t('768P') + case HailuoResolution.P1080: + return t('1080P') + default: + return t('未知') + } +} + +/** + * 获取海螺视频分辨率选项列表 + * + * @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件 + */ +export function GetHailuoResolutionOptions() { + return [ + { + label: GetHailuoResolutionLabel(HailuoResolution.P512), + value: HailuoResolution.P512 + }, + { + label: GetHailuoResolutionLabel(HailuoResolution.P720), + value: HailuoResolution.P720 + }, + { + label: GetHailuoResolutionLabel(HailuoResolution.P768), + value: HailuoResolution.P768 + }, + { + label: GetHailuoResolutionLabel(HailuoResolution.P1080), + value: HailuoResolution.P1080 + } + ] +} + +/** + * 获取海螺视频时长的标签 + * + * @param duration 海螺视频时长枚举值或数字 + * @returns 返回对应的中文标签 + */ +export function GetHailuoDurationLabel(duration: HailuoDuration | number) { + switch (duration) { + case HailuoDuration.SIX: + return t('6秒') + case HailuoDuration.TEN: + return t('10秒') + default: + return t('未知') + } +} + +/** + * 获取海螺视频时长选项列表 + * + * @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件 + */ +export function GetHailuoDurationOptions() { + return [ + { + label: GetHailuoDurationLabel(HailuoDuration.SIX), + value: HailuoDuration.SIX + }, + { + label: GetHailuoDurationLabel(HailuoDuration.TEN), + value: HailuoDuration.TEN + } + ] +} + +//#endregion diff --git a/src/define/ipcDefineString/subDefineString/bookDefineString.ts b/src/define/ipcDefineString/subDefineString/bookDefineString.ts index 75ab7da..449f10c 100644 --- a/src/define/ipcDefineString/subDefineString/bookDefineString.ts +++ b/src/define/ipcDefineString/subDefineString/bookDefineString.ts @@ -167,6 +167,9 @@ const BOOK = { /** Kling 图转视频返回前端数据任务 */ KLING_IMAGE_TO_VIDEO_RETURN: 'KLING_IMAGE_TO_VIDEO_RETURN', + /** 海螺图转视频返回前端数据任务 */ + HAILUO_TO_VIDEO_RETURN: 'HAILUO_TO_VIDEO_RETURN', + //#endregion } diff --git a/src/define/model/book/bookTaskDetail.d.ts b/src/define/model/book/bookTaskDetail.d.ts index c8d09c8..c3c7671 100644 --- a/src/define/model/book/bookTaskDetail.d.ts +++ b/src/define/model/book/bookTaskDetail.d.ts @@ -5,10 +5,15 @@ import { KlingModelName, MJVideoBatchSize, MJVideoType, + MJVideoAction, + MJVideoMotion, RunawayModel, RunwaySeconds, VideoModel, - VideoStatus + VideoStatus, + HailuoModel, + HailuoResolution, + HailuoDuration } from '@/define/enum/video' declare namespace BookTaskDetail { @@ -31,6 +36,9 @@ declare namespace BookTaskDetail { lumaOptions?: string klingOptions?: string mjVideoOptions?: string + hailuoTextToVideoOptions?: string + hailuoFirstFrameOnlyOptions?: string + hailuoFirstLastFrameOptions?: string messageData?: string videoUrls?: string[] // 视频地址数组 messageData?: string @@ -191,6 +199,141 @@ declare namespace BookTaskDetail { loop?: boolean } + /** + * 海螺视频生成参数 - 基础接口 + */ + interface HailuoBaseOptions { + /** + * 模型名称,必填 + * 可用值:MiniMax-Hailuo-02, I2V-01-Director, I2V-01-live, I2V-01 + */ + model: HailuoModel + + /** + * 视频的文本描述,最大 2000 字符 + * 对于 MiniMax-Hailuo-02 和 I2V-01-Director 模型,支持使用 [指令] 语法进行运镜控制 + * + * 支持 15 种运镜指令: + * - 左右移: [左移], [右移] + * - 左右摇: [左摇], [右摇] + * - 推拉: [推进], [拉远] + * - 升降: [上升], [下降] + * - 上下摇: [上摇], [下摇] + * - 变焦: [变焦推近], [变焦拉远] + * - 其他: [晃动], [跟随], [固定] + * + * 使用规则: + * - 组合运镜: 同一组 [] 内的多个指令会同时生效,如 [左摇,上升],建议组合不超过 3 个 + * - 顺序运镜: prompt 中前后出现的指令会依次生效,如 "...[推进], 然后...[拉远]" + * - 自然语言: 也支持通过自然语言描述运镜,但使用标准指令能获得更准确的响应 + */ + prompt?: string + + /** + * 是否自动优化 prompt,默认为 true + * 设为 false 可进行更精确的控制 + */ + prompt_optimizer?: boolean + + /** + * 视频时长(秒),默认值为 6 + * 其可用值与模型和分辨率相关: + * + * 模型 MiniMax-Hailuo-02: + * - 512P:6 或 10 秒 + * - 768P:6 或 10 秒 + * - 1080P:6 秒 + * + * 其他模型: + * - 720P:6 秒 + */ + duration?: HailuoDuration + + /** + * 视频分辨率 + * 其可用值与模型相关: + * + * 模型 MiniMax-Hailuo-02: + * - 6秒:512P, 768P (默认), 1080P + * - 10秒:512P, 768P (默认) + * + * 其他模型: + * - 6秒:720P (默认) + * - 10秒:不支持 + */ + resolution?: HailuoResolution + } + + /** + * 海螺视频生成参数 - 仅首帧模式 + * 适用于只有起始帧的视频生成 + */ + interface HailuoFirstFrameOnlyOptions extends HailuoBaseOptions { + /** + * 将指定图片作为视频的起始帧 + * 支持公网 URL 或 Base64 编码的 Data URL (data:image/jpeg;base64,...) + * + * 必填条件: + * - 当 model 为 I2V-01, I2V-01-Director, I2V-01-live 时 + * - 当 model 为 MiniMax-Hailuo-02 且 resolution 为 512P 时 + * + * 图片要求: + * - 格式:JPG, JPEG, PNG, WebP + * - 体积:小于 20MB + * - 尺寸:短边像素大于 300px,长宽比在 2:5 和 5:2 之间 + */ + first_frame_image: string + + /** + * 是否缩短 prompt_optimizer 的优化耗时,默认为 false + * 仅对 MiniMax-Hailuo-02 模型生效 + */ + fast_pretreatment?: boolean + } + + /** + * 海螺视频生成参数 - 首尾帧模式 + * 适用于同时指定起始帧和结束帧的视频生成 + */ + interface HailuoFirstLastFrameOptions extends HailuoFirstFrameOnlyOptions { + /** + * 将指定图片作为视频的结束帧 + * 支持公网 URL 或 Base64 编码的 Data URL (data:image/jpeg;base64,...) + * + * 图片要求: + * - 格式:JPG, JPEG, PNG, WebP + * - 体积:小于 20MB + * - 尺寸:短边像素大于 300px,长宽比在 2:5 和 5:2 之间 + * ⚠️ 生成视频尺寸遵循首帧图片,当首帧和尾帧的图片尺寸不一致时,模型将参考首帧对尾帧图片进行裁剪 + * ⚠️ 包含尾帧的请求仅支持5秒时长 + */ + last_frame_image: string + + /** + * 当使用尾帧时,时长固定为5秒 + */ + duration: HailuoDuration + } + + /** + * 海螺视频生成参数 - 文本生视频模式(无图片) + * 适用于纯文本描述生成视频 + */ + interface HailuoTextToVideoOptions extends HailuoBaseOptions { + /** + * 是否缩短 prompt_optimizer 的优化耗时,默认为 false + * 仅对 MiniMax-Hailuo-02 模型生效 + */ + fast_pretreatment?: boolean + + } + + /** + * 海螺视频统一选项类型 + * 根据不同使用场景选择对应的接口 + */ + type HailuoOptions = HailuoTextToVideoOptions | HailuoFirstFrameOnlyOptions | HailuoFirstLastFrameOptions + //#endregion //#region 小说文案相关 diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index b862d63..e2d264b 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -1660,9 +1660,55 @@ export default { "高性能 (std)": "High Performance (std)", "高表现 (pro)": "High Performance (pro)", "选择Video": "Select Video", - "必须

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间": "必须

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间", - "参考图像 - 尾帧控制": "参考图像 - 尾帧控制", - '可选

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间': '可选

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间', + "必须

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间": "Required

• Supported formats: .jpg/.jpeg/.png
• File size: not exceeding 10MB
• Resolution: not less than 300*300px
• Aspect ratio: between 1:2.5 ~ 2.5:1", + "参考图像 - 尾帧控制": "Reference Image - End Frame Control", + '可选

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间': 'Optional

• Supported formats: .jpg/.jpeg/.png
• File size: not exceeding 10MB
• Resolution: not less than 300*300px
• Aspect ratio: between 1:2.5 ~ 2.5:1', + "海螺": "HaiLuo", + "海螺首尾帧视频任务完成!": 'HaiLuo first-last frame video task completed!', + "海螺首尾帧视频任务失败,失败信息:{error}": "HaiLuo first-last frame video task failed, error details: {error}", + "海螺图转视频任务完成!": 'HaiLuo image-to-video task completed!', + '海螺图转视频任务失败,失败信息:{error}': 'HaiLuo image-to-video task failed, error details: {error}', + "当前分镜数据的海螺视频参数的提示词为空,请检查": "The prompt for the HaiLuo video parameters in the current storyboard data is empty, please check", + "海螺文生视频任务完成!": 'HaiLuo text-to-video task completed!', + "海螺文生视频任务失败,失败信息:{error}": 'HaiLuo text-to-video task failed, error details: {error}', + "海螺视频任务正在执行中...": "HaiLuo video task in progress...", + "海螺视频任务已完成!": "HaiLuo video task completed!", + "海螺视频任务失败,失败信息:{error}": "HaiLuo video task failed, error details: {error}", + "获取海螺视频下载地址失败": "Failed to get HaiLuo video download address", + "获取海螺视频下载地址失败,下载地址为空": "Failed to get HaiLuo video download address, download address is empty", + "已成功提交海螺视频任务,任务ID:{taskId}": "HaiLuo video task submitted successfully, task ID: {taskId}", + "当前分镜数据的海螺视频参数的模型参数为空,请检查": "The model parameter for the HaiLuo video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺视频参数的模型参数不合法,请检查": "The model parameter for the HaiLuo video parameters in the current storyboard data is invalid, please check", + "当前分镜数据的海螺视频参数的分辨率参数为空,请检查": "The resolution parameter for the HaiLuo video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺视频参数的时长参数为空,请检查": "The duration parameter for the HaiLuo video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺视频参数的分辨率参数不合法,请检查": "The resolution parameter for the HaiLuo video parameters in the current storyboard data is invalid, please check", + "当前分镜数据的海螺视频参数的时长参数不合法,请检查": "The duration parameter for the HaiLuo video parameters in the current storyboard data is invalid, please check", + "非MiniMax-Hailuo-02不支持启用fast_pretreatment参数,请检查": "Non-MiniMax-Hailuo-02 does not support enabling the fast_pretreatment parameter, please check", + "当前分镜数据的海螺图生视频参数的首帧图片参数为空,请检查": "The first frame image parameter for the HaiLuo image-to-video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺首尾帧视频参数的首帧图片参数为空,请检查": "The first frame image parameter for the HaiLuo first-last frame video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺首尾帧视频参数的尾帧图片参数为空,请检查": "The last frame image parameter for the HaiLuo first-last frame video parameters in the current storyboard data is empty, please check", + "当前分镜数据的海螺文生视频参数为空或参数校验失败,请检查": 'The HaiLuo text-to-video parameters for the current storyboard data are empty or validation failed, please check', + "当前分镜数据的海螺图生视频参数为空或参数校验失败,请检查": "The HaiLuo image-to-video parameters for the current storyboard data are empty or validation failed, please check", + "当前分镜数据的海螺首尾帧视频参数为空或参数校验失败,请检查": "The HaiLuo first-last frame video parameters for the current storyboard data are empty or validation failed, please check", + "不支持的海螺视频类型:{type},请检查": "Unsupported HaiLuo video type: {type}, please check", + "将当前首尾帧视频的基础设置批量应用到所有的分镜中": "Apply the current first-last frame video's basic settings to all storyboards in batch", + "首帧图片": "First Frame Image", + "尾帧图片": "Last Frame Image", + "视频分辨率": "Video Resolution", + "自动优化提示词": "Auto Optimize Prompt", + "模型变更后已自动调整相关参数以确保兼容性": 'Model change has automatically adjusted related parameters to ensure compatibility', + "分辨率变更后已自动调整时长以确保兼容性": "Resolution change has automatically adjusted duration to ensure compatibility", + "已自动设置默认值并调整参数以确保兼容性": "Default values have been automatically set and parameters adjusted to ensure compatibility", + "时长变更后已自动调整分辨率以确保兼容性": "Duration change has automatically adjusted resolution to ensure compatibility", + "将当前图生视频的基础设置批量应用到所有的分镜中": "Apply the current image-to-video basic settings to all storyboards in batch", + "快速预处理": "Fast Pretreatment", + "将当前文生视频的基础设置批量应用到所有的分镜中": "Apply the current text-to-video basic settings to all storyboards in batch", + "首尾帧视频": "First-Last Frame Video", + "文生视频": "Text-to-Video", + "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称、分辨率、时长、提示词优化等基础设置\n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?": "Do you want to apply the current storyboard settings to all other storyboards in batch?\n\nSynchronized settings: Model Name, Resolution, Duration, Prompt Optimization and other basic settings\n\nAfter batch application, the above basic settings of other storyboards will be replaced with the current storyboard data. Continue?", + "请输入提示词和选择模型": "Please enter prompt and select model", + "请上传首帧图片": "Please upload first frame image", + "请上传首帧和尾帧图片": "Please upload first and last frame images", //#endregion //#region MJ diff --git a/src/i18n/locales/zh-cn.ts b/src/i18n/locales/zh-cn.ts index 47b5ae8..e69891a 100644 --- a/src/i18n/locales/zh-cn.ts +++ b/src/i18n/locales/zh-cn.ts @@ -1558,10 +1558,10 @@ export default { '7. 需要在外部手动选择需要的{type}数据时,请点击“{button}” 按钮进行导入到标签集中': '7. 需要在外部手动选择需要的{type}数据时,请点击“{button}” 按钮进行导入到标签集中', '即将开始自动推理,该操作会将之前的 {type} 数据覆盖,是否继续?': '即将开始自动推理,该操作会将之前的场景数据覆盖,是否继续?', '正在推理,请稍等...': '正在推理,请稍等...', - "通用前/后缀" : "通用前/后缀", - "提示词前后缀设置" : "提示词前后缀设置", - "通用前缀" : "通用前缀", - "通用后缀" : "通用后缀", + "通用前/后缀": "通用前/后缀", + "提示词前后缀设置": "提示词前后缀设置", + "通用前缀": "通用前缀", + "通用后缀": "通用后缀", //#endregion //#region 转视频 @@ -1656,13 +1656,59 @@ export default { "对应于视频ID的任务ID,通常在选择视频后自动填充": "对应于视频ID的任务ID,通常在选择视频后自动填充", "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?": "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?", "请选择一个已有的视频任务作为延长的基础": "请选择一个已有的视频任务作为延长的基础", - "父任务选择成功,视频ID已更新为: {videoId}" : "父任务选择成功,视频ID已更新为: {videoId}", - "高性能 (std)" : "高性能 (std)", - "高表现 (pro)" : "高表现 (pro)", - "选择Video" : "选择Video", + "父任务选择成功,视频ID已更新为: {videoId}": "父任务选择成功,视频ID已更新为: {videoId}", + "高性能 (std)": "高性能 (std)", + "高表现 (pro)": "高表现 (pro)", + "选择Video": "选择Video", "必须

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间": "必须

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间", "参考图像 - 尾帧控制": "参考图像 - 尾帧控制", '可选

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间': '可选

• 支持格式:.jpg/.jpeg/.png
• 文件大小:不超过10MB
• 分辨率:不小于300*300px
• 宽高比:1:2.5 ~ 2.5:1之间', + "海螺": "海螺", + "海螺首尾帧视频任务完成!": "海螺首尾帧视频任务完成!", + "海螺首尾帧视频任务失败,失败信息:{error}": "海螺首尾帧视频任务失败,失败信息:{error}", + "海螺图转视频任务完成!": '海螺图转视频任务完成!', + '海螺图转视频任务失败,失败信息:{error}': '海螺图转视频任务失败,失败信息:{error}', + "当前分镜数据的海螺视频参数的提示词为空,请检查": "当前分镜数据的海螺视频参数的提示词为空,请检查", + "海螺文生视频任务完成!": '海螺文生视频任务完成!', + "海螺文生视频任务失败,失败信息:{error}": '海螺文生视频任务失败,失败信息:{error}', + "海螺视频任务正在执行中...": "海螺视频任务正在执行中...", + "海螺视频任务已完成!": "海螺视频任务已完成!", + "海螺视频任务失败,失败信息:{error}": "海螺视频任务失败,失败信息:{error}", + "获取海螺视频下载地址失败": "获取海螺视频下载地址失败", + "获取海螺视频下载地址失败,下载地址为空": "获取海螺视频下载地址失败,下载地址为空", + "已成功提交海螺视频任务,任务ID:{taskId}": "已成功提交海螺视频任务,任务ID:{taskId}", + "当前分镜数据的海螺视频参数的模型参数为空,请检查": "当前分镜数据的海螺视频参数的模型参数为空,请检查", + "当前分镜数据的海螺视频参数的模型参数不合法,请检查": "当前分镜数据的海螺视频参数的模型参数不合法,请检查", + "当前分镜数据的海螺视频参数的分辨率参数为空,请检查": "当前分镜数据的海螺视频参数的分辨率参数为空,请检查", + "当前分镜数据的海螺视频参数的时长参数为空,请检查": "当前分镜数据的海螺视频参数的时长参数为空,请检查", + "当前分镜数据的海螺视频参数的分辨率参数不合法,请检查": "当前分镜数据的海螺视频参数的分辨率参数不合法,请检查", + "当前分镜数据的海螺视频参数的时长参数不合法,请检查": "当前分镜数据的海螺视频参数的时长参数不合法,请检查", + "非MiniMax-Hailuo-02不支持启用fast_pretreatment参数,请检查": "非MiniMax-Hailuo-02不支持启用fast_pretreatment参数,请检查", + "当前分镜数据的海螺图生视频参数的首帧图片参数为空,请检查": "当前分镜数据的海螺图生视频参数的首帧图片参数为空,请检查", + "当前分镜数据的海螺首尾帧视频参数的首帧图片参数为空,请检查": "当前分镜数据的海螺首尾帧视频参数的首帧图片参数为空,请检查", + "当前分镜数据的海螺首尾帧视频参数的尾帧图片参数为空,请检查": "当前分镜数据的海螺首尾帧视频参数的尾帧图片参数为空,请检查", + "当前分镜数据的海螺文生视频参数为空或参数校验失败,请检查": "当前分镜数据的海螺文生视频参数为空或参数校验失败,请检查", + "当前分镜数据的海螺图生视频参数为空或参数校验失败,请检查": "当前分镜数据的海螺图生视频参数为空或参数校验失败,请检查", + "当前分镜数据的海螺首尾帧视频参数为空或参数校验失败,请检查": "当前分镜数据的海螺首尾帧视频参数为空或参数校验失败,请检查", + "不支持的海螺视频类型:{type},请检查": "不支持的海螺视频类型:{type},请检查", + "将当前首尾帧视频的基础设置批量应用到所有的分镜中": "将当前首尾帧视频的基础设置批量应用到所有的分镜中", + "首帧图片": "首帧图片", + "尾帧图片": "尾帧图片", + "视频分辨率": "视频分辨率", + "自动优化提示词": "自动优化提示词", + "模型变更后已自动调整相关参数以确保兼容性": "模型变更后已自动调整相关参数以确保兼容性", + "分辨率变更后已自动调整时长以确保兼容性": "分辨率变更后已自动调整时长以确保兼容性", + "已自动设置默认值并调整参数以确保兼容性": "已自动设置默认值并调整参数以确保兼容性", + "时长变更后已自动调整分辨率以确保兼容性": "时长变更后已自动调整分辨率以确保兼容性", + "将当前图生视频的基础设置批量应用到所有的分镜中": "将当前图生视频的基础设置批量应用到所有的分镜中", + "快速预处理": "快速预处理", + "将当前文生视频的基础设置批量应用到所有的分镜中": "将当前文生视频的基础设置批量应用到所有的分镜中", + "首尾帧视频": "首尾帧视频", + "文生视频": "文生视频", + "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称、分辨率、时长、提示词优化等基础设置\n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?": "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称、分辨率、时长、提示词优化等基础设置\n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?", + "请输入提示词和选择模型": "请输入提示词和选择模型", + "请上传首帧图片": "请上传首帧图片", + "请上传首帧和尾帧图片": "请上传首帧和尾帧图片", //#endregion //#region MJ diff --git a/src/main/service/book/subBookHandle/bookBasicHandle.ts b/src/main/service/book/subBookHandle/bookBasicHandle.ts index 0e47654..a949888 100644 --- a/src/main/service/book/subBookHandle/bookBasicHandle.ts +++ b/src/main/service/book/subBookHandle/bookBasicHandle.ts @@ -15,6 +15,7 @@ import { DownloadFile } from '@/define/Tools/common' import { MappingTaskTypeToVideoModel } from '@/define/enum/video' import { BookBackTaskType } from '@/define/enum/bookEnum' import { t } from '@/i18n' +import { PresetRealmService } from '@/define/db/service/presetService' export class BookBasicHandle { bookTaskDetailService!: BookTaskDetailService @@ -22,6 +23,7 @@ export class BookBasicHandle { optionRealmService!: OptionRealmService bookService!: BookService taskListService!: TaskListService + presetRealmService!: PresetRealmService constructor() { // 初始化 @@ -44,6 +46,9 @@ export class BookBasicHandle { if (!this.taskListService) { this.taskListService = await TaskListService.getInstance() } + if (!this.presetRealmService) { + this.presetRealmService = await PresetRealmService.getInstance() + } } /** @@ -151,6 +156,9 @@ export class BookBasicHandle { && !videoUrl.startsWith('https://cdn.midjourney.com') && task.type != BookBackTaskType.KLING_VIDEO && task.type != BookBackTaskType.KLING_VIDEO_EXTEND + && task.type != BookBackTaskType.HAILUO_TEXT_TO_VIDEO + && task.type != BookBackTaskType.HAILUO_IMAGE_TO_VIDEO + && task.type != BookBackTaskType.HAILUO_FIRST_LAST_FRAME ) { // 转存一下视频文件 // 获取当前url的文件名 diff --git a/src/main/service/book/subBookHandle/bookVideoServiceHandle.ts b/src/main/service/book/subBookHandle/bookVideoServiceHandle.ts index 9f7a965..5de0255 100644 --- a/src/main/service/book/subBookHandle/bookVideoServiceHandle.ts +++ b/src/main/service/book/subBookHandle/bookVideoServiceHandle.ts @@ -2,6 +2,9 @@ import { errorMessage, SendReturnMessage, successMessage } from '@/public/genera import { BookBasicHandle } from './bookBasicHandle' import { Book } from '@/define/model/book/book' import { + HailuoDuration, + HailuoModel, + HailuoResolution, ImageToVideoModels, KlingDuration, KlingMode, @@ -239,15 +242,45 @@ export class BookVideoServiceHandle extends BookBasicHandle { index: undefined, motion: MJVideoMotion.High, // 根据 Motion 类型的定义提供默认值 noStorage: false, - notifyHook: undefined, + notifyHook: "", prompt: null, - state: undefined, - taskId: undefined, + state: "", + taskId: "", raw: false, batchSize: MJVideoBatchSize.ONE, videoType: MJVideoType.HD } + let hailuoTextToVideoOptions: BookTaskDetail.HailuoTextToVideoOptions = { + model: HailuoModel.MINIMAX_HAILUO_02, + prompt: '', + prompt_optimizer: true, + fast_pretreatment: false, + duration: HailuoDuration.SIX, + resolution: HailuoResolution.P768 + } + + // 初始化海螺设置 + let hailuoFirstFrameOnlyOptions: BookTaskDetail.HailuoFirstFrameOnlyOptions = { + model: HailuoModel.MINIMAX_HAILUO_02, + prompt: '', + prompt_optimizer: true, + fast_pretreatment: false, + duration: HailuoDuration.SIX, + resolution: HailuoResolution.P768, + first_frame_image: outImage + } + + let hailuoFirstLastFrameOptions: BookTaskDetail.HailuoFirstLastFrameOptions = { + model: HailuoModel.MINIMAX_HAILUO_02, + prompt: '', + first_frame_image: outImage, + last_frame_image: "", + prompt_optimizer: true, + duration: HailuoDuration.SIX, + resolution: HailuoResolution.P768, + } + let videoMessage: BookTaskDetail.VideoMessage = { id: bookTaskDetail.id, msg: '', @@ -264,7 +297,7 @@ export class BookVideoServiceHandle extends BookBasicHandle { model: VideoModel.IMAGE_TO_VIDEO } - return { optionObject, lumaOptions, klingOptions, mjVideoOptions, videoMessage } + return { optionObject, lumaOptions, klingOptions, mjVideoOptions, hailuoTextToVideoOptions, hailuoFirstFrameOnlyOptions, hailuoFirstLastFrameOptions, videoMessage } } catch (error) { throw error } @@ -321,9 +354,18 @@ export class BookVideoServiceHandle extends BookBasicHandle { case BookBackTaskType.KLING_VIDEO: res = await videoHandle.KlingImageToVideo(task) break - case BookBackTaskType.KLING_VIDEO_EXTEND : + case BookBackTaskType.KLING_VIDEO_EXTEND: res = await videoHandle.KlingVideoExtend(task) break + case BookBackTaskType.HAILUO_TEXT_TO_VIDEO: + res = await videoHandle.HailuoTextToVideo(task) + break + case BookBackTaskType.HAILUO_IMAGE_TO_VIDEO: + res = await videoHandle.HailuoImageToVideo(task) + break + case BookBackTaskType.HAILUO_FIRST_LAST_FRAME: + res = await videoHandle.HailuoFirstLastFrameToVideo(task) + break default: throw new Error(t('未知的视频生成方式,请检查')) } diff --git a/src/main/service/mj/mjBasic.ts b/src/main/service/mj/mjBasic.ts index 623a296..5a9c639 100644 --- a/src/main/service/mj/mjBasic.ts +++ b/src/main/service/mj/mjBasic.ts @@ -1,60 +1,16 @@ -import { OptionRealmService } from '@/define/db/service/optionService' import { OptionKeyName } from '@/define/enum/option' import { optionSerialization } from '../option/optionSerialization' import { SettingModal } from '@/define/model/setting' -import { BookTaskDetailService } from '@/define/db/service/book/bookTaskDetailService' -import { BookTaskService } from '@/define/db/service/book/bookTaskService' -import { BookService } from '@/define/db/service/book/bookService' -import { PresetRealmService } from '@/define/db/service/presetService' -import { TaskListService } from '@/define/db/service/book/taskListService' import { t } from '@/i18n' +import { BookBasicHandle } from '../book/subBookHandle/bookBasicHandle' -export class MJBasic { - optionRealmService!: OptionRealmService +export class MJBasic extends BookBasicHandle { mjGeneralSetting?: SettingModal.MJGeneralSettings mjApiSetting?: SettingModal.MJApiSettings mjPackageSetting?: SettingModal.MJPackageSetting mjRemoteSetting?: SettingModal.MJRemoteSetting mjLocalSetting?: SettingModal.MJLocalSetting - bookTaskDetailService!: BookTaskDetailService - bookTaskService!: BookTaskService - bookService!: BookService - presetRealmService!: PresetRealmService - taskListService!: TaskListService - - /** - * 初始化MJBasic类基础服务 - * - * 该方法确保MJBasic类所需的选项服务(optionRealmService,bookTaskDetailService,bookTaskService)已正确实例化。 - * 采用懒加载模式,只在服务未初始化时创建实例,避免资源浪费。 - * 此初始化是所有需要访问配置项的MJ相关操作的前提条件。 - * - * @returns {Promise} 无返回值的Promise对象 - * @throws {Error} 如果OptionRealmService.getInstance()失败可能会抛出错误 - */ - - async InitMJBasic(): Promise { - // 如果 mjServiceHandle 已经初始化,则直接返回 - if (!this.optionRealmService) { - this.optionRealmService = await OptionRealmService.getInstance() - } - if (!this.bookTaskDetailService) { - this.bookTaskDetailService = await BookTaskDetailService.getInstance() - } - if (!this.bookTaskService) { - this.bookTaskService = await BookTaskService.getInstance() - } - if (!this.bookService) { - this.bookService = await BookService.getInstance() - } - if (!this.presetRealmService) { - this.presetRealmService = await PresetRealmService.getInstance() - } - if (!this.taskListService) { - this.taskListService = await TaskListService.getInstance() - } - } /** * 获取Midjourney通用设置 @@ -68,7 +24,7 @@ export class MJBasic { * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 */ async GetMJGeneralSetting(): Promise { - await this.InitMJBasic() + await this.InitBookBasicHandle() let generalSetting = this.optionRealmService.GetOptionByKey( OptionKeyName.Midjourney.GeneralSetting ) @@ -111,7 +67,7 @@ export class MJBasic { * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 */ async GetMJPackageSetting(): Promise { - await this.InitMJBasic() + await this.InitBookBasicHandle() let packageSetting = this.optionRealmService.GetOptionByKey( OptionKeyName.Midjourney.PackageSetting ) @@ -135,7 +91,7 @@ export class MJBasic { * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 */ async GetMjRemoteSetting(): Promise { - await this.InitMJBasic() + await this.InitBookBasicHandle() let remoteSetting = this.optionRealmService.GetOptionByKey( OptionKeyName.Midjourney.RemoteSetting ) @@ -159,7 +115,7 @@ export class MJBasic { * @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误 */ async GetMjLocalSetting(): Promise { - await this.InitMJBasic() + await this.InitBookBasicHandle() let localSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.LocalSetting) this.mjLocalSetting = optionSerialization( localSetting, diff --git a/src/main/service/task/taskManage.ts b/src/main/service/task/taskManage.ts index 0948402..bd42819 100644 --- a/src/main/service/task/taskManage.ts +++ b/src/main/service/task/taskManage.ts @@ -440,6 +440,9 @@ export class TaskManager { case BookBackTaskType.KLING_VIDEO_EXTEND: case BookBackTaskType.MJ_VIDEO: case BookBackTaskType.MJ_VIDEO_EXTEND: + case BookBackTaskType.HAILUO_TEXT_TO_VIDEO: + case BookBackTaskType.HAILUO_IMAGE_TO_VIDEO: + case BookBackTaskType.HAILUO_FIRST_LAST_FRAME: this.AddImageToVideo(task) break diff --git a/src/main/service/video/hailuoVideo.ts b/src/main/service/video/hailuoVideo.ts new file mode 100644 index 0000000..7c8b805 --- /dev/null +++ b/src/main/service/video/hailuoVideo.ts @@ -0,0 +1,886 @@ +import { TaskModal } from "@/define/model/task"; +import { VideoBasicHandle } from "./videoBasic"; +import { t } from "@/i18n"; +import { Book } from "@/define/model/book/book"; +import { BookTaskDetail } from "@/define/model/book/bookTaskDetail"; +import { ValidateJson } from "@/define/Tools/validate"; +import { GetHailuoModelOptions, HailuoModel, IsValidDuratio, IsValidResolution, ToVIdeoType, VideoStatus } from "@/define/enum/video"; +import { cloneDeep, isEmpty } from "lodash"; +import axios from "axios"; +import { SendReturnMessage, successMessage } from "@/public/generalTools"; +import { ResponseMessageType } from "@/define/enum/softwareEnum"; +import { BookBackTaskStatus, BookTaskStatus } from "@/define/enum/bookEnum"; +import { GetImageBase64 } from "@/define/Tools/image"; + +//#region private interface +/** + * 海螺视频选项结果基础接口 + * + * 定义获取海螺视频配置选项时返回的基础数据结构。 + * 包含分镜任务详情和视频消息配置信息。 + */ +interface HaiLuoOptionsResult { + /** 分镜任务详情对象,包含任务的基本信息和状态 */ + bookTaskDetail: Book.SelectBookTaskDetail, + /** 视频消息配置对象,包含视频生成相关的所有配置信息 */ + videoMessage: BookTaskDetail.VideoMessage, +} + +/** + * 海螺文生视频选项结果接口 + * + * 继承自HaiLuoOptionsResult,专门用于文生视频任务的结果返回。 + * 包含解析后的文生视频配置参数。 + */ +interface HaiLuoTextToVideoOptionsResult extends HaiLuoOptionsResult { + /** 解析后的海螺文生视频配置参数对象 */ + hailuoOption: BookTaskDetail.HailuoTextToVideoOptions +} + +/** + * 海螺图生视频选项结果接口 + * + * 继承自HaiLuoOptionsResult,专门用于图生视频任务的结果返回。 + * 包含解析后的图生视频配置参数。 + */ +interface HaiLuoFirstFrameOnlyOptionsResult extends HaiLuoOptionsResult { + /** 解析后的海螺图生视频配置参数对象 */ + hailuoOption: BookTaskDetail.HailuoFirstFrameOnlyOptions +} + +/** + * 海螺首尾帧视频选项结果接口 + * + * 继承自HaiLuoOptionsResult,专门用于首尾帧视频任务的结果返回。 + * 包含解析后的首尾帧视频配置参数。 + */ +interface HaiLuoFirstLastFrameOptionsResult extends HaiLuoOptionsResult { + /** 解析后的海螺首尾帧视频配置参数对象 */ + hailuoOption: BookTaskDetail.HailuoFirstLastFrameOptions +} + +//#endregion + +export class HaiLuoVideoService extends VideoBasicHandle { + /** + * 海螺视频服务构造函数 + * + * 初始化海螺视频服务实例,继承自VideoBasicHandle基类, + * 提供海螺AI视频生成相关的完整功能支持。 + * + * @description + * 该服务支持的功能: + * - 文生视频:根据文本提示词生成视频 + * - 图生视频:根据首帧图片生成视频 + * - 首尾帧视频:根据首帧和尾帧图片生成视频 + * - 任务状态监控和结果获取 + * - 视频文件下载和本地存储 + */ + constructor() { + super(); + } + + //#region 首尾帧 + + /** + * 海螺首尾帧视频任务处理方法 + * + * 该方法负责处理海螺AI首尾帧视频任务的完整流程,基于首帧和尾帧图片生成中间过渡视频。 + * 支持MiniMax-Hailuo-02等模型,可以根据两帧图片自动补间生成视频内容。 + * + * @param task 任务对象,包含任务详情和配置信息 + * + * @description + * 处理流程: + * 1. 初始化基础句柄和API设置 + * 2. 获取和验证海螺首尾帧视频配置参数 + * 3. 检查首帧图片、尾帧图片和其他必要参数 + * 4. 处理图片格式转换(本地图片转base64,网络图片保持URL) + * 5. 构建API请求体并调用海螺视频生成接口 + * 6. 处理任务提交成功的后续流程 + * + * 技术特点: + * - 支持本地图片和网络图片的混合使用 + * - 自动处理图片格式转换和编码 + * - 固定时长为6秒(首尾帧模式限制) + * - 支持提示词优化以提升生成质量 + * + * @throws {Error} 当首帧或尾帧图片为空时抛出异常 + * @throws {Error} 当参数验证失败时抛出异常 + * @throws {Error} 当图片处理失败时抛出异常 + * @throws {Error} 当API调用失败时抛出异常 + * + * @returns 返回成功消息和任务标识 + * + * @example + * ```typescript + * const task = { + * bookTaskDetailId: "task-123", + * messageName: "HAILUO_FIRST_LAST_FRAME_VIDEO_RETURN" + * }; + * await hailuoService.HailuoFirstLastFrameToVideo(task); + * ``` + */ + async HailuoFirstLastFrameToVideo(task: TaskModal.Task) { + try { + + // 初始化基础句柄和API设置 + await this.InitBookBasicHandle(); + await this.InitApiSetting(); + + let { bookTaskDetail, hailuoOption, videoMessage } = await this.GetHailuoOptions(task.bookTaskDetailId as string, 'firstLastFrame'); + + this.CheckHaiLuoParams("firstLastFrame", hailuoOption); + + let model = hailuoOption.model; + let prompt = hailuoOption.prompt; + let first_frame_image = hailuoOption.first_frame_image; + let last_frame_image = hailuoOption.last_frame_image; + let prompt_optimizer = hailuoOption.prompt_optimizer == null ? true : hailuoOption.prompt_optimizer; + let duration = hailuoOption.duration; + let resolution = hailuoOption.resolution; + if (!first_frame_image.startsWith("http")) { + // 不是网络图片,转为base64 + first_frame_image = await GetImageBase64(first_frame_image, false) + } + if (!last_frame_image.startsWith("http")) { + // 不是网络图片,转为base64 + last_frame_image = await GetImageBase64(last_frame_image, false) + } + + let body: BookTaskDetail.HailuoFirstLastFrameOptions = { + model: model, + prompt: prompt, + first_frame_image: first_frame_image, + last_frame_image: last_frame_image, + prompt_optimizer: prompt_optimizer, + duration: duration, + resolution: resolution + } + + let url = this.inferenceSetting.apiProviderItem.base_url + "/v1/video_generation"; + let res = await axios.post(url, body, { + headers: { + 'Authorization': `Bearer ${this.inferenceSetting.apiToken}`, + 'Content-Type': 'application/json' + } + }); + + let resData = res.data; + + // 海螺转视频任务提交成功 + await this.HailuoSubmitSuccess(resData, task, bookTaskDetail, videoMessage) + + return successMessage( + t('海螺首尾帧视频任务完成!'), + 'HaiLuoOptionsResult_HailuoFirstLastFrameToVideo' + ) + } catch (error) { + throw new Error(t('海螺首尾帧视频任务失败,失败信息:{error}', { error: (error as Error).message })); + } + } + + //#endregion + + //#region 图转视频 + + /** + * 海螺图生视频任务处理方法 + * + * 该方法负责处理海螺AI图生视频任务的完整流程,基于首帧图片生成视频内容。 + * 支持MiniMax-Hailuo-02等多种模型,可以根据单张图片和提示词生成动态视频。 + * + * @param task 任务对象,包含任务详情和配置信息 + * + * @description + * 处理流程: + * 1. 初始化基础句柄和API设置 + * 2. 获取和验证海螺图生视频配置参数 + * 3. 检查首帧图片和其他必要参数 + * 4. 处理图片格式转换(本地图片转base64,网络图片保持URL) + * 5. 构建API请求体并调用海螺视频生成接口 + * 6. 处理任务提交成功的后续流程 + * + * 技术特点: + * - 支持多种分辨率:512P、720P、768P、1080P + * - 支持多种时长:6秒、10秒(根据模型和分辨率限制) + * - 支持提示词优化以提升生成质量 + * - 支持快速预处理模式(MiniMax-Hailuo-02模型) + * - 自动处理本地图片和网络图片 + * + * @throws {Error} 当首帧图片为空时抛出异常 + * @throws {Error} 当参数验证失败时抛出异常 + * @throws {Error} 当图片处理失败时抛出异常 + * @throws {Error} 当API调用失败时抛出异常 + * + * @returns 返回成功消息和任务标识 + * + * @example + * ```typescript + * const task = { + * bookTaskDetailId: "task-123", + * messageName: "HAILUO_IMAGE_TO_VIDEO_RETURN" + * }; + * await hailuoService.HailuoImageToVideo(task); + * ``` + */ + async HailuoImageToVideo(task: TaskModal.Task) { + try { + // 初始化基础句柄和API设置 + await this.InitBookBasicHandle(); + await this.InitApiSetting(); + + let { bookTaskDetail, hailuoOption, videoMessage } = await this.GetHailuoOptions(task.bookTaskDetailId as string, 'imageToVideo'); + + this.CheckHaiLuoParams("imageToVideo", hailuoOption); + + let prompt = hailuoOption.prompt; + let prompt_optimizer = hailuoOption.prompt_optimizer == null ? true : hailuoOption.prompt_optimizer; + let fast_pretreatment = hailuoOption.fast_pretreatment == null ? false : hailuoOption.fast_pretreatment; + let duration = hailuoOption.duration; + let model = hailuoOption.model; + let resolution = hailuoOption.resolution; + let first_frame_image = hailuoOption.first_frame_image; + if (!first_frame_image.startsWith("http")) { + // 不是网络图片,转为base64 + first_frame_image = await GetImageBase64(first_frame_image, false) + } + + let body: BookTaskDetail.HailuoFirstFrameOnlyOptions = { + model: model, + prompt: prompt, + prompt_optimizer: prompt_optimizer, + duration: duration, + resolution: resolution, + first_frame_image: first_frame_image + } + if (model == HailuoModel.MINIMAX_HAILUO_02) { + body.fast_pretreatment = fast_pretreatment + } + let url = this.inferenceSetting.apiProviderItem.base_url + "/v1/video_generation"; + let res = await axios.post(url, body, { + headers: { + 'Authorization': `Bearer ${this.inferenceSetting.apiToken}`, + 'Content-Type': 'application/json' + } + }); + + let resData = res.data; + + // 海螺转视频任务提交成功 + await this.HailuoSubmitSuccess(resData, task, bookTaskDetail, videoMessage) + return successMessage( + t('海螺图转视频任务完成!'), + 'HaiLuoOptionsResult_HailuoImageToVideo' + ) + } catch (error) { + throw new Error(t('海螺图转视频任务失败,失败信息:{error}', { error: (error as Error).message })); + } + } + + //#endregion + + + + //#region 文生视频 + /** + * 海螺文生视频任务处理方法 + * + * 该方法负责处理海螺AI文生视频任务的完整流程,包括参数验证、API调用和结果处理。 + * 支持MiniMax-Hailuo-02等模型,可根据文本提示词生成视频内容。 + * + * @param task 任务对象,包含任务详情和配置信息 + * + * @description + * 处理流程: + * 1. 初始化基础句柄和API设置 + * 2. 获取和验证海螺文生视频配置参数 + * 3. 检查提示词和其他必要参数 + * 4. 构建API请求体并调用海螺视频生成接口 + * 5. 处理任务提交成功的后续流程 + * + * @throws {Error} 当提示词为空时抛出异常 + * @throws {Error} 当参数验证失败时抛出异常 + * @throws {Error} 当API调用失败时抛出异常 + * + * @returns 返回成功消息和任务标识 + * + * @example + * ```typescript + * const task = { + * bookTaskDetailId: "task-123", + * messageName: "HAILUO_TEXT_TO_VIDEO_RETURN" + * }; + * await hailuoService.HailuoTextToVideo(task); + * ``` + */ + async HailuoTextToVideo(task: TaskModal.Task) { + try { + // 初始化基础句柄和API设置 + await this.InitBookBasicHandle(); + await this.InitApiSetting(); + + let { bookTaskDetail, hailuoOption, videoMessage } = await this.GetHailuoOptions(task.bookTaskDetailId as string, 'textToVideo'); + + let prompt = hailuoOption.prompt; + if (prompt == null || isEmpty(prompt)) { + throw new Error(t("当前分镜数据的海螺视频参数的提示词为空,请检查")) + } + + // 检查参数 + this.CheckHaiLuoParams("textToVideo", hailuoOption); + + let model = hailuoOption.model; + + let body: BookTaskDetail.HailuoTextToVideoOptions = { + model: model, + prompt: prompt, + prompt_optimizer: hailuoOption.prompt_optimizer == null ? true : hailuoOption.prompt_optimizer, + duration: hailuoOption.duration, + resolution: hailuoOption.resolution, + } + + if (model == HailuoModel.MINIMAX_HAILUO_02) { + body.fast_pretreatment = hailuoOption.fast_pretreatment == null ? false : hailuoOption.fast_pretreatment + } + + let url = this.inferenceSetting.apiProviderItem.base_url + "/v1/video_generation" + let res = await axios.post(url, body, { + headers: { + 'Authorization': `Bearer ${this.inferenceSetting.apiToken}`, + 'Content-Type': 'application/json' + } + }) + let resData = res.data; + + // 海螺转视频任务提交成功 + await this.HailuoSubmitSuccess(resData, task, bookTaskDetail, videoMessage) + + return successMessage( + t('海螺文生视频任务完成!'), + 'HaiLuoOptionsResult_HailuoTextToVideo' + ) + } catch (error) { + throw new Error(t('海螺文生视频任务失败,失败信息:{error}', { error: (error as Error).message })); + } + } + + //#endregion + + //#region fetch task status + + /** + * 获取海螺视频生成任务结果 + * + * 该方法通过轮询方式持续查询海螺视频生成任务的执行状态,直到任务完成或失败。 + * 支持处理任务的不同状态:准备中、排队中、处理中、成功、失败等。 + * + * @param bookTaskDetail 分镜任务详情对象,包含任务基本信息 + * @param task 任务对象,用于消息回传和状态更新 + * @param hailuoId 海螺任务ID,用于查询任务状态 + * @param isTransfer 是否为转换任务(当前未使用,预留参数) + * + * @description + * 执行流程: + * 1. 循环查询任务状态直到完成 + * 2. 根据不同状态更新本地数据库和界面显示 + * 3. 成功时下载生成的视频文件 + * 4. 失败时记录错误信息并更新状态 + * + * 状态处理: + * - Preparing/Queueing/Processing: 更新为处理中状态,等待20秒后重试 + * - Success: 获取视频下载地址,更新状态为成功,下载视频文件 + * - 其他: 视为失败,记录错误信息 + * + * @throws {Error} 当API调用失败时抛出异常 + * @throws {Error} 当视频下载失败时抛出异常 + * + * @example + * ```typescript + * await hailuoService.FecthHailuoToVideoResult( + * bookTaskDetail, + * task, + * "hailuo-task-123", + * false + * ); + * ``` + */ + async FecthHailuoToVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, hailuoId: string, isTransfer: boolean) { + console.log(isTransfer); + while (true) { + let fetchUrl = this.inferenceSetting.apiProviderItem.base_url + "/v1/query/video_generation" + `?task_id=${hailuoId}`; + + let res = await axios.get(fetchUrl, { + headers: { + 'Authorization': `Bearer ${this.inferenceSetting.apiToken}`, + 'Content-Type': 'application/json' + } + }); + let resData = res.data; + + console.log(resData); + + if (resData.status == undefined && resData.base_resp && resData.base_resp.status_code == 0) { + resData.status = "Processing" + } + + // 判断不同的状态 + if (resData.status == "Preparing" || resData.status == "Queueing" || resData.status == "Processing") { + // 任务正在执行中 + let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {} + videoMessage.status = VideoStatus.PROCESSING + videoMessage.taskId = hailuoId + videoMessage.messageData = JSON.stringify(resData) + delete videoMessage.imageUrl + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage( + task.bookTaskDetailId as string, + videoMessage + ) + + SendReturnMessage( + { + code: 1, + id: bookTaskDetail.id as string, + message: t('海螺视频任务正在执行中...'), + type: ResponseMessageType.HAI_LUO_VIDEO, + data: JSON.stringify(videoMessage) + }, + task.messageName as string + ) + + // 没有成功 等待二十秒后继续执行 + await new Promise((resolve) => setTimeout(resolve, 20000)) + } else if (resData.status == "Success") { + // 成功 + let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {} + videoMessage.status = VideoStatus.SUCCESS + videoMessage.taskId = hailuoId + + let fileId = resData.file_id + + // 获取海螺的视频下载地址 + let { videoUrls, fileIds } = await this.GetHailuoVideoUrl(fileId, hailuoId); + + if (videoUrls.length > 0) { + videoMessage.videoUrls = [] + videoUrls.forEach((item: any) => { + videoMessage.videoUrls?.push(item.url || item) + }) + } + videoMessage.messageData = JSON.stringify(resData) + delete videoMessage.imageUrl + + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage( + task.bookTaskDetailId as string, + videoMessage + ) + + // 修改小说分镜状态 + this.bookTaskDetailService.ModifyBookTaskDetailById(task.bookTaskDetailId as string, { + status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS + }) + + // 修改任务状态 + this.taskListService.UpdateBackTaskData(task.id as string, { + status: BookBackTaskStatus.DONE, + taskId: hailuoId, + taskMessage: JSON.stringify(resData) + }) + + + // 下载 视频 + await this.DownloadVideoUrls(videoMessage.videoUrls || [], task, bookTaskDetail, hailuoId, fileIds) + + SendReturnMessage( + { + code: 1, + id: bookTaskDetail.id as string, + message: t('海螺视频任务已完成!'), + type: ResponseMessageType.HAI_LUO_VIDEO, + data: JSON.stringify(videoMessage) + }, + task.messageName as string + ) + break; + } else { + // 失败 + // 修改小说分镜的 videoMessage + let videoMessage = cloneDeep(bookTaskDetail.videoMessage) ?? {} + + videoMessage.status = VideoStatus.FAIL + videoMessage.msg = resData.task_status_msg + videoMessage.taskId = hailuoId + videoMessage.messageData = JSON.stringify(resData) + delete videoMessage.imageUrl + + // 修改 videoMessage数据 + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage( + bookTaskDetail.id as string, + videoMessage + ) + + // 修改TASK + this.taskListService.UpdateBackTaskData(task.id as string, { + taskId: hailuoId, + taskMessage: JSON.stringify(resData) + }) + + // 返回前端数据 + SendReturnMessage( + { + code: 0, + id: bookTaskDetail.id as string, + message: t('海螺视频任务失败,失败信息:{error}', { + error: resData.base_resp.status_msg || t("未知错误") + }), + type: ResponseMessageType.HAI_LUO_VIDEO, + data: JSON.stringify(videoMessage) + }, + task.messageName as string + ) + throw new Error(resData.base_resp.status_msg) + + } + } + + } + + //#endregion + + //#region get video url + + /** + * 获取海螺视频下载地址 + * + * 该方法通过文件ID和任务ID从海螺API获取生成视频的下载地址。 + * 用于在视频生成成功后获取可下载的视频文件链接。 + * + * @param fileId 海螺返回的文件ID,用于标识生成的视频文件 + * @param hailuoId 海螺任务ID,用于验证文件归属 + * + * @returns Promise<{videoUrls: string[], fileIds: string[]}> 返回包含视频下载地址和文件ID的对象 + * - videoUrls: 视频下载地址数组 + * - fileIds: 文件ID数组 + * + * @description + * 执行流程: + * 1. 构建文件检索API的请求URL + * 2. 发送GET请求获取文件信息 + * 3. 检查响应状态码,确保请求成功 + * 4. 提取视频下载地址并返回 + * + * @throws {Error} 当API请求失败时抛出异常 + * @throws {Error} 当状态码非0时抛出异常,包含详细错误信息 + * + * @example + * ```typescript + * const { videoUrls, fileIds } = await hailuoService.GetHailuoVideoUrl( + * "file-123", + * "hailuo-task-456" + * ); + * console.log("视频下载地址:", videoUrls[0]); + * ``` + */ + async GetHailuoVideoUrl(fileId: string, hailuoId: string): Promise<{ videoUrls: string[], fileIds: string[] }> { + let fetchUrl = this.inferenceSetting.apiProviderItem.base_url + `/v1/files/retrieve?file_id=${fileId}&task_id=${hailuoId}`; + + let res = await axios.get(fetchUrl, { + headers: { + 'Authorization': `Bearer ${this.inferenceSetting.apiToken}`, + 'Content-Type': 'application/json' + } + }); + let resData = res.data; + let statusCode = resData.base_resp.status_code; + if (statusCode != 0) { + throw new Error(resData.base_resp.status_msg || t("获取海螺视频下载地址失败")); + } + let videoUrl = resData.file.download_url; + if (videoUrl == null || isEmpty(videoUrl)) { + throw new Error(t("获取海螺视频下载地址失败,下载地址为空")); + } + return { + videoUrls: [videoUrl], + fileIds: [fileId] + } + } + + //#endregion + + //#region submit success + + /** + * 海螺视频任务提交成功后的处理方法 + * + * 该方法在海螺视频任务成功提交到API后执行,负责更新本地数据库状态, + * 记录任务ID和响应信息,并向前端发送任务状态更新消息。 + * + * @param resData API响应数据,包含任务ID和其他相关信息 + * @param task 任务对象,包含任务基本信息和配置 + * @param bookTaskDetail 分镜任务详情对象,用于状态更新 + * @param videoMessage 视频消息对象,用于记录任务状态和数据 + * + * @description + * 执行流程: + * 1. 提取海螺任务ID并更新后端任务表 + * 2. 更新视频消息状态为已提交(SUBMITTED) + * 3. 记录API响应数据用于后续查询 + * 4. 向前端发送任务提交成功的消息通知 + * 5. 启动后台轮询任务以监控生成进度 + * + * 状态管理: + * - 任务状态设为SUBMITTED(已提交) + * - 清空错误信息 + * - 保存完整的API响应数据 + * + * @example + * ```typescript + * const resData = { task_id: "hailuo-123", status: "submitted" }; + * await hailuoService.HailuoSubmitSuccess(resData, task, bookTaskDetail, videoMessage); + * ``` + */ + async HailuoSubmitSuccess(resData: any, task: TaskModal.Task, bookTaskDetail: Book.SelectBookTaskDetail, videoMessage: BookTaskDetail.VideoMessage) { + // 修改Task, 将数据写入 + let hailuoId = resData.task_id + + this.taskListService.UpdateBackTaskData(task.id as string, { + taskId: hailuoId as string, + taskMessage: JSON.stringify(resData) + }) + + // 修改videoMessage + videoMessage.taskId = hailuoId + videoMessage.status = VideoStatus.SUBMITTED + videoMessage.messageData = JSON.stringify(resData) + videoMessage.msg = '' + delete videoMessage.imageUrl // 不要修改原本的图片地址 + + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage( + task.bookTaskDetailId as string, + videoMessage + ) + + // 添加任务成功 返回前端任务事件 + SendReturnMessage( + { + code: 1, + id: task.bookTaskDetailId as string, + message: t("已成功提交海螺视频任务,任务ID:{taskId}", { taskId: hailuoId }), + type: ResponseMessageType.HAI_LUO_VIDEO, + data: JSON.stringify(videoMessage) + }, + task.messageName as string + ) + await this.FecthHailuoToVideoResult(bookTaskDetail, task, hailuoId, false) + } + + //#endregion + + //#region hailuo check params + + /** + * 海螺视频参数验证方法 + * + * 该方法对海螺视频生成的各种参数进行全面验证,确保参数符合API要求和模型限制。 + * 支持文生视频、图生视频、首尾帧视频三种类型的参数验证。 + * + * @param type 视频生成类型 - 'textToVideo' (文生视频), 'imageToVideo' (图生视频), 'firstLastFrame' (首尾帧) + * @param hailuoOption 海螺视频配置参数对象,包含模型、分辨率、时长等设置 + * + * @returns {boolean} 验证通过返回true + * + * @description + * 验证项目: + * 1. 基础参数验证:模型名称、分辨率、时长不能为空 + * 2. 参数合法性验证:分辨率和时长的组合是否被模型支持 + * 3. 类型特定验证: + * - 文生视频:验证提示词不为空 + * - 图生视频:验证首帧图片不为空 + * - 首尾帧视频:验证首帧和尾帧图片都不为空 + * 4. 模型限制验证:特定模型对首帧图片的要求 + * + * @throws {Error} 当模型参数为空时抛出异常 + * @throws {Error} 当分辨率或时长参数为空时抛出异常 + * @throws {Error} 当分辨率与模型/时长组合不合法时抛出异常 + * @throws {Error} 当时长与模型/分辨率组合不合法时抛出异常 + * @throws {Error} 当必需的图片参数缺失时抛出异常 + * + * @example + * ```typescript + * const options = { + * model: HailuoModel.MINIMAX_HAILUO_02, + * resolution: HailuoResolution.P768, + * duration: HailuoDuration.SIX, + * prompt: "生成一个美丽的风景视频" + * }; + * + * const isValid = hailuoService.CheckHaiLuoParams('textToVideo', options); + * // isValid === true + * ``` + */ + CheckHaiLuoParams(type: ToVIdeoType, hailuoOption: BookTaskDetail.HailuoTextToVideoOptions | BookTaskDetail.HailuoFirstFrameOnlyOptions | BookTaskDetail.HailuoFirstLastFrameOptions): boolean { + + if (hailuoOption.model == null || isEmpty(hailuoOption.model)) { + throw new Error(t("当前分镜数据的海螺视频参数的模型参数为空,请检查")) + } + + // 检验类型 + let models = GetHailuoModelOptions(type); + if (models.some(m => m.value == hailuoOption.model) == false) { + throw new Error(t("当前分镜数据的海螺视频参数的模型参数不合法,请检查")) + } + + if (hailuoOption.resolution == undefined || hailuoOption.resolution == null) { + throw new Error(t("当前分镜数据的海螺视频参数的分辨率参数为空,请检查")) + } + if (hailuoOption.duration == undefined || hailuoOption.duration == null) { + throw new Error(t("当前分镜数据的海螺视频参数的时长参数为空,请检查")) + } + // 检查分辨率和时长 + let checkResolution = IsValidResolution(type, hailuoOption.model, hailuoOption.duration, hailuoOption.resolution); + let checkDuration = IsValidDuratio(type, hailuoOption.model, hailuoOption.resolution, hailuoOption.duration); + + if (!checkResolution) { + throw new Error(t("当前分镜数据的海螺视频参数的分辨率参数不合法,请检查")) + } + if (!checkDuration) { + throw new Error(t("当前分镜数据的海螺视频参数的时长参数不合法,请检查")) + } + + if (type == "textToVideo" || type == "imageToVideo") { + if (hailuoOption.model != HailuoModel.MINIMAX_HAILUO_02 && hailuoOption.fast_pretreatment == true) { + throw new Error(t("非MiniMax-Hailuo-02不支持启用fast_pretreatment参数,请检查")) + } + } + + if (type == 'imageToVideo') { + let first_frame_image = (hailuoOption as BookTaskDetail.HailuoFirstFrameOnlyOptions).first_frame_image; + if (first_frame_image == null || isEmpty(first_frame_image)) { + throw new Error(t("当前分镜数据的海螺图生视频参数的首帧图片参数为空,请检查")) + } + } + + if (type == 'firstLastFrame') { + let first_frame_image = (hailuoOption as BookTaskDetail.HailuoFirstLastFrameOptions).first_frame_image; + if (first_frame_image == null || isEmpty(first_frame_image)) { + throw new Error(t("当前分镜数据的海螺首尾帧视频参数的首帧图片参数为空,请检查")) + } + + let last_frame_image = (hailuoOption as BookTaskDetail.HailuoFirstLastFrameOptions).last_frame_image; + if (last_frame_image == null || isEmpty(last_frame_image)) { + throw new Error(t("当前分镜数据的海螺首尾帧视频参数的尾帧图片参数为空,请检查")) + } + } + + return true; + } + + //#endregion + + //#region get hailuo options + + /** + * 获取海螺文生视频配置选项 + * @param bookTaskDetailId 分镜任务详情ID + * @param type 视频类型,默认为"textToVideo" + * @returns 返回包含分镜详情、视频消息和海螺文生视频配置的结果对象 + */ + async GetHailuoOptions(bookTaskDetailId: string, type: "textToVideo"): Promise + + /** + * 获取海螺图生视频配置选项 + * @param bookTaskDetailId 分镜任务详情ID + * @param type 视频类型,必须为"imageToVideo" + * @returns 返回包含分镜详情、视频消息和海螺图生视频配置的结果对象 + */ + async GetHailuoOptions(bookTaskDetailId: string, type: "imageToVideo"): Promise + + /** + * 获取海螺首尾帧视频配置选项 + * @param bookTaskDetailId 分镜任务详情ID + * @param type 视频类型,必须为"firstLastFrame" + * @returns 返回包含分镜详情、视频消息和海螺首尾帧视频配置的结果对象 + */ + async GetHailuoOptions(bookTaskDetailId: string, type: "firstLastFrame"): Promise + + /** + * 获取海螺视频配置选项的通用实现方法 + * 根据传入的类型参数,获取对应的海螺视频配置信息 + * + * @param bookTaskDetailId 分镜任务详情ID - 用于查询对应的分镜数据 + * @param type 视频生成类型 - 支持以下类型: + * - "textToVideo": 文生视频,根据文本提示词生成视频 + * - "imageToVideo": 图生视频,根据首帧图片生成视频 + * - "firstLastFrame": 首尾帧视频,根据首帧和尾帧图片生成视频 + * + * @returns 返回包含以下信息的结果对象: + * - bookTaskDetail: 分镜任务详情数据 + * - videoMessage: 视频消息配置信息 + * - hailuoOption: 解析后的海螺视频配置参数 + * + * @throws {Error} 当分镜数据为空时抛出异常 + * @throws {Error} 当对应类型的配置参数为空或JSON格式错误时抛出异常 + * @throws {Error} 当传入不支持的视频类型时抛出异常 + * + * @example + * ```typescript + * // 获取文生视频配置 + * const textOptions = await hailuoService.GetHailuoOptions(taskId, "textToVideo"); + * + * // 获取图生视频配置 + * const imageOptions = await hailuoService.GetHailuoOptions(taskId, "imageToVideo"); + * + * // 获取首尾帧视频配置 + * const frameOptions = await hailuoService.GetHailuoOptions(taskId, "firstLastFrame"); + * ``` + */ + async GetHailuoOptions(bookTaskDetailId: string, type: ToVIdeoType = "textToVideo"): Promise { + // 1. 根据分镜任务详情ID查询分镜数据,包含关联的视频消息配置 + let bookTaskDetail = await this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId, true); + + // 2. 验证视频配置信息是否存在 + let videoMessage = bookTaskDetail.videoMessage + if (videoMessage == null || videoMessage == undefined) { + throw new Error(t("小说批次任务的分镜数据的转视频配置为空,请检查")) + } + + // 3. 根据视频类型获取对应的配置字段名和错误提示信息 + let hailuoOptionsString: string + let errorMessage: string + + switch (type) { + case "textToVideo": + // 文生视频:获取文本转视频的配置参数 + hailuoOptionsString = bookTaskDetail.videoMessage?.hailuoTextToVideoOptions as string + errorMessage = t("当前分镜数据的海螺文生视频参数为空或参数校验失败,请检查") + break + case "imageToVideo": + // 图生视频:获取图片转视频的配置参数(首帧图片) + hailuoOptionsString = bookTaskDetail.videoMessage?.hailuoFirstFrameOnlyOptions as string + errorMessage = t("当前分镜数据的海螺图生视频参数为空或参数校验失败,请检查") + break + case "firstLastFrame": + // 首尾帧视频:获取首尾帧图片转视频的配置参数 + hailuoOptionsString = bookTaskDetail.videoMessage?.hailuoFirstLastFrameOptions as string + errorMessage = t("当前分镜数据的海螺首尾帧视频参数为空或参数校验失败,请检查") + break + default: + // 不支持的视频类型,抛出异常 + throw new Error(t("不支持的海螺视频类型:{type}", { type })) + } + + // 4. 验证配置参数是否为有效的JSON格式 + if (!ValidateJson(hailuoOptionsString)) { + throw new Error(errorMessage) + } + + // 5. 解析JSON配置参数为对象 + let hailuoOptions = JSON.parse(hailuoOptionsString) + + // 6. 返回包含分镜详情、视频消息和海螺配置的完整结果对象 + return { bookTaskDetail: bookTaskDetail, hailuoOption: hailuoOptions, videoMessage: videoMessage } + } + + //#endregion + +} \ No newline at end of file diff --git a/src/main/service/video/index.ts b/src/main/service/video/index.ts index 666f9c5..248a5ab 100644 --- a/src/main/service/video/index.ts +++ b/src/main/service/video/index.ts @@ -1,14 +1,17 @@ import { TaskModal } from '@/define/model/task' import { MJVideoService } from './mjVideo' import { KlingVideoService } from './klingVideo' +import { HaiLuoVideoService } from './hailuoVideo' export class VideoHandle { mjVideoService: MJVideoService klingVideoService: KlingVideoService + hailuoVideoService: HaiLuoVideoService // 这里可以添加 VideoHandle 特有的方法 constructor() { // mixin 装饰器会处理初始化 this.mjVideoService = new MJVideoService() this.klingVideoService = new KlingVideoService() + this.hailuoVideoService = new HaiLuoVideoService() } /** MJ图片转视频处理方法 将指定的图片通过Midjourney API转换为视频 */ @@ -26,7 +29,20 @@ export class VideoHandle { return this.klingVideoService.KlingImageToVideo(task) } + /** 可灵视频延长服务 */ KlingVideoExtend(task: TaskModal.Task) { return this.klingVideoService.KlingVideoExtend(task) } + + HailuoTextToVideo(task: TaskModal.Task) { + return this.hailuoVideoService.HailuoTextToVideo(task) + } + + HailuoImageToVideo(task: TaskModal.Task) { + return this.hailuoVideoService.HailuoImageToVideo(task) + } + + HailuoFirstLastFrameToVideo(task: TaskModal.Task) { + return this.hailuoVideoService.HailuoFirstLastFrameToVideo(task) + } } diff --git a/src/main/service/video/klingVideo.ts b/src/main/service/video/klingVideo.ts index ce5b9d0..b8fbc71 100644 --- a/src/main/service/video/klingVideo.ts +++ b/src/main/service/video/klingVideo.ts @@ -1,9 +1,6 @@ import { TaskModal } from "@/define/model/task"; -import { BookBasicHandle } from "../book/subBookHandle/bookBasicHandle"; -import { getInferenceSetting } from "../option/optionCommonService"; import { t } from "@/i18n"; import { cloneDeep, isEmpty } from "lodash"; -import { SettingModal } from "@/define/model/setting"; import { ValidateJson } from "@/define/Tools/validate"; import { BookTaskDetail } from "@/define/model/book/bookTaskDetail"; import { KlingDuration, KlingMode, KlingModelName, VideoStatus } from "@/define/enum/video"; @@ -15,28 +12,13 @@ import { ResponseMessageType } from "@/define/enum/softwareEnum"; import { GeneralResponse } from "@/define/model/generalResponse"; import { Book } from "@/define/model/book/book"; import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus } from "@/define/enum/bookEnum"; +import { VideoBasicHandle } from "./videoBasic"; -export class KlingVideoService extends BookBasicHandle { - inferenceSetting!: SettingModal.InferenceAISettingAndProvider +export class KlingVideoService extends VideoBasicHandle { constructor() { super(); } - private async InitApiSetting() { - // 加载推理设置中的数据 - const inferenceSetting = await getInferenceSetting(); - this.inferenceSetting = inferenceSetting; - // 判断一些数据是不是存在 - if (isEmpty(this.inferenceSetting.apiProviderItem.base_url)) { - throw new Error(t('未找到有效的API地址')); - } - - if (this.inferenceSetting.apiToken == null || isEmpty(this.inferenceSetting.apiToken)) { - throw new Error(t('请先配置AI推理的API密钥')); - } - - } - //#region KlingImageToVideo /** * 可灵图转视频服务 diff --git a/src/main/service/video/mjVideo.ts b/src/main/service/video/mjVideo.ts index d6d43a3..ab74fba 100644 --- a/src/main/service/video/mjVideo.ts +++ b/src/main/service/video/mjVideo.ts @@ -39,7 +39,7 @@ export class MJVideoService extends MJApiService { */ async MJImageToVideo(task: TaskModal.Task) { try { - await this.InitMJBasic() + await this.InitBookBasicHandle() // 加载设置 await this.InitMJSetting(ImageGenerateMode.MJ_API) @@ -177,7 +177,7 @@ export class MJVideoService extends MJApiService { async MJVideoExtendToVideo(task: TaskModal.Task) { try { - await this.InitMJBasic() + await this.InitBookBasicHandle() await this.InitMJSetting(ImageGenerateMode.MJ_API) // 检查是否支持视频功能 diff --git a/src/main/service/video/videoBasic.ts b/src/main/service/video/videoBasic.ts new file mode 100644 index 0000000..66ae5d4 --- /dev/null +++ b/src/main/service/video/videoBasic.ts @@ -0,0 +1,110 @@ +import { SettingModal } from "@/define/model/setting"; +import { BookBasicHandle } from "../book/subBookHandle/bookBasicHandle"; +import { getInferenceSetting } from "../option/optionCommonService"; +import { isEmpty } from "lodash"; +import { t } from "@/i18n"; + +/** + * 视频处理基础句柄类 + * + * 继承自BookBasicHandle,提供视频相关服务的基础功能和API配置管理。 + * 封装了AI推理设置的初始化和验证逻辑,为各种视频处理服务提供统一的基础设施。 + * + * @extends BookBasicHandle - 继承书籍基础处理功能 + * + * @description + * 核心功能: + * - AI推理API配置的加载和验证 + * - 统一的错误处理和配置检查 + * - 为子类提供通用的API设置基础 + * - 支持多种AI服务提供商的配置管理 + * + * 设计理念: + * - 提供视频服务的通用基础设施 + * - 统一管理API配置和验证逻辑 + * - 确保所有视频服务具有一致的初始化流程 + * - 支持扩展不同类型的视频处理服务 + * + * @note + * - 这是一个基础类,通常被具体的视频服务类继承使用 + * - 提供了inferenceSetting属性用于存储AI推理配置 + * - 所有子类都应该在操作前调用InitApiSetting()方法 + * - 支持可灵、Midjourney等多种视频生成服务 + * + * @example + * ```typescript + * // 通常作为基类被继承使用 + * class KlingVideoService extends VideoBasicHandle { + * async processVideo() { + * await this.InitBookBasicHandle(); + * await this.InitApiSetting(); // 使用基类的API初始化 + * // 具体的视频处理逻辑... + * } + * } + * + * // 或者直接使用 + * const videoHandler = new VideoBasicHandle(); + * await videoHandler.InitApiSetting(); + * ``` + * + * @see BookBasicHandle - 父类提供的基础功能 + * @see SettingModal.InferenceAISettingAndProvider - AI推理配置数据结构 + * @see getInferenceSetting - 获取推理设置的方法 + */ +export class VideoBasicHandle extends BookBasicHandle { + inferenceSetting!: SettingModal.InferenceAISettingAndProvider; + constructor() { + super() + } + + /** + * 初始化API设置 + * + * 加载并验证AI推理相关的API配置设置,确保视频服务所需的基础配置完整有效。 + * 包括获取API提供商信息、验证API地址和密钥的有效性,为后续的视频处理操作做好准备。 + * + * @throws {Error} 当API地址为空或无效时抛出错误 + * @throws {Error} 当API密钥未配置或为空时抛出错误 + * + * @description + * 初始化流程: + * 1. 通过getInferenceSetting()获取完整的推理设置配置 + * 2. 将配置赋值到实例的inferenceSetting属性 + * 3. 验证API提供商的base_url是否存在且有效 + * 4. 验证API访问令牌(apiToken)是否已配置 + * 5. 如果任何必需配置缺失,抛出相应的错误信息 + * + * @note + * - 这个方法是所有视频处理服务的前置依赖 + * - 必须在调用任何视频API之前执行 + * - 配置验证失败会阻止后续的API调用 + * - 支持多种API提供商的统一配置管理 + * + * @example + * ```typescript + * const videoService = new VideoBasicHandle(); + * try { + * await videoService.InitApiSetting(); + * console.log('API设置初始化成功'); + * } catch (error) { + * console.error('API配置错误:', error.message); + * } + * ``` + * + * @see getInferenceSetting - 获取推理设置的具体方法 + * @see SettingModal.InferenceAISettingAndProvider - 配置数据结构定义 + */ + async InitApiSetting() { + // 加载推理设置中的数据 + const inferenceSetting = await getInferenceSetting(); + this.inferenceSetting = inferenceSetting; + // 判断一些数据是不是存在 + if (isEmpty(this.inferenceSetting.apiProviderItem.base_url)) { + throw new Error(t('未找到有效的API地址')); + } + + if (this.inferenceSetting.apiToken == null || isEmpty(this.inferenceSetting.apiToken)) { + throw new Error(t('请先配置AI推理的API密钥')); + } + } +} \ No newline at end of file diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts index cecb7a3..227374e 100644 --- a/src/renderer/components.d.ts +++ b/src/renderer/components.d.ts @@ -44,6 +44,9 @@ declare module 'vue' { EditWord: typeof import('./src/components/Original/Copywriter/EditWord.vue')['default'] FindReplaceRound: typeof import('./src/components/common/Icon/FindReplaceRound.vue')['default'] GeneralSettings: typeof import('./src/components/Setting/GeneralSettings.vue')['default'] + HailuoFirstLastFrameInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoFirstLastFrameInfo.vue')['default'] + HailuoImageToVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoImageToVideoInfo.vue')['default'] + HailuoTextToVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoTextToVideoInfo.vue')['default'] HandGroup: typeof import('./src/components/Original/Copywriter/HandGroup.vue')['default'] ImageCompressHome: typeof import('./src/components/ToolBox/ImageCompress/ImageCompressHome.vue')['default'] ImageDisplay: typeof import('./src/components/ToolBox/ImageUpload/ImageDisplay.vue')['default'] @@ -60,6 +63,7 @@ declare module 'vue' { MediaToVideoInfoBasicInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoBasicInfo.vue')['default'] MediaToVideoInfoConfig: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue')['default'] MediaToVideoInfoEmptyState: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoEmptyState.vue')['default'] + MediaToVideoInfoHaiLuoVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue')['default'] MediaToVideoInfoHome: typeof import('./src/components/MediaToVideo/MediaToVideoInfoHome.vue')['default'] MediaToVideoInfoKlingVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoKling/MediaToVideoInfoKlingVideoInfo.vue')['default'] MediaToVideoInfoMJVideoExtend: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoExtend.vue')['default'] diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue index d6b1b0a..249d056 100644 --- a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue @@ -137,6 +137,11 @@ + +
+ +
+
diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoFirstLastFrameInfo.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoFirstLastFrameInfo.vue new file mode 100644 index 0000000..6a1ae15 --- /dev/null +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoFirstLastFrameInfo.vue @@ -0,0 +1,522 @@ + + + + + diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoImageToVideoInfo.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoImageToVideoInfo.vue new file mode 100644 index 0000000..79df319 --- /dev/null +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoImageToVideoInfo.vue @@ -0,0 +1,460 @@ + + + + + diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoTextToVideoInfo.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoTextToVideoInfo.vue new file mode 100644 index 0000000..c7acf1d --- /dev/null +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/HailuoTextToVideoInfo.vue @@ -0,0 +1,419 @@ + + + + + diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue new file mode 100644 index 0000000..f5c7b61 --- /dev/null +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue @@ -0,0 +1,387 @@ + + + + + diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/README.md b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/README.md new file mode 100644 index 0000000..376248f --- /dev/null +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoHaiLuo/README.md @@ -0,0 +1,185 @@ +# 海螺视频合成组件 + +## 📋 概述 + +基于海螺AI的视频生成组件,支持三种不同的视频生成模式: +- **文生视频**:纯文本描述生成视频 +- **图生视频**:基于首帧图片生成视频 +- **首尾帧视频**:指定首帧和尾帧生成过渡视频 + +## 🏗️ 组件架构 + +``` +MediaToVideoInfoHaiLuoVideoInfo.vue (主容器) +├── HailuoTextToVideoInfo.vue (文生视频) +├── HailuoImageToVideoInfo.vue (图生视频) +└── HailuoFirstLastFrameInfo.vue (首尾帧视频) +``` + +## 📊 数据结构分析 + +### VideoMessage 数据结构 +```typescript +type VideoMessage = { + // ... 其他字段 + hailuoTextToVideoOptions?: string // 文生视频配置JSON + hailuoFirstFrameOnlyOptions?: string // 图生视频配置JSON + hailuoFirstLastFrameOptions?: string // 首尾帧配置JSON + imageUrl?: string // 图片URL(兼容字段) +} +``` + +### 三种配置类型 + +#### 1. 文生视频配置 (HailuoTextToVideoOptions) +```typescript +interface HailuoTextToVideoOptions extends HailuoBaseOptions { + fast_pretreatment?: boolean // 快速预处理 + // 可选的首帧图片 +} +``` + +#### 2. 图生视频配置 (HailuoFirstFrameOnlyOptions) +```typescript +interface HailuoFirstFrameOnlyOptions extends HailuoBaseOptions { + first_frame_image: string // 必填的首帧图片 + fast_pretreatment?: boolean // 快速预处理 +} +``` + +#### 3. 首尾帧配置 (HailuoFirstLastFrameOptions) +```typescript +interface HailuoFirstLastFrameOptions extends HailuoFirstFrameOnlyOptions { + last_frame_image: string // 必填的尾帧图片 + duration: HailuoDuration // 根据API限制的固定时长 +} +``` + +### 基础配置 (HailuoBaseOptions) +```typescript +interface HailuoBaseOptions { + model: HailuoModel // 模型名称 + prompt?: string // 提示词 (最大2000字符) + prompt_optimizer?: boolean // 自动优化提示词 + duration?: HailuoDuration // 视频时长 + resolution?: HailuoResolution // 视频分辨率 +} +``` + +## 🎯 核心功能 + +### 1. 智能配置验证 +- 根据模型类型自动验证首帧图片是否必填 +- 动态调整支持的分辨率和时长选项 +- 运镜指令语法支持检查 + +### 2. 响应式数据绑定 +- 三种配置类型独立存储和管理 +- 实时保存到数据库 +- 支持批量应用设置到所有分镜 + +### 3. 用户交互优化 +- Tab切换不丢失数据 +- 智能提示和错误处理 +- 配置项动态显示/隐藏 + +## 🔧 使用方法 + +### 在父组件中使用 +```vue + + + +``` + +### 数据更新流程 +1. 用户在UI中修改配置 +2. 触发 `handleConfigChange` 事件 +3. 调用 `handleHailuoOptionsUpdate` 更新数据库 +4. 同步更新本地数据状态 +5. 重新计算响应式数据 + +## 🎨 配置选项详解 + +### 模型选择 (HailuoModel) +- `MiniMax-Hailuo-02`: 支持运镜指令和快速预处理 +- `I2V-01-Director`: 支持运镜指令,必须提供首帧 +- `I2V-01-live`: 必须提供首帧 +- `I2V-01`: 必须提供首帧 + +### 运镜指令支持 +支持15种运镜指令语法: +- 左右移: `[左移]`, `[右移]` +- 左右摇: `[左摇]`, `[右摇]` +- 推拉: `[推进]`, `[拉远]` +- 升降: `[上升]`, `[下降]` +- 上下摇: `[上摇]`, `[下摇]` +- 变焦: `[变焦推近]`, `[变焦拉远]` +- 其他: `[晃动]`, `[跟随]`, `[固定]` + +### 分辨率和时长限制 +根据模型不同有以下限制: + +**MiniMax-Hailuo-02:** +- 6秒: 支持 512P, 768P, 1080P +- 10秒: 支持 512P, 768P + +**其他模型:** +- 6秒: 支持 720P +- 10秒: 不支持 + +## 🚀 任务执行 + +### 任务类型映射 +- 文生视频 → `BookBackTaskType.HAILUO_VIDEO` +- 图生视频 → `BookBackTaskType.HAILUO_VIDEO` +- 首尾帧视频 → `BookBackTaskType.HAILUO_VIDEO` + +### 消息名称 +- 文生视频: `HAILUO_TEXT_TO_VIDEO_RETURN` +- 图生视频: `HAILUO_IMAGE_TO_VIDEO_RETURN` +- 首尾帧视频: `HAILUO_FIRST_LAST_FRAME_VIDEO_RETURN` + +## 🛠️ 扩展性 + +### 添加新的配置项 +1. 在 `HailuoBaseOptions` 或对应接口中添加字段 +2. 在对应的 `*Options` 计算属性中添加配置项 +3. 更新 `handleConfigChange` 处理逻辑 + +### 支持新的模型 +1. 在 `HailuoModel` 枚举中添加新模型 +2. 更新相关的工具函数 (`IsHailuoModelRequireFirstFrame` 等) +3. 在配置选项中添加对应的显示/隐藏逻辑 + +## 📝 注意事项 + +1. **图片格式要求**: JPG、JPEG、PNG、WebP,小于20MB +2. **提示词长度**: 最大2000字符 +3. **首尾帧限制**: 根据API文档可能有特殊的时长限制 +4. **数据同步**: 确保配置变更及时保存到数据库 +5. **类型安全**: 利用TypeScript接口确保数据结构正确 + +## 🔍 调试建议 + +1. 检查控制台日志中的配置变更信息 +2. 验证数据库更新是否成功 +3. 确认模型和参数的兼容性 +4. 测试Tab切换时的数据保持 + +这个组件架构提供了完整的海螺视频生成功能,具有良好的扩展性和维护性。 \ No newline at end of file diff --git a/src/renderer/src/components/MediaToVideo/MediaToVideoInfoHome.vue b/src/renderer/src/components/MediaToVideo/MediaToVideoInfoHome.vue index 6a5939a..07a6148 100644 --- a/src/renderer/src/components/MediaToVideo/MediaToVideoInfoHome.vue +++ b/src/renderer/src/components/MediaToVideo/MediaToVideoInfoHome.vue @@ -85,6 +85,7 @@ import { useBookStore, useSoftwareStore } from '@/renderer/src/stores' import { ResponseMessageType } from '@/define/enum/softwareEnum' import { VideoStatus } from '@/define/enum/video' import { DEFINE_STRING } from '@/define/ipcDefineString' +import { t } from '@/i18n' const bookStore = useBookStore() const softwareStore = useSoftwareStore() @@ -135,6 +136,7 @@ onUnmounted(() => { // 清理事件监听 window.system.removeEventListen([DEFINE_STRING.BOOK.MJ_VIDEO_TO_VIDEO_RETURN]) window.system.removeEventListen(DEFINE_STRING.BOOK.KLING_IMAGE_TO_VIDEO_RETURN) + window.system.removeEventListen(DEFINE_STRING.BOOK.HAILUO_TO_VIDEO_RETURN) }) // 接收到消息修改的处理小说批次任务信息的逻辑 @@ -148,6 +150,22 @@ function handleMessageChange(videoMessage, id) { } } +// 事件监听 +function handleIpcTaskListChange() { + // 监听SD出图返回的数据 + window.system.setEventListen([DEFINE_STRING.BOOK.MJ_VIDEO_TO_VIDEO_RETURN], (value) => { + handleEventReceive(value) + }) + + window.system.setEventListen(DEFINE_STRING.BOOK.KLING_IMAGE_TO_VIDEO_RETURN, (value) => { + handleEventReceive(value) + }) + + window.system.setEventListen(DEFINE_STRING.BOOK.HAILUO_TO_VIDEO_RETURN, (value) => { + handleEventReceive(value) + }) +} + function handleEventReceive(value) { try { if (value.type == ResponseMessageType.MJ_VIDEO) { @@ -168,6 +186,10 @@ function handleEventReceive(value) { let videoMessage = JSON.parse(value.data) console.log('收到 Kling video extend 视频处理进度', videoMessage) handleMessageChange(videoMessage, value.id) + } else if (value.type == ResponseMessageType.HAI_LUO_VIDEO) { + let videoMessage = JSON.parse(value.data) + console.log('收到 海螺 video extend 视频处理进度', videoMessage) + handleMessageChange(videoMessage, value.id) } else if (value.type == ResponseMessageType.VIDEO_SUCESS) { // 执行返回 返回全部的最新数据 let bookTaskDetail = JSON.parse(value.data) @@ -191,17 +213,6 @@ function handleEventReceive(value) { } } -function handleIpcTaskListChange() { - // 监听SD出图返回的数据 - window.system.setEventListen([DEFINE_STRING.BOOK.MJ_VIDEO_TO_VIDEO_RETURN], (value) => { - handleEventReceive(value) - }) - - window.system.setEventListen(DEFINE_STRING.BOOK.KLING_IMAGE_TO_VIDEO_RETURN, (value) => { - handleEventReceive(value) - }) -} - // 查看详情 function handleViewDetail(row) { selectedTask.value = { ...row }