cinematic-renderer2d 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/LICENSE +21 -0
- package/README.md +827 -0
- package/dist/angular.cjs +189 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +577 -0
- package/dist/angular.d.ts +577 -0
- package/dist/angular.js +189 -0
- package/dist/angular.js.map +1 -0
- package/dist/cli/index.js +1243 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +182 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1663 -0
- package/dist/index.d.ts +1663 -0
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/react.cjs +182 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +510 -0
- package/dist/react.d.ts +510 -0
- package/dist/react.js +182 -0
- package/dist/react.js.map +1 -0
- package/package.json +151 -0
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
// src/cli/index.ts
|
|
7
|
+
|
|
8
|
+
// src/animation/AnimationCompiler.ts
|
|
9
|
+
var AnimationCompiler = class {
|
|
10
|
+
/**
|
|
11
|
+
* Compiles an animation track specification into an optimized runtime track
|
|
12
|
+
*
|
|
13
|
+
* @param track - Animation track specification to compile
|
|
14
|
+
* @returns Compiled animation track with precompiled interpolation function
|
|
15
|
+
*/
|
|
16
|
+
static compileTrack(track) {
|
|
17
|
+
const easingFunction = this.compileEasing(track.easing || "ease");
|
|
18
|
+
const interpolationFunction = this.compileInterpolation(track.from, track.to, easingFunction);
|
|
19
|
+
return {
|
|
20
|
+
property: track.property,
|
|
21
|
+
startMs: track.startMs,
|
|
22
|
+
endMs: track.endMs,
|
|
23
|
+
interpolate: interpolationFunction,
|
|
24
|
+
loop: track.loop || false,
|
|
25
|
+
yoyo: track.yoyo || false,
|
|
26
|
+
easingType: track.easing || "ease",
|
|
27
|
+
currentLoop: 0,
|
|
28
|
+
isReverse: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compiles an easing function from string specification to optimized function
|
|
33
|
+
*
|
|
34
|
+
* @param easing - Easing type or cubic-bezier specification
|
|
35
|
+
* @returns Optimized easing function that maps [0,1] to [0,1]
|
|
36
|
+
*/
|
|
37
|
+
static compileEasing(easing) {
|
|
38
|
+
if (typeof easing === "string" && easing.startsWith("cubic-bezier(")) {
|
|
39
|
+
return this.compileCubicBezier(easing);
|
|
40
|
+
}
|
|
41
|
+
switch (easing) {
|
|
42
|
+
case "linear":
|
|
43
|
+
return (t) => t;
|
|
44
|
+
case "ease":
|
|
45
|
+
return this.cubicBezier(0.25, 0.1, 0.25, 1);
|
|
46
|
+
case "ease-in":
|
|
47
|
+
return this.cubicBezier(0.42, 0, 1, 1);
|
|
48
|
+
case "ease-out":
|
|
49
|
+
return this.cubicBezier(0, 0, 0.58, 1);
|
|
50
|
+
case "ease-in-out":
|
|
51
|
+
return this.cubicBezier(0.42, 0, 0.58, 1);
|
|
52
|
+
// Sine easing functions
|
|
53
|
+
case "ease-in-sine":
|
|
54
|
+
return (t) => 1 - Math.cos(t * Math.PI / 2);
|
|
55
|
+
case "ease-out-sine":
|
|
56
|
+
return (t) => Math.sin(t * Math.PI / 2);
|
|
57
|
+
case "ease-in-out-sine":
|
|
58
|
+
return (t) => -(Math.cos(Math.PI * t) - 1) / 2;
|
|
59
|
+
// Quadratic easing functions
|
|
60
|
+
case "ease-in-quad":
|
|
61
|
+
return (t) => t * t;
|
|
62
|
+
case "ease-out-quad":
|
|
63
|
+
return (t) => 1 - (1 - t) * (1 - t);
|
|
64
|
+
case "ease-in-out-quad":
|
|
65
|
+
return (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
66
|
+
// Cubic easing functions
|
|
67
|
+
case "ease-in-cubic":
|
|
68
|
+
return (t) => t * t * t;
|
|
69
|
+
case "ease-out-cubic":
|
|
70
|
+
return (t) => 1 - Math.pow(1 - t, 3);
|
|
71
|
+
case "ease-in-out-cubic":
|
|
72
|
+
return (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
73
|
+
// Quartic easing functions
|
|
74
|
+
case "ease-in-quart":
|
|
75
|
+
return (t) => t * t * t * t;
|
|
76
|
+
case "ease-out-quart":
|
|
77
|
+
return (t) => 1 - Math.pow(1 - t, 4);
|
|
78
|
+
case "ease-in-out-quart":
|
|
79
|
+
return (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
|
|
80
|
+
// Quintic easing functions
|
|
81
|
+
case "ease-in-quint":
|
|
82
|
+
return (t) => t * t * t * t * t;
|
|
83
|
+
case "ease-out-quint":
|
|
84
|
+
return (t) => 1 - Math.pow(1 - t, 5);
|
|
85
|
+
case "ease-in-out-quint":
|
|
86
|
+
return (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
|
|
87
|
+
// Exponential easing functions
|
|
88
|
+
case "ease-in-expo":
|
|
89
|
+
return (t) => t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
|
|
90
|
+
case "ease-out-expo":
|
|
91
|
+
return (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
|
|
92
|
+
case "ease-in-out-expo":
|
|
93
|
+
return (t) => {
|
|
94
|
+
if (t === 0) return 0;
|
|
95
|
+
if (t === 1) return 1;
|
|
96
|
+
return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
|
|
97
|
+
};
|
|
98
|
+
// Circular easing functions
|
|
99
|
+
case "ease-in-circ":
|
|
100
|
+
return (t) => 1 - Math.sqrt(1 - Math.pow(t, 2));
|
|
101
|
+
case "ease-out-circ":
|
|
102
|
+
return (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
|
|
103
|
+
case "ease-in-out-circ":
|
|
104
|
+
return (t) => t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
|
|
105
|
+
// Back easing functions
|
|
106
|
+
case "ease-in-back":
|
|
107
|
+
return this.backEasing(1.70158, "in");
|
|
108
|
+
case "ease-out-back":
|
|
109
|
+
return this.backEasing(1.70158, "out");
|
|
110
|
+
case "ease-in-out-back":
|
|
111
|
+
return this.backEasing(1.70158, "in-out");
|
|
112
|
+
// Elastic easing functions
|
|
113
|
+
case "ease-in-elastic":
|
|
114
|
+
return this.elasticEasing(2 * Math.PI / 3, "in");
|
|
115
|
+
case "ease-out-elastic":
|
|
116
|
+
return this.elasticEasing(2 * Math.PI / 3, "out");
|
|
117
|
+
case "ease-in-out-elastic":
|
|
118
|
+
return this.elasticEasing(2 * Math.PI / 4.5, "in-out");
|
|
119
|
+
// Bounce easing functions
|
|
120
|
+
case "ease-in-bounce":
|
|
121
|
+
return (t) => 1 - this.bounceOut(1 - t);
|
|
122
|
+
case "ease-out-bounce":
|
|
123
|
+
return this.bounceOut;
|
|
124
|
+
case "ease-in-out-bounce":
|
|
125
|
+
return (t) => t < 0.5 ? (1 - this.bounceOut(1 - 2 * t)) / 2 : (1 + this.bounceOut(2 * t - 1)) / 2;
|
|
126
|
+
default:
|
|
127
|
+
return this.cubicBezier(0.25, 0.1, 0.25, 1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compiles an interpolation function for animating between two values
|
|
132
|
+
*
|
|
133
|
+
* @param from - Starting value
|
|
134
|
+
* @param to - Ending value
|
|
135
|
+
* @param easingFunction - Easing function to apply
|
|
136
|
+
* @returns Optimized interpolation function
|
|
137
|
+
*/
|
|
138
|
+
static compileInterpolation(from, to, easingFunction) {
|
|
139
|
+
const fromType = this.getValueType(from);
|
|
140
|
+
const toType = this.getValueType(to);
|
|
141
|
+
if (fromType !== toType) {
|
|
142
|
+
return (progress) => {
|
|
143
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
144
|
+
return easedProgress < 0.5 ? from : to;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
switch (fromType) {
|
|
148
|
+
case "number":
|
|
149
|
+
return this.compileNumberInterpolation(from, to, easingFunction);
|
|
150
|
+
case "string":
|
|
151
|
+
return this.compileStringInterpolation(from, to, easingFunction);
|
|
152
|
+
case "boolean":
|
|
153
|
+
return this.compileBooleanInterpolation(from, to, easingFunction);
|
|
154
|
+
case "object":
|
|
155
|
+
return this.compileObjectInterpolation(
|
|
156
|
+
from,
|
|
157
|
+
to,
|
|
158
|
+
easingFunction
|
|
159
|
+
);
|
|
160
|
+
default:
|
|
161
|
+
return (progress) => {
|
|
162
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
163
|
+
return easedProgress < 0.5 ? from : to;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Creates a cubic-bezier easing function with the given control points
|
|
169
|
+
*/
|
|
170
|
+
static cubicBezier(x1, y1, x2, y2) {
|
|
171
|
+
return (t) => {
|
|
172
|
+
const cx = 3 * x1;
|
|
173
|
+
const bx = 3 * (x2 - x1) - cx;
|
|
174
|
+
const ax = 1 - cx - bx;
|
|
175
|
+
const cy = 3 * y1;
|
|
176
|
+
const by = 3 * (y2 - y1) - cy;
|
|
177
|
+
const ay = 1 - cy - by;
|
|
178
|
+
let x = t;
|
|
179
|
+
for (let i = 0; i < 8; i++) {
|
|
180
|
+
const currentX = ((ax * x + bx) * x + cx) * x;
|
|
181
|
+
const currentSlope = (3 * ax * x + 2 * bx) * x + cx;
|
|
182
|
+
if (Math.abs(currentSlope) < 1e-6) break;
|
|
183
|
+
x = x - (currentX - t) / currentSlope;
|
|
184
|
+
}
|
|
185
|
+
return ((ay * x + by) * x + cy) * x;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Parses and compiles a cubic-bezier string specification
|
|
190
|
+
*/
|
|
191
|
+
static compileCubicBezier(bezierString) {
|
|
192
|
+
const match = bezierString.match(/cubic-bezier\(([^)]+)\)/);
|
|
193
|
+
if (!match || !match[1]) {
|
|
194
|
+
return this.cubicBezier(0.25, 0.1, 0.25, 1);
|
|
195
|
+
}
|
|
196
|
+
const values = match[1].split(",").map((v) => parseFloat(v.trim()));
|
|
197
|
+
if (values.length !== 4 || values.some((v) => isNaN(v))) {
|
|
198
|
+
return this.cubicBezier(0.25, 0.1, 0.25, 1);
|
|
199
|
+
}
|
|
200
|
+
return this.cubicBezier(values[0], values[1], values[2], values[3]);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Creates a back easing function with overshoot
|
|
204
|
+
*/
|
|
205
|
+
static backEasing(c, type) {
|
|
206
|
+
const c1 = c;
|
|
207
|
+
const c2 = c1 * 1.525;
|
|
208
|
+
const c3 = c1 + 1;
|
|
209
|
+
switch (type) {
|
|
210
|
+
case "in":
|
|
211
|
+
return (t) => c3 * t * t * t - c1 * t * t;
|
|
212
|
+
case "out":
|
|
213
|
+
return (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
214
|
+
case "in-out":
|
|
215
|
+
return (t) => t < 0.5 ? Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
216
|
+
default:
|
|
217
|
+
return (t) => t;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Creates an elastic easing function with oscillation
|
|
222
|
+
*/
|
|
223
|
+
static elasticEasing(c, type) {
|
|
224
|
+
switch (type) {
|
|
225
|
+
case "in":
|
|
226
|
+
return (t) => {
|
|
227
|
+
if (t === 0) return 0;
|
|
228
|
+
if (t === 1) return 1;
|
|
229
|
+
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c);
|
|
230
|
+
};
|
|
231
|
+
case "out":
|
|
232
|
+
return (t) => {
|
|
233
|
+
if (t === 0) return 0;
|
|
234
|
+
if (t === 1) return 1;
|
|
235
|
+
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c) + 1;
|
|
236
|
+
};
|
|
237
|
+
case "in-out":
|
|
238
|
+
return (t) => {
|
|
239
|
+
if (t === 0) return 0;
|
|
240
|
+
if (t === 1) return 1;
|
|
241
|
+
return t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c)) / 2 : Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c) / 2 + 1;
|
|
242
|
+
};
|
|
243
|
+
default:
|
|
244
|
+
return (t) => t;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Bounce easing out function
|
|
249
|
+
*/
|
|
250
|
+
static bounceOut(t) {
|
|
251
|
+
const n1 = 7.5625;
|
|
252
|
+
const d1 = 2.75;
|
|
253
|
+
if (t < 1 / d1) {
|
|
254
|
+
return n1 * t * t;
|
|
255
|
+
} else if (t < 2 / d1) {
|
|
256
|
+
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
257
|
+
} else if (t < 2.5 / d1) {
|
|
258
|
+
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
259
|
+
} else {
|
|
260
|
+
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Determines the type of an animation value
|
|
265
|
+
*/
|
|
266
|
+
static getValueType(value) {
|
|
267
|
+
if (typeof value === "number") return "number";
|
|
268
|
+
if (typeof value === "string") return "string";
|
|
269
|
+
if (typeof value === "boolean") return "boolean";
|
|
270
|
+
if (typeof value === "object" && value !== null) return "object";
|
|
271
|
+
return "object";
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Compiles number interpolation with optimized performance
|
|
275
|
+
*/
|
|
276
|
+
static compileNumberInterpolation(from, to, easingFunction) {
|
|
277
|
+
const delta = to - from;
|
|
278
|
+
return (progress) => {
|
|
279
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
280
|
+
return from + delta * easedProgress;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Compiles string interpolation with CSS value support
|
|
285
|
+
*/
|
|
286
|
+
static compileStringInterpolation(from, to, easingFunction) {
|
|
287
|
+
const fromMatch = from.match(/^([+-]?\d*\.?\d+)(.*)$/);
|
|
288
|
+
const toMatch = to.match(/^([+-]?\d*\.?\d+)(.*)$/);
|
|
289
|
+
if (fromMatch && toMatch && fromMatch[2] === toMatch[2]) {
|
|
290
|
+
const fromNum = parseFloat(fromMatch[1]);
|
|
291
|
+
const toNum = parseFloat(toMatch[1]);
|
|
292
|
+
const unit = fromMatch[2];
|
|
293
|
+
const delta = toNum - fromNum;
|
|
294
|
+
return (progress) => {
|
|
295
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
296
|
+
const interpolatedValue = fromNum + delta * easedProgress;
|
|
297
|
+
const roundedValue = Math.round(interpolatedValue * 1e6) / 1e6;
|
|
298
|
+
return `${roundedValue}${unit}`;
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return (progress) => {
|
|
302
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
303
|
+
return easedProgress < 0.5 ? from : to;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Compiles boolean interpolation (discrete transition)
|
|
308
|
+
*/
|
|
309
|
+
static compileBooleanInterpolation(from, to, easingFunction) {
|
|
310
|
+
return (progress) => {
|
|
311
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
312
|
+
return easedProgress < 0.5 ? from : to;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Compiles object interpolation for complex values
|
|
317
|
+
*/
|
|
318
|
+
static compileObjectInterpolation(from, to, easingFunction) {
|
|
319
|
+
const propertyInterpolators = /* @__PURE__ */ new Map();
|
|
320
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(from), ...Object.keys(to)]);
|
|
321
|
+
for (const key of allKeys) {
|
|
322
|
+
const fromValue = from[key];
|
|
323
|
+
const toValue = to[key];
|
|
324
|
+
if (fromValue !== void 0 && toValue !== void 0) {
|
|
325
|
+
propertyInterpolators.set(key, this.compileInterpolation(fromValue, toValue, easingFunction));
|
|
326
|
+
} else if (fromValue !== void 0) {
|
|
327
|
+
propertyInterpolators.set(key, () => fromValue);
|
|
328
|
+
} else {
|
|
329
|
+
propertyInterpolators.set(key, (progress) => {
|
|
330
|
+
const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
|
|
331
|
+
return easedProgress < 0.5 ? void 0 : toValue;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return (progress) => {
|
|
336
|
+
const result = {};
|
|
337
|
+
for (const [key, interpolator] of propertyInterpolators) {
|
|
338
|
+
const value = interpolator(progress);
|
|
339
|
+
if (value !== void 0) {
|
|
340
|
+
result[key] = value;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/parsing/SpecParser.ts
|
|
349
|
+
var SUPPORTED_SCHEMA_VERSIONS = ["1.0.0", "1.1.0"];
|
|
350
|
+
var QualityLevelSchema = z.enum(["low", "medium", "high", "ultra", "auto"]);
|
|
351
|
+
var LayerTypeSchema = z.enum([
|
|
352
|
+
"gradient",
|
|
353
|
+
"image",
|
|
354
|
+
"textBlock",
|
|
355
|
+
"vignette",
|
|
356
|
+
"glowOrb",
|
|
357
|
+
"noiseOverlay",
|
|
358
|
+
"particles",
|
|
359
|
+
"starfield",
|
|
360
|
+
"dust",
|
|
361
|
+
"nebulaNoise",
|
|
362
|
+
"webgl-custom"
|
|
363
|
+
]);
|
|
364
|
+
var EasingTypeSchema = z.enum([
|
|
365
|
+
"linear",
|
|
366
|
+
"ease",
|
|
367
|
+
"ease-in",
|
|
368
|
+
"ease-out",
|
|
369
|
+
"ease-in-out",
|
|
370
|
+
"ease-in-sine",
|
|
371
|
+
"ease-out-sine",
|
|
372
|
+
"ease-in-out-sine",
|
|
373
|
+
"ease-in-quad",
|
|
374
|
+
"ease-out-quad",
|
|
375
|
+
"ease-in-out-quad",
|
|
376
|
+
"ease-in-cubic",
|
|
377
|
+
"ease-out-cubic",
|
|
378
|
+
"ease-in-out-cubic",
|
|
379
|
+
"ease-in-quart",
|
|
380
|
+
"ease-out-quart",
|
|
381
|
+
"ease-in-out-quart",
|
|
382
|
+
"ease-in-quint",
|
|
383
|
+
"ease-out-quint",
|
|
384
|
+
"ease-in-out-quint",
|
|
385
|
+
"ease-in-expo",
|
|
386
|
+
"ease-out-expo",
|
|
387
|
+
"ease-in-out-expo",
|
|
388
|
+
"ease-in-circ",
|
|
389
|
+
"ease-out-circ",
|
|
390
|
+
"ease-in-out-circ",
|
|
391
|
+
"ease-in-back",
|
|
392
|
+
"ease-out-back",
|
|
393
|
+
"ease-in-out-back",
|
|
394
|
+
"ease-in-elastic",
|
|
395
|
+
"ease-out-elastic",
|
|
396
|
+
"ease-in-out-elastic",
|
|
397
|
+
"ease-in-bounce",
|
|
398
|
+
"ease-out-bounce",
|
|
399
|
+
"ease-in-out-bounce"
|
|
400
|
+
]).or(z.string().regex(/^cubic-bezier\(\d*\.?\d+,\d*\.?\d+,\d*\.?\d+,\d*\.?\d+\)$/));
|
|
401
|
+
var AudioTrackTypeSchema = z.enum(["voiceover", "ambience", "transition", "music", "sfx"]);
|
|
402
|
+
var TransitionTypeSchema = z.enum(["fade", "slide", "zoom", "wipe", "dissolve", "blur"]);
|
|
403
|
+
var AssetTypeSchema = z.enum(["image", "video", "audio", "font", "json", "binary"]);
|
|
404
|
+
var TransformConfigSchema = z.object({
|
|
405
|
+
x: z.union([z.number(), z.string()]).optional(),
|
|
406
|
+
y: z.union([z.number(), z.string()]).optional(),
|
|
407
|
+
scale: z.number().optional(),
|
|
408
|
+
rotation: z.number().optional(),
|
|
409
|
+
origin: z.string().optional()
|
|
410
|
+
}).optional();
|
|
411
|
+
var LayerConfigSchema = z.object({
|
|
412
|
+
opacity: z.number().min(0).max(1).optional(),
|
|
413
|
+
visible: z.boolean().optional(),
|
|
414
|
+
transform: TransformConfigSchema
|
|
415
|
+
}).catchall(z.any());
|
|
416
|
+
var AnimationValueSchema = z.union([
|
|
417
|
+
z.number(),
|
|
418
|
+
z.string(),
|
|
419
|
+
z.boolean(),
|
|
420
|
+
z.record(z.union([z.number(), z.string(), z.boolean()]))
|
|
421
|
+
]);
|
|
422
|
+
var AnimationTrackSpecSchema = z.object({
|
|
423
|
+
property: z.string().min(1),
|
|
424
|
+
from: AnimationValueSchema,
|
|
425
|
+
to: AnimationValueSchema,
|
|
426
|
+
startMs: z.number().min(0),
|
|
427
|
+
endMs: z.number().min(0),
|
|
428
|
+
easing: EasingTypeSchema.optional().default("ease"),
|
|
429
|
+
loop: z.boolean().optional().default(false),
|
|
430
|
+
yoyo: z.boolean().optional().default(false)
|
|
431
|
+
}).refine((data) => data.endMs > data.startMs, {
|
|
432
|
+
message: "endMs must be greater than startMs",
|
|
433
|
+
path: ["endMs"]
|
|
434
|
+
});
|
|
435
|
+
var AudioTrackSpecSchema = z.object({
|
|
436
|
+
id: z.string().min(1),
|
|
437
|
+
type: AudioTrackTypeSchema,
|
|
438
|
+
src: z.string().min(1),
|
|
439
|
+
startMs: z.number().min(0),
|
|
440
|
+
endMs: z.number().min(0).optional(),
|
|
441
|
+
volume: z.number().min(0).max(1).optional().default(1),
|
|
442
|
+
fadeIn: z.number().min(0).optional().default(0),
|
|
443
|
+
fadeOut: z.number().min(0).optional().default(0),
|
|
444
|
+
loop: z.boolean().optional().default(false)
|
|
445
|
+
});
|
|
446
|
+
var TransitionConfigSchema = z.object({
|
|
447
|
+
direction: z.enum(["up", "down", "left", "right", "in", "out"]).optional(),
|
|
448
|
+
blurAmount: z.number().min(0).optional()
|
|
449
|
+
}).catchall(z.any()).optional();
|
|
450
|
+
var TransitionSpecSchema = z.object({
|
|
451
|
+
type: TransitionTypeSchema,
|
|
452
|
+
duration: z.number().min(0),
|
|
453
|
+
easing: EasingTypeSchema.optional().default("ease"),
|
|
454
|
+
config: TransitionConfigSchema
|
|
455
|
+
});
|
|
456
|
+
var AssetMetadataSchema = z.object({
|
|
457
|
+
size: z.number().min(0).optional(),
|
|
458
|
+
mimeType: z.string().optional(),
|
|
459
|
+
cacheDuration: z.number().min(0).optional(),
|
|
460
|
+
dimensions: z.object({
|
|
461
|
+
width: z.number().min(1),
|
|
462
|
+
height: z.number().min(1)
|
|
463
|
+
}).optional(),
|
|
464
|
+
duration: z.number().min(0).optional()
|
|
465
|
+
}).optional();
|
|
466
|
+
var AssetDefinitionSchema = z.object({
|
|
467
|
+
id: z.string().min(1),
|
|
468
|
+
type: AssetTypeSchema,
|
|
469
|
+
src: z.string().min(1),
|
|
470
|
+
preload: z.boolean().optional().default(true),
|
|
471
|
+
fallback: z.string().optional(),
|
|
472
|
+
metadata: AssetMetadataSchema
|
|
473
|
+
});
|
|
474
|
+
var LayerSpecSchema = z.object({
|
|
475
|
+
id: z.string().min(1),
|
|
476
|
+
type: LayerTypeSchema,
|
|
477
|
+
zIndex: z.number(),
|
|
478
|
+
config: LayerConfigSchema,
|
|
479
|
+
animations: z.array(AnimationTrackSpecSchema).optional().default([])
|
|
480
|
+
});
|
|
481
|
+
var CinematicSceneSchema = z.object({
|
|
482
|
+
id: z.string().min(1),
|
|
483
|
+
name: z.string().min(1),
|
|
484
|
+
duration: z.number().min(0),
|
|
485
|
+
layers: z.array(LayerSpecSchema).min(1),
|
|
486
|
+
audio: z.array(AudioTrackSpecSchema).optional().default([])
|
|
487
|
+
});
|
|
488
|
+
var CinematicEventSchema = z.object({
|
|
489
|
+
id: z.string().min(1),
|
|
490
|
+
name: z.string().min(1),
|
|
491
|
+
scenes: z.array(z.string().min(1)).min(1),
|
|
492
|
+
transitions: z.array(TransitionSpecSchema).optional().default([])
|
|
493
|
+
});
|
|
494
|
+
var EngineConfigSchema = z.object({
|
|
495
|
+
targetFps: z.number().min(1).max(240).optional().default(60),
|
|
496
|
+
quality: QualityLevelSchema.optional().default("auto"),
|
|
497
|
+
debug: z.boolean().optional().default(false),
|
|
498
|
+
autoplay: z.boolean().optional().default(false)
|
|
499
|
+
});
|
|
500
|
+
var CinematicSpecSchema = z.object({
|
|
501
|
+
schemaVersion: z.string().min(1),
|
|
502
|
+
engine: EngineConfigSchema,
|
|
503
|
+
events: z.array(CinematicEventSchema).min(1),
|
|
504
|
+
scenes: z.array(CinematicSceneSchema).min(1),
|
|
505
|
+
assets: z.array(AssetDefinitionSchema).optional().default([])
|
|
506
|
+
}).refine((data) => {
|
|
507
|
+
const sceneIds = new Set(data.scenes.map((scene) => scene.id));
|
|
508
|
+
const referencedSceneIds = new Set(data.events.flatMap((event) => event.scenes));
|
|
509
|
+
for (const sceneId of referencedSceneIds) {
|
|
510
|
+
if (!sceneIds.has(sceneId)) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return true;
|
|
515
|
+
}, {
|
|
516
|
+
message: "All scene IDs referenced in events must exist in the scenes array",
|
|
517
|
+
path: ["events"]
|
|
518
|
+
}).refine((data) => {
|
|
519
|
+
var _a;
|
|
520
|
+
const eventIds = data.events.map((e) => e.id);
|
|
521
|
+
const sceneIds = data.scenes.map((s) => s.id);
|
|
522
|
+
const assetIds = ((_a = data.assets) == null ? void 0 : _a.map((a) => a.id)) || [];
|
|
523
|
+
return new Set(eventIds).size === eventIds.length && new Set(sceneIds).size === sceneIds.length && new Set(assetIds).size === assetIds.length;
|
|
524
|
+
}, {
|
|
525
|
+
message: "All IDs must be unique within their respective collections",
|
|
526
|
+
path: ["events", "scenes", "assets"]
|
|
527
|
+
});
|
|
528
|
+
var SpecParser = class {
|
|
529
|
+
/**
|
|
530
|
+
* Validates and parses a raw JSON specification into a validated CinematicSpec
|
|
531
|
+
*
|
|
532
|
+
* @param spec - Raw JSON specification to validate
|
|
533
|
+
* @returns Validated CinematicSpec with applied defaults
|
|
534
|
+
* @throws ZodError with descriptive validation messages
|
|
535
|
+
*/
|
|
536
|
+
static validate(spec) {
|
|
537
|
+
try {
|
|
538
|
+
if (typeof spec !== "object" || spec === null) {
|
|
539
|
+
throw new Error("Specification must be a valid object");
|
|
540
|
+
}
|
|
541
|
+
const rawSpec = spec;
|
|
542
|
+
const schemaVersion = rawSpec["schemaVersion"];
|
|
543
|
+
if (!schemaVersion || typeof schemaVersion !== "string") {
|
|
544
|
+
throw new Error("Missing or invalid schemaVersion. Current supported versions: " + SUPPORTED_SCHEMA_VERSIONS.join(", "));
|
|
545
|
+
}
|
|
546
|
+
if (!SUPPORTED_SCHEMA_VERSIONS.includes(schemaVersion)) {
|
|
547
|
+
throw new Error(`Unsupported schema version: ${schemaVersion}. Supported versions: ${SUPPORTED_SCHEMA_VERSIONS.join(", ")}`);
|
|
548
|
+
}
|
|
549
|
+
const validatedSpec = this.validateByVersion(spec, schemaVersion);
|
|
550
|
+
return validatedSpec;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error instanceof z.ZodError) {
|
|
553
|
+
throw new Error(this.formatZodError(error));
|
|
554
|
+
}
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Compiles a validated CinematicSpec into an optimized CompiledSpec
|
|
560
|
+
*
|
|
561
|
+
* @param spec - Validated CinematicSpec to compile
|
|
562
|
+
* @returns CompiledSpec with precompiled animations and optimized runtime structures
|
|
563
|
+
*/
|
|
564
|
+
static parse(spec) {
|
|
565
|
+
const context = {
|
|
566
|
+
schemaVersion: spec.schemaVersion,
|
|
567
|
+
assetPromises: /* @__PURE__ */ new Map(),
|
|
568
|
+
layerTypes: new Set(spec.scenes.flatMap((s) => s.layers.map((l) => l.type))),
|
|
569
|
+
diagnostics: [],
|
|
570
|
+
optimizations: {
|
|
571
|
+
precompileAnimations: true,
|
|
572
|
+
optimizeAssetLoading: true,
|
|
573
|
+
enableObjectPooling: true,
|
|
574
|
+
minimizeDOMUpdates: true,
|
|
575
|
+
useWebWorkers: false
|
|
576
|
+
// Disabled for now
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
try {
|
|
580
|
+
const compiledAssets = this.compileAssets(spec.assets || [], context);
|
|
581
|
+
const compiledScenes = this.compileScenes(spec.scenes, context);
|
|
582
|
+
const compiledEvents = this.compileEvents(spec.events, compiledScenes, context);
|
|
583
|
+
const totalDuration = Math.max(...Array.from(compiledEvents.values()).map((event) => event.startTime + event.duration));
|
|
584
|
+
const compiledSpec = {
|
|
585
|
+
events: compiledEvents,
|
|
586
|
+
scenes: compiledScenes,
|
|
587
|
+
assets: compiledAssets,
|
|
588
|
+
globalConfig: spec.engine,
|
|
589
|
+
schemaVersion: spec.schemaVersion,
|
|
590
|
+
totalDuration,
|
|
591
|
+
compiledAt: Date.now()
|
|
592
|
+
};
|
|
593
|
+
if (context.diagnostics.length > 0) {
|
|
594
|
+
console.warn("Compilation completed with warnings:", context.diagnostics);
|
|
595
|
+
}
|
|
596
|
+
return compiledSpec;
|
|
597
|
+
} catch (error) {
|
|
598
|
+
context.diagnostics.push({
|
|
599
|
+
level: "error",
|
|
600
|
+
message: error instanceof Error ? error.message : "Unknown compilation error",
|
|
601
|
+
code: "COMPILATION_ERROR"
|
|
602
|
+
});
|
|
603
|
+
throw new Error(`Compilation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Validates specification based on schema version
|
|
608
|
+
*/
|
|
609
|
+
static validateByVersion(spec, version) {
|
|
610
|
+
switch (version) {
|
|
611
|
+
case "1.0.0":
|
|
612
|
+
case "1.1.0":
|
|
613
|
+
return CinematicSpecSchema.parse(spec);
|
|
614
|
+
default:
|
|
615
|
+
throw new Error(`Unsupported schema version: ${version}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Compiles asset definitions into runtime assets
|
|
620
|
+
*/
|
|
621
|
+
static compileAssets(assets, _context) {
|
|
622
|
+
var _a, _b, _c, _d, _e;
|
|
623
|
+
const compiledAssets = /* @__PURE__ */ new Map();
|
|
624
|
+
for (const assetDef of assets) {
|
|
625
|
+
const asset = {
|
|
626
|
+
id: assetDef.id,
|
|
627
|
+
type: assetDef.type,
|
|
628
|
+
src: assetDef.src,
|
|
629
|
+
data: null,
|
|
630
|
+
loaded: false,
|
|
631
|
+
error: null,
|
|
632
|
+
fallback: assetDef.fallback,
|
|
633
|
+
metadata: {
|
|
634
|
+
size: ((_a = assetDef.metadata) == null ? void 0 : _a.size) || 0,
|
|
635
|
+
mimeType: ((_b = assetDef.metadata) == null ? void 0 : _b.mimeType) || "",
|
|
636
|
+
cacheDuration: ((_c = assetDef.metadata) == null ? void 0 : _c.cacheDuration) || 36e5,
|
|
637
|
+
// 1 hour default
|
|
638
|
+
dimensions: (_d = assetDef.metadata) == null ? void 0 : _d.dimensions,
|
|
639
|
+
duration: (_e = assetDef.metadata) == null ? void 0 : _e.duration,
|
|
640
|
+
quality: 1,
|
|
641
|
+
streamable: false,
|
|
642
|
+
priority: "normal"
|
|
643
|
+
},
|
|
644
|
+
progress: 0
|
|
645
|
+
};
|
|
646
|
+
compiledAssets.set(assetDef.id, asset);
|
|
647
|
+
}
|
|
648
|
+
return compiledAssets;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Compiles scene definitions into runtime scenes
|
|
652
|
+
*/
|
|
653
|
+
static compileScenes(scenes, context) {
|
|
654
|
+
const compiledScenes = /* @__PURE__ */ new Map();
|
|
655
|
+
let currentStartTime = 0;
|
|
656
|
+
for (const scene of scenes) {
|
|
657
|
+
const compiledLayers = this.compileLayers(scene.layers, context);
|
|
658
|
+
const compiledAudioTracks = this.compileAudioTracks(scene.audio || [], context);
|
|
659
|
+
const compiledScene = {
|
|
660
|
+
id: scene.id,
|
|
661
|
+
name: scene.name,
|
|
662
|
+
duration: scene.duration,
|
|
663
|
+
layers: compiledLayers,
|
|
664
|
+
audioTracks: compiledAudioTracks,
|
|
665
|
+
startTime: currentStartTime,
|
|
666
|
+
endTime: currentStartTime + scene.duration
|
|
667
|
+
};
|
|
668
|
+
compiledScenes.set(scene.id, compiledScene);
|
|
669
|
+
currentStartTime += scene.duration;
|
|
670
|
+
}
|
|
671
|
+
return compiledScenes;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Compiles layer specifications into runtime layers
|
|
675
|
+
*/
|
|
676
|
+
static compileLayers(layers, context) {
|
|
677
|
+
return layers.sort((a, b) => a.zIndex - b.zIndex).map((layer) => {
|
|
678
|
+
const compiledAnimations = this.compileAnimationTracks(layer.animations || [], context);
|
|
679
|
+
const layerInstance = {
|
|
680
|
+
id: layer.id,
|
|
681
|
+
type: layer.type,
|
|
682
|
+
zIndex: layer.zIndex,
|
|
683
|
+
mount: () => {
|
|
684
|
+
},
|
|
685
|
+
update: () => {
|
|
686
|
+
},
|
|
687
|
+
destroy: () => {
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
const compiledLayer = {
|
|
691
|
+
id: layer.id,
|
|
692
|
+
type: layer.type,
|
|
693
|
+
zIndex: layer.zIndex,
|
|
694
|
+
instance: layerInstance,
|
|
695
|
+
animations: compiledAnimations,
|
|
696
|
+
initialConfig: { ...layer.config },
|
|
697
|
+
active: true
|
|
698
|
+
};
|
|
699
|
+
return compiledLayer;
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Compiles animation tracks into optimized runtime animations
|
|
704
|
+
*/
|
|
705
|
+
static compileAnimationTracks(tracks, _context) {
|
|
706
|
+
return tracks.map((track) => AnimationCompiler.compileTrack(track));
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Compiles audio tracks into runtime audio objects
|
|
710
|
+
*/
|
|
711
|
+
static compileAudioTracks(tracks, _context) {
|
|
712
|
+
return tracks.map((track) => {
|
|
713
|
+
const asset = {
|
|
714
|
+
id: track.id,
|
|
715
|
+
type: "audio",
|
|
716
|
+
src: track.src,
|
|
717
|
+
data: null,
|
|
718
|
+
loaded: false,
|
|
719
|
+
error: null,
|
|
720
|
+
metadata: {
|
|
721
|
+
size: 0,
|
|
722
|
+
mimeType: "audio/mpeg",
|
|
723
|
+
cacheDuration: 36e5,
|
|
724
|
+
priority: "normal"
|
|
725
|
+
},
|
|
726
|
+
progress: 0
|
|
727
|
+
};
|
|
728
|
+
const compiledTrack = {
|
|
729
|
+
id: track.id,
|
|
730
|
+
type: track.type,
|
|
731
|
+
asset,
|
|
732
|
+
startMs: track.startMs,
|
|
733
|
+
endMs: track.endMs || 0,
|
|
734
|
+
// Will be set when audio loads
|
|
735
|
+
volume: track.volume || 1,
|
|
736
|
+
fadeIn: track.fadeIn || 0,
|
|
737
|
+
fadeOut: track.fadeOut || 0,
|
|
738
|
+
loop: track.loop || false
|
|
739
|
+
};
|
|
740
|
+
return compiledTrack;
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Compiles event definitions into runtime events
|
|
745
|
+
*/
|
|
746
|
+
static compileEvents(events, scenes, context) {
|
|
747
|
+
const compiledEvents = /* @__PURE__ */ new Map();
|
|
748
|
+
let currentStartTime = 0;
|
|
749
|
+
for (const event of events) {
|
|
750
|
+
const eventScenes = event.scenes.map((sceneId) => {
|
|
751
|
+
const scene = scenes.get(sceneId);
|
|
752
|
+
if (!scene) {
|
|
753
|
+
throw new Error(`Scene with ID "${sceneId}" not found for event "${event.id}"`);
|
|
754
|
+
}
|
|
755
|
+
return scene;
|
|
756
|
+
});
|
|
757
|
+
const compiledTransitions = this.compileTransitions(event.transitions || [], context);
|
|
758
|
+
const eventDuration = eventScenes.reduce((total, scene) => total + scene.duration, 0);
|
|
759
|
+
const compiledEvent = {
|
|
760
|
+
id: event.id,
|
|
761
|
+
name: event.name,
|
|
762
|
+
scenes: eventScenes,
|
|
763
|
+
transitions: compiledTransitions,
|
|
764
|
+
duration: eventDuration,
|
|
765
|
+
startTime: currentStartTime
|
|
766
|
+
};
|
|
767
|
+
compiledEvents.set(event.id, compiledEvent);
|
|
768
|
+
currentStartTime += eventDuration;
|
|
769
|
+
}
|
|
770
|
+
return compiledEvents;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Compiles transition specifications into runtime transitions
|
|
774
|
+
*/
|
|
775
|
+
static compileTransitions(transitions, _context) {
|
|
776
|
+
return transitions.map((transition) => {
|
|
777
|
+
const easingFunction = AnimationCompiler.compileEasing(transition.easing || "ease");
|
|
778
|
+
const executeFunction = (progress, _fromElement, _toElement) => {
|
|
779
|
+
};
|
|
780
|
+
const compiledTransition = {
|
|
781
|
+
type: transition.type,
|
|
782
|
+
duration: transition.duration,
|
|
783
|
+
easingFunction,
|
|
784
|
+
config: transition.config || {},
|
|
785
|
+
execute: executeFunction
|
|
786
|
+
};
|
|
787
|
+
return compiledTransition;
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Formats Zod validation errors into human-readable messages
|
|
792
|
+
*/
|
|
793
|
+
static formatZodError(error) {
|
|
794
|
+
const messages = error.errors.map((err) => {
|
|
795
|
+
const path = err.path.length > 0 ? `at ${err.path.join(".")}` : "at root";
|
|
796
|
+
return `${path}: ${err.message}`;
|
|
797
|
+
});
|
|
798
|
+
return `Specification validation failed:
|
|
799
|
+
${messages.join("\n")}`;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// src/cli/index.ts
|
|
804
|
+
async function main() {
|
|
805
|
+
const args = process.argv.slice(2);
|
|
806
|
+
if (args.length === 0) {
|
|
807
|
+
showHelp();
|
|
808
|
+
process.exit(0);
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
const options = parseArgs(args);
|
|
812
|
+
await executeCommand(options);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
console.error("Error:", error instanceof Error ? error.message : "Unknown error");
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function parseArgs(args) {
|
|
819
|
+
const options = {};
|
|
820
|
+
for (let i = 0; i < args.length; i++) {
|
|
821
|
+
const arg = args[i];
|
|
822
|
+
switch (arg) {
|
|
823
|
+
case "validate":
|
|
824
|
+
case "preview":
|
|
825
|
+
if (options.command) {
|
|
826
|
+
throw new Error("Multiple commands specified. Use only one command.");
|
|
827
|
+
}
|
|
828
|
+
options.command = arg;
|
|
829
|
+
break;
|
|
830
|
+
case "-f":
|
|
831
|
+
case "--file":
|
|
832
|
+
if (i + 1 >= args.length) {
|
|
833
|
+
throw new Error("--file requires a file path");
|
|
834
|
+
}
|
|
835
|
+
const fileArg = args[i + 1];
|
|
836
|
+
if (!fileArg) {
|
|
837
|
+
throw new Error("--file requires a file path");
|
|
838
|
+
}
|
|
839
|
+
options.file = fileArg;
|
|
840
|
+
i++;
|
|
841
|
+
break;
|
|
842
|
+
case "-v":
|
|
843
|
+
case "--verbose":
|
|
844
|
+
options.verbose = true;
|
|
845
|
+
break;
|
|
846
|
+
case "-o":
|
|
847
|
+
case "--output":
|
|
848
|
+
if (i + 1 >= args.length) {
|
|
849
|
+
throw new Error("--output requires a file path");
|
|
850
|
+
}
|
|
851
|
+
const outputArg = args[i + 1];
|
|
852
|
+
if (!outputArg) {
|
|
853
|
+
throw new Error("--output requires a file path");
|
|
854
|
+
}
|
|
855
|
+
options.output = outputArg;
|
|
856
|
+
i++;
|
|
857
|
+
break;
|
|
858
|
+
case "-h":
|
|
859
|
+
case "--help":
|
|
860
|
+
showHelp();
|
|
861
|
+
process.exit(0);
|
|
862
|
+
break;
|
|
863
|
+
default:
|
|
864
|
+
if (arg && !options.command && !arg.startsWith("-")) {
|
|
865
|
+
throw new Error(`Unknown command: ${arg}. Use 'validate' or 'preview'.`);
|
|
866
|
+
} else if (arg && !options.file && !arg.startsWith("-")) {
|
|
867
|
+
options.file = arg;
|
|
868
|
+
} else if (arg) {
|
|
869
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (!options.command) {
|
|
874
|
+
throw new Error('No command specified. Use "validate" or "preview".');
|
|
875
|
+
}
|
|
876
|
+
if (!options.file) {
|
|
877
|
+
throw new Error("No file specified. Use --file or provide file path.");
|
|
878
|
+
}
|
|
879
|
+
return options;
|
|
880
|
+
}
|
|
881
|
+
async function executeCommand(options) {
|
|
882
|
+
switch (options.command) {
|
|
883
|
+
case "validate":
|
|
884
|
+
await validateCommand(options);
|
|
885
|
+
break;
|
|
886
|
+
case "preview":
|
|
887
|
+
await previewCommand(options);
|
|
888
|
+
break;
|
|
889
|
+
default:
|
|
890
|
+
throw new Error(`Unknown command: ${options.command}`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function validateCommand(options) {
|
|
894
|
+
var _a, _b;
|
|
895
|
+
const filePath = resolve(options.file);
|
|
896
|
+
if (!existsSync(filePath)) {
|
|
897
|
+
throw new Error(`File not found: ${filePath}`);
|
|
898
|
+
}
|
|
899
|
+
if (options.verbose) {
|
|
900
|
+
console.log(`Validating specification: ${filePath}`);
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
904
|
+
let jsonSpec;
|
|
905
|
+
try {
|
|
906
|
+
jsonSpec = JSON.parse(fileContent);
|
|
907
|
+
} catch (parseError) {
|
|
908
|
+
throw new Error(`Invalid JSON format: ${parseError instanceof Error ? parseError.message : "Unknown JSON error"}`);
|
|
909
|
+
}
|
|
910
|
+
const validatedSpec = SpecParser.validate(jsonSpec);
|
|
911
|
+
const compiledSpec = SpecParser.parse(validatedSpec);
|
|
912
|
+
console.log("\u2705 Specification is valid!");
|
|
913
|
+
if (options.verbose) {
|
|
914
|
+
console.log("\nSpecification Details:");
|
|
915
|
+
console.log(` Schema Version: ${validatedSpec.schemaVersion}`);
|
|
916
|
+
console.log(` Events: ${validatedSpec.events.length}`);
|
|
917
|
+
console.log(` Scenes: ${validatedSpec.scenes.length}`);
|
|
918
|
+
console.log(` Assets: ${((_a = validatedSpec.assets) == null ? void 0 : _a.length) || 0}`);
|
|
919
|
+
console.log(` Total Duration: ${compiledSpec.totalDuration}ms`);
|
|
920
|
+
console.log(` Target FPS: ${validatedSpec.engine.targetFps}`);
|
|
921
|
+
console.log(` Quality: ${validatedSpec.engine.quality}`);
|
|
922
|
+
const layerTypes = new Set(validatedSpec.scenes.flatMap((s) => s.layers.map((l) => l.type)));
|
|
923
|
+
console.log(` Layer Types: ${Array.from(layerTypes).join(", ")}`);
|
|
924
|
+
const audioTracks = validatedSpec.scenes.flatMap((s) => s.audio || []);
|
|
925
|
+
if (audioTracks.length > 0) {
|
|
926
|
+
console.log(` Audio Tracks: ${audioTracks.length}`);
|
|
927
|
+
const audioTypes = new Set(audioTracks.map((a) => a.type));
|
|
928
|
+
console.log(` Audio Types: ${Array.from(audioTypes).join(", ")}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (options.output) {
|
|
932
|
+
const report = {
|
|
933
|
+
valid: true,
|
|
934
|
+
file: filePath,
|
|
935
|
+
validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
936
|
+
spec: {
|
|
937
|
+
schemaVersion: validatedSpec.schemaVersion,
|
|
938
|
+
events: validatedSpec.events.length,
|
|
939
|
+
scenes: validatedSpec.scenes.length,
|
|
940
|
+
assets: ((_b = validatedSpec.assets) == null ? void 0 : _b.length) || 0,
|
|
941
|
+
totalDuration: compiledSpec.totalDuration,
|
|
942
|
+
targetFps: validatedSpec.engine.targetFps,
|
|
943
|
+
quality: validatedSpec.engine.quality
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
const { writeFileSync } = await import('fs');
|
|
947
|
+
writeFileSync(options.output, JSON.stringify(report, null, 2));
|
|
948
|
+
console.log(`\u{1F4C4} Validation report saved to: ${options.output}`);
|
|
949
|
+
}
|
|
950
|
+
} catch (error) {
|
|
951
|
+
console.error("\u274C Specification validation failed:");
|
|
952
|
+
console.error(error instanceof Error ? error.message : "Unknown validation error");
|
|
953
|
+
if (options.output) {
|
|
954
|
+
const errorReport = {
|
|
955
|
+
valid: false,
|
|
956
|
+
file: filePath,
|
|
957
|
+
validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
958
|
+
error: error instanceof Error ? error.message : "Unknown validation error"
|
|
959
|
+
};
|
|
960
|
+
const { writeFileSync } = await import('fs');
|
|
961
|
+
writeFileSync(options.output, JSON.stringify(errorReport, null, 2));
|
|
962
|
+
console.log(`\u{1F4C4} Error report saved to: ${options.output}`);
|
|
963
|
+
}
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
async function previewCommand(options) {
|
|
968
|
+
const filePath = resolve(options.file);
|
|
969
|
+
if (!existsSync(filePath)) {
|
|
970
|
+
throw new Error(`File not found: ${filePath}`);
|
|
971
|
+
}
|
|
972
|
+
if (options.verbose) {
|
|
973
|
+
console.log(`Generating preview for: ${filePath}`);
|
|
974
|
+
}
|
|
975
|
+
try {
|
|
976
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
977
|
+
let jsonSpec;
|
|
978
|
+
try {
|
|
979
|
+
jsonSpec = JSON.parse(fileContent);
|
|
980
|
+
} catch (parseError) {
|
|
981
|
+
throw new Error(`Invalid JSON format: ${parseError instanceof Error ? parseError.message : "Unknown JSON error"}`);
|
|
982
|
+
}
|
|
983
|
+
const validatedSpec = SpecParser.validate(jsonSpec);
|
|
984
|
+
const compiledSpec = SpecParser.parse(validatedSpec);
|
|
985
|
+
const previewHtml = generatePreviewHtml(validatedSpec, compiledSpec, filePath);
|
|
986
|
+
if (options.output) {
|
|
987
|
+
const { writeFileSync } = await import('fs');
|
|
988
|
+
writeFileSync(options.output, previewHtml);
|
|
989
|
+
console.log(`\u{1F3AC} Preview saved to: ${options.output}`);
|
|
990
|
+
console.log(` Open in browser to view the cinematic experience.`);
|
|
991
|
+
} else {
|
|
992
|
+
const { tmpdir } = await import('os');
|
|
993
|
+
const { join } = await import('path');
|
|
994
|
+
const tempFile = join(tmpdir(), `cinematic-preview-${Date.now()}.html`);
|
|
995
|
+
const { writeFileSync } = await import('fs');
|
|
996
|
+
writeFileSync(tempFile, previewHtml);
|
|
997
|
+
console.log(`\u{1F3AC} Preview generated: ${tempFile}`);
|
|
998
|
+
console.log(` Open this file in your browser to view the cinematic experience.`);
|
|
999
|
+
try {
|
|
1000
|
+
const { exec } = await import('child_process');
|
|
1001
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1002
|
+
exec(`${command} "${tempFile}"`);
|
|
1003
|
+
console.log(` Opening in default browser...`);
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
console.error("\u274C Preview generation failed:");
|
|
1009
|
+
console.error(error instanceof Error ? error.message : "Unknown preview error");
|
|
1010
|
+
process.exit(1);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function generatePreviewHtml(spec, compiledSpec, filePath) {
|
|
1014
|
+
var _a, _b;
|
|
1015
|
+
const specJson = JSON.stringify(spec, null, 2);
|
|
1016
|
+
return `<!DOCTYPE html>
|
|
1017
|
+
<html lang="en">
|
|
1018
|
+
<head>
|
|
1019
|
+
<meta charset="UTF-8">
|
|
1020
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1021
|
+
<title>Cinematic Preview - ${((_a = spec.events[0]) == null ? void 0 : _a.name) || "Untitled"}</title>
|
|
1022
|
+
<style>
|
|
1023
|
+
body {
|
|
1024
|
+
margin: 0;
|
|
1025
|
+
padding: 20px;
|
|
1026
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1027
|
+
background: #1a1a1a;
|
|
1028
|
+
color: #ffffff;
|
|
1029
|
+
}
|
|
1030
|
+
.container {
|
|
1031
|
+
max-width: 1200px;
|
|
1032
|
+
margin: 0 auto;
|
|
1033
|
+
}
|
|
1034
|
+
.header {
|
|
1035
|
+
text-align: center;
|
|
1036
|
+
margin-bottom: 30px;
|
|
1037
|
+
}
|
|
1038
|
+
.preview-area {
|
|
1039
|
+
background: #000;
|
|
1040
|
+
border-radius: 8px;
|
|
1041
|
+
margin-bottom: 30px;
|
|
1042
|
+
position: relative;
|
|
1043
|
+
aspect-ratio: 16/9;
|
|
1044
|
+
display: flex;
|
|
1045
|
+
align-items: center;
|
|
1046
|
+
justify-content: center;
|
|
1047
|
+
border: 2px solid #333;
|
|
1048
|
+
}
|
|
1049
|
+
.placeholder {
|
|
1050
|
+
text-align: center;
|
|
1051
|
+
color: #666;
|
|
1052
|
+
}
|
|
1053
|
+
.controls {
|
|
1054
|
+
display: flex;
|
|
1055
|
+
justify-content: center;
|
|
1056
|
+
gap: 10px;
|
|
1057
|
+
margin-bottom: 30px;
|
|
1058
|
+
}
|
|
1059
|
+
.btn {
|
|
1060
|
+
padding: 10px 20px;
|
|
1061
|
+
background: #007acc;
|
|
1062
|
+
color: white;
|
|
1063
|
+
border: none;
|
|
1064
|
+
border-radius: 4px;
|
|
1065
|
+
cursor: pointer;
|
|
1066
|
+
font-size: 14px;
|
|
1067
|
+
}
|
|
1068
|
+
.btn:hover {
|
|
1069
|
+
background: #005a9e;
|
|
1070
|
+
}
|
|
1071
|
+
.btn:disabled {
|
|
1072
|
+
background: #333;
|
|
1073
|
+
cursor: not-allowed;
|
|
1074
|
+
}
|
|
1075
|
+
.info-panel {
|
|
1076
|
+
background: #2a2a2a;
|
|
1077
|
+
border-radius: 8px;
|
|
1078
|
+
padding: 20px;
|
|
1079
|
+
margin-bottom: 20px;
|
|
1080
|
+
}
|
|
1081
|
+
.info-grid {
|
|
1082
|
+
display: grid;
|
|
1083
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1084
|
+
gap: 15px;
|
|
1085
|
+
}
|
|
1086
|
+
.info-item {
|
|
1087
|
+
background: #333;
|
|
1088
|
+
padding: 15px;
|
|
1089
|
+
border-radius: 4px;
|
|
1090
|
+
}
|
|
1091
|
+
.info-label {
|
|
1092
|
+
font-size: 12px;
|
|
1093
|
+
color: #999;
|
|
1094
|
+
text-transform: uppercase;
|
|
1095
|
+
margin-bottom: 5px;
|
|
1096
|
+
}
|
|
1097
|
+
.info-value {
|
|
1098
|
+
font-size: 16px;
|
|
1099
|
+
font-weight: 600;
|
|
1100
|
+
}
|
|
1101
|
+
.spec-details {
|
|
1102
|
+
background: #2a2a2a;
|
|
1103
|
+
border-radius: 8px;
|
|
1104
|
+
padding: 20px;
|
|
1105
|
+
}
|
|
1106
|
+
.spec-json {
|
|
1107
|
+
background: #1a1a1a;
|
|
1108
|
+
border-radius: 4px;
|
|
1109
|
+
padding: 15px;
|
|
1110
|
+
overflow-x: auto;
|
|
1111
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
1112
|
+
font-size: 12px;
|
|
1113
|
+
line-height: 1.4;
|
|
1114
|
+
max-height: 400px;
|
|
1115
|
+
overflow-y: auto;
|
|
1116
|
+
}
|
|
1117
|
+
.warning {
|
|
1118
|
+
background: #ff6b35;
|
|
1119
|
+
color: white;
|
|
1120
|
+
padding: 15px;
|
|
1121
|
+
border-radius: 4px;
|
|
1122
|
+
margin-bottom: 20px;
|
|
1123
|
+
}
|
|
1124
|
+
</style>
|
|
1125
|
+
</head>
|
|
1126
|
+
<body>
|
|
1127
|
+
<div class="container">
|
|
1128
|
+
<div class="header">
|
|
1129
|
+
<h1>\u{1F3AC} Cinematic Preview</h1>
|
|
1130
|
+
<p>Specification: <code>${filePath}</code></p>
|
|
1131
|
+
</div>
|
|
1132
|
+
|
|
1133
|
+
<div class="warning">
|
|
1134
|
+
<strong>\u26A0\uFE0F Preview Mode:</strong> This is a static preview of your specification.
|
|
1135
|
+
For full interactive playback, integrate the cinematic-renderer2d library into your application.
|
|
1136
|
+
</div>
|
|
1137
|
+
|
|
1138
|
+
<div class="preview-area">
|
|
1139
|
+
<div class="placeholder">
|
|
1140
|
+
<h2>\u{1F3AD} Cinematic Renderer Preview</h2>
|
|
1141
|
+
<p>Interactive preview would appear here</p>
|
|
1142
|
+
<p>Total Duration: ${compiledSpec.totalDuration}ms</p>
|
|
1143
|
+
</div>
|
|
1144
|
+
</div>
|
|
1145
|
+
|
|
1146
|
+
<div class="controls">
|
|
1147
|
+
<button class="btn" disabled>\u25B6\uFE0F Play</button>
|
|
1148
|
+
<button class="btn" disabled>\u23F8\uFE0F Pause</button>
|
|
1149
|
+
<button class="btn" disabled>\u23F9\uFE0F Stop</button>
|
|
1150
|
+
<button class="btn" disabled>\u23EE\uFE0F Previous</button>
|
|
1151
|
+
<button class="btn" disabled>\u23ED\uFE0F Next</button>
|
|
1152
|
+
</div>
|
|
1153
|
+
|
|
1154
|
+
<div class="info-panel">
|
|
1155
|
+
<h3>Specification Overview</h3>
|
|
1156
|
+
<div class="info-grid">
|
|
1157
|
+
<div class="info-item">
|
|
1158
|
+
<div class="info-label">Schema Version</div>
|
|
1159
|
+
<div class="info-value">${spec.schemaVersion}</div>
|
|
1160
|
+
</div>
|
|
1161
|
+
<div class="info-item">
|
|
1162
|
+
<div class="info-label">Events</div>
|
|
1163
|
+
<div class="info-value">${spec.events.length}</div>
|
|
1164
|
+
</div>
|
|
1165
|
+
<div class="info-item">
|
|
1166
|
+
<div class="info-label">Scenes</div>
|
|
1167
|
+
<div class="info-value">${spec.scenes.length}</div>
|
|
1168
|
+
</div>
|
|
1169
|
+
<div class="info-item">
|
|
1170
|
+
<div class="info-label">Assets</div>
|
|
1171
|
+
<div class="info-value">${((_b = spec.assets) == null ? void 0 : _b.length) || 0}</div>
|
|
1172
|
+
</div>
|
|
1173
|
+
<div class="info-item">
|
|
1174
|
+
<div class="info-label">Target FPS</div>
|
|
1175
|
+
<div class="info-value">${spec.engine.targetFps}</div>
|
|
1176
|
+
</div>
|
|
1177
|
+
<div class="info-item">
|
|
1178
|
+
<div class="info-label">Quality</div>
|
|
1179
|
+
<div class="info-value">${spec.engine.quality}</div>
|
|
1180
|
+
</div>
|
|
1181
|
+
<div class="info-item">
|
|
1182
|
+
<div class="info-label">Total Duration</div>
|
|
1183
|
+
<div class="info-value">${Math.round(compiledSpec.totalDuration / 1e3)}s</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
<div class="info-item">
|
|
1186
|
+
<div class="info-label">Layer Types</div>
|
|
1187
|
+
<div class="info-value">${Array.from(new Set(spec.scenes.flatMap((s) => s.layers.map((l) => l.type)))).join(", ")}</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<div class="spec-details">
|
|
1193
|
+
<h3>Specification Details</h3>
|
|
1194
|
+
<pre class="spec-json">${specJson}</pre>
|
|
1195
|
+
</div>
|
|
1196
|
+
</div>
|
|
1197
|
+
</body>
|
|
1198
|
+
</html>`;
|
|
1199
|
+
}
|
|
1200
|
+
function showHelp() {
|
|
1201
|
+
console.log(`
|
|
1202
|
+
\u{1F3AC} Cinematic Renderer 2D CLI
|
|
1203
|
+
|
|
1204
|
+
USAGE:
|
|
1205
|
+
cinematic-cli <command> [options]
|
|
1206
|
+
|
|
1207
|
+
COMMANDS:
|
|
1208
|
+
validate Validate a JSON specification file
|
|
1209
|
+
preview Generate an HTML preview of the specification
|
|
1210
|
+
|
|
1211
|
+
OPTIONS:
|
|
1212
|
+
-f, --file <path> Specification file to process (required)
|
|
1213
|
+
-v, --verbose Show detailed output
|
|
1214
|
+
-o, --output <path> Output file path (optional)
|
|
1215
|
+
-h, --help Show this help message
|
|
1216
|
+
|
|
1217
|
+
EXAMPLES:
|
|
1218
|
+
# Validate a specification
|
|
1219
|
+
cinematic-cli validate --file my-spec.json
|
|
1220
|
+
|
|
1221
|
+
# Validate with verbose output
|
|
1222
|
+
cinematic-cli validate --file my-spec.json --verbose
|
|
1223
|
+
|
|
1224
|
+
# Generate preview
|
|
1225
|
+
cinematic-cli preview --file my-spec.json
|
|
1226
|
+
|
|
1227
|
+
# Save preview to specific file
|
|
1228
|
+
cinematic-cli preview --file my-spec.json --output preview.html
|
|
1229
|
+
|
|
1230
|
+
# Save validation report
|
|
1231
|
+
cinematic-cli validate --file my-spec.json --output report.json
|
|
1232
|
+
`);
|
|
1233
|
+
}
|
|
1234
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1235
|
+
main().catch((error) => {
|
|
1236
|
+
console.error("Fatal error:", error);
|
|
1237
|
+
process.exit(1);
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export { main as runCLI };
|
|
1242
|
+
//# sourceMappingURL=index.js.map
|
|
1243
|
+
//# sourceMappingURL=index.js.map
|