fix: support video durations from 5 to 14 seconds
This commit is contained in:
@@ -99,7 +99,7 @@ npm start
|
||||
- 新版素材库只在列表中返回 `ready` 素材;`pending` / `failed` 通过单素材接口查询。
|
||||
- 视频生成应长期保存素材库返回的 `asset://...` 引用,不要依赖原始图片 URL。
|
||||
- 参考图/参考视频/首帧/首尾帧属于不同互斥调用方式;本 Demo 采用“多模态参考图”方式。
|
||||
- 默认视频 720p,时长 4–15 秒;真人/数字人必须使用审核通过的 `mat_*` 素材 ID。
|
||||
- 默认视频 720p,时长可选 5–14 秒整数;真人/数字人必须使用审核通过的 `mat_*` 素材 ID。
|
||||
|
||||
接口依据:[快快 AI Hub 最新文档](https://ai.kkidc.com/api-docs/8238027m0)。
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
|
||||
10
server.mjs
10
server.mjs
@@ -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("视频时长必须是 5~14 秒之间的整数");
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user