Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.

Commit 416f800

Browse files
rootclaude
authored andcommitted
feat: 视频模型分站点支持及代码优化
## 视频模型更新 - 移除 jimeng-video-3.5 模型(国内站和国际站) - 美国站仅保留 jimeng-video-3.5-pro 和 jimeng-video-3.0 - 亚洲国际站(HK/JP/SG)新增 veo3、veo3.1、sora2 模型 - 各模型时长支持: - veo3/veo3.1: 固定 8 秒 - sora2: 4/8/12 秒(默认 4 秒) - 3.5-pro: 5/10/12 秒(默认 5 秒) - 其他: 5/10 秒(默认 5 秒) - resolution 参数仅 video-3.0 和 video-3.0-fast 支持 ## Bug 修复 - 修复 getTokenLiveStatus 重复调用 checkResult 导致始终返回 false - 修复轮询逻辑因 API 最终一致性导致的早期误判失败 - 路由层增加文件数量上限校验(最多 2 个) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ef23d08 commit 416f800

6 files changed

Lines changed: 154 additions & 43 deletions

File tree

README.CN.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -423,8 +423,12 @@ A: 可以。现在支持直接上传本地文件。请参考上方的“本地
423423
- `model` (string): 使用的视频模型名称。
424424
- `prompt` (string): 视频内容的文本描述。
425425
- `ratio` (string, 可选): 视频比例,默认为 `"1:1"`。支持的比例:`1:1`, `4:3`, `3:4`, `16:9`, `9:16`, `21:9`。**注意**:在图生视频模式下(有图片输入时),此参数将被忽略,视频比例由输入图片的实际比例决定。
426-
- `resolution` (string, 可选): 视频分辨率,默认为 `"720p"`。支持的分辨率:`720p`, `1080p`。
427-
- `duration` (number, 可选): 视频时长(秒),默认为 `5`。支持的值:`5`(5秒),`10`(10秒)。
426+
- `resolution` (string, 可选): 视频分辨率,默认为 `"720p"`。支持的分辨率:`720p`, `1080p`。**注意**:仅 `jimeng-video-3.0` 和 `jimeng-video-3.0-fast` 支持此参数,其他模型会忽略。
427+
- `duration` (number, 可选): 视频时长(秒)。不同模型支持的值:
428+
- `jimeng-video-veo3` / `jimeng-video-veo3.1`: `8`(固定)
429+
- `jimeng-video-sora2`: `4`(默认)、`8`、`12`
430+
- `jimeng-video-3.5-pro`: `5`(默认)、`10`、`12`
431+
- 其他模型: `5`(默认)、`10`
428432
- `file_paths` (array, 可选): 一个包含图片URL的数组,用于指定视频的**首帧**(数组第1个元素)和**尾帧**(数组第2个元素)。
429433
- `[file]` (file, 可选): 通过 `multipart/form-data` 方式上传的本地图片文件(最多2个),用于指定视频的**首帧**和**尾帧**。字段名可以任意,例如 `image1`。
430434
- `response_format` (string, 可选): 响应格式,支持 `url` (默认) 或 `b64_json`。
@@ -437,12 +441,16 @@ A: 可以。现在支持直接上传本地文件。请参考上方的“本地
437441

438442
**支持的视频模型**:
439443
- `jimeng-video-3.5-pro` - 专业版v3.5,国内/国际站均支持 **(默认)**
440-
- `jimeng-video-3.5` - 标准版v3.5,国内/国际站均支持
441-
- `jimeng-video-3.0-pro` - 专业版,国内/国际站均支持
444+
- `jimeng-video-veo3` - Veo3模型,仅亚洲国际站 (HK/JP/SG) 支持,固定8秒时长
445+
- `jimeng-video-veo3.1` - Veo3.1模型,仅亚洲国际站 (HK/JP/SG) 支持,固定8秒时长
446+
- `jimeng-video-sora2` - Sora2模型,仅亚洲国际站 (HK/JP/SG) 支持
447+
- `jimeng-video-3.0-pro` - 专业版,国内站和亚洲国际站 (HK/JP/SG) 支持
442448
- `jimeng-video-3.0` - 标准版,国内/国际站均支持
443449
- `jimeng-video-3.0-fast` - 极速版(仅国内站支持)
444-
- `jimeng-video-2.0-pro` - 专业版v2,国内/国际站均支持
445-
- `jimeng-video-2.0` - 标准版v2,国内/国际站均支持
450+
- `jimeng-video-2.0-pro` - 专业版v2,国内站和亚洲国际站 (HK/JP/SG) 支持
451+
- `jimeng-video-2.0` - 标准版v2,国内站和亚洲国际站 (HK/JP/SG) 支持
452+
453+
> **注意**: 美国站仅支持 `jimeng-video-3.5-pro` 和 `jimeng-video-3.0` 模型。
446454

447455
**使用示例**:
448456

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,12 @@ Generate a video from a text prompt (Text-to-Video) or from start/end frame imag
404404
- `model` (string): The name of the video model to use.
405405
- `prompt` (string): The text description of the video content.
406406
- `ratio` (string, optional): Video aspect ratio, defaults to `"1:1"`. Supported ratios: `1:1`, `4:3`, `3:4`, `16:9`, `9:16`, `21:9`. **Note**: In image-to-video mode (when images are provided), this parameter will be ignored, and the video aspect ratio will be determined by the input image's actual ratio.
407-
- `resolution` (string, optional): Video resolution, defaults to `"720p"`. Supported resolutions: `720p`, `1080p`.
408-
- `duration` (number, optional): Video duration in seconds, defaults to `5`. Supported values: `5` (5 seconds), `10` (10 seconds).
407+
- `resolution` (string, optional): Video resolution, defaults to `"720p"`. Supported resolutions: `720p`, `1080p`. **Note**: Only `jimeng-video-3.0` and `jimeng-video-3.0-fast` support this parameter; other models ignore it.
408+
- `duration` (number, optional): Video duration in seconds. Supported values vary by model:
409+
- `jimeng-video-veo3` / `jimeng-video-veo3.1`: `8` (fixed)
410+
- `jimeng-video-sora2`: `4` (default), `8`, `12`
411+
- `jimeng-video-3.5-pro`: `5` (default), `10`, `12`
412+
- Other models: `5` (default), `10`
409413
- `file_paths` (array, optional): An array of image URLs to specify the **start frame** (1st element) and **end frame** (2nd element) of the video.
410414
- `[file]` (file, optional): Local image files uploaded via `multipart/form-data` (up to 2) to specify the **start frame** and **end frame**. The field name can be arbitrary, e.g., `image1`.
411415
- `response_format` (string, optional): Response format, supports `url` (default) or `b64_json`.
@@ -418,12 +422,16 @@ Generate a video from a text prompt (Text-to-Video) or from start/end frame imag
418422

419423
**Supported Video Models**:
420424
- `jimeng-video-3.5-pro` - Professional Edition v3.5, works on all sites **(Default)**
421-
- `jimeng-video-3.5` - Standard Edition v3.5, works on all sites
422-
- `jimeng-video-3.0-pro` - Professional Edition, works on all sites
425+
- `jimeng-video-veo3` - Veo3 model, Asia international sites only (HK/JP/SG), fixed 8s duration
426+
- `jimeng-video-veo3.1` - Veo3.1 model, Asia international sites only (HK/JP/SG), fixed 8s duration
427+
- `jimeng-video-sora2` - Sora2 model, Asia international sites only (HK/JP/SG)
428+
- `jimeng-video-3.0-pro` - Professional Edition, China and Asia international sites (HK/JP/SG)
423429
- `jimeng-video-3.0` - Standard Edition, works on all sites
424430
- `jimeng-video-3.0-fast` - Fast Edition (China site only)
425-
- `jimeng-video-2.0-pro` - Professional Edition v2, works on all sites
426-
- `jimeng-video-2.0` - Standard Edition v2, works on all sites
431+
- `jimeng-video-2.0-pro` - Professional Edition v2, China and Asia international sites (HK/JP/SG)
432+
- `jimeng-video-2.0` - Standard Edition v2, China and Asia international sites (HK/JP/SG)
433+
434+
> **Note**: US site only supports `jimeng-video-3.5-pro` and `jimeng-video-3.0` models.
427435

428436
**Usage Examples**:
429437

src/api/consts/common.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,34 @@ export const IMAGE_MODEL_MAP_US = {
6161
"nanobananapro": "dreamina_image_lib_1",
6262
};
6363

64-
// 视频模型映射
64+
// 视频模型映射 - 国内站 (CN)
6565
export const VIDEO_MODEL_MAP = {
6666
"jimeng-video-3.5-pro": "dreamina_ic_generate_video_model_vgfm_3.5_pro",
67-
"jimeng-video-3.5": "dreamina_ic_generate_video_model_vgfm_3.5",
6867
"jimeng-video-3.0-pro": "dreamina_ic_generate_video_model_vgfm_3.0_pro",
6968
"jimeng-video-3.0": "dreamina_ic_generate_video_model_vgfm_3.0",
7069
"jimeng-video-3.0-fast": "dreamina_ic_generate_video_model_vgfm_3.0_fast",
7170
"jimeng-video-2.0": "dreamina_ic_generate_video_model_vgfm_lite",
7271
"jimeng-video-2.0-pro": "dreamina_ic_generate_video_model_vgfm1.0"
7372
};
7473

74+
// 视频模型映射 - 美国站 (US) - 仅保留 3.0 和 3.5-pro
75+
export const VIDEO_MODEL_MAP_US = {
76+
"jimeng-video-3.5-pro": "dreamina_ic_generate_video_model_vgfm_3.5_pro",
77+
"jimeng-video-3.0": "dreamina_ic_generate_video_model_vgfm_3.0",
78+
};
79+
80+
// 视频模型映射 - 亚洲国际站 (HK/JP/SG)
81+
export const VIDEO_MODEL_MAP_ASIA = {
82+
"jimeng-video-veo3": "dreamina_veo3_generate_video",
83+
"jimeng-video-veo3.1": "dreamina_veo3.1_generate_video",
84+
"jimeng-video-sora2": "dreamina_sora2_generate_video",
85+
"jimeng-video-3.5-pro": "dreamina_ic_generate_video_model_vgfm_3.5_pro",
86+
"jimeng-video-3.0-pro": "dreamina_ic_generate_video_model_vgfm_3.0_pro",
87+
"jimeng-video-3.0": "dreamina_ic_generate_video_model_vgfm_3.0",
88+
"jimeng-video-2.0": "dreamina_ic_generate_video_model_vgfm_lite",
89+
"jimeng-video-2.0-pro": "dreamina_ic_generate_video_model_vgfm1.0"
90+
};
91+
7592
// 状态码映射
7693
export const STATUS_CODE_MAP = {
7794
20: 'PROCESSING',

src/api/controllers/core.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -548,19 +548,19 @@ export function tokenSplit(authorization: string) {
548548
* 获取Token存活状态
549549
*/
550550
export async function getTokenLiveStatus(refreshToken: string) {
551-
const result = await request(
552-
"POST",
553-
"/passport/account/info/v2",
554-
refreshToken,
555-
{
556-
params: {
557-
account_sdk_source: "web",
558-
},
559-
}
560-
);
561551
try {
562-
const { user_id } = checkResult(result);
563-
return !!user_id;
552+
const result = await request(
553+
"POST",
554+
"/passport/account/info/v2",
555+
refreshToken,
556+
{
557+
params: {
558+
account_sdk_source: "web",
559+
},
560+
}
561+
);
562+
// request 内部已调用 checkResult,直接使用返回值
563+
return !!result?.user_id;
564564
} catch (err) {
565565
return false;
566566
}

src/api/controllers/videos.ts

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,38 @@ import util from "@/lib/util.ts";
77
import { getCredit, receiveCredit, request, parseRegionFromToken, getAssistantId, RegionInfo } from "./core.ts";
88
import logger from "@/lib/logger.ts";
99
import { SmartPoller, PollingStatus } from "@/lib/smart-poller.ts";
10-
import { DEFAULT_ASSISTANT_ID_CN, DEFAULT_ASSISTANT_ID_US, DEFAULT_ASSISTANT_ID_HK, DEFAULT_ASSISTANT_ID_JP, DEFAULT_ASSISTANT_ID_SG, DEFAULT_VIDEO_MODEL, DRAFT_VERSION, VIDEO_MODEL_MAP } from "@/api/consts/common.ts";
10+
import { DEFAULT_ASSISTANT_ID_CN, DEFAULT_ASSISTANT_ID_US, DEFAULT_ASSISTANT_ID_HK, DEFAULT_ASSISTANT_ID_JP, DEFAULT_ASSISTANT_ID_SG, DEFAULT_VIDEO_MODEL, DRAFT_VERSION, VIDEO_MODEL_MAP, VIDEO_MODEL_MAP_US, VIDEO_MODEL_MAP_ASIA } from "@/api/consts/common.ts";
1111
import { uploadImageBuffer } from "@/lib/image-uploader.ts";
1212
import { extractVideoUrl } from "@/lib/image-utils.ts";
1313

1414
export const DEFAULT_MODEL = DEFAULT_VIDEO_MODEL;
1515

16-
export function getModel(model: string) {
17-
return VIDEO_MODEL_MAP[model] || VIDEO_MODEL_MAP[DEFAULT_MODEL];
16+
export function getModel(model: string, regionInfo: RegionInfo) {
17+
// 根据站点选择不同的模型映射
18+
let modelMap: Record<string, string>;
19+
if (regionInfo.isUS) {
20+
modelMap = VIDEO_MODEL_MAP_US;
21+
} else if (regionInfo.isHK || regionInfo.isJP || regionInfo.isSG) {
22+
modelMap = VIDEO_MODEL_MAP_ASIA;
23+
} else {
24+
modelMap = VIDEO_MODEL_MAP;
25+
}
26+
return modelMap[model] || modelMap[DEFAULT_MODEL] || VIDEO_MODEL_MAP[DEFAULT_MODEL];
1827
}
1928

2029
function getVideoBenefitType(model: string): string {
30+
// veo3.1 模型 (需先于 veo3 检查)
31+
if (model.includes("veo3.1")) {
32+
return "generate_video_veo3.1";
33+
}
34+
// veo3 模型
35+
if (model.includes("veo3")) {
36+
return "generate_video_veo3";
37+
}
38+
// sora2 模型
39+
if (model.includes("sora2")) {
40+
return "generate_video_sora2";
41+
}
2142
if (model.includes("3.5_pro")) {
2243
return "dreamina_video_seedance_15_pro";
2344
}
@@ -89,12 +110,51 @@ export async function generateVideo(
89110

90111
logger.info(`视频生成区域检测: isInternational=${isInternational}`);
91112

92-
const model = getModel(_model);
93-
94-
// 将秒转换为毫秒,只支持5秒和10秒
95-
const durationMs = duration === 10 ? 10000 : 5000;
113+
const model = getModel(_model, regionInfo);
114+
const isVeo3 = model.includes("veo3");
115+
const isSora2 = model.includes("sora2");
116+
const is35Pro = model.includes("3.5_pro");
117+
// 只有 video-3.0 和 video-3.0-fast 支持 resolution 参数
118+
const supportsResolution = model.includes("vgfm_3.0") || model.includes("vgfm_3.0_fast");
119+
120+
// 将秒转换为毫秒
121+
// veo3 模型固定 8 秒
122+
// sora2 模型支持 4秒、8秒、12秒,默认4秒
123+
// 3.5-pro 模型支持 5秒、10秒、12秒,默认5秒
124+
// 其他模型支持 5秒、10秒,默认5秒
125+
let durationMs: number;
126+
let actualDuration: number;
127+
if (isVeo3) {
128+
durationMs = 8000;
129+
actualDuration = 8;
130+
} else if (isSora2) {
131+
if (duration === 12) {
132+
durationMs = 12000;
133+
actualDuration = 12;
134+
} else if (duration === 8) {
135+
durationMs = 8000;
136+
actualDuration = 8;
137+
} else {
138+
durationMs = 4000;
139+
actualDuration = 4;
140+
}
141+
} else if (is35Pro) {
142+
if (duration === 12) {
143+
durationMs = 12000;
144+
actualDuration = 12;
145+
} else if (duration === 10) {
146+
durationMs = 10000;
147+
actualDuration = 10;
148+
} else {
149+
durationMs = 5000;
150+
actualDuration = 5;
151+
}
152+
} else {
153+
durationMs = duration === 10 ? 10000 : 5000;
154+
actualDuration = duration === 10 ? 10 : 5;
155+
}
96156

97-
logger.info(`使用模型: ${_model} 映射模型: ${model} 比例: ${ratio} 分辨率: ${resolution} 时长: ${duration}s`);
157+
logger.info(`使用模型: ${_model} 映射模型: ${model} 比例: ${ratio} 分辨率: ${supportsResolution ? resolution : '不支持'} 时长: ${actualDuration}s`);
98158

99159
// 检查积分
100160
const { totalCredit } = await getCredit(refreshToken);
@@ -208,12 +268,13 @@ export async function generateVideo(
208268
const sceneOption = {
209269
type: "video",
210270
scene: "BasicVideoGenerateButton",
271+
...(supportsResolution ? { resolution: resolution } : {}),
211272
modelReqKey: model,
212-
videoDuration: duration,
273+
videoDuration: actualDuration,
213274
reportParams: {
214275
enterSource: "generate",
215276
vipSource: "generate",
216-
extraVipFunctionKey: model,
277+
extraVipFunctionKey: supportsResolution ? `${model}-${resolution}` : model,
217278
useVipFunctionDetailsReporterHoc: true,
218279
},
219280
};
@@ -304,7 +365,7 @@ export async function generateVideo(
304365
"video_mode": 2,
305366
"fps": 24,
306367
"duration_ms": durationMs,
307-
"resolution": resolution,
368+
...(supportsResolution ? { "resolution": resolution } : {}),
308369
"first_frame_image": first_frame_image,
309370
"end_frame_image": end_frame_image,
310371
"idip_meta_list": []
@@ -386,9 +447,17 @@ export async function generateVideo(
386447
}
387448

388449
// 检查响应中是否有该 history_id 的数据
450+
// 由于 API 存在最终一致性,早期轮询可能暂时获取不到记录,返回处理中状态继续轮询
389451
if (!result[historyId]) {
390-
logger.warn(`API未返回历史记录,historyId: ${historyId}`);
391-
throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "记录不存在");
452+
logger.warn(`API未返回历史记录 (轮询第${pollAttempts}次),historyId: ${historyId},继续等待...`);
453+
return {
454+
status: {
455+
status: 20, // PROCESSING
456+
itemCount: 0,
457+
historyId
458+
} as PollingStatus,
459+
data: { status: 20, item_list: [] }
460+
};
392461
}
393462

394463
const historyData = result[historyId];

src/api/routes/videos.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,28 @@ export default {
2323
.validate('body.resolution', v => _.isUndefined(v) || _.isString(v))
2424
.validate('body.duration', v => {
2525
if (_.isUndefined(v)) return true;
26+
// 支持的时长: 4/8/12 (sora2) 和 5/10 (其他模型)
27+
const validDurations = [4, 5, 8, 10, 12];
2628
// 对于 multipart/form-data,允许字符串类型的数字
2729
if (isMultiPart && typeof v === 'string') {
2830
const num = parseInt(v);
29-
return num === 5 || num === 10;
31+
return validDurations.includes(num);
3032
}
3133
// 对于 JSON,要求数字类型
32-
return _.isFinite(v) && (v === 5 || v === 10);
34+
return _.isFinite(v) && validDurations.includes(v);
3335
})
34-
.validate('body.file_paths', v => _.isUndefined(v) || _.isArray(v))
35-
.validate('body.filePaths', v => _.isUndefined(v) || _.isArray(v))
36+
// 限制图片URL数量最多2个
37+
.validate('body.file_paths', v => _.isUndefined(v) || (_.isArray(v) && v.length <= 2))
38+
.validate('body.filePaths', v => _.isUndefined(v) || (_.isArray(v) && v.length <= 2))
3639
.validate('body.response_format', v => _.isUndefined(v) || _.isString(v))
3740
.validate('headers.authorization', _.isString);
3841

42+
// 限制上传文件数量最多2个
43+
const uploadedFiles = request.files ? _.values(request.files) : [];
44+
if (uploadedFiles.length > 2) {
45+
throw new Error('最多只能上传2个图片文件');
46+
}
47+
3948
// refresh_token切分
4049
const tokens = tokenSplit(request.headers.authorization);
4150
// 随机挑选一个refresh_token

0 commit comments

Comments
 (0)