pixi-effects 0.7.0 → 0.8.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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +7 -6
  3. package/ai/reference/cheatsheet.md +5 -1
  4. package/ai/template.html +3 -3
  5. package/dist/Composition-CM62XJKT.cjs +14 -0
  6. package/dist/{Composition-3KHQPRBA.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
  7. package/dist/Composition-RJDSQYTV.js +5 -0
  8. package/dist/{Composition-DT657FO5.js.map → Composition-RJDSQYTV.js.map} +1 -1
  9. package/dist/Controller.cjs +55 -20
  10. package/dist/Controller.cjs.map +1 -1
  11. package/dist/Controller.d.cts +31 -2
  12. package/dist/Controller.d.ts +31 -2
  13. package/dist/Controller.js +55 -21
  14. package/dist/Controller.js.map +1 -1
  15. package/dist/{Movie-BK9daiTt.d.cts → Movie-CQOhUmko.d.cts} +24 -1
  16. package/dist/{Movie-B0OUKlZI.d.ts → Movie-HF3pSGjQ.d.ts} +24 -1
  17. package/dist/{chunk-6I55T4OH.cjs → chunk-AGMNSJJH.cjs} +13 -4
  18. package/dist/chunk-AGMNSJJH.cjs.map +1 -0
  19. package/dist/{chunk-4O2NYDJG.js → chunk-FNKSXBKB.js} +13 -4
  20. package/dist/chunk-FNKSXBKB.js.map +1 -0
  21. package/dist/{chunk-36Z4RIRK.js → chunk-O4AJYUPK.js} +3 -3
  22. package/dist/{chunk-36Z4RIRK.js.map → chunk-O4AJYUPK.js.map} +1 -1
  23. package/dist/{chunk-4VSIJLU3.cjs → chunk-PDBDDGG5.cjs} +21 -21
  24. package/dist/{chunk-4VSIJLU3.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
  25. package/dist/index.cjs +82 -23
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +71 -12
  30. package/dist/index.js.map +1 -1
  31. package/dist/three.cjs +5 -5
  32. package/dist/three.js +1 -1
  33. package/docs/api.md +51 -0
  34. package/docs/dsl.md +14 -2
  35. package/llms-full.txt +70 -3
  36. package/package.json +1 -1
  37. package/dist/Composition-3KHQPRBA.cjs +0 -14
  38. package/dist/Composition-DT657FO5.js +0 -5
  39. package/dist/chunk-4O2NYDJG.js.map +0 -1
  40. package/dist/chunk-6I55T4OH.cjs.map +0 -1
package/dist/three.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunk6I55T4OH_cjs = require('./chunk-6I55T4OH.cjs');
3
+ var chunkAGMNSJJH_cjs = require('./chunk-AGMNSJJH.cjs');
4
4
  require('./chunk-UQBGW5DO.cjs');
5
5
  var pixi_js = require('pixi.js');
6
6
  var three$1 = require('three');
7
7
 
8
- var ThreeSequence = class extends chunk6I55T4OH_cjs.Sequence {
8
+ var ThreeSequence = class extends chunkAGMNSJJH_cjs.Sequence {
9
9
  _renderer = null;
10
10
  _scene = null;
11
11
  _camera = null;
@@ -24,7 +24,7 @@ var ThreeSequence = class extends chunk6I55T4OH_cjs.Sequence {
24
24
  if (this.duration === void 0) {
25
25
  this.duration = this.parent?.duration ?? this.root.duration;
26
26
  }
27
- const scope = chunk6I55T4OH_cjs.buildScope(this, this.parent, this.root);
27
+ const scope = chunkAGMNSJJH_cjs.buildScope(this, this.parent, this.root);
28
28
  const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
29
29
  const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
30
30
  const resolution = spec.resolution ?? 1;
@@ -144,12 +144,12 @@ var ThreeSequence = class extends chunk6I55T4OH_cjs.Sequence {
144
144
  function resolveDim(v, scope) {
145
145
  if (v === void 0) return void 0;
146
146
  if (typeof v === "number") return v;
147
- return chunk6I55T4OH_cjs.evaluateExpr(v, scope);
147
+ return chunkAGMNSJJH_cjs.evaluateExpr(v, scope);
148
148
  }
149
149
 
150
150
  // src/three/index.ts
151
151
  function registerThree() {
152
- chunk6I55T4OH_cjs.registerSequenceType("three", ThreeSequence);
152
+ chunkAGMNSJJH_cjs.registerSequenceType("three", ThreeSequence);
153
153
  }
154
154
  function three(spec) {
155
155
  return spec;
package/dist/three.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-4O2NYDJG.js';
1
+ import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-FNKSXBKB.js';
2
2
  import './chunk-DKGG7WD4.js';
3
3
  import { Texture, Sprite } from 'pixi.js';
4
4
  import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
package/docs/api.md CHANGED
@@ -163,6 +163,8 @@ interface RenderOptions {
163
163
  }
164
164
  ```
165
165
 
166
+ **Audio codec fallback:** unless you name `audio.codec`, the first codec this browser can actually encode is used (mp4: `aac`, then `opus`, `mp3`, `flac`; webm: `opus`, `vorbis`). Chrome on Linux has no AAC encoder, so there an mp4 with sound carries Opus and a warning says so (before, the export failed with a message about encoder configurations). A codec you name is never swapped: if it cannot be encoded the error says which one would work.
167
+
166
168
  Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
167
169
 
168
170
  The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
@@ -191,6 +193,12 @@ movie.off(event, fn): this
191
193
  | `'play'` | none | when `play()` actually transitions from paused |
192
194
  | `'pause'` | none | when `pause()` actually transitions from playing |
193
195
  | `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
196
+ | `'seeking'` / `'seeked'` | `{ frame, totalFrames }` (the frame it lands on) | a `gotoFrame()` to another frame starts / has finished. NOT emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()` |
197
+ | `'ended'` | none | playback ran off the end (after `'pause'`); pausing by hand does not emit it |
198
+ | `'volumechange'` | `{ volume: number; muted: boolean }` | `volume` or `muted` really changed (clamping counts) |
199
+ | `'error'` | `{ where: 'init' \| 'render' \| 'playback'; message: string; error: unknown }` | `init()` / `render()` failed (the call still rejects), or a frame failed during playback |
200
+
201
+ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>` ports over.
194
202
 
195
203
  `progress` is `0..100` (rounded integer percent).
196
204
 
@@ -216,6 +224,24 @@ Flips `muted` and returns the new value.
216
224
 
217
225
  ---
218
226
 
227
+ ## Building your own player
228
+
229
+ `Controller` is one optional player; everything it does is public on the movie, so a player of your own is plain HTML + CSS + a few handlers (`examples/15-custom-player.html` is a complete one):
230
+
231
+ ```js
232
+ const el = id => document.getElementById(id);
233
+ movie.on('ready', () => { el('seek').max = movie.totalFrames; });
234
+ movie.on('frame', ({ frame }) => { el('seek').value = frame; el('time').textContent = (frame / movie.frameRate).toFixed(1) + ' s'; });
235
+ movie.on('play', () => { el('play').textContent = '❚❚'; });
236
+ movie.on('pause', () => { el('play').textContent = '▶'; });
237
+ movie.on('ended', () => { el('play').textContent = '↻'; }); // play() at the end starts again from 0
238
+ movie.on('volumechange', ({ volume, muted }) => { el('vol').value = muted ? 0 : volume; });
239
+ el('play').onclick = () => (movie.isPlaying ? movie.pause() : movie.play());
240
+ el('seek').oninput = e => movie.gotoFrame(+e.target.value);
241
+ ```
242
+
243
+ What you have: `play()`, `pause()`, `gotoFrame(frame)`, `volume`, `muted` / `toggleMute()`; the state `currentFrame`, `totalFrames`, `frameRate`, `duration`, `isPlaying`, `isReady`; the events above; `render()` for a download button; `snapshot()` for a thumbnail; `timelineData()` / `timelineSvg()` for a timeline like `pixi-effects-view`'s. Keep the movie's `canvas` wherever your layout wants it (the page's CSS sizes it). To only change how `Controller` looks, see **Theme** below.
244
+
219
245
  ## `Controller`
220
246
 
221
247
  Imported from `pixi-effects/controller`.
@@ -247,6 +273,7 @@ interface ControllerOptions {
247
273
  showExportButton?: boolean; // default true; hides ⬇ + popover
248
274
  enableKeyboardShortcuts?: boolean; // default true
249
275
  className?: string; // default 'movie-controller'
276
+ theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
250
277
  }
251
278
  ```
252
279
 
@@ -255,6 +282,30 @@ Mounting strategy:
255
282
  - If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
256
283
  - Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
257
284
 
285
+ ### Theme: change the colours of the bar
286
+
287
+ The bar is blue by default. Every colour and the track thickness are CSS custom properties with the old values as defaults, so there are two ways to change them:
288
+
289
+ ```js
290
+ new Controller(movie, { canvas, theme: { accent: '#ff4d6d', foreground: '#ffe9a8', trackHeight: 5 } }); // in code
291
+ controller.setTheme({ accent: '#7bd88f' }); // later, live; only the keys you give change
292
+ controller.setTheme({ accent: null }); // null = back to the default
293
+ ```
294
+ ```css
295
+ :root { --mc-accent: #ff4d6d; } /* or in your page CSS: the canvas's parent, or :root */
296
+ ```
297
+
298
+ | `theme` key | CSS property | What | Default |
299
+ |---|---|---|---|
300
+ | `accent` | `--mc-accent` | progress bar, its thumb, focus rings, the export button | `#007AFF` |
301
+ | `foreground` | `--mc-fg` | icons, the time, the volume slider | `#fff` |
302
+ | `track` | `--mc-track` | the unfilled part of the progress / volume bars | `rgba(255,255,255,0.25)` |
303
+ | `barBackground` | `--mc-bar-bg` | the background behind the buttons (any CSS background, e.g. a gradient) | black fading to transparent upward |
304
+ | `trackHeight` | `--mc-track-height` | thickness of the progress bar (a number is px) | `3px` |
305
+ | `font` | `--mc-font` | `font-family` of the bar | system UI font |
306
+
307
+ A misspelt key warns with the valid ones. The default stylesheet is exported as `CONTROLLER_CSS` (from `pixi-effects/controller`) if you want to copy and restyle more.
308
+
258
309
  ### `controller.destroy(): void`
259
310
 
260
311
  Idempotent. Removes:
package/docs/dsl.md CHANGED
@@ -944,7 +944,7 @@ A filter by name, as plain data. `type` is the filter's class name without `Filt
944
944
  keyframes: [{ at: 0, to: { 'filters.halo.outerStrength': 6 }, duration: 1 }] } // animate any scalar option by name
945
945
  ```
946
946
 
947
- Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Verified rendering and exporting in a real browser (`examples/_checks/named-filters.html`, 24 filters):
947
+ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Rendering and exporting were verified in a real browser for 40 of them (`examples/_checks/named-filters.html` and `named-filters-2.html`); `colorMap` and `simpleLightmap` need a texture, which JSON cannot hold, so use `custom` for those two:
948
948
 
949
949
  | `type` | Options (the useful ones) | What it does |
950
950
  |---|---|---|
@@ -972,13 +972,25 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
972
972
  | `ascii` | `size color replaceColor` | Text-art |
973
973
  | `colorOverlay` | `color alpha` | Tint over everything |
974
974
  | `advancedBloom` / `bloom` | `threshold bloomScale brightness blur` | Bloom |
975
- | `bevel`, `dot`, `crossHatch`, `tiltShift`, `reflection`, `shockwave`, `godray`, `simplexNoise`, `colorGradient`, `colorMap`, `colorReplace`, `multiColorReplace`, `convolution`, `backdropBlur`, `simpleLightmap`, `tiltShiftAxis` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) | Other pixi-filters |
975
+ | `bevel` | `thickness rotation lightColor lightAlpha shadowColor shadowAlpha` | Raised edge |
976
+ | `dot` / `crossHatch` | `scale angle grayscale` / – | Halftone / hatching |
977
+ | `godray` | `angle gain lacunarity alpha parallel time` | Light shafts (animate `time`) |
978
+ | `reflection` | `mirror boundary amplitude waveLength alpha time` (ranges are `[start, end]`) | Water reflection |
979
+ | `simplexNoise` | `strength scale offset` | Cloudy noise over the layer |
980
+ | `colorGradient` | `gradientType: 'linear' 'radial' 'conic'` (the filter's own option is `type`, which the DSL uses), `stops: [{ offset, color, alpha }]`, `angle` | A gradient laid over the layer |
981
+ | `colorReplace` / `multiColorReplace` | `originalColor targetColor tolerance` / `replacements: [[from, to], …] tolerance` | Swap colours |
982
+ | `convolution` | `matrix` (9 numbers), `width height` | Sharpen, edge detect |
983
+ | `tiltShift` / `tiltShiftAxis` | `blur gradientBlur start end` (canvas pixels) | Miniature blur |
984
+ | `shockwave` | `center amplitude wavelength brightness speed radius time` (animate `time` 0 → 1) | A ripple from a point |
985
+ | `backdropBlur` | `strength` | Blurs what is BEHIND the layer, so alone it shows nothing |
986
+ | the rest of pixi-filters (`colorMap`, `simpleLightmap`) | need a texture: `{ type: 'custom' }` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) |
976
987
 
977
988
  Gotchas, all measured:
978
989
 
979
990
  - **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
980
991
  - **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
981
992
  - **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) do not tween as a whole; animate `'filters.<name>.red.x'` if the filter exposes it, or swap the filter. `pixelate` animates through `sizeX` / `sizeY`, not `size`.
993
+ - `shockwave` and `godray` are driven by `time` (a still frame at `time: 0` looks like nothing): animate it. `radialBlur`, `shockwave` and `tiltShift` positions are canvas pixels too.
982
994
  - Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
983
995
 
984
996
  ### `custom`
package/llms-full.txt CHANGED
@@ -139,9 +139,13 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
139
139
 
140
140
  Add `threeD: true` to any visual layer, then use `z`, `rotationX`, `rotationY` (centre rotation with `anchorX/Y: 0.5` or `pivotX/Y`). Add a `{ type: 'camera' }` layer for a view. A `threeD` layer at `z: 0` with the default camera looks identical to a 2D one. Consecutive `threeD` layers are drawn farthest-first (equal `z` keeps array order); non-`threeD` layers ignore the camera and keep array order. Layers at/behind the camera plane are hidden. Not supported on `threeD` layers: masks, transitions, `filterArea`.
141
141
 
142
+ ## Player
143
+
144
+ `new Controller(movie, { canvas, theme: { accent: '#ff4d6d' } })` (import from `pixi-effects/controller`; theme keys `accent foreground track barBackground trackHeight font`, or CSS `--mc-accent` …; `controller.setTheme(…)`). Your own player: `movie.play() pause() gotoFrame(f) volume muted toggleMute()`, state `currentFrame totalFrames frameRate duration isPlaying`, events `ready frame play pause ended seeking seeked volumechange error progress` (`<video>` names). `examples/15-custom-player.html` is a complete one.
145
+
142
146
  ## Filters by name (no import)
143
147
 
144
- `filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha. **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
148
+ `filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha · `colorGradient` gradientType('linear'|'radial'|'conic') stops[{offset,color,alpha}] angle · `colorReplace` originalColor targetColor tolerance · `convolution` matrix(9) · `shockwave` / `godray` are driven by `time` (animate it) · `colorMap` / `simpleLightmap` need a texture (use `custom`). **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
145
149
 
146
150
  ## Transitions (on the parent composition)
147
151
 
@@ -1757,7 +1761,7 @@ A filter by name, as plain data. `type` is the filter's class name without `Filt
1757
1761
  keyframes: [{ at: 0, to: { 'filters.halo.outerStrength': 6 }, duration: 1 }] } // animate any scalar option by name
1758
1762
  ```
1759
1763
 
1760
- Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Verified rendering and exporting in a real browser (`examples/_checks/named-filters.html`, 24 filters):
1764
+ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Rendering and exporting were verified in a real browser for 40 of them (`examples/_checks/named-filters.html` and `named-filters-2.html`); `colorMap` and `simpleLightmap` need a texture, which JSON cannot hold, so use `custom` for those two:
1761
1765
 
1762
1766
  | `type` | Options (the useful ones) | What it does |
1763
1767
  |---|---|---|
@@ -1785,13 +1789,25 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
1785
1789
  | `ascii` | `size color replaceColor` | Text-art |
1786
1790
  | `colorOverlay` | `color alpha` | Tint over everything |
1787
1791
  | `advancedBloom` / `bloom` | `threshold bloomScale brightness blur` | Bloom |
1788
- | `bevel`, `dot`, `crossHatch`, `tiltShift`, `reflection`, `shockwave`, `godray`, `simplexNoise`, `colorGradient`, `colorMap`, `colorReplace`, `multiColorReplace`, `convolution`, `backdropBlur`, `simpleLightmap`, `tiltShiftAxis` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) | Other pixi-filters |
1792
+ | `bevel` | `thickness rotation lightColor lightAlpha shadowColor shadowAlpha` | Raised edge |
1793
+ | `dot` / `crossHatch` | `scale angle grayscale` / – | Halftone / hatching |
1794
+ | `godray` | `angle gain lacunarity alpha parallel time` | Light shafts (animate `time`) |
1795
+ | `reflection` | `mirror boundary amplitude waveLength alpha time` (ranges are `[start, end]`) | Water reflection |
1796
+ | `simplexNoise` | `strength scale offset` | Cloudy noise over the layer |
1797
+ | `colorGradient` | `gradientType: 'linear' 'radial' 'conic'` (the filter's own option is `type`, which the DSL uses), `stops: [{ offset, color, alpha }]`, `angle` | A gradient laid over the layer |
1798
+ | `colorReplace` / `multiColorReplace` | `originalColor targetColor tolerance` / `replacements: [[from, to], …] tolerance` | Swap colours |
1799
+ | `convolution` | `matrix` (9 numbers), `width height` | Sharpen, edge detect |
1800
+ | `tiltShift` / `tiltShiftAxis` | `blur gradientBlur start end` (canvas pixels) | Miniature blur |
1801
+ | `shockwave` | `center amplitude wavelength brightness speed radius time` (animate `time` 0 → 1) | A ripple from a point |
1802
+ | `backdropBlur` | `strength` | Blurs what is BEHIND the layer, so alone it shows nothing |
1803
+ | the rest of pixi-filters (`colorMap`, `simpleLightmap`) | need a texture: `{ type: 'custom' }` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) |
1789
1804
 
1790
1805
  Gotchas, all measured:
1791
1806
 
1792
1807
  - **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
1793
1808
  - **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
1794
1809
  - **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) do not tween as a whole; animate `'filters.<name>.red.x'` if the filter exposes it, or swap the filter. `pixelate` animates through `sizeX` / `sizeY`, not `size`.
1810
+ - `shockwave` and `godray` are driven by `time` (a still frame at `time: 0` looks like nothing): animate it. `radialBlur`, `shockwave` and `tiltShift` positions are canvas pixels too.
1795
1811
  - Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
1796
1812
 
1797
1813
  ### `custom`
@@ -2115,6 +2131,8 @@ interface RenderOptions {
2115
2131
  }
2116
2132
  ```
2117
2133
 
2134
+ **Audio codec fallback:** unless you name `audio.codec`, the first codec this browser can actually encode is used (mp4: `aac`, then `opus`, `mp3`, `flac`; webm: `opus`, `vorbis`). Chrome on Linux has no AAC encoder, so there an mp4 with sound carries Opus and a warning says so (before, the export failed with a message about encoder configurations). A codec you name is never swapped: if it cannot be encoded the error says which one would work.
2135
+
2118
2136
  Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
2119
2137
 
2120
2138
  The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
@@ -2143,6 +2161,12 @@ movie.off(event, fn): this
2143
2161
  | `'play'` | none | when `play()` actually transitions from paused |
2144
2162
  | `'pause'` | none | when `pause()` actually transitions from playing |
2145
2163
  | `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
2164
+ | `'seeking'` / `'seeked'` | `{ frame, totalFrames }` (the frame it lands on) | a `gotoFrame()` to another frame starts / has finished. NOT emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()` |
2165
+ | `'ended'` | none | playback ran off the end (after `'pause'`); pausing by hand does not emit it |
2166
+ | `'volumechange'` | `{ volume: number; muted: boolean }` | `volume` or `muted` really changed (clamping counts) |
2167
+ | `'error'` | `{ where: 'init' \| 'render' \| 'playback'; message: string; error: unknown }` | `init()` / `render()` failed (the call still rejects), or a frame failed during playback |
2168
+
2169
+ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>` ports over.
2146
2170
 
2147
2171
  `progress` is `0..100` (rounded integer percent).
2148
2172
 
@@ -2168,6 +2192,24 @@ Flips `muted` and returns the new value.
2168
2192
 
2169
2193
  ---
2170
2194
 
2195
+ ## Building your own player
2196
+
2197
+ `Controller` is one optional player; everything it does is public on the movie, so a player of your own is plain HTML + CSS + a few handlers (`examples/15-custom-player.html` is a complete one):
2198
+
2199
+ ```js
2200
+ const el = id => document.getElementById(id);
2201
+ movie.on('ready', () => { el('seek').max = movie.totalFrames; });
2202
+ movie.on('frame', ({ frame }) => { el('seek').value = frame; el('time').textContent = (frame / movie.frameRate).toFixed(1) + ' s'; });
2203
+ movie.on('play', () => { el('play').textContent = '❚❚'; });
2204
+ movie.on('pause', () => { el('play').textContent = '▶'; });
2205
+ movie.on('ended', () => { el('play').textContent = '↻'; }); // play() at the end starts again from 0
2206
+ movie.on('volumechange', ({ volume, muted }) => { el('vol').value = muted ? 0 : volume; });
2207
+ el('play').onclick = () => (movie.isPlaying ? movie.pause() : movie.play());
2208
+ el('seek').oninput = e => movie.gotoFrame(+e.target.value);
2209
+ ```
2210
+
2211
+ What you have: `play()`, `pause()`, `gotoFrame(frame)`, `volume`, `muted` / `toggleMute()`; the state `currentFrame`, `totalFrames`, `frameRate`, `duration`, `isPlaying`, `isReady`; the events above; `render()` for a download button; `snapshot()` for a thumbnail; `timelineData()` / `timelineSvg()` for a timeline like `pixi-effects-view`'s. Keep the movie's `canvas` wherever your layout wants it (the page's CSS sizes it). To only change how `Controller` looks, see **Theme** below.
2212
+
2171
2213
  ## `Controller`
2172
2214
 
2173
2215
  Imported from `pixi-effects/controller`.
@@ -2199,6 +2241,7 @@ interface ControllerOptions {
2199
2241
  showExportButton?: boolean; // default true; hides ⬇ + popover
2200
2242
  enableKeyboardShortcuts?: boolean; // default true
2201
2243
  className?: string; // default 'movie-controller'
2244
+ theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
2202
2245
  }
2203
2246
  ```
2204
2247
 
@@ -2207,6 +2250,30 @@ Mounting strategy:
2207
2250
  - If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
2208
2251
  - Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
2209
2252
 
2253
+ ### Theme: change the colours of the bar
2254
+
2255
+ The bar is blue by default. Every colour and the track thickness are CSS custom properties with the old values as defaults, so there are two ways to change them:
2256
+
2257
+ ```js
2258
+ new Controller(movie, { canvas, theme: { accent: '#ff4d6d', foreground: '#ffe9a8', trackHeight: 5 } }); // in code
2259
+ controller.setTheme({ accent: '#7bd88f' }); // later, live; only the keys you give change
2260
+ controller.setTheme({ accent: null }); // null = back to the default
2261
+ ```
2262
+ ```css
2263
+ :root { --mc-accent: #ff4d6d; } /* or in your page CSS: the canvas's parent, or :root */
2264
+ ```
2265
+
2266
+ | `theme` key | CSS property | What | Default |
2267
+ |---|---|---|---|
2268
+ | `accent` | `--mc-accent` | progress bar, its thumb, focus rings, the export button | `#007AFF` |
2269
+ | `foreground` | `--mc-fg` | icons, the time, the volume slider | `#fff` |
2270
+ | `track` | `--mc-track` | the unfilled part of the progress / volume bars | `rgba(255,255,255,0.25)` |
2271
+ | `barBackground` | `--mc-bar-bg` | the background behind the buttons (any CSS background, e.g. a gradient) | black fading to transparent upward |
2272
+ | `trackHeight` | `--mc-track-height` | thickness of the progress bar (a number is px) | `3px` |
2273
+ | `font` | `--mc-font` | `font-family` of the bar | system UI font |
2274
+
2275
+ A misspelt key warns with the valid ones. The default stylesheet is exported as `CONTROLLER_CSS` (from `pixi-effects/controller`) if you want to copy and restyle more.
2276
+
2210
2277
  ### `controller.destroy(): void`
2211
2278
 
2212
2279
  Idempotent. Removes:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-effects",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,14 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk4VSIJLU3_cjs = require('./chunk-4VSIJLU3.cjs');
4
- require('./chunk-6I55T4OH.cjs');
5
- require('./chunk-UQBGW5DO.cjs');
6
-
7
-
8
-
9
- Object.defineProperty(exports, "CompositionSequence", {
10
- enumerable: true,
11
- get: function () { return chunk4VSIJLU3_cjs.CompositionSequence; }
12
- });
13
- //# sourceMappingURL=Composition-3KHQPRBA.cjs.map
14
- //# sourceMappingURL=Composition-3KHQPRBA.cjs.map
@@ -1,5 +0,0 @@
1
- export { CompositionSequence } from './chunk-36Z4RIRK.js';
2
- import './chunk-4O2NYDJG.js';
3
- import './chunk-DKGG7WD4.js';
4
- //# sourceMappingURL=Composition-DT657FO5.js.map
5
- //# sourceMappingURL=Composition-DT657FO5.js.map