fix: support video durations from 5 to 14 seconds

This commit is contained in:
xuejianwu
2026-07-31 13:41:09 +08:00
parent afd4118fdb
commit 77dc339f87
5 changed files with 25 additions and 9 deletions

View File

@@ -99,7 +99,7 @@ npm start
- 新版素材库只在列表中返回 `ready` 素材;`pending` / `failed` 通过单素材接口查询。
- 视频生成应长期保存素材库返回的 `asset://...` 引用,不要依赖原始图片 URL。
- 参考图/参考视频/首帧/首尾帧属于不同互斥调用方式;本 Demo 采用“多模态参考图”方式。
- 默认视频 720p时长 415 秒;真人/数字人必须使用审核通过的 `mat_*` 素材 ID。
- 默认视频 720p时长可选 514整数;真人/数字人必须使用审核通过的 `mat_*` 素材 ID。
接口依据:[快快 AI Hub 最新文档](https://ai.kkidc.com/api-docs/8238027m0)。

View File

@@ -456,6 +456,8 @@ function switchModule(module) {
}
function createShot(seed = {}, index = state.shots.length) {
const seedDuration = Number(seed.duration);
const duration = Number.isFinite(seedDuration) ? Math.min(14, Math.max(5, Math.round(seedDuration))) : 5;
return {
id: seed.id || `shot-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
title: seed.title || (index === 0 ? "电梯口对话" : `片段 ${String(index + 1).padStart(2, "0")}`),
@@ -466,7 +468,7 @@ function createShot(seed = {}, index = state.shots.length) {
roleIds: Array.isArray(seed.roleIds) ? [...seed.roleIds] : state.roles.map((role) => role.id),
model: seed.model || "doubao-seedance-2-0-260128",
ratio: seed.ratio || "9:16",
duration: Number(seed.duration) || 5,
duration,
resolution: seed.resolution || "720p",
generateAudio: seed.generateAudio !== false,
watermark: Boolean(seed.watermark),

View File

@@ -204,9 +204,15 @@
<span>时长</span>
<select id="duration">
<option value="5">5 秒</option>
<option value="6">6 秒</option>
<option value="7">7 秒</option>
<option value="8">8 秒</option>
<option value="9">9 秒</option>
<option value="10">10 秒</option>
<option value="15">15</option>
<option value="11">11</option>
<option value="12">12 秒</option>
<option value="13">13 秒</option>
<option value="14">14 秒</option>
</select>
</label>
<label class="field">
@@ -283,6 +289,6 @@
</div>
<div class="toast hidden" id="toast"></div>
<script src="./app.js?v=29"></script>
<script src="./app.js?v=30"></script>
</body>
</html>

View File

@@ -534,10 +534,6 @@ function requireCustomerToken(request, taskId = "") {
return token;
}
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function resolveResolution(model = "", requested = "720p") {
return requested === "480p" ? "480p" : "720p";
}
@@ -562,6 +558,10 @@ function buildVideoPayload(body) {
throw new ClientError("参考视频最多 3 条,且必须是公网可访问的 MP4/MOV URL");
}
if (!String(body.prompt || "").trim()) throw new ClientError("请填写分镜提示词");
const duration = Number(body.duration);
if (!Number.isInteger(duration) || duration < 5 || duration > 14) {
throw new ClientError("视频时长必须是 514 秒之间的整数");
}
return {
model: resolveVideoModel(body.model),
content: [
@@ -577,7 +577,7 @@ function buildVideoPayload(body) {
role: "reference_video",
})),
],
duration: clamp(Number(body.duration || 5), 4, 15),
duration,
resolution: resolveResolution(body.model, body.resolution),
ratio: body.ratio || "9:16",
generate_audio: body.generateAudio !== false,

View File

@@ -1,5 +1,6 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import {
extractVideoTaskId,
extractVideoUrl,
@@ -24,3 +25,10 @@ test("兼容视频任务 id 和状态字段", () => {
test("从常见响应位置提取视频地址", () => {
assert.equal(extractVideoUrl({ output: { video_url: "https://example.com/demo.mp4" } }), "https://example.com/demo.mp4");
});
test("生成时长提供 5 到 14 秒全部整数选项", () => {
const html = readFileSync(new URL("../public/index.html", import.meta.url), "utf8");
const durationSelect = html.match(/<select id="duration">([\s\S]*?)<\/select>/)?.[1] || "";
const values = [...durationSelect.matchAll(/<option value="(\d+)">/g)].map((match) => Number(match[1]));
assert.deepEqual(values, [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);
});