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.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -6
- package/ai/reference/cheatsheet.md +5 -1
- package/ai/template.html +3 -3
- package/dist/Composition-CM62XJKT.cjs +14 -0
- package/dist/{Composition-3KHQPRBA.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
- package/dist/Composition-RJDSQYTV.js +5 -0
- package/dist/{Composition-DT657FO5.js.map → Composition-RJDSQYTV.js.map} +1 -1
- package/dist/Controller.cjs +55 -20
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +31 -2
- package/dist/Controller.d.ts +31 -2
- package/dist/Controller.js +55 -21
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-BK9daiTt.d.cts → Movie-CQOhUmko.d.cts} +24 -1
- package/dist/{Movie-B0OUKlZI.d.ts → Movie-HF3pSGjQ.d.ts} +24 -1
- package/dist/{chunk-6I55T4OH.cjs → chunk-AGMNSJJH.cjs} +13 -4
- package/dist/chunk-AGMNSJJH.cjs.map +1 -0
- package/dist/{chunk-4O2NYDJG.js → chunk-FNKSXBKB.js} +13 -4
- package/dist/chunk-FNKSXBKB.js.map +1 -0
- package/dist/{chunk-36Z4RIRK.js → chunk-O4AJYUPK.js} +3 -3
- package/dist/{chunk-36Z4RIRK.js.map → chunk-O4AJYUPK.js.map} +1 -1
- package/dist/{chunk-4VSIJLU3.cjs → chunk-PDBDDGG5.cjs} +21 -21
- package/dist/{chunk-4VSIJLU3.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
- package/dist/index.cjs +82 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +71 -12
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +51 -0
- package/docs/dsl.md +14 -2
- package/llms-full.txt +70 -3
- package/package.json +1 -1
- package/dist/Composition-3KHQPRBA.cjs +0 -14
- package/dist/Composition-DT657FO5.js +0 -5
- package/dist/chunk-4O2NYDJG.js.map +0 -1
- package/dist/chunk-6I55T4OH.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkPDBDDGG5_cjs = require('./chunk-PDBDDGG5.cjs');
|
|
4
|
+
var chunkAGMNSJJH_cjs = require('./chunk-AGMNSJJH.cjs');
|
|
5
5
|
var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
|
|
6
6
|
var PIXI = require('pixi.js');
|
|
7
7
|
var gsap = require('gsap');
|
|
@@ -308,6 +308,28 @@ var AUDIO_CODEC_BY_FORMAT = {
|
|
|
308
308
|
webm: "opus",
|
|
309
309
|
mkv: "opus"
|
|
310
310
|
};
|
|
311
|
+
var AUDIO_PREFERENCE = {
|
|
312
|
+
mp4: ["aac", "opus", "mp3", "flac"],
|
|
313
|
+
mov: ["aac", "mp3"],
|
|
314
|
+
webm: ["opus", "vorbis"],
|
|
315
|
+
mkv: ["opus", "vorbis", "aac", "flac", "mp3"]
|
|
316
|
+
};
|
|
317
|
+
async function chooseAudioCodec(fmt, requested, container, audio, bitrate) {
|
|
318
|
+
const supported = container.getSupportedAudioCodecs?.();
|
|
319
|
+
const preferred = AUDIO_PREFERENCE[fmt].filter((c) => !supported || supported.includes(c));
|
|
320
|
+
const canEncode = (list) => mediabunny.getFirstEncodableAudioCodec(list, { numberOfChannels: audio.numberOfChannels ?? 2, sampleRate: audio.sampleRate, bitrate });
|
|
321
|
+
if (requested) {
|
|
322
|
+
if (await canEncode([requested])) return requested;
|
|
323
|
+
const alt = await canEncode(preferred.filter((c) => c !== requested));
|
|
324
|
+
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)."));
|
|
325
|
+
}
|
|
326
|
+
const chosen = await canEncode(preferred);
|
|
327
|
+
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.`);
|
|
328
|
+
if (chosen !== preferred[0]) {
|
|
329
|
+
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.`);
|
|
330
|
+
}
|
|
331
|
+
return chosen;
|
|
332
|
+
}
|
|
311
333
|
var qualityMap = {
|
|
312
334
|
"very-low": mediabunny.QUALITY_VERY_LOW,
|
|
313
335
|
"low": mediabunny.QUALITY_LOW,
|
|
@@ -340,8 +362,10 @@ async function exportFrames(movie, options = {}) {
|
|
|
340
362
|
bitrate: qualityMap[options.audio?.bitrate ?? "high"] ?? mediabunny.QUALITY_HIGH
|
|
341
363
|
}
|
|
342
364
|
};
|
|
365
|
+
const container = makeOutputFormat(opts.format);
|
|
366
|
+
if (movie.audioBuffer) opts.audio.codec = await chooseAudioCodec(fmt, options.audio?.codec, container, movie.audioBuffer, opts.audio.bitrate);
|
|
343
367
|
const output = new mediabunny.Output({
|
|
344
|
-
format:
|
|
368
|
+
format: container,
|
|
345
369
|
target: new mediabunny.BufferTarget()
|
|
346
370
|
});
|
|
347
371
|
const canvasSource = new mediabunny.CanvasSource(movie.app.canvas, {
|
|
@@ -819,16 +843,16 @@ function expandTransitions(spec) {
|
|
|
819
843
|
if (toUses > 1) {
|
|
820
844
|
throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
|
|
821
845
|
}
|
|
822
|
-
const tStart =
|
|
846
|
+
const tStart = chunkAGMNSJJH_cjs.resolveAt(t.at, parentDuration);
|
|
823
847
|
const tEnd = tStart + t.duration;
|
|
824
848
|
const fromSeq = fromEntry.seq;
|
|
825
|
-
const fromStart =
|
|
849
|
+
const fromStart = chunkAGMNSJJH_cjs.resolveAt(fromSeq.at, parentDuration);
|
|
826
850
|
const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
|
|
827
851
|
if (tStart < fromStart || tEnd > fromEnd) {
|
|
828
852
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
|
|
829
853
|
}
|
|
830
854
|
const toSeq = toEntry.seq;
|
|
831
|
-
const toStart =
|
|
855
|
+
const toStart = chunkAGMNSJJH_cjs.resolveAt(toSeq.at, parentDuration);
|
|
832
856
|
const toEnd = toStart + (toSeq.duration ?? parentDuration);
|
|
833
857
|
if (tStart < toStart || tEnd > toEnd) {
|
|
834
858
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
@@ -1168,7 +1192,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1168
1192
|
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
1169
1193
|
if (area === 0) onCanvas = "none";
|
|
1170
1194
|
}
|
|
1171
|
-
const keys =
|
|
1195
|
+
const keys = chunkAGMNSJJH_cjs.collectPropKeys(seq.spec);
|
|
1172
1196
|
const moving = parentMoving || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
1173
1197
|
const scale = t?.scale;
|
|
1174
1198
|
const zero = parentZero || !!scale && (scale.x === 0 || scale.y === 0);
|
|
@@ -1184,7 +1208,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1184
1208
|
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
1185
1209
|
if (group) scene.set(info, group);
|
|
1186
1210
|
if (zero || seq.showsNothingYet) scaledToNothing.add(info);
|
|
1187
|
-
if (seq instanceof
|
|
1211
|
+
if (seq instanceof chunkPDBDDGG5_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
|
|
1188
1212
|
});
|
|
1189
1213
|
};
|
|
1190
1214
|
walk2(root, "", false, true);
|
|
@@ -1244,10 +1268,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
|
|
|
1244
1268
|
const start = parentStart + (seq.at ?? 0);
|
|
1245
1269
|
const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
|
|
1246
1270
|
const span = end - start;
|
|
1247
|
-
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start +
|
|
1271
|
+
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkAGMNSJJH_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
|
|
1248
1272
|
const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
|
|
1249
1273
|
const inner = [];
|
|
1250
|
-
const isComp = seq instanceof
|
|
1274
|
+
const isComp = seq instanceof chunkPDBDDGG5_cjs.CompositionSequence;
|
|
1251
1275
|
if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
|
|
1252
1276
|
entries.push({ row, inner, groupable: !isComp });
|
|
1253
1277
|
});
|
|
@@ -1438,6 +1462,8 @@ var Movie = class {
|
|
|
1438
1462
|
audioSource = null;
|
|
1439
1463
|
gainNode = null;
|
|
1440
1464
|
_cancelAudioStart = null;
|
|
1465
|
+
/** > 0 while the movie moves its own playhead (playback, render, snapshot, contactSheet): those are not the seeks `seeking` / `seeked` report. */
|
|
1466
|
+
_quiet = 0;
|
|
1441
1467
|
_volume = 1;
|
|
1442
1468
|
_muted = false;
|
|
1443
1469
|
isPlaying = false;
|
|
@@ -1531,7 +1557,7 @@ var Movie = class {
|
|
|
1531
1557
|
height: options.composition.height ?? this.height,
|
|
1532
1558
|
duration: options.composition.duration ?? this.duration
|
|
1533
1559
|
} : void 0;
|
|
1534
|
-
await
|
|
1560
|
+
await chunkAGMNSJJH_cjs.ensureFilterLibrary(seededComposition);
|
|
1535
1561
|
const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
|
|
1536
1562
|
const rootSeqSpec = {
|
|
1537
1563
|
type: "composition",
|
|
@@ -1541,7 +1567,7 @@ var Movie = class {
|
|
|
1541
1567
|
...userComposition
|
|
1542
1568
|
};
|
|
1543
1569
|
if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
|
|
1544
|
-
const composition = new
|
|
1570
|
+
const composition = new chunkPDBDDGG5_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
|
|
1545
1571
|
await composition.build();
|
|
1546
1572
|
this._rootSequence = composition;
|
|
1547
1573
|
if (composition.target) root.addChild(composition.target);
|
|
@@ -1565,12 +1591,32 @@ var Movie = class {
|
|
|
1565
1591
|
} catch (cleanupErr) {
|
|
1566
1592
|
console.warn("pixi-effects: cleanup after init failure also threw:", cleanupErr);
|
|
1567
1593
|
}
|
|
1594
|
+
this._emitError("init", err);
|
|
1568
1595
|
throw err;
|
|
1569
1596
|
}
|
|
1570
1597
|
}
|
|
1571
1598
|
async gotoFrame(frame, force = false) {
|
|
1572
1599
|
if (this._initState !== "ready") return;
|
|
1573
1600
|
if (!force && this.currentFrame === frame) return;
|
|
1601
|
+
if (this._quiet > 0) return this._goto(frame);
|
|
1602
|
+
const target = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1603
|
+
this.emit("seeking", { frame: target, totalFrames: this.totalFrames });
|
|
1604
|
+
await this._goto(frame);
|
|
1605
|
+
this.emit("seeked", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1606
|
+
}
|
|
1607
|
+
/** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
|
|
1608
|
+
async _quietly(fn) {
|
|
1609
|
+
this._quiet++;
|
|
1610
|
+
try {
|
|
1611
|
+
return await fn();
|
|
1612
|
+
} finally {
|
|
1613
|
+
this._quiet--;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
_emitError(where, error) {
|
|
1617
|
+
this.emit("error", { where, message: error instanceof Error ? error.message : String(error), error });
|
|
1618
|
+
}
|
|
1619
|
+
async _goto(frame) {
|
|
1574
1620
|
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1575
1621
|
this.timeline.time(this.currentFrame / this.frameRate);
|
|
1576
1622
|
await this._awaitVideoFrames();
|
|
@@ -1592,7 +1638,7 @@ var Movie = class {
|
|
|
1592
1638
|
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1593
1639
|
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1594
1640
|
this._requireReady("snapshot");
|
|
1595
|
-
await this.gotoFrame(clampFrame(frame, this.totalFrames), true);
|
|
1641
|
+
await this._quietly(() => this.gotoFrame(clampFrame(frame, this.totalFrames), true));
|
|
1596
1642
|
const scale = opts.scale ?? 1;
|
|
1597
1643
|
const out = document.createElement("canvas");
|
|
1598
1644
|
out.width = Math.max(1, Math.round(this.width * scale));
|
|
@@ -1616,7 +1662,7 @@ var Movie = class {
|
|
|
1616
1662
|
try {
|
|
1617
1663
|
for (let i = 0; i < frames.length; i++) {
|
|
1618
1664
|
const f = frames[i];
|
|
1619
|
-
await this.gotoFrame(f, true);
|
|
1665
|
+
await this._quietly(() => this.gotoFrame(f, true));
|
|
1620
1666
|
const { x, y } = L.positions[i];
|
|
1621
1667
|
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1622
1668
|
g.fillStyle = "#e8ecf8";
|
|
@@ -1625,7 +1671,7 @@ var Movie = class {
|
|
|
1625
1671
|
g.fillText(`frame ${f} \xB7 ${(f / this.frameRate).toFixed(2)}s`, x + 8, y + LABEL_H / 2);
|
|
1626
1672
|
}
|
|
1627
1673
|
} finally {
|
|
1628
|
-
await this.gotoFrame(back, true);
|
|
1674
|
+
await this._quietly(() => this.gotoFrame(back, true));
|
|
1629
1675
|
}
|
|
1630
1676
|
return encodeCanvas(sheet, "image/png", opts.as ?? "blob");
|
|
1631
1677
|
}
|
|
@@ -1708,17 +1754,20 @@ var Movie = class {
|
|
|
1708
1754
|
const frame = Math.floor(elapsed * this.frameRate);
|
|
1709
1755
|
if (frame <= this.totalFrames) {
|
|
1710
1756
|
inFlight = true;
|
|
1711
|
-
this.
|
|
1757
|
+
this._goto(frame).catch((err) => {
|
|
1712
1758
|
console.warn("pixi-effects: gotoFrame failed during playback:", err);
|
|
1759
|
+
this._emitError("playback", err);
|
|
1713
1760
|
}).finally(() => {
|
|
1714
1761
|
inFlight = false;
|
|
1715
1762
|
});
|
|
1716
1763
|
this._raf = requestAnimationFrame(tick);
|
|
1717
1764
|
} else {
|
|
1718
1765
|
this.pause();
|
|
1719
|
-
this.
|
|
1766
|
+
this._goto(this.totalFrames).catch((err) => {
|
|
1720
1767
|
console.warn("pixi-effects: final gotoFrame failed:", err);
|
|
1768
|
+
this._emitError("playback", err);
|
|
1721
1769
|
});
|
|
1770
|
+
this.emit("ended");
|
|
1722
1771
|
}
|
|
1723
1772
|
};
|
|
1724
1773
|
this._raf = requestAnimationFrame(tick);
|
|
@@ -1762,15 +1811,20 @@ var Movie = class {
|
|
|
1762
1811
|
if (wasPlaying) this.emit("pause");
|
|
1763
1812
|
}
|
|
1764
1813
|
set volume(v) {
|
|
1765
|
-
|
|
1814
|
+
const next = Math.max(0, Math.min(1, v));
|
|
1815
|
+
const changed = next !== this._volume;
|
|
1816
|
+
this._volume = next;
|
|
1766
1817
|
if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
|
|
1818
|
+
if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
|
|
1767
1819
|
}
|
|
1768
1820
|
get volume() {
|
|
1769
1821
|
return this._volume;
|
|
1770
1822
|
}
|
|
1771
1823
|
set muted(v) {
|
|
1824
|
+
const changed = !!v !== this._muted;
|
|
1772
1825
|
this._muted = !!v;
|
|
1773
1826
|
if (this.gainNode) this.gainNode.gain.value = this._muted ? 0 : this._volume;
|
|
1827
|
+
if (changed) this.emit("volumechange", { volume: this._volume, muted: this._muted });
|
|
1774
1828
|
}
|
|
1775
1829
|
get muted() {
|
|
1776
1830
|
return this._muted;
|
|
@@ -1781,7 +1835,12 @@ var Movie = class {
|
|
|
1781
1835
|
}
|
|
1782
1836
|
async render(options) {
|
|
1783
1837
|
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
1784
|
-
|
|
1838
|
+
try {
|
|
1839
|
+
return await this._quietly(() => exportFrames(this, options));
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
this._emitError("render", err);
|
|
1842
|
+
throw err;
|
|
1843
|
+
}
|
|
1785
1844
|
}
|
|
1786
1845
|
async destroy() {
|
|
1787
1846
|
safeRun(() => this.pause());
|
|
@@ -2004,17 +2063,17 @@ function orbit(opts) {
|
|
|
2004
2063
|
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
2005
2064
|
}
|
|
2006
2065
|
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
2007
|
-
const fixedR = opts.radius ??
|
|
2066
|
+
const fixedR = opts.radius ?? chunkAGMNSJJH_cjs.homeDistance(height, fov ?? chunkAGMNSJJH_cjs.DEFAULT_FOV);
|
|
2008
2067
|
const start = opts.start ?? -degrees / 2;
|
|
2009
2068
|
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
2010
2069
|
const easeFn = parseEase(ease);
|
|
2011
2070
|
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
2012
2071
|
const pos = (i) => {
|
|
2013
2072
|
const p = i / steps;
|
|
2014
|
-
const a = (start + degrees * easeFn(p)) *
|
|
2073
|
+
const a = (start + degrees * easeFn(p)) * chunkAGMNSJJH_cjs.DEG;
|
|
2015
2074
|
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
2016
2075
|
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
2017
|
-
const R =
|
|
2076
|
+
const R = chunkAGMNSJJH_cjs.homeDistance(height, f);
|
|
2018
2077
|
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
2019
2078
|
};
|
|
2020
2079
|
const keyframes = [];
|
|
@@ -2063,7 +2122,7 @@ function withFade(spec, opts) {
|
|
|
2063
2122
|
|
|
2064
2123
|
Object.defineProperty(exports, "registerSequenceType", {
|
|
2065
2124
|
enumerable: true,
|
|
2066
|
-
get: function () { return
|
|
2125
|
+
get: function () { return chunkAGMNSJJH_cjs.registerSequenceType; }
|
|
2067
2126
|
});
|
|
2068
2127
|
exports.Movie = Movie;
|
|
2069
2128
|
exports.kenBurns = kenBurns;
|