164 lines
5.2 KiB
TypeScript
164 lines
5.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import {
|
|
defaultVideoCanvasFunctionConfig,
|
|
normalizeVideoCanvasFunctionConfig,
|
|
} from "../../lib/video-canvas/config";
|
|
import {
|
|
composeVideoCanvasSmartStoryboardPrompt,
|
|
getVideoCanvasSmartStoryboard,
|
|
normalizeVideoCanvasSmartStoryboard,
|
|
setVideoCanvasSmartStoryboard,
|
|
supportsVideoCanvasSmartStoryboard,
|
|
validateVideoCanvasSmartStoryboard,
|
|
} from "../../lib/video-canvas/smart-storyboard";
|
|
|
|
const config = defaultVideoCanvasFunctionConfig.smartStoryboard;
|
|
|
|
test.describe("video canvas smart storyboard", () => {
|
|
test("matches the observed Kling O3 duration and model rules", () => {
|
|
expect(config).toMatchObject({
|
|
defaultShotDurationSeconds: 2,
|
|
maxTotalDurationSeconds: 15,
|
|
minShotDurationSeconds: 1,
|
|
minTotalDurationSeconds: 3,
|
|
modelPatterns: ["Kling O3"],
|
|
});
|
|
expect(supportsVideoCanvasSmartStoryboard(config, "Kling O3")).toBe(true);
|
|
expect(
|
|
supportsVideoCanvasSmartStoryboard(config, "Seedance 2.0 VIP"),
|
|
).toBe(false);
|
|
});
|
|
|
|
test("creates one disabled shot from the current node prompt", () => {
|
|
expect(
|
|
normalizeVideoCanvasSmartStoryboard(undefined, {
|
|
config,
|
|
fallbackPrompt: "人物走向镜头。",
|
|
fallbackTotalDurationSeconds: 5,
|
|
}),
|
|
).toEqual({
|
|
enabled: false,
|
|
shots: [
|
|
{
|
|
description: "人物走向镜头。",
|
|
durationSeconds: 5,
|
|
id: "shot-1",
|
|
},
|
|
],
|
|
totalDurationSeconds: 5,
|
|
});
|
|
});
|
|
|
|
test("validates empty descriptions before exact total duration", () => {
|
|
const storyboard = normalizeVideoCanvasSmartStoryboard(
|
|
{
|
|
enabled: true,
|
|
shots: [
|
|
{ description: "第一个镜头。", durationSeconds: 5, id: "one" },
|
|
{ description: "", durationSeconds: 2, id: "two" },
|
|
],
|
|
totalDurationSeconds: 5,
|
|
},
|
|
{ config },
|
|
);
|
|
expect(validateVideoCanvasSmartStoryboard(storyboard)).toEqual({
|
|
emptyShotIndexes: [1],
|
|
status: "over",
|
|
totalShotDurationSeconds: 7,
|
|
valid: false,
|
|
});
|
|
});
|
|
|
|
test("requires shot durations to equal the selected total exactly", () => {
|
|
const base = {
|
|
enabled: true,
|
|
shots: [
|
|
{ description: "镜头一。", durationSeconds: 5, id: "one" },
|
|
{ description: "镜头二。", durationSeconds: 2, id: "two" },
|
|
],
|
|
totalDurationSeconds: 7,
|
|
};
|
|
expect(validateVideoCanvasSmartStoryboard(base).valid).toBe(true);
|
|
expect(
|
|
validateVideoCanvasSmartStoryboard({ ...base, totalDurationSeconds: 8 }),
|
|
).toMatchObject({ status: "under", valid: false });
|
|
expect(
|
|
validateVideoCanvasSmartStoryboard({ ...base, totalDurationSeconds: 6 }),
|
|
).toMatchObject({ status: "over", valid: false });
|
|
});
|
|
|
|
test("persists structured shots in both node and provider params", () => {
|
|
const storyboard = normalizeVideoCanvasSmartStoryboard(
|
|
{
|
|
enabled: true,
|
|
shots: [{ description: "镜头一。", durationSeconds: 5, id: "one" }],
|
|
totalDurationSeconds: 5,
|
|
},
|
|
{ config },
|
|
);
|
|
const draftValues = setVideoCanvasSmartStoryboard(
|
|
{ params: { model: "kling-o3", prompt: "人物走向镜头。" }, untouched: true },
|
|
storyboard,
|
|
);
|
|
expect(draftValues).toMatchObject({
|
|
params: { model: "kling-o3", smartStoryboard: storyboard },
|
|
smartStoryboard: storyboard,
|
|
untouched: true,
|
|
});
|
|
expect(
|
|
getVideoCanvasSmartStoryboard(draftValues, {
|
|
config,
|
|
fallbackTotalDurationSeconds: 3,
|
|
}),
|
|
).toEqual(storyboard);
|
|
});
|
|
|
|
test("serializes exact shot timing through administrator templates", () => {
|
|
const storyboard = {
|
|
enabled: true,
|
|
shots: [
|
|
{ description: "人物抬头。", durationSeconds: 3, id: "one" },
|
|
{ description: "镜头推近。", durationSeconds: 2, id: "two" },
|
|
],
|
|
totalDurationSeconds: 5,
|
|
};
|
|
const prompt = composeVideoCanvasSmartStoryboardPrompt(
|
|
"保持角色一致。",
|
|
storyboard,
|
|
{
|
|
...config,
|
|
promptTemplate: "总计{{totalDuration}}秒/{{shotCount}}镜头\n{{prompt}}\n{{shots}}",
|
|
shotPromptTemplate: "#{{index}} {{duration}}秒 {{description}}",
|
|
},
|
|
);
|
|
expect(prompt).toBe(
|
|
"总计5秒/2镜头\n保持角色一致。\n#1 3秒 人物抬头。\n#2 2秒 镜头推近。",
|
|
);
|
|
});
|
|
|
|
test("normalizes administrator ranges and hidden templates", () => {
|
|
const normalized = normalizeVideoCanvasFunctionConfig({
|
|
smartStoryboard: {
|
|
defaultShotDurationSeconds: 99,
|
|
maxShots: 999,
|
|
maxTotalDurationSeconds: 12,
|
|
minShotDurationSeconds: 0,
|
|
minTotalDurationSeconds: 4,
|
|
modelPatterns: [" Kling O3 ", "Kling O3"],
|
|
promptTemplate: "{{shots}}",
|
|
shotPromptTemplate: "{{description}}",
|
|
},
|
|
}).smartStoryboard;
|
|
expect(normalized).toMatchObject({
|
|
defaultShotDurationSeconds: 12,
|
|
maxShots: 100,
|
|
maxTotalDurationSeconds: 12,
|
|
minShotDurationSeconds: 1,
|
|
minTotalDurationSeconds: 4,
|
|
modelPatterns: ["Kling O3"],
|
|
promptTemplate: "{{shots}}",
|
|
shotPromptTemplate: "{{description}}",
|
|
});
|
|
});
|
|
});
|