pixi-effects 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +15 -5
- package/ai/SKILL.md +44 -0
- package/ai/reference/cheatsheet.md +120 -0
- package/ai/reference/pitfalls.md +54 -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-B1Tdn0Cv.d.cts → Base-BDw6dKRB.d.cts} +1 -1
- package/dist/{Base-Bh_VSusi.d.ts → Base-Ba4Ta4ap.d.ts} +1 -1
- package/dist/Composition-6FDH5OPM.cjs +13 -0
- package/dist/{Composition-BI5HZJFL.cjs.map → Composition-6FDH5OPM.cjs.map} +1 -1
- package/dist/Composition-FEYAUMFI.js +4 -0
- package/dist/{Composition-IO7ZN32J.js.map → Composition-FEYAUMFI.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/Movie-DMpaT67V.d.ts +179 -0
- package/dist/Movie-W8ISiEBB.d.cts +179 -0
- package/dist/{chunk-H55V3U56.js → chunk-DIJG2RSF.js} +28 -11
- package/dist/chunk-DIJG2RSF.js.map +1 -0
- package/dist/{chunk-VJCDG6YG.js → chunk-PN5A6QA7.js} +248 -64
- package/dist/chunk-PN5A6QA7.js.map +1 -0
- package/dist/{chunk-7OIWYXGV.cjs → chunk-SIRVULFF.cjs} +251 -62
- package/dist/chunk-SIRVULFF.cjs.map +1 -0
- package/dist/{chunk-64IHCYYN.cjs → chunk-ZL262ZRM.cjs} +37 -20
- package/dist/chunk-ZL262ZRM.cjs.map +1 -0
- package/dist/index.cjs +324 -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 +321 -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-3c8Vymgw.d.cts} +53 -6
- package/dist/{types-CNBilhpz.d.ts → types-3c8Vymgw.d.ts} +53 -6
- package/docs/api.md +333 -0
- package/docs/dsl.md +1017 -0
- package/llms-full.txt +1966 -0
- package/llms.txt +30 -0
- package/package.json +4 -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.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CompositionSequence } from './chunk-
|
|
2
|
-
import { resolveAt } from './chunk-
|
|
3
|
-
export { registerSequenceType } from './chunk-
|
|
1
|
+
import { CompositionSequence } from './chunk-DIJG2RSF.js';
|
|
2
|
+
import { homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-PN5A6QA7.js';
|
|
3
|
+
export { registerSequenceType } from './chunk-PN5A6QA7.js';
|
|
4
4
|
import * as PIXI from 'pixi.js';
|
|
5
|
-
import { extensions, CullerPlugin, Application, Container, Rectangle, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
|
|
5
|
+
import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
|
|
6
6
|
import { gsap } from 'gsap';
|
|
7
7
|
import { PixiPlugin } from 'gsap/PixiPlugin';
|
|
8
8
|
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';
|
|
@@ -611,29 +611,34 @@ function expandTransitions(spec) {
|
|
|
611
611
|
if (tStart < toStart || tEnd > toEnd) {
|
|
612
612
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
613
613
|
}
|
|
614
|
-
|
|
614
|
+
const abs = { ...t, at: tStart };
|
|
615
|
+
const nFrom = keyframeCount(fromEntry.seq);
|
|
616
|
+
const nTo = keyframeCount(toEntry.seq);
|
|
617
|
+
switch (abs.kind) {
|
|
615
618
|
case "crossfade":
|
|
616
|
-
expandCrossfade(out,
|
|
619
|
+
expandCrossfade(out, abs, fromEntry.seq, toEntry.seq);
|
|
617
620
|
break;
|
|
618
621
|
case "wipe":
|
|
619
|
-
expandMask(out,
|
|
622
|
+
expandMask(out, abs, fromEntry.seq, toEntry.seq, i, wipeMode(abs.direction), abs.smoothing);
|
|
620
623
|
break;
|
|
621
624
|
case "iris":
|
|
622
|
-
expandMask(out,
|
|
625
|
+
expandMask(out, abs, fromEntry.seq, toEntry.seq, i, abs.mode === "out" ? "iris-out" : "iris-in", abs.smoothing);
|
|
623
626
|
break;
|
|
624
627
|
case "slide":
|
|
625
|
-
expandSlide(out,
|
|
628
|
+
expandSlide(out, abs, fromEntry.seq, toEntry.seq);
|
|
626
629
|
break;
|
|
627
630
|
case "dip":
|
|
628
|
-
expandDip(out,
|
|
631
|
+
expandDip(out, abs, fromEntry.seq, toEntry.seq);
|
|
629
632
|
break;
|
|
630
633
|
case "zoom":
|
|
631
|
-
expandZoom(out,
|
|
634
|
+
expandZoom(out, abs, fromEntry.seq, toEntry.seq);
|
|
632
635
|
break;
|
|
633
636
|
case "dissolve":
|
|
634
|
-
expandDissolve(out,
|
|
637
|
+
expandDissolve(out, abs, fromEntry.seq, toEntry.seq, i);
|
|
635
638
|
break;
|
|
636
639
|
}
|
|
640
|
+
rebaseNewKeyframes(fromEntry.seq, nFrom, fromStart);
|
|
641
|
+
rebaseNewKeyframes(toEntry.seq, nTo, toStart);
|
|
637
642
|
}
|
|
638
643
|
delete out.transitions;
|
|
639
644
|
return out;
|
|
@@ -665,6 +670,17 @@ function wrapAsFullComposition(seq, compW, compH) {
|
|
|
665
670
|
sequences: [inner]
|
|
666
671
|
};
|
|
667
672
|
}
|
|
673
|
+
function keyframeCount(seq) {
|
|
674
|
+
return seq.keyframes?.length ?? 0;
|
|
675
|
+
}
|
|
676
|
+
function rebaseNewKeyframes(seq, from, seqStart) {
|
|
677
|
+
const kfs = seq.keyframes;
|
|
678
|
+
if (!kfs) return;
|
|
679
|
+
for (let i = from; i < kfs.length; i++) {
|
|
680
|
+
const kf = kfs[i];
|
|
681
|
+
if (typeof kf.at === "number") kfs[i] = { ...kf, at: kf.at - seqStart };
|
|
682
|
+
}
|
|
683
|
+
}
|
|
668
684
|
function ensureKeyframes(seq) {
|
|
669
685
|
const s = seq;
|
|
670
686
|
if (!s.keyframes) s.keyframes = [];
|
|
@@ -881,6 +897,153 @@ function rejectReservedNameCollision(filters, seqName) {
|
|
|
881
897
|
}
|
|
882
898
|
}
|
|
883
899
|
|
|
900
|
+
// src/core/inspect.ts
|
|
901
|
+
var FAINT = 0.3;
|
|
902
|
+
var intersection = (a, b) => {
|
|
903
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
904
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
905
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
906
|
+
};
|
|
907
|
+
function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
908
|
+
const layers = [];
|
|
909
|
+
const view = { x: 0, y: 0, width: canvas.width, height: canvas.height };
|
|
910
|
+
const walk = (comp, prefix, insideThreeD, parentVisible) => {
|
|
911
|
+
comp.layers().forEach(({ seq, display, threeD }, i) => {
|
|
912
|
+
const t = seq.target;
|
|
913
|
+
const name = seq.spec.name;
|
|
914
|
+
const label = name ?? `${seq.spec.type}#${i}`;
|
|
915
|
+
const alpha = t?.alpha ?? 1;
|
|
916
|
+
const alive = threeD ? display.visible : t?.renderable ?? true;
|
|
917
|
+
const visible = parentVisible && alive && alpha > 0;
|
|
918
|
+
let bounds = null;
|
|
919
|
+
if (!insideThreeD) {
|
|
920
|
+
const b = display.getBounds();
|
|
921
|
+
bounds = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
922
|
+
}
|
|
923
|
+
let onCanvas = null;
|
|
924
|
+
if (bounds) {
|
|
925
|
+
const inter = intersection(bounds, view);
|
|
926
|
+
const area = bounds.width * bounds.height;
|
|
927
|
+
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
928
|
+
if (area === 0) onCanvas = "none";
|
|
929
|
+
}
|
|
930
|
+
const keys = collectPropKeys(seq.spec);
|
|
931
|
+
const moving = (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
932
|
+
layers.push({ path: prefix + label, name, type: seq.spec.type, threeD, visible, alpha, moving, bounds, onCanvas });
|
|
933
|
+
if (seq instanceof CompositionSequence) walk(seq, prefix + label + "/", insideThreeD || threeD, visible);
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
walk(root, "", false, true);
|
|
937
|
+
const issues = [];
|
|
938
|
+
const texts = layers.filter((l) => l.type === "text" && l.visible && l.alpha >= FAINT && l.bounds);
|
|
939
|
+
for (const l of texts) {
|
|
940
|
+
const b = l.bounds;
|
|
941
|
+
const who = `text layer "${l.path}"`;
|
|
942
|
+
if (b.width < 1 || b.height < 1) {
|
|
943
|
+
issues.push(`${who} has no size (empty text, or not drawn yet)`);
|
|
944
|
+
} else if (l.moving) ; else if (l.onCanvas === "none") {
|
|
945
|
+
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)})`);
|
|
946
|
+
} else if (l.onCanvas === "partial") {
|
|
947
|
+
const over = [];
|
|
948
|
+
if (b.x < 0) over.push(`${Math.round(-b.x)}px beyond the left edge`);
|
|
949
|
+
if (b.x + b.width > canvas.width) over.push(`${Math.round(b.x + b.width - canvas.width)}px beyond the right edge`);
|
|
950
|
+
if (b.y < 0) over.push(`${Math.round(-b.y)}px beyond the top edge`);
|
|
951
|
+
if (b.y + b.height > canvas.height) over.push(`${Math.round(b.y + b.height - canvas.height)}px beyond the bottom edge`);
|
|
952
|
+
issues.push(`${who} is cut off by the canvas edge: ${over.join(", ")}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (let i = 0; i < texts.length; i++) {
|
|
956
|
+
for (let j = i + 1; j < texts.length; j++) {
|
|
957
|
+
const a = texts[i].bounds, b = texts[j].bounds;
|
|
958
|
+
const smaller = Math.min(a.width * a.height, b.width * b.height);
|
|
959
|
+
if (smaller <= 0) continue;
|
|
960
|
+
const share = intersection(a, b) / smaller;
|
|
961
|
+
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`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const mode = opts.layers ?? "all";
|
|
965
|
+
const listed = mode === "none" ? [] : mode === "visible" ? layers.filter((l) => l.visible) : layers;
|
|
966
|
+
return { frame, time, canvas, summary: { layers: layers.length, visible: layers.filter((l) => l.visible).length }, issues, layers: listed };
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/core/frames.ts
|
|
970
|
+
function pickFrames(lastFrame, count) {
|
|
971
|
+
const last = Math.max(0, Math.floor(lastFrame));
|
|
972
|
+
const n = Math.max(1, Math.floor(count));
|
|
973
|
+
if (n === 1) return [0];
|
|
974
|
+
const out = [];
|
|
975
|
+
for (let i = 0; i < n; i++) {
|
|
976
|
+
const f = Math.round(last * i / (n - 1));
|
|
977
|
+
if (out[out.length - 1] !== f) out.push(f);
|
|
978
|
+
}
|
|
979
|
+
return out;
|
|
980
|
+
}
|
|
981
|
+
function sheetLayout(n, columns, cellW, srcW, srcH, labelH) {
|
|
982
|
+
const cols = Math.max(1, Math.min(Math.floor(columns), n));
|
|
983
|
+
const rows = Math.ceil(n / cols);
|
|
984
|
+
const cellH = Math.round(cellW * srcH / srcW);
|
|
985
|
+
const positions = Array.from({ length: n }, (_, i) => ({ x: i % cols * cellW, y: Math.floor(i / cols) * (cellH + labelH) }));
|
|
986
|
+
return { cols, rows, cellW, cellH, width: cols * cellW, height: rows * (cellH + labelH), positions };
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/core/options.ts
|
|
990
|
+
function distance(a, b) {
|
|
991
|
+
const dp = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
992
|
+
for (let j = 1; j <= b.length; j++) dp[0][j] = j;
|
|
993
|
+
for (let i = 1; i <= a.length; i++) {
|
|
994
|
+
for (let j = 1; j <= b.length; j++) {
|
|
995
|
+
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));
|
|
996
|
+
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);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return dp[a.length][b.length];
|
|
1000
|
+
}
|
|
1001
|
+
var isSubsequence = (small, big) => {
|
|
1002
|
+
let i = 0;
|
|
1003
|
+
for (const ch of big) if (i < small.length && ch === small[i]) i++;
|
|
1004
|
+
return i === small.length;
|
|
1005
|
+
};
|
|
1006
|
+
var COMMON_ALIASES = {
|
|
1007
|
+
fps: "frameRate",
|
|
1008
|
+
framerate: "frameRate",
|
|
1009
|
+
frame_rate: "frameRate",
|
|
1010
|
+
bg: "background",
|
|
1011
|
+
backgroundcolor: "background",
|
|
1012
|
+
bgcolor: "background",
|
|
1013
|
+
seconds: "duration",
|
|
1014
|
+
length: "duration",
|
|
1015
|
+
time: "duration",
|
|
1016
|
+
w: "width",
|
|
1017
|
+
h: "height",
|
|
1018
|
+
cols: "columns",
|
|
1019
|
+
sweep: "degrees",
|
|
1020
|
+
angle: "degrees"
|
|
1021
|
+
};
|
|
1022
|
+
function suggestName(name, valid) {
|
|
1023
|
+
const n = name.toLowerCase();
|
|
1024
|
+
const alias = COMMON_ALIASES[n];
|
|
1025
|
+
if (alias && valid.includes(alias)) return alias;
|
|
1026
|
+
let best = null;
|
|
1027
|
+
for (const v of valid) {
|
|
1028
|
+
const lv = v.toLowerCase();
|
|
1029
|
+
const d = distance(n, lv);
|
|
1030
|
+
const close = d <= Math.max(1, Math.floor(Math.min(n.length, lv.length) / 3));
|
|
1031
|
+
const abbreviation = n.length >= 3 && isSubsequence(n, lv) || lv.length >= 2 && n.length > lv.length && n.includes(lv);
|
|
1032
|
+
if (!close && !abbreviation) continue;
|
|
1033
|
+
const score = close ? d : 3 + (lv.length - n.length) / 100;
|
|
1034
|
+
if (!best || score < best.score) best = { v, score };
|
|
1035
|
+
}
|
|
1036
|
+
return best ? best.v : null;
|
|
1037
|
+
}
|
|
1038
|
+
function warnUnknownOptions(where, opts, valid) {
|
|
1039
|
+
if (!opts || typeof opts !== "object") return;
|
|
1040
|
+
for (const key of Object.keys(opts)) {
|
|
1041
|
+
if (valid.includes(key)) continue;
|
|
1042
|
+
const guess = suggestName(key, valid);
|
|
1043
|
+
console.warn(`pixi-effects: ${where}: unknown option "${key}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${valid.join(", ")})`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
884
1047
|
// src/core/Movie.ts
|
|
885
1048
|
extensions.add(CullerPlugin);
|
|
886
1049
|
gsap.registerPlugin(PixiPlugin);
|
|
@@ -939,6 +1102,7 @@ var Movie = class {
|
|
|
939
1102
|
return this._audioContext;
|
|
940
1103
|
}
|
|
941
1104
|
async init(options = {}) {
|
|
1105
|
+
warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition"]);
|
|
942
1106
|
this._initState = "pending";
|
|
943
1107
|
try {
|
|
944
1108
|
this.width = options.width ?? 1920;
|
|
@@ -1003,7 +1167,7 @@ var Movie = class {
|
|
|
1003
1167
|
this.timeline.progress(1).progress(0);
|
|
1004
1168
|
await this._awaitVideoFrames();
|
|
1005
1169
|
this._updateSpace();
|
|
1006
|
-
this.
|
|
1170
|
+
this._renderNow();
|
|
1007
1171
|
this._initState = "ready";
|
|
1008
1172
|
this.emit("ready");
|
|
1009
1173
|
} catch (err) {
|
|
@@ -1022,7 +1186,7 @@ var Movie = class {
|
|
|
1022
1186
|
this.timeline.time(this.currentFrame / this.frameRate);
|
|
1023
1187
|
await this._awaitVideoFrames();
|
|
1024
1188
|
this._updateSpace();
|
|
1025
|
-
this.
|
|
1189
|
+
this._renderNow();
|
|
1026
1190
|
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1027
1191
|
}
|
|
1028
1192
|
async _awaitVideoFrames() {
|
|
@@ -1036,6 +1200,75 @@ var Movie = class {
|
|
|
1036
1200
|
return v.awaitFrameAt(local);
|
|
1037
1201
|
}));
|
|
1038
1202
|
}
|
|
1203
|
+
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1204
|
+
warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1205
|
+
this._requireReady("snapshot");
|
|
1206
|
+
await this.gotoFrame(clampFrame(frame, this.totalFrames), true);
|
|
1207
|
+
const scale = opts.scale ?? 1;
|
|
1208
|
+
const out = document.createElement("canvas");
|
|
1209
|
+
out.width = Math.max(1, Math.round(this.width * scale));
|
|
1210
|
+
out.height = Math.max(1, Math.round(this.height * scale));
|
|
1211
|
+
out.getContext("2d").drawImage(this.app.canvas, 0, 0, out.width, out.height);
|
|
1212
|
+
return encodeCanvas(out, opts.type ?? "image/png", opts.as ?? "blob");
|
|
1213
|
+
}
|
|
1214
|
+
async contactSheet(opts = {}) {
|
|
1215
|
+
warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as"]);
|
|
1216
|
+
this._requireReady("contactSheet");
|
|
1217
|
+
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));
|
|
1218
|
+
const LABEL_H = 26;
|
|
1219
|
+
const L = sheetLayout(frames.length, opts.columns ?? 3, opts.cellWidth ?? 480, this.width, this.height, LABEL_H);
|
|
1220
|
+
const sheet = document.createElement("canvas");
|
|
1221
|
+
sheet.width = L.width;
|
|
1222
|
+
sheet.height = L.height;
|
|
1223
|
+
const g = sheet.getContext("2d");
|
|
1224
|
+
g.fillStyle = "#10131c";
|
|
1225
|
+
g.fillRect(0, 0, L.width, L.height);
|
|
1226
|
+
const back = this.currentFrame;
|
|
1227
|
+
try {
|
|
1228
|
+
for (let i = 0; i < frames.length; i++) {
|
|
1229
|
+
const f = frames[i];
|
|
1230
|
+
await this.gotoFrame(f, true);
|
|
1231
|
+
const { x, y } = L.positions[i];
|
|
1232
|
+
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1233
|
+
g.fillStyle = "#e8ecf8";
|
|
1234
|
+
g.font = "600 15px system-ui, sans-serif";
|
|
1235
|
+
g.textBaseline = "middle";
|
|
1236
|
+
g.fillText(`frame ${f} \xB7 ${(f / this.frameRate).toFixed(2)}s`, x + 8, y + LABEL_H / 2);
|
|
1237
|
+
}
|
|
1238
|
+
} finally {
|
|
1239
|
+
await this.gotoFrame(back, true);
|
|
1240
|
+
}
|
|
1241
|
+
return encodeCanvas(sheet, "image/png", opts.as ?? "blob");
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Where every layer is drawn at `frame` (canvas pixels, visibility) plus `issues` — text that is off
|
|
1245
|
+
* the canvas, cut by an edge, empty, or overlapping other text. Seeks there and stays there.
|
|
1246
|
+
*/
|
|
1247
|
+
async inspect(frame = this.currentFrame, opts = { layers: "visible" }) {
|
|
1248
|
+
warnUnknownOptions("movie.inspect()", opts, ["layers"]);
|
|
1249
|
+
this._requireReady("inspect");
|
|
1250
|
+
const f = clampFrame(frame, this.totalFrames);
|
|
1251
|
+
await this.gotoFrame(f, true);
|
|
1252
|
+
return inspectScene(this._rootSequence, f, f / this.frameRate, { width: this.width, height: this.height }, opts);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Draw the stage now. PixiJS's CullerPlugin only culls inside `app.render()` (the ticker), and we render
|
|
1256
|
+
* by hand — while seeking and especially during `render()`, which stops the ticker — so without this the
|
|
1257
|
+
* `culled` flags are whatever the last tick left: a layer that is now on screen can stay culled and be
|
|
1258
|
+
* missing from the frame or from the exported video. Cull again (updating transforms: the timeline just
|
|
1259
|
+
* moved things) right before drawing.
|
|
1260
|
+
*/
|
|
1261
|
+
_renderNow() {
|
|
1262
|
+
const app = this.app;
|
|
1263
|
+
if (!app?.renderer) return;
|
|
1264
|
+
Culler.shared.cull(app.stage, app.renderer.screen, false);
|
|
1265
|
+
app.renderer.render({ container: app.stage });
|
|
1266
|
+
}
|
|
1267
|
+
_requireReady(what) {
|
|
1268
|
+
if (this._initState !== "ready" || !this._rootSequence || !this.app) {
|
|
1269
|
+
throw new Error(`pixi-effects: movie.${what}() needs a ready movie \u2014 await movie.init(...) first`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1039
1272
|
/** Projects every `threeD` layer for the current frame (see src/space). */
|
|
1040
1273
|
_updateSpace() {
|
|
1041
1274
|
if (!this._rootSequence || !this.app) return;
|
|
@@ -1121,6 +1354,7 @@ var Movie = class {
|
|
|
1121
1354
|
return this.muted;
|
|
1122
1355
|
}
|
|
1123
1356
|
async render(options) {
|
|
1357
|
+
warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
1124
1358
|
return await exportFrames(this, options);
|
|
1125
1359
|
}
|
|
1126
1360
|
async destroy() {
|
|
@@ -1138,6 +1372,11 @@ var Movie = class {
|
|
|
1138
1372
|
this._initState = "destroyed";
|
|
1139
1373
|
}
|
|
1140
1374
|
};
|
|
1375
|
+
var clampFrame = (f, last) => Math.max(0, Math.min(Math.round(f), last));
|
|
1376
|
+
function encodeCanvas(canvas, type, as) {
|
|
1377
|
+
if (as === "dataURL") return Promise.resolve(canvas.toDataURL(type));
|
|
1378
|
+
return new Promise((resolve, reject) => canvas.toBlob((b) => b ? resolve(b) : reject(new Error("pixi-effects: could not encode the image")), type));
|
|
1379
|
+
}
|
|
1141
1380
|
function safeRun(fn) {
|
|
1142
1381
|
try {
|
|
1143
1382
|
fn();
|
|
@@ -1156,7 +1395,9 @@ function collectVideoSequences(seq, out) {
|
|
|
1156
1395
|
}
|
|
1157
1396
|
|
|
1158
1397
|
// src/presets/kenBurns.ts
|
|
1398
|
+
var KEN_BURNS_KEYS = ["asset", "name", "at", "duration", "fit", "ease", "motion", "origin", "zoom", "direction", "angle", "from", "to"];
|
|
1159
1399
|
function kenBurns(opts) {
|
|
1400
|
+
warnUnknownOptions("kenBurns()", opts, KEN_BURNS_KEYS);
|
|
1160
1401
|
const {
|
|
1161
1402
|
asset,
|
|
1162
1403
|
name,
|
|
@@ -1166,7 +1407,6 @@ function kenBurns(opts) {
|
|
|
1166
1407
|
ease = "sine.inOut"
|
|
1167
1408
|
} = opts;
|
|
1168
1409
|
const baseScale = fit;
|
|
1169
|
-
const baseAt = at ?? 0;
|
|
1170
1410
|
let initial;
|
|
1171
1411
|
let keyframes;
|
|
1172
1412
|
if (opts.motion === "still") {
|
|
@@ -1197,7 +1437,7 @@ function kenBurns(opts) {
|
|
|
1197
1437
|
scale: initialScale
|
|
1198
1438
|
};
|
|
1199
1439
|
keyframes = [
|
|
1200
|
-
{ at:
|
|
1440
|
+
{ at: 0, to: { scale: `${baseScale} * ${toZ}` }, duration, ease }
|
|
1201
1441
|
];
|
|
1202
1442
|
} else if (opts.motion === "rotation") {
|
|
1203
1443
|
const angle = clamp(opts.angle ?? 8, 0, 30);
|
|
@@ -1228,7 +1468,7 @@ function kenBurns(opts) {
|
|
|
1228
1468
|
const tHere = duration * tFrac;
|
|
1229
1469
|
const tPrev = duration * ((i - 1) / (sampleCount - 1));
|
|
1230
1470
|
keyframes.push({
|
|
1231
|
-
at:
|
|
1471
|
+
at: tPrev,
|
|
1232
1472
|
to: { rotation: angleHere, scale: scaleAt(angleHere) },
|
|
1233
1473
|
duration: tHere - tPrev,
|
|
1234
1474
|
ease: i === 1 ? ease : "none"
|
|
@@ -1249,7 +1489,7 @@ function kenBurns(opts) {
|
|
|
1249
1489
|
scale: scaleExpr
|
|
1250
1490
|
};
|
|
1251
1491
|
keyframes = [
|
|
1252
|
-
{ at:
|
|
1492
|
+
{ at: 0, to: { x: xExpr(to[0]), y: yExpr(to[1]) }, duration, ease }
|
|
1253
1493
|
];
|
|
1254
1494
|
}
|
|
1255
1495
|
const spec = {
|
|
@@ -1266,13 +1506,72 @@ function kenBurns(opts) {
|
|
|
1266
1506
|
function clamp(v, lo, hi) {
|
|
1267
1507
|
return Math.max(lo, Math.min(hi, v));
|
|
1268
1508
|
}
|
|
1509
|
+
function parseEase(name) {
|
|
1510
|
+
return gsap.parseEase(name);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/presets/orbit.ts
|
|
1514
|
+
var ORBIT_KEYS = ["duration", "degrees", "start", "radius", "center", "width", "height", "fov", "ease", "dollyZoom", "at", "name", "stepsPerSecond"];
|
|
1515
|
+
function orbit(opts) {
|
|
1516
|
+
warnUnknownOptions("orbit()", opts, ORBIT_KEYS);
|
|
1517
|
+
const {
|
|
1518
|
+
duration,
|
|
1519
|
+
degrees,
|
|
1520
|
+
width = 1280,
|
|
1521
|
+
height = 720,
|
|
1522
|
+
fov,
|
|
1523
|
+
ease = "sine.inOut",
|
|
1524
|
+
stepsPerSecond = 10
|
|
1525
|
+
} = opts;
|
|
1526
|
+
if (!(duration > 0)) throw new Error(`pixi-effects: orbit duration must be > 0 (got ${duration})`);
|
|
1527
|
+
const dolly = opts.dollyZoom;
|
|
1528
|
+
if (dolly && opts.radius !== void 0) {
|
|
1529
|
+
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
1530
|
+
}
|
|
1531
|
+
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
1532
|
+
const fixedR = opts.radius ?? homeDistance(height, fov ?? DEFAULT_FOV);
|
|
1533
|
+
const start = opts.start ?? -degrees / 2;
|
|
1534
|
+
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
1535
|
+
const easeFn = parseEase(ease);
|
|
1536
|
+
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
1537
|
+
const pos = (i) => {
|
|
1538
|
+
const p = i / steps;
|
|
1539
|
+
const a = (start + degrees * easeFn(p)) * DEG;
|
|
1540
|
+
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
1541
|
+
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
1542
|
+
const R = homeDistance(height, f);
|
|
1543
|
+
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
1544
|
+
};
|
|
1545
|
+
const keyframes = [];
|
|
1546
|
+
for (let i = 0; i < steps; i++) {
|
|
1547
|
+
keyframes.push({ at: duration * i / steps, to: pos(i + 1), duration: duration / steps, ease: "none" });
|
|
1548
|
+
}
|
|
1549
|
+
const p0 = pos(0);
|
|
1550
|
+
const spec = {
|
|
1551
|
+
type: "camera",
|
|
1552
|
+
duration,
|
|
1553
|
+
initial: {
|
|
1554
|
+
x: p0.x,
|
|
1555
|
+
y: cy,
|
|
1556
|
+
z: p0.z,
|
|
1557
|
+
lookAtX: cx,
|
|
1558
|
+
lookAtY: cy,
|
|
1559
|
+
lookAtZ: 0,
|
|
1560
|
+
...p0.fov !== void 0 ? { fov: p0.fov } : fov !== void 0 ? { fov } : {}
|
|
1561
|
+
},
|
|
1562
|
+
keyframes
|
|
1563
|
+
};
|
|
1564
|
+
if (opts.at !== void 0) spec.at = opts.at;
|
|
1565
|
+
if (opts.name !== void 0) spec.name = opts.name;
|
|
1566
|
+
return spec;
|
|
1567
|
+
}
|
|
1269
1568
|
|
|
1270
1569
|
// src/transforms/withFade.ts
|
|
1271
1570
|
function withFade(spec, opts) {
|
|
1272
|
-
|
|
1571
|
+
warnUnknownOptions("withFade()", opts, ["in", "out"]);
|
|
1273
1572
|
if (opts.in != null && opts.in > 0) {
|
|
1274
1573
|
spec.initial = { ...spec.initial ?? {}, alpha: 0 };
|
|
1275
|
-
const kf = { at, to: { alpha: 1 }, duration: opts.in };
|
|
1574
|
+
const kf = { at: 0, to: { alpha: 1 }, duration: opts.in };
|
|
1276
1575
|
spec.keyframes = [...spec.keyframes ?? [], kf];
|
|
1277
1576
|
}
|
|
1278
1577
|
if (opts.out != null && opts.out > 0) {
|
|
@@ -1281,12 +1580,12 @@ function withFade(spec, opts) {
|
|
|
1281
1580
|
"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."
|
|
1282
1581
|
);
|
|
1283
1582
|
}
|
|
1284
|
-
const kf = { at:
|
|
1583
|
+
const kf = { at: spec.duration - opts.out, to: { alpha: 0 }, duration: opts.out };
|
|
1285
1584
|
spec.keyframes = [...spec.keyframes ?? [], kf];
|
|
1286
1585
|
}
|
|
1287
1586
|
return spec;
|
|
1288
1587
|
}
|
|
1289
1588
|
|
|
1290
|
-
export { Movie, kenBurns, withFade };
|
|
1589
|
+
export { Movie, kenBurns, orbit, withFade };
|
|
1291
1590
|
//# sourceMappingURL=index.js.map
|
|
1292
1591
|
//# sourceMappingURL=index.js.map
|