pixi-effects 0.1.0 → 0.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +117 -7
  3. package/ai/SKILL.md +44 -0
  4. package/ai/reference/cheatsheet.md +120 -0
  5. package/ai/reference/pitfalls.md +54 -0
  6. package/ai/reference/recipes.md +375 -0
  7. package/ai/template.html +82 -0
  8. package/ai/tools/save-image.py +24 -0
  9. package/dist/Base-BDw6dKRB.d.cts +92 -0
  10. package/dist/Base-Ba4Ta4ap.d.ts +92 -0
  11. package/dist/Composition-6FDH5OPM.cjs +13 -0
  12. package/dist/Composition-6FDH5OPM.cjs.map +1 -0
  13. package/dist/Composition-FEYAUMFI.js +4 -0
  14. package/dist/Composition-FEYAUMFI.js.map +1 -0
  15. package/dist/Controller.d.cts +2 -1
  16. package/dist/Controller.d.ts +2 -1
  17. package/dist/Movie-DMpaT67V.d.ts +179 -0
  18. package/dist/Movie-W8ISiEBB.d.cts +179 -0
  19. package/dist/chunk-DIJG2RSF.js +370 -0
  20. package/dist/chunk-DIJG2RSF.js.map +1 -0
  21. package/dist/chunk-PN5A6QA7.js +2031 -0
  22. package/dist/chunk-PN5A6QA7.js.map +1 -0
  23. package/dist/chunk-SIRVULFF.cjs +2049 -0
  24. package/dist/chunk-SIRVULFF.cjs.map +1 -0
  25. package/dist/chunk-ZL262ZRM.cjs +372 -0
  26. package/dist/chunk-ZL262ZRM.cjs.map +1 -0
  27. package/dist/index.cjs +371 -1798
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +95 -24
  30. package/dist/index.d.ts +95 -24
  31. package/dist/index.js +364 -1796
  32. package/dist/index.js.map +1 -1
  33. package/dist/three.cjs +161 -0
  34. package/dist/three.cjs.map +1 -0
  35. package/dist/three.d.cts +79 -0
  36. package/dist/three.d.ts +79 -0
  37. package/dist/three.js +157 -0
  38. package/dist/three.js.map +1 -0
  39. package/dist/{Movie-Bp66rkaC.d.cts → types-3c8Vymgw.d.cts} +84 -81
  40. package/dist/{Movie-Bp66rkaC.d.ts → types-3c8Vymgw.d.ts} +84 -81
  41. package/docs/api.md +333 -0
  42. package/docs/dsl.md +1017 -0
  43. package/llms-full.txt +1966 -0
  44. package/llms.txt +30 -0
  45. package/package.json +19 -7
package/docs/api.md ADDED
@@ -0,0 +1,333 @@
1
+ # API Reference
2
+
3
+ - [`Movie`](#movie) — composition runtime: init, playback, render
4
+ - [`Controller`](#controller) — drop-in player UI overlay
5
+ - [Helpers](#helpers) — pure utilities exported from `pixi-effects/controller`
6
+ - [`pixi-effects/three`](#pixi-effectsthree) — optional three.js integration
7
+
8
+ For DSL types (composition spec, sequences, filters, keyframes, expressions), see [DSL reference](./dsl.md).
9
+
10
+ ---
11
+
12
+ ## `Movie`
13
+
14
+ Imported from `pixi-effects`.
15
+
16
+ ```ts
17
+ import { Movie } from 'pixi-effects';
18
+
19
+ const movie = new Movie();
20
+ await movie.init({ /* ... */ });
21
+ movie.play();
22
+ ```
23
+
24
+ ### Constructor
25
+
26
+ ```ts
27
+ new Movie()
28
+ ```
29
+
30
+ No arguments. State is fully populated by `init()`.
31
+
32
+ ### `movie.init(options): Promise<void>`
33
+
34
+ Loads assets, builds the composition tree, mixes audio, and renders frame 0.
35
+
36
+ ```ts
37
+ interface MovieOptions {
38
+ width?: number; // canvas pixels (default 1920)
39
+ height?: number; // canvas pixels (default 1080)
40
+ duration?: number; // seconds (default 10)
41
+ frameRate?: number; // fps (default 30)
42
+ background?: string; // CSS color hex (default '#000000')
43
+ canvas?: HTMLCanvasElement; // existing canvas to render into; otherwise PixiJS creates one
44
+ assets?: AssetSpec[]; // [{ name, src }]
45
+ composition?: CompositionSpec; // root composition (see DSL reference)
46
+ }
47
+ ```
48
+
49
+ Resolves once the composition is ready and the first frame has been rendered. Emits the `ready` event.
50
+
51
+ ### `movie.play(): void`
52
+
53
+ Starts the requestAnimationFrame loop that drives `gotoFrame()` per tick. If `currentFrame >= totalFrames`, restarts from 0.
54
+
55
+ If audio sources exist, schedules them on the AudioContext at the appropriate offsets.
56
+
57
+ ### `movie.pause(): void`
58
+
59
+ Stops the rAF loop and any playing audio. Emits `'pause'` only when the previous state was playing (so calling `pause()` on an already-paused movie is a no-op for listeners).
60
+
61
+ ### `movie.gotoFrame(frame, force?): Promise<void>`
62
+
63
+ ```ts
64
+ gotoFrame(frame: number, force?: boolean): Promise<void>
65
+ ```
66
+
67
+ Seeks to a specific frame. Pauses if currently playing? **No** — does not change `isPlaying`. Updates `timeline.time()`, awaits any video frame readiness, renders, and emits `'frame'`.
68
+
69
+ `force=true` skips the early-return when the requested frame equals `currentFrame`. Use it after a composition rebuild.
70
+
71
+ ### `movie.snapshot(frame?, options?): Promise<Blob | string>`
72
+
73
+ ```ts
74
+ snapshot(frame?: number, options?: { scale?: number; type?: 'image/png' | 'image/jpeg'; as?: 'blob' | 'dataURL' }): Promise<Blob | string>
75
+ ```
76
+
77
+ A picture of one frame: **the canvas only** (the player bar is not in it). Seeks to `frame` (default: the current frame) and stays there. `as: 'dataURL'` returns a `data:` URL string, handy when a script can only return text. Use it to look at what you built.
78
+
79
+ ### `movie.contactSheet(options?): Promise<Blob | string>`
80
+
81
+ ```ts
82
+ contactSheet(options?: {
83
+ frames?: number[]; times?: number[]; count?: number; // which frames: explicit, in seconds, or `count` evenly spread (default 6)
84
+ columns?: number; // default 3
85
+ cellWidth?: number; // picture width in px, default 480
86
+ as?: 'blob' | 'dataURL';
87
+ }): Promise<Blob | string>
88
+ ```
89
+
90
+ Many frames on **one** PNG, each labelled `frame N · T s`. The cheapest way to check a whole animation by eye (include the middle of every transition and the last second). Restores the current frame afterwards.
91
+
92
+ ### `movie.inspect(frame?, options?): Promise<InspectReport>`
93
+
94
+ Where every layer is drawn at `frame`, as JSON — for checking layout without eyes. Seeks there and stays there. `options.layers`: `'visible'` (default, only layers drawn at this frame), `'all'`, or `'none'` (just `summary` and `issues`). Faint layers (alpha < 0.3) and text whose x / y is animated (tickers) are not reported.
95
+
96
+ ```ts
97
+ interface InspectReport {
98
+ frame: number; time: number; canvas: { width: number; height: number };
99
+ summary: { layers: number; visible: number };
100
+ issues: string[]; // text off the canvas / cut by an edge / empty / overlapping other text — read this first
101
+ layers: Array<{
102
+ path: string; // names (or type#index) from the root, joined by '/'
103
+ name?: string; type: string; threeD: boolean;
104
+ visible: boolean; // alive at this frame, not hidden by an ancestor, alpha > 0
105
+ alpha: number;
106
+ bounds: { x: number; y: number; width: number; height: number } | null; // canvas pixels; null inside a threeD layer
107
+ onCanvas: 'full' | 'partial' | 'none' | null;
108
+ }>;
109
+ }
110
+ ```
111
+
112
+ ### `movie.render(options?): Promise<Blob>`
113
+
114
+ Renders the entire timeline to a single video file. Pauses playback first.
115
+
116
+ ```ts
117
+ interface RenderOptions {
118
+ format?: 'mp4' | 'mov' | 'webm' | 'mkv'; // default 'mp4'
119
+ video?: {
120
+ codec?: string; // default per format (mp4/mov→avc, webm/mkv→vp9)
121
+ bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high'; // default 'high'
122
+ };
123
+ audio?: {
124
+ codec?: string; // default per format (mp4/mov→aac, webm/mkv→opus)
125
+ bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high'; // default 'high'
126
+ };
127
+ }
128
+ ```
129
+
130
+ Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
131
+
132
+ The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
133
+
134
+ ### `movie.destroy(): Promise<void>`
135
+
136
+ Pauses playback, destroys the underlying PIXI Application, releases audio buffers and AudioContext, and marks the instance unusable.
137
+
138
+ ### Events
139
+
140
+ ```ts
141
+ movie.on(event, fn): this
142
+ movie.off(event, fn): this
143
+ ```
144
+
145
+ | Event | Payload | Fired |
146
+ | ----------- | ------------------------------------------------- | ----------------------------------------------------- |
147
+ | `'ready'` | none | once, after `init()` resolves |
148
+ | `'frame'` | `{ frame: number; totalFrames: number }` | every `gotoFrame` (so once per playback frame too) |
149
+ | `'pause'` | none | when `pause()` actually transitions from playing |
150
+ | `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
151
+
152
+ `progress` is `0..100` (rounded integer percent).
153
+
154
+ ### Public properties
155
+
156
+ | Property | Type | Notes |
157
+ | ---------------- | --------- | ------------------------------------------------------- |
158
+ | `isPlaying` | boolean | true while the rAF loop is active |
159
+ | `currentFrame` | number | 0-based current frame |
160
+ | `totalFrames` | number | `Math.round(duration * frameRate)` |
161
+ | `frameRate` | number | from `init` |
162
+ | `duration` | number | seconds |
163
+ | `width`, `height`| number | canvas pixels |
164
+ | `background` | string | CSS color |
165
+ | `volume` | number | 0..1 getter/setter; immediate. Setter clamps and applies to active audio |
166
+ | `muted` | boolean | getter/setter; immediate |
167
+ | `app` | `pixi.js Application \| null` | PIXI Application instance (advanced/escape hatch) |
168
+ | `timeline` | GSAP Timeline `\| null` | underlying GSAP timeline (advanced) |
169
+
170
+ ### `movie.toggleMute(): boolean`
171
+
172
+ Flips `muted` and returns the new value.
173
+
174
+ ---
175
+
176
+ ## `Controller`
177
+
178
+ Imported from `pixi-effects/controller`.
179
+
180
+ ```ts
181
+ import { Controller } from 'pixi-effects/controller';
182
+
183
+ const ctrl = new Controller(movie, { canvas });
184
+ // later:
185
+ ctrl.destroy();
186
+ ```
187
+
188
+ A YouTube-style overlay anchored to the canvas:
189
+
190
+ ```
191
+ [▶] [🔉━━━] 0:00 / 0:08 ··· [⬇] [⛶]
192
+ └── play └── volume └── time └── export └── fullscreen
193
+ ```
194
+
195
+ The bar auto-hides 2.5s after pointer activity stops (in both playing and paused state) and reappears on pointer move. Clicking the download icon opens a popover with format/quality selectors and a `Download` confirm button. Fullscreen scales the canvas + bar to the viewport.
196
+
197
+ ### Constructor
198
+
199
+ ```ts
200
+ new Controller(movie: Movie, options: ControllerOptions)
201
+
202
+ interface ControllerOptions {
203
+ canvas: HTMLCanvasElement; // required
204
+ showExportButton?: boolean; // default true; hides ⬇ + popover
205
+ enableKeyboardShortcuts?: boolean; // default true
206
+ className?: string; // default 'movie-controller'
207
+ }
208
+ ```
209
+
210
+ Mounting strategy:
211
+
212
+ - If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
213
+ - Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
214
+
215
+ ### `controller.destroy(): void`
216
+
217
+ Idempotent. Removes:
218
+
219
+ - the controller bar DOM
220
+ - all listeners (Movie events, document keydown/pointerdown/fullscreenchange, wrapper pointermove/mouseleave)
221
+ - the auto-hide timer
222
+ - the wrapper, if it was created here
223
+ - the injected stylesheet (ref-counted across multiple controllers)
224
+
225
+ If the controller still owns `document.fullscreenElement`, it calls `exitFullscreen()`.
226
+
227
+ ### Export popover
228
+
229
+ Format options: **MP4**, **WebM**, **MOV** (mp4 ↔ avc/aac, webm ↔ vp9/opus, mov ↔ avc/aac).
230
+
231
+ Quality options: **Low**, **Medium**, **High** (default), **Very High**.
232
+
233
+ These map directly to `Movie.render()`'s `format` and `video.bitrate` / `audio.bitrate` parameters. Selection persists for the lifetime of the controller instance (no localStorage).
234
+
235
+ The download is triggered by an in-page `<a download>` click, so the file lands in the browser's default download location with a name like `movie-{YYYYMMDD-HHMMSS}.{ext}`.
236
+
237
+ ### Keyboard shortcuts
238
+
239
+ Active when `enableKeyboardShortcuts: true` and the key target is not `<input>`/`<textarea>`/`<select>`/contenteditable.
240
+
241
+ | Key | Action |
242
+ | ----------- | --------------------------------------------------- |
243
+ | `Space` | play / pause |
244
+ | `←` / `→` | step ±1 frame |
245
+ | `↑` / `↓` | volume ±5% (clears mute when increasing past zero) |
246
+ | `M` | toggle mute |
247
+ | `Shift+E` | export with current settings (skips the popover) |
248
+ | `F` | toggle fullscreen |
249
+ | `Esc` | close the export popover or exit fullscreen (browser) |
250
+
251
+ ### Theming
252
+
253
+ The bar uses fixed colors (`#007AFF` for the active track / fill / confirm button, white for icons, `rgba(0,0,0,0.75)` gradient background). Override by adding stricter CSS rules under `.movie-controller`. A theming API (CSS custom properties) is on the roadmap.
254
+
255
+ ---
256
+
257
+ ## Helpers
258
+
259
+ These pure functions are exported from `pixi-effects/controller` for consumers who want to build custom controls or reuse the parsing utilities. All are side-effect-free.
260
+
261
+ ```ts
262
+ import { formatTime, frameToPercent, pxToFrame, pxToFraction, extensionForMimeType }
263
+ from 'pixi-effects/controller';
264
+ ```
265
+
266
+ ### `formatTime(seconds: number): string`
267
+
268
+ Returns `M:SS` (no leading zero on minutes). `formatTime(125)` → `"2:05"`. Negatives clamp to zero.
269
+
270
+ ### `frameToPercent(frame: number, totalFrames: number): number`
271
+
272
+ Returns 0..100 (clamped). `totalFrames <= 0` returns 0.
273
+
274
+ ### `pxToFrame(clientX, rect, totalFrames): number`
275
+
276
+ Maps a pointer X coordinate (relative to viewport) inside a `DOMRect`-shaped object to a frame index 0..totalFrames. Rounded.
277
+
278
+ ```ts
279
+ pxToFrame(150, { left: 100, width: 200 } as DOMRect, 100) // 25
280
+ ```
281
+
282
+ ### `pxToFraction(clientX, rect, inset?): number`
283
+
284
+ Same as `pxToFrame` but returns a normalized 0..1 fraction. The optional `inset` shrinks the active range by that many pixels on each side (used internally for the volume slider).
285
+
286
+ ### `extensionForMimeType(mime: string): string`
287
+
288
+ Maps common video MIME types to file extensions. Falls back to `'mp4'`.
289
+
290
+ | MIME contains | Extension |
291
+ | ------------------- | --------- |
292
+ | `webm` | `webm` |
293
+ | `quicktime` / `mov` | `mov` |
294
+ | `matroska` / `mkv` | `mkv` |
295
+ | anything else | `mp4` |
296
+
297
+ ---
298
+
299
+ ## `pixi-effects/three`
300
+
301
+ Optional three.js integration, imported from its own entry so the core `pixi-effects` entry never touches three.js:
302
+
303
+ ```ts
304
+ import { registerThree, three, ThreeSequence } from 'pixi-effects/three';
305
+ ```
306
+
307
+ **Install:** `npm i three`. `three` is a peer dependency marked optional (`peerDependenciesMeta.three.optional = true`) — consumers who never import `pixi-effects/three` are unaffected either way.
308
+
309
+ For the `type: 'three'` spec shape (fields, keyframe paths, rules), see [DSL reference § three](./dsl.md#three).
310
+
311
+ ### `registerThree(): void`
312
+
313
+ Registers the `'three'` sequence type with the composition builder. Call once, before `Movie.init()` builds a composition containing a `type: 'three'` sequence. Idempotent.
314
+
315
+ ### `three(spec: ThreeSequenceSpec): SequenceSpec`
316
+
317
+ Typing helper — accepts a strongly-typed three spec and returns it as a plain `SequenceSpec`, so it drops straight into `composition.sequences` alongside `text` / `image` / etc. A cast only; not required for the sequence to work, but gives editor autocomplete on `setup` / `update` / `dispose`.
318
+
319
+ ### `ThreeSequence`
320
+
321
+ The `Sequence` subclass that backs `type: 'three'`. Exported for advanced use (e.g. `instanceof` checks); most consumers only need `registerThree()` and `three()`.
322
+
323
+ ### Exported types
324
+
325
+ ```ts
326
+ import type { ThreeContext, ThreeSetupResult, ThreeSequenceSpec } from 'pixi-effects/three';
327
+ ```
328
+
329
+ | Type | Notes |
330
+ | ------------------- | ------------------------------------------------------------------------------- |
331
+ | `ThreeContext` | `{ scene, camera, renderer, width, height }` handed to `setup` / `update` / `dispose`. |
332
+ | `ThreeSetupResult` | `{ objects?, camera? }` returned from `setup`. |
333
+ | `ThreeSequenceSpec` | the `type: 'three'` sequence spec. |