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/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, e as SoundMeasure, T as TimelineData, f as TimelineHtmlOptions, g as TimelineRow, h as TimelineSvgOptions, i as TimelineTransition } from './Movie-BK9daiTt.cjs';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieErrorEvent, e as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, f as SoundMeasure, T as TimelineData, g as TimelineHtmlOptions, h as TimelineRow, i as TimelineSvgOptions, j as TimelineTransition, V as VolumeEvent } from './Movie-CQOhUmko.cjs';
2
2
  import { S as Sequence } from './Base-CSIulyIq.cjs';
3
3
  import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.cjs';
4
4
  export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioSequenceSpec, f as AudioSfxSpec, g as ChromaKeyFilterSpec, h as CircleShapeSpec, i as CompositionSequenceSpec, j as CompositionSpec, k as CrossfadeTransition, l as CustomFilterSpec, D as DipTransition, m as DissolveTransition, E as EllipseShapeSpec, n as Expr, F as FilterSpec, G as GradientSpec, o as IrisTransition, K as Keyframe, L as LineShapeSpec, P as PathShapeSpec, p as PolygonShapeSpec, q as PropValue, r as Props, R as RectShapeSpec, s as SfxKnobs, t as SfxOptions, u as SfxPreset, v as SfxVoice, w as ShapeSequenceSpec, x as SlideTransition, T as TextSequenceSpec, y as TransitionCommon, z as TransitionSpec, V as VideoSequenceSpec, W as WipeTransition, Z as ZoomTransition } from './types-oJijbn-K.cjs';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, e as SoundMeasure, T as TimelineData, f as TimelineHtmlOptions, g as TimelineRow, h as TimelineSvgOptions, i as TimelineTransition } from './Movie-B0OUKlZI.js';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, M as Movie, d as MovieErrorEvent, e as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, f as SoundMeasure, T as TimelineData, g as TimelineHtmlOptions, h as TimelineRow, i as TimelineSvgOptions, j as TimelineTransition, V as VolumeEvent } from './Movie-HF3pSGjQ.js';
2
2
  import { S as Sequence } from './Base-DVgwyIUj.js';
3
3
  import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.js';
4
4
  export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioSequenceSpec, f as AudioSfxSpec, g as ChromaKeyFilterSpec, h as CircleShapeSpec, i as CompositionSequenceSpec, j as CompositionSpec, k as CrossfadeTransition, l as CustomFilterSpec, D as DipTransition, m as DissolveTransition, E as EllipseShapeSpec, n as Expr, F as FilterSpec, G as GradientSpec, o as IrisTransition, K as Keyframe, L as LineShapeSpec, P as PathShapeSpec, p as PolygonShapeSpec, q as PropValue, r as Props, R as RectShapeSpec, s as SfxKnobs, t as SfxOptions, u as SfxPreset, v as SfxVoice, w as ShapeSequenceSpec, x as SlideTransition, T as TextSequenceSpec, y as TransitionCommon, z as TransitionSpec, V as VideoSequenceSpec, W as WipeTransition, Z as ZoomTransition } from './types-oJijbn-K.js';
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
- import { CompositionSequence } from './chunk-36Z4RIRK.js';
2
- import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-4O2NYDJG.js';
3
- export { registerSequenceType } from './chunk-4O2NYDJG.js';
1
+ import { CompositionSequence } from './chunk-O4AJYUPK.js';
2
+ import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-FNKSXBKB.js';
3
+ export { registerSequenceType } from './chunk-FNKSXBKB.js';
4
4
  import { warnUnknownOptions, suggestName } from './chunk-DKGG7WD4.js';
5
5
  import * as PIXI from 'pixi.js';
6
6
  import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
7
7
  import { gsap } from 'gsap';
8
8
  import { PixiPlugin } from 'gsap/PixiPlugin';
9
- import { QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat } from 'mediabunny';
9
+ import { QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, getFirstEncodableAudioCodec } from 'mediabunny';
10
10
 
11
11
  var registered = false;
12
12
  function ensureLoadersRegistered(audioContext) {
@@ -288,6 +288,28 @@ var AUDIO_CODEC_BY_FORMAT = {
288
288
  webm: "opus",
289
289
  mkv: "opus"
290
290
  };
291
+ var AUDIO_PREFERENCE = {
292
+ mp4: ["aac", "opus", "mp3", "flac"],
293
+ mov: ["aac", "mp3"],
294
+ webm: ["opus", "vorbis"],
295
+ mkv: ["opus", "vorbis", "aac", "flac", "mp3"]
296
+ };
297
+ async function chooseAudioCodec(fmt, requested, container, audio, bitrate) {
298
+ const supported = container.getSupportedAudioCodecs?.();
299
+ const preferred = AUDIO_PREFERENCE[fmt].filter((c) => !supported || supported.includes(c));
300
+ const canEncode = (list) => getFirstEncodableAudioCodec(list, { numberOfChannels: audio.numberOfChannels ?? 2, sampleRate: audio.sampleRate, bitrate });
301
+ if (requested) {
302
+ if (await canEncode([requested])) return requested;
303
+ const alt = await canEncode(preferred.filter((c) => c !== requested));
304
+ throw new Error(`pixi-effects: this browser cannot encode "${requested}" audio for ${fmt}` + (alt ? `; "${alt}" would work: render({ format: '${fmt}', audio: { codec: '${alt}' } }), or leave audio.codec out and the best one is chosen.` : "; it has no audio encoder for this format at all (try another browser, or a movie without sound)."));
305
+ }
306
+ const chosen = await canEncode(preferred);
307
+ if (!chosen) throw new Error(`pixi-effects: this browser has no audio encoder for ${fmt} (it tried ${preferred.join(", ")}): try another browser, or render a movie without sound.`);
308
+ if (chosen !== preferred[0]) {
309
+ console.warn(`pixi-effects: this browser cannot encode ${preferred[0].toUpperCase()} audio (Chrome on Linux has no AAC encoder), so the ${fmt} carries ${chosen} instead; current players play it. A webm export needs no AAC.`);
310
+ }
311
+ return chosen;
312
+ }
291
313
  var qualityMap = {
292
314
  "very-low": QUALITY_VERY_LOW,
293
315
  "low": QUALITY_LOW,
@@ -320,8 +342,10 @@ async function exportFrames(movie, options = {}) {
320
342
  bitrate: qualityMap[options.audio?.bitrate ?? "high"] ?? QUALITY_HIGH
321
343
  }
322
344
  };
345
+ const container = makeOutputFormat(opts.format);
346
+ if (movie.audioBuffer) opts.audio.codec = await chooseAudioCodec(fmt, options.audio?.codec, container, movie.audioBuffer, opts.audio.bitrate);
323
347
  const output = new Output({
324
- format: makeOutputFormat(opts.format),
348
+ format: container,
325
349
  target: new BufferTarget()
326
350
  });
327
351
  const canvasSource = new CanvasSource(movie.app.canvas, {
@@ -1418,6 +1442,8 @@ var Movie = class {
1418
1442
  audioSource = null;
1419
1443
  gainNode = null;
1420
1444
  _cancelAudioStart = null;
1445
+ /** > 0 while the movie moves its own playhead (playback, render, snapshot, contactSheet): those are not the seeks `seeking` / `seeked` report. */
1446
+ _quiet = 0;
1421
1447
  _volume = 1;
1422
1448
  _muted = false;
1423
1449
  isPlaying = false;
@@ -1545,12 +1571,32 @@ var Movie = class {
1545
1571
  } catch (cleanupErr) {
1546
1572
  console.warn("pixi-effects: cleanup after init failure also threw:", cleanupErr);
1547
1573
  }
1574
+ this._emitError("init", err);
1548
1575
  throw err;
1549
1576
  }
1550
1577
  }
1551
1578
  async gotoFrame(frame, force = false) {
1552
1579
  if (this._initState !== "ready") return;
1553
1580
  if (!force && this.currentFrame === frame) return;
1581
+ if (this._quiet > 0) return this._goto(frame);
1582
+ const target = Math.max(0, Math.min(frame, this.totalFrames));
1583
+ this.emit("seeking", { frame: target, totalFrames: this.totalFrames });
1584
+ await this._goto(frame);
1585
+ this.emit("seeked", { frame: this.currentFrame, totalFrames: this.totalFrames });
1586
+ }
1587
+ /** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
1588
+ async _quietly(fn) {
1589
+ this._quiet++;
1590
+ try {
1591
+ return await fn();
1592
+ } finally {
1593
+ this._quiet--;
1594
+ }
1595
+ }
1596
+ _emitError(where, error) {
1597
+ this.emit("error", { where, message: error instanceof Error ? error.message : String(error), error });
1598
+ }
1599
+ async _goto(frame) {
1554
1600
  this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
1555
1601
  this.timeline.time(this.currentFrame / this.frameRate);
1556
1602
  await this._awaitVideoFrames();
@@ -1572,7 +1618,7 @@ var Movie = class {
1572
1618
  async snapshot(frame = this.currentFrame, opts = {}) {
1573
1619
  warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
1574
1620
  this._requireReady("snapshot");
1575
- await this.gotoFrame(clampFrame(frame, this.totalFrames), true);
1621
+ await this._quietly(() => this.gotoFrame(clampFrame(frame, this.totalFrames), true));
1576
1622
  const scale = opts.scale ?? 1;
1577
1623
  const out = document.createElement("canvas");
1578
1624
  out.width = Math.max(1, Math.round(this.width * scale));
@@ -1596,7 +1642,7 @@ var Movie = class {
1596
1642
  try {
1597
1643
  for (let i = 0; i < frames.length; i++) {
1598
1644
  const f = frames[i];
1599
- await this.gotoFrame(f, true);
1645
+ await this._quietly(() => this.gotoFrame(f, true));
1600
1646
  const { x, y } = L.positions[i];
1601
1647
  g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
1602
1648
  g.fillStyle = "#e8ecf8";
@@ -1605,7 +1651,7 @@ var Movie = class {
1605
1651
  g.fillText(`frame ${f} \xB7 ${(f / this.frameRate).toFixed(2)}s`, x + 8, y + LABEL_H / 2);
1606
1652
  }
1607
1653
  } finally {
1608
- await this.gotoFrame(back, true);
1654
+ await this._quietly(() => this.gotoFrame(back, true));
1609
1655
  }
1610
1656
  return encodeCanvas(sheet, "image/png", opts.as ?? "blob");
1611
1657
  }
@@ -1688,17 +1734,20 @@ var Movie = class {
1688
1734
  const frame = Math.floor(elapsed * this.frameRate);
1689
1735
  if (frame <= this.totalFrames) {
1690
1736
  inFlight = true;
1691
- this.gotoFrame(frame).catch((err) => {
1737
+ this._goto(frame).catch((err) => {
1692
1738
  console.warn("pixi-effects: gotoFrame failed during playback:", err);
1739
+ this._emitError("playback", err);
1693
1740
  }).finally(() => {
1694
1741
  inFlight = false;
1695
1742
  });
1696
1743
  this._raf = requestAnimationFrame(tick);
1697
1744
  } else {
1698
1745
  this.pause();
1699
- this.gotoFrame(this.totalFrames).catch((err) => {
1746
+ this._goto(this.totalFrames).catch((err) => {
1700
1747
  console.warn("pixi-effects: final gotoFrame failed:", err);
1748
+ this._emitError("playback", err);
1701
1749
  });
1750
+ this.emit("ended");
1702
1751
  }
1703
1752
  };
1704
1753
  this._raf = requestAnimationFrame(tick);
@@ -1742,15 +1791,20 @@ var Movie = class {
1742
1791
  if (wasPlaying) this.emit("pause");
1743
1792
  }
1744
1793
  set volume(v) {
1745
- this._volume = Math.max(0, Math.min(1, v));
1794
+ const next = Math.max(0, Math.min(1, v));
1795
+ const changed = next !== this._volume;
1796
+ this._volume = next;
1746
1797
  if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
1798
+ if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
1747
1799
  }
1748
1800
  get volume() {
1749
1801
  return this._volume;
1750
1802
  }
1751
1803
  set muted(v) {
1804
+ const changed = !!v !== this._muted;
1752
1805
  this._muted = !!v;
1753
1806
  if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
1807
+ if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
1754
1808
  }
1755
1809
  get muted() {
1756
1810
  return this._muted;
@@ -1761,7 +1815,12 @@ var Movie = class {
1761
1815
  }
1762
1816
  async render(options) {
1763
1817
  warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
1764
- return await exportFrames(this, options);
1818
+ try {
1819
+ return await this._quietly(() => exportFrames(this, options));
1820
+ } catch (err) {
1821
+ this._emitError("render", err);
1822
+ throw err;
1823
+ }
1765
1824
  }
1766
1825
  async destroy() {
1767
1826
  safeRun(() => this.pause());