新增海螺视频
This commit is contained in:
parent
2ce5409ec7
commit
3d5307c8e4
@ -64,6 +64,9 @@ export class VideoMessage extends Realm.Object<VideoMessage> {
|
|||||||
lumaOptions!: string | null // 生成视频的一些设置
|
lumaOptions!: string | null // 生成视频的一些设置
|
||||||
klingOptions!: string | null // 生成视频的一些设置
|
klingOptions!: string | null // 生成视频的一些设置
|
||||||
mjVideoOptions!: string | null // MJ生成视频的一些设置
|
mjVideoOptions!: string | null // MJ生成视频的一些设置
|
||||||
|
hailuoTextToVideoOptions?: string
|
||||||
|
hailuoFirstFrameOnlyOptions?: string
|
||||||
|
hailuoFirstLastFrameOptions?: string
|
||||||
messageData!: string | null
|
messageData!: string | null
|
||||||
static schema: ObjectSchema = {
|
static schema: ObjectSchema = {
|
||||||
name: 'VideoMessage',
|
name: 'VideoMessage',
|
||||||
@ -83,6 +86,9 @@ export class VideoMessage extends Realm.Object<VideoMessage> {
|
|||||||
lumaOptions: 'string?',
|
lumaOptions: 'string?',
|
||||||
klingOptions: 'string?',
|
klingOptions: 'string?',
|
||||||
mjVideoOptions: 'string?',
|
mjVideoOptions: 'string?',
|
||||||
|
hailuoTextToVideoOptions: "string?",
|
||||||
|
hailuoFirstFrameOnlyOptions: "string?",
|
||||||
|
hailuoFirstLastFrameOptions: "string?",
|
||||||
messageData: 'string?'
|
messageData: 'string?'
|
||||||
},
|
},
|
||||||
primaryKey: 'id'
|
primaryKey: 'id'
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export class RealmBaseService extends BaseService {
|
|||||||
PresetModel
|
PresetModel
|
||||||
],
|
],
|
||||||
path: this.dbpath,
|
path: this.dbpath,
|
||||||
schemaVersion: 21, // 数据库版本号,修改时需要增加
|
schemaVersion: 22, // 数据库版本号,修改时需要增加
|
||||||
migration: migration
|
migration: migration
|
||||||
}
|
}
|
||||||
this.realm = await Realm.open(config)
|
this.realm = await Realm.open(config)
|
||||||
|
|||||||
@ -120,7 +120,13 @@ export enum BookBackTaskType {
|
|||||||
// MJ Video
|
// MJ Video
|
||||||
MJ_VIDEO = 'mj_video',
|
MJ_VIDEO = 'mj_video',
|
||||||
// MJ VIDEO EXTEND 视频拓展
|
// 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 {
|
export enum BookBackTaskStatus {
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export enum ResponseMessageType {
|
|||||||
KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND', // Kling生成视频拓展
|
KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND', // Kling生成视频拓展
|
||||||
MJ_VIDEO = 'MJ_VIDEO', // MJ生成视频
|
MJ_VIDEO = 'MJ_VIDEO', // MJ生成视频
|
||||||
MJ_VIDEO_EXTEND = 'MJ_VIDEO_EXTEND', // MJ生成视频拓展
|
MJ_VIDEO_EXTEND = 'MJ_VIDEO_EXTEND', // MJ生成视频拓展
|
||||||
|
HAI_LUO_VIDEO = 'HAI_LUO_VIDEO', // 海螺生成视频
|
||||||
VIDEO_SUCESS = 'VIDEO_SUCESS' //视频生成成功
|
VIDEO_SUCESS = 'VIDEO_SUCESS' //视频生成成功
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { BookBackTaskType } from './bookEnum'
|
import { BookBackTaskType } from './bookEnum'
|
||||||
|
|
||||||
|
export type ToVIdeoType = 'textToVideo' | 'imageToVideo' | "firstLastFrame"
|
||||||
|
|
||||||
/** 图片转视频的方式 */
|
/** 图片转视频的方式 */
|
||||||
export enum ImageToVideoModels {
|
export enum ImageToVideoModels {
|
||||||
/** runway 生成视频 */
|
/** runway 生成视频 */
|
||||||
@ -15,6 +17,8 @@ export enum ImageToVideoModels {
|
|||||||
KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND',
|
KLING_VIDEO_EXTEND = 'KLING_VIDEO_EXTEND',
|
||||||
/** Pika 生成视频 */
|
/** Pika 生成视频 */
|
||||||
PIKA = 'PIKA',
|
PIKA = 'PIKA',
|
||||||
|
/** 海螺生成视频 */
|
||||||
|
HAILUO = 'HAILUO',
|
||||||
/** MJ 图转视频 */
|
/** MJ 图转视频 */
|
||||||
MJ_VIDEO = 'MJ_VIDEO',
|
MJ_VIDEO = 'MJ_VIDEO',
|
||||||
/** MJ 视频拓展 */
|
/** 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) => {
|
export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case BookBackTaskType.LUMA_VIDEO:
|
case BookBackTaskType.LUMA_VIDEO:
|
||||||
return ImageToVideoModels.LUMA
|
return ImageToVideoModels.LUMA
|
||||||
|
case BookBackTaskType.HAILUO_TEXT_TO_VIDEO:
|
||||||
|
return ImageToVideoModels.HAILUO
|
||||||
case BookBackTaskType.RUNWAY_VIDEO:
|
case BookBackTaskType.RUNWAY_VIDEO:
|
||||||
return ImageToVideoModels.RUNWAY
|
return ImageToVideoModels.RUNWAY
|
||||||
case BookBackTaskType.KLING_VIDEO:
|
case BookBackTaskType.KLING_VIDEO:
|
||||||
@ -56,6 +86,8 @@ export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) =
|
|||||||
return t('可灵')
|
return t('可灵')
|
||||||
case ImageToVideoModels.PIKA:
|
case ImageToVideoModels.PIKA:
|
||||||
return 'Pika'
|
return 'Pika'
|
||||||
|
case ImageToVideoModels.HAILUO:
|
||||||
|
return t('海螺')
|
||||||
case ImageToVideoModels.MJ_VIDEO:
|
case ImageToVideoModels.MJ_VIDEO:
|
||||||
case ImageToVideoModels.MJ_VIDEO_EXTEND:
|
case ImageToVideoModels.MJ_VIDEO_EXTEND:
|
||||||
return t('MJ视频')
|
return t('MJ视频')
|
||||||
@ -78,6 +110,10 @@ export const GetImageToVideoModelsOptions = () => {
|
|||||||
{
|
{
|
||||||
label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO),
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO),
|
||||||
value: ImageToVideoModels.MJ_VIDEO
|
value: ImageToVideoModels.MJ_VIDEO
|
||||||
|
}, {
|
||||||
|
|
||||||
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.HAILUO),
|
||||||
|
value: ImageToVideoModels.HAILUO
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY),
|
label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY),
|
||||||
@ -492,3 +528,458 @@ export function GetMJVideoBatchSizeOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#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
|
||||||
|
|||||||
@ -167,6 +167,9 @@ const BOOK = {
|
|||||||
/** Kling 图转视频返回前端数据任务 */
|
/** Kling 图转视频返回前端数据任务 */
|
||||||
KLING_IMAGE_TO_VIDEO_RETURN: 'KLING_IMAGE_TO_VIDEO_RETURN',
|
KLING_IMAGE_TO_VIDEO_RETURN: 'KLING_IMAGE_TO_VIDEO_RETURN',
|
||||||
|
|
||||||
|
/** 海螺图转视频返回前端数据任务 */
|
||||||
|
HAILUO_TO_VIDEO_RETURN: 'HAILUO_TO_VIDEO_RETURN',
|
||||||
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/define/model/book/bookTaskDetail.d.ts
vendored
145
src/define/model/book/bookTaskDetail.d.ts
vendored
@ -5,10 +5,15 @@ import {
|
|||||||
KlingModelName,
|
KlingModelName,
|
||||||
MJVideoBatchSize,
|
MJVideoBatchSize,
|
||||||
MJVideoType,
|
MJVideoType,
|
||||||
|
MJVideoAction,
|
||||||
|
MJVideoMotion,
|
||||||
RunawayModel,
|
RunawayModel,
|
||||||
RunwaySeconds,
|
RunwaySeconds,
|
||||||
VideoModel,
|
VideoModel,
|
||||||
VideoStatus
|
VideoStatus,
|
||||||
|
HailuoModel,
|
||||||
|
HailuoResolution,
|
||||||
|
HailuoDuration
|
||||||
} from '@/define/enum/video'
|
} from '@/define/enum/video'
|
||||||
|
|
||||||
declare namespace BookTaskDetail {
|
declare namespace BookTaskDetail {
|
||||||
@ -31,6 +36,9 @@ declare namespace BookTaskDetail {
|
|||||||
lumaOptions?: string
|
lumaOptions?: string
|
||||||
klingOptions?: string
|
klingOptions?: string
|
||||||
mjVideoOptions?: string
|
mjVideoOptions?: string
|
||||||
|
hailuoTextToVideoOptions?: string
|
||||||
|
hailuoFirstFrameOnlyOptions?: string
|
||||||
|
hailuoFirstLastFrameOptions?: string
|
||||||
messageData?: string
|
messageData?: string
|
||||||
videoUrls?: string[] // 视频地址数组
|
videoUrls?: string[] // 视频地址数组
|
||||||
messageData?: string
|
messageData?: string
|
||||||
@ -191,6 +199,141 @@ declare namespace BookTaskDetail {
|
|||||||
loop?: boolean
|
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
|
//#endregion
|
||||||
|
|
||||||
//#region 小说文案相关
|
//#region 小说文案相关
|
||||||
|
|||||||
@ -1660,9 +1660,55 @@ export default {
|
|||||||
"高性能 (std)": "High Performance (std)",
|
"高性能 (std)": "High Performance (std)",
|
||||||
"高表现 (pro)": "High Performance (pro)",
|
"高表现 (pro)": "High Performance (pro)",
|
||||||
"选择Video": "Select Video",
|
"选择Video": "Select Video",
|
||||||
"<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间": "<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间",
|
"<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间": "<strong>Required</strong><br/><br/>• Supported formats: <strong>.jpg/.jpeg/.png</strong><br/>• File size: <strong>not exceeding 10MB</strong><br/>• Resolution: <strong>not less than 300*300px</strong><br/>• Aspect ratio: <strong>between 1:2.5 ~ 2.5:1</strong>",
|
||||||
"参考图像 - 尾帧控制": "参考图像 - 尾帧控制",
|
"参考图像 - 尾帧控制": "Reference Image - End Frame Control",
|
||||||
'<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间': '<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间',
|
'<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间': '<strong>Optional</strong><br/><br/>• Supported formats: <strong>.jpg/.jpeg/.png</strong><br/>• File size: <strong>not exceeding 10MB</strong><br/>• Resolution: <strong>not less than 300*300px</strong><br/>• Aspect ratio: <strong>between 1:2.5 ~ 2.5:1</strong>',
|
||||||
|
"海螺": "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
|
//#endregion
|
||||||
|
|
||||||
//#region MJ
|
//#region MJ
|
||||||
|
|||||||
@ -1558,10 +1558,10 @@ export default {
|
|||||||
'7. 需要在外部手动选择需要的{type}数据时,请点击<strong>“{button}”</strong> 按钮进行导入到标签集中': '7. 需要在外部手动选择需要的{type}数据时,请点击<strong>“{button}”</strong> 按钮进行导入到标签集中',
|
'7. 需要在外部手动选择需要的{type}数据时,请点击<strong>“{button}”</strong> 按钮进行导入到标签集中': '7. 需要在外部手动选择需要的{type}数据时,请点击<strong>“{button}”</strong> 按钮进行导入到标签集中',
|
||||||
'即将开始自动推理,该操作会将之前的 {type} 数据覆盖,是否继续?': '即将开始自动推理,该操作会将之前的场景数据覆盖,是否继续?',
|
'即将开始自动推理,该操作会将之前的 {type} 数据覆盖,是否继续?': '即将开始自动推理,该操作会将之前的场景数据覆盖,是否继续?',
|
||||||
'正在推理,请稍等...': '正在推理,请稍等...',
|
'正在推理,请稍等...': '正在推理,请稍等...',
|
||||||
"通用前/后缀" : "通用前/后缀",
|
"通用前/后缀": "通用前/后缀",
|
||||||
"提示词前后缀设置" : "提示词前后缀设置",
|
"提示词前后缀设置": "提示词前后缀设置",
|
||||||
"通用前缀" : "通用前缀",
|
"通用前缀": "通用前缀",
|
||||||
"通用后缀" : "通用后缀",
|
"通用后缀": "通用后缀",
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region 转视频
|
//#region 转视频
|
||||||
@ -1656,13 +1656,59 @@ export default {
|
|||||||
"对应于视频ID的任务ID,通常在选择视频后自动填充": "对应于视频ID的任务ID,通常在选择视频后自动填充",
|
"对应于视频ID的任务ID,通常在选择视频后自动填充": "对应于视频ID的任务ID,通常在选择视频后自动填充",
|
||||||
"是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?": "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?",
|
"是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?": "是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称(Model Name),生成模式(Mode),视频时长(Duration),自由度(CFG Scale) \n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?",
|
||||||
"请选择一个已有的视频任务作为延长的基础": "请选择一个已有的视频任务作为延长的基础",
|
"请选择一个已有的视频任务作为延长的基础": "请选择一个已有的视频任务作为延长的基础",
|
||||||
"父任务选择成功,视频ID已更新为: {videoId}" : "父任务选择成功,视频ID已更新为: {videoId}",
|
"父任务选择成功,视频ID已更新为: {videoId}": "父任务选择成功,视频ID已更新为: {videoId}",
|
||||||
"高性能 (std)" : "高性能 (std)",
|
"高性能 (std)": "高性能 (std)",
|
||||||
"高表现 (pro)" : "高表现 (pro)",
|
"高表现 (pro)": "高表现 (pro)",
|
||||||
"选择Video" : "选择Video",
|
"选择Video": "选择Video",
|
||||||
"<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间": "<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间",
|
"<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间": "<strong>必须</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间",
|
||||||
"参考图像 - 尾帧控制": "参考图像 - 尾帧控制",
|
"参考图像 - 尾帧控制": "参考图像 - 尾帧控制",
|
||||||
'<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间': '<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间',
|
'<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间': '<strong>可选</strong><br/><br/>• 支持格式:<strong>.jpg/.jpeg/.png</strong><br/>• 文件大小:<strong>不超过10MB</strong><br/>• 分辨率:<strong>不小于300*300px</strong><br/>• 宽高比:<strong>1:2.5 ~ 2.5:1</strong>之间',
|
||||||
|
"海螺": "海螺",
|
||||||
|
"海螺首尾帧视频任务完成!": "海螺首尾帧视频任务完成!",
|
||||||
|
"海螺首尾帧视频任务失败,失败信息:{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
|
//#endregion
|
||||||
|
|
||||||
//#region MJ
|
//#region MJ
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { DownloadFile } from '@/define/Tools/common'
|
|||||||
import { MappingTaskTypeToVideoModel } from '@/define/enum/video'
|
import { MappingTaskTypeToVideoModel } from '@/define/enum/video'
|
||||||
import { BookBackTaskType } from '@/define/enum/bookEnum'
|
import { BookBackTaskType } from '@/define/enum/bookEnum'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { PresetRealmService } from '@/define/db/service/presetService'
|
||||||
|
|
||||||
export class BookBasicHandle {
|
export class BookBasicHandle {
|
||||||
bookTaskDetailService!: BookTaskDetailService
|
bookTaskDetailService!: BookTaskDetailService
|
||||||
@ -22,6 +23,7 @@ export class BookBasicHandle {
|
|||||||
optionRealmService!: OptionRealmService
|
optionRealmService!: OptionRealmService
|
||||||
bookService!: BookService
|
bookService!: BookService
|
||||||
taskListService!: TaskListService
|
taskListService!: TaskListService
|
||||||
|
presetRealmService!: PresetRealmService
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// 初始化
|
// 初始化
|
||||||
@ -44,6 +46,9 @@ export class BookBasicHandle {
|
|||||||
if (!this.taskListService) {
|
if (!this.taskListService) {
|
||||||
this.taskListService = await TaskListService.getInstance()
|
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')
|
&& !videoUrl.startsWith('https://cdn.midjourney.com')
|
||||||
&& task.type != BookBackTaskType.KLING_VIDEO
|
&& task.type != BookBackTaskType.KLING_VIDEO
|
||||||
&& task.type != BookBackTaskType.KLING_VIDEO_EXTEND
|
&& 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的文件名
|
// 获取当前url的文件名
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import { errorMessage, SendReturnMessage, successMessage } from '@/public/genera
|
|||||||
import { BookBasicHandle } from './bookBasicHandle'
|
import { BookBasicHandle } from './bookBasicHandle'
|
||||||
import { Book } from '@/define/model/book/book'
|
import { Book } from '@/define/model/book/book'
|
||||||
import {
|
import {
|
||||||
|
HailuoDuration,
|
||||||
|
HailuoModel,
|
||||||
|
HailuoResolution,
|
||||||
ImageToVideoModels,
|
ImageToVideoModels,
|
||||||
KlingDuration,
|
KlingDuration,
|
||||||
KlingMode,
|
KlingMode,
|
||||||
@ -239,15 +242,45 @@ export class BookVideoServiceHandle extends BookBasicHandle {
|
|||||||
index: undefined,
|
index: undefined,
|
||||||
motion: MJVideoMotion.High, // 根据 Motion 类型的定义提供默认值
|
motion: MJVideoMotion.High, // 根据 Motion 类型的定义提供默认值
|
||||||
noStorage: false,
|
noStorage: false,
|
||||||
notifyHook: undefined,
|
notifyHook: "",
|
||||||
prompt: null,
|
prompt: null,
|
||||||
state: undefined,
|
state: "",
|
||||||
taskId: undefined,
|
taskId: "",
|
||||||
raw: false,
|
raw: false,
|
||||||
batchSize: MJVideoBatchSize.ONE,
|
batchSize: MJVideoBatchSize.ONE,
|
||||||
videoType: MJVideoType.HD
|
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 = {
|
let videoMessage: BookTaskDetail.VideoMessage = {
|
||||||
id: bookTaskDetail.id,
|
id: bookTaskDetail.id,
|
||||||
msg: '',
|
msg: '',
|
||||||
@ -264,7 +297,7 @@ export class BookVideoServiceHandle extends BookBasicHandle {
|
|||||||
model: VideoModel.IMAGE_TO_VIDEO
|
model: VideoModel.IMAGE_TO_VIDEO
|
||||||
}
|
}
|
||||||
|
|
||||||
return { optionObject, lumaOptions, klingOptions, mjVideoOptions, videoMessage }
|
return { optionObject, lumaOptions, klingOptions, mjVideoOptions, hailuoTextToVideoOptions, hailuoFirstFrameOnlyOptions, hailuoFirstLastFrameOptions, videoMessage }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@ -321,9 +354,18 @@ export class BookVideoServiceHandle extends BookBasicHandle {
|
|||||||
case BookBackTaskType.KLING_VIDEO:
|
case BookBackTaskType.KLING_VIDEO:
|
||||||
res = await videoHandle.KlingImageToVideo(task)
|
res = await videoHandle.KlingImageToVideo(task)
|
||||||
break
|
break
|
||||||
case BookBackTaskType.KLING_VIDEO_EXTEND :
|
case BookBackTaskType.KLING_VIDEO_EXTEND:
|
||||||
res = await videoHandle.KlingVideoExtend(task)
|
res = await videoHandle.KlingVideoExtend(task)
|
||||||
break
|
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:
|
default:
|
||||||
throw new Error(t('未知的视频生成方式,请检查'))
|
throw new Error(t('未知的视频生成方式,请检查'))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,60 +1,16 @@
|
|||||||
import { OptionRealmService } from '@/define/db/service/optionService'
|
|
||||||
import { OptionKeyName } from '@/define/enum/option'
|
import { OptionKeyName } from '@/define/enum/option'
|
||||||
import { optionSerialization } from '../option/optionSerialization'
|
import { optionSerialization } from '../option/optionSerialization'
|
||||||
import { SettingModal } from '@/define/model/setting'
|
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 { t } from '@/i18n'
|
||||||
|
import { BookBasicHandle } from '../book/subBookHandle/bookBasicHandle'
|
||||||
|
|
||||||
export class MJBasic {
|
export class MJBasic extends BookBasicHandle {
|
||||||
optionRealmService!: OptionRealmService
|
|
||||||
mjGeneralSetting?: SettingModal.MJGeneralSettings
|
mjGeneralSetting?: SettingModal.MJGeneralSettings
|
||||||
mjApiSetting?: SettingModal.MJApiSettings
|
mjApiSetting?: SettingModal.MJApiSettings
|
||||||
mjPackageSetting?: SettingModal.MJPackageSetting
|
mjPackageSetting?: SettingModal.MJPackageSetting
|
||||||
mjRemoteSetting?: SettingModal.MJRemoteSetting
|
mjRemoteSetting?: SettingModal.MJRemoteSetting
|
||||||
mjLocalSetting?: SettingModal.MJLocalSetting
|
mjLocalSetting?: SettingModal.MJLocalSetting
|
||||||
|
|
||||||
bookTaskDetailService!: BookTaskDetailService
|
|
||||||
bookTaskService!: BookTaskService
|
|
||||||
bookService!: BookService
|
|
||||||
presetRealmService!: PresetRealmService
|
|
||||||
taskListService!: TaskListService
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化MJBasic类基础服务
|
|
||||||
*
|
|
||||||
* 该方法确保MJBasic类所需的选项服务(optionRealmService,bookTaskDetailService,bookTaskService)已正确实例化。
|
|
||||||
* 采用懒加载模式,只在服务未初始化时创建实例,避免资源浪费。
|
|
||||||
* 此初始化是所有需要访问配置项的MJ相关操作的前提条件。
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} 无返回值的Promise对象
|
|
||||||
* @throws {Error} 如果OptionRealmService.getInstance()失败可能会抛出错误
|
|
||||||
*/
|
|
||||||
|
|
||||||
async InitMJBasic(): Promise<void> {
|
|
||||||
// 如果 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通用设置
|
* 获取Midjourney通用设置
|
||||||
@ -68,7 +24,7 @@ export class MJBasic {
|
|||||||
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
||||||
*/
|
*/
|
||||||
async GetMJGeneralSetting(): Promise<void> {
|
async GetMJGeneralSetting(): Promise<void> {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
let generalSetting = this.optionRealmService.GetOptionByKey(
|
let generalSetting = this.optionRealmService.GetOptionByKey(
|
||||||
OptionKeyName.Midjourney.GeneralSetting
|
OptionKeyName.Midjourney.GeneralSetting
|
||||||
)
|
)
|
||||||
@ -111,7 +67,7 @@ export class MJBasic {
|
|||||||
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
||||||
*/
|
*/
|
||||||
async GetMJPackageSetting(): Promise<void> {
|
async GetMJPackageSetting(): Promise<void> {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
let packageSetting = this.optionRealmService.GetOptionByKey(
|
let packageSetting = this.optionRealmService.GetOptionByKey(
|
||||||
OptionKeyName.Midjourney.PackageSetting
|
OptionKeyName.Midjourney.PackageSetting
|
||||||
)
|
)
|
||||||
@ -135,7 +91,7 @@ export class MJBasic {
|
|||||||
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
||||||
*/
|
*/
|
||||||
async GetMjRemoteSetting(): Promise<void> {
|
async GetMjRemoteSetting(): Promise<void> {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
let remoteSetting = this.optionRealmService.GetOptionByKey(
|
let remoteSetting = this.optionRealmService.GetOptionByKey(
|
||||||
OptionKeyName.Midjourney.RemoteSetting
|
OptionKeyName.Midjourney.RemoteSetting
|
||||||
)
|
)
|
||||||
@ -159,7 +115,7 @@ export class MJBasic {
|
|||||||
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
* @throws {Error} 如果设置不存在或格式不正确,optionSerialization可能会抛出错误
|
||||||
*/
|
*/
|
||||||
async GetMjLocalSetting(): Promise<void> {
|
async GetMjLocalSetting(): Promise<void> {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
let localSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.LocalSetting)
|
let localSetting = this.optionRealmService.GetOptionByKey(OptionKeyName.Midjourney.LocalSetting)
|
||||||
this.mjLocalSetting = optionSerialization<SettingModal.MJLocalSetting>(
|
this.mjLocalSetting = optionSerialization<SettingModal.MJLocalSetting>(
|
||||||
localSetting,
|
localSetting,
|
||||||
|
|||||||
@ -440,6 +440,9 @@ export class TaskManager {
|
|||||||
case BookBackTaskType.KLING_VIDEO_EXTEND:
|
case BookBackTaskType.KLING_VIDEO_EXTEND:
|
||||||
case BookBackTaskType.MJ_VIDEO:
|
case BookBackTaskType.MJ_VIDEO:
|
||||||
case BookBackTaskType.MJ_VIDEO_EXTEND:
|
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)
|
this.AddImageToVideo(task)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
886
src/main/service/video/hailuoVideo.ts
Normal file
886
src/main/service/video/hailuoVideo.ts
Normal file
@ -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<HaiLuoTextToVideoOptionsResult>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取海螺图生视频配置选项
|
||||||
|
* @param bookTaskDetailId 分镜任务详情ID
|
||||||
|
* @param type 视频类型,必须为"imageToVideo"
|
||||||
|
* @returns 返回包含分镜详情、视频消息和海螺图生视频配置的结果对象
|
||||||
|
*/
|
||||||
|
async GetHailuoOptions(bookTaskDetailId: string, type: "imageToVideo"): Promise<HaiLuoFirstFrameOnlyOptionsResult>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取海螺首尾帧视频配置选项
|
||||||
|
* @param bookTaskDetailId 分镜任务详情ID
|
||||||
|
* @param type 视频类型,必须为"firstLastFrame"
|
||||||
|
* @returns 返回包含分镜详情、视频消息和海螺首尾帧视频配置的结果对象
|
||||||
|
*/
|
||||||
|
async GetHailuoOptions(bookTaskDetailId: string, type: "firstLastFrame"): Promise<HaiLuoFirstLastFrameOptionsResult>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取海螺视频配置选项的通用实现方法
|
||||||
|
* 根据传入的类型参数,获取对应的海螺视频配置信息
|
||||||
|
*
|
||||||
|
* @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<HaiLuoTextToVideoOptionsResult | HaiLuoFirstFrameOnlyOptionsResult | HaiLuoFirstLastFrameOptionsResult> {
|
||||||
|
// 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
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,14 +1,17 @@
|
|||||||
import { TaskModal } from '@/define/model/task'
|
import { TaskModal } from '@/define/model/task'
|
||||||
import { MJVideoService } from './mjVideo'
|
import { MJVideoService } from './mjVideo'
|
||||||
import { KlingVideoService } from './klingVideo'
|
import { KlingVideoService } from './klingVideo'
|
||||||
|
import { HaiLuoVideoService } from './hailuoVideo'
|
||||||
export class VideoHandle {
|
export class VideoHandle {
|
||||||
mjVideoService: MJVideoService
|
mjVideoService: MJVideoService
|
||||||
klingVideoService: KlingVideoService
|
klingVideoService: KlingVideoService
|
||||||
|
hailuoVideoService: HaiLuoVideoService
|
||||||
// 这里可以添加 VideoHandle 特有的方法
|
// 这里可以添加 VideoHandle 特有的方法
|
||||||
constructor() {
|
constructor() {
|
||||||
// mixin 装饰器会处理初始化
|
// mixin 装饰器会处理初始化
|
||||||
this.mjVideoService = new MJVideoService()
|
this.mjVideoService = new MJVideoService()
|
||||||
this.klingVideoService = new KlingVideoService()
|
this.klingVideoService = new KlingVideoService()
|
||||||
|
this.hailuoVideoService = new HaiLuoVideoService()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** MJ图片转视频处理方法 将指定的图片通过Midjourney API转换为视频 */
|
/** MJ图片转视频处理方法 将指定的图片通过Midjourney API转换为视频 */
|
||||||
@ -26,7 +29,20 @@ export class VideoHandle {
|
|||||||
return this.klingVideoService.KlingImageToVideo(task)
|
return this.klingVideoService.KlingImageToVideo(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 可灵视频延长服务 */
|
||||||
KlingVideoExtend(task: TaskModal.Task) {
|
KlingVideoExtend(task: TaskModal.Task) {
|
||||||
return this.klingVideoService.KlingVideoExtend(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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { TaskModal } from "@/define/model/task";
|
import { TaskModal } from "@/define/model/task";
|
||||||
import { BookBasicHandle } from "../book/subBookHandle/bookBasicHandle";
|
|
||||||
import { getInferenceSetting } from "../option/optionCommonService";
|
|
||||||
import { t } from "@/i18n";
|
import { t } from "@/i18n";
|
||||||
import { cloneDeep, isEmpty } from "lodash";
|
import { cloneDeep, isEmpty } from "lodash";
|
||||||
import { SettingModal } from "@/define/model/setting";
|
|
||||||
import { ValidateJson } from "@/define/Tools/validate";
|
import { ValidateJson } from "@/define/Tools/validate";
|
||||||
import { BookTaskDetail } from "@/define/model/book/bookTaskDetail";
|
import { BookTaskDetail } from "@/define/model/book/bookTaskDetail";
|
||||||
import { KlingDuration, KlingMode, KlingModelName, VideoStatus } from "@/define/enum/video";
|
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 { GeneralResponse } from "@/define/model/generalResponse";
|
||||||
import { Book } from "@/define/model/book/book";
|
import { Book } from "@/define/model/book/book";
|
||||||
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus } from "@/define/enum/bookEnum";
|
import { BookBackTaskStatus, BookBackTaskType, BookTaskStatus } from "@/define/enum/bookEnum";
|
||||||
|
import { VideoBasicHandle } from "./videoBasic";
|
||||||
|
|
||||||
export class KlingVideoService extends BookBasicHandle {
|
export class KlingVideoService extends VideoBasicHandle {
|
||||||
inferenceSetting!: SettingModal.InferenceAISettingAndProvider
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
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
|
//#region KlingImageToVideo
|
||||||
/**
|
/**
|
||||||
* 可灵图转视频服务
|
* 可灵图转视频服务
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export class MJVideoService extends MJApiService {
|
|||||||
*/
|
*/
|
||||||
async MJImageToVideo(task: TaskModal.Task) {
|
async MJImageToVideo(task: TaskModal.Task) {
|
||||||
try {
|
try {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
|
|
||||||
// 加载设置
|
// 加载设置
|
||||||
await this.InitMJSetting(ImageGenerateMode.MJ_API)
|
await this.InitMJSetting(ImageGenerateMode.MJ_API)
|
||||||
@ -177,7 +177,7 @@ export class MJVideoService extends MJApiService {
|
|||||||
|
|
||||||
async MJVideoExtendToVideo(task: TaskModal.Task) {
|
async MJVideoExtendToVideo(task: TaskModal.Task) {
|
||||||
try {
|
try {
|
||||||
await this.InitMJBasic()
|
await this.InitBookBasicHandle()
|
||||||
await this.InitMJSetting(ImageGenerateMode.MJ_API)
|
await this.InitMJSetting(ImageGenerateMode.MJ_API)
|
||||||
|
|
||||||
// 检查是否支持视频功能
|
// 检查是否支持视频功能
|
||||||
|
|||||||
110
src/main/service/video/videoBasic.ts
Normal file
110
src/main/service/video/videoBasic.ts
Normal file
@ -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密钥'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/renderer/components.d.ts
vendored
4
src/renderer/components.d.ts
vendored
@ -44,6 +44,9 @@ declare module 'vue' {
|
|||||||
EditWord: typeof import('./src/components/Original/Copywriter/EditWord.vue')['default']
|
EditWord: typeof import('./src/components/Original/Copywriter/EditWord.vue')['default']
|
||||||
FindReplaceRound: typeof import('./src/components/common/Icon/FindReplaceRound.vue')['default']
|
FindReplaceRound: typeof import('./src/components/common/Icon/FindReplaceRound.vue')['default']
|
||||||
GeneralSettings: typeof import('./src/components/Setting/GeneralSettings.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']
|
HandGroup: typeof import('./src/components/Original/Copywriter/HandGroup.vue')['default']
|
||||||
ImageCompressHome: typeof import('./src/components/ToolBox/ImageCompress/ImageCompressHome.vue')['default']
|
ImageCompressHome: typeof import('./src/components/ToolBox/ImageCompress/ImageCompressHome.vue')['default']
|
||||||
ImageDisplay: typeof import('./src/components/ToolBox/ImageUpload/ImageDisplay.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']
|
MediaToVideoInfoBasicInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoBasicInfo.vue')['default']
|
||||||
MediaToVideoInfoConfig: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue')['default']
|
MediaToVideoInfoConfig: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoConfig.vue')['default']
|
||||||
MediaToVideoInfoEmptyState: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoEmptyState.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']
|
MediaToVideoInfoHome: typeof import('./src/components/MediaToVideo/MediaToVideoInfoHome.vue')['default']
|
||||||
MediaToVideoInfoKlingVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoKling/MediaToVideoInfoKlingVideoInfo.vue')['default']
|
MediaToVideoInfoKlingVideoInfo: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoKling/MediaToVideoInfoKlingVideoInfo.vue')['default']
|
||||||
MediaToVideoInfoMJVideoExtend: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoExtend.vue')['default']
|
MediaToVideoInfoMJVideoExtend: typeof import('./src/components/MediaToVideo/MediaToVideoInfo/MediaToVideoInfoMJVideo/MediaToVideoInfoMJVideoExtend.vue')['default']
|
||||||
|
|||||||
@ -137,6 +137,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kling 类型 -->
|
||||||
|
<div v-else-if="selectedVideoType === ImageToVideoModels.HAILUO" class="info-content">
|
||||||
|
<MediaToVideoInfoHaiLuoVideoInfo :task="task" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Kling 类型 -->
|
<!-- Kling 类型 -->
|
||||||
<div v-else-if="selectedVideoType === 'KLING'" class="info-content">
|
<div v-else-if="selectedVideoType === 'KLING'" class="info-content">
|
||||||
<MediaToVideoInfoKlingVideoInfo :task="task" />
|
<MediaToVideoInfoKlingVideoInfo :task="task" />
|
||||||
|
|||||||
@ -0,0 +1,522 @@
|
|||||||
|
<template>
|
||||||
|
<n-space vertical :size="20" style="width: 100%">
|
||||||
|
<ConfigOptionGroup
|
||||||
|
v-model:value="videoMessage.hailuoFirstLastFrameOptionsObject"
|
||||||
|
:options="firstLastFrameOptions"
|
||||||
|
@change="handleConfigChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 12px; width: 100%">
|
||||||
|
<TooltipButton
|
||||||
|
:tooltip="t('将当前首尾帧视频的基础设置批量应用到所有的分镜中')"
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="handleBatchSettings"
|
||||||
|
style="width: 100px"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M3,6V8H21V6H3M3,11V13H21V11H3M3,16V18H21V16H3Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('应用设置') }}
|
||||||
|
</TooltipButton>
|
||||||
|
|
||||||
|
<n-button type="primary" size="small" @click="handleFirstLastFrameVideo" style="flex: 1">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('生成视频') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import ConfigOptionGroup from '@/renderer/src/components/common/ConfigOptionGroup.vue'
|
||||||
|
import TooltipButton from '@/renderer/src/components/common/TooltipButton.vue'
|
||||||
|
import {
|
||||||
|
GetHailuoModelOptions,
|
||||||
|
HailuoModel,
|
||||||
|
HailuoResolution,
|
||||||
|
HailuoDuration,
|
||||||
|
GetHailuoModelSupportedDurations,
|
||||||
|
GetHailuoModelSupportedResolutions
|
||||||
|
} from '@/define/enum/video'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
import { useFile } from '@/renderer/src/hooks/useFile'
|
||||||
|
const { UploadImageToLaiTool } = useFile()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
task: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
videoMessage: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update-hailuo-options', 'batch-settings', 'first-last-frame-video'])
|
||||||
|
|
||||||
|
// 验证并修正配置的独立函数
|
||||||
|
const validateAndFixFirstLastFrameOptions = (currentData) => {
|
||||||
|
// 设置默认值
|
||||||
|
const model = currentData.model || HailuoModel.MINIMAX_HAILUO_02
|
||||||
|
const resolution = currentData.resolution || HailuoResolution.P768
|
||||||
|
const duration = currentData.duration || HailuoDuration.SIX
|
||||||
|
|
||||||
|
// 获取支持的时长选项
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
model,
|
||||||
|
resolution,
|
||||||
|
'firstLastFrame'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取支持的分辨率选项
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'firstLastFrame',
|
||||||
|
model,
|
||||||
|
duration
|
||||||
|
)
|
||||||
|
|
||||||
|
let needsUpdate = false
|
||||||
|
const updates = {}
|
||||||
|
|
||||||
|
// 检查并设置默认模型
|
||||||
|
if (!currentData.model) {
|
||||||
|
updates.model = HailuoModel.MINIMAX_HAILUO_02
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并设置默认分辨率
|
||||||
|
if (!currentData.resolution) {
|
||||||
|
updates.resolution = HailuoResolution.P768
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并设置默认时长
|
||||||
|
if (!currentData.duration) {
|
||||||
|
updates.duration = HailuoDuration.SIX
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查时长是否有效
|
||||||
|
if (currentData.duration && !supportedDurations.some((d) => d.value === currentData.duration)) {
|
||||||
|
updates.duration = supportedDurations[0]?.value || HailuoDuration.SIX
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查分辨率是否有效
|
||||||
|
if (
|
||||||
|
currentData.resolution &&
|
||||||
|
!supportedResolutions.some((r) => r.value === currentData.resolution)
|
||||||
|
) {
|
||||||
|
updates.resolution = supportedResolutions[0]?.value || HailuoResolution.P768
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证最终组合的有效性(双重验证)
|
||||||
|
const finalModel = updates.model || currentData.model || HailuoModel.MINIMAX_HAILUO_02
|
||||||
|
const finalResolution = updates.resolution || currentData.resolution || HailuoResolution.P768
|
||||||
|
const finalDuration = updates.duration || currentData.duration || HailuoDuration.SIX
|
||||||
|
|
||||||
|
// 重新获取最终组合的支持选项
|
||||||
|
const finalSupportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
finalModel,
|
||||||
|
finalResolution,
|
||||||
|
'firstLastFrame'
|
||||||
|
)
|
||||||
|
|
||||||
|
const finalSupportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'firstLastFrame',
|
||||||
|
finalModel,
|
||||||
|
finalDuration
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsUpdate,
|
||||||
|
updates,
|
||||||
|
supportedDurations: finalSupportedDurations,
|
||||||
|
supportedResolutions: finalSupportedResolutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首尾帧视频配置选项
|
||||||
|
const firstLastFrameOptions = computed(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoFirstLastFrameOptionsObject || {}
|
||||||
|
|
||||||
|
const { supportedDurations, supportedResolutions } =
|
||||||
|
validateAndFixFirstLastFrameOptions(currentData)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('模型名称'),
|
||||||
|
key: 'model',
|
||||||
|
type: 'select',
|
||||||
|
width: '200px',
|
||||||
|
options: GetHailuoModelOptions('firstLastFrame'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">模型名称:MiniMax-Hailuo-02</div>
|
||||||
|
<div>首尾帧模式专用的视频生成模型</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('首帧图片'),
|
||||||
|
key: 'first_frame_image',
|
||||||
|
type: 'image',
|
||||||
|
fullWidth: true, // 占满整行,换行显示
|
||||||
|
onUpload: handleImageUpload,
|
||||||
|
placeholder: t('上传或输入图片URL,作为视频的起始帧'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">将指定图片作为视频的起始帧</div>
|
||||||
|
<div style="margin-bottom: 12px;">支持公网 URL 或 Base64 编码的 Data URL</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">支持格式:</div>
|
||||||
|
<div>• 格式:JPG、JPEG、PNG、WebP</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">图片要求:</div>
|
||||||
|
<div>• 体积:小于 20MB</div>
|
||||||
|
<div>• 尺寸:短边像素大于 300px</div>
|
||||||
|
<div>• 长宽比在 2:5 和 5:2 之间</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; color: #f56c6c; margin-top: 8px;">
|
||||||
|
<strong>⚠️ 生成视频尺寸遵循首帧图片</strong>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
required: true,
|
||||||
|
accept: 'image/jpeg,image/jpg,image/png,image/webp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('尾帧图片'),
|
||||||
|
key: 'last_frame_image',
|
||||||
|
fullWidth: true, // 占满整行,换行显示
|
||||||
|
type: 'image',
|
||||||
|
onUpload: handleImageUpload,
|
||||||
|
placeholder: t('上传或输入图片URL,作为视频的结束帧'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">将指定图片作为视频的结束帧</div>
|
||||||
|
<div style="margin-bottom: 12px;">支持公网 URL 或 Base64 编码的 Data URL</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">支持格式:</div>
|
||||||
|
<div>• 格式:JPG、JPEG、PNG、WebP</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">图片要求:</div>
|
||||||
|
<div>• 体积:小于 20MB</div>
|
||||||
|
<div>• 尺寸:短边像素大于 300px</div>
|
||||||
|
<div>• 长宽比在 2:5 和 5:2 之间</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; color: #f56c6c; margin-top: 8px;">
|
||||||
|
<strong>⚠️ 当首尾帧尺寸不一致时,模型将参考首帧对尾帧图片进行裁剪</strong>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
required: true,
|
||||||
|
accept: 'image/jpeg,image/jpg,image/png,image/webp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('提示词'),
|
||||||
|
key: 'prompt',
|
||||||
|
type: 'input',
|
||||||
|
fullWidth: true, // 占满整行,换行显示
|
||||||
|
inputType: 'textarea',
|
||||||
|
autosize: { minRows: 3, maxRows: 3 },
|
||||||
|
placeholder: t('描述从首帧到尾帧的过渡过程,最大2000字符。支持运镜指令如[推进]、[左摇]等'),
|
||||||
|
tooltip: `<div style="max-width: 450px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">视频的文本描述,最大 2000 字符</div>
|
||||||
|
<div style="margin-bottom: 12px;">对于 MiniMax-Hailuo-02 支持使用 [指令] 语法进行运镜控制</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">支持 15 种运镜指令:</div>
|
||||||
|
<div style="font-size: 12px; line-height: 1.6;">
|
||||||
|
<div>左右移: [左移], [右移]</div>
|
||||||
|
<div>左右摇: [左摇], [右摇]</div>
|
||||||
|
<div>推拉: [推进], [拉远]</div>
|
||||||
|
<div>升降: [上升], [下降]</div>
|
||||||
|
<div>上下摇: [上摇], [下摇]</div>
|
||||||
|
<div>变焦: [变焦推近], [变焦拉远]</div>
|
||||||
|
<div>其他: [晃动], [跟随], [固定]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">使用规则:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>组合运镜:</strong>同一组 [] 内的多个指令会同时生效,如 [左摇,上升],建议组合不超过 3 个</div>
|
||||||
|
<div>• <strong>顺序运镜:</strong>prompt 中前后出现的指令会依次生效,如 "...[推进], 然后...[拉远]"</div>
|
||||||
|
<div>• <strong>自然语言:</strong>也支持通过自然语言描述运镜,但使用标准指令能获得更准确的响应</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
maxLength: 2000,
|
||||||
|
rows: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频时长'),
|
||||||
|
key: 'duration',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedDurations,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">视频时长(秒),默认值为 6</div>
|
||||||
|
<div style="margin-bottom: 12px;">可用值与模型和分辨率相关</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">1080P</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">MiniMax-Hailuo-02</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6 或 10</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
disabled: false // 根据API文档,如果时长固定则设为true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频分辨率'),
|
||||||
|
key: 'resolution',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedResolutions,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">视频分辨率,首尾帧功能支持 768P、1080P</div>
|
||||||
|
<div style="margin-bottom: 12px;">可用值与模型相关</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">10s</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">MiniMax-Hailuo-02</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P (默认), 1080P</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('自动优化提示词'),
|
||||||
|
key: 'prompt_optimizer',
|
||||||
|
type: 'switch',
|
||||||
|
tooltip: `<div style="max-width: 350px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">是否自动优化 prompt,默认为 true</div>
|
||||||
|
<div style="margin-bottom: 12px;">设为 false 可进行更精确的控制</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">功能说明:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>启用(true):</strong>系统会自动优化和增强你的prompt描述</div>
|
||||||
|
<div>• <strong>禁用(false):</strong>严格按照原始prompt生成,适合精确控制</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件挂载时验证并修正配置
|
||||||
|
onMounted(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoFirstLastFrameOptionsObject || {}
|
||||||
|
const { needsUpdate, updates } = validateAndFixFirstLastFrameOptions(currentData)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
const updatedData = { ...currentData, ...updates }
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', '', '', updatedData)
|
||||||
|
|
||||||
|
// 如果有多项更新,给用户友好的提示
|
||||||
|
if (Object.keys(updates).length > 1) {
|
||||||
|
message.info(t('已自动设置默认值并调整参数以确保兼容性'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图片上传的逻辑
|
||||||
|
async function handleImageUpload(key, imagePath) {
|
||||||
|
const url = await UploadImageToLaiTool(imagePath, 'video')
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
const newValue = {
|
||||||
|
...props.videoMessage.hailuoFirstLastFrameOptionsObject,
|
||||||
|
[key]: url
|
||||||
|
}
|
||||||
|
// 上传成功,更新数据
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', key, url, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理配置变更
|
||||||
|
const handleConfigChange = (key, value, newValue) => {
|
||||||
|
// console.log('HailuoFirstLastFrameInfo配置变更:', { key, value, currentData: newValue })
|
||||||
|
|
||||||
|
// 模型变更时的智能调整
|
||||||
|
if (key === 'model') {
|
||||||
|
const newModel = value
|
||||||
|
let adjustments = {}
|
||||||
|
let hasChanges = false
|
||||||
|
|
||||||
|
// 检查当前分辨率是否被新模型支持
|
||||||
|
if (newValue.resolution) {
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions('firstLastFrame', newModel)
|
||||||
|
const supportedResolutionValues = supportedResolutions.map((r) => r.value)
|
||||||
|
|
||||||
|
if (!supportedResolutionValues.includes(newValue.resolution)) {
|
||||||
|
// 选择第一个支持的分辨率
|
||||||
|
const defaultResolution =
|
||||||
|
supportedResolutions.find((r) => r.value) || supportedResolutions[0]
|
||||||
|
if (defaultResolution) {
|
||||||
|
adjustments.resolution = defaultResolution.value
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前时长是否被新模型支持(考虑新的分辨率)
|
||||||
|
if (newValue.duration) {
|
||||||
|
const currentResolution = adjustments.resolution || newValue.resolution
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
newModel,
|
||||||
|
currentResolution,
|
||||||
|
'firstLastFrame'
|
||||||
|
)
|
||||||
|
const supportedDurationValues = supportedDurations.map((d) => d.value)
|
||||||
|
|
||||||
|
if (!supportedDurationValues.includes(newValue.duration)) {
|
||||||
|
// 选择第一个支持的时长
|
||||||
|
const defaultDuration = supportedDurations.find((d) => d.value) || supportedDurations[0]
|
||||||
|
if (defaultDuration) {
|
||||||
|
adjustments.duration = defaultDuration.value
|
||||||
|
hasChanges = true
|
||||||
|
// console.log(`时长已自动调整为 ${defaultDuration.label}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有自动调整,应用更改并显示提示
|
||||||
|
if (hasChanges) {
|
||||||
|
const mergedData = { ...newValue, ...adjustments }
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', key, value, mergedData)
|
||||||
|
|
||||||
|
// 显示用户友好的提示信息
|
||||||
|
message.info(t('模型变更后已自动调整相关参数以确保兼容性'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分辨率变更时的智能调整
|
||||||
|
if (key === 'resolution') {
|
||||||
|
let adjustments = {}
|
||||||
|
let hasChanges = false
|
||||||
|
|
||||||
|
// 检查当前时长是否与新分辨率兼容
|
||||||
|
if (newValue.duration && newValue.model) {
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
newValue.model,
|
||||||
|
value,
|
||||||
|
'firstLastFrame'
|
||||||
|
)
|
||||||
|
const supportedDurationValues = supportedDurations.map((d) => d.value)
|
||||||
|
|
||||||
|
if (!supportedDurationValues.includes(newValue.duration)) {
|
||||||
|
// 选择第一个支持的时长
|
||||||
|
const defaultDuration = supportedDurations.find((d) => d.value) || supportedDurations[0]
|
||||||
|
if (defaultDuration) {
|
||||||
|
adjustments.duration = defaultDuration.value
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有自动调整,应用更改并显示提示
|
||||||
|
if (hasChanges) {
|
||||||
|
const mergedData = { ...newValue, ...adjustments }
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', key, value, mergedData)
|
||||||
|
message.info('分辨率变更后已自动调整时长以确保兼容性')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时长变更时的验证逻辑
|
||||||
|
if (key === 'duration') {
|
||||||
|
let adjustments = {}
|
||||||
|
let hasChanges = false
|
||||||
|
|
||||||
|
// 检查当前分辨率是否与新时长兼容
|
||||||
|
if (newValue.model && newValue.resolution) {
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'firstLastFrame',
|
||||||
|
newValue.model,
|
||||||
|
value // 新的时长值
|
||||||
|
)
|
||||||
|
const supportedResolutionValues = supportedResolutions.map((r) => r.value)
|
||||||
|
|
||||||
|
if (!supportedResolutionValues.includes(newValue.resolution)) {
|
||||||
|
// 当前分辨率与新时长不兼容,选择一个兼容的分辨率
|
||||||
|
const defaultResolution = supportedResolutions.find((r) => r.value) || supportedResolutions[0]
|
||||||
|
if (defaultResolution) {
|
||||||
|
adjustments.resolution = defaultResolution.value
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有自动调整,应用更改并显示提示
|
||||||
|
if (hasChanges) {
|
||||||
|
const mergedData = { ...newValue, ...adjustments }
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', key, value, mergedData)
|
||||||
|
message.info('时长变更后已自动调整分辨率以确保兼容性')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无需特殊处理的情况,直接应用更改
|
||||||
|
emit('update-hailuo-options', 'firstLastFrame', key, value, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置基础设置
|
||||||
|
async function handleBatchSettings() {
|
||||||
|
emit('batch-settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行首尾帧视频任务
|
||||||
|
async function handleFirstLastFrameVideo() {
|
||||||
|
emit('first-last-frame-video')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 组件样式 */
|
||||||
|
</style>
|
||||||
@ -0,0 +1,460 @@
|
|||||||
|
<template>
|
||||||
|
<n-space vertical :size="20" style="width: 100%">
|
||||||
|
<ConfigOptionGroup
|
||||||
|
v-model:value="videoMessage.hailuoFirstFrameOptionsObject"
|
||||||
|
:options="imageToVideoOptions"
|
||||||
|
@change="handleConfigChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 12px; width: 100%">
|
||||||
|
<TooltipButton
|
||||||
|
:tooltip="t('将当前图生视频的基础设置批量应用到所有的分镜中')"
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="handleBatchSettings"
|
||||||
|
style="width: 100px"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M3,6V8H21V6H3M3,11V13H21V11H3M3,16V18H21V16H3Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('应用设置') }}
|
||||||
|
</TooltipButton>
|
||||||
|
|
||||||
|
<n-button type="primary" size="small" @click="handleImageToVideo" style="flex: 1">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('生成视频') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import ConfigOptionGroup from '@/renderer/src/components/common/ConfigOptionGroup.vue'
|
||||||
|
import TooltipButton from '@/renderer/src/components/common/TooltipButton.vue'
|
||||||
|
import {
|
||||||
|
GetHailuoModelOptions,
|
||||||
|
HailuoModel,
|
||||||
|
HailuoResolution,
|
||||||
|
HailuoDuration,
|
||||||
|
GetHailuoModelSupportedDurations,
|
||||||
|
GetHailuoModelSupportedResolutions
|
||||||
|
} from '@/define/enum/video'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
import { useFile } from '@/renderer/src/hooks/useFile'
|
||||||
|
const { UploadImageToLaiTool } = useFile()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
task: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
videoMessage: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update-hailuo-options', 'batch-settings', 'image-to-video'])
|
||||||
|
|
||||||
|
// 验证并修正配置的独立函数
|
||||||
|
const validateAndFixImageToVideoOptions = (currentData) => {
|
||||||
|
// 获取支持的时长选项
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
'imageToVideo',
|
||||||
|
currentData.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
currentData.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取支持的分辨率选项
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'imageToVideo',
|
||||||
|
currentData.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
currentData.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
let needsUpdate = false
|
||||||
|
const updates = {}
|
||||||
|
|
||||||
|
// 检查时长是否有效
|
||||||
|
if (currentData.duration && !supportedDurations.some((d) => d.value === currentData.duration)) {
|
||||||
|
updates.duration = supportedDurations[0]?.value || HailuoDuration.SIX
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查分辨率是否有效
|
||||||
|
if (
|
||||||
|
currentData.resolution &&
|
||||||
|
!supportedResolutions.some((r) => r.value === currentData.resolution)
|
||||||
|
) {
|
||||||
|
updates.resolution = supportedResolutions[0]?.value || HailuoResolution.P768
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsUpdate,
|
||||||
|
updates,
|
||||||
|
supportedDurations,
|
||||||
|
supportedResolutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图生视频配置选项
|
||||||
|
const imageToVideoOptions = computed(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoFirstFrameOptionsObject || {}
|
||||||
|
|
||||||
|
const { supportedDurations, supportedResolutions } =
|
||||||
|
validateAndFixImageToVideoOptions(currentData)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('模型名称'),
|
||||||
|
key: 'model',
|
||||||
|
type: 'select',
|
||||||
|
width: '200px',
|
||||||
|
options: GetHailuoModelOptions('imageToVideo'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">海螺图生视频模型</div>
|
||||||
|
<div style="margin-bottom: 12px;">选择图像转视频生成模型,不同模型支持的功能和约束条件不同</div>
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">可用模型:</div>
|
||||||
|
<div>• <strong>MiniMax-Hailuo-02</strong>:支持多种分辨率和时长,高质量生成</div>
|
||||||
|
<div>• <strong>I2V-01-Director</strong>:导演级图生视频,支持运镜控制</div>
|
||||||
|
<div>• <strong>I2V-01-live</strong>:实时风格图生视频转换</div>
|
||||||
|
<div>• <strong>I2V-01</strong>:基础图生视频模型</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('首帧图片'),
|
||||||
|
key: 'first_frame_image',
|
||||||
|
type: 'image',
|
||||||
|
fullWidth: true, // 占满整行,换行显示
|
||||||
|
onUpload: handleImageUpload,
|
||||||
|
placeholder: t('上传或输入图片URL,作为视频的起始帧'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">首帧图片设置</div>
|
||||||
|
<div style="margin-bottom: 12px;">指定图片作为视频的起始帧,支持公网URL或Base64编码</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">必填条件:</div>
|
||||||
|
<div>• I2V-01, I2V-01-Director, I2V-01-live 模型时</div>
|
||||||
|
<div>• MiniMax-Hailuo-02 且分辨率为 512P 时</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">图片要求:</div>
|
||||||
|
<div>• <strong>格式:</strong>JPG, JPEG, PNG, WebP</div>
|
||||||
|
<div>• <strong>体积:</strong>小于 20MB</div>
|
||||||
|
<div>• <strong>尺寸:</strong>短边像素大于 300px</div>
|
||||||
|
<div>• <strong>比例:</strong>长宽比在 2:5 和 5:2 之间</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
accept: 'image/jpeg,image/jpg,image/png,image/webp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('提示词'),
|
||||||
|
key: 'prompt',
|
||||||
|
type: 'input',
|
||||||
|
inputType: 'textarea',
|
||||||
|
fullWidth: true, // 占满整行,换行显示
|
||||||
|
autosize: { minRows: 3, maxRows: 3 },
|
||||||
|
placeholder: t(
|
||||||
|
'描述你想要基于图片生成的视频内容,最大2000字符。支持运镜指令如[推进]、[左摇]等'
|
||||||
|
),
|
||||||
|
tooltip: `<div style="max-width: 450px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">视频提示词</div>
|
||||||
|
<div style="margin-bottom: 12px;">基于首帧图片的视频内容描述,最大2000字符。MiniMax-Hailuo-02和I2V-01-Director支持运镜控制</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">15种运镜指令:</div>
|
||||||
|
<div style="font-size: 12px; line-height: 1.6;">
|
||||||
|
<div>左右移: [左移], [右移]</div>
|
||||||
|
<div>左右摇: [左摇], [右摇]</div>
|
||||||
|
<div>推拉: [推进], [拉远]</div>
|
||||||
|
<div>升降: [上升], [下降]</div>
|
||||||
|
<div>上下摇: [上摇], [下摇]</div>
|
||||||
|
<div>变焦: [变焦推近], [变焦拉远]</div>
|
||||||
|
<div>其他: [晃动], [跟随], [固定]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">使用规则:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>组合运镜:</strong>同一组[]内多个指令同时生效,如[左摇,上升],建议不超过3个</div>
|
||||||
|
<div>• <strong>顺序运镜:</strong>prompt中前后出现的指令依次生效,如"...[推进],然后...[拉远]"</div>
|
||||||
|
<div>• <strong>自然语言:</strong>也可用自然语言描述,但标准指令更准确</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
支持中文描述 + 运镜指令的混合使用方式
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
maxLength: 2000,
|
||||||
|
rows: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频时长'),
|
||||||
|
key: 'duration',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedDurations,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">视频时长设置</div>
|
||||||
|
<div style="margin-bottom: 12px;">生成视频的时长(秒),默认值为6,可用值与模型和分辨率相关</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">512P</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">1080P</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;"><strong>MiniMax-Hailuo-02</strong></td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6 或 10</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6 或 10</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;"><strong>其他模型</strong></td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; margin-top: 8px;">
|
||||||
|
系统会根据所选模型和分辨率自动调整可选时长
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频分辨率'),
|
||||||
|
key: 'resolution',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedResolutions,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">视频分辨率设置</div>
|
||||||
|
<div style="margin-bottom: 12px;">生成视频的分辨率,可用值与模型和时长相关</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px; ">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<tr style="">
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">10s</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;"><strong>MiniMax-Hailuo-02</strong></td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">512P, 768P(默认), 1080P</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">512P, 768P(默认)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;"><strong>其他模型</strong></td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">720P(默认)</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">不支持</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; margin-top: 8px;">
|
||||||
|
系统会根据所选模型和时长自动调整可选分辨率
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('自动优化提示词'),
|
||||||
|
key: 'prompt_optimizer',
|
||||||
|
type: 'switch',
|
||||||
|
tooltip: `<div style="max-width: 350px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px; ">提示词自动优化</div>
|
||||||
|
<div style="margin-bottom: 12px;">是否自动优化prompt以获得更好的视频生成效果</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">功能说明:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>启用(true):</strong>系统会自动优化和增强你的prompt描述</div>
|
||||||
|
<div>• <strong>禁用(false):</strong>严格按照原始prompt生成,适合精确控制</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('快速预处理'),
|
||||||
|
key: 'fast_pretreatment',
|
||||||
|
type: 'switch',
|
||||||
|
tooltip: `<div style="max-width: 350px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">快速预处理模式</div>
|
||||||
|
<div style="margin-bottom: 12px;">是否缩短prompt_optimizer的优化耗时,提升生成速度</div>
|
||||||
|
|
||||||
|
<div style=" padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">模型限制:</div>
|
||||||
|
<div style="font-size: 12px; ">
|
||||||
|
仅对 <strong>MiniMax-Hailuo-02</strong> 模型生效,其他模型忽略此设置
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
show: (data) => data.model === HailuoModel.MINIMAX_HAILUO_02
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件挂载时验证并修正配置
|
||||||
|
onMounted(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoFirstFrameOptionsObject || {}
|
||||||
|
const { needsUpdate, updates } = validateAndFixImageToVideoOptions(currentData)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
const updatedData = { ...currentData, ...updates }
|
||||||
|
emit('update-hailuo-options', 'imageToVideo', '', '', updatedData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图片上传的逻辑
|
||||||
|
async function handleImageUpload(key, imagePath) {
|
||||||
|
const url = await UploadImageToLaiTool(imagePath, 'video')
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
const newValue = {
|
||||||
|
...props.videoMessage.hailuoFirstFrameOptionsObject,
|
||||||
|
[key]: url
|
||||||
|
}
|
||||||
|
// 上传成功,更新数据
|
||||||
|
emit('update-hailuo-options', 'imageToVideo', key, url, newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理配置变更
|
||||||
|
const handleConfigChange = (key, value, newValue) => {
|
||||||
|
// 当模型变更时,需要重置不兼容的配置
|
||||||
|
if (key === 'model') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前分辨率是否被新模型支持
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'imageToVideo',
|
||||||
|
value,
|
||||||
|
updatedValue.duration
|
||||||
|
)
|
||||||
|
const currentResolutionSupported = supportedResolutions.some(
|
||||||
|
(option) => option.value === updatedValue.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentResolutionSupported) {
|
||||||
|
// 如果当前分辨率不被支持,设置为默认分辨率
|
||||||
|
if (value === HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.resolution = HailuoResolution.P768 // MiniMax-Hailuo-02 默认768P
|
||||||
|
} else {
|
||||||
|
updatedValue.resolution = HailuoResolution.P720 // 其他模型默认720P
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前时长是否被新模型和分辨率组合支持
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
value,
|
||||||
|
updatedValue.resolution,
|
||||||
|
'imageToVideo'
|
||||||
|
)
|
||||||
|
const currentDurationSupported = supportedDurations.some(
|
||||||
|
(option) => option.value === updatedValue.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentDurationSupported) {
|
||||||
|
// 如果当前时长不被支持,设置为默认时长(6秒)
|
||||||
|
updatedValue.duration = HailuoDuration.SIX
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果切换到非MiniMax-Hailuo-02模型,关闭快速预处理
|
||||||
|
if (value !== HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.fast_pretreatment = false
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'firstFrameOnly', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当分辨率变更时,检查时长兼容性和首帧图片要求
|
||||||
|
if (key === 'resolution') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前时长是否被新分辨率支持
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
updatedValue.model,
|
||||||
|
value,
|
||||||
|
'imageToVideo'
|
||||||
|
)
|
||||||
|
const currentDurationSupported = supportedDurations.some(
|
||||||
|
(option) => option.value === updatedValue.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentDurationSupported) {
|
||||||
|
// 如果当前时长不被支持,设置为默认时长(6秒)
|
||||||
|
updatedValue.duration = HailuoDuration.SIX
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'firstFrameOnly', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当时长变更时,检查分辨率兼容性
|
||||||
|
if (key === 'duration') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前分辨率是否被新时长支持
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'imageToVideo',
|
||||||
|
updatedValue.model,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
const currentResolutionSupported = supportedResolutions.some(
|
||||||
|
(option) => option.value === updatedValue.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentResolutionSupported) {
|
||||||
|
// 如果当前分辨率不被支持,设置为默认分辨率
|
||||||
|
if (updatedValue.model === HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.resolution = HailuoResolution.P768 // MiniMax-Hailuo-02 默认768P
|
||||||
|
} else {
|
||||||
|
updatedValue.resolution = HailuoResolution.P720 // 其他模型默认720P
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'firstFrameOnly', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他配置项的普通处理
|
||||||
|
emit('update-hailuo-options', 'firstFrameOnly', key, value, newValue)
|
||||||
|
console.log('Hailuo image-to-video options changed:', key, value, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置基础设置
|
||||||
|
async function handleBatchSettings() {
|
||||||
|
emit('batch-settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行图生视频任务
|
||||||
|
async function handleImageToVideo() {
|
||||||
|
emit('image-to-video')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 组件样式 */
|
||||||
|
</style>
|
||||||
@ -0,0 +1,419 @@
|
|||||||
|
<template>
|
||||||
|
<n-space vertical :size="20" style="width: 100%">
|
||||||
|
<ConfigOptionGroup
|
||||||
|
v-model:value="videoMessage.hailuoTextToVideoOptionsObject"
|
||||||
|
:options="textToVideoOptions"
|
||||||
|
@change="handleConfigChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 12px; width: 100%">
|
||||||
|
<TooltipButton
|
||||||
|
:tooltip="t('将当前文生视频的基础设置批量应用到所有的分镜中')"
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click="handleBatchSettings"
|
||||||
|
style="width: 100px"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M3,6V8H21V6H3M3,11V13H21V11H3M3,16V18H21V16H3Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('应用设置') }}
|
||||||
|
</TooltipButton>
|
||||||
|
|
||||||
|
<n-button type="primary" size="small" @click="handleTextToVideo" style="flex: 1">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<svg viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M8,5.14V19.14L19,12.14L8,5.14Z" />
|
||||||
|
</svg>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('生成视频') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import ConfigOptionGroup from '@/renderer/src/components/common/ConfigOptionGroup.vue'
|
||||||
|
import TooltipButton from '@/renderer/src/components/common/TooltipButton.vue'
|
||||||
|
import {
|
||||||
|
GetHailuoModelOptions,
|
||||||
|
GetHailuoModelSupportedDurations,
|
||||||
|
GetHailuoModelSupportedResolutions,
|
||||||
|
HailuoModel,
|
||||||
|
HailuoResolution,
|
||||||
|
HailuoDuration
|
||||||
|
} from '@/define/enum/video'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
task: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
videoMessage: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update-hailuo-options', 'batch-settings', 'text-to-video'])
|
||||||
|
|
||||||
|
// 验证并修正配置的独立函数
|
||||||
|
const validateAndFixTextToVideoOptions = (currentData) => {
|
||||||
|
// 获取支持的时长选项
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
'textToVideo',
|
||||||
|
currentData.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
currentData.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取支持的分辨率选项
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'textToVideo',
|
||||||
|
currentData.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
currentData.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
let needsUpdate = false
|
||||||
|
const updates = {}
|
||||||
|
|
||||||
|
// 检查时长是否有效
|
||||||
|
if (currentData.duration && !supportedDurations.some((d) => d.value === currentData.duration)) {
|
||||||
|
updates.duration = supportedDurations[0]?.value || HailuoDuration.SIX
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查分辨率是否有效
|
||||||
|
if (
|
||||||
|
currentData.resolution &&
|
||||||
|
!supportedResolutions.some((r) => r.value === currentData.resolution)
|
||||||
|
) {
|
||||||
|
updates.resolution = supportedResolutions[0]?.value || HailuoResolution.P768
|
||||||
|
needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsUpdate,
|
||||||
|
updates,
|
||||||
|
supportedDurations,
|
||||||
|
supportedResolutions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文生视频配置选项
|
||||||
|
const textToVideoOptions = computed(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoTextToVideoOptionsObject || {}
|
||||||
|
|
||||||
|
const { supportedDurations, supportedResolutions } = validateAndFixTextToVideoOptions(currentData)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('模型名称'),
|
||||||
|
key: 'model',
|
||||||
|
type: 'select',
|
||||||
|
width: '200px',
|
||||||
|
options: GetHailuoModelOptions('textToVideo'),
|
||||||
|
tooltip: `<div style="max-width: 400px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">模型选择说明:</div>
|
||||||
|
<div style="margin-bottom: 12px;">选择图像转视频生成模型,不同模型支持的功能和约束条件不同</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">可用模型:</div>
|
||||||
|
<div>• <strong>MiniMax-Hailuo-02</strong>:最新模型,支持运镜指令</div>
|
||||||
|
<div>• <strong>T2V-01-Director</strong>:支持运镜控制的导演版模型</div>
|
||||||
|
<div>• <strong>T2V-01</strong>:基础文生视频模型</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; margin-top: 8px;">
|
||||||
|
运镜指令功能仅对 MiniMax-Hailuo-02 和 Director 系列模型生效
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('提示词'),
|
||||||
|
key: 'prompt',
|
||||||
|
type: 'input',
|
||||||
|
inputType: 'textarea',
|
||||||
|
autosize: { minRows: 3, maxRows: 3 },
|
||||||
|
fullWidth: true,
|
||||||
|
placeholder: t('描述你想要生成的视频内容,最大2000字符。支持运镜指令如[推进]、[左摇]等'),
|
||||||
|
tooltip: `<div style="max-width: 450px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">视频文本描述(最大2000字符)</div>
|
||||||
|
<div style="margin-bottom: 12px;">对于 MiniMax-Hailuo-02 和 *-Director 系列模型,支持运镜指令:</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">支持 15 种运镜指令:</div>
|
||||||
|
<div style="font-size: 12px; line-height: 1.6;">
|
||||||
|
<div>左右移: [左移], [右移]</div>
|
||||||
|
<div>左右摇: [左摇], [右摇]</div>
|
||||||
|
<div>推拉: [推进], [拉远]</div>
|
||||||
|
<div>升降: [上升], [下降]</div>
|
||||||
|
<div>上下摇: [上摇], [下摇]</div>
|
||||||
|
<div>变焦: [变焦推近], [变焦拉远]</div>
|
||||||
|
<div>其他: [晃动], [跟随], [固定]</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">使用规则:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>组合运镜</strong>:[左摇,上升] 同时生效,建议不超过3个</div>
|
||||||
|
<div>• <strong>顺序运镜</strong>:前后指令依次生效,如"[推进],然后[拉远]"</div>
|
||||||
|
<div>• <strong>自然语言</strong>:也支持描述运镜,但标准指令更准确</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
maxLength: 2000,
|
||||||
|
rows: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频时长'),
|
||||||
|
key: 'duration',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedDurations,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">视频时长限制:</div>
|
||||||
|
<div style="margin-bottom: 12px;">生成视频的时长(秒),默认值为6,可用值与模型和分辨率相关</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">1080P</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">MiniMax-Hailuo-02</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s 或 10s</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">其他模型</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">不支持</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('视频分辨率'),
|
||||||
|
key: 'resolution',
|
||||||
|
type: 'select',
|
||||||
|
options: supportedResolutions,
|
||||||
|
tooltip: `<div style="max-width: 380px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">分辨率支持情况:</div>
|
||||||
|
<div style="margin-bottom: 12px;">生成视频的分辨率,可用值与模型和时长相关</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 6px;">模型支持矩阵:</div>
|
||||||
|
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: left;">模型</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">6s</th>
|
||||||
|
<th style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">10s</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">MiniMax-Hailuo-02</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P(默认), 1080P</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">768P(默认)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px;">其他模型</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">720P(默认)</td>
|
||||||
|
<td style="border: 1px solid #d1d5db; padding: 4px; text-align: center;">不支持</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; margin-top: 8px;">
|
||||||
|
系统会根据所选模型和时长自动调整可选分辨率
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('自动优化提示词'),
|
||||||
|
key: 'prompt_optimizer',
|
||||||
|
type: 'switch',
|
||||||
|
tooltip: `<div style="max-width: 350px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">提示词自动优化</div>
|
||||||
|
<div style="margin-bottom: 12px;">是否自动优化prompt以获得更好的视频生成效果</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">功能说明:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
<div>• <strong>开启(默认)</strong>:系统自动优化prompt以获得更好效果</div>
|
||||||
|
<div>• <strong>关闭</strong>:使用原始prompt,进行更精确的控制</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="font-size: 12px; margin-top: 8px;">
|
||||||
|
建议保持开启以获得最佳生成效果
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('快速预处理'),
|
||||||
|
key: 'fast_pretreatment',
|
||||||
|
type: 'switch',
|
||||||
|
tooltip: `<div style="max-width: 350px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 8px;">快速预处理模式</div>
|
||||||
|
<div style="margin-bottom: 12px;">是否缩短prompt_optimizer的优化耗时,提升生成速度</div>
|
||||||
|
|
||||||
|
<div style="padding: 8px; border-radius: 6px; margin-bottom: 8px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 4px;">模型限制:</div>
|
||||||
|
<div style="font-size: 12px;">
|
||||||
|
仅对 <strong>MiniMax-Hailuo-02</strong> 模型生效,其他模型忽略此设置
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
show: (data) => data.model === HailuoModel.MINIMAX_HAILUO_02
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件挂载时验证并修正配置
|
||||||
|
onMounted(() => {
|
||||||
|
const currentData = props.videoMessage.hailuoTextToVideoOptionsObject || {}
|
||||||
|
const { needsUpdate, updates } = validateAndFixTextToVideoOptions(currentData)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
const updatedData = { ...currentData, ...updates }
|
||||||
|
emit('update-hailuo-options', 'textToVideo', '', '', updatedData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理配置变更
|
||||||
|
const handleConfigChange = (key, value, newValue) => {
|
||||||
|
// 当模型变更时,需要重置不兼容的配置
|
||||||
|
if (key === 'model') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前分辨率是否被新模型支持
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'textToVideo',
|
||||||
|
value,
|
||||||
|
updatedValue.duration
|
||||||
|
)
|
||||||
|
const currentResolutionSupported = supportedResolutions.some(
|
||||||
|
(option) => option.value === updatedValue.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentResolutionSupported) {
|
||||||
|
// 如果当前分辨率不被支持,设置为默认分辨率
|
||||||
|
if (value === HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.resolution = HailuoResolution.P768 // MiniMax-Hailuo-02 默认768P
|
||||||
|
} else {
|
||||||
|
updatedValue.resolution = HailuoResolution.P720 // 其他模型默认720P
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前时长是否被新模型和分辨率组合支持
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
value,
|
||||||
|
updatedValue.resolution,
|
||||||
|
'textToVideo'
|
||||||
|
)
|
||||||
|
const currentDurationSupported = supportedDurations.some(
|
||||||
|
(option) => option.value === updatedValue.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentDurationSupported) {
|
||||||
|
// 如果当前时长不被支持,设置为默认时长(6秒)
|
||||||
|
updatedValue.duration = HailuoDuration.SIX
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果切换到非MiniMax-Hailuo-02模型,关闭快速预处理
|
||||||
|
if (value !== HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.fast_pretreatment = false
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'textToVideo', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当分辨率变更时,检查时长兼容性
|
||||||
|
if (key === 'resolution') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前时长是否被新分辨率支持
|
||||||
|
const supportedDurations = GetHailuoModelSupportedDurations(
|
||||||
|
updatedValue.model,
|
||||||
|
value,
|
||||||
|
'textToVideo'
|
||||||
|
)
|
||||||
|
const currentDurationSupported = supportedDurations.some(
|
||||||
|
(option) => option.value === updatedValue.duration
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentDurationSupported) {
|
||||||
|
// 如果当前时长不被支持,设置为默认时长(6秒)
|
||||||
|
updatedValue.duration = HailuoDuration.SIX
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'textToVideo', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当时长变更时,检查分辨率兼容性
|
||||||
|
if (key === 'duration') {
|
||||||
|
const updatedValue = { ...newValue }
|
||||||
|
|
||||||
|
// 检查当前分辨率是否被新时长支持
|
||||||
|
const supportedResolutions = GetHailuoModelSupportedResolutions(
|
||||||
|
'textToVideo',
|
||||||
|
updatedValue.model,
|
||||||
|
value
|
||||||
|
)
|
||||||
|
const currentResolutionSupported = supportedResolutions.some(
|
||||||
|
(option) => option.value === updatedValue.resolution
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!currentResolutionSupported) {
|
||||||
|
// 如果当前分辨率不被支持,设置为默认分辨率
|
||||||
|
if (updatedValue.model === HailuoModel.MINIMAX_HAILUO_02) {
|
||||||
|
updatedValue.resolution = HailuoResolution.P768 // MiniMax-Hailuo-02 默认768P
|
||||||
|
} else {
|
||||||
|
updatedValue.resolution = HailuoResolution.P720 // 其他模型默认720P
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('update-hailuo-options', 'textToVideo', key, value, updatedValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他配置项的普通处理
|
||||||
|
emit('update-hailuo-options', 'textToVideo', key, value, newValue)
|
||||||
|
console.log('Hailuo text-to-video options changed:', key, value, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置基础设置
|
||||||
|
async function handleBatchSettings() {
|
||||||
|
emit('batch-settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行文生视频任务
|
||||||
|
async function handleTextToVideo() {
|
||||||
|
emit('text-to-video')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 组件样式 */
|
||||||
|
</style>
|
||||||
@ -0,0 +1,387 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hailuo-video-container">
|
||||||
|
<n-tabs v-model:value="activeTab" type="segment" size="small">
|
||||||
|
<!-- 首尾帧视频 Tab -->
|
||||||
|
<n-tab-pane name="first-last-frame" :tab="t('首尾帧视频')">
|
||||||
|
<HailuoFirstLastFrameInfo
|
||||||
|
:task="props.task"
|
||||||
|
:video-message="videoMessage"
|
||||||
|
@update-hailuo-options="handleHailuoOptionsUpdate"
|
||||||
|
@batch-settings="handleBatchSettings"
|
||||||
|
@first-last-frame-video="handleFirstLastFrameVideo"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<!-- 图生视频 Tab -->
|
||||||
|
<n-tab-pane name="image-to-video" :tab="t('图生视频')">
|
||||||
|
<HailuoImageToVideoInfo
|
||||||
|
:task="props.task"
|
||||||
|
:video-message="videoMessage"
|
||||||
|
@update-hailuo-options="handleHailuoOptionsUpdate"
|
||||||
|
@batch-settings="handleBatchSettings"
|
||||||
|
@image-to-video="handleImageToVideo"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<!-- 文生视频 Tab -->
|
||||||
|
<n-tab-pane name="text-to-video" :tab="t('文生视频')">
|
||||||
|
<HailuoTextToVideoInfo
|
||||||
|
:task="props.task"
|
||||||
|
:video-message="videoMessage"
|
||||||
|
@update-hailuo-options="handleHailuoOptionsUpdate"
|
||||||
|
@batch-settings="handleBatchSettings"
|
||||||
|
@text-to-video="handleTextToVideo"
|
||||||
|
/>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, h } from 'vue'
|
||||||
|
import { useMessage, useDialog } from 'naive-ui'
|
||||||
|
import HailuoTextToVideoInfo from './HailuoTextToVideoInfo.vue'
|
||||||
|
import HailuoImageToVideoInfo from './HailuoImageToVideoInfo.vue'
|
||||||
|
import HailuoFirstLastFrameInfo from './HailuoFirstLastFrameInfo.vue'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
import { ValidateJsonAndParse } from '@/define/Tools/validate'
|
||||||
|
import { HailuoModel, HailuoResolution, HailuoDuration } from '@/define/enum/video'
|
||||||
|
import { useSoftwareStore, useBookStore } from '@/renderer/src/stores'
|
||||||
|
import { BookBackTaskType, TaskExecuteType } from '@/define/enum/bookEnum'
|
||||||
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
|
import { AddOneTask } from '@/renderer/src/common/task'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
const softwareStore = useSoftwareStore()
|
||||||
|
const bookStore = useBookStore()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
task: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前激活的标签页
|
||||||
|
const activeTab = ref('first-last-frame')
|
||||||
|
|
||||||
|
// 海螺视频表单数据
|
||||||
|
const videoMessage = computed(() => {
|
||||||
|
console.log('MediaToVideoInfoHaiLuoVideoInfo props.task', props.task, props.task?.videoMessage)
|
||||||
|
let videoMessage = props.task?.videoMessage || {}
|
||||||
|
|
||||||
|
// 解析文生视频配置
|
||||||
|
let hailuoTextToVideoOptionsString = videoMessage.hailuoTextToVideoOptions || '{}'
|
||||||
|
let hailuoTextToVideoOptions = ValidateJsonAndParse(hailuoTextToVideoOptionsString)
|
||||||
|
|
||||||
|
// 解析图生视频配置
|
||||||
|
let hailuoFirstFrameOptionsString = videoMessage.hailuoFirstFrameOnlyOptions || '{}'
|
||||||
|
let hailuoFirstFrameOptions = ValidateJsonAndParse(hailuoFirstFrameOptionsString)
|
||||||
|
|
||||||
|
// 解析首尾帧视频配置
|
||||||
|
let hailuoFirstLastFrameOptionsString = videoMessage.hailuoFirstLastFrameOptions || '{}'
|
||||||
|
let hailuoFirstLastFrameOptions = ValidateJsonAndParse(hailuoFirstLastFrameOptionsString)
|
||||||
|
|
||||||
|
// 创建清洁的文生视频配置对象
|
||||||
|
const cleanTextToVideoOptions = {
|
||||||
|
model: hailuoTextToVideoOptions.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
prompt: hailuoTextToVideoOptions.prompt || '',
|
||||||
|
duration: hailuoTextToVideoOptions.duration ?? HailuoDuration.SIX,
|
||||||
|
resolution: hailuoTextToVideoOptions.resolution || HailuoResolution.P768,
|
||||||
|
prompt_optimizer: hailuoTextToVideoOptions.prompt_optimizer ?? true,
|
||||||
|
fast_pretreatment: hailuoTextToVideoOptions.fast_pretreatment ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建清洁的图生视频配置对象
|
||||||
|
const cleanFirstFrameOptions = {
|
||||||
|
model: hailuoFirstFrameOptions.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
first_frame_image: videoMessage.imageUrl || hailuoFirstFrameOptions.first_frame_image || '',
|
||||||
|
prompt: hailuoFirstFrameOptions.prompt || '',
|
||||||
|
duration: hailuoFirstFrameOptions.duration ?? HailuoDuration.SIX,
|
||||||
|
resolution: hailuoFirstFrameOptions.resolution || HailuoResolution.P768,
|
||||||
|
prompt_optimizer: hailuoFirstFrameOptions.prompt_optimizer ?? true,
|
||||||
|
fast_pretreatment: hailuoFirstFrameOptions.fast_pretreatment ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建清洁的首尾帧视频配置对象
|
||||||
|
const cleanFirstLastFrameOptions = {
|
||||||
|
model: hailuoFirstLastFrameOptions.model || HailuoModel.MINIMAX_HAILUO_02,
|
||||||
|
first_frame_image: videoMessage.imageUrl || hailuoFirstLastFrameOptions.first_frame_image || '',
|
||||||
|
last_frame_image: hailuoFirstLastFrameOptions.last_frame_image || '',
|
||||||
|
prompt: hailuoFirstLastFrameOptions.prompt || '',
|
||||||
|
duration: HailuoDuration.SIX, // 首尾帧固定为6秒,根据API文档调整
|
||||||
|
resolution: hailuoFirstLastFrameOptions.resolution || HailuoResolution.P768,
|
||||||
|
prompt_optimizer: hailuoFirstLastFrameOptions.prompt_optimizer ?? true,
|
||||||
|
fast_pretreatment: hailuoFirstLastFrameOptions.fast_pretreatment ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
videoMessage.hailuoTextToVideoOptionsObject = cleanTextToVideoOptions
|
||||||
|
videoMessage.hailuoFirstFrameOptionsObject = cleanFirstFrameOptions
|
||||||
|
videoMessage.hailuoFirstLastFrameOptionsObject = cleanFirstLastFrameOptions
|
||||||
|
|
||||||
|
console.log('MediaToVideoInfoHaiLuoVideoInfo videoMessage', videoMessage)
|
||||||
|
return videoMessage
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理海螺选项更新
|
||||||
|
async function handleHailuoOptionsUpdate(optionsType, key, value, newOptions) {
|
||||||
|
// 根据选项类型确定要更新的字段
|
||||||
|
let updateData = {
|
||||||
|
imageUrl: newOptions.first_frame_image || videoMessage.value.imageUrl
|
||||||
|
}
|
||||||
|
// 根据选项类型更新对应的配置
|
||||||
|
switch (optionsType) {
|
||||||
|
case 'textToVideo':
|
||||||
|
updateData.hailuoTextToVideoOptions = JSON.stringify(newOptions)
|
||||||
|
break
|
||||||
|
case 'firstFrameOnly':
|
||||||
|
updateData.hailuoFirstFrameOnlyOptions = JSON.stringify(newOptions)
|
||||||
|
break
|
||||||
|
case 'firstLastFrame':
|
||||||
|
updateData.hailuoFirstLastFrameOptions = JSON.stringify(newOptions)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await window.book.video.UpdateBookTaskDetailVideoMessage(props.task.id, updateData)
|
||||||
|
if (res.code !== 1) {
|
||||||
|
message.error(
|
||||||
|
t('保存失败:{error}', {
|
||||||
|
error: res.message
|
||||||
|
}) + `, Key: ${key}`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改成功后同步更新本地数据
|
||||||
|
if (!props.task.videoMessage) {
|
||||||
|
props.task.videoMessage = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(props.task.videoMessage, updateData)
|
||||||
|
|
||||||
|
// 同步更新对应的配置对象
|
||||||
|
switch (optionsType) {
|
||||||
|
case 'textToVideo':
|
||||||
|
videoMessage.value.hailuoTextToVideoOptionsObject = { ...newOptions }
|
||||||
|
break
|
||||||
|
case 'firstFrameOnly':
|
||||||
|
videoMessage.value.hailuoFirstFrameOptionsObject = { ...newOptions }
|
||||||
|
break
|
||||||
|
case 'firstLastFrame':
|
||||||
|
videoMessage.value.hailuoFirstLastFrameOptionsObject = { ...newOptions }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量设置基础设置
|
||||||
|
async function handleBatchSettings() {
|
||||||
|
const currentOptions = getCurrentTabOptions()
|
||||||
|
|
||||||
|
let da = dialog.warning({
|
||||||
|
title: t('操作确认'),
|
||||||
|
content: () =>
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
whiteSpace: 'pre-line'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () =>
|
||||||
|
t(
|
||||||
|
'是否将当前分镜的设置批量应用到其余所有分镜?\n\n同步的设置:模型名称、分辨率、时长、提示词优化等基础设置\n\n批量应用后,其余分镜的上述基础设置会被替换为当前分镜的数据,是否继续?'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
positiveText: t('确认'),
|
||||||
|
negativeText: t('取消'),
|
||||||
|
closable: true,
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
da.destroy()
|
||||||
|
try {
|
||||||
|
softwareStore.spin.spinning = true
|
||||||
|
softwareStore.spin.tip = t('正在批量应用当前设置...')
|
||||||
|
|
||||||
|
// 开始修改所有的分镜
|
||||||
|
for (let i = 0; i < bookStore.selectBookTaskDetail.length; i++) {
|
||||||
|
const element = bookStore.selectBookTaskDetail[i]
|
||||||
|
|
||||||
|
let updateObject = {}
|
||||||
|
let elementVideoMessage = element?.videoMessage || {}
|
||||||
|
|
||||||
|
// 根据当前激活的tab更新对应的配置
|
||||||
|
switch (activeTab.value) {
|
||||||
|
case 'text-to-video':
|
||||||
|
let textOptions = ValidateJsonAndParse(
|
||||||
|
elementVideoMessage.hailuoTextToVideoOptions || '{}'
|
||||||
|
)
|
||||||
|
textOptions.model = currentOptions.model
|
||||||
|
textOptions.duration = currentOptions.duration
|
||||||
|
textOptions.resolution = currentOptions.resolution
|
||||||
|
textOptions.prompt_optimizer = currentOptions.prompt_optimizer
|
||||||
|
textOptions.fast_pretreatment = currentOptions.fast_pretreatment
|
||||||
|
updateObject.hailuoTextToVideoOptions = JSON.stringify(textOptions)
|
||||||
|
elementVideoMessage.hailuoTextToVideoOptions = updateObject.hailuoTextToVideoOptions
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'image-to-video':
|
||||||
|
let frameOptions = ValidateJsonAndParse(
|
||||||
|
elementVideoMessage.hailuoFirstFrameOnlyOptions || '{}'
|
||||||
|
)
|
||||||
|
frameOptions.model = currentOptions.model
|
||||||
|
frameOptions.duration = currentOptions.duration
|
||||||
|
frameOptions.resolution = currentOptions.resolution
|
||||||
|
frameOptions.prompt_optimizer = currentOptions.prompt_optimizer
|
||||||
|
frameOptions.fast_pretreatment = currentOptions.fast_pretreatment
|
||||||
|
updateObject.hailuoFirstFrameOnlyOptions = JSON.stringify(frameOptions)
|
||||||
|
elementVideoMessage.hailuoFirstFrameOnlyOptions =
|
||||||
|
updateObject.hailuoFirstFrameOnlyOptions
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'first-last-frame':
|
||||||
|
let lastFrameOptions = ValidateJsonAndParse(
|
||||||
|
elementVideoMessage.hailuoFirstLastFrameOptions || '{}'
|
||||||
|
)
|
||||||
|
lastFrameOptions.model = currentOptions.model
|
||||||
|
lastFrameOptions.duration = currentOptions.duration
|
||||||
|
lastFrameOptions.resolution = currentOptions.resolution
|
||||||
|
lastFrameOptions.prompt_optimizer = currentOptions.prompt_optimizer
|
||||||
|
lastFrameOptions.fast_pretreatment = currentOptions.fast_pretreatment
|
||||||
|
updateObject.hailuoFirstLastFrameOptions = JSON.stringify(lastFrameOptions)
|
||||||
|
elementVideoMessage.hailuoFirstLastFrameOptions =
|
||||||
|
updateObject.hailuoFirstLastFrameOptions
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始修改数据库
|
||||||
|
let res = await window.book.video.UpdateBookTaskDetailVideoMessage(
|
||||||
|
element.id,
|
||||||
|
updateObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.code !== 1) {
|
||||||
|
message.error(
|
||||||
|
t('批量应用当前设置失败,{error}', {
|
||||||
|
error: res.message
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success(t('批量应用当前设置成功!'))
|
||||||
|
} catch (error) {
|
||||||
|
message.error(
|
||||||
|
t('批量应用当前设置失败,{error}', {
|
||||||
|
error: error.message
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
softwareStore.spin.spinning = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNegativeClick: () => {
|
||||||
|
da.destroy()
|
||||||
|
message.info(t('取消操作'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前标签页的配置选项
|
||||||
|
function getCurrentTabOptions() {
|
||||||
|
switch (activeTab.value) {
|
||||||
|
case 'text-to-video':
|
||||||
|
return videoMessage.value.hailuoTextToVideoOptionsObject
|
||||||
|
case 'image-to-video':
|
||||||
|
return videoMessage.value.hailuoFirstFrameOptionsObject
|
||||||
|
case 'first-last-frame':
|
||||||
|
return videoMessage.value.hailuoFirstLastFrameOptionsObject
|
||||||
|
default:
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文生视频任务
|
||||||
|
async function handleTextToVideo() {
|
||||||
|
const options = videoMessage.value.hailuoTextToVideoOptionsObject
|
||||||
|
|
||||||
|
if (!options.prompt && !options.model) {
|
||||||
|
message.error(t('请输入提示词和选择模型'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await AddOneTask({
|
||||||
|
bookId: props.task.bookId,
|
||||||
|
type: BookBackTaskType.HAILUO_TEXT_TO_VIDEO,
|
||||||
|
executeType: TaskExecuteType.AUTO,
|
||||||
|
bookTaskId: props.task.bookTaskId,
|
||||||
|
bookTaskDetailId: props.task.id,
|
||||||
|
messageName: DEFINE_STRING.BOOK.HAILUO_TO_VIDEO_RETURN
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code != 1) {
|
||||||
|
message.error(res.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.success(res.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图生视频任务
|
||||||
|
async function handleImageToVideo() {
|
||||||
|
const options = videoMessage.value.hailuoFirstFrameOptionsObject
|
||||||
|
|
||||||
|
if (!options.first_frame_image) {
|
||||||
|
message.error(t('请上传首帧图片'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await AddOneTask({
|
||||||
|
bookId: props.task.bookId,
|
||||||
|
type: BookBackTaskType.HAILUO_IMAGE_TO_VIDEO,
|
||||||
|
executeType: TaskExecuteType.AUTO,
|
||||||
|
bookTaskId: props.task.bookTaskId,
|
||||||
|
bookTaskDetailId: props.task.id,
|
||||||
|
messageName: DEFINE_STRING.BOOK.HAILUO_TO_VIDEO_RETURN
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code != 1) {
|
||||||
|
message.error(res.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.success(res.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首尾帧视频任务
|
||||||
|
async function handleFirstLastFrameVideo() {
|
||||||
|
const options = videoMessage.value.hailuoFirstLastFrameOptionsObject
|
||||||
|
|
||||||
|
if (!options.first_frame_image || !options.last_frame_image) {
|
||||||
|
message.error(t('请上传首帧和尾帧图片'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await AddOneTask({
|
||||||
|
bookId: props.task.bookId,
|
||||||
|
type: BookBackTaskType.HAILUO_FIRST_LAST_FRAME,
|
||||||
|
executeType: TaskExecuteType.AUTO,
|
||||||
|
bookTaskId: props.task.bookTaskId,
|
||||||
|
bookTaskDetailId: props.task.id,
|
||||||
|
messageName: DEFINE_STRING.BOOK.HAILUO_TO_VIDEO_RETURN
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code != 1) {
|
||||||
|
message.error(res.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.success(res.message)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hailuo-video-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -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
|
||||||
|
<template>
|
||||||
|
<MediaToVideoInfoHaiLuoVideoInfo :task="currentTask" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import MediaToVideoInfoHaiLuoVideoInfo from './MediaToVideoInfoHaiLuo/MediaToVideoInfoHaiLuoVideoInfo.vue'
|
||||||
|
|
||||||
|
const currentTask = ref({
|
||||||
|
id: 'task-id',
|
||||||
|
bookId: 'book-id',
|
||||||
|
videoMessage: {
|
||||||
|
hailuoTextToVideoOptions: '{}',
|
||||||
|
hailuoFirstFrameOnlyOptions: '{}',
|
||||||
|
hailuoFirstLastFrameOptions: '{}',
|
||||||
|
imageUrl: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据更新流程
|
||||||
|
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切换时的数据保持
|
||||||
|
|
||||||
|
这个组件架构提供了完整的海螺视频生成功能,具有良好的扩展性和维护性。
|
||||||
@ -85,6 +85,7 @@ import { useBookStore, useSoftwareStore } from '@/renderer/src/stores'
|
|||||||
import { ResponseMessageType } from '@/define/enum/softwareEnum'
|
import { ResponseMessageType } from '@/define/enum/softwareEnum'
|
||||||
import { VideoStatus } from '@/define/enum/video'
|
import { VideoStatus } from '@/define/enum/video'
|
||||||
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
import { DEFINE_STRING } from '@/define/ipcDefineString'
|
||||||
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
const bookStore = useBookStore()
|
const bookStore = useBookStore()
|
||||||
const softwareStore = useSoftwareStore()
|
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.MJ_VIDEO_TO_VIDEO_RETURN])
|
||||||
window.system.removeEventListen(DEFINE_STRING.BOOK.KLING_IMAGE_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) {
|
function handleEventReceive(value) {
|
||||||
try {
|
try {
|
||||||
if (value.type == ResponseMessageType.MJ_VIDEO) {
|
if (value.type == ResponseMessageType.MJ_VIDEO) {
|
||||||
@ -168,6 +186,10 @@ function handleEventReceive(value) {
|
|||||||
let videoMessage = JSON.parse(value.data)
|
let videoMessage = JSON.parse(value.data)
|
||||||
console.log('收到 Kling video extend 视频处理进度', videoMessage)
|
console.log('收到 Kling video extend 视频处理进度', videoMessage)
|
||||||
handleMessageChange(videoMessage, value.id)
|
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) {
|
} else if (value.type == ResponseMessageType.VIDEO_SUCESS) {
|
||||||
// 执行返回 返回全部的最新数据
|
// 执行返回 返回全部的最新数据
|
||||||
let bookTaskDetail = JSON.parse(value.data)
|
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) {
|
function handleViewDetail(row) {
|
||||||
selectedTask.value = { ...row }
|
selectedTask.value = { ...row }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user