pixi-effects 0.5.0 → 0.7.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 +30 -0
- package/README.md +17 -10
- package/ai/SKILL.md +1 -1
- package/ai/reference/cheatsheet.md +8 -2
- package/ai/reference/pitfalls.md +1 -0
- package/ai/reference/recipes.md +36 -1
- package/ai/template.html +4 -3
- package/ai/tools/check.mjs +14 -5
- package/ai/tools/render.mjs +202 -0
- package/ai/tools/view.mjs +269 -0
- package/dist/{Base-DnRAxroQ.d.cts → Base-CSIulyIq.d.cts} +1 -1
- package/dist/{Base-CPSFBkPm.d.ts → Base-DVgwyIUj.d.ts} +1 -1
- package/dist/Composition-3KHQPRBA.cjs +14 -0
- package/dist/{Composition-63EXHNJJ.cjs.map → Composition-3KHQPRBA.cjs.map} +1 -1
- package/dist/Composition-DT657FO5.js +5 -0
- package/dist/{Composition-TUCRSMJU.js.map → Composition-DT657FO5.js.map} +1 -1
- package/dist/Controller.cjs +77 -2
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +16 -2
- package/dist/Controller.d.ts +16 -2
- package/dist/Controller.js +77 -2
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-CuU_aaCm.d.cts → Movie-B0OUKlZI.d.ts} +54 -2
- package/dist/{Movie-VyDiUYOj.d.ts → Movie-BK9daiTt.d.cts} +54 -2
- package/dist/{chunk-5Q5XZG7Z.js → chunk-36Z4RIRK.js} +3 -3
- package/dist/{chunk-5Q5XZG7Z.js.map → chunk-36Z4RIRK.js.map} +1 -1
- package/dist/{chunk-UBPZLGLU.js → chunk-4O2NYDJG.js} +365 -44
- package/dist/chunk-4O2NYDJG.js.map +1 -0
- package/dist/{chunk-23M5QTWW.cjs → chunk-4VSIJLU3.cjs} +21 -21
- package/dist/{chunk-23M5QTWW.cjs.map → chunk-4VSIJLU3.cjs.map} +1 -1
- package/dist/{chunk-2RR43OIN.cjs → chunk-6I55T4OH.cjs} +403 -62
- package/dist/chunk-6I55T4OH.cjs.map +1 -0
- package/dist/index.cjs +234 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +226 -16
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +6 -5
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +2 -1
- package/dist/three.js.map +1 -1
- package/dist/{types-7VtTEKUW.d.ts → types-oJijbn-K.d.cts} +36 -7
- package/dist/{types-7VtTEKUW.d.cts → types-oJijbn-K.d.ts} +36 -7
- package/docs/api.md +7 -0
- package/docs/dsl.md +74 -8
- package/llms-full.txt +127 -12
- package/package.json +7 -4
- package/dist/Composition-63EXHNJJ.cjs +0 -13
- package/dist/Composition-TUCRSMJU.js +0 -4
- package/dist/chunk-2RR43OIN.cjs.map +0 -1
- package/dist/chunk-UBPZLGLU.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 } from './Movie-
|
|
2
|
-
import { S as Sequence } from './Base-
|
|
3
|
-
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-
|
|
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-
|
|
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';
|
|
2
|
+
import { S as Sequence } from './Base-CSIulyIq.cjs';
|
|
3
|
+
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.cjs';
|
|
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';
|
|
5
5
|
import 'pixi.js';
|
|
6
6
|
import 'gsap';
|
|
7
7
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 } from './Movie-
|
|
2
|
-
import { S as Sequence } from './Base-
|
|
3
|
-
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-
|
|
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-
|
|
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';
|
|
2
|
+
import { S as Sequence } from './Base-DVgwyIUj.js';
|
|
3
|
+
import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, b as SequenceCommon } from './types-oJijbn-K.js';
|
|
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';
|
|
5
5
|
import 'pixi.js';
|
|
6
6
|
import 'gsap';
|
|
7
7
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
4
|
import { warnUnknownOptions, suggestName } from './chunk-DKGG7WD4.js';
|
|
2
|
-
import { CompositionSequence } from './chunk-5Q5XZG7Z.js';
|
|
3
|
-
import { homeDistance, DEFAULT_FOV, DEG, resolveAt, collectPropKeys } from './chunk-UBPZLGLU.js';
|
|
4
|
-
export { registerSequenceType } from './chunk-UBPZLGLU.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';
|
|
@@ -161,7 +161,7 @@ function fft(re, im) {
|
|
|
161
161
|
}
|
|
162
162
|
var FRAME = 2048;
|
|
163
163
|
function spectralCentroid(samples, sampleRate, from = 0, to = samples.length) {
|
|
164
|
-
let
|
|
164
|
+
let num2 = 0;
|
|
165
165
|
let den = 0;
|
|
166
166
|
const re = new Float64Array(FRAME);
|
|
167
167
|
const im = new Float64Array(FRAME);
|
|
@@ -176,11 +176,11 @@ function spectralCentroid(samples, sampleRate, from = 0, to = samples.length) {
|
|
|
176
176
|
fft(re, im);
|
|
177
177
|
for (let b = 1; b < FRAME / 2; b++) {
|
|
178
178
|
const power = re[b] * re[b] + im[b] * im[b];
|
|
179
|
-
|
|
179
|
+
num2 += power * (b * sampleRate / FRAME);
|
|
180
180
|
den += power;
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
-
return den > 1e-9 ? Math.round(
|
|
183
|
+
return den > 1e-9 ? Math.round(num2 / den / 10) * 10 : 0;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// src/core/inspectAudio.ts
|
|
@@ -1120,7 +1120,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1120
1120
|
const seen = /* @__PURE__ */ new Map();
|
|
1121
1121
|
const scene = /* @__PURE__ */ new Map();
|
|
1122
1122
|
const scaledToNothing = /* @__PURE__ */ new Set();
|
|
1123
|
-
const
|
|
1123
|
+
const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMoving = false, parentZero = false) => {
|
|
1124
1124
|
const blending = /* @__PURE__ */ new Set();
|
|
1125
1125
|
for (const w of transitionWindowsOf(comp.spec)) {
|
|
1126
1126
|
const start = (comp.absoluteStart ?? 0) + w.start, end = (comp.absoluteStart ?? 0) + w.end;
|
|
@@ -1163,11 +1163,11 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1163
1163
|
seen.set(info, shown);
|
|
1164
1164
|
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
1165
1165
|
if (group) scene.set(info, group);
|
|
1166
|
-
if (zero) scaledToNothing.add(info);
|
|
1167
|
-
if (seq instanceof CompositionSequence)
|
|
1166
|
+
if (zero || seq.showsNothingYet) scaledToNothing.add(info);
|
|
1167
|
+
if (seq instanceof CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
|
|
1168
1168
|
});
|
|
1169
1169
|
};
|
|
1170
|
-
|
|
1170
|
+
walk2(root, "", false, true);
|
|
1171
1171
|
const issues = [];
|
|
1172
1172
|
const texts = layers.filter((l) => l.type === "text" && l.visible && l.alpha >= FAINT && l.bounds);
|
|
1173
1173
|
for (const l of texts) {
|
|
@@ -1204,6 +1204,158 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1204
1204
|
return { frame, time, canvas, summary: { layers: layers.length, visible: layers.filter((l) => l.visible).length }, issues, layers: listed };
|
|
1205
1205
|
}
|
|
1206
1206
|
|
|
1207
|
+
// src/core/timelineChart.ts
|
|
1208
|
+
var GROUP_MIN = 4;
|
|
1209
|
+
var MAX_KEYS = 200;
|
|
1210
|
+
var round = (n) => Math.round(n * 1e6) / 1e6;
|
|
1211
|
+
var family = (name) => name.replace(/\d+/g, "#");
|
|
1212
|
+
function detailOf(seq) {
|
|
1213
|
+
const s = seq.spec;
|
|
1214
|
+
if (typeof s.text === "string") return s.text.length > 60 ? s.text.slice(0, 57) + "\u2026" : s.text;
|
|
1215
|
+
if (typeof s.asset === "string") return `asset \u201C${s.asset}\u201D`;
|
|
1216
|
+
if (typeof s.sfx === "string") return `sfx \u201C${s.sfx}\u201D`;
|
|
1217
|
+
if (s.sfx && typeof s.sfx === "object") return "sfx (custom)";
|
|
1218
|
+
return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
|
|
1221
|
+
const entries = [];
|
|
1222
|
+
comp._children.forEach((seq, i) => {
|
|
1223
|
+
const name = seq.spec.name ?? `${seq.spec.type}#${i}`;
|
|
1224
|
+
const start = parentStart + (seq.at ?? 0);
|
|
1225
|
+
const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
|
|
1226
|
+
const span = end - start;
|
|
1227
|
+
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
|
|
1228
|
+
const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
|
|
1229
|
+
const inner = [];
|
|
1230
|
+
const isComp = seq instanceof CompositionSequence;
|
|
1231
|
+
if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
|
|
1232
|
+
entries.push({ row, inner, groupable: !isComp });
|
|
1233
|
+
});
|
|
1234
|
+
rows.push(...groupFamilies(entries));
|
|
1235
|
+
for (const w of transitionWindowsOf(comp.spec)) {
|
|
1236
|
+
transitions.push({ from: prefix + w.from, to: prefix + w.to, start: round(parentStart + w.start), end: round(parentStart + w.end) });
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function groupFamilies(entries) {
|
|
1240
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1241
|
+
for (const e of entries) {
|
|
1242
|
+
if (!e.groupable) continue;
|
|
1243
|
+
const key = e.row.type + "|" + family(e.row.name);
|
|
1244
|
+
(buckets.get(key) ?? buckets.set(key, []).get(key)).push(e.row);
|
|
1245
|
+
}
|
|
1246
|
+
const out = [];
|
|
1247
|
+
const done = /* @__PURE__ */ new Set();
|
|
1248
|
+
for (const e of entries) {
|
|
1249
|
+
const key = e.row.type + "|" + family(e.row.name);
|
|
1250
|
+
const members = e.groupable ? buckets.get(key) : null;
|
|
1251
|
+
if (members && members.length >= GROUP_MIN) {
|
|
1252
|
+
if (done.has(key)) continue;
|
|
1253
|
+
done.add(key);
|
|
1254
|
+
const label = `${family(e.row.name)} \xD7${members.length}`;
|
|
1255
|
+
out.push({
|
|
1256
|
+
path: e.row.path.slice(0, e.row.path.length - e.row.name.length) + label,
|
|
1257
|
+
name: label,
|
|
1258
|
+
type: e.row.type,
|
|
1259
|
+
depth: e.row.depth,
|
|
1260
|
+
start: Math.min(...members.map((x) => x.start)),
|
|
1261
|
+
end: Math.max(...members.map((x) => x.end)),
|
|
1262
|
+
keys: members.flatMap((x) => x.keys).sort((a, b) => a - b).slice(0, MAX_KEYS),
|
|
1263
|
+
parts: members.map((x) => ({ start: x.start, end: x.end }))
|
|
1264
|
+
});
|
|
1265
|
+
} else {
|
|
1266
|
+
out.push(e.row, ...e.inner);
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
out.push(...e.inner);
|
|
1270
|
+
}
|
|
1271
|
+
return out;
|
|
1272
|
+
}
|
|
1273
|
+
function collectTimeline(root, duration) {
|
|
1274
|
+
const rows = [];
|
|
1275
|
+
const transitions = [];
|
|
1276
|
+
walk(root, 0, duration, 0, "", rows, transitions);
|
|
1277
|
+
return { duration, rows, transitions };
|
|
1278
|
+
}
|
|
1279
|
+
var COLORS = {
|
|
1280
|
+
text: "#4cc9f0",
|
|
1281
|
+
shape: "#ffd166",
|
|
1282
|
+
image: "#7bd88f",
|
|
1283
|
+
video: "#c77dff",
|
|
1284
|
+
audio: "#ff7aa8",
|
|
1285
|
+
composition: "#8ea4c8",
|
|
1286
|
+
three: "#ff9f68",
|
|
1287
|
+
camera: "#b0b0b0"
|
|
1288
|
+
};
|
|
1289
|
+
var esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1290
|
+
var num = (n) => String(Math.round(n * 100) / 100);
|
|
1291
|
+
function tickStep(duration, chartWidth) {
|
|
1292
|
+
for (const s of [0.04, 0.1, 0.2, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 1200, 3600]) if (s / duration * chartWidth >= 70) return s;
|
|
1293
|
+
return 7200;
|
|
1294
|
+
}
|
|
1295
|
+
function timelineSvg(data, opts = {}) {
|
|
1296
|
+
const CHART = Math.max(100, opts.chartWidth ?? 950), withLabels = opts.labels !== false;
|
|
1297
|
+
const LABEL = withLabels ? 250 : 0, ROW = 22, TOP = 34, PAD = 16;
|
|
1298
|
+
const W = LABEL + CHART + PAD * 2, H = TOP + data.rows.length * ROW + PAD;
|
|
1299
|
+
const x = (t) => PAD + LABEL + Math.max(0, Math.min(data.duration, t)) / data.duration * CHART;
|
|
1300
|
+
const step = tickStep(data.duration, CHART);
|
|
1301
|
+
const out = [];
|
|
1302
|
+
for (let t = 0; t <= data.duration + 1e-9; t += step) {
|
|
1303
|
+
out.push(`<line class="grid" x1="${num(x(t))}" y1="${TOP - 6}" x2="${num(x(t))}" y2="${H - PAD}"/>`);
|
|
1304
|
+
out.push(`<text class="tick" x="${num(x(t))}" y="${TOP - 12}" text-anchor="middle">${num(t)}${t === 0 || Math.abs(t - data.duration) < 1e-9 ? " s" : ""}</text>`);
|
|
1305
|
+
}
|
|
1306
|
+
if (Math.abs(data.duration % step) > 1e-9) out.push(`<text class="tick" x="${num(x(data.duration))}" y="${TOP - 12}" text-anchor="middle">${num(data.duration)} s</text>`);
|
|
1307
|
+
for (const t of data.transitions) {
|
|
1308
|
+
out.push(`<g><title>transition ${esc(t.from)} \u2192 ${esc(t.to)} (${num(t.start)}\u2013${num(t.end)} s)</title><rect class="transition" x="${num(x(t.start))}" y="${TOP - 4}" width="${num(Math.max(2, x(t.end) - x(t.start)))}" height="${H - TOP - PAD + 4}"/></g>`);
|
|
1309
|
+
}
|
|
1310
|
+
data.rows.forEach((r, i) => {
|
|
1311
|
+
const y = TOP + i * ROW;
|
|
1312
|
+
const color = COLORS[r.type] ?? "#9aa7b8";
|
|
1313
|
+
const tip = `${r.path} \xB7 ${r.type} \xB7 ${num(r.start)}\u2013${num(r.end)} s (${num(r.end - r.start)} s)${r.keys.length ? ` \xB7 ${r.keys.length} keyframe${r.keys.length > 1 ? "s" : ""}` : ""}${r.detail ? ` \xB7 ${r.detail}` : ""}`;
|
|
1314
|
+
out.push(`<g class="row" data-start="${num(r.start)}"><title>${esc(tip)}</title>`);
|
|
1315
|
+
out.push(`<rect class="band" x="${PAD}" y="${y}" width="${LABEL + CHART}" height="${ROW}" ${i % 2 ? 'fill-opacity=".05"' : 'fill-opacity="0"'}/>`);
|
|
1316
|
+
if (withLabels) out.push(`<text class="label" x="${PAD + 6 + r.depth * 14}" y="${y + 15}">${esc(r.name)}</text>`);
|
|
1317
|
+
for (const p of r.parts ?? [{ start: r.start, end: r.end }]) {
|
|
1318
|
+
out.push(`<rect class="bar" x="${num(x(p.start))}" y="${y + 4}" width="${num(Math.max(2, x(p.end) - x(p.start)))}" height="${ROW - 8}" rx="3" fill="${color}"/>`);
|
|
1319
|
+
}
|
|
1320
|
+
for (const k of r.keys) out.push(`<path class="key" d="M ${num(x(k))} ${y + 7} l 3 4 l -3 4 l -3 -4 z"/>`);
|
|
1321
|
+
out.push("</g>");
|
|
1322
|
+
});
|
|
1323
|
+
const legend = Object.entries(COLORS).map(([type, c], i) => `<g transform="translate(${PAD + i * 108},${H + 10})"><rect width="12" height="12" rx="3" fill="${c}"/><text x="18" y="10" class="tick">${type}</text></g>`).join("");
|
|
1324
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H + 32}" viewBox="0 0 ${W} ${H + 32}" role="img" aria-label="Timeline of ${data.rows.length} rows" data-duration="${num(data.duration)}" data-x0="${num(x(0))}" data-x1="${num(x(data.duration))}" data-top="${TOP - 6}" data-bottom="${H - PAD}" data-row="${ROW}">
|
|
1325
|
+
${out.join("\n")}
|
|
1326
|
+
${legend}
|
|
1327
|
+
</svg>`;
|
|
1328
|
+
}
|
|
1329
|
+
function timelineHtml(data, opts = {}) {
|
|
1330
|
+
const W = 250 + 950 + 32;
|
|
1331
|
+
const title = esc(opts.title ?? "Timeline");
|
|
1332
|
+
return `<!doctype html>
|
|
1333
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${title} \u2014 timeline</title>
|
|
1334
|
+
<style>
|
|
1335
|
+
:root { --bg: #f7f5f1; --ink: #1d2433; --dim: #6b7587; --grid: #d9d5cc; --key: #1d2433; --trans: #4cc9f0; }
|
|
1336
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #0e1320; --ink: #e8eefc; --dim: #8fa0bf; --grid: #26304a; --key: #ffffff; --trans: #4cc9f0; } }
|
|
1337
|
+
body { margin: 0; background: var(--bg); color: var(--ink); font: 14px system-ui, sans-serif; }
|
|
1338
|
+
main { padding: 20px 16px 32px; max-width: ${W + 32}px; margin: 0 auto; overflow-x: auto; }
|
|
1339
|
+
h1 { font-size: 18px; font-weight: 600; margin: 0 0 4px; }
|
|
1340
|
+
p { margin: 0 0 14px; color: var(--dim); font-size: 13px; }
|
|
1341
|
+
svg { display: block; }
|
|
1342
|
+
.grid { stroke: var(--grid); stroke-width: 1; }
|
|
1343
|
+
.tick { fill: var(--dim); font-size: 11px; }
|
|
1344
|
+
.label { fill: var(--ink); font-size: 12px; font-family: ui-monospace, Menlo, monospace; }
|
|
1345
|
+
.band { fill: var(--ink); }
|
|
1346
|
+
.key { fill: var(--key); opacity: .85; }
|
|
1347
|
+
.transition { fill: var(--trans); opacity: .16; }
|
|
1348
|
+
.bar { opacity: .88; }
|
|
1349
|
+
.row:hover .bar { opacity: 1; }
|
|
1350
|
+
</style></head>
|
|
1351
|
+
<body><main>
|
|
1352
|
+
<h1>${title}</h1>
|
|
1353
|
+
<p>${data.rows.length} row${data.rows.length === 1 ? "" : "s"} \xB7 ${num(data.duration)} s \xB7 \u25C6 keyframe start \xB7 shaded band: a transition \xB7 hover a bar for details</p>
|
|
1354
|
+
${timelineSvg(data)}
|
|
1355
|
+
</main></body></html>
|
|
1356
|
+
`;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1207
1359
|
// src/core/frames.ts
|
|
1208
1360
|
function pickFrames(lastFrame, count) {
|
|
1209
1361
|
const last = Math.max(0, Math.floor(lastFrame));
|
|
@@ -1224,6 +1376,35 @@ function sheetLayout(n, columns, cellW, srcW, srcH, labelH) {
|
|
|
1224
1376
|
return { cols, rows, cellW, cellH, width: cols * cellW, height: rows * (cellH + labelH), positions };
|
|
1225
1377
|
}
|
|
1226
1378
|
|
|
1379
|
+
// src/core/startWhenRunning.ts
|
|
1380
|
+
function startWhenRunning(ctx, start, gesture) {
|
|
1381
|
+
if (ctx.state === "running") {
|
|
1382
|
+
start();
|
|
1383
|
+
return () => {
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
let done = false;
|
|
1387
|
+
const resume = () => {
|
|
1388
|
+
ctx.resume?.()?.catch(() => {
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
const stop = () => {
|
|
1392
|
+
done = true;
|
|
1393
|
+
ctx.removeEventListener("statechange", onState);
|
|
1394
|
+
for (const e of GESTURES) gesture?.removeEventListener(e, resume, true);
|
|
1395
|
+
};
|
|
1396
|
+
const onState = () => {
|
|
1397
|
+
if (done || ctx.state !== "running") return;
|
|
1398
|
+
stop();
|
|
1399
|
+
start();
|
|
1400
|
+
};
|
|
1401
|
+
ctx.addEventListener("statechange", onState);
|
|
1402
|
+
for (const e of GESTURES) gesture?.addEventListener(e, resume, true);
|
|
1403
|
+
resume();
|
|
1404
|
+
return stop;
|
|
1405
|
+
}
|
|
1406
|
+
var GESTURES = ["pointerdown", "mousedown", "touchend", "keydown"];
|
|
1407
|
+
|
|
1227
1408
|
// src/core/Movie.ts
|
|
1228
1409
|
extensions.add(CullerPlugin);
|
|
1229
1410
|
gsap.registerPlugin(PixiPlugin);
|
|
@@ -1236,6 +1417,7 @@ var Movie = class {
|
|
|
1236
1417
|
audioBuffer = null;
|
|
1237
1418
|
audioSource = null;
|
|
1238
1419
|
gainNode = null;
|
|
1420
|
+
_cancelAudioStart = null;
|
|
1239
1421
|
_volume = 1;
|
|
1240
1422
|
_muted = false;
|
|
1241
1423
|
isPlaying = false;
|
|
@@ -1329,6 +1511,7 @@ var Movie = class {
|
|
|
1329
1511
|
height: options.composition.height ?? this.height,
|
|
1330
1512
|
duration: options.composition.duration ?? this.duration
|
|
1331
1513
|
} : void 0;
|
|
1514
|
+
await ensureFilterLibrary(seededComposition);
|
|
1332
1515
|
const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
|
|
1333
1516
|
const rootSeqSpec = {
|
|
1334
1517
|
type: "composition",
|
|
@@ -1437,6 +1620,24 @@ var Movie = class {
|
|
|
1437
1620
|
await this.gotoFrame(f, true);
|
|
1438
1621
|
return inspectScene(this._rootSequence, f, f / this.frameRate, { width: this.width, height: this.height }, opts);
|
|
1439
1622
|
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Every layer as a row with absolute start / end / keyframe times (seconds), plus the transition windows. Runs of
|
|
1625
|
+
* similar layers (`pop-1` … `pop-12`) are one row with a `parts` span each. Read it, or draw it with `timelineChart()`.
|
|
1626
|
+
*/
|
|
1627
|
+
timelineData() {
|
|
1628
|
+
this._requireReady("timelineData");
|
|
1629
|
+
return collectTimeline(this._rootSequence, this.duration);
|
|
1630
|
+
}
|
|
1631
|
+
/** The timeline as one self-contained HTML page (an inline SVG, no scripts): layers as bars on a time axis, ◆ keyframes, transition bands. Open it in a browser. */
|
|
1632
|
+
timelineChart(opts = {}) {
|
|
1633
|
+
warnUnknownOptions("movie.timelineChart()", opts, ["title"]);
|
|
1634
|
+
return timelineHtml(this.timelineData(), opts);
|
|
1635
|
+
}
|
|
1636
|
+
/** Just the chart: one `<svg>` whose geometry is in `data-*` attributes (a viewer puts a playhead on it: `pixi-effects-view`). */
|
|
1637
|
+
timelineSvg(opts = {}) {
|
|
1638
|
+
warnUnknownOptions("movie.timelineSvg()", opts, ["chartWidth", "labels"]);
|
|
1639
|
+
return timelineSvg(this.timelineData(), opts);
|
|
1640
|
+
}
|
|
1440
1641
|
/**
|
|
1441
1642
|
* The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
|
|
1442
1643
|
* (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
|
|
@@ -1473,6 +1674,7 @@ var Movie = class {
|
|
|
1473
1674
|
play() {
|
|
1474
1675
|
if (this._initState !== "ready") return;
|
|
1475
1676
|
if (this.currentFrame >= this.totalFrames) this.currentFrame = 0;
|
|
1677
|
+
const wasPlaying = this.isPlaying;
|
|
1476
1678
|
this.isPlaying = true;
|
|
1477
1679
|
const startTime = performance.now() - this.currentFrame / this.frameRate * 1e3;
|
|
1478
1680
|
let inFlight = false;
|
|
@@ -1502,17 +1704,25 @@ var Movie = class {
|
|
|
1502
1704
|
this._raf = requestAnimationFrame(tick);
|
|
1503
1705
|
if (this.audioBuffer) {
|
|
1504
1706
|
const ctx = this._ensureAudioContext();
|
|
1505
|
-
|
|
1506
|
-
this.
|
|
1507
|
-
this.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1707
|
+
const buffer = this.audioBuffer;
|
|
1708
|
+
this._cancelAudioStart?.();
|
|
1709
|
+
this._cancelAudioStart = startWhenRunning(ctx, () => {
|
|
1710
|
+
if (!this.isPlaying) return;
|
|
1711
|
+
this.audioSource = ctx.createBufferSource();
|
|
1712
|
+
this.audioSource.buffer = buffer;
|
|
1713
|
+
this.gainNode = ctx.createGain();
|
|
1714
|
+
this.gainNode.gain.value = this._muted ? 0 : this._volume;
|
|
1715
|
+
this.audioSource.connect(this.gainNode).connect(ctx.destination);
|
|
1716
|
+
this.audioSource.start(0, Math.min(this.currentFrame / this.frameRate, buffer.duration));
|
|
1717
|
+
}, typeof window !== "undefined" ? window : void 0);
|
|
1511
1718
|
}
|
|
1719
|
+
if (!wasPlaying) this.emit("play");
|
|
1512
1720
|
}
|
|
1513
1721
|
pause() {
|
|
1514
1722
|
const wasPlaying = this.isPlaying;
|
|
1515
1723
|
this.isPlaying = false;
|
|
1724
|
+
this._cancelAudioStart?.();
|
|
1725
|
+
this._cancelAudioStart = null;
|
|
1516
1726
|
if (this._raf) {
|
|
1517
1727
|
cancelAnimationFrame(this._raf);
|
|
1518
1728
|
this._raf = null;
|