mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Merge pull request #174 from modelstudioai/feat/update-def-video-model
Switch default video model to wan3.0-video
This commit is contained in:
@@ -178,7 +178,7 @@ export async function ensurePrerequisites(ctx) {
|
||||
"video",
|
||||
"generate",
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"压测前置短视频:海浪与静态远景,无明显人物。",
|
||||
"--duration",
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function generateCombinedFixtures({ suiteRoot, cliPackage }) {
|
||||
"video",
|
||||
"generate",
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"压测前置短视频:海浪与静态远景,无明显人物。",
|
||||
"--duration",
|
||||
|
||||
@@ -16,7 +16,7 @@ const motions = [
|
||||
|
||||
export const runStress = defineStressTarget({
|
||||
canonical: "video-i2v",
|
||||
defaultModel: "happyhorse-1.1-i2v",
|
||||
defaultModel: "wan3.0-video",
|
||||
batchDirPrefix: "video-i2v-batch",
|
||||
helpText: "pnpm run test:stress -- video-i2v [--reuse-fixtures] -- --count 5 -c 2",
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const prompts = [
|
||||
|
||||
export const runStress = defineStressTarget({
|
||||
canonical: "video-ref",
|
||||
defaultModel: "happyhorse-1.1-r2v",
|
||||
defaultModel: "wan3.0-video",
|
||||
batchDirPrefix: "video-ref-batch",
|
||||
helpText: "pnpm run test:stress -- video-ref [--reuse-fixtures] -- --count 5 -c 2",
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
export const runStress = defineStressTarget({
|
||||
canonical: "video-t2v",
|
||||
defaultModel: "happyhorse-1.1-t2v",
|
||||
defaultModel: "wan3.0-video",
|
||||
batchDirPrefix: "video-t2v-batch",
|
||||
helpText: `用法:pnpm run test:stress -- video-t2v -- --concurrency 1 --count 3
|
||||
详见 docs/agents/stress-batch-tests.md`,
|
||||
|
||||
@@ -69,7 +69,9 @@ export const UI_BOOLEAN_KEYS = new Set<string>(["telemetry"]);
|
||||
export const UI_MODEL_DEFAULTS: Record<string, string> = {
|
||||
default_text_model: "qwen3.8-max",
|
||||
default_image_model: "qwen-image-3.0",
|
||||
default_video_model: "happyhorse-1.1-t2v",
|
||||
default_video_model: "wan3.0-video",
|
||||
default_image_to_video_model: "wan3.0-video",
|
||||
default_reference_to_video_model: "wan3.0-video",
|
||||
default_speech_model: "cosyvoice-v3-flash",
|
||||
default_omni_model: "qwen3.5-omni-plus",
|
||||
};
|
||||
@@ -100,8 +102,18 @@ export const UI_MODEL_CATALOG: Record<string, ModelOption[]> = {
|
||||
{ id: "wanx2.x", role: "image/generate · async series" },
|
||||
],
|
||||
default_video_model: [
|
||||
{ id: "happyhorse-1.1-t2v", role: "video/generate default · text-to-video" },
|
||||
{ id: "wan3.0-video", role: "video/generate · t2v / i2v / r2v default" },
|
||||
{ id: "happyhorse-1.1-t2v", role: "video/generate · text-to-video" },
|
||||
{ id: "happyhorse-1.1-i2v", role: "video/generate · image-to-video" },
|
||||
{ id: "happyhorse-1.1-r2v", role: "video/ref · reference-to-video" },
|
||||
],
|
||||
default_image_to_video_model: [
|
||||
{ id: "wan3.0-video", role: "video/generate default · image-to-video" },
|
||||
{ id: "happyhorse-1.1-i2v", role: "video/generate · image-to-video" },
|
||||
],
|
||||
default_reference_to_video_model: [
|
||||
{ id: "wan3.0-video", role: "video/ref default · reference-to-video" },
|
||||
{ id: "happyhorse-1.1-r2v", role: "video/ref · reference-to-video" },
|
||||
],
|
||||
default_speech_model: [
|
||||
{ id: "cosyvoice-v3-flash", role: "speech/synthesize (TTS) default" },
|
||||
|
||||
@@ -24,9 +24,8 @@ import { BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, BOOL_FLAG_WATERMARK } from "bailia
|
||||
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US":
|
||||
"Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v)",
|
||||
"zh-CN": "根据文本或图片生成视频(happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v)",
|
||||
"en-US": "Generate a video from text or image (wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v)",
|
||||
"zh-CN": "根据文本或图片生成视频(wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v)",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "--prompt <text> [--image <url>] [flags]",
|
||||
@@ -35,8 +34,8 @@ export default defineCommand({
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: {
|
||||
"en-US": "Model ID (default: happyhorse-1.1-t2v, or happyhorse-1.1-i2v with --image)",
|
||||
"zh-CN": "模型 ID(默认:happyhorse-1.1-t2v;使用 --image 时为 happyhorse-1.1-i2v)",
|
||||
"en-US": "Model ID (default: wan3.0-video)",
|
||||
"zh-CN": "模型 ID(默认:wan3.0-video)",
|
||||
},
|
||||
},
|
||||
prompt: {
|
||||
@@ -119,6 +118,16 @@ export default defineCommand({
|
||||
"zh-CN": "完成后将视频保存到文件",
|
||||
},
|
||||
},
|
||||
file: {
|
||||
type: "string",
|
||||
valueHint: "<url-or-path>",
|
||||
description: {
|
||||
"en-US":
|
||||
"Reference file URL or local path for file-to-video (wan3.0-video only; mutually exclusive with --image/--last-frame)",
|
||||
"zh-CN":
|
||||
"参考文件 URL 或本地路径,用于文件生视频(仅 wan3.0-video;与 --image/--last-frame 互斥)",
|
||||
},
|
||||
},
|
||||
...ASYNC_FLAG,
|
||||
...CONCURRENT_FLAG,
|
||||
pollInterval: {
|
||||
@@ -159,12 +168,20 @@ export default defineCommand({
|
||||
const model =
|
||||
flags.model ||
|
||||
(flags.image
|
||||
? settings.defaultImageToVideoModel || "happyhorse-1.1-i2v"
|
||||
: settings.defaultVideoModel || "happyhorse-1.1-t2v");
|
||||
? settings.defaultImageToVideoModel || "wan3.0-video"
|
||||
: settings.defaultVideoModel || "wan3.0-video");
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
const imageUrl = flags.image;
|
||||
const lastFrameUrl = flags.lastFrame as string | undefined;
|
||||
const fileUrl = flags.file as string | undefined;
|
||||
|
||||
if (fileUrl && (imageUrl || lastFrameUrl)) {
|
||||
throw new BailianError(
|
||||
"--file is mutually exclusive with --image/--last-frame.",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-upload local image file for i2v
|
||||
let resolvedImageUrl: string | undefined;
|
||||
@@ -178,6 +195,18 @@ export default defineCommand({
|
||||
|
||||
// kf2v mode: both --image and --last-frame provided.
|
||||
const isKf2v = Boolean(resolvedImageUrl && resolvedLastFrameUrl);
|
||||
// 万相 3.x(All-in-One)首尾帧走 media[];旧 kf2v 仍走 image2video 平铺字段。
|
||||
// 与 video/ref.ts 的 useReferenceAudio 保持同一判定:忽略大小写、覆盖 wan3.x 系列。
|
||||
const isWan30 = /^wan3\./i.test(model);
|
||||
|
||||
if (fileUrl && !isWan30) {
|
||||
throw new BailianError("--file is only supported by wan3.0-video.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
let resolvedFileUrl: string | undefined;
|
||||
if (fileUrl) {
|
||||
resolvedFileUrl = await ctx.client.uploadFile(fileUrl, model);
|
||||
}
|
||||
|
||||
const watermark = resolveWatermark(flags.watermark);
|
||||
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
|
||||
@@ -187,16 +216,26 @@ export default defineCommand({
|
||||
input: {
|
||||
prompt: prompt,
|
||||
negative_prompt: flags.negativePrompt || undefined,
|
||||
// kf2v: first+last frame flat fields via image2video endpoint.
|
||||
// wan3.0 kf2v: media[first_frame, last_frame] via video-generation endpoint.
|
||||
// legacy kf2v: first_frame_url/last_frame_url via image2video endpoint.
|
||||
// wan2.1~2.6 i2v: flat img_url via video-generation endpoint.
|
||||
// wan2.7+ / happyhorse i2v: media[] via video-generation endpoint.
|
||||
...(isKf2v
|
||||
? { first_frame_url: resolvedImageUrl, last_frame_url: resolvedLastFrameUrl }
|
||||
: resolvedImageUrl
|
||||
? /wan[x]?2\.[1-6]/i.test(model)
|
||||
? { img_url: resolvedImageUrl }
|
||||
: { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
|
||||
: {}),
|
||||
...(resolvedFileUrl
|
||||
? { media: [{ type: "file" as const, url: resolvedFileUrl }] }
|
||||
: isKf2v
|
||||
? isWan30
|
||||
? {
|
||||
media: [
|
||||
{ type: "first_frame" as const, url: resolvedImageUrl! },
|
||||
{ type: "last_frame" as const, url: resolvedLastFrameUrl! },
|
||||
],
|
||||
}
|
||||
: { first_frame_url: resolvedImageUrl, last_frame_url: resolvedLastFrameUrl }
|
||||
: resolvedImageUrl
|
||||
? /wan[x]?2\.[1-6]/i.test(model)
|
||||
? { img_url: resolvedImageUrl }
|
||||
: { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
|
||||
: {}),
|
||||
},
|
||||
parameters: {
|
||||
resolution: flags.resolution || undefined,
|
||||
@@ -210,13 +249,29 @@ export default defineCommand({
|
||||
|
||||
if (settings.dryRun) {
|
||||
let previewBody = body;
|
||||
if (isKf2v) {
|
||||
if (resolvedFileUrl) {
|
||||
previewBody = {
|
||||
...body,
|
||||
input: {
|
||||
...body.input,
|
||||
first_frame_url: redactDataUri(resolvedImageUrl ?? ""),
|
||||
last_frame_url: redactDataUri(resolvedLastFrameUrl ?? ""),
|
||||
media: [{ type: "file" as const, url: redactDataUri(resolvedFileUrl) }],
|
||||
},
|
||||
};
|
||||
} else if (isKf2v) {
|
||||
const redactedFirst = redactDataUri(resolvedImageUrl ?? "");
|
||||
const redactedLast = redactDataUri(resolvedLastFrameUrl ?? "");
|
||||
previewBody = {
|
||||
...body,
|
||||
input: {
|
||||
...body.input,
|
||||
...(isWan30
|
||||
? {
|
||||
media: [
|
||||
{ type: "first_frame" as const, url: redactedFirst },
|
||||
{ type: "last_frame" as const, url: redactedLast },
|
||||
],
|
||||
}
|
||||
: { first_frame_url: redactedFirst, last_frame_url: redactedLast }),
|
||||
},
|
||||
};
|
||||
} else if (resolvedImageUrl) {
|
||||
@@ -243,7 +298,7 @@ export default defineCommand({
|
||||
settings,
|
||||
() =>
|
||||
ctx.client.requestJson<DashScopeAsyncResponse>({
|
||||
path: isKf2v ? image2videoPath() : videoGeneratePath(),
|
||||
path: isKf2v && !isWan30 ? image2videoPath() : videoGeneratePath(),
|
||||
method: "POST",
|
||||
body,
|
||||
async: true,
|
||||
|
||||
@@ -24,8 +24,9 @@ import { BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, BOOL_FLAG_WATERMARK } from "bailia
|
||||
export default defineCommand({
|
||||
description: {
|
||||
"en-US":
|
||||
"Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice",
|
||||
"zh-CN": "参考生视频(happyhorse-1.1-r2v / wan2.6-r2v):支持多主体、多镜头和语音",
|
||||
"Reference-to-video generation (wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice",
|
||||
"zh-CN":
|
||||
"参考生视频(wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v):支持多主体、多镜头和语音",
|
||||
},
|
||||
auth: "apiKey",
|
||||
usageArgs: "--prompt <text> --image <url>... [--ref-video <url>...] [flags]",
|
||||
@@ -34,8 +35,8 @@ export default defineCommand({
|
||||
type: "string",
|
||||
valueHint: "<model>",
|
||||
description: {
|
||||
"en-US": "Model ID (default: happyhorse-1.1-r2v)",
|
||||
"zh-CN": "模型 ID(默认:happyhorse-1.1-r2v)",
|
||||
"en-US": "Model ID (default: wan3.0-video)",
|
||||
"zh-CN": "模型 ID(默认:wan3.0-video)",
|
||||
},
|
||||
},
|
||||
prompt: {
|
||||
@@ -67,16 +68,20 @@ export default defineCommand({
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: {
|
||||
"en-US": "Voice URL for corresponding image (pairs by position)",
|
||||
"zh-CN": "对应图片的语音 URL(按位置配对)",
|
||||
"en-US":
|
||||
"Voice URL for corresponding image (pairs by position). On wan3.0-video emitted as reference_audio (refer to Audio 1, Audio 2 in prompt)",
|
||||
"zh-CN":
|
||||
"对应图片的语音 URL(按位置配对)。在 wan3.0-video 上作为 reference_audio 条目发送(prompt 中用「音频1」「音频2」引用)",
|
||||
},
|
||||
},
|
||||
videoVoice: {
|
||||
type: "array",
|
||||
valueHint: "<url>",
|
||||
description: {
|
||||
"en-US": "Voice URL for corresponding ref-video (pairs by position)",
|
||||
"zh-CN": "对应参考视频的语音 URL(按位置配对)",
|
||||
"en-US":
|
||||
"Voice URL for corresponding ref-video (pairs by position). On wan3.0-video emitted as reference_audio (refer to Audio 1, Audio 2 in prompt)",
|
||||
"zh-CN":
|
||||
"对应参考视频的语音 URL(按位置配对)。在 wan3.0-video 上作为 reference_audio 条目发送(prompt 中用「音频1」「音频2」引用)",
|
||||
},
|
||||
},
|
||||
resolution: {
|
||||
@@ -182,11 +187,16 @@ export default defineCommand({
|
||||
const imageVoices = flags.imageVoice || [];
|
||||
const videoVoices = flags.videoVoice || [];
|
||||
|
||||
const model = flags.model || settings.defaultReferenceToVideoModel || "happyhorse-1.1-r2v";
|
||||
const model = flags.model || settings.defaultReferenceToVideoModel || "wan3.0-video";
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
// wan3.0-video emits voice as standalone reference_audio entries (referenced as Audio 1, Audio 2 in prompt);
|
||||
// other models (wan2.7-r2v / happyhorse-1.1-r2v, etc.) keep the legacy reference_voice field attached to each asset.
|
||||
const useReferenceAudio = /^wan3\./i.test(model);
|
||||
|
||||
// --- Resolve file URLs (auto-upload local files) ---
|
||||
const media: DashScopeVideoRefRequest["input"]["media"] = [];
|
||||
const audioEntries: Array<{ type: "reference_audio"; url: string }> = [];
|
||||
|
||||
// Add reference images
|
||||
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
|
||||
@@ -199,7 +209,11 @@ export default defineCommand({
|
||||
// Pair voice by position
|
||||
if (imageVoices[imageIndex]) {
|
||||
const resolvedVoice = await ctx.client.uploadFile(imageVoices[imageIndex]!, model);
|
||||
entry.reference_voice = resolvedVoice;
|
||||
if (useReferenceAudio) {
|
||||
audioEntries.push({ type: "reference_audio", url: resolvedVoice });
|
||||
} else {
|
||||
entry.reference_voice = resolvedVoice;
|
||||
}
|
||||
}
|
||||
|
||||
media.push(entry);
|
||||
@@ -216,12 +230,23 @@ export default defineCommand({
|
||||
// Pair voice by position
|
||||
if (videoVoices[videoIndex]) {
|
||||
const resolvedVoice = await ctx.client.uploadFile(videoVoices[videoIndex]!, model);
|
||||
entry.reference_voice = resolvedVoice;
|
||||
if (useReferenceAudio) {
|
||||
audioEntries.push({ type: "reference_audio", url: resolvedVoice });
|
||||
} else {
|
||||
entry.reference_voice = resolvedVoice;
|
||||
}
|
||||
}
|
||||
|
||||
media.push(entry);
|
||||
}
|
||||
|
||||
// wan3.0: append reference voices as standalone media entries, ordered image-voice then video-voice (Audio 1, Audio 2, ...)
|
||||
if (useReferenceAudio) {
|
||||
for (const audioEntry of audioEntries) {
|
||||
media.push(audioEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Build request body ---
|
||||
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
|
||||
const watermark = resolveWatermark(flags.watermark);
|
||||
|
||||
@@ -62,6 +62,7 @@ export const VIDEO_ROUTES: E2eRouteExports = {
|
||||
"video ref": "videoRef",
|
||||
"video task get": "videoTaskGet",
|
||||
"video download": "videoDownload",
|
||||
"speech synthesize": "speechSynthesize",
|
||||
};
|
||||
|
||||
export const VISION_ROUTES: E2eRouteExports = {
|
||||
|
||||
@@ -114,46 +114,27 @@ describe("e2e: video generate (i2v)", () => {
|
||||
});
|
||||
|
||||
test.each([
|
||||
// wan2.1~2.6 (legacy) use flat img_url; wan2.7+ and happyhorse use media[].
|
||||
// wan2.1~2.6 (legacy) use flat img_url; wan2.7+ / wan3.0 / happyhorse use media[].
|
||||
// 用普通 dry-run(不绑 token-plan):TP 不支持 wan3.0,绑 TP 会形成错误测试契约。
|
||||
["wan2.5-i2v-preview", "img_url"],
|
||||
["wan2.6-i2v", "img_url"],
|
||||
["wan2.7-i2v", "media"],
|
||||
["wan3.0-video", "media"],
|
||||
["happyhorse-1.1-i2v", "media"],
|
||||
])("video generate --dry-run %s 首帧走 %s 字段", async (model, field) => {
|
||||
const configDir = makeE2eOutputDir(`video-i2v-input-shape-${model}`);
|
||||
writeFileSync(
|
||||
join(configDir, "config.json"),
|
||||
JSON.stringify({
|
||||
"token-plan": {
|
||||
api_key: "sk-sp-e2e-placeholder",
|
||||
base_url: "https://token-plan.cn-beijing.maas.aliyuncs.com",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(
|
||||
VIDEO_ROUTES,
|
||||
[
|
||||
"video",
|
||||
"generate",
|
||||
"--config",
|
||||
"token-plan",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
model,
|
||||
"--image",
|
||||
"https://example.com/placeholder.png",
|
||||
"--prompt",
|
||||
"干跑校验",
|
||||
"--output",
|
||||
"json",
|
||||
],
|
||||
{
|
||||
BAILIAN_CONFIG_DIR: configDir,
|
||||
DASHSCOPE_API_KEY: "",
|
||||
DASHSCOPE_BASE_URL: "",
|
||||
},
|
||||
);
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
model,
|
||||
"--image",
|
||||
"https://example.com/placeholder.png",
|
||||
"--prompt",
|
||||
"干跑校验",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
request?: {
|
||||
@@ -168,6 +149,98 @@ describe("e2e: video generate (i2v)", () => {
|
||||
expect(data.request?.input?.img_url).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("video generate --dry-run --file 走 media file 字段", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--file",
|
||||
"https://example.com/reference.pdf",
|
||||
"--prompt",
|
||||
"文件生视频干跑",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
request?: {
|
||||
model?: string;
|
||||
input?: {
|
||||
media?: Array<{ type?: string; url?: string }>;
|
||||
img_url?: string;
|
||||
first_frame_url?: string;
|
||||
};
|
||||
};
|
||||
}>(stdout);
|
||||
expect(data.request?.model).toBe("wan3.0-video");
|
||||
expect(data.request?.input?.media?.[0]?.type).toBe("file");
|
||||
expect(data.request?.input?.media?.[0]?.url).toBe("https://example.com/reference.pdf");
|
||||
expect(data.request?.input?.img_url).toBeUndefined();
|
||||
expect(data.request?.input?.first_frame_url).toBeUndefined();
|
||||
});
|
||||
|
||||
test("--file 与 --image 互斥时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--file",
|
||||
"https://example.com/reference.pdf",
|
||||
"--image",
|
||||
"https://example.com/placeholder.png",
|
||||
"--prompt",
|
||||
"互斥校验",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--file.*mutually exclusive|--file.*互斥|mutually exclusive/i);
|
||||
});
|
||||
|
||||
test("--file 与 --last-frame 互斥时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--file",
|
||||
"https://example.com/reference.pdf",
|
||||
"--last-frame",
|
||||
"https://example.com/last-frame.png",
|
||||
"--prompt",
|
||||
"互斥校验",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--file.*mutually exclusive|--file.*互斥|mutually exclusive/i);
|
||||
});
|
||||
|
||||
test("非 wan3.0 模型使用 --file 时报用法错误并退出 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan2.6-i2v",
|
||||
"--file",
|
||||
"https://example.com/reference.pdf",
|
||||
"--prompt",
|
||||
"模型限制校验",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(
|
||||
/--file.*only supported by wan3\.0-video|--file.*仅.*wan3\.0-video|only supported by wan3\.0-video/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
@@ -179,7 +252,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-i2v",
|
||||
"wan3.0-video",
|
||||
"--image",
|
||||
"https://example.com/placeholder.png",
|
||||
]);
|
||||
@@ -194,7 +267,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
...cliTimeoutPrefix(),
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"干跑无图",
|
||||
"--output",
|
||||
@@ -208,7 +281,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
expect(data.request?.input?.media).toBeUndefined();
|
||||
});
|
||||
|
||||
test("【happyhorse-1.1-i2v】图片生成视频", async () => {
|
||||
test("【wan3.0-video】图片生成视频", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const png = join(outDir, "e2e-gen.png");
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
@@ -234,7 +307,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-i2v",
|
||||
"wan3.0-video",
|
||||
"--image",
|
||||
imagePath,
|
||||
"--prompt",
|
||||
|
||||
@@ -32,7 +32,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"wan3.0-video",
|
||||
]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--prompt|Usage:/i);
|
||||
@@ -44,8 +44,6 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"generate",
|
||||
"--dry-run",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"--prompt",
|
||||
"干跑校验",
|
||||
"--output",
|
||||
@@ -55,18 +53,18 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
const data = parseStdoutJson<{ request?: { model?: string; input?: { prompt?: string } } }>(
|
||||
stdout,
|
||||
);
|
||||
expect(data.request?.model).toBe("happyhorse-1.1-t2v");
|
||||
expect(data.request?.model).toBe("wan3.0-video");
|
||||
expect(data.request?.input?.prompt).toBe("干跑校验");
|
||||
});
|
||||
|
||||
test("【happyhorse-1.1-t2v】文本生成视频", async () => {
|
||||
test("【wan3.0-video】文本生成视频", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-t2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"夕阳下海面波光,远景静态镜头",
|
||||
"--download",
|
||||
|
||||
@@ -45,6 +45,87 @@ describe("e2e: video ref (r2v)", () => {
|
||||
expect(data.request?.input?.media?.[0]?.url).toBe("https://example.com/person.png");
|
||||
});
|
||||
|
||||
test("video ref --dry-run 在 wan3.0-video 上将 --image-voice 转为独立 reference_audio 条目", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"Image 1 speaks with Audio 1",
|
||||
"--image",
|
||||
"https://example.com/a.jpg",
|
||||
"--ref-video",
|
||||
"https://example.com/b.mp4",
|
||||
"--image-voice",
|
||||
"https://example.com/va.mp3",
|
||||
"--video-voice",
|
||||
"https://example.com/vb.mp3",
|
||||
"--resolution",
|
||||
"720P",
|
||||
"--ratio",
|
||||
"16:9",
|
||||
"--duration",
|
||||
"5",
|
||||
"--prompt-extend",
|
||||
"true",
|
||||
"--watermark",
|
||||
"false",
|
||||
"--seed",
|
||||
"42",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
request?: {
|
||||
input?: {
|
||||
media?: Array<{ type?: string; url?: string; reference_voice?: string }>;
|
||||
};
|
||||
parameters?: { prompt_extend?: boolean; watermark?: boolean; seed?: number };
|
||||
};
|
||||
}>(stdout);
|
||||
const media = data.request?.input?.media ?? [];
|
||||
// wan3.0: image/video 条目不挂 reference_voice
|
||||
const imageEntry = media.find((m) => m.type === "reference_image");
|
||||
expect(imageEntry?.reference_voice).toBeUndefined();
|
||||
// 音色作为独立 reference_audio 条目,按 image-voice 再 video-voice 顺序追加
|
||||
const audioEntries = media.filter((m) => m.type === "reference_audio");
|
||||
expect(audioEntries).toHaveLength(2);
|
||||
expect(audioEntries[0]?.url).toBe("https://example.com/va.mp3");
|
||||
expect(audioEntries[1]?.url).toBe("https://example.com/vb.mp3");
|
||||
// 全部生成参数进入 parameters
|
||||
expect(data.request?.parameters?.prompt_extend).toBe(true);
|
||||
expect(data.request?.parameters?.watermark).toBe(false);
|
||||
expect(data.request?.parameters?.seed).toBe(42);
|
||||
});
|
||||
|
||||
test("video ref --dry-run 在 wan2.7-r2v 上沿用 reference_voice 字段(既有逻辑)", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
"--dry-run",
|
||||
"--model",
|
||||
"wan2.7-r2v",
|
||||
"--prompt",
|
||||
"Image 1 speaks",
|
||||
"--image",
|
||||
"https://example.com/a.jpg",
|
||||
"--image-voice",
|
||||
"https://example.com/va.mp3",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{
|
||||
request?: { input?: { media?: Array<{ type?: string; reference_voice?: string }> } };
|
||||
}>(stdout);
|
||||
const media = data.request?.input?.media ?? [];
|
||||
expect(media).toHaveLength(1);
|
||||
expect(media[0]?.reference_voice).toBe("https://example.com/va.mp3");
|
||||
});
|
||||
|
||||
test("Token Plan 使用独立的参考生视频默认模型", async () => {
|
||||
const configDir = makeE2eOutputDir("video-r2v-token-plan-default");
|
||||
writeFileSync(
|
||||
@@ -141,7 +222,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-r2v",
|
||||
"wan3.0-video",
|
||||
"--image",
|
||||
"https://example.com/x.png",
|
||||
]);
|
||||
@@ -155,7 +236,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-r2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"仅有描述无素材",
|
||||
]);
|
||||
@@ -163,7 +244,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
expect(stderr).toMatch(/--image|ref-video|At least one|required/i);
|
||||
});
|
||||
|
||||
test("【happyhorse-1.1-r2v】视频参考生成", async () => {
|
||||
test("【wan3.0-video】视频参考生成", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"image",
|
||||
@@ -189,7 +270,7 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"happyhorse-1.1-r2v",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"图1在画面中心轻微晃动",
|
||||
"--image",
|
||||
@@ -204,5 +285,125 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
|
||||
expect(data.status).toBe("SUCCEEDED");
|
||||
expect(data.video_url?.startsWith("https://")).toBe(true);
|
||||
}, 3_600_000);
|
||||
|
||||
test("【wan3.0-video】全参数参考生视频(图+视频+双音色+全部生成参数)", async () => {
|
||||
const outDir = makeE2eOutputDir(e2eLabelFromMetaUrl(import.meta.url));
|
||||
|
||||
// 1. 参考图
|
||||
const gen = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"image",
|
||||
"generate",
|
||||
"--model",
|
||||
"qwen-image-3.0",
|
||||
"--prompt",
|
||||
"一片绿色的树叶,白底",
|
||||
"--out-dir",
|
||||
outDir,
|
||||
"--out-prefix",
|
||||
"e2e-ref-img",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(gen.exitCode, gen.stderr).toBe(0);
|
||||
const imagePath = parseStdoutJson<{ saved?: string[] }>(gen.stdout).saved?.[0];
|
||||
expect(imagePath).toBeTruthy();
|
||||
|
||||
// 2. 参考视频(t2v 生成一段素材)
|
||||
const vid = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"generate",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"海面波光粼粼,远景静态镜头",
|
||||
"--download",
|
||||
join(outDir, "e2e-ref-video.mp4"),
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(vid.exitCode, vid.stderr).toBe(0);
|
||||
const refVideoPath = parseStdoutJson<{ saved?: string }>(vid.stdout).saved;
|
||||
expect(refVideoPath).toBeTruthy();
|
||||
|
||||
// 3. 两个参考音色(文本足够长确保 ≥1s,合计 ≤15s)
|
||||
const vaOut = join(outDir, "e2e-va.mp3");
|
||||
const va = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
"cosyvoice-v3-flash",
|
||||
"--voice",
|
||||
"longwan_v3",
|
||||
"--text",
|
||||
"你好,很高兴见到你,今天我们一起聊聊吧",
|
||||
"--out",
|
||||
vaOut,
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(va.exitCode, va.stderr).toBe(0);
|
||||
expect(parseStdoutJson<{ saved?: string }>(va.stdout).saved).toBe(vaOut);
|
||||
|
||||
const vbOut = join(outDir, "e2e-vb.mp3");
|
||||
const vb = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"speech",
|
||||
"synthesize",
|
||||
"--model",
|
||||
"cosyvoice-v3-flash",
|
||||
"--voice",
|
||||
"longxiaochun_v3",
|
||||
"--text",
|
||||
"你好呀,今天天气真不错,我们出去走走吧",
|
||||
"--out",
|
||||
vbOut,
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(vb.exitCode, vb.stderr).toBe(0);
|
||||
expect(parseStdoutJson<{ saved?: string }>(vb.stdout).saved).toBe(vbOut);
|
||||
|
||||
// 4. 全参数 video ref:图1/视频1/音频1/音频2 + 全部生成参数
|
||||
const { stdout, stderr, exitCode } = await runCommandE2e(VIDEO_ROUTES, [
|
||||
"video",
|
||||
"ref",
|
||||
...cliTimeoutPrefix(),
|
||||
"--model",
|
||||
"wan3.0-video",
|
||||
"--prompt",
|
||||
"图1中的人物看向视频1中的角色,并用音频1的音色说:你好,视频1中的角色用音频2的音色回应:你好呀",
|
||||
"--image",
|
||||
imagePath!,
|
||||
"--ref-video",
|
||||
refVideoPath!,
|
||||
"--image-voice",
|
||||
vaOut,
|
||||
"--video-voice",
|
||||
vbOut,
|
||||
"--resolution",
|
||||
"720P",
|
||||
"--ratio",
|
||||
"16:9",
|
||||
"--duration",
|
||||
"5",
|
||||
"--prompt-extend",
|
||||
"true",
|
||||
"--watermark",
|
||||
"false",
|
||||
"--seed",
|
||||
"42",
|
||||
"--poll-interval",
|
||||
"10",
|
||||
"--download",
|
||||
join(outDir, "e2e-video-ref-full.mp4"),
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{ status?: string; video_url?: string; saved?: string }>(stdout);
|
||||
expect(data.status).toBe("SUCCEEDED");
|
||||
expect(data.video_url?.startsWith("https://")).toBe(true);
|
||||
expect(data.saved).toBeTruthy();
|
||||
}, 3_600_000);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -209,7 +209,14 @@ export interface DashScopeVideoRequest {
|
||||
first_frame_url?: string;
|
||||
last_frame_url?: string;
|
||||
media?: Array<{
|
||||
type: "image" | "video" | "first_frame" | "last_frame" | "driving_audio" | "first_clip";
|
||||
type:
|
||||
| "image"
|
||||
| "video"
|
||||
| "first_frame"
|
||||
| "last_frame"
|
||||
| "driving_audio"
|
||||
| "first_clip"
|
||||
| "file";
|
||||
url: string;
|
||||
}>;
|
||||
};
|
||||
@@ -228,7 +235,7 @@ export interface DashScopeVideoRefRequest {
|
||||
input: {
|
||||
prompt: string;
|
||||
media: Array<{
|
||||
type: "reference_image" | "reference_video";
|
||||
type: "reference_image" | "reference_video" | "reference_audio";
|
||||
url: string;
|
||||
reference_voice?: string;
|
||||
}>;
|
||||
|
||||
@@ -433,7 +433,7 @@ export async function videoGenerate(
|
||||
});
|
||||
}
|
||||
|
||||
const model = input.model || (input.image ? "happyhorse-1.1-i2v" : "happyhorse-1.1-t2v");
|
||||
const model = input.model || "wan3.0-video";
|
||||
|
||||
let resolvedImageUrl: string | undefined;
|
||||
if (input.image) {
|
||||
|
||||
@@ -33,9 +33,9 @@ description: >-
|
||||
| ---------------------------------------- | ----------------------------------------- | --------------------------------------------------- |
|
||||
| Text-to-image | `bl image generate` | `qwen-image-3.0` |
|
||||
| Image edit / multi-image merge | `bl image edit` (repeat `--image`) | `qwen-image-3.0` |
|
||||
| Text-to-video / image-to-video | `bl video generate` | `happyhorse-1.1-t2v` / `-i2v` (with `--image`) |
|
||||
| Text-to-video / image-to-video | `bl video generate` | `wan3.0-video` |
|
||||
| Video edit / style transfer | `bl video edit` | `happyhorse-1.0-video-edit` |
|
||||
| Reference-to-video + voice | `bl video ref` | `happyhorse-1.1-r2v` |
|
||||
| Reference-to-video + voice | `bl video ref` | `wan3.0-video` |
|
||||
| Speech synthesis (TTS / voiceover) | `bl speech synthesize` | `cosyvoice-v3-flash` |
|
||||
| Speech recognition (ASR / transcription) | `bl speech recognize` | `fun-asr` |
|
||||
| Image describe | `bl vision describe` | `qwen3-vl-plus`;宿主能做且未点名 → host-first |
|
||||
|
||||
@@ -9,19 +9,19 @@ Use this index for the skill-scoped quick index and global flags.
|
||||
|
||||
## Quick index
|
||||
|
||||
| Command | Authentication | Description | Detail |
|
||||
| ---------------------- | -------------- | ----------------------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| `bl image edit` | API Key | Edit an existing image with text instructions (Qwen-Image / Wan 2.7) | [image.md](image.md) |
|
||||
| `bl image generate` | API Key | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl omni` | API Key | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl speech recognize` | API Key | Recognize speech from audio files (FunAudio-ASR / Qwen-ASR Flash) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | API Key | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl video download` | API Key | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | API Key | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | API Key | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) | [video.md](video.md) |
|
||||
| `bl video ref` | API Key | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | API Key | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | API Key | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
| Command | Authentication | Description | Detail |
|
||||
| ---------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| `bl image edit` | API Key | Edit an existing image with text instructions (Qwen-Image / Wan 2.7) | [image.md](image.md) |
|
||||
| `bl image generate` | API Key | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl omni` | API Key | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl speech recognize` | API Key | Recognize speech from audio files (FunAudio-ASR / Qwen-ASR Flash) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | API Key | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl video download` | API Key | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | API Key | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | API Key | Generate a video from text or image (wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v) | [video.md](video.md) |
|
||||
| `bl video ref` | API Key | Reference-to-video generation (wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | API Key | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | API Key | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
|
||||
## By group
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Authentication | Description |
|
||||
| ------------------- | -------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `bl video download` | API Key | Download a completed video by task ID |
|
||||
| `bl video edit` | API Key | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) |
|
||||
| `bl video generate` | API Key | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) |
|
||||
| `bl video ref` | API Key | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice |
|
||||
| `bl video task get` | API Key | Query async task status |
|
||||
| Command | Authentication | Description |
|
||||
| ------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| `bl video download` | API Key | Download a completed video by task ID |
|
||||
| `bl video edit` | API Key | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) |
|
||||
| `bl video generate` | API Key | Generate a video from text or image (wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v) |
|
||||
| `bl video ref` | API Key | Reference-to-video generation (wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice |
|
||||
| `bl video task get` | API Key | Query async task status |
|
||||
|
||||
## Command details
|
||||
|
||||
@@ -97,34 +97,35 @@ bl video edit --video https://example.com/input.mp4 --prompt "Put clothes on the
|
||||
|
||||
### `bl video generate`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| **Name** | `video generate` |
|
||||
| **Description** | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl video generate --prompt <text> [--image <url>] [flags]` |
|
||||
| Field | Value |
|
||||
| ------------------ | ------------------------------------------------------------------------------------ |
|
||||
| **Name** | `video generate` |
|
||||
| **Description** | Generate a video from text or image (wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v) |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl video generate --prompt <text> [--image <url>] [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: happyhorse-1.1-t2v, or happyhorse-1.1-i2v with --image) |
|
||||
| `--prompt <text>` | string | yes | Video description |
|
||||
| `--image <url>` | string | no | Input image URL for image-to-video generation |
|
||||
| `--last-frame <url>` | string | no | Last frame image URL (with --image, enables kf2v first+last frame mode) |
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (e.g. 16:9, 9:16, 1:1) |
|
||||
| `--duration <seconds>` | number | no | Video duration in seconds (default: 5) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--async` | switch | no | Return async task id without waiting |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 5) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: wan3.0-video) |
|
||||
| `--prompt <text>` | string | yes | Video description |
|
||||
| `--image <url>` | string | no | Input image URL for image-to-video generation |
|
||||
| `--last-frame <url>` | string | no | Last frame image URL (with --image, enables kf2v first+last frame mode) |
|
||||
| `--negative-prompt <text>` | string | no | Negative prompt to exclude unwanted content |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (e.g. 16:9, 9:16, 1:1) |
|
||||
| `--duration <seconds>` | number | no | Video duration in seconds (default: 5) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--file <url-or-path>` | string | no | Reference file URL or local path for file-to-video (wan3.0-video only; mutually exclusive with --image/--last-frame) |
|
||||
| `--async` | switch | no | Return async task id without waiting |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 5) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -150,35 +151,35 @@ bl video generate --prompt "A cat playing with a ball" --watermark false
|
||||
|
||||
### `bl video ref`
|
||||
|
||||
| Field | Value |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `video ref` |
|
||||
| **Description** | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl video ref --prompt <text> --image <url>... [--ref-video <url>...] [flags]` |
|
||||
| Field | Value |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `video ref` |
|
||||
| **Description** | Reference-to-video generation (wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice |
|
||||
| **Authentication** | API Key |
|
||||
| **Usage** | `bl video ref --prompt <text> --image <url>... [--ref-video <url>...] [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: happyhorse-1.1-r2v) |
|
||||
| `--prompt <text>` | string | yes | Video description with reference markers (image1, video1, etc.) |
|
||||
| `--image <url>` | array | no | Reference image URL or local file (repeatable for multiple subjects) |
|
||||
| `--ref-video <url>` | array | no | Reference video URL or local file (repeatable) |
|
||||
| `--image-voice <url>` | array | no | Voice URL for corresponding image (pairs by position) |
|
||||
| `--video-voice <url>` | array | no | Voice URL for corresponding ref-video (pairs by position) |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (16:9, 9:16, 1:1) |
|
||||
| `--duration <seconds>` | number | no | Video duration in seconds (default: 5) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--async` | switch | no | Return async task id without waiting |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 15) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
| Flag | Type | Required | Description |
|
||||
| --------------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `--model <model>` | string | no | Model ID (default: wan3.0-video) |
|
||||
| `--prompt <text>` | string | yes | Video description with reference markers (image1, video1, etc.) |
|
||||
| `--image <url>` | array | no | Reference image URL or local file (repeatable for multiple subjects) |
|
||||
| `--ref-video <url>` | array | no | Reference video URL or local file (repeatable) |
|
||||
| `--image-voice <url>` | array | no | Voice URL for corresponding image (pairs by position). On wan3.0-video emitted as reference_audio (refer to Audio 1, Audio 2 in prompt) |
|
||||
| `--video-voice <url>` | array | no | Voice URL for corresponding ref-video (pairs by position). On wan3.0-video emitted as reference_audio (refer to Audio 1, Audio 2 in prompt) |
|
||||
| `--resolution <res>` | string | no | Resolution: 720P or 1080P (default: 1080P) |
|
||||
| `--ratio <ratio>` | string | no | Aspect ratio (16:9, 9:16, 1:1) |
|
||||
| `--duration <seconds>` | number | no | Video duration in seconds (default: 5) |
|
||||
| `--prompt-extend <bool>` | boolean | no | Enable prompt extend (true/false). Omit flag to omit the parameter (DashScope default). |
|
||||
| `--watermark <bool>` | boolean | no | Enable watermark (true/false). Omit flag to use CLI default (true). |
|
||||
| `--seed <n>` | number | no | Random seed for reproducible generation |
|
||||
| `--download <path>` | string | no | Save video to file on completion |
|
||||
| `--async` | switch | no | Return async task id without waiting |
|
||||
| `--concurrent <n>` | number | no | Run N parallel requests (default: 1) |
|
||||
| `--poll-interval <seconds>` | number | no | Polling interval when waiting (default: 15) |
|
||||
| `--api-key <key>` | string | no | API key |
|
||||
| `--base-url <url>` | string | no | API base URL |
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
Reference in New Issue
Block a user