Skip to content

Commit fe11227

Browse files
Merge pull request #174 from modelstudioai/feat/update-def-video-model
Switch default video model to wan3.0-video
2 parents cc209ce + 0d24f46 commit fe11227

17 files changed

Lines changed: 535 additions & 162 deletions

File tree

packages/cli/tests/stress/lib/fixtures.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export async function ensurePrerequisites(ctx) {
178178
"video",
179179
"generate",
180180
"--model",
181-
"happyhorse-1.1-t2v",
181+
"wan3.0-video",
182182
"--prompt",
183183
"压测前置短视频:海浪与静态远景,无明显人物。",
184184
"--duration",

packages/cli/tests/stress/lib/suite-fixtures.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export async function generateCombinedFixtures({ suiteRoot, cliPackage }) {
130130
"video",
131131
"generate",
132132
"--model",
133-
"happyhorse-1.1-t2v",
133+
"wan3.0-video",
134134
"--prompt",
135135
"压测前置短视频:海浪与静态远景,无明显人物。",
136136
"--duration",

packages/cli/tests/stress/targets/video-i2v.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const motions = [
1616

1717
export const runStress = defineStressTarget({
1818
canonical: "video-i2v",
19-
defaultModel: "happyhorse-1.1-i2v",
19+
defaultModel: "wan3.0-video",
2020
batchDirPrefix: "video-i2v-batch",
2121
helpText: "pnpm run test:stress -- video-i2v [--reuse-fixtures] -- --count 5 -c 2",
2222

packages/cli/tests/stress/targets/video-ref.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const prompts = [
1616

1717
export const runStress = defineStressTarget({
1818
canonical: "video-ref",
19-
defaultModel: "happyhorse-1.1-r2v",
19+
defaultModel: "wan3.0-video",
2020
batchDirPrefix: "video-ref-batch",
2121
helpText: "pnpm run test:stress -- video-ref [--reuse-fixtures] -- --count 5 -c 2",
2222

packages/cli/tests/stress/targets/video-t2v.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
4545

4646
export const runStress = defineStressTarget({
4747
canonical: "video-t2v",
48-
defaultModel: "happyhorse-1.1-t2v",
48+
defaultModel: "wan3.0-video",
4949
batchDirPrefix: "video-t2v-batch",
5050
helpText: `用法:pnpm run test:stress -- video-t2v -- --concurrency 1 --count 3
5151
详见 docs/agents/stress-batch-tests.md`,

packages/commands/src/commands/config/shared.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ export const UI_BOOLEAN_KEYS = new Set<string>(["telemetry"]);
6969
export const UI_MODEL_DEFAULTS: Record<string, string> = {
7070
default_text_model: "qwen3.8-max",
7171
default_image_model: "qwen-image-3.0",
72-
default_video_model: "happyhorse-1.1-t2v",
72+
default_video_model: "wan3.0-video",
73+
default_image_to_video_model: "wan3.0-video",
74+
default_reference_to_video_model: "wan3.0-video",
7375
default_speech_model: "cosyvoice-v3-flash",
7476
default_omni_model: "qwen3.5-omni-plus",
7577
};
@@ -100,8 +102,18 @@ export const UI_MODEL_CATALOG: Record<string, ModelOption[]> = {
100102
{ id: "wanx2.x", role: "image/generate · async series" },
101103
],
102104
default_video_model: [
103-
{ id: "happyhorse-1.1-t2v", role: "video/generate default · text-to-video" },
105+
{ id: "wan3.0-video", role: "video/generate · t2v / i2v / r2v default" },
106+
{ id: "happyhorse-1.1-t2v", role: "video/generate · text-to-video" },
104107
{ id: "happyhorse-1.1-i2v", role: "video/generate · image-to-video" },
108+
{ id: "happyhorse-1.1-r2v", role: "video/ref · reference-to-video" },
109+
],
110+
default_image_to_video_model: [
111+
{ id: "wan3.0-video", role: "video/generate default · image-to-video" },
112+
{ id: "happyhorse-1.1-i2v", role: "video/generate · image-to-video" },
113+
],
114+
default_reference_to_video_model: [
115+
{ id: "wan3.0-video", role: "video/ref default · reference-to-video" },
116+
{ id: "happyhorse-1.1-r2v", role: "video/ref · reference-to-video" },
105117
],
106118
default_speech_model: [
107119
{ id: "cosyvoice-v3-flash", role: "speech/synthesize (TTS) default" },

packages/commands/src/commands/video/generate.ts

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ import { BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, BOOL_FLAG_WATERMARK } from "bailia
2424

2525
export default defineCommand({
2626
description: {
27-
"en-US":
28-
"Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v)",
29-
"zh-CN": "根据文本或图片生成视频(happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v)",
27+
"en-US": "Generate a video from text or image (wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v)",
28+
"zh-CN": "根据文本或图片生成视频(wan3.0-video / wan2.6-t2v / happyhorse-1.1-i2v)",
3029
},
3130
auth: "apiKey",
3231
usageArgs: "--prompt <text> [--image <url>] [flags]",
@@ -35,8 +34,8 @@ export default defineCommand({
3534
type: "string",
3635
valueHint: "<model>",
3736
description: {
38-
"en-US": "Model ID (default: happyhorse-1.1-t2v, or happyhorse-1.1-i2v with --image)",
39-
"zh-CN": "模型 ID(默认:happyhorse-1.1-t2v;使用 --image 时为 happyhorse-1.1-i2v)",
37+
"en-US": "Model ID (default: wan3.0-video)",
38+
"zh-CN": "模型 ID(默认:wan3.0-video)",
4039
},
4140
},
4241
prompt: {
@@ -119,6 +118,16 @@ export default defineCommand({
119118
"zh-CN": "完成后将视频保存到文件",
120119
},
121120
},
121+
file: {
122+
type: "string",
123+
valueHint: "<url-or-path>",
124+
description: {
125+
"en-US":
126+
"Reference file URL or local path for file-to-video (wan3.0-video only; mutually exclusive with --image/--last-frame)",
127+
"zh-CN":
128+
"参考文件 URL 或本地路径,用于文件生视频(仅 wan3.0-video;与 --image/--last-frame 互斥)",
129+
},
130+
},
122131
...ASYNC_FLAG,
123132
...CONCURRENT_FLAG,
124133
pollInterval: {
@@ -159,12 +168,20 @@ export default defineCommand({
159168
const model =
160169
flags.model ||
161170
(flags.image
162-
? settings.defaultImageToVideoModel || "happyhorse-1.1-i2v"
163-
: settings.defaultVideoModel || "happyhorse-1.1-t2v");
171+
? settings.defaultImageToVideoModel || "wan3.0-video"
172+
: settings.defaultVideoModel || "wan3.0-video");
164173
const format = detectOutputFormat(settings.output);
165174

166175
const imageUrl = flags.image;
167176
const lastFrameUrl = flags.lastFrame as string | undefined;
177+
const fileUrl = flags.file as string | undefined;
178+
179+
if (fileUrl && (imageUrl || lastFrameUrl)) {
180+
throw new BailianError(
181+
"--file is mutually exclusive with --image/--last-frame.",
182+
ExitCode.USAGE,
183+
);
184+
}
168185

169186
// Auto-upload local image file for i2v
170187
let resolvedImageUrl: string | undefined;
@@ -178,6 +195,18 @@ export default defineCommand({
178195

179196
// kf2v mode: both --image and --last-frame provided.
180197
const isKf2v = Boolean(resolvedImageUrl && resolvedLastFrameUrl);
198+
// 万相 3.x(All-in-One)首尾帧走 media[];旧 kf2v 仍走 image2video 平铺字段。
199+
// 与 video/ref.ts 的 useReferenceAudio 保持同一判定:忽略大小写、覆盖 wan3.x 系列。
200+
const isWan30 = /^wan3\./i.test(model);
201+
202+
if (fileUrl && !isWan30) {
203+
throw new BailianError("--file is only supported by wan3.0-video.", ExitCode.USAGE);
204+
}
205+
206+
let resolvedFileUrl: string | undefined;
207+
if (fileUrl) {
208+
resolvedFileUrl = await ctx.client.uploadFile(fileUrl, model);
209+
}
181210

182211
const watermark = resolveWatermark(flags.watermark);
183212
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
@@ -187,16 +216,26 @@ export default defineCommand({
187216
input: {
188217
prompt: prompt,
189218
negative_prompt: flags.negativePrompt || undefined,
190-
// kf2v: first+last frame flat fields via image2video endpoint.
219+
// wan3.0 kf2v: media[first_frame, last_frame] via video-generation endpoint.
220+
// legacy kf2v: first_frame_url/last_frame_url via image2video endpoint.
191221
// wan2.1~2.6 i2v: flat img_url via video-generation endpoint.
192222
// wan2.7+ / happyhorse i2v: media[] via video-generation endpoint.
193-
...(isKf2v
194-
? { first_frame_url: resolvedImageUrl, last_frame_url: resolvedLastFrameUrl }
195-
: resolvedImageUrl
196-
? /wan[x]?2\.[1-6]/i.test(model)
197-
? { img_url: resolvedImageUrl }
198-
: { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
199-
: {}),
223+
...(resolvedFileUrl
224+
? { media: [{ type: "file" as const, url: resolvedFileUrl }] }
225+
: isKf2v
226+
? isWan30
227+
? {
228+
media: [
229+
{ type: "first_frame" as const, url: resolvedImageUrl! },
230+
{ type: "last_frame" as const, url: resolvedLastFrameUrl! },
231+
],
232+
}
233+
: { first_frame_url: resolvedImageUrl, last_frame_url: resolvedLastFrameUrl }
234+
: resolvedImageUrl
235+
? /wan[x]?2\.[1-6]/i.test(model)
236+
? { img_url: resolvedImageUrl }
237+
: { media: [{ type: "first_frame" as const, url: resolvedImageUrl }] }
238+
: {}),
200239
},
201240
parameters: {
202241
resolution: flags.resolution || undefined,
@@ -210,13 +249,29 @@ export default defineCommand({
210249

211250
if (settings.dryRun) {
212251
let previewBody = body;
213-
if (isKf2v) {
252+
if (resolvedFileUrl) {
253+
previewBody = {
254+
...body,
255+
input: {
256+
...body.input,
257+
media: [{ type: "file" as const, url: redactDataUri(resolvedFileUrl) }],
258+
},
259+
};
260+
} else if (isKf2v) {
261+
const redactedFirst = redactDataUri(resolvedImageUrl ?? "");
262+
const redactedLast = redactDataUri(resolvedLastFrameUrl ?? "");
214263
previewBody = {
215264
...body,
216265
input: {
217266
...body.input,
218-
first_frame_url: redactDataUri(resolvedImageUrl ?? ""),
219-
last_frame_url: redactDataUri(resolvedLastFrameUrl ?? ""),
267+
...(isWan30
268+
? {
269+
media: [
270+
{ type: "first_frame" as const, url: redactedFirst },
271+
{ type: "last_frame" as const, url: redactedLast },
272+
],
273+
}
274+
: { first_frame_url: redactedFirst, last_frame_url: redactedLast }),
220275
},
221276
};
222277
} else if (resolvedImageUrl) {
@@ -243,7 +298,7 @@ export default defineCommand({
243298
settings,
244299
() =>
245300
ctx.client.requestJson<DashScopeAsyncResponse>({
246-
path: isKf2v ? image2videoPath() : videoGeneratePath(),
301+
path: isKf2v && !isWan30 ? image2videoPath() : videoGeneratePath(),
247302
method: "POST",
248303
body,
249304
async: true,

packages/commands/src/commands/video/ref.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ import { BOOL_FLAG_PROMPT_EXTEND_API_DEFAULT, BOOL_FLAG_WATERMARK } from "bailia
2424
export default defineCommand({
2525
description: {
2626
"en-US":
27-
"Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice",
28-
"zh-CN": "参考生视频(happyhorse-1.1-r2v / wan2.6-r2v):支持多主体、多镜头和语音",
27+
"Reference-to-video generation (wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice",
28+
"zh-CN":
29+
"参考生视频(wan3.0-video / happyhorse-1.1-r2v / wan2.6-r2v):支持多主体、多镜头和语音",
2930
},
3031
auth: "apiKey",
3132
usageArgs: "--prompt <text> --image <url>... [--ref-video <url>...] [flags]",
@@ -34,8 +35,8 @@ export default defineCommand({
3435
type: "string",
3536
valueHint: "<model>",
3637
description: {
37-
"en-US": "Model ID (default: happyhorse-1.1-r2v)",
38-
"zh-CN": "模型 ID(默认:happyhorse-1.1-r2v)",
38+
"en-US": "Model ID (default: wan3.0-video)",
39+
"zh-CN": "模型 ID(默认:wan3.0-video)",
3940
},
4041
},
4142
prompt: {
@@ -67,16 +68,20 @@ export default defineCommand({
6768
type: "array",
6869
valueHint: "<url>",
6970
description: {
70-
"en-US": "Voice URL for corresponding image (pairs by position)",
71-
"zh-CN": "对应图片的语音 URL(按位置配对)",
71+
"en-US":
72+
"Voice URL for corresponding image (pairs by position). On wan3.0-video emitted as reference_audio (refer to Audio 1, Audio 2 in prompt)",
73+
"zh-CN":
74+
"对应图片的语音 URL(按位置配对)。在 wan3.0-video 上作为 reference_audio 条目发送(prompt 中用「音频1」「音频2」引用)",
7275
},
7376
},
7477
videoVoice: {
7578
type: "array",
7679
valueHint: "<url>",
7780
description: {
78-
"en-US": "Voice URL for corresponding ref-video (pairs by position)",
79-
"zh-CN": "对应参考视频的语音 URL(按位置配对)",
81+
"en-US":
82+
"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)",
83+
"zh-CN":
84+
"对应参考视频的语音 URL(按位置配对)。在 wan3.0-video 上作为 reference_audio 条目发送(prompt 中用「音频1」「音频2」引用)",
8085
},
8186
},
8287
resolution: {
@@ -182,11 +187,16 @@ export default defineCommand({
182187
const imageVoices = flags.imageVoice || [];
183188
const videoVoices = flags.videoVoice || [];
184189

185-
const model = flags.model || settings.defaultReferenceToVideoModel || "happyhorse-1.1-r2v";
190+
const model = flags.model || settings.defaultReferenceToVideoModel || "wan3.0-video";
186191
const format = detectOutputFormat(settings.output);
187192

193+
// wan3.0-video emits voice as standalone reference_audio entries (referenced as Audio 1, Audio 2 in prompt);
194+
// other models (wan2.7-r2v / happyhorse-1.1-r2v, etc.) keep the legacy reference_voice field attached to each asset.
195+
const useReferenceAudio = /^wan3\./i.test(model);
196+
188197
// --- Resolve file URLs (auto-upload local files) ---
189198
const media: DashScopeVideoRefRequest["input"]["media"] = [];
199+
const audioEntries: Array<{ type: "reference_audio"; url: string }> = [];
190200

191201
// Add reference images
192202
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
@@ -199,7 +209,11 @@ export default defineCommand({
199209
// Pair voice by position
200210
if (imageVoices[imageIndex]) {
201211
const resolvedVoice = await ctx.client.uploadFile(imageVoices[imageIndex]!, model);
202-
entry.reference_voice = resolvedVoice;
212+
if (useReferenceAudio) {
213+
audioEntries.push({ type: "reference_audio", url: resolvedVoice });
214+
} else {
215+
entry.reference_voice = resolvedVoice;
216+
}
203217
}
204218

205219
media.push(entry);
@@ -216,12 +230,23 @@ export default defineCommand({
216230
// Pair voice by position
217231
if (videoVoices[videoIndex]) {
218232
const resolvedVoice = await ctx.client.uploadFile(videoVoices[videoIndex]!, model);
219-
entry.reference_voice = resolvedVoice;
233+
if (useReferenceAudio) {
234+
audioEntries.push({ type: "reference_audio", url: resolvedVoice });
235+
} else {
236+
entry.reference_voice = resolvedVoice;
237+
}
220238
}
221239

222240
media.push(entry);
223241
}
224242

243+
// wan3.0: append reference voices as standalone media entries, ordered image-voice then video-voice (Audio 1, Audio 2, ...)
244+
if (useReferenceAudio) {
245+
for (const audioEntry of audioEntries) {
246+
media.push(audioEntry);
247+
}
248+
}
249+
225250
// --- Build request body ---
226251
const promptExtend = resolveBooleanFlag(flags.promptExtend, undefined, "prompt-extend");
227252
const watermark = resolveWatermark(flags.watermark);

packages/commands/tests/e2e/topic-routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const VIDEO_ROUTES: E2eRouteExports = {
6262
"video ref": "videoRef",
6363
"video task get": "videoTaskGet",
6464
"video download": "videoDownload",
65+
"speech synthesize": "speechSynthesize",
6566
};
6667

6768
export const VISION_ROUTES: E2eRouteExports = {

0 commit comments

Comments
 (0)