pixi-effects 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +6 -6
  3. package/ai/CHAT.md +3 -0
  4. package/ai/SKILL.md +2 -2
  5. package/ai/chat-template.html +8 -4
  6. package/ai/reference/cheatsheet.md +2 -0
  7. package/ai/template.html +5 -5
  8. package/ai/tools/check.mjs +25 -109
  9. package/ai/tools/render.mjs +1 -0
  10. package/dist/Composition-6JMXWNVV.cjs +14 -0
  11. package/dist/{Composition-LMVEASHF.cjs.map → Composition-6JMXWNVV.cjs.map} +1 -1
  12. package/dist/Composition-MLBHYYFF.js +5 -0
  13. package/dist/{Composition-HYAOYPAX.js.map → Composition-MLBHYYFF.js.map} +1 -1
  14. package/dist/Controller.d.cts +1 -1
  15. package/dist/Controller.d.ts +1 -1
  16. package/dist/{Movie-DHJRrUfI.d.ts → Movie-DlHQ9C02.d.ts} +46 -2
  17. package/dist/{Movie-CoxF2Z7t.d.cts → Movie-K2YuVAdg.d.cts} +46 -2
  18. package/dist/Presenter.d.cts +1 -1
  19. package/dist/Presenter.d.ts +1 -1
  20. package/dist/{chunk-LBJRXU7N.cjs → chunk-ANO5R2E6.cjs} +14 -4
  21. package/dist/chunk-ANO5R2E6.cjs.map +1 -0
  22. package/dist/{chunk-ETAIVOHP.cjs → chunk-JW67MDHQ.cjs} +26 -26
  23. package/dist/{chunk-ETAIVOHP.cjs.map → chunk-JW67MDHQ.cjs.map} +1 -1
  24. package/dist/{chunk-ORTYBA42.js → chunk-SG3Y66FN.js} +14 -4
  25. package/dist/chunk-SG3Y66FN.js.map +1 -0
  26. package/dist/{chunk-Q3IVBYT7.js → chunk-UXDR3ZDU.js} +3 -3
  27. package/dist/{chunk-Q3IVBYT7.js.map → chunk-UXDR3ZDU.js.map} +1 -1
  28. package/dist/index.cjs +142 -31
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +119 -8
  33. package/dist/index.js.map +1 -1
  34. package/dist/loader.css +1 -1
  35. package/dist/three.cjs +5 -5
  36. package/dist/three.js +1 -1
  37. package/llms-full.txt +4 -2
  38. package/package.json +1 -1
  39. package/dist/Composition-HYAOYPAX.js +0 -5
  40. package/dist/Composition-LMVEASHF.cjs +0 -14
  41. package/dist/chunk-LBJRXU7N.cjs.map +0 -1
  42. package/dist/chunk-ORTYBA42.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, L as LayerInfo, e as LoaderOption, M as MotionBlurOptions, f as MotionBlurSpec, g as Movie, h as MovieErrorEvent, i as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, j as SoundMeasure, k as StopEvent, l as StopImage, m as StopImagesOptions, T as TimelineData, n as TimelineHtmlOptions, o as TimelineRow, p as TimelineSvgOptions, q as TimelineTransition, V as VolumeEvent, r as dismissLoader, s as failLoader, t as resolveLoader } from './Movie-CoxF2Z7t.cjs';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoaderOption, M as MotionBlurOptions, g as MotionBlurSpec, h as Movie, i as MovieErrorEvent, j as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, k as ReviewOptions, l as ReviewReport, S as SnapshotOptions, m as SoundMeasure, n as StopEvent, o as StopImage, p as StopImagesOptions, T as TimelineData, q as TimelineHtmlOptions, r as TimelineRow, s as TimelineSvgOptions, t as TimelineTransition, V as VolumeEvent, u as dismissLoader, v as failLoader, w as resolveLoader } from './Movie-K2YuVAdg.cjs';
2
2
  import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-SS51lP6O.cjs';
3
3
  export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-SS51lP6O.cjs';
4
4
  import { S as Sequence } from './Base-OHqp8qvX.cjs';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, L as LayerInfo, e as LoaderOption, M as MotionBlurOptions, f as MotionBlurSpec, g as Movie, h as MovieErrorEvent, i as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, j as SoundMeasure, k as StopEvent, l as StopImage, m as StopImagesOptions, T as TimelineData, n as TimelineHtmlOptions, o as TimelineRow, p as TimelineSvgOptions, q as TimelineTransition, V as VolumeEvent, r as dismissLoader, s as failLoader, t as resolveLoader } from './Movie-DHJRrUfI.js';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoaderOption, M as MotionBlurOptions, g as MotionBlurSpec, h as Movie, i as MovieErrorEvent, j as MovieOptions, O as OnionSkinOptions, P as ProgressEvent, R as RenderOptions, k as ReviewOptions, l as ReviewReport, S as SnapshotOptions, m as SoundMeasure, n as StopEvent, o as StopImage, p as StopImagesOptions, T as TimelineData, q as TimelineHtmlOptions, r as TimelineRow, s as TimelineSvgOptions, t as TimelineTransition, V as VolumeEvent, u as dismissLoader, v as failLoader, w as resolveLoader } from './Movie-DlHQ9C02.js';
2
2
  import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-SS51lP6O.js';
3
3
  export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-SS51lP6O.js';
4
4
  import { S as Sequence } from './Base-BQUodYZK.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { CompositionSequence } from './chunk-Q3IVBYT7.js';
2
- import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, resolveAt, checkEase, collectPropKeys } from './chunk-ORTYBA42.js';
3
- export { noise, rand, random, registerSequenceType } from './chunk-ORTYBA42.js';
1
+ import { CompositionSequence } from './chunk-UXDR3ZDU.js';
2
+ import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, resolveAt, checkEase, collectPropKeys } from './chunk-SG3Y66FN.js';
3
+ export { noise, rand, random, registerSequenceType } from './chunk-SG3Y66FN.js';
4
4
  import { warnUnknownOptions, suggestName } from './chunk-DKGG7WD4.js';
5
5
  import * as PIXI from 'pixi.js';
6
6
  import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
@@ -446,19 +446,22 @@ function inspectFonts(root) {
446
446
  if (face.status === "error") failed.add(face.family.replace(/^["']|["']$/g, ""));
447
447
  });
448
448
  const missing = [];
449
+ const used = /* @__PURE__ */ new Set();
449
450
  const walk2 = (seq, index) => {
450
451
  const spec = seq.spec;
451
452
  if (spec.type === "text") {
452
453
  const raw = spec.style?.fontFamily;
453
454
  const family2 = Array.isArray(raw) ? raw.join(", ") : typeof raw === "string" ? raw : "";
454
455
  const list = family2.split(",").map((f) => f.trim()).filter(Boolean);
456
+ for (const f of list) used.add(f.replace(/^["']|["']$/g, "").toLowerCase());
455
457
  if (list.length && !list.some(fontAvailable) && !list.some((f) => failed.has(f.replace(/^["']|["']$/g, "")))) missing.push({ layer: spec.name ?? `text#${index}`, family: family2 });
456
458
  }
457
459
  if (seq.maskSequence) walk2(seq.maskSequence, index);
458
460
  (seq._children ?? []).forEach((child, i) => walk2(child, i));
459
461
  };
460
462
  walk2(root, 0);
461
- return { missing, failed: [...failed] };
463
+ const all = [...failed];
464
+ return { missing, failed: all.filter((f) => used.has(f.toLowerCase())), failedUnused: all.filter((f) => !used.has(f.toLowerCase())) };
462
465
  }
463
466
 
464
467
  // src/core/scenes.ts
@@ -466,6 +469,92 @@ function namedScenes(rows) {
466
469
  return rows.filter((r) => r.depth === 0 && r.type === "composition" && !/[#×]/.test(r.name) && r.end - r.start >= 1).map((r) => ({ name: r.name, start: r.start, end: r.end }));
467
470
  }
468
471
 
472
+ // src/core/review.ts
473
+ function sampleFrames({ totalFrames, frameRate, scenes = [], step = 0.25, cap = 240 }) {
474
+ const last = totalFrames;
475
+ const clamp2 = (f) => Math.max(0, Math.min(last, Math.round(f)));
476
+ const edges = /* @__PURE__ */ new Set([0, last]);
477
+ for (const s of scenes) {
478
+ edges.add(clamp2(s.start * frameRate));
479
+ edges.add(clamp2(s.end * frameRate - 1));
480
+ }
481
+ const room = Math.max(0, cap - edges.size);
482
+ const seconds = totalFrames / frameRate;
483
+ const wanted = Math.floor(seconds / step) + 1;
484
+ const stride = wanted <= room ? step : seconds / Math.max(1, room);
485
+ const frames = new Set(edges);
486
+ for (let t = stride; frames.size < cap && t < seconds; t += stride) frames.add(clamp2(t * frameRate));
487
+ return [...frames].sort((a, b) => a - b);
488
+ }
489
+ var AT_HELP = "use seconds (3.5), a percentage (50%), a frame (f120) or a layer's start / mid / end (name@end)";
490
+ function resolveAtList(list, ctx) {
491
+ const { frameRate: fps, totalFrames, duration, rows } = ctx;
492
+ const out = [], used = /* @__PURE__ */ new Set();
493
+ const named = rows.flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
494
+ for (const raw of String(list).split(",").map((s) => s.trim()).filter(Boolean)) {
495
+ let frame, label, m;
496
+ if (m = raw.match(/^(.+)@(start|mid|end)$/)) {
497
+ const row = named.find((r) => r.name === m[1]);
498
+ if (!row) throw new Error(`--at "${raw}": no layer named "${m[1]}" (names: ${named.slice(0, 40).map((r) => r.name).join(", ")}${named.length > 40 ? ", \u2026" : ""})`);
499
+ const t = m[2] === "start" ? row.start : m[2] === "mid" ? (row.start + row.end) / 2 : row.end - 1 / fps;
500
+ frame = Math.round(t * fps);
501
+ label = `${m[1]}-${m[2]}`;
502
+ } else if (m = raw.match(/^(\d+(?:\.\d+)?)%$/)) {
503
+ frame = Math.round(Number(m[1]) / 100 * totalFrames);
504
+ label = `${m[1]}pct`;
505
+ } else if (m = raw.match(/^f(\d+)$/i)) {
506
+ frame = Number(m[1]);
507
+ label = `f${m[1]}`;
508
+ } else if (/^\d+(\.\d+)?$/.test(raw)) {
509
+ frame = Math.round(Number(raw) * fps);
510
+ label = `${Number(raw).toFixed(2)}s`;
511
+ } else throw new Error(`--at: cannot read "${raw}": ${AT_HELP}`);
512
+ if (frame > totalFrames || frame < 0) throw new Error(`--at "${raw}" is past the end: the movie is ${duration} s (${totalFrames} frames)`);
513
+ label = label.replace(/[\\/:*?"<>|]+/g, "-");
514
+ let unique = label, n = 2;
515
+ while (used.has(unique)) unique = `${label}-${n++}`;
516
+ used.add(unique);
517
+ out.push({ label: unique, frame });
518
+ }
519
+ return out;
520
+ }
521
+ function groupIssues(perFrame) {
522
+ const groups = /* @__PURE__ */ new Map();
523
+ for (const { frame, issues } of perFrame) {
524
+ for (const message of issues) {
525
+ const key = message.replace(/-?\d+(\.\d+)?/g, "#");
526
+ const g = groups.get(key);
527
+ if (g) {
528
+ g.count++;
529
+ g.lastFrame = frame;
530
+ } else groups.set(key, { message, count: 1, firstFrame: frame, lastFrame: frame });
531
+ }
532
+ }
533
+ return [...groups.values()];
534
+ }
535
+ function splitIssues(groups, strict = false) {
536
+ if (strict) return { problems: groups, review: [] };
537
+ const overlap = (g) => /\boverlap\b/.test(g.message);
538
+ return { problems: groups.filter((g) => !overlap(g)), review: groups.filter(overlap) };
539
+ }
540
+ async function reviewMovie(movie, opts = {}) {
541
+ const rows = movie.timelineData().rows;
542
+ const at = opts.at ? resolveAtList(opts.at, { frameRate: movie.frameRate, totalFrames: movie.totalFrames, duration: movie.duration, rows }) : [];
543
+ const frames = sampleFrames({ totalFrames: movie.totalFrames, frameRate: movie.frameRate, scenes: namedScenes(rows) });
544
+ const perFrame = [];
545
+ for (const frame of frames) {
546
+ const r = await movie.inspect(frame, { layers: "none" });
547
+ if (r.issues.length) perFrame.push({ frame, issues: r.issues });
548
+ }
549
+ const grouped = splitIssues(groupIssues(perFrame), !!opts.strict);
550
+ const fonts = movie.inspectFonts();
551
+ for (const f of fonts.failedUnused) grouped.review.push({ message: `web font "${f}" failed to load, but no text layer uses it (a broken or unused @font-face)`, count: 1, firstFrame: 0, lastFrame: 0 });
552
+ for (const m of fonts.missing) {
553
+ (opts.strict ? grouped.problems : grouped.review).push({ message: `layer "${m.layer}": none of the fonts "${m.family}" is available here (it is drawn in a fallback font)`, count: 1, firstFrame: 0, lastFrame: 0 });
554
+ }
555
+ return { frames: frames.length, problems: grouped.problems, review: grouped.review, fonts, audio: movie.audioBuffer ? movie.inspectAudio() : null, at };
556
+ }
557
+
469
558
  // src/core/onion.ts
470
559
  function onionTimes(from, to, count) {
471
560
  if (count <= 1) return [from];
@@ -502,9 +591,9 @@ function resolveRange(range2, ctx) {
502
591
  if (range2 === void 0) return { fromFrame: 0, toFrame: totalFrames, fromSec: 0, toSec: duration, partial: false };
503
592
  let from, to;
504
593
  if (typeof range2 === "string") {
505
- const rows = (ctx.rows ?? []).filter((r) => r.depth === 0);
594
+ const rows = (ctx.rows ?? []).filter((r) => r.depth === 0).flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
506
595
  const row = rows.find((r) => r.name === range2);
507
- if (!row) throw new Error(`pixi-effects: render({ range: "${range2}" }): no layer named "${range2}" at the top level (names: ${rows.map((r) => r.name).join(", ") || "none"})`);
596
+ if (!row) throw new Error(`pixi-effects: render({ range: "${range2}" }): no layer named "${range2}" at the top level (names: ${rows.slice(0, 40).map((r) => r.name).join(", ") || "none"}${rows.length > 40 ? ", \u2026" : ""})`);
508
597
  from = row.start;
509
598
  to = row.end;
510
599
  } else {
@@ -669,7 +758,7 @@ async function exportFrames(movie, options = {}) {
669
758
  }
670
759
  await canvasSource.close();
671
760
  await output.finalize();
672
- if (mb) await movie.gotoFrame(movie.totalFrames, true);
761
+ if (mb) await movie.gotoFrame(range2.toFrame, true);
673
762
  return new Blob([output.target.buffer], { type: output.format.mimeType });
674
763
  } finally {
675
764
  movie.app.ticker.start();
@@ -1585,7 +1674,8 @@ function groupFamilies(entries) {
1585
1674
  start: Math.min(...members.map((x) => x.start)),
1586
1675
  end: Math.max(...members.map((x) => x.end)),
1587
1676
  keys: members.flatMap((x) => x.keys).sort((a, b) => a - b).slice(0, MAX_KEYS),
1588
- parts: members.map((x) => ({ start: x.start, end: x.end }))
1677
+ parts: members.map((x) => ({ start: x.start, end: x.end })),
1678
+ partNames: members.map((x) => x.name)
1589
1679
  });
1590
1680
  } else {
1591
1681
  out.push(e.row, ...e.inner);
@@ -2405,6 +2495,27 @@ var Movie = class {
2405
2495
  * The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
2406
2496
  * (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
2407
2497
  */
2498
+ /** The pictures to look at for moments such as `3.5`, `50%`, `f120`, `title@end` (comma-separated). A mistake says what could not be read. */
2499
+ resolveAt(list) {
2500
+ this._requireReady("resolveAt");
2501
+ return resolveAtList(list, { frameRate: this.frameRate, totalFrames: this.totalFrames, duration: this.duration, rows: this.timelineData().rows });
2502
+ }
2503
+ /**
2504
+ * One call that looks at everything `pixi-effects-check` looks at inside the page: layout issues over the whole timeline (every
2505
+ * scene's first and last frame and a frame every 0.25 s), fonts and the sound. See `ReviewReport`. Page warnings are in `window.__logs`. The movie is left as it was.
2506
+ */
2507
+ async review(opts = {}) {
2508
+ warnUnknownOptions("movie.review()", opts, ["at", "strict"]);
2509
+ this._requireReady("review");
2510
+ const back = this.currentFrame, wasAtPoster = this._atPoster;
2511
+ try {
2512
+ return await reviewMovie(this, opts);
2513
+ } finally {
2514
+ this.currentFrame = back;
2515
+ if (wasAtPoster) await this._showPoster();
2516
+ else await this._quietly(() => this.gotoFrame(back, true));
2517
+ }
2518
+ }
2408
2519
  /**
2409
2520
  * The fonts of the text layers: `missing` lists layers none of whose `fontFamily` entries is available in this browser (they are
2410
2521
  * drawn in a fallback font), `failed` lists web fonts (`@font-face`) whose file could not be loaded.