@depths/waves 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 ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Depths AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,435 @@
1
+ # @depths/waves
2
+
3
+ `@depths/waves` is a TypeScript-first library for rendering videos from a JSON “intermediate representation” (IR).
4
+
5
+ The intended workflow is:
6
+
7
+ 1. An LLM (or a human) produces JSON that conforms to a schema (`VideoIRSchema`).
8
+ 2. The IR is validated (schema + semantics).
9
+ 3. The IR is rendered to an output video file (MP4 by default) using Remotion.
10
+
11
+ This project is designed around explicitness:
12
+
13
+ - You only render components that have been explicitly registered in a component registry.
14
+ - Component props are validated with Zod before rendering.
15
+ - Scene timing is validated for common authoring mistakes (gaps, overlaps, duration mismatches, children exceeding parent bounds).
16
+
17
+ ## What you get (v0.1.0)
18
+
19
+ - A stable IR format (currently `version: "1.0"`) for videos composed of scenes and components.
20
+ - A component registry (`ComponentRegistry`) that maps `type` strings to React components + Zod props schemas + metadata.
21
+ - A validator (`IRValidator`) with schema + semantic checks.
22
+ - A rendering engine (`WavesEngine`) that bundles a Remotion project on the fly and renders to a media file.
23
+ - Built-in primitives: `Scene`, `Text`, `Audio`.
24
+ - Utilities to generate JSON Schemas for LLM prompting from registered component props.
25
+
26
+ ## Why this exists
27
+
28
+ Many “LLM → video” pipelines fail for one of these reasons:
29
+
30
+ - The LLM output is not reliably validated and fails at render time.
31
+ - The rendering system has hidden magic (implicit component inference, brittle conventions).
32
+ - The model lacks a precise schema + component catalog to target.
33
+
34
+ Waves solves this by:
35
+
36
+ - Giving the model a JSON schema to follow.
37
+ - Enforcing strict component registration and props validation.
38
+ - Providing deterministic, testable semantic rules around timing.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm i @depths/waves
44
+ ```
45
+
46
+ Peer dependencies (must be installed by the consumer):
47
+
48
+ ```bash
49
+ npm i react remotion
50
+ ```
51
+
52
+ Rendering prerequisites:
53
+
54
+ - `ffmpeg` must be installed and available on the PATH.
55
+ - A Chromium browser must be available to `@remotion/renderer` (Remotion’s renderer runs headless Chromium).
56
+
57
+ ## CLI / Agent workflow
58
+
59
+ Waves ships a `waves` CLI so a locally running AI agent (or any terminal user) can:
60
+
61
+ 1) fetch a system prompt + JSON Schemas (the authoring contract)
62
+ 2) write a starter IR file
63
+ 3) validate the IR
64
+ 4) render an MP4 from the IR
65
+
66
+ Install locally (recommended):
67
+
68
+ ```bash
69
+ npm i -D @depths/waves
70
+ ```
71
+
72
+ Run the CLI via the local bin:
73
+
74
+ ```bash
75
+ npx waves --help
76
+ ```
77
+
78
+ Or run without installing (one-off):
79
+
80
+ ```bash
81
+ npx -y -p @depths/waves waves --help
82
+ ```
83
+
84
+ ### 1) Get the agent prompt + schemas
85
+
86
+ Machine-readable payload (recommended for agents):
87
+
88
+ ```bash
89
+ npx waves prompt --format json --out ./waves-prompt.json
90
+ ```
91
+
92
+ Human-readable system prompt:
93
+
94
+ ```bash
95
+ npx waves prompt --format text --out ./waves-system-prompt.txt
96
+ ```
97
+
98
+ Schemas only:
99
+
100
+ ```bash
101
+ npx waves schema --kind all --pretty --out ./waves-schemas.json
102
+ ```
103
+
104
+ If you register custom components at module import time, include them with repeatable `--register`:
105
+
106
+ ```bash
107
+ npx waves prompt --format json --register ./src/register-waves-components.ts
108
+ ```
109
+
110
+ ### 2) Write IR JSON
111
+
112
+ ```bash
113
+ npx waves write-ir --template basic --pretty --out ./video.ir.json
114
+ ```
115
+
116
+ ### 3) Validate IR JSON
117
+
118
+ ```bash
119
+ npx waves validate --in ./video.ir.json
120
+ ```
121
+
122
+ Structured validation result:
123
+
124
+ ```bash
125
+ npx waves validate --in ./video.ir.json --format json
126
+ ```
127
+
128
+ ### 4) Render MP4
129
+
130
+ ```bash
131
+ npx waves render --in ./video.ir.json --out ./output.mp4 --codec h264 --crf 28 --concurrency 1
132
+ ```
133
+
134
+ If your IR references `"/assets/..."` paths, pass `--publicDir` and ensure the files exist at `${publicDir}/assets/...`:
135
+
136
+ ```bash
137
+ npx waves render --in ./video.ir.json --out ./output.mp4 --publicDir ./public
138
+ ```
139
+
140
+ ### Exit codes
141
+
142
+ - `0`: success
143
+ - `1`: usage error (invalid flags/command)
144
+ - `2`: validation failure (invalid JSON or IR validation errors)
145
+ - `3`: render failure
146
+ - `4`: I/O error (missing/unreadable/unwritable files)
147
+ - `5`: internal error (bug)
148
+
149
+ ## Quickstart (one function)
150
+
151
+ ```ts
152
+ import { renderVideo } from '@depths/waves';
153
+
154
+ await renderVideo(
155
+ {
156
+ version: '1.0',
157
+ video: { id: 'my-video', width: 1920, height: 1080, fps: 30, durationInFrames: 90 },
158
+ scenes: [
159
+ {
160
+ id: 'scene-1',
161
+ type: 'Scene',
162
+ timing: { from: 0, durationInFrames: 90 },
163
+ props: { background: { type: 'color', value: '#000000' } },
164
+ children: [
165
+ {
166
+ id: 'title',
167
+ type: 'Text',
168
+ timing: { from: 0, durationInFrames: 90 },
169
+ props: { content: 'Hello Waves', fontSize: 72, animation: 'fade' }
170
+ }
171
+ ]
172
+ }
173
+ ]
174
+ },
175
+ { outputPath: './output.mp4' }
176
+ );
177
+ ```
178
+
179
+ ## Core concepts
180
+
181
+ ### 1) IR (Intermediate Representation)
182
+
183
+ The IR is plain JSON. At minimum, it includes:
184
+
185
+ - `version`: currently `"1.0"`
186
+ - `video`: dimensions, fps, total duration
187
+ - `scenes`: an array of `Scene` components
188
+
189
+ All timing is expressed in frames.
190
+
191
+ ### 2) Timing model
192
+
193
+ - Every component has a `timing` object: `{ from, durationInFrames }`
194
+ - Scenes are expected to be sequential:
195
+ - Scene 0 starts at frame 0
196
+ - Scene N starts exactly where Scene N-1 ends
197
+ - Sum of scene durations must equal `video.durationInFrames`
198
+ - Child components (inside a `Scene`) must fit within the scene duration.
199
+
200
+ ### 3) Component registry
201
+
202
+ Waves never renders arbitrary `type` strings. A component `type` must be registered:
203
+
204
+ - `type`: a string identifier, e.g. `"Text"`
205
+ - `component`: a React component to render
206
+ - `propsSchema`: a Zod schema used to validate and coerce defaults
207
+ - `metadata`: descriptions and examples useful for LLM prompting
208
+
209
+ ### 4) Rendering engine
210
+
211
+ Rendering is done through Remotion:
212
+
213
+ 1. Validate IR
214
+ 2. Generate a temporary Remotion entry point
215
+ 3. Bundle using `@remotion/bundler`
216
+ 4. Select the composition using `@remotion/renderer`
217
+ 5. Render the media to `outputPath`
218
+
219
+ Important limitation (v0.1.0):
220
+
221
+ - `WavesEngine` currently requires using `globalRegistry` for rendering, because the generated Remotion bundle needs to register the same components at bundle-time.
222
+
223
+ ## API reference
224
+
225
+ ### `VideoIRSchema`
226
+
227
+ Use this to validate IR generated by an LLM:
228
+
229
+ ```ts
230
+ import { VideoIRSchema } from '@depths/waves';
231
+
232
+ const parsed = VideoIRSchema.safeParse(maybeJson);
233
+ if (!parsed.success) {
234
+ // parsed.error.issues
235
+ }
236
+ ```
237
+
238
+ ### `IRValidator`
239
+
240
+ Runs both schema validation and semantic checks:
241
+
242
+ ```ts
243
+ import { IRValidator } from '@depths/waves';
244
+
245
+ const validator = new IRValidator();
246
+ const result = validator.validate(maybeJson);
247
+ if (!result.success) {
248
+ // result.errors: { path: string[]; message: string; code: string }[]
249
+ }
250
+ ```
251
+
252
+ Semantic checks include:
253
+
254
+ - Scene duration sum matches `video.durationInFrames` (`DURATION_MISMATCH`)
255
+ - Scene `from` frames are sequential (`TIMING_GAP_OR_OVERLAP`)
256
+ - Child components do not exceed their parent duration (`COMPONENT_EXCEEDS_PARENT`)
257
+
258
+ ### `ComponentRegistry` / `globalRegistry`
259
+
260
+ ```ts
261
+ import { ComponentRegistry, globalRegistry } from '@depths/waves';
262
+ ```
263
+
264
+ Typically you will use `globalRegistry` so that the rendering bundle can register and render the same types.
265
+
266
+ ### `registerBuiltInComponents()`
267
+
268
+ Registers built-in primitives (`Scene`, `Text`, `Audio`) into `globalRegistry`.
269
+
270
+ ```ts
271
+ import { registerBuiltInComponents } from '@depths/waves';
272
+
273
+ registerBuiltInComponents();
274
+ ```
275
+
276
+ This function is idempotent: calling it multiple times does not double-register types.
277
+
278
+ ### `renderVideo()`
279
+
280
+ Convenience wrapper:
281
+
282
+ - Registers built-ins
283
+ - Constructs `WavesEngine(globalRegistry, new IRValidator())`
284
+ - Calls `engine.render()`
285
+
286
+ ```ts
287
+ import { renderVideo } from '@depths/waves';
288
+
289
+ await renderVideo(ir, { outputPath: './out.mp4' });
290
+ ```
291
+
292
+ ### `WavesEngine`
293
+
294
+ Lower-level control:
295
+
296
+ ```ts
297
+ import { WavesEngine, globalRegistry, IRValidator, registerBuiltInComponents } from '@depths/waves';
298
+
299
+ registerBuiltInComponents();
300
+ const engine = new WavesEngine(globalRegistry, new IRValidator());
301
+ await engine.render(ir, {
302
+ outputPath: './out.mp4',
303
+ codec: 'h264',
304
+ crf: 18,
305
+ concurrency: 4
306
+ });
307
+ ```
308
+
309
+ Options:
310
+
311
+ - `outputPath` (required): output file path
312
+ - `codec`: one of `'h264' | 'h265' | 'vp8' | 'vp9'` (default: `h264`)
313
+ - `crf`: quality parameter (lower is higher quality)
314
+ - `concurrency`: number or percentage string supported by Remotion renderer
315
+ - `publicDir`: optional directory for Remotion `staticFile()` assets
316
+
317
+ Advanced / internal:
318
+
319
+ - `rootDir`: temp directory root (defaults to `process.cwd()`)
320
+ - `registrationModules`: module specifiers to import into the Remotion bundle before rendering (useful if you register custom components at module import time)
321
+
322
+ ## Asset handling
323
+
324
+ Waves supports two kinds of asset references in the IR:
325
+
326
+ 1. Remote URLs: `https://...` or `http://...`
327
+ 2. Absolute “public” paths: `/assets/foo.png`
328
+
329
+ For absolute paths:
330
+
331
+ - `Scene`/`Audio` resolve them using Remotion’s `staticFile()` which expects a path relative to the `publicDir`.
332
+ - Example: `src: "/assets/a.png"` becomes `staticFile("assets/a.png")`.
333
+
334
+ If you reference `"/assets/..."`, pass `publicDir` to `renderVideo()` / `engine.render()` and make sure the file exists at:
335
+
336
+ ```
337
+ ${publicDir}/assets/...
338
+ ```
339
+
340
+ ## Built-in components (v0.1.0)
341
+
342
+ ### `Scene`
343
+
344
+ Container component for a segment of the video.
345
+
346
+ Props:
347
+
348
+ - `background` (required):
349
+ - `{ type: "color", value: "#RRGGBB" }`
350
+ - `{ type: "image", value: "/assets/bg.png" | "https://..." }`
351
+ - `{ type: "video", value: "/assets/bg.mp4" | "https://..." }`
352
+
353
+ Children:
354
+
355
+ - Any registered components; typically `Text` and `Audio`.
356
+
357
+ ### `Text`
358
+
359
+ Animated text overlay.
360
+
361
+ Props:
362
+
363
+ - `content` (required)
364
+ - `fontSize` (default `48`)
365
+ - `color` (default `#FFFFFF`)
366
+ - `position` (default `"center"`) — `"top" | "center" | "bottom" | "left" | "right"`
367
+ - `animation` (default `"fade"`) — `"none" | "fade" | "slide" | "zoom"`
368
+
369
+ ### `Audio`
370
+
371
+ Audio playback. Supports remote URLs or `staticFile()` assets.
372
+
373
+ Props:
374
+
375
+ - `src` (required)
376
+ - `volume` (default `1`)
377
+ - `startFrom` (default `0`) — trims from the beginning in frames
378
+ - `fadeIn` (default `0`) — fade-in duration in frames
379
+ - `fadeOut` (default `0`) — fade-out duration in frames
380
+
381
+ Notes:
382
+
383
+ - `fadeIn`/`fadeOut` are implemented via a `volume(frame)` curve.
384
+ - The engine passes an internal prop `__wavesDurationInFrames` so `fadeOut` can compute its end.
385
+
386
+ ## LLM integration
387
+
388
+ ### Generating a prompt schema for the model
389
+
390
+ ```ts
391
+ import { globalRegistry, registerBuiltInComponents } from '@depths/waves';
392
+
393
+ registerBuiltInComponents();
394
+ const componentCatalog = globalRegistry.getJSONSchemaForLLM();
395
+ ```
396
+
397
+ This returns an object keyed by component type, including:
398
+
399
+ - A JSON Schema describing the component’s props
400
+ - The component’s metadata (description, examples, etc.)
401
+
402
+ ### Typical prompt rules (recommended)
403
+
404
+ When you prompt a model, include rules like:
405
+
406
+ - All timing is in frames (`fps` defaults to 30)
407
+ - Total scene durations must equal `video.durationInFrames`
408
+ - Scene start frames must be sequential with no gaps/overlaps
409
+ - Asset paths are either full URLs or absolute paths like `/assets/foo.png`
410
+
411
+ ## Troubleshooting
412
+
413
+ ### Rendering fails with browser/Chromium errors
414
+
415
+ Remotion renders using headless Chromium. Ensure a compatible Chromium is available to `@remotion/renderer`.
416
+
417
+ ### Rendering fails with ffmpeg errors
418
+
419
+ Install `ffmpeg` and ensure it is on the PATH.
420
+
421
+ ### Unknown component type
422
+
423
+ If the engine throws “Unknown component type”:
424
+
425
+ - Ensure you called `registerBuiltInComponents()`
426
+ - If it’s a custom component, ensure it is registered into `globalRegistry`
427
+
428
+ ### Props validation errors
429
+
430
+ Props are validated using the registered Zod schema. Defaults are applied by Zod on parse.
431
+
432
+ ## License notes
433
+
434
+ - This package is MIT licensed (see `LICENSE`).
435
+ - Remotion has separate licensing terms; ensure you comply with Remotion’s license for your use case.