pixi-effects 0.2.0 → 0.4.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 +55 -0
- package/README.md +16 -6
- package/ai/SKILL.md +46 -0
- package/ai/reference/cheatsheet.md +121 -0
- package/ai/reference/pitfalls.md +75 -0
- package/ai/reference/recipes.md +375 -0
- package/ai/template.html +82 -0
- package/ai/tools/save-image.py +24 -0
- package/dist/{Base-Bh_VSusi.d.ts → Base-5R7C5pHR.d.cts} +7 -1
- package/dist/{Base-B1Tdn0Cv.d.cts → Base-DhY9oMxr.d.ts} +7 -1
- package/dist/Composition-D7RCAGEA.cjs +13 -0
- package/dist/{Composition-BI5HZJFL.cjs.map → Composition-D7RCAGEA.cjs.map} +1 -1
- package/dist/Composition-RGTRZ2JX.js +4 -0
- package/dist/{Composition-IO7ZN32J.js.map → Composition-RGTRZ2JX.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/Movie-CghFXprP.d.cts +179 -0
- package/dist/Movie-DRZtIGU7.d.ts +179 -0
- package/dist/{chunk-7OIWYXGV.cjs → chunk-6BQ6IEW2.cjs} +363 -91
- package/dist/chunk-6BQ6IEW2.cjs.map +1 -0
- package/dist/{chunk-H55V3U56.js → chunk-A574IA4F.js} +68 -13
- package/dist/chunk-A574IA4F.js.map +1 -0
- package/dist/{chunk-64IHCYYN.cjs → chunk-QGBOC5YL.cjs} +76 -21
- package/dist/chunk-QGBOC5YL.cjs.map +1 -0
- package/dist/{chunk-VJCDG6YG.js → chunk-ZQX7WIYL.js} +359 -93
- package/dist/chunk-ZQX7WIYL.js.map +1 -0
- package/dist/index.cjs +366 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -31
- package/dist/index.d.ts +61 -31
- package/dist/index.js +363 -22
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-CNBilhpz.d.cts → types-CRE9WKd4.d.cts} +59 -6
- package/dist/{types-CNBilhpz.d.ts → types-CRE9WKd4.d.ts} +59 -6
- package/docs/api.md +333 -0
- package/docs/dsl.md +1028 -0
- package/llms-full.txt +2001 -0
- package/llms.txt +30 -0
- package/package.json +5 -3
- package/dist/Composition-BI5HZJFL.cjs +0 -13
- package/dist/Composition-IO7ZN32J.js +0 -4
- package/dist/Movie-CcR6h2jO.d.cts +0 -82
- package/dist/Movie-D-n8glA6.d.ts +0 -82
- package/dist/chunk-64IHCYYN.cjs.map +0 -1
- package/dist/chunk-7OIWYXGV.cjs.map +0 -1
- package/dist/chunk-H55V3U56.js.map +0 -1
- package/dist/chunk-VJCDG6YG.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkQGBOC5YL_cjs = require('./chunk-QGBOC5YL.cjs');
|
|
4
|
+
var chunk6BQ6IEW2_cjs = require('./chunk-6BQ6IEW2.cjs');
|
|
5
5
|
var PIXI = require('pixi.js');
|
|
6
6
|
var gsap = require('gsap');
|
|
7
7
|
var PixiPlugin = require('gsap/PixiPlugin');
|
|
@@ -521,6 +521,15 @@ var TransitionMaskFilter = class extends PIXI.Filter {
|
|
|
521
521
|
};
|
|
522
522
|
|
|
523
523
|
// src/core/Transitions.ts
|
|
524
|
+
var windows = /* @__PURE__ */ new WeakMap();
|
|
525
|
+
function transitionWindowsOf(spec) {
|
|
526
|
+
return windows.get(spec) ?? [];
|
|
527
|
+
}
|
|
528
|
+
function carryTransitionWindows(from, to) {
|
|
529
|
+
const w = windows.get(from);
|
|
530
|
+
if (w) windows.set(to, w);
|
|
531
|
+
return to;
|
|
532
|
+
}
|
|
524
533
|
function expandTransitions(spec) {
|
|
525
534
|
const out = {
|
|
526
535
|
...spec,
|
|
@@ -617,43 +626,51 @@ function expandTransitions(spec) {
|
|
|
617
626
|
if (toUses > 1) {
|
|
618
627
|
throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
|
|
619
628
|
}
|
|
620
|
-
const tStart =
|
|
629
|
+
const tStart = chunk6BQ6IEW2_cjs.resolveAt(t.at, parentDuration);
|
|
621
630
|
const tEnd = tStart + t.duration;
|
|
622
631
|
const fromSeq = fromEntry.seq;
|
|
623
|
-
const fromStart =
|
|
632
|
+
const fromStart = chunk6BQ6IEW2_cjs.resolveAt(fromSeq.at, parentDuration);
|
|
624
633
|
const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
|
|
625
634
|
if (tStart < fromStart || tEnd > fromEnd) {
|
|
626
635
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
|
|
627
636
|
}
|
|
628
637
|
const toSeq = toEntry.seq;
|
|
629
|
-
const toStart =
|
|
638
|
+
const toStart = chunk6BQ6IEW2_cjs.resolveAt(toSeq.at, parentDuration);
|
|
630
639
|
const toEnd = toStart + (toSeq.duration ?? parentDuration);
|
|
631
640
|
if (tStart < toStart || tEnd > toEnd) {
|
|
632
641
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
633
642
|
}
|
|
634
|
-
|
|
643
|
+
const record = windows.get(out) ?? [];
|
|
644
|
+
record.push({ from: t.from, to: t.to, start: tStart, end: tEnd });
|
|
645
|
+
windows.set(out, record);
|
|
646
|
+
const abs = { ...t, at: tStart };
|
|
647
|
+
const nFrom = keyframeCount(fromEntry.seq);
|
|
648
|
+
const nTo = keyframeCount(toEntry.seq);
|
|
649
|
+
switch (abs.kind) {
|
|
635
650
|
case "crossfade":
|
|
636
|
-
expandCrossfade(out,
|
|
651
|
+
expandCrossfade(out, abs, fromEntry.seq, toEntry.seq);
|
|
637
652
|
break;
|
|
638
653
|
case "wipe":
|
|
639
|
-
expandMask(out,
|
|
654
|
+
expandMask(out, abs, fromEntry.seq, toEntry.seq, i, wipeMode(abs.direction), abs.smoothing);
|
|
640
655
|
break;
|
|
641
656
|
case "iris":
|
|
642
|
-
expandMask(out,
|
|
657
|
+
expandMask(out, abs, fromEntry.seq, toEntry.seq, i, abs.mode === "out" ? "iris-out" : "iris-in", abs.smoothing);
|
|
643
658
|
break;
|
|
644
659
|
case "slide":
|
|
645
|
-
expandSlide(out,
|
|
660
|
+
expandSlide(out, abs, fromEntry.seq, toEntry.seq);
|
|
646
661
|
break;
|
|
647
662
|
case "dip":
|
|
648
|
-
expandDip(out,
|
|
663
|
+
expandDip(out, abs, fromEntry.seq, toEntry.seq);
|
|
649
664
|
break;
|
|
650
665
|
case "zoom":
|
|
651
|
-
expandZoom(out,
|
|
666
|
+
expandZoom(out, abs, fromEntry.seq, toEntry.seq);
|
|
652
667
|
break;
|
|
653
668
|
case "dissolve":
|
|
654
|
-
expandDissolve(out,
|
|
669
|
+
expandDissolve(out, abs, fromEntry.seq, toEntry.seq, i);
|
|
655
670
|
break;
|
|
656
671
|
}
|
|
672
|
+
rebaseNewKeyframes(fromEntry.seq, nFrom, fromStart);
|
|
673
|
+
rebaseNewKeyframes(toEntry.seq, nTo, toStart);
|
|
657
674
|
}
|
|
658
675
|
delete out.transitions;
|
|
659
676
|
return out;
|
|
@@ -685,6 +702,17 @@ function wrapAsFullComposition(seq, compW, compH) {
|
|
|
685
702
|
sequences: [inner]
|
|
686
703
|
};
|
|
687
704
|
}
|
|
705
|
+
function keyframeCount(seq) {
|
|
706
|
+
return seq.keyframes?.length ?? 0;
|
|
707
|
+
}
|
|
708
|
+
function rebaseNewKeyframes(seq, from, seqStart) {
|
|
709
|
+
const kfs = seq.keyframes;
|
|
710
|
+
if (!kfs) return;
|
|
711
|
+
for (let i = from; i < kfs.length; i++) {
|
|
712
|
+
const kf = kfs[i];
|
|
713
|
+
if (typeof kf.at === "number") kfs[i] = { ...kf, at: kf.at - seqStart };
|
|
714
|
+
}
|
|
715
|
+
}
|
|
688
716
|
function ensureKeyframes(seq) {
|
|
689
717
|
const s = seq;
|
|
690
718
|
if (!s.keyframes) s.keyframes = [];
|
|
@@ -901,6 +929,181 @@ function rejectReservedNameCollision(filters, seqName) {
|
|
|
901
929
|
}
|
|
902
930
|
}
|
|
903
931
|
|
|
932
|
+
// src/core/inspect.ts
|
|
933
|
+
var FAINT = 0.3;
|
|
934
|
+
var intersection = (a, b) => {
|
|
935
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
936
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
937
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
938
|
+
};
|
|
939
|
+
var clip = (a, b) => {
|
|
940
|
+
const x = Math.max(a.x, b.x), y = Math.max(a.y, b.y);
|
|
941
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - x, h = Math.min(a.y + a.height, b.y + b.height) - y;
|
|
942
|
+
return w > 0 && h > 0 ? { x, y, width: w, height: h } : null;
|
|
943
|
+
};
|
|
944
|
+
function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
945
|
+
const layers = [];
|
|
946
|
+
const view = { x: 0, y: 0, width: canvas.width, height: canvas.height };
|
|
947
|
+
const seen = /* @__PURE__ */ new Map();
|
|
948
|
+
const scene = /* @__PURE__ */ new Map();
|
|
949
|
+
const walk = (comp, prefix, insideThreeD, parentVisible, parentScene) => {
|
|
950
|
+
const blending = /* @__PURE__ */ new Set();
|
|
951
|
+
for (const w of transitionWindowsOf(comp.spec)) {
|
|
952
|
+
const start = (comp.absoluteStart ?? 0) + w.start, end = (comp.absoluteStart ?? 0) + w.end;
|
|
953
|
+
if (time >= start - 1e-9 && time <= end + 1e-9) {
|
|
954
|
+
blending.add(w.from);
|
|
955
|
+
blending.add(w.to);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
comp.layers().forEach(({ seq, display, threeD }, i) => {
|
|
959
|
+
const t = seq.target;
|
|
960
|
+
const name = seq.spec.name;
|
|
961
|
+
const label = name ?? `${seq.spec.type}#${i}`;
|
|
962
|
+
const alpha = t?.alpha ?? 1;
|
|
963
|
+
const alive = threeD ? display.visible : t?.renderable ?? true;
|
|
964
|
+
const visible = parentVisible && alive && alpha > 0;
|
|
965
|
+
let bounds = null;
|
|
966
|
+
if (!insideThreeD) {
|
|
967
|
+
const b = display.getBounds();
|
|
968
|
+
bounds = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
969
|
+
}
|
|
970
|
+
let onCanvas = null;
|
|
971
|
+
if (bounds) {
|
|
972
|
+
const inter = intersection(bounds, view);
|
|
973
|
+
const area = bounds.width * bounds.height;
|
|
974
|
+
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
975
|
+
if (area === 0) onCanvas = "none";
|
|
976
|
+
}
|
|
977
|
+
const keys = chunk6BQ6IEW2_cjs.collectPropKeys(seq.spec);
|
|
978
|
+
const moving = (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
979
|
+
const info = { path: prefix + label, name, type: seq.spec.type, threeD, visible, alpha, moving, bounds, onCanvas };
|
|
980
|
+
layers.push(info);
|
|
981
|
+
let shown = bounds ? clip(bounds, view) : null;
|
|
982
|
+
const maskTarget = seq.maskSequence?.target;
|
|
983
|
+
if (shown && maskTarget && !seq.spec.maskInverted) {
|
|
984
|
+
const m = maskTarget.getBounds();
|
|
985
|
+
shown = clip(shown, { x: m.x, y: m.y, width: m.width, height: m.height });
|
|
986
|
+
}
|
|
987
|
+
seen.set(info, shown);
|
|
988
|
+
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
989
|
+
if (group) scene.set(info, group);
|
|
990
|
+
if (seq instanceof chunkQGBOC5YL_cjs.CompositionSequence) walk(seq, prefix + label + "/", insideThreeD || threeD, visible, group);
|
|
991
|
+
});
|
|
992
|
+
};
|
|
993
|
+
walk(root, "", false, true);
|
|
994
|
+
const issues = [];
|
|
995
|
+
const texts = layers.filter((l) => l.type === "text" && l.visible && l.alpha >= FAINT && l.bounds);
|
|
996
|
+
for (const l of texts) {
|
|
997
|
+
const b = l.bounds;
|
|
998
|
+
const who = `text layer "${l.path}"`;
|
|
999
|
+
if (b.width < 1 || b.height < 1) {
|
|
1000
|
+
issues.push(`${who} has no size (empty text, or not drawn yet)`);
|
|
1001
|
+
} else if (l.moving || scene.has(l)) ; else if (l.onCanvas === "none") {
|
|
1002
|
+
issues.push(`${who} is entirely outside the canvas (at ${Math.round(b.x)},${Math.round(b.y)}, ${Math.round(b.width)}\xD7${Math.round(b.height)})`);
|
|
1003
|
+
} else if (l.onCanvas === "partial") {
|
|
1004
|
+
const over = [];
|
|
1005
|
+
if (b.x < 0) over.push(`${Math.round(-b.x)}px beyond the left edge`);
|
|
1006
|
+
if (b.x + b.width > canvas.width) over.push(`${Math.round(b.x + b.width - canvas.width)}px beyond the right edge`);
|
|
1007
|
+
if (b.y < 0) over.push(`${Math.round(-b.y)}px beyond the top edge`);
|
|
1008
|
+
if (b.y + b.height > canvas.height) over.push(`${Math.round(b.y + b.height - canvas.height)}px beyond the bottom edge`);
|
|
1009
|
+
issues.push(`${who} is cut off by the canvas edge: ${over.join(", ")}`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
for (let i = 0; i < texts.length; i++) {
|
|
1013
|
+
for (let j = i + 1; j < texts.length; j++) {
|
|
1014
|
+
const a = seen.get(texts[i]), b = seen.get(texts[j]);
|
|
1015
|
+
if (!a || !b) continue;
|
|
1016
|
+
const sa = scene.get(texts[i]), sb = scene.get(texts[j]);
|
|
1017
|
+
if (sa && sb && sa !== sb) continue;
|
|
1018
|
+
const smaller = Math.min(a.width * a.height, b.width * b.height);
|
|
1019
|
+
if (smaller <= 0) continue;
|
|
1020
|
+
const share = intersection(a, b) / smaller;
|
|
1021
|
+
if (share > 0.25) issues.push(`text layers "${texts[i].path}" and "${texts[j].path}" overlap by ${Math.round(share * 100)}% of the smaller one`);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
const mode = opts.layers ?? "all";
|
|
1025
|
+
const listed = mode === "none" ? [] : mode === "visible" ? layers.filter((l) => l.visible) : layers;
|
|
1026
|
+
return { frame, time, canvas, summary: { layers: layers.length, visible: layers.filter((l) => l.visible).length }, issues, layers: listed };
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/core/frames.ts
|
|
1030
|
+
function pickFrames(lastFrame, count) {
|
|
1031
|
+
const last = Math.max(0, Math.floor(lastFrame));
|
|
1032
|
+
const n = Math.max(1, Math.floor(count));
|
|
1033
|
+
if (n === 1) return [0];
|
|
1034
|
+
const out = [];
|
|
1035
|
+
for (let i = 0; i < n; i++) {
|
|
1036
|
+
const f = Math.round(last * i / (n - 1));
|
|
1037
|
+
if (out[out.length - 1] !== f) out.push(f);
|
|
1038
|
+
}
|
|
1039
|
+
return out;
|
|
1040
|
+
}
|
|
1041
|
+
function sheetLayout(n, columns, cellW, srcW, srcH, labelH) {
|
|
1042
|
+
const cols = Math.max(1, Math.min(Math.floor(columns), n));
|
|
1043
|
+
const rows = Math.ceil(n / cols);
|
|
1044
|
+
const cellH = Math.round(cellW * srcH / srcW);
|
|
1045
|
+
const positions = Array.from({ length: n }, (_, i) => ({ x: i % cols * cellW, y: Math.floor(i / cols) * (cellH + labelH) }));
|
|
1046
|
+
return { cols, rows, cellW, cellH, width: cols * cellW, height: rows * (cellH + labelH), positions };
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/core/options.ts
|
|
1050
|
+
function distance(a, b) {
|
|
1051
|
+
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
1052
|
+
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
|
1053
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1054
|
+
for (let j = 1; j <= b.length; j++) {
|
|
1055
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
1056
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return dp[a.length][b.length];
|
|
1060
|
+
}
|
|
1061
|
+
var isSubsequence = (small, big) => {
|
|
1062
|
+
let i = 0;
|
|
1063
|
+
for (const ch of big) if (i < small.length && ch === small[i]) i++;
|
|
1064
|
+
return i === small.length;
|
|
1065
|
+
};
|
|
1066
|
+
var COMMON_ALIASES = {
|
|
1067
|
+
fps: "frameRate",
|
|
1068
|
+
framerate: "frameRate",
|
|
1069
|
+
frame_rate: "frameRate",
|
|
1070
|
+
bg: "background",
|
|
1071
|
+
backgroundcolor: "background",
|
|
1072
|
+
bgcolor: "background",
|
|
1073
|
+
seconds: "duration",
|
|
1074
|
+
length: "duration",
|
|
1075
|
+
time: "duration",
|
|
1076
|
+
w: "width",
|
|
1077
|
+
h: "height",
|
|
1078
|
+
cols: "columns",
|
|
1079
|
+
sweep: "degrees",
|
|
1080
|
+
angle: "degrees"
|
|
1081
|
+
};
|
|
1082
|
+
function suggestName(name, valid) {
|
|
1083
|
+
const n = name.toLowerCase();
|
|
1084
|
+
const alias = COMMON_ALIASES[n];
|
|
1085
|
+
if (alias && valid.includes(alias)) return alias;
|
|
1086
|
+
let best = null;
|
|
1087
|
+
for (const v of valid) {
|
|
1088
|
+
const lv = v.toLowerCase();
|
|
1089
|
+
const d = distance(n, lv);
|
|
1090
|
+
const close = d <= Math.max(1, Math.floor(Math.min(n.length, lv.length) / 3));
|
|
1091
|
+
const abbreviation = n.length >= 3 && isSubsequence(n, lv) || lv.length >= 2 && n.length > lv.length && n.includes(lv);
|
|
1092
|
+
if (!close && !abbreviation) continue;
|
|
1093
|
+
const score = close ? d : 3 + (lv.length - n.length) / 100;
|
|
1094
|
+
if (!best || score < best.score) best = { v, score };
|
|
1095
|
+
}
|
|
1096
|
+
return best ? best.v : null;
|
|
1097
|
+
}
|
|
1098
|
+
function warnUnknownOptions(where, opts, valid) {
|
|
1099
|
+
if (!opts || typeof opts !== "object") return;
|
|
1100
|
+
for (const key of Object.keys(opts)) {
|
|
1101
|
+
if (valid.includes(key)) continue;
|
|
1102
|
+
const guess = suggestName(key, valid);
|
|
1103
|
+
console.warn(`pixi-effects: ${where}: unknown option "${key}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${valid.join(", ")})`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
904
1107
|
// src/core/Movie.ts
|
|
905
1108
|
PIXI.extensions.add(PIXI.CullerPlugin);
|
|
906
1109
|
gsap.gsap.registerPlugin(PixiPlugin.PixiPlugin);
|
|
@@ -959,6 +1162,7 @@ var Movie = class {
|
|
|
959
1162
|
return this._audioContext;
|
|
960
1163
|
}
|
|
961
1164
|
async init(options = {}) {
|
|
1165
|
+
warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition"]);
|
|
962
1166
|
this._initState = "pending";
|
|
963
1167
|
try {
|
|
964
1168
|
this.width = options.width ?? 1920;
|
|
@@ -1010,7 +1214,8 @@ var Movie = class {
|
|
|
1010
1214
|
duration: this.duration,
|
|
1011
1215
|
...userComposition
|
|
1012
1216
|
};
|
|
1013
|
-
|
|
1217
|
+
if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
|
|
1218
|
+
const composition = new chunkQGBOC5YL_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
|
|
1014
1219
|
await composition.build();
|
|
1015
1220
|
this._rootSequence = composition;
|
|
1016
1221
|
if (composition.target) root.addChild(composition.target);
|
|
@@ -1023,7 +1228,7 @@ var Movie = class {
|
|
|
1023
1228
|
this.timeline.progress(1).progress(0);
|
|
1024
1229
|
await this._awaitVideoFrames();
|
|
1025
1230
|
this._updateSpace();
|
|
1026
|
-
this.
|
|
1231
|
+
this._renderNow();
|
|
1027
1232
|
this._initState = "ready";
|
|
1028
1233
|
this.emit("ready");
|
|
1029
1234
|
} catch (err) {
|
|
@@ -1042,7 +1247,7 @@ var Movie = class {
|
|
|
1042
1247
|
this.timeline.time(this.currentFrame / this.frameRate);
|
|
1043
1248
|
await this._awaitVideoFrames();
|
|
1044
1249
|
this._updateSpace();
|
|
1045
|
-
this.
|
|
1250
|
+
this._renderNow();
|
|
1046
1251
|
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1047
1252
|
}
|
|
1048
1253
|
async _awaitVideoFrames() {
|
|
@@ -1056,6 +1261,76 @@ var Movie = class {
|
|
|
1056
1261
|
return v.awaitFrameAt(local);
|
|
1057
1262
|
}));
|
|
1058
1263
|
}
|
|
1264
|
+
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1265
|
+
warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1266
|
+
this._requireReady("snapshot");
|
|
1267
|
+
await this.gotoFrame(clampFrame(frame, this.totalFrames), true);
|
|
1268
|
+
const scale = opts.scale ?? 1;
|
|
1269
|
+
const out = document.createElement("canvas");
|
|
1270
|
+
out.width = Math.max(1, Math.round(this.width * scale));
|
|
1271
|
+
out.height = Math.max(1, Math.round(this.height * scale));
|
|
1272
|
+
out.getContext("2d").drawImage(this.app.canvas, 0, 0, out.width, out.height);
|
|
1273
|
+
return encodeCanvas(out, opts.type ?? "image/png", opts.as ?? "blob");
|
|
1274
|
+
}
|
|
1275
|
+
async contactSheet(opts = {}) {
|
|
1276
|
+
warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as"]);
|
|
1277
|
+
this._requireReady("contactSheet");
|
|
1278
|
+
const frames = (opts.frames ?? opts.times?.map((t) => Math.round(t * this.frameRate)) ?? pickFrames(this.totalFrames, opts.count ?? 6)).map((f) => clampFrame(f, this.totalFrames));
|
|
1279
|
+
const LABEL_H = 26;
|
|
1280
|
+
const L = sheetLayout(frames.length, opts.columns ?? 3, opts.cellWidth ?? 480, this.width, this.height, LABEL_H);
|
|
1281
|
+
const sheet = document.createElement("canvas");
|
|
1282
|
+
sheet.width = L.width;
|
|
1283
|
+
sheet.height = L.height;
|
|
1284
|
+
const g = sheet.getContext("2d");
|
|
1285
|
+
g.fillStyle = "#10131c";
|
|
1286
|
+
g.fillRect(0, 0, L.width, L.height);
|
|
1287
|
+
const back = this.currentFrame;
|
|
1288
|
+
try {
|
|
1289
|
+
for (let i = 0; i < frames.length; i++) {
|
|
1290
|
+
const f = frames[i];
|
|
1291
|
+
await this.gotoFrame(f, true);
|
|
1292
|
+
const { x, y } = L.positions[i];
|
|
1293
|
+
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1294
|
+
g.fillStyle = "#e8ecf8";
|
|
1295
|
+
g.font = "600 15px system-ui, sans-serif";
|
|
1296
|
+
g.textBaseline = "middle";
|
|
1297
|
+
g.fillText(`frame ${f} \xB7 ${(f / this.frameRate).toFixed(2)}s`, x + 8, y + LABEL_H / 2);
|
|
1298
|
+
}
|
|
1299
|
+
} finally {
|
|
1300
|
+
await this.gotoFrame(back, true);
|
|
1301
|
+
}
|
|
1302
|
+
return encodeCanvas(sheet, "image/png", opts.as ?? "blob");
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Where every layer is drawn at `frame` (canvas pixels, visibility) plus `issues` — text that is off
|
|
1306
|
+
* the canvas, cut by an edge, empty, or overlapping other text. Seeks there and stays there.
|
|
1307
|
+
*/
|
|
1308
|
+
async inspect(frame = this.currentFrame, opts = { layers: "visible" }) {
|
|
1309
|
+
warnUnknownOptions("movie.inspect()", opts, ["layers"]);
|
|
1310
|
+
this._requireReady("inspect");
|
|
1311
|
+
const f = clampFrame(frame, this.totalFrames);
|
|
1312
|
+
await this.gotoFrame(f, true);
|
|
1313
|
+
return inspectScene(this._rootSequence, f, f / this.frameRate, { width: this.width, height: this.height }, opts);
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Draw the stage now. PixiJS's CullerPlugin only culls inside `app.render()` (the ticker), and we render
|
|
1317
|
+
* by hand — while seeking and especially during `render()`, which stops the ticker — so without this the
|
|
1318
|
+
* `culled` flags are whatever the last tick left: a layer that is now on screen can stay culled and be
|
|
1319
|
+
* missing from the frame or from the exported video. Cull again (updating transforms: the timeline just
|
|
1320
|
+
* moved things) right before drawing.
|
|
1321
|
+
*/
|
|
1322
|
+
_renderNow() {
|
|
1323
|
+
const app = this.app;
|
|
1324
|
+
if (!app?.renderer) return;
|
|
1325
|
+
this._rootSequence?.syncFrame();
|
|
1326
|
+
PIXI.Culler.shared.cull(app.stage, app.renderer.screen, false);
|
|
1327
|
+
app.renderer.render({ container: app.stage });
|
|
1328
|
+
}
|
|
1329
|
+
_requireReady(what) {
|
|
1330
|
+
if (this._initState !== "ready" || !this._rootSequence || !this.app) {
|
|
1331
|
+
throw new Error(`pixi-effects: movie.${what}() needs a ready movie \u2014 await movie.init(...) first`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1059
1334
|
/** Projects every `threeD` layer for the current frame (see src/space). */
|
|
1060
1335
|
_updateSpace() {
|
|
1061
1336
|
if (!this._rootSequence || !this.app) return;
|
|
@@ -1141,6 +1416,7 @@ var Movie = class {
|
|
|
1141
1416
|
return this.muted;
|
|
1142
1417
|
}
|
|
1143
1418
|
async render(options) {
|
|
1419
|
+
warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
1144
1420
|
return await exportFrames(this, options);
|
|
1145
1421
|
}
|
|
1146
1422
|
async destroy() {
|
|
@@ -1158,6 +1434,11 @@ var Movie = class {
|
|
|
1158
1434
|
this._initState = "destroyed";
|
|
1159
1435
|
}
|
|
1160
1436
|
};
|
|
1437
|
+
var clampFrame = (f, last) => Math.max(0, Math.min(Math.round(f), last));
|
|
1438
|
+
function encodeCanvas(canvas, type, as) {
|
|
1439
|
+
if (as === "dataURL") return Promise.resolve(canvas.toDataURL(type));
|
|
1440
|
+
return new Promise((resolve, reject) => canvas.toBlob((b) => b ? resolve(b) : reject(new Error("pixi-effects: could not encode the image")), type));
|
|
1441
|
+
}
|
|
1161
1442
|
function safeRun(fn) {
|
|
1162
1443
|
try {
|
|
1163
1444
|
fn();
|
|
@@ -1176,7 +1457,9 @@ function collectVideoSequences(seq, out) {
|
|
|
1176
1457
|
}
|
|
1177
1458
|
|
|
1178
1459
|
// src/presets/kenBurns.ts
|
|
1460
|
+
var KEN_BURNS_KEYS = ["asset", "name", "at", "duration", "fit", "ease", "motion", "origin", "zoom", "direction", "angle", "from", "to"];
|
|
1179
1461
|
function kenBurns(opts) {
|
|
1462
|
+
warnUnknownOptions("kenBurns()", opts, KEN_BURNS_KEYS);
|
|
1180
1463
|
const {
|
|
1181
1464
|
asset,
|
|
1182
1465
|
name,
|
|
@@ -1186,7 +1469,6 @@ function kenBurns(opts) {
|
|
|
1186
1469
|
ease = "sine.inOut"
|
|
1187
1470
|
} = opts;
|
|
1188
1471
|
const baseScale = fit;
|
|
1189
|
-
const baseAt = at ?? 0;
|
|
1190
1472
|
let initial;
|
|
1191
1473
|
let keyframes;
|
|
1192
1474
|
if (opts.motion === "still") {
|
|
@@ -1217,7 +1499,7 @@ function kenBurns(opts) {
|
|
|
1217
1499
|
scale: initialScale
|
|
1218
1500
|
};
|
|
1219
1501
|
keyframes = [
|
|
1220
|
-
{ at:
|
|
1502
|
+
{ at: 0, to: { scale: `${baseScale} * ${toZ}` }, duration, ease }
|
|
1221
1503
|
];
|
|
1222
1504
|
} else if (opts.motion === "rotation") {
|
|
1223
1505
|
const angle = clamp(opts.angle ?? 8, 0, 30);
|
|
@@ -1248,7 +1530,7 @@ function kenBurns(opts) {
|
|
|
1248
1530
|
const tHere = duration * tFrac;
|
|
1249
1531
|
const tPrev = duration * ((i - 1) / (sampleCount - 1));
|
|
1250
1532
|
keyframes.push({
|
|
1251
|
-
at:
|
|
1533
|
+
at: tPrev,
|
|
1252
1534
|
to: { rotation: angleHere, scale: scaleAt(angleHere) },
|
|
1253
1535
|
duration: tHere - tPrev,
|
|
1254
1536
|
ease: i === 1 ? ease : "none"
|
|
@@ -1269,7 +1551,7 @@ function kenBurns(opts) {
|
|
|
1269
1551
|
scale: scaleExpr
|
|
1270
1552
|
};
|
|
1271
1553
|
keyframes = [
|
|
1272
|
-
{ at:
|
|
1554
|
+
{ at: 0, to: { x: xExpr(to[0]), y: yExpr(to[1]) }, duration, ease }
|
|
1273
1555
|
];
|
|
1274
1556
|
}
|
|
1275
1557
|
const spec = {
|
|
@@ -1286,13 +1568,72 @@ function kenBurns(opts) {
|
|
|
1286
1568
|
function clamp(v, lo, hi) {
|
|
1287
1569
|
return Math.max(lo, Math.min(hi, v));
|
|
1288
1570
|
}
|
|
1571
|
+
function parseEase(name) {
|
|
1572
|
+
return gsap.gsap.parseEase(name);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// src/presets/orbit.ts
|
|
1576
|
+
var ORBIT_KEYS = ["duration", "degrees", "start", "radius", "center", "width", "height", "fov", "ease", "dollyZoom", "at", "name", "stepsPerSecond"];
|
|
1577
|
+
function orbit(opts) {
|
|
1578
|
+
warnUnknownOptions("orbit()", opts, ORBIT_KEYS);
|
|
1579
|
+
const {
|
|
1580
|
+
duration,
|
|
1581
|
+
degrees,
|
|
1582
|
+
width = 1280,
|
|
1583
|
+
height = 720,
|
|
1584
|
+
fov,
|
|
1585
|
+
ease = "sine.inOut",
|
|
1586
|
+
stepsPerSecond = 10
|
|
1587
|
+
} = opts;
|
|
1588
|
+
if (!(duration > 0)) throw new Error(`pixi-effects: orbit duration must be > 0 (got ${duration})`);
|
|
1589
|
+
const dolly = opts.dollyZoom;
|
|
1590
|
+
if (dolly && opts.radius !== void 0) {
|
|
1591
|
+
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
1592
|
+
}
|
|
1593
|
+
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
1594
|
+
const fixedR = opts.radius ?? chunk6BQ6IEW2_cjs.homeDistance(height, fov ?? chunk6BQ6IEW2_cjs.DEFAULT_FOV);
|
|
1595
|
+
const start = opts.start ?? -degrees / 2;
|
|
1596
|
+
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
1597
|
+
const easeFn = parseEase(ease);
|
|
1598
|
+
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
1599
|
+
const pos = (i) => {
|
|
1600
|
+
const p = i / steps;
|
|
1601
|
+
const a = (start + degrees * easeFn(p)) * chunk6BQ6IEW2_cjs.DEG;
|
|
1602
|
+
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
1603
|
+
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
1604
|
+
const R = chunk6BQ6IEW2_cjs.homeDistance(height, f);
|
|
1605
|
+
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
1606
|
+
};
|
|
1607
|
+
const keyframes = [];
|
|
1608
|
+
for (let i = 0; i < steps; i++) {
|
|
1609
|
+
keyframes.push({ at: duration * i / steps, to: pos(i + 1), duration: duration / steps, ease: "none" });
|
|
1610
|
+
}
|
|
1611
|
+
const p0 = pos(0);
|
|
1612
|
+
const spec = {
|
|
1613
|
+
type: "camera",
|
|
1614
|
+
duration,
|
|
1615
|
+
initial: {
|
|
1616
|
+
x: p0.x,
|
|
1617
|
+
y: cy,
|
|
1618
|
+
z: p0.z,
|
|
1619
|
+
lookAtX: cx,
|
|
1620
|
+
lookAtY: cy,
|
|
1621
|
+
lookAtZ: 0,
|
|
1622
|
+
...p0.fov !== void 0 ? { fov: p0.fov } : fov !== void 0 ? { fov } : {}
|
|
1623
|
+
},
|
|
1624
|
+
keyframes
|
|
1625
|
+
};
|
|
1626
|
+
if (opts.at !== void 0) spec.at = opts.at;
|
|
1627
|
+
if (opts.name !== void 0) spec.name = opts.name;
|
|
1628
|
+
return spec;
|
|
1629
|
+
}
|
|
1289
1630
|
|
|
1290
1631
|
// src/transforms/withFade.ts
|
|
1291
1632
|
function withFade(spec, opts) {
|
|
1292
|
-
|
|
1633
|
+
warnUnknownOptions("withFade()", opts, ["in", "out"]);
|
|
1293
1634
|
if (opts.in != null && opts.in > 0) {
|
|
1294
1635
|
spec.initial = { ...spec.initial ?? {}, alpha: 0 };
|
|
1295
|
-
const kf = { at, to: { alpha: 1 }, duration: opts.in };
|
|
1636
|
+
const kf = { at: 0, to: { alpha: 1 }, duration: opts.in };
|
|
1296
1637
|
spec.keyframes = [...spec.keyframes ?? [], kf];
|
|
1297
1638
|
}
|
|
1298
1639
|
if (opts.out != null && opts.out > 0) {
|
|
@@ -1301,7 +1642,7 @@ function withFade(spec, opts) {
|
|
|
1301
1642
|
"withFade(spec, { out }) requires spec.duration to be set so the fade-out can be anchored to the sequence end. Set spec.duration before passing the spec in."
|
|
1302
1643
|
);
|
|
1303
1644
|
}
|
|
1304
|
-
const kf = { at:
|
|
1645
|
+
const kf = { at: spec.duration - opts.out, to: { alpha: 0 }, duration: opts.out };
|
|
1305
1646
|
spec.keyframes = [...spec.keyframes ?? [], kf];
|
|
1306
1647
|
}
|
|
1307
1648
|
return spec;
|
|
@@ -1309,10 +1650,11 @@ function withFade(spec, opts) {
|
|
|
1309
1650
|
|
|
1310
1651
|
Object.defineProperty(exports, "registerSequenceType", {
|
|
1311
1652
|
enumerable: true,
|
|
1312
|
-
get: function () { return
|
|
1653
|
+
get: function () { return chunk6BQ6IEW2_cjs.registerSequenceType; }
|
|
1313
1654
|
});
|
|
1314
1655
|
exports.Movie = Movie;
|
|
1315
1656
|
exports.kenBurns = kenBurns;
|
|
1657
|
+
exports.orbit = orbit;
|
|
1316
1658
|
exports.withFade = withFade;
|
|
1317
1659
|
//# sourceMappingURL=index.cjs.map
|
|
1318
1660
|
//# sourceMappingURL=index.cjs.map
|