@vanillaskyai/video 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +201 -0
- package/PUBLIC-API.md +276 -0
- package/README.md +152 -0
- package/SECURITY.md +22 -0
- package/SUPPORT.md +28 -0
- package/bin/vanillasky.js +3 -0
- package/dist/bg-confetti-WNQXS7ZS.js +89 -0
- package/dist/bg-emoji-ETI47GLS.js +150 -0
- package/dist/bg-media-SVUZAMGX.js +99 -0
- package/dist/brand-message-F3RD4P3P.js +253 -0
- package/dist/builtin-server-F7YXRVCH.js +8 -0
- package/dist/catalog-types-BIhSpOWK.d.ts +74 -0
- package/dist/chart-bar-CNSC7BNK.js +304 -0
- package/dist/chart-counter-MCRHKN77.js +299 -0
- package/dist/chart-progress-ring-RMI4A7ZK.js +296 -0
- package/dist/check-runtime.d.ts +15 -0
- package/dist/check-runtime.js +102 -0
- package/dist/chunk-2E6T633S.js +27 -0
- package/dist/chunk-352ZZCET.js +83 -0
- package/dist/chunk-73NTSFFI.js +81 -0
- package/dist/chunk-A2X45FET.js +46 -0
- package/dist/chunk-ABPKM4GK.js +187 -0
- package/dist/chunk-ABZACD4G.js +216 -0
- package/dist/chunk-BCRUUJ6A.js +756 -0
- package/dist/chunk-CHLGW22F.js +3551 -0
- package/dist/chunk-CJ7F2CE6.js +104 -0
- package/dist/chunk-CR7FE7BW.js +109 -0
- package/dist/chunk-E7CL7UPB.js +109 -0
- package/dist/chunk-EE6PK6XC.js +18 -0
- package/dist/chunk-ERY4S6QV.js +385 -0
- package/dist/chunk-EVG2ZYIY.js +125 -0
- package/dist/chunk-FNAEQ7QO.js +155 -0
- package/dist/chunk-FQ47WXAJ.js +38 -0
- package/dist/chunk-FSG2PYWG.js +336 -0
- package/dist/chunk-GHJEAP4O.js +504 -0
- package/dist/chunk-GRMUV7QY.js +262 -0
- package/dist/chunk-IFOW65Z6.js +205 -0
- package/dist/chunk-IXCUY3B3.js +786 -0
- package/dist/chunk-L5BTTCZV.js +97 -0
- package/dist/chunk-LMG3GHLL.js +107 -0
- package/dist/chunk-OOPIETLW.js +101 -0
- package/dist/chunk-P22HHFDK.js +43 -0
- package/dist/chunk-QAAO7OAY.js +19 -0
- package/dist/chunk-S7HFLYNN.js +35 -0
- package/dist/chunk-SPVTJH3F.js +24 -0
- package/dist/chunk-W2XJKG6C.js +3177 -0
- package/dist/chunk-XB7LH4J2.js +301 -0
- package/dist/chunk-XGRA2MUV.js +242 -0
- package/dist/chunk-XTOXFMLM.js +105 -0
- package/dist/chunk-YC5MFXA5.js +132 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1681 -0
- package/dist/compose-video-CN3VSWST.js +13 -0
- package/dist/cta-logo-SVA5GP2A.js +209 -0
- package/dist/cta-media-E7VLLK42.js +215 -0
- package/dist/events-_wfjbmEp.d.ts +81 -0
- package/dist/incoming-call-62MFZL3Z.js +320 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/infographic-before-after-AXCDAWND.js +460 -0
- package/dist/infographic-feature-list-GW5GUE6O.js +266 -0
- package/dist/infographic-problem-solution-7NURJANA.js +340 -0
- package/dist/infographic-stat-row-J7IZR6ZN.js +241 -0
- package/dist/infographic-steps-VSZM6F5M.js +400 -0
- package/dist/kit-BMNcHqa9.d.ts +79 -0
- package/dist/prompt-input-LOUCRXCY.js +236 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +835 -0
- package/dist/reaction-QG4CYZLQ.js +119 -0
- package/dist/server.d.ts +141 -0
- package/dist/server.js +498 -0
- package/dist/showcase-code-RUTFZR3H.js +351 -0
- package/dist/showcase-phone-6CP6XEU5.js +272 -0
- package/dist/showcase-terminal-MGAEQWPR.js +306 -0
- package/dist/showcase-web-Y4W2HVRJ.js +390 -0
- package/dist/social-conversation-75SD2637.js +695 -0
- package/dist/social-milestone-XI2QE5BO.js +292 -0
- package/dist/social-notification-2UOHQCUY.js +244 -0
- package/dist/social-review-stack-FDYZGZTJ.js +239 -0
- package/dist/social-testimonial-OQ7N52GJ.js +284 -0
- package/dist/social-tweet-5OLCNPPC.js +295 -0
- package/dist/state-DZcKuS32.d.ts +3 -0
- package/dist/system-prompt-A6UXNTPK.js +8 -0
- package/dist/template-catalog.d.ts +24 -0
- package/dist/template-catalog.js +6 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.js +19 -0
- package/dist/test.d.ts +149 -0
- package/dist/test.js +272 -0
- package/dist/text-stream-6XHC7J47.js +10 -0
- package/dist/types-B-PZR6G_.d.ts +208 -0
- package/dist/validate-T7GBU2YF.js +8 -0
- package/docs/agent-integration.md +39 -0
- package/docs/architecture.md +84 -0
- package/docs/branding-and-personalization.md +87 -0
- package/docs/concepts.md +106 -0
- package/docs/custom-templates.md +329 -0
- package/docs/customization.md +141 -0
- package/docs/errors.md +70 -0
- package/docs/getting-started.md +111 -0
- package/docs/input-and-first-scene.md +41 -0
- package/docs/integrate-nextjs.md +79 -0
- package/docs/media-and-audio.md +69 -0
- package/docs/motion-and-effects.md +107 -0
- package/docs/persistence.md +95 -0
- package/docs/production.md +122 -0
- package/docs/prompt-and-input.md +185 -0
- package/docs/provider-integration.md +115 -0
- package/docs/reference/protocol.md +96 -0
- package/docs/reference/provider-adapters.md +158 -0
- package/docs/responsive-orientation.md +37 -0
- package/docs/security.md +38 -0
- package/docs/streaming-protocol.md +15 -0
- package/docs/testing.md +137 -0
- package/docs/use-cases.md +59 -0
- package/examples/custom-template/README.md +19 -0
- package/examples/custom-template/minimal-text.tsx +82 -0
- package/examples/custom-template/structured-data.tsx +104 -0
- package/examples/custom-template/supplied-media.tsx +92 -0
- package/examples/nextjs-quickstart/.env.example +2 -0
- package/examples/nextjs-quickstart/README.md +22 -0
- package/examples/nextjs-quickstart/next-env.d.ts +4 -0
- package/examples/nextjs-quickstart/package.json +24 -0
- package/examples/nextjs-quickstart/src/app/api/video/route.ts +22 -0
- package/examples/nextjs-quickstart/src/app/layout.tsx +5 -0
- package/examples/nextjs-quickstart/src/app/page.tsx +31 -0
- package/examples/nextjs-quickstart/tsconfig.json +26 -0
- package/package.json +167 -0
- package/registry/items/backgrounds.json +52 -0
- package/registry/items/barChart.json +237 -0
- package/registry/items/beforeAfter.json +180 -0
- package/registry/items/bigNumber.json +225 -0
- package/registry/items/brandMessage.json +158 -0
- package/registry/items/cardList.json +206 -0
- package/registry/items/chatMessenger.json +151 -0
- package/registry/items/chatWhatsapp.json +151 -0
- package/registry/items/codeEditor.json +184 -0
- package/registry/items/confetti.json +107 -0
- package/registry/items/ctaLogo.json +160 -0
- package/registry/items/ctaMedia.json +193 -0
- package/registry/items/emojiBurst.json +101 -0
- package/registry/items/incomingCall.json +170 -0
- package/registry/items/media.json +179 -0
- package/registry/items/milestone.json +205 -0
- package/registry/items/motion.json +45 -0
- package/registry/items/notification.json +182 -0
- package/registry/items/phoneMockup.json +254 -0
- package/registry/items/problemSolution.json +186 -0
- package/registry/items/progressRing.json +199 -0
- package/registry/items/promptInput.json +148 -0
- package/registry/items/reaction.json +170 -0
- package/registry/items/reviewStack.json +224 -0
- package/registry/items/steps.json +207 -0
- package/registry/items/template-context.json +31 -0
- package/registry/items/terminal.json +212 -0
- package/registry/items/testimonial.json +164 -0
- package/registry/items/theme.json +47 -0
- package/registry/items/tripleStats.json +228 -0
- package/registry/items/tweet.json +203 -0
- package/registry/items/typography.json +47 -0
- package/registry/items/webMockup.json +273 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VIDEO_SCHEMA_VERSION
|
|
3
|
+
} from "./chunk-EE6PK6XC.js";
|
|
4
|
+
import {
|
|
5
|
+
validateVideoBrand
|
|
6
|
+
} from "./chunk-XGRA2MUV.js";
|
|
7
|
+
|
|
8
|
+
// src/protocol/persistence.ts
|
|
9
|
+
var MAX_RETAINED_SOURCE_LENGTH = 16384;
|
|
10
|
+
var MAX_RETAINED_INSTRUCTIONS_LENGTH = 4096;
|
|
11
|
+
var MAX_RETAINED_MEDIA_URLS = 16;
|
|
12
|
+
var MAX_RETAINED_MEDIA_URL_LENGTH = 2048;
|
|
13
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
14
|
+
var MAX_JSON_DEPTH = 50;
|
|
15
|
+
var VideoValidationError = class extends Error {
|
|
16
|
+
code;
|
|
17
|
+
constructor(message, code = "invalid_video") {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "VideoValidationError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function fail(message) {
|
|
24
|
+
throw new VideoValidationError(message);
|
|
25
|
+
}
|
|
26
|
+
function ownField(value, key, path) {
|
|
27
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
28
|
+
if (!descriptor) return { present: false };
|
|
29
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
30
|
+
fail(`${path} must be an enumerable data field`);
|
|
31
|
+
}
|
|
32
|
+
return { present: true, value: descriptor.value };
|
|
33
|
+
}
|
|
34
|
+
function requiredField(value, key, path) {
|
|
35
|
+
const field = ownField(value, key, path);
|
|
36
|
+
if (!field.present) fail(`${path} is required`);
|
|
37
|
+
return field.value;
|
|
38
|
+
}
|
|
39
|
+
function record(value, path) {
|
|
40
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
41
|
+
return fail(`${path} must be an object`);
|
|
42
|
+
}
|
|
43
|
+
const prototype = Object.getPrototypeOf(value);
|
|
44
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
45
|
+
return fail(`${path} must be a plain JSON object`);
|
|
46
|
+
}
|
|
47
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
48
|
+
return fail(`${path} contains symbol fields`);
|
|
49
|
+
}
|
|
50
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
51
|
+
ownField(value, key, `${path}.${key}`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function allowedKeys(value, allowed, path) {
|
|
56
|
+
const permitted = new Set(allowed);
|
|
57
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
58
|
+
if (!permitted.has(key)) fail(`${path} contains unsupported field ${key}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function arrayItems(value, path) {
|
|
62
|
+
if (!Array.isArray(value)) fail(`${path} must be an array`);
|
|
63
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) {
|
|
64
|
+
fail(`${path} must be a standard JSON array`);
|
|
65
|
+
}
|
|
66
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
67
|
+
fail(`${path} contains symbol fields`);
|
|
68
|
+
}
|
|
69
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
70
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor) || lengthDescriptor.enumerable || typeof lengthDescriptor.value !== "number" || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
71
|
+
fail(`${path}.length must be a standard array length`);
|
|
72
|
+
}
|
|
73
|
+
const length = lengthDescriptor.value;
|
|
74
|
+
const names = Object.getOwnPropertyNames(value);
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
if (name === "length") continue;
|
|
77
|
+
if (!/^(?:0|[1-9]\d*)$/.test(name)) {
|
|
78
|
+
fail(`${path} contains unsupported array field ${name}`);
|
|
79
|
+
}
|
|
80
|
+
const index = Number(name);
|
|
81
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= length) {
|
|
82
|
+
fail(`${path} contains unsupported array field ${name}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (names.length !== length + 1) fail(`${path} must not contain sparse holes`);
|
|
86
|
+
const items = [];
|
|
87
|
+
for (let index = 0; index < length; index += 1) {
|
|
88
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
89
|
+
if (!descriptor) fail(`${path}[${index}] must not be a sparse hole`);
|
|
90
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
91
|
+
fail(`${path}[${index}] must be an enumerable data field`);
|
|
92
|
+
}
|
|
93
|
+
items[index] = descriptor.value;
|
|
94
|
+
}
|
|
95
|
+
return items;
|
|
96
|
+
}
|
|
97
|
+
function createDetachedSnapshot(value, path, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
98
|
+
if (depth > MAX_JSON_DEPTH) fail(`${path} exceeds the maximum JSON depth`);
|
|
99
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
100
|
+
if (typeof value === "number") {
|
|
101
|
+
if (!Number.isFinite(value)) fail(`${path} must be finite`);
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
if (!value || typeof value !== "object") fail(`${path} must be JSON-safe`);
|
|
105
|
+
if (seen.has(value)) fail(`${path} must be JSON-safe`);
|
|
106
|
+
seen.add(value);
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) {
|
|
109
|
+
fail(`${path} must be a standard JSON array`);
|
|
110
|
+
}
|
|
111
|
+
const keys2 = Reflect.ownKeys(value);
|
|
112
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
113
|
+
const names = [];
|
|
114
|
+
for (let keyIndex = 0; keyIndex < keys2.length; keyIndex += 1) {
|
|
115
|
+
const key = keys2[keyIndex];
|
|
116
|
+
if (typeof key !== "string") fail(`${path} contains symbol fields`);
|
|
117
|
+
names[names.length] = key;
|
|
118
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
119
|
+
if (!descriptor) fail(`${path}.${key} changed while creating the snapshot`);
|
|
120
|
+
descriptors.set(key, descriptor);
|
|
121
|
+
}
|
|
122
|
+
const lengthDescriptor = descriptors.get("length");
|
|
123
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor) || lengthDescriptor.enumerable || typeof lengthDescriptor.value !== "number" || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
124
|
+
fail(`${path}.length must be a standard array length`);
|
|
125
|
+
}
|
|
126
|
+
const length = lengthDescriptor.value;
|
|
127
|
+
for (let nameIndex = 0; nameIndex < names.length; nameIndex += 1) {
|
|
128
|
+
const name = names[nameIndex];
|
|
129
|
+
if (name === "length") continue;
|
|
130
|
+
if (!/^(?:0|[1-9]\d*)$/.test(name)) {
|
|
131
|
+
fail(`${path} contains unsupported array field ${name}`);
|
|
132
|
+
}
|
|
133
|
+
const index = Number(name);
|
|
134
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= length) {
|
|
135
|
+
fail(`${path} contains unsupported array field ${name}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (names.length !== length + 1) fail(`${path} must not contain sparse holes`);
|
|
139
|
+
const snapshot2 = [];
|
|
140
|
+
for (let index = 0; index < length; index += 1) {
|
|
141
|
+
const descriptor = descriptors.get(String(index));
|
|
142
|
+
if (!descriptor) fail(`${path}[${index}] must not be a sparse hole`);
|
|
143
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
144
|
+
fail(`${path}[${index}] must be an enumerable data field`);
|
|
145
|
+
}
|
|
146
|
+
snapshot2[index] = createDetachedSnapshot(
|
|
147
|
+
descriptor.value,
|
|
148
|
+
`${path}[${index}]`,
|
|
149
|
+
seen,
|
|
150
|
+
depth + 1
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
seen.delete(value);
|
|
154
|
+
return Object.freeze(snapshot2);
|
|
155
|
+
}
|
|
156
|
+
const prototype = Object.getPrototypeOf(value);
|
|
157
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
158
|
+
fail(`${path} must be a plain JSON object`);
|
|
159
|
+
}
|
|
160
|
+
const keys = Reflect.ownKeys(value);
|
|
161
|
+
const snapshot = {};
|
|
162
|
+
for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
|
|
163
|
+
const key = keys[keyIndex];
|
|
164
|
+
if (typeof key !== "string") fail(`${path} contains symbol fields`);
|
|
165
|
+
if (DANGEROUS_KEYS.has(key)) fail(`${path} contains unsupported field ${key}`);
|
|
166
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
167
|
+
if (!descriptor) fail(`${path}.${key} changed while creating the snapshot`);
|
|
168
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
169
|
+
fail(`${path}.${key} must be an enumerable data field`);
|
|
170
|
+
}
|
|
171
|
+
snapshot[key] = createDetachedSnapshot(
|
|
172
|
+
descriptor.value,
|
|
173
|
+
`${path}.${key}`,
|
|
174
|
+
seen,
|
|
175
|
+
depth + 1
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
seen.delete(value);
|
|
179
|
+
return Object.freeze(snapshot);
|
|
180
|
+
}
|
|
181
|
+
function nonEmptyString(value, path, maxLength) {
|
|
182
|
+
if (typeof value !== "string" || !value.trim()) fail(`${path} must be a non-empty string`);
|
|
183
|
+
if (maxLength != null && value.length > maxLength) {
|
|
184
|
+
fail(`${path} must be at most ${maxLength} characters`);
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
function finite(value, path) {
|
|
189
|
+
if (typeof value !== "number" || !Number.isFinite(value)) fail(`${path} must be finite`);
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
function validateSchemaVersion(value) {
|
|
193
|
+
if (typeof value !== "string" || !/^\d+\.\d+$/.test(value)) {
|
|
194
|
+
fail("video.schemaVersion must be a version string");
|
|
195
|
+
}
|
|
196
|
+
if (value !== VIDEO_SCHEMA_VERSION) {
|
|
197
|
+
throw new VideoValidationError(
|
|
198
|
+
`video.schemaVersion ${value} is unsupported`,
|
|
199
|
+
"unsupported_video_version"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function jsonValue(value, path, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
204
|
+
if (depth > MAX_JSON_DEPTH) fail(`${path} exceeds the maximum JSON depth`);
|
|
205
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
206
|
+
if (typeof value === "number") {
|
|
207
|
+
finite(value, path);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!value || typeof value !== "object" || seen.has(value)) {
|
|
211
|
+
fail(`${path} must be JSON-safe`);
|
|
212
|
+
}
|
|
213
|
+
seen.add(value);
|
|
214
|
+
if (Array.isArray(value)) {
|
|
215
|
+
const items = arrayItems(value, path);
|
|
216
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
217
|
+
jsonValue(items[index], `${path}[${index}]`, seen, depth + 1);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const result = record(value, path);
|
|
221
|
+
for (const key of Object.getOwnPropertyNames(result)) {
|
|
222
|
+
if (DANGEROUS_KEYS.has(key)) fail(`${path} contains unsupported field ${key}`);
|
|
223
|
+
jsonValue(requiredField(result, key, `${path}.${key}`), `${path}.${key}`, seen, depth + 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
seen.delete(value);
|
|
227
|
+
}
|
|
228
|
+
function validateTiming(value, path) {
|
|
229
|
+
const result = record(value, path);
|
|
230
|
+
allowedKeys(result, [
|
|
231
|
+
"beatStart",
|
|
232
|
+
"beatEnd",
|
|
233
|
+
"startTime",
|
|
234
|
+
"endTime",
|
|
235
|
+
"fixedDuration",
|
|
236
|
+
"durationWeight"
|
|
237
|
+
], path);
|
|
238
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const key of ["beatStart", "beatEnd"]) {
|
|
240
|
+
const field = ownField(result, key, `${path}.${key}`);
|
|
241
|
+
if (!field.present) continue;
|
|
242
|
+
const beat = finite(field.value, `${path}.${key}`);
|
|
243
|
+
if (!Number.isInteger(beat) || beat < 0) fail(`${path}.${key} must be a non-negative integer`);
|
|
244
|
+
numbers.set(key, beat);
|
|
245
|
+
}
|
|
246
|
+
for (const key of ["startTime", "endTime"]) {
|
|
247
|
+
const field = ownField(result, key, `${path}.${key}`);
|
|
248
|
+
if (!field.present) continue;
|
|
249
|
+
const time = finite(field.value, `${path}.${key}`);
|
|
250
|
+
if (time < 0) fail(`${path}.${key} must be non-negative`);
|
|
251
|
+
numbers.set(key, time);
|
|
252
|
+
}
|
|
253
|
+
for (const key of ["fixedDuration", "durationWeight"]) {
|
|
254
|
+
const field = ownField(result, key, `${path}.${key}`);
|
|
255
|
+
if (!field.present) continue;
|
|
256
|
+
const amount = finite(field.value, `${path}.${key}`);
|
|
257
|
+
if (amount <= 0) fail(`${path}.${key} must be positive`);
|
|
258
|
+
numbers.set(key, amount);
|
|
259
|
+
}
|
|
260
|
+
if (numbers.has("startTime") && numbers.has("endTime") && numbers.get("endTime") <= numbers.get("startTime")) {
|
|
261
|
+
fail(`${path}.endTime must be after ${path}.startTime`);
|
|
262
|
+
}
|
|
263
|
+
if (numbers.has("beatStart") && numbers.has("beatEnd") && numbers.get("beatEnd") <= numbers.get("beatStart")) {
|
|
264
|
+
fail(`${path}.beatEnd must be after ${path}.beatStart`);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function validateScene(value, path) {
|
|
269
|
+
const result = record(value, path);
|
|
270
|
+
allowedKeys(result, [
|
|
271
|
+
"id",
|
|
272
|
+
"templateId",
|
|
273
|
+
"variables",
|
|
274
|
+
"textArchetype",
|
|
275
|
+
"backgroundEffect",
|
|
276
|
+
"timing"
|
|
277
|
+
], path);
|
|
278
|
+
nonEmptyString(requiredField(result, "id", `${path}.id`), `${path}.id`, 128);
|
|
279
|
+
nonEmptyString(requiredField(result, "templateId", `${path}.templateId`), `${path}.templateId`, 128);
|
|
280
|
+
const variables = requiredField(result, "variables", `${path}.variables`);
|
|
281
|
+
record(variables, `${path}.variables`);
|
|
282
|
+
jsonValue(variables, `${path}.variables`);
|
|
283
|
+
validateTiming(requiredField(result, "timing", `${path}.timing`), `${path}.timing`);
|
|
284
|
+
for (const key of ["textArchetype", "backgroundEffect"]) {
|
|
285
|
+
const field = ownField(result, key, `${path}.${key}`);
|
|
286
|
+
if (field.present) nonEmptyString(field.value, `${path}.${key}`, 128);
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
function validateStyle(value, path) {
|
|
291
|
+
const result = record(value, path);
|
|
292
|
+
allowedKeys(result, [
|
|
293
|
+
"brand",
|
|
294
|
+
"preset",
|
|
295
|
+
"defaultBackgroundEffect",
|
|
296
|
+
"defaultTextArchetype",
|
|
297
|
+
"defaultTransition",
|
|
298
|
+
"density",
|
|
299
|
+
"motion"
|
|
300
|
+
], path);
|
|
301
|
+
const brandValue = requiredField(result, "brand", `${path}.brand`);
|
|
302
|
+
const brand = record(brandValue, `${path}.brand`);
|
|
303
|
+
for (const key of ["name", "logoUrl"]) {
|
|
304
|
+
const field = ownField(brand, key, `${path}.brand.${key}`);
|
|
305
|
+
if (field.present) nonEmptyString(field.value, `${path}.brand.${key}`);
|
|
306
|
+
}
|
|
307
|
+
validateVideoBrand(brandValue, `${path}.brand`);
|
|
308
|
+
for (const key of [
|
|
309
|
+
"preset",
|
|
310
|
+
"defaultBackgroundEffect",
|
|
311
|
+
"defaultTextArchetype",
|
|
312
|
+
"defaultTransition"
|
|
313
|
+
]) {
|
|
314
|
+
const field = ownField(result, key, `${path}.${key}`);
|
|
315
|
+
if (field.present) nonEmptyString(field.value, `${path}.${key}`, 128);
|
|
316
|
+
}
|
|
317
|
+
const density = ownField(result, "density", `${path}.density`);
|
|
318
|
+
if (density.present && density.value !== "airy" && density.value !== "normal" && density.value !== "packed") {
|
|
319
|
+
fail(`${path}.density is unsupported`);
|
|
320
|
+
}
|
|
321
|
+
const motion = ownField(result, "motion", `${path}.motion`);
|
|
322
|
+
if (motion.present && motion.value !== "calm" && motion.value !== "normal" && motion.value !== "punchy") {
|
|
323
|
+
fail(`${path}.motion is unsupported`);
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
function validateAudio(value, path) {
|
|
328
|
+
const result = record(value, path);
|
|
329
|
+
allowedKeys(result, [
|
|
330
|
+
"trackId",
|
|
331
|
+
"audioUrl",
|
|
332
|
+
"sourceDuration",
|
|
333
|
+
"duration",
|
|
334
|
+
"beatDetection",
|
|
335
|
+
"beatMarkers",
|
|
336
|
+
"volume",
|
|
337
|
+
"fadeOutMs"
|
|
338
|
+
], path);
|
|
339
|
+
nonEmptyString(requiredField(result, "trackId", `${path}.trackId`), `${path}.trackId`, 128);
|
|
340
|
+
nonEmptyString(
|
|
341
|
+
requiredField(result, "audioUrl", `${path}.audioUrl`),
|
|
342
|
+
`${path}.audioUrl`,
|
|
343
|
+
MAX_RETAINED_MEDIA_URL_LENGTH
|
|
344
|
+
);
|
|
345
|
+
if (finite(requiredField(result, "duration", `${path}.duration`), `${path}.duration`) <= 0) {
|
|
346
|
+
fail(`${path}.duration must be positive`);
|
|
347
|
+
}
|
|
348
|
+
const sourceDuration = ownField(result, "sourceDuration", `${path}.sourceDuration`);
|
|
349
|
+
if (sourceDuration.present && finite(sourceDuration.value, `${path}.sourceDuration`) <= 0) {
|
|
350
|
+
fail(`${path}.sourceDuration must be positive`);
|
|
351
|
+
}
|
|
352
|
+
const detection = record(
|
|
353
|
+
requiredField(result, "beatDetection", `${path}.beatDetection`),
|
|
354
|
+
`${path}.beatDetection`
|
|
355
|
+
);
|
|
356
|
+
allowedKeys(detection, ["sensitivity", "targetBeats", "minInterval"], `${path}.beatDetection`);
|
|
357
|
+
const sensitivity = finite(
|
|
358
|
+
requiredField(detection, "sensitivity", `${path}.beatDetection.sensitivity`),
|
|
359
|
+
`${path}.beatDetection.sensitivity`
|
|
360
|
+
);
|
|
361
|
+
if (sensitivity < 0 || sensitivity > 1) fail(`${path}.beatDetection.sensitivity must be 0\u20131`);
|
|
362
|
+
const targetBeats = ownField(detection, "targetBeats", `${path}.beatDetection.targetBeats`);
|
|
363
|
+
if (targetBeats.present) {
|
|
364
|
+
const target = finite(targetBeats.value, `${path}.beatDetection.targetBeats`);
|
|
365
|
+
if (!Number.isInteger(target) || target <= 0) {
|
|
366
|
+
fail(`${path}.beatDetection.targetBeats must be a positive integer`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const minInterval = ownField(detection, "minInterval", `${path}.beatDetection.minInterval`);
|
|
370
|
+
if (minInterval.present && finite(minInterval.value, `${path}.beatDetection.minInterval`) <= 0) {
|
|
371
|
+
fail(`${path}.beatDetection.minInterval must be positive`);
|
|
372
|
+
}
|
|
373
|
+
const markers = arrayItems(
|
|
374
|
+
requiredField(result, "beatMarkers", `${path}.beatMarkers`),
|
|
375
|
+
`${path}.beatMarkers`
|
|
376
|
+
);
|
|
377
|
+
let previousTime = -1;
|
|
378
|
+
for (let index = 0; index < markers.length; index += 1) {
|
|
379
|
+
const markerPath = `${path}.beatMarkers[${index}]`;
|
|
380
|
+
const marker = record(markers[index], markerPath);
|
|
381
|
+
allowedKeys(marker, ["time", "manual", "energy"], markerPath);
|
|
382
|
+
const time = finite(requiredField(marker, "time", `${markerPath}.time`), `${markerPath}.time`);
|
|
383
|
+
if (time < 0) fail(`${markerPath}.time must be non-negative`);
|
|
384
|
+
if (time <= previousTime) fail(`${markerPath}.time must be strictly increasing`);
|
|
385
|
+
previousTime = time;
|
|
386
|
+
const manual = ownField(marker, "manual", `${markerPath}.manual`);
|
|
387
|
+
if (manual.present && typeof manual.value !== "boolean") {
|
|
388
|
+
fail(`${markerPath}.manual must be boolean`);
|
|
389
|
+
}
|
|
390
|
+
const energy = ownField(marker, "energy", `${markerPath}.energy`);
|
|
391
|
+
if (energy.present && energy.value !== "high" && energy.value !== "medium" && energy.value !== "low") {
|
|
392
|
+
fail(`${markerPath}.energy is unsupported`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const volume = ownField(result, "volume", `${path}.volume`);
|
|
396
|
+
if (volume.present) {
|
|
397
|
+
const amount = finite(volume.value, `${path}.volume`);
|
|
398
|
+
if (amount < 0 || amount > 1) fail(`${path}.volume must be 0\u20131`);
|
|
399
|
+
}
|
|
400
|
+
const fadeOutMs = ownField(result, "fadeOutMs", `${path}.fadeOutMs`);
|
|
401
|
+
if (fadeOutMs.present && finite(fadeOutMs.value, `${path}.fadeOutMs`) < 0) {
|
|
402
|
+
fail(`${path}.fadeOutMs must be non-negative`);
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
function validateMeta(value, path) {
|
|
407
|
+
const result = record(value, path);
|
|
408
|
+
allowedKeys(result, ["name", "prompt", "source", "uploadedMediaUrls"], path);
|
|
409
|
+
const name = ownField(result, "name", `${path}.name`);
|
|
410
|
+
if (name.present) nonEmptyString(name.value, `${path}.name`, 256);
|
|
411
|
+
const prompt = ownField(result, "prompt", `${path}.prompt`);
|
|
412
|
+
if (prompt.present) {
|
|
413
|
+
nonEmptyString(prompt.value, `${path}.prompt`, MAX_RETAINED_INSTRUCTIONS_LENGTH);
|
|
414
|
+
}
|
|
415
|
+
const source = ownField(result, "source", `${path}.source`);
|
|
416
|
+
if (source.present) {
|
|
417
|
+
nonEmptyString(source.value, `${path}.source`, MAX_RETAINED_SOURCE_LENGTH);
|
|
418
|
+
}
|
|
419
|
+
const uploadedMediaUrls = ownField(result, "uploadedMediaUrls", `${path}.uploadedMediaUrls`);
|
|
420
|
+
if (uploadedMediaUrls.present) {
|
|
421
|
+
const urls = arrayItems(uploadedMediaUrls.value, `${path}.uploadedMediaUrls`);
|
|
422
|
+
if (urls.length > MAX_RETAINED_MEDIA_URLS) {
|
|
423
|
+
fail(`${path}.uploadedMediaUrls must contain at most ${MAX_RETAINED_MEDIA_URLS} entries`);
|
|
424
|
+
}
|
|
425
|
+
for (let index = 0; index < urls.length; index += 1) {
|
|
426
|
+
nonEmptyString(urls[index], `${path}.uploadedMediaUrls[${index}]`, MAX_RETAINED_MEDIA_URL_LENGTH);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function validateCurrentVideo(value) {
|
|
431
|
+
const result = record(value, "video");
|
|
432
|
+
allowedKeys(result, ["schemaVersion", "orientation", "audio", "scenes", "style", "meta"], "video");
|
|
433
|
+
validateSchemaVersion(requiredField(result, "schemaVersion", "video.schemaVersion"));
|
|
434
|
+
jsonValue(result, "video");
|
|
435
|
+
const orientation = ownField(result, "orientation", "video.orientation");
|
|
436
|
+
if (orientation.present && orientation.value !== "portrait" && orientation.value !== "landscape") {
|
|
437
|
+
fail("video.orientation is unsupported");
|
|
438
|
+
}
|
|
439
|
+
const audioField = ownField(result, "audio", "video.audio");
|
|
440
|
+
const audio = audioField.present ? validateAudio(audioField.value, "video.audio") : void 0;
|
|
441
|
+
const sceneItems = arrayItems(requiredField(result, "scenes", "video.scenes"), "video.scenes");
|
|
442
|
+
if (sceneItems.length === 0) fail("video.scenes must contain at least one completed scene");
|
|
443
|
+
const scenes = [];
|
|
444
|
+
for (let index = 0; index < sceneItems.length; index += 1) {
|
|
445
|
+
scenes[index] = validateScene(sceneItems[index], `video.scenes[${index}]`);
|
|
446
|
+
}
|
|
447
|
+
const ids = /* @__PURE__ */ new Set();
|
|
448
|
+
let cursor = 0;
|
|
449
|
+
for (let index = 0; index < scenes.length; index += 1) {
|
|
450
|
+
const scene = scenes[index];
|
|
451
|
+
if (ids.has(scene.id)) fail(`video.scenes[${index}].id must be unique`);
|
|
452
|
+
ids.add(scene.id);
|
|
453
|
+
const timing = scene.timing;
|
|
454
|
+
const beatStart = timing.beatStart == null ? void 0 : audio?.beatMarkers[timing.beatStart]?.time;
|
|
455
|
+
const beatEnd = timing.beatEnd == null ? void 0 : audio?.beatMarkers[timing.beatEnd]?.time;
|
|
456
|
+
if (timing.beatStart != null && beatStart == null) {
|
|
457
|
+
fail(`video.scenes[${index}].timing.beatStart does not reference an audio beat`);
|
|
458
|
+
}
|
|
459
|
+
if (timing.beatEnd != null && beatEnd == null) {
|
|
460
|
+
fail(`video.scenes[${index}].timing.beatEnd does not reference an audio beat`);
|
|
461
|
+
}
|
|
462
|
+
const start = timing.startTime ?? beatStart ?? cursor;
|
|
463
|
+
const end = timing.endTime ?? beatEnd ?? start + (timing.fixedDuration ?? 5);
|
|
464
|
+
if (start < cursor) fail(`video.scenes[${index}] overlaps the previous scene`);
|
|
465
|
+
if (end <= start) fail(`video.scenes[${index}] must have positive resolved duration`);
|
|
466
|
+
cursor = end;
|
|
467
|
+
}
|
|
468
|
+
validateStyle(requiredField(result, "style", "video.style"), "video.style");
|
|
469
|
+
const meta = ownField(result, "meta", "video.meta");
|
|
470
|
+
if (meta.present) validateMeta(meta.value, "video.meta");
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
function asValidationError(error) {
|
|
474
|
+
try {
|
|
475
|
+
return error instanceof VideoValidationError ? error : void 0;
|
|
476
|
+
} catch {
|
|
477
|
+
return void 0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function parseVideo(value) {
|
|
481
|
+
try {
|
|
482
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
483
|
+
fail("video must be an object");
|
|
484
|
+
}
|
|
485
|
+
const versionField = ownField(value, "schemaVersion", "video.schemaVersion");
|
|
486
|
+
if (!versionField.present) fail("video.schemaVersion is required");
|
|
487
|
+
validateSchemaVersion(versionField.value);
|
|
488
|
+
const snapshot = createDetachedSnapshot(value, "video");
|
|
489
|
+
return validateCurrentVideo(snapshot);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
const validationError = asValidationError(error);
|
|
492
|
+
if (validationError) throw validationError;
|
|
493
|
+
throw new VideoValidationError("video is invalid");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export {
|
|
498
|
+
MAX_RETAINED_SOURCE_LENGTH,
|
|
499
|
+
MAX_RETAINED_INSTRUCTIONS_LENGTH,
|
|
500
|
+
MAX_RETAINED_MEDIA_URLS,
|
|
501
|
+
MAX_RETAINED_MEDIA_URL_LENGTH,
|
|
502
|
+
VideoValidationError,
|
|
503
|
+
parseVideo
|
|
504
|
+
};
|