@scenerok/sdk 0.2.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/README.md +114 -0
- package/dist/duration.d.ts +29 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +87 -0
- package/dist/grid.d.ts +61 -0
- package/dist/grid.d.ts.map +1 -0
- package/dist/grid.js +174 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/ir-builder.d.ts +28 -0
- package/dist/ir-builder.d.ts.map +1 -0
- package/dist/ir-builder.js +452 -0
- package/dist/ir.d.ts +204 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +30 -0
- package/dist/media.d.ts +16 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +105 -0
- package/dist/motion.d.ts +9 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +31 -0
- package/dist/plugins/cloudflare.d.ts +80 -0
- package/dist/plugins/cloudflare.d.ts.map +1 -0
- package/dist/plugins/cloudflare.js +55 -0
- package/dist/plugins/elevenlabs-music.d.ts +37 -0
- package/dist/plugins/elevenlabs-music.d.ts.map +1 -0
- package/dist/plugins/elevenlabs-music.js +21 -0
- package/dist/plugins/invoke.d.ts +26 -0
- package/dist/plugins/invoke.d.ts.map +1 -0
- package/dist/plugins/invoke.js +109 -0
- package/dist/plugins/types.d.ts +21 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +4 -0
- package/dist/plugins/xai.d.ts +76 -0
- package/dist/plugins/xai.d.ts.map +1 -0
- package/dist/plugins/xai.js +60 -0
- package/dist/stdlib.d.ts +96 -0
- package/dist/stdlib.d.ts.map +1 -0
- package/dist/stdlib.js +66 -0
- package/dist/timeline.d.ts +27 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +117 -0
- package/dist/transitions/catalog.d.ts +33 -0
- package/dist/transitions/catalog.d.ts.map +1 -0
- package/dist/transitions/catalog.js +51 -0
- package/dist/transitions/index.d.ts +62 -0
- package/dist/transitions/index.d.ts.map +1 -0
- package/dist/transitions/index.js +57 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @scenerok/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript-first authoring SDK for SceneRok. Agents use this for **complex, data-driven scenes**. Humans keep **VidScript** for short declarative scripts. Both emit the same `IRTimeline` for preview and render.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @scenerok/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { timeline, video, loadVideo, text, audio, s, fadeIn } from '@scenerok/sdk'
|
|
15
|
+
|
|
16
|
+
export default async function main() {
|
|
17
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
18
|
+
const scene1 = await loadVideo('./hero.mp4')
|
|
19
|
+
|
|
20
|
+
t[0][scene1.duration] = scene1.animate(fadeIn(s(0.4)))
|
|
21
|
+
t['0s']['3s'] = text('Hello', { size: 72 })
|
|
22
|
+
t[scene1.duration][scene1.duration + s(2)] = text('Next')
|
|
23
|
+
t.push(audio('./bed.mp3', { duration: 7, volume: 0.4 }))
|
|
24
|
+
|
|
25
|
+
return t.toIR()
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use `loadVideo(path)` / `loadAudio(path)` when the asset exists and the SceneRok host can probe it. They call the media stdlib to fill `duration` when omitted, then return normal `video(...)` / `audio(...)` values:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const clip = await loadVideo('./hero.mp4')
|
|
33
|
+
t[0][clip.duration] = clip
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The sync `video(path, { duration })` and `audio(path, { duration })` factories remain available when you already know the timing.
|
|
37
|
+
|
|
38
|
+
## Schedule syntax
|
|
39
|
+
|
|
40
|
+
JavaScript has no `t[0, end]` dual key (comma operator). Use:
|
|
41
|
+
|
|
42
|
+
| Form | Meaning |
|
|
43
|
+
|------|---------|
|
|
44
|
+
| `t[start][end] = value` | Dual-index Proxy (preferred) |
|
|
45
|
+
| `t.range(start, end, value)` | Explicit call |
|
|
46
|
+
| `t['-'] = value` / `t.push(value)` | Auto-append at playhead |
|
|
47
|
+
|
|
48
|
+
`start` / `end` accept numbers (seconds), `s(3)` / `ms(300)`, or strings (`'3s'`, `'1:30'`). Full JS arithmetic works: `clip.duration + 0.5`.
|
|
49
|
+
|
|
50
|
+
No `video` / `text` keywords after `=` — the RHS type is inferred from factories.
|
|
51
|
+
|
|
52
|
+
## When to use SDK vs VidScript
|
|
53
|
+
|
|
54
|
+
| Use | Language |
|
|
55
|
+
|-----|----------|
|
|
56
|
+
| Short promos, human edits, templates | VidScript (`.vid`) |
|
|
57
|
+
| Loops, branches, computed timing, agents | TypeScript + `@scenerok/sdk` |
|
|
58
|
+
|
|
59
|
+
## AI plugins
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { timeline, text, s } from '@scenerok/sdk'
|
|
63
|
+
import xai from '@scenerok/xai'
|
|
64
|
+
import eleven from '@elevenlabs/music'
|
|
65
|
+
|
|
66
|
+
export default async function main() {
|
|
67
|
+
const t = timeline({ width: 1080, height: 1920, fps: 30 })
|
|
68
|
+
t.grid({
|
|
69
|
+
rows: '16% 1fr 18% 10%',
|
|
70
|
+
areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
71
|
+
})
|
|
72
|
+
const clip = await xai.genVideo('Cinematic drone shot', { duration: 5, resolution: '720p' })
|
|
73
|
+
const bed = await eleven.music('Warm ambient bed', { duration: 10 })
|
|
74
|
+
t[0][clip.duration] = clip
|
|
75
|
+
t[0][s(3)] = text('Launch', { size: 72, area: 'title' })
|
|
76
|
+
t.push(bed)
|
|
77
|
+
return t.toIR()
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also available: `@scenerok/cloudflare` (`cf.image`, `cf.video`, …). Host secrets and billing match VidScript.
|
|
82
|
+
|
|
83
|
+
## Grid areas
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
t.grid({
|
|
87
|
+
rows: '20% 1fr 30%',
|
|
88
|
+
cols: '1fr 1fr',
|
|
89
|
+
areas: ['title title', 'left right', 'caption caption'],
|
|
90
|
+
})
|
|
91
|
+
t[0]['3s'] = text('Left', { area: 'left' })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Without `t.grid(...)`, `area: 'title' | 'stage' | 'lower-third' | 'caption'` uses the built-in preset.
|
|
95
|
+
|
|
96
|
+
## Transitions
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { timeline, video } from '@scenerok/sdk'
|
|
100
|
+
import tr from '@scenerok/transitions'
|
|
101
|
+
|
|
102
|
+
t[0][5] = video('./a.mp4', { duration: 5 })
|
|
103
|
+
t[5][5.6] = tr.dissolve()
|
|
104
|
+
t[5.6][10] = video('./b.mp4', { duration: 4.4 })
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Same 29 shaders as VidScript. `toIR()` binds neighboring clips automatically.
|
|
108
|
+
|
|
109
|
+
## Validate / render
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
scenerok validate scene.ts
|
|
113
|
+
scenerok render scene.ts --watch
|
|
114
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Duration helpers — seconds as the canonical unit (matches IR). */
|
|
2
|
+
declare const DURATION_BRAND: unique symbol;
|
|
3
|
+
export type TimeInput = number | Duration | string;
|
|
4
|
+
export declare class Duration {
|
|
5
|
+
readonly [DURATION_BRAND]: true;
|
|
6
|
+
/** Length in seconds. */
|
|
7
|
+
readonly seconds: number;
|
|
8
|
+
constructor(seconds: number);
|
|
9
|
+
valueOf(): number;
|
|
10
|
+
toString(): string;
|
|
11
|
+
add(other: TimeInput): Duration;
|
|
12
|
+
sub(other: TimeInput): Duration;
|
|
13
|
+
mul(factor: number): Duration;
|
|
14
|
+
static isDuration(value: unknown): value is Duration;
|
|
15
|
+
}
|
|
16
|
+
/** Seconds helper: `s(3)` → 3s. */
|
|
17
|
+
export declare function s(seconds: number): Duration;
|
|
18
|
+
/** Milliseconds helper: `ms(300)` → 0.3s. */
|
|
19
|
+
export declare function ms(milliseconds: number): Duration;
|
|
20
|
+
/** Frame count at a given fps (default 30). */
|
|
21
|
+
export declare function frames(count: number, fps?: number): Duration;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a time value to seconds.
|
|
24
|
+
* Accepts numbers (already seconds), Duration, or strings like `3s`, `300ms`, `1:30`, `0:01:30`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toSeconds(value: TimeInput): number;
|
|
27
|
+
export declare function isAutoPlayheadKey(key: PropertyKey): boolean;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=duration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAAA,qEAAqE;AAErE,QAAA,MAAM,cAAc,eAAkC,CAAC;AAEvD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnD,qBAAa,QAAQ;IACnB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAG,IAAI,CAAU;IAC1C,yBAAyB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM;IAO3B,OAAO,IAAI,MAAM;IAIjB,QAAQ,IAAI,MAAM;IAIlB,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ;IAI/B,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,QAAQ;IAI/B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;IAI7B,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ;CAOrD;AAED,mCAAmC;AACnC,wBAAgB,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAE3C;AAED,6CAA6C;AAC7C,wBAAgB,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,CAEjD;AAED,+CAA+C;AAC/C,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,QAAQ,CAGxD;AAKD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CA4BlD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAE3D"}
|
package/dist/duration.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Duration helpers — seconds as the canonical unit (matches IR). */
|
|
2
|
+
const DURATION_BRAND = Symbol.for('scenerok.Duration');
|
|
3
|
+
export class Duration {
|
|
4
|
+
[DURATION_BRAND] = true;
|
|
5
|
+
/** Length in seconds. */
|
|
6
|
+
seconds;
|
|
7
|
+
constructor(seconds) {
|
|
8
|
+
if (!Number.isFinite(seconds)) {
|
|
9
|
+
throw new TypeError(`Duration requires a finite number of seconds, got ${seconds}`);
|
|
10
|
+
}
|
|
11
|
+
this.seconds = seconds;
|
|
12
|
+
}
|
|
13
|
+
valueOf() {
|
|
14
|
+
return this.seconds;
|
|
15
|
+
}
|
|
16
|
+
toString() {
|
|
17
|
+
return `${this.seconds}s`;
|
|
18
|
+
}
|
|
19
|
+
add(other) {
|
|
20
|
+
return new Duration(this.seconds + toSeconds(other));
|
|
21
|
+
}
|
|
22
|
+
sub(other) {
|
|
23
|
+
return new Duration(this.seconds - toSeconds(other));
|
|
24
|
+
}
|
|
25
|
+
mul(factor) {
|
|
26
|
+
return new Duration(this.seconds * factor);
|
|
27
|
+
}
|
|
28
|
+
static isDuration(value) {
|
|
29
|
+
return (typeof value === 'object'
|
|
30
|
+
&& value !== null
|
|
31
|
+
&& value[DURATION_BRAND] === true);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Seconds helper: `s(3)` → 3s. */
|
|
35
|
+
export function s(seconds) {
|
|
36
|
+
return new Duration(seconds);
|
|
37
|
+
}
|
|
38
|
+
/** Milliseconds helper: `ms(300)` → 0.3s. */
|
|
39
|
+
export function ms(milliseconds) {
|
|
40
|
+
return new Duration(milliseconds / 1000);
|
|
41
|
+
}
|
|
42
|
+
/** Frame count at a given fps (default 30). */
|
|
43
|
+
export function frames(count, fps = 30) {
|
|
44
|
+
if (fps <= 0)
|
|
45
|
+
throw new TypeError('fps must be positive');
|
|
46
|
+
return new Duration(count / fps);
|
|
47
|
+
}
|
|
48
|
+
const TIME_STRING_RE = /^(?:(?<h>\d+):(?<m>\d{2}):(?<s>\d{2}(?:\.\d+)?))$|^(?:(?<mm>\d+):(?<ss>\d{2}(?:\.\d+)?))$|^(?<num>\d+(?:\.\d+)?)\s*(?<unit>s|sec|secs|seconds|ms|milliseconds)?$/i;
|
|
49
|
+
/**
|
|
50
|
+
* Parse a time value to seconds.
|
|
51
|
+
* Accepts numbers (already seconds), Duration, or strings like `3s`, `300ms`, `1:30`, `0:01:30`.
|
|
52
|
+
*/
|
|
53
|
+
export function toSeconds(value) {
|
|
54
|
+
if (typeof value === 'number') {
|
|
55
|
+
if (!Number.isFinite(value))
|
|
56
|
+
throw new TypeError(`Invalid time number: ${value}`);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
if (Duration.isDuration(value))
|
|
60
|
+
return value.seconds;
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
const trimmed = value.trim();
|
|
63
|
+
if (trimmed === '-' || trimmed === '') {
|
|
64
|
+
throw new TypeError(`Cannot convert auto-playhead marker '${value}' via toSeconds; use timeline.push / t['-']`);
|
|
65
|
+
}
|
|
66
|
+
const match = TIME_STRING_RE.exec(trimmed);
|
|
67
|
+
if (!match || !match.groups) {
|
|
68
|
+
throw new TypeError(`Unrecognized time string: ${JSON.stringify(value)}`);
|
|
69
|
+
}
|
|
70
|
+
const g = match.groups;
|
|
71
|
+
if (g.h !== undefined) {
|
|
72
|
+
return Number(g.h) * 3600 + Number(g.m) * 60 + Number(g.s);
|
|
73
|
+
}
|
|
74
|
+
if (g.mm !== undefined) {
|
|
75
|
+
return Number(g.mm) * 60 + Number(g.ss);
|
|
76
|
+
}
|
|
77
|
+
const num = Number(g.num);
|
|
78
|
+
const unit = (g.unit || 's').toLowerCase();
|
|
79
|
+
if (unit === 'ms' || unit === 'milliseconds')
|
|
80
|
+
return num / 1000;
|
|
81
|
+
return num;
|
|
82
|
+
}
|
|
83
|
+
throw new TypeError(`Unsupported time input: ${String(value)}`);
|
|
84
|
+
}
|
|
85
|
+
export function isAutoPlayheadKey(key) {
|
|
86
|
+
return key === '-' || key === '-';
|
|
87
|
+
}
|
package/dist/grid.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS-Grid-style named regions for SceneRok authoring.
|
|
3
|
+
*
|
|
4
|
+
* Declare a grid once (or rely on the built-in preset) and place text with
|
|
5
|
+
* `area: "title"` instead of guessing coordinates:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* t.grid({
|
|
9
|
+
* rows: '16% 1fr 18% 10%',
|
|
10
|
+
* areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
11
|
+
* })
|
|
12
|
+
* t[0]['3s'] = text('BIG SALE', { area: 'title' })
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Semantics follow CSS Grid: `rows`/`cols` are track lists (`%` and `fr` units),
|
|
16
|
+
* `areas` is grid-template-areas — one string per row, space-separated cell
|
|
17
|
+
* names, `.` for an empty cell, and a name spanning adjacent cells forms a
|
|
18
|
+
* rectangle. Everything resolves to percentages of the canvas so the result is
|
|
19
|
+
* resolution-independent.
|
|
20
|
+
*/
|
|
21
|
+
export interface GridSpec {
|
|
22
|
+
rows?: string;
|
|
23
|
+
cols?: string;
|
|
24
|
+
/** grid-template-areas: one string per row, e.g. ["title title", "left right"] */
|
|
25
|
+
areas: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Cell rectangle in percent units (0–100) of the canvas. */
|
|
28
|
+
export interface GridAreaRect {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
/** Placement params derived from a named grid area (percent strings). */
|
|
35
|
+
export interface GridAreaPlacement {
|
|
36
|
+
x: string;
|
|
37
|
+
y: string;
|
|
38
|
+
maxWidth: string;
|
|
39
|
+
maxHeight: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Built-in preset so `area:` works with no grid declaration. Areas match the
|
|
43
|
+
* common social-video anatomy; proportions differ per orientation.
|
|
44
|
+
*/
|
|
45
|
+
export declare function defaultGridSpec(width: number, height: number): GridSpec;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a grid spec into named area rectangles (percent units).
|
|
48
|
+
* Throws on malformed specs — callers surface the message as a compile error.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveGridAreas(spec: GridSpec): Map<string, GridAreaRect>;
|
|
51
|
+
/** Convert a grid area rect into centered text placement params (percent strings). */
|
|
52
|
+
export declare function placementFromGridArea(rect: GridAreaRect): GridAreaPlacement;
|
|
53
|
+
/**
|
|
54
|
+
* Look up a named area and return placement params.
|
|
55
|
+
* Uses the built-in social-video preset when `areas` is omitted.
|
|
56
|
+
*/
|
|
57
|
+
export declare function placeInArea(name: string, areasOrSpec?: Map<string, GridAreaRect> | GridSpec, canvas?: {
|
|
58
|
+
width?: number;
|
|
59
|
+
height?: number;
|
|
60
|
+
}): GridAreaPlacement;
|
|
61
|
+
//# sourceMappingURL=grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../src/grid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAavE;AAgDD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CA6D1E;AAMD,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAO3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,QAAQ,EAClD,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/C,iBAAiB,CAgBnB"}
|
package/dist/grid.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS-Grid-style named regions for SceneRok authoring.
|
|
3
|
+
*
|
|
4
|
+
* Declare a grid once (or rely on the built-in preset) and place text with
|
|
5
|
+
* `area: "title"` instead of guessing coordinates:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* t.grid({
|
|
9
|
+
* rows: '16% 1fr 18% 10%',
|
|
10
|
+
* areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
11
|
+
* })
|
|
12
|
+
* t[0]['3s'] = text('BIG SALE', { area: 'title' })
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Semantics follow CSS Grid: `rows`/`cols` are track lists (`%` and `fr` units),
|
|
16
|
+
* `areas` is grid-template-areas — one string per row, space-separated cell
|
|
17
|
+
* names, `.` for an empty cell, and a name spanning adjacent cells forms a
|
|
18
|
+
* rectangle. Everything resolves to percentages of the canvas so the result is
|
|
19
|
+
* resolution-independent.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Built-in preset so `area:` works with no grid declaration. Areas match the
|
|
23
|
+
* common social-video anatomy; proportions differ per orientation.
|
|
24
|
+
*/
|
|
25
|
+
export function defaultGridSpec(width, height) {
|
|
26
|
+
if (width > height) {
|
|
27
|
+
// Landscape: wider lower-third band, shallower title.
|
|
28
|
+
return {
|
|
29
|
+
rows: '14% 1fr 20% 12%',
|
|
30
|
+
areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Portrait / square: leave headroom for platform UI chrome.
|
|
34
|
+
return {
|
|
35
|
+
rows: '16% 1fr 18% 10%',
|
|
36
|
+
areas: ['title', 'stage', 'lower-third', 'caption'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseTrackList(list, count, label) {
|
|
40
|
+
const source = (list ?? '').trim();
|
|
41
|
+
if (!source) {
|
|
42
|
+
return Array.from({ length: count }, () => ({ kind: 'fr', value: 1 }));
|
|
43
|
+
}
|
|
44
|
+
const tokens = source.split(/\s+/);
|
|
45
|
+
const tracks = tokens.map((token) => {
|
|
46
|
+
if (token.endsWith('%')) {
|
|
47
|
+
const value = Number(token.slice(0, -1));
|
|
48
|
+
if (!Number.isFinite(value) || value < 0)
|
|
49
|
+
throw new Error(`Invalid ${label} track '${token}'`);
|
|
50
|
+
return { kind: 'percent', value };
|
|
51
|
+
}
|
|
52
|
+
if (token.endsWith('fr')) {
|
|
53
|
+
const value = Number(token.slice(0, -2));
|
|
54
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
55
|
+
throw new Error(`Invalid ${label} track '${token}'`);
|
|
56
|
+
return { kind: 'fr', value };
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Invalid ${label} track '${token}' — use '%' or 'fr' units `
|
|
59
|
+
+ '(px is not supported because grids resolve before the output resolution is known)');
|
|
60
|
+
});
|
|
61
|
+
if (tracks.length !== count) {
|
|
62
|
+
throw new Error(`grid ${label} defines ${tracks.length} track(s) but areas has ${count}`);
|
|
63
|
+
}
|
|
64
|
+
return tracks;
|
|
65
|
+
}
|
|
66
|
+
/** Resolve track sizes to percent-of-container (they always total 100). */
|
|
67
|
+
function resolveTracks(tracks) {
|
|
68
|
+
const fixed = tracks.reduce((sum, t) => sum + (t.kind === 'percent' ? t.value : 0), 0);
|
|
69
|
+
if (fixed > 100.0001) {
|
|
70
|
+
throw new Error(`grid percent tracks total ${fixed}% (must be <= 100%)`);
|
|
71
|
+
}
|
|
72
|
+
const frTotal = tracks.reduce((sum, t) => sum + (t.kind === 'fr' ? t.value : 0), 0);
|
|
73
|
+
const remaining = Math.max(0, 100 - fixed);
|
|
74
|
+
return tracks.map((t) => t.kind === 'percent' ? t.value : frTotal > 0 ? (remaining * t.value) / frTotal : 0);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a grid spec into named area rectangles (percent units).
|
|
78
|
+
* Throws on malformed specs — callers surface the message as a compile error.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveGridAreas(spec) {
|
|
81
|
+
if (!Array.isArray(spec.areas) || spec.areas.length === 0) {
|
|
82
|
+
throw new Error('grid requires areas: an array of row strings');
|
|
83
|
+
}
|
|
84
|
+
const cellRows = spec.areas.map((row) => String(row).trim().split(/\s+/));
|
|
85
|
+
const colCount = Math.max(...cellRows.map((row) => row.length));
|
|
86
|
+
for (const row of cellRows) {
|
|
87
|
+
if (row.length !== colCount) {
|
|
88
|
+
throw new Error('grid areas rows must all have the same number of cells');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const rowSizes = resolveTracks(parseTrackList(spec.rows, cellRows.length, 'rows'));
|
|
92
|
+
const colSizes = resolveTracks(parseTrackList(spec.cols, colCount, 'cols'));
|
|
93
|
+
const rowOffsets = [0];
|
|
94
|
+
for (const size of rowSizes)
|
|
95
|
+
rowOffsets.push(rowOffsets[rowOffsets.length - 1] + size);
|
|
96
|
+
const colOffsets = [0];
|
|
97
|
+
for (const size of colSizes)
|
|
98
|
+
colOffsets.push(colOffsets[colOffsets.length - 1] + size);
|
|
99
|
+
const bounds = new Map();
|
|
100
|
+
for (let r = 0; r < cellRows.length; r++) {
|
|
101
|
+
for (let c = 0; c < colCount; c++) {
|
|
102
|
+
const name = cellRows[r][c];
|
|
103
|
+
if (name === '.')
|
|
104
|
+
continue;
|
|
105
|
+
const b = bounds.get(name);
|
|
106
|
+
if (!b) {
|
|
107
|
+
bounds.set(name, { r0: r, r1: r, c0: c, c1: c });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
b.r0 = Math.min(b.r0, r);
|
|
111
|
+
b.r1 = Math.max(b.r1, r);
|
|
112
|
+
b.c0 = Math.min(b.c0, c);
|
|
113
|
+
b.c1 = Math.max(b.c1, c);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// CSS requires named areas to be rectangular; enforce it so a typo like
|
|
118
|
+
// ["title stage", "stage title"] fails loudly instead of producing a
|
|
119
|
+
// surprising bounding box.
|
|
120
|
+
for (const [name, b] of bounds) {
|
|
121
|
+
for (let r = b.r0; r <= b.r1; r++) {
|
|
122
|
+
for (let c = b.c0; c <= b.c1; c++) {
|
|
123
|
+
if (cellRows[r][c] !== name) {
|
|
124
|
+
throw new Error(`grid area '${name}' is not rectangular`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const rects = new Map();
|
|
130
|
+
for (const [name, b] of bounds) {
|
|
131
|
+
rects.set(name, {
|
|
132
|
+
x: colOffsets[b.c0],
|
|
133
|
+
y: rowOffsets[b.r0],
|
|
134
|
+
width: colOffsets[b.c1 + 1] - colOffsets[b.c0],
|
|
135
|
+
height: rowOffsets[b.r1 + 1] - rowOffsets[b.r0],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return rects;
|
|
139
|
+
}
|
|
140
|
+
function roundPercent(value) {
|
|
141
|
+
return Math.round(value * 100) / 100;
|
|
142
|
+
}
|
|
143
|
+
/** Convert a grid area rect into centered text placement params (percent strings). */
|
|
144
|
+
export function placementFromGridArea(rect) {
|
|
145
|
+
return {
|
|
146
|
+
x: `${roundPercent(rect.x + rect.width / 2)}%`,
|
|
147
|
+
y: `${roundPercent(rect.y + rect.height / 2)}%`,
|
|
148
|
+
maxWidth: `${roundPercent(rect.width)}%`,
|
|
149
|
+
maxHeight: `${roundPercent(rect.height)}%`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Look up a named area and return placement params.
|
|
154
|
+
* Uses the built-in social-video preset when `areas` is omitted.
|
|
155
|
+
*/
|
|
156
|
+
export function placeInArea(name, areasOrSpec, canvas = {}) {
|
|
157
|
+
const width = canvas.width ?? 1080;
|
|
158
|
+
const height = canvas.height ?? 1920;
|
|
159
|
+
let areas;
|
|
160
|
+
if (!areasOrSpec) {
|
|
161
|
+
areas = resolveGridAreas(defaultGridSpec(width, height));
|
|
162
|
+
}
|
|
163
|
+
else if (areasOrSpec instanceof Map) {
|
|
164
|
+
areas = areasOrSpec;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
areas = resolveGridAreas(areasOrSpec);
|
|
168
|
+
}
|
|
169
|
+
const rect = areas.get(name);
|
|
170
|
+
if (!rect) {
|
|
171
|
+
throw new Error(`Unknown grid area '${name}' (available: ${[...areas.keys()].join(', ')})`);
|
|
172
|
+
}
|
|
173
|
+
return placementFromGridArea(rect);
|
|
174
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @scenerok/sdk — TypeScript-first authoring for SceneRok.
|
|
3
|
+
*
|
|
4
|
+
* Agents use this for complex, data-driven scenes. Humans keep VidScript for
|
|
5
|
+
* short declarative scripts. Both emit the same IRTimeline for Pass 2.
|
|
6
|
+
*/
|
|
7
|
+
export type { IRTimeline, IRClip, IRLayer, IRTextLayer, IRSurface, IREffect, IRAudioTrack, IROutput, IRAnimation, IRMotionTrack, } from './ir.js';
|
|
8
|
+
export { defaultIRTimeline, defaultIROutput } from './ir.js';
|
|
9
|
+
export { Duration, s, ms, frames, toSeconds, type TimeInput, } from './duration.js';
|
|
10
|
+
export type { VideoClip, VideoOptions, TextLayer, TextOptions, AudioTrack, AudioOptions, FilterEffect, ShaderEffect, TransitionEffect, TransitionOptions, AnimationDescriptor, Schedulable, TimelineOptions, TimelineLike, SdkHost, } from './types.js';
|
|
11
|
+
export { isSdkNode, isSchedulable } from './types.js';
|
|
12
|
+
export { video, loadVideo, text, audio, loadAudio, filter, shader } from './media.js';
|
|
13
|
+
export { fadeIn, fadeOut, slideY, slideX, scale, popIn } from './motion.js';
|
|
14
|
+
export { timeline, isTimeline, type Timeline } from './timeline.js';
|
|
15
|
+
export { createIrBuilder, scheduleAt, resetIrBuilderIds, inferAutoDuration, assertSchedulable, bindTransitions, setGrid, type IrBuilderState, } from './ir-builder.js';
|
|
16
|
+
export { defaultGridSpec, resolveGridAreas, placementFromGridArea, placeInArea, type GridSpec, type GridAreaRect, type GridAreaPlacement, } from './grid.js';
|
|
17
|
+
export { getVideoLength, getAudioLength, getMediaInfo, getDimensions, getFps, getLoudness, hasAudio, mediaKind, grabFrame, grabFrames, grabFrameAtFraction, measureText, fitContain, fitCover, clampTime, parseTime, timecode, beats, type MediaSource, type MediaInfo, type Dimensions, type LoudnessInfo, type FitBox, type GrabFrameOptions, type MeasureTextStyle, type TextMetrics, type StdlibMediaKind, type StdlibHostApi, } from './stdlib.js';
|
|
18
|
+
export { default as xai } from './plugins/xai.js';
|
|
19
|
+
export type { XaiPlugin, XaiVideoOptions, XaiImageOptions, XaiTtsOptions, XaiVoiceInfo, XaiVoiceList, XaiImageRef, XaiVideoResolution, XaiImageResolution, XaiVoiceId, XaiTtsCodec, } from './plugins/xai.js';
|
|
20
|
+
export { default as cloudflare } from './plugins/cloudflare.js';
|
|
21
|
+
export type { CloudflarePlugin, CloudflareVideoOptions, CloudflareImageOptions, CloudflareModelCatalog, CloudflareVideoModelInfo, CloudflareImageModelInfo, CloudflareVideoModel, CloudflareImageModel, } from './plugins/cloudflare.js';
|
|
22
|
+
export { default as elevenlabsMusic } from './plugins/elevenlabs-music.js';
|
|
23
|
+
export type { ElevenLabsMusicPlugin, ElevenLabsMusicOptions, ElevenLabsCompositionPlan, ElevenLabsMusicModel, ElevenLabsOutputFormat, } from './plugins/elevenlabs-music.js';
|
|
24
|
+
export type { AspectRatio, MediaUrlRef, StringEnum, } from './plugins/types.js';
|
|
25
|
+
export { default as transitions, transition } from './transitions/index.js';
|
|
26
|
+
export type { TransitionsPlugin, TransitionCatalogEntry, TransitionFnName, } from './transitions/index.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,UAAU,EACV,MAAM,EACN,OAAO,EACP,WAAW,EACX,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,WAAW,EACX,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE7D,OAAO,EACL,QAAQ,EACR,CAAC,EACD,EAAE,EACF,MAAM,EACN,SAAS,EACT,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,SAAS,EACT,YAAY,EACZ,SAAS,EACT,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,YAAY,EACZ,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EACL,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,OAAO,EACP,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,qBAAqB,EACrB,WAAW,EACX,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,cAAc,EACd,cAAc,EACd,YAAY,EACZ,aAAa,EACb,MAAM,EACN,WAAW,EACX,QAAQ,EACR,SAAS,EACT,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,KAAK,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EACV,SAAS,EACT,eAAe,EACf,eAAe,EACf,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAC3E,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,yBAAyB,EACzB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,GACX,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC5E,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @scenerok/sdk — TypeScript-first authoring for SceneRok.
|
|
3
|
+
*
|
|
4
|
+
* Agents use this for complex, data-driven scenes. Humans keep VidScript for
|
|
5
|
+
* short declarative scripts. Both emit the same IRTimeline for Pass 2.
|
|
6
|
+
*/
|
|
7
|
+
export { defaultIRTimeline, defaultIROutput } from './ir.js';
|
|
8
|
+
export { Duration, s, ms, frames, toSeconds, } from './duration.js';
|
|
9
|
+
export { isSdkNode, isSchedulable } from './types.js';
|
|
10
|
+
export { video, loadVideo, text, audio, loadAudio, filter, shader } from './media.js';
|
|
11
|
+
export { fadeIn, fadeOut, slideY, slideX, scale, popIn } from './motion.js';
|
|
12
|
+
export { timeline, isTimeline } from './timeline.js';
|
|
13
|
+
export { createIrBuilder, scheduleAt, resetIrBuilderIds, inferAutoDuration, assertSchedulable, bindTransitions, setGrid, } from './ir-builder.js';
|
|
14
|
+
export { defaultGridSpec, resolveGridAreas, placementFromGridArea, placeInArea, } from './grid.js';
|
|
15
|
+
// Media stdlib — host-injected at compile time (`@scenerok/stdlib` alias)
|
|
16
|
+
export { getVideoLength, getAudioLength, getMediaInfo, getDimensions, getFps, getLoudness, hasAudio, mediaKind, grabFrame, grabFrames, grabFrameAtFraction, measureText, fitContain, fitCover, clampTime, parseTime, timecode, beats, } from './stdlib.js';
|
|
17
|
+
// AI plugins — prefer package imports (`@scenerok/xai`, …); also re-exported for convenience
|
|
18
|
+
export { default as xai } from './plugins/xai.js';
|
|
19
|
+
export { default as cloudflare } from './plugins/cloudflare.js';
|
|
20
|
+
export { default as elevenlabsMusic } from './plugins/elevenlabs-music.js';
|
|
21
|
+
// Transitions — prefer `import tr from '@scenerok/transitions'`; also re-exported
|
|
22
|
+
export { default as transitions, transition } from './transitions/index.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared IR builder — appends SDK nodes onto an IRTimeline.
|
|
3
|
+
* Used by @scenerok/sdk Timeline and (later) optionally by the VidScript compiler.
|
|
4
|
+
*/
|
|
5
|
+
import { type IRTimeline } from './ir.js';
|
|
6
|
+
import { type TimeInput } from './duration.js';
|
|
7
|
+
import { type GridAreaRect, type GridSpec } from './grid.js';
|
|
8
|
+
import type { Schedulable, TimelineOptions } from './types.js';
|
|
9
|
+
export declare function resetIrBuilderIds(seed?: number): void;
|
|
10
|
+
export interface IrBuilderState {
|
|
11
|
+
ir: IRTimeline;
|
|
12
|
+
visualCursor: number;
|
|
13
|
+
audioCursor: number;
|
|
14
|
+
/** Named area rects (percent) from `timeline.grid(...)`; null until declared. */
|
|
15
|
+
gridAreas: Map<string, GridAreaRect> | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function createIrBuilder(options?: TimelineOptions): IrBuilderState;
|
|
18
|
+
/** Declare or replace the timeline grid used by `area:` text placement. */
|
|
19
|
+
export declare function setGrid(state: IrBuilderState, spec: GridSpec): Map<string, GridAreaRect>;
|
|
20
|
+
/**
|
|
21
|
+
* Post-pass: resolve fromId/toId for transition surfaces from neighboring clips.
|
|
22
|
+
* Mirrors VidScript `bindTransitions` in compiler-v2.
|
|
23
|
+
*/
|
|
24
|
+
export declare function bindTransitions(ir: IRTimeline, warnings?: string[]): void;
|
|
25
|
+
export declare function scheduleAt(state: IrBuilderState, start: number, end: number, value: Schedulable): void;
|
|
26
|
+
export declare function inferAutoDuration(value: Schedulable, explicit?: TimeInput): number;
|
|
27
|
+
export declare function assertSchedulable(value: unknown): asserts value is Schedulable;
|
|
28
|
+
//# sourceMappingURL=ir-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ir-builder.d.ts","sourceRoot":"","sources":["../src/ir-builder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAWL,KAAK,UAAU,EAEhB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAIV,WAAW,EAIX,eAAe,EAGhB,MAAM,YAAY,CAAC;AAKpB,wBAAgB,iBAAiB,CAAC,IAAI,SAAI,GAAG,IAAI,CAEhD;AA0GD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,UAAU,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAqB7E;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAIxF;AA2LD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,UAAU,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,IAAI,CAiD7E;AAmED,wBAAgB,UAAU,CACxB,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,GACjB,IAAI,CA6BN;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,CAkBlF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAM9E"}
|