pixi-effects 0.16.3 → 0.17.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 (48) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +6 -6
  3. package/ai/CHAT.md +4 -0
  4. package/ai/SKILL.md +2 -1
  5. package/ai/chat-template.html +3 -3
  6. package/ai/reference/cheatsheet.md +4 -2
  7. package/ai/reference/pitfalls.md +3 -0
  8. package/ai/reference/recipes.md +67 -0
  9. package/ai/template.html +5 -5
  10. package/ai/tools/check.mjs +162 -18
  11. package/ai/tools/render.mjs +21 -7
  12. package/dist/Composition-HYAOYPAX.js +5 -0
  13. package/dist/{Composition-VJWY2NDL.js.map → Composition-HYAOYPAX.js.map} +1 -1
  14. package/dist/Composition-LMVEASHF.cjs +14 -0
  15. package/dist/{Composition-3WWNVRR5.cjs.map → Composition-LMVEASHF.cjs.map} +1 -1
  16. package/dist/Controller.d.cts +1 -1
  17. package/dist/Controller.d.ts +1 -1
  18. package/dist/{Movie-oA0SUAyb.d.cts → Movie-CoxF2Z7t.d.cts} +82 -2
  19. package/dist/{Movie-DilBC348.d.ts → Movie-DHJRrUfI.d.ts} +82 -2
  20. package/dist/Presenter.d.cts +1 -1
  21. package/dist/Presenter.d.ts +1 -1
  22. package/dist/{chunk-2ROVCPSL.cjs → chunk-ETAIVOHP.cjs} +32 -27
  23. package/dist/chunk-ETAIVOHP.cjs.map +1 -0
  24. package/dist/{chunk-FJA5VUOF.cjs → chunk-LBJRXU7N.cjs} +15 -3
  25. package/dist/chunk-LBJRXU7N.cjs.map +1 -0
  26. package/dist/{chunk-ZNKQH356.js → chunk-ORTYBA42.js} +15 -4
  27. package/dist/chunk-ORTYBA42.js.map +1 -0
  28. package/dist/{chunk-XTIHKODH.js → chunk-Q3IVBYT7.js} +10 -5
  29. package/dist/chunk-Q3IVBYT7.js.map +1 -0
  30. package/dist/index.cjs +351 -41
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +329 -19
  35. package/dist/index.js.map +1 -1
  36. package/dist/loader.css +1 -1
  37. package/dist/three.cjs +5 -5
  38. package/dist/three.js +1 -1
  39. package/docs/dsl.md +2 -1
  40. package/llms-full.txt +78 -4
  41. package/llms.txt +1 -1
  42. package/package.json +6 -3
  43. package/dist/Composition-3WWNVRR5.cjs +0 -14
  44. package/dist/Composition-VJWY2NDL.js +0 -5
  45. package/dist/chunk-2ROVCPSL.cjs.map +0 -1
  46. package/dist/chunk-FJA5VUOF.cjs.map +0 -1
  47. package/dist/chunk-XTIHKODH.js.map +0 -1
  48. package/dist/chunk-ZNKQH356.js.map +0 -1
@@ -13,6 +13,8 @@
13
13
  *
14
14
  * Options: -o, --out FILE (default ./<page name>.<format>; the container follows the extension: mp4 webm mov mkv) ·
15
15
  * --format mp4|webm|mov|mkv|pdf (a .pdf is the deck as pages: one picture per page of a movie with `stops`; --all-stops makes a page of every stop) · --quality very-low|low|medium|high|very-high (video and audio bitrate, default high) ·
16
+ * --range A:B (only seconds A to B; 2: runs to the end, :5 starts at the beginning) · --scene NAME (the span of a top-level layer: a movie's scene) · --scale S (output size, 0 < S <= 1: smaller picture, smaller file) ·
17
+ * --draft (for looking: half size, low quality, no motion blur; a --quality you give still wins) ·
16
18
  * --motion-blur SAMPLES (2-64: each frame is drawn that many times over the shutter and averaged; overrides the page's own motionBlur) ·
17
19
  * --shutter FRACTION (with --motion-blur: how long the shutter is open, 0-1 of a frame, default 0.5) ·
18
20
  * --video-codec C · --audio-codec C · --query "a=1&b=2" (added to the page URL) · --fail-on-warn (exit 1 when the page
@@ -41,7 +43,7 @@ export function defaultOutput(page, format) {
41
43
  }
42
44
 
43
45
  export function parseRenderArgs(argv) {
44
- const o = { page: null, out: null, format: null, quality: 'high', videoCodec: null, audioCodec: null, allStops: false, motionBlur: null, shutter: null, query: null, failOnWarn: false, quiet: false, timeout: 900, root: null, chrome: null };
46
+ const o = { range: null, scene: null, scale: null, draft: false, qualityGiven: false, page: null, out: null, format: null, quality: 'high', videoCodec: null, audioCodec: null, allStops: false, motionBlur: null, shutter: null, query: null, failOnWarn: false, quiet: false, timeout: 900, root: null, chrome: null };
45
47
  const need = (i, name) => { if (i + 1 >= argv.length) throw new Error(`${name} needs a value`); return argv[i + 1]; };
46
48
  for (let i = 0; i < argv.length; i++) {
47
49
  const a = argv[i];
@@ -50,6 +52,7 @@ export function parseRenderArgs(argv) {
50
52
  o.format = need(i++, a).toLowerCase();
51
53
  if (!FORMATS.includes(o.format)) throw new Error(`--format must be mp4, webm, mov, mkv or pdf (got "${o.format}")`);
52
54
  } else if (a === '--quality') {
55
+ o.qualityGiven = true;
53
56
  o.quality = need(i++, a);
54
57
  if (!QUALITIES.includes(o.quality)) throw new Error(`--quality must be very-low, low, medium, high or very-high (got "${o.quality}")`);
55
58
  } else if (a === '--video-codec') o.videoCodec = need(i++, a);
@@ -60,7 +63,16 @@ export function parseRenderArgs(argv) {
60
63
  } else if (a === '--shutter') {
61
64
  o.shutter = Number(need(i++, a));
62
65
  if (!(o.shutter > 0 && o.shutter <= 1)) throw new Error(`--shutter must be above 0 and at most 1 (0.5 is a 180° shutter), got "${argv[i]}"`);
63
- } else if (a === '--query') o.query = need(i++, a).replace(/^\?/, '');
66
+ } else if (a === '--range') {
67
+ const m = need(i++, a).match(/^(\d+(?:\.\d+)?)?:(\d+(?:\.\d+)?)?$/);
68
+ if (!m || (m[1] === undefined && m[2] === undefined)) throw new Error(`--range must look like 2:5 (seconds; 2: runs to the end, :5 starts at the beginning), got "${argv[i]}"`);
69
+ o.range = [m[1] === undefined ? null : Number(m[1]), m[2] === undefined ? null : Number(m[2])];
70
+ } else if (a === '--scene') o.scene = need(i++, a);
71
+ else if (a === '--scale') {
72
+ o.scale = Number(need(i++, a));
73
+ if (!(o.scale > 0 && o.scale <= 1)) throw new Error(`--scale must be above 0 and at most 1 (0.5 is half size), got "${argv[i]}"`);
74
+ } else if (a === '--draft') o.draft = true;
75
+ else if (a === '--query') o.query = need(i++, a).replace(/^\?/, '');
64
76
  else if (a === '--all-stops') o.allStops = true;
65
77
  else if (a === '--fail-on-warn') o.failOnWarn = true;
66
78
  else if (a === '--quiet') o.quiet = true;
@@ -74,6 +86,8 @@ export function parseRenderArgs(argv) {
74
86
  if (o.shutter !== null && o.motionBlur === null) throw new Error('--shutter goes with --motion-blur SAMPLES');
75
87
  if (o.allStops && o.format !== 'pdf' && !(o.out && formatFromPath(o.out) === 'pdf')) throw new Error('--all-stops goes with a PDF (-o deck.pdf)');
76
88
  o.format ??= (o.out ? formatFromPath(o.out) : null) ?? 'mp4';
89
+ if (o.range && o.scene !== null) throw new Error('--range and --scene cannot be used together: a scene is a range');
90
+ if (o.format === 'pdf' && (o.range || o.scene !== null || o.scale !== null || o.draft)) throw new Error('--range, --scene, --scale and --draft are not for a PDF (a PDF is the pages of a deck)');
77
91
  return o;
78
92
  }
79
93
 
@@ -81,16 +95,16 @@ const UPLOAD = '/__pixi_effects_out';
81
95
  const kb = n => (n >= 1048576 ? `${(n / 1048576).toFixed(2)} MB` : `${Math.max(1, Math.round(n / 1024))} KB`);
82
96
 
83
97
  /** The page's side: render, and PUT the Blob to our server (so a long video never goes through base64). */
84
- const renderScript = (format, quality, videoCodec, audioCodec, motionBlur, shutter, allStops) => `(() => {
98
+ const renderScript = (format, quality, videoCodec, audioCodec, motionBlur, shutter, allStops, part = {}) => `(() => {
85
99
  window.__r = { state: 'running', progress: 0 };
86
100
  movie.on('progress', e => { window.__r.progress = e.progress; });
87
101
  (async () => {
88
102
  try {
89
- const video = { bitrate: ${JSON.stringify(quality)}${videoCodec ? `, codec: ${JSON.stringify(videoCodec)}` : ''} };
103
+ const video = { ${[part.draft && !part.qualityGiven ? null : `bitrate: ${JSON.stringify(quality)}`, videoCodec ? `codec: ${JSON.stringify(videoCodec)}` : null].filter(Boolean).join(', ')} };
90
104
  const audio = { bitrate: ${JSON.stringify(quality)}${audioCodec ? `, codec: ${JSON.stringify(audioCodec)}` : ''} };
91
105
  const blob = ${JSON.stringify(format)} === 'pdf'
92
106
  ? await movie.exportPDF({ which: ${allStops ? "'stops'" : "'pages'"}, title: document.title })
93
- : await movie.render({ format: ${JSON.stringify(format)}, video, audio${motionBlur ? `, motionBlur: { samples: ${motionBlur}${shutter ? `, shutter: ${shutter}` : ''} }` : ''} });
107
+ : await movie.render({ format: ${JSON.stringify(format)}, video, audio${part.range ? `, range: [${part.range[0] ?? 0}, ${part.range[1] ?? 'movie.duration'}]` : ''}${part.scene ? `, range: ${JSON.stringify(part.scene)}` : ''}${part.scale ? `, scale: ${part.scale}` : ''}${part.draft ? ', draft: true' : ''}${motionBlur ? `, motionBlur: { samples: ${motionBlur}${shutter ? `, shutter: ${shutter}` : ''} }` : ''} });
94
108
  const res = await fetch(${JSON.stringify(UPLOAD)}, { method: 'PUT', body: blob });
95
109
  if (!res.ok) throw new Error('could not hand the file to the command (' + res.status + ')');
96
110
  window.__r = { state: 'done', bytes: blob.size, type: blob.type };
@@ -156,7 +170,7 @@ export async function runRender(opts, log = console.log, progress = () => {}) {
156
170
  }
157
171
  Object.assign(result, { duration: info.duration, frames: info.totalFrames, width: info.width, height: info.height, frameRate: info.frameRate });
158
172
 
159
- await cdp.eval(renderScript(opts.format, opts.quality, opts.videoCodec, opts.audioCodec, opts.motionBlur, opts.shutter, opts.allStops));
173
+ await cdp.eval(renderScript(opts.format, opts.quality, opts.videoCodec, opts.audioCodec, opts.motionBlur, opts.shutter, opts.allStops, { range: opts.range, scene: opts.scene, scale: opts.scale, draft: opts.draft, qualityGiven: opts.qualityGiven }));
160
174
  let state;
161
175
  for (;;) {
162
176
  left();
@@ -190,7 +204,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
190
204
  if (typeof WebSocket === 'undefined') throw new Error('Node >= 22 is needed (built-in WebSocket)');
191
205
  const opts = parseRenderArgs(process.argv.slice(2));
192
206
  if (!opts.page) {
193
- console.error('usage: node ai/tools/render.mjs <page.html> [-o out.mp4] [--format mp4|webm|mov|mkv|pdf] [--quality very-low|low|medium|high|very-high] [--query a=1] [--fail-on-warn] [--quiet] [--timeout S] [--root DIR] [--chrome PATH]');
207
+ console.error('usage: node ai/tools/render.mjs <page.html> [-o out.mp4] [--format mp4|webm|mov|mkv|pdf] [--range A:B | --scene NAME] [--scale S] [--draft] [--quality very-low|low|medium|high|very-high] [--query a=1] [--fail-on-warn] [--quiet] [--timeout S] [--root DIR] [--chrome PATH]');
194
208
  process.exit(2);
195
209
  }
196
210
  let last = -1;
@@ -0,0 +1,5 @@
1
+ export { CompositionSequence } from './chunk-Q3IVBYT7.js';
2
+ import './chunk-ORTYBA42.js';
3
+ import './chunk-DKGG7WD4.js';
4
+ //# sourceMappingURL=Composition-HYAOYPAX.js.map
5
+ //# sourceMappingURL=Composition-HYAOYPAX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-VJWY2NDL.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-HYAOYPAX.js"}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ var chunkETAIVOHP_cjs = require('./chunk-ETAIVOHP.cjs');
4
+ require('./chunk-LBJRXU7N.cjs');
5
+ require('./chunk-UQBGW5DO.cjs');
6
+
7
+
8
+
9
+ Object.defineProperty(exports, "CompositionSequence", {
10
+ enumerable: true,
11
+ get: function () { return chunkETAIVOHP_cjs.CompositionSequence; }
12
+ });
13
+ //# sourceMappingURL=Composition-LMVEASHF.cjs.map
14
+ //# sourceMappingURL=Composition-LMVEASHF.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-3WWNVRR5.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-LMVEASHF.cjs"}
@@ -1,4 +1,4 @@
1
- import { f as Movie } from './Movie-oA0SUAyb.cjs';
1
+ import { g as Movie } from './Movie-CoxF2Z7t.cjs';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
4
  import './types-SS51lP6O.cjs';
@@ -1,4 +1,4 @@
1
- import { f as Movie } from './Movie-DilBC348.js';
1
+ import { g as Movie } from './Movie-DHJRrUfI.js';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
4
  import './types-SS51lP6O.js';
@@ -5,6 +5,12 @@ import { U as Stop, e as AssetSpec, c as CompositionSpec } from './types-SS51lP6
5
5
  interface AudioInspectOptions {
6
6
  /** Width of each loudness window in seconds. Default 0.1. */
7
7
  window?: number;
8
+ /** The scenes to measure one by one (`movie.inspectAudio()` fills this from the movie's named top-level compositions). Default: the whole movie as one. */
9
+ scenes?: Array<{
10
+ name: string;
11
+ start: number;
12
+ end: number;
13
+ }>;
8
14
  }
9
15
  /** One sound measured ON ITS OWN (not mixed), so overlapping layers do not blur it. */
10
16
  interface SoundMeasure {
@@ -44,8 +50,44 @@ interface AudioReport {
44
50
  peakDb: number;
45
51
  brightnessHz: number;
46
52
  }>;
47
- /** Problems worth fixing: limiting, inaudible layers, sounds cut off by the end of the movie. Read this first. */
53
+ /** Problems worth fixing: limiting, clipping, a true peak above 0 dBTP, inaudible layers, sounds cut off by the end of the movie. Read this first. */
48
54
  issues: string[];
55
+ /** The mix as a whole (ITU-R BS.1770 loudness; `integratedLufs` is null for a mix shorter than 0.4 s or with nothing above −70 LUFS). */
56
+ loudness: {
57
+ integratedLufs: number | null;
58
+ truePeakDb: number;
59
+ clippedSamples: number;
60
+ silences: Array<{
61
+ from: number;
62
+ to: number;
63
+ }>;
64
+ };
65
+ /** The same measure for each scene (`silent`: nothing above −60 dBFS). */
66
+ scenes: Array<{
67
+ name: string;
68
+ from: number;
69
+ to: number;
70
+ lufs: number | null;
71
+ peakDb: number;
72
+ silent: boolean;
73
+ }>;
74
+ /** When each sound starts: what a waveform marks. */
75
+ cues: Array<{
76
+ t: number;
77
+ name: string;
78
+ }>;
79
+ /** Advice, not faults: a mix that is quiet or loud for web video, little headroom, silent stretches. */
80
+ notes: string[];
81
+ }
82
+
83
+ interface FontReport {
84
+ /** Text layers none of whose fonts is available here: they are drawn in a fallback. */
85
+ missing: Array<{
86
+ layer: string;
87
+ family: string;
88
+ }>;
89
+ /** Web fonts (`@font-face`) whose file could not be loaded. */
90
+ failed: string[];
49
91
  }
50
92
 
51
93
  interface Rect {
@@ -196,6 +238,15 @@ interface RenderOptions {
196
238
  codec?: string;
197
239
  bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high';
198
240
  };
241
+ /**
242
+ * Only part of the movie: `[from, to]` in seconds, or the name of a top-level layer (its start to its end). The file starts at 0
243
+ * and its sound is cut the same way (with a 10 ms fade on a cut edge). Past the end, backwards or an unknown name is an error.
244
+ */
245
+ range?: [number, number] | string;
246
+ /** Output size relative to the canvas, above 0 and at most 1. Each frame is drawn at full size and copied smaller before it is encoded: the file and the encoding get smaller, the drawing is not faster. */
247
+ scale?: number;
248
+ /** For looking, not for delivering: `scale: 0.5`, `video.bitrate: 'low'` and no motion blur. Anything you set yourself wins. */
249
+ draft?: boolean;
199
250
  }
200
251
  interface SnapshotOptions {
201
252
  /** Motion blur for this picture (see `MovieOptions.motionBlur`); overrides the movie's own setting, `false` turns it off. */
@@ -248,6 +299,19 @@ interface ContactSheetOptions {
248
299
  cellWidth?: number;
249
300
  as?: 'blob' | 'dataURL';
250
301
  }
302
+ interface OnionSkinOptions {
303
+ /** Where the movement starts, in seconds. Default 0. */
304
+ from?: number;
305
+ /** Where it ends, in seconds. Default: the end of the movie. */
306
+ to?: number;
307
+ /** How many frames are overlaid, 1–64, spread evenly from `from` to `to`. Default 8. */
308
+ count?: number;
309
+ /** Output size relative to the canvas, above 0 and at most 4. Default 1. */
310
+ scale?: number;
311
+ /** Motion blur for these pictures (see `MovieOptions.motionBlur`); overrides the movie's own setting, `false` turns it off. */
312
+ motionBlur?: MotionBlurSpec;
313
+ as?: 'blob' | 'dataURL';
314
+ }
251
315
  interface FrameEvent {
252
316
  frame: number;
253
317
  totalFrames: number;
@@ -419,6 +483,17 @@ declare class Movie {
419
483
  motionBlur?: MotionBlurSpec;
420
484
  onImage?: (image: StopImage) => void;
421
485
  }): Promise<Blob>;
486
+ /**
487
+ * One picture of a movement: `count` frames from `from` to `to` drawn one over the other, each later one stronger, so a thing that
488
+ * moves leaves a trail (its path and its easing: even steps = constant speed, bunched = slow) and what stays still stays itself.
489
+ * The movie is left where it was.
490
+ */
491
+ onionSkin(opts?: OnionSkinOptions & {
492
+ as?: 'blob';
493
+ }): Promise<Blob>;
494
+ onionSkin(opts: OnionSkinOptions & {
495
+ as: 'dataURL';
496
+ }): Promise<string>;
422
497
  /**
423
498
  * Several frames on ONE image, each labelled with its frame number and time — the cheapest way to
424
499
  * check a whole animation by eye. Restores the current frame afterwards.
@@ -447,6 +522,11 @@ declare class Movie {
447
522
  * The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
448
523
  * (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
449
524
  */
525
+ /**
526
+ * The fonts of the text layers: `missing` lists layers none of whose `fontFamily` entries is available in this browser (they are
527
+ * drawn in a fallback font), `failed` lists web fonts (`@font-face`) whose file could not be loaded.
528
+ */
529
+ inspectFonts(): FontReport;
450
530
  inspectAudio(opts?: AudioInspectOptions): AudioReport;
451
531
  /**
452
532
  * Draw the stage now. PixiJS's CullerPlugin only culls inside `app.render()` (the ticker), and we render
@@ -509,4 +589,4 @@ declare class Movie {
509
589
  destroy(): Promise<void>;
510
590
  }
511
591
 
512
- export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FrameEvent as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type InspectReport as c, type LoaderOption as d, type MotionBlurSpec as e, Movie as f, type MovieErrorEvent as g, type MovieOptions as h, type SoundMeasure as i, type StopEvent as j, type StopImage as k, type StopImagesOptions as l, type TimelineHtmlOptions as m, type TimelineRow as n, type TimelineSvgOptions as o, type TimelineTransition as p, dismissLoader as q, failLoader as r, resolveLoader as s };
592
+ export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type LoaderOption as e, type MotionBlurSpec as f, Movie as g, type MovieErrorEvent as h, type MovieOptions as i, type SoundMeasure as j, type StopEvent as k, type StopImage as l, type StopImagesOptions as m, type TimelineHtmlOptions as n, type TimelineRow as o, type TimelineSvgOptions as p, type TimelineTransition as q, dismissLoader as r, failLoader as s, resolveLoader as t };
@@ -5,6 +5,12 @@ import { U as Stop, e as AssetSpec, c as CompositionSpec } from './types-SS51lP6
5
5
  interface AudioInspectOptions {
6
6
  /** Width of each loudness window in seconds. Default 0.1. */
7
7
  window?: number;
8
+ /** The scenes to measure one by one (`movie.inspectAudio()` fills this from the movie's named top-level compositions). Default: the whole movie as one. */
9
+ scenes?: Array<{
10
+ name: string;
11
+ start: number;
12
+ end: number;
13
+ }>;
8
14
  }
9
15
  /** One sound measured ON ITS OWN (not mixed), so overlapping layers do not blur it. */
10
16
  interface SoundMeasure {
@@ -44,8 +50,44 @@ interface AudioReport {
44
50
  peakDb: number;
45
51
  brightnessHz: number;
46
52
  }>;
47
- /** Problems worth fixing: limiting, inaudible layers, sounds cut off by the end of the movie. Read this first. */
53
+ /** Problems worth fixing: limiting, clipping, a true peak above 0 dBTP, inaudible layers, sounds cut off by the end of the movie. Read this first. */
48
54
  issues: string[];
55
+ /** The mix as a whole (ITU-R BS.1770 loudness; `integratedLufs` is null for a mix shorter than 0.4 s or with nothing above −70 LUFS). */
56
+ loudness: {
57
+ integratedLufs: number | null;
58
+ truePeakDb: number;
59
+ clippedSamples: number;
60
+ silences: Array<{
61
+ from: number;
62
+ to: number;
63
+ }>;
64
+ };
65
+ /** The same measure for each scene (`silent`: nothing above −60 dBFS). */
66
+ scenes: Array<{
67
+ name: string;
68
+ from: number;
69
+ to: number;
70
+ lufs: number | null;
71
+ peakDb: number;
72
+ silent: boolean;
73
+ }>;
74
+ /** When each sound starts: what a waveform marks. */
75
+ cues: Array<{
76
+ t: number;
77
+ name: string;
78
+ }>;
79
+ /** Advice, not faults: a mix that is quiet or loud for web video, little headroom, silent stretches. */
80
+ notes: string[];
81
+ }
82
+
83
+ interface FontReport {
84
+ /** Text layers none of whose fonts is available here: they are drawn in a fallback. */
85
+ missing: Array<{
86
+ layer: string;
87
+ family: string;
88
+ }>;
89
+ /** Web fonts (`@font-face`) whose file could not be loaded. */
90
+ failed: string[];
49
91
  }
50
92
 
51
93
  interface Rect {
@@ -196,6 +238,15 @@ interface RenderOptions {
196
238
  codec?: string;
197
239
  bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high';
198
240
  };
241
+ /**
242
+ * Only part of the movie: `[from, to]` in seconds, or the name of a top-level layer (its start to its end). The file starts at 0
243
+ * and its sound is cut the same way (with a 10 ms fade on a cut edge). Past the end, backwards or an unknown name is an error.
244
+ */
245
+ range?: [number, number] | string;
246
+ /** Output size relative to the canvas, above 0 and at most 1. Each frame is drawn at full size and copied smaller before it is encoded: the file and the encoding get smaller, the drawing is not faster. */
247
+ scale?: number;
248
+ /** For looking, not for delivering: `scale: 0.5`, `video.bitrate: 'low'` and no motion blur. Anything you set yourself wins. */
249
+ draft?: boolean;
199
250
  }
200
251
  interface SnapshotOptions {
201
252
  /** Motion blur for this picture (see `MovieOptions.motionBlur`); overrides the movie's own setting, `false` turns it off. */
@@ -248,6 +299,19 @@ interface ContactSheetOptions {
248
299
  cellWidth?: number;
249
300
  as?: 'blob' | 'dataURL';
250
301
  }
302
+ interface OnionSkinOptions {
303
+ /** Where the movement starts, in seconds. Default 0. */
304
+ from?: number;
305
+ /** Where it ends, in seconds. Default: the end of the movie. */
306
+ to?: number;
307
+ /** How many frames are overlaid, 1–64, spread evenly from `from` to `to`. Default 8. */
308
+ count?: number;
309
+ /** Output size relative to the canvas, above 0 and at most 4. Default 1. */
310
+ scale?: number;
311
+ /** Motion blur for these pictures (see `MovieOptions.motionBlur`); overrides the movie's own setting, `false` turns it off. */
312
+ motionBlur?: MotionBlurSpec;
313
+ as?: 'blob' | 'dataURL';
314
+ }
251
315
  interface FrameEvent {
252
316
  frame: number;
253
317
  totalFrames: number;
@@ -419,6 +483,17 @@ declare class Movie {
419
483
  motionBlur?: MotionBlurSpec;
420
484
  onImage?: (image: StopImage) => void;
421
485
  }): Promise<Blob>;
486
+ /**
487
+ * One picture of a movement: `count` frames from `from` to `to` drawn one over the other, each later one stronger, so a thing that
488
+ * moves leaves a trail (its path and its easing: even steps = constant speed, bunched = slow) and what stays still stays itself.
489
+ * The movie is left where it was.
490
+ */
491
+ onionSkin(opts?: OnionSkinOptions & {
492
+ as?: 'blob';
493
+ }): Promise<Blob>;
494
+ onionSkin(opts: OnionSkinOptions & {
495
+ as: 'dataURL';
496
+ }): Promise<string>;
422
497
  /**
423
498
  * Several frames on ONE image, each labelled with its frame number and time — the cheapest way to
424
499
  * check a whole animation by eye. Restores the current frame afterwards.
@@ -447,6 +522,11 @@ declare class Movie {
447
522
  * The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
448
523
  * (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
449
524
  */
525
+ /**
526
+ * The fonts of the text layers: `missing` lists layers none of whose `fontFamily` entries is available in this browser (they are
527
+ * drawn in a fallback font), `failed` lists web fonts (`@font-face`) whose file could not be loaded.
528
+ */
529
+ inspectFonts(): FontReport;
450
530
  inspectAudio(opts?: AudioInspectOptions): AudioReport;
451
531
  /**
452
532
  * Draw the stage now. PixiJS's CullerPlugin only culls inside `app.render()` (the ticker), and we render
@@ -509,4 +589,4 @@ declare class Movie {
509
589
  destroy(): Promise<void>;
510
590
  }
511
591
 
512
- export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FrameEvent as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type InspectReport as c, type LoaderOption as d, type MotionBlurSpec as e, Movie as f, type MovieErrorEvent as g, type MovieOptions as h, type SoundMeasure as i, type StopEvent as j, type StopImage as k, type StopImagesOptions as l, type TimelineHtmlOptions as m, type TimelineRow as n, type TimelineSvgOptions as o, type TimelineTransition as p, dismissLoader as q, failLoader as r, resolveLoader as s };
592
+ export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type LoaderOption as e, type MotionBlurSpec as f, Movie as g, type MovieErrorEvent as h, type MovieOptions as i, type SoundMeasure as j, type StopEvent as k, type StopImage as l, type StopImagesOptions as m, type TimelineHtmlOptions as n, type TimelineRow as o, type TimelineSvgOptions as p, type TimelineTransition as q, dismissLoader as r, failLoader as s, resolveLoader as t };
@@ -1,4 +1,4 @@
1
- import { f as Movie } from './Movie-oA0SUAyb.cjs';
1
+ import { g as Movie } from './Movie-CoxF2Z7t.cjs';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
4
  import './types-SS51lP6O.cjs';
@@ -1,4 +1,4 @@
1
- import { f as Movie } from './Movie-DilBC348.js';
1
+ import { g as Movie } from './Movie-DHJRrUfI.js';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
4
  import './types-SS51lP6O.js';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkFJA5VUOF_cjs = require('./chunk-FJA5VUOF.cjs');
3
+ var chunkLBJRXU7N_cjs = require('./chunk-LBJRXU7N.cjs');
4
4
  var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
5
5
  var pixi_js = require('pixi.js');
6
6
  var gsap = require('gsap');
@@ -65,8 +65,8 @@ function readLayerTransform(target) {
65
65
  x: c.x,
66
66
  y: c.y,
67
67
  z: c.z ?? 0,
68
- rotationX: (c.rotationX ?? 0) * chunkFJA5VUOF_cjs.DEG,
69
- rotationY: (c.rotationY ?? 0) * chunkFJA5VUOF_cjs.DEG,
68
+ rotationX: (c.rotationX ?? 0) * chunkLBJRXU7N_cjs.DEG,
69
+ rotationY: (c.rotationY ?? 0) * chunkLBJRXU7N_cjs.DEG,
70
70
  rotationZ: c.rotation,
71
71
  scaleX: c.scale.x,
72
72
  scaleY: c.scale.y,
@@ -111,19 +111,19 @@ var Layer3D = class {
111
111
  if (!(needW >= 1 && needH >= 1) || !Number.isFinite(bounds.x) || !Number.isFinite(bounds.y)) return this.hide();
112
112
  const size = this.sizeFor(needW, needH);
113
113
  const frame = { x: bounds.x - pad, y: bounds.y - pad, width: size.w, height: size.h };
114
- const projected = chunkFJA5VUOF_cjs.projectLayer(readLayerTransform(target), frame, basis);
114
+ const projected = chunkLBJRXU7N_cjs.projectLayer(readLayerTransform(target), frame, basis);
115
115
  this.depth = projected.depth;
116
116
  if (!projected.visible) {
117
117
  if (!this.warnedBehind) {
118
118
  this.warnedBehind = true;
119
119
  if (this.seq.spec.hideBehindCamera) return this.hide();
120
- if (projected.depth > chunkFJA5VUOF_cjs.NEAR) {
120
+ if (projected.depth > chunkLBJRXU7N_cjs.NEAR) {
121
121
  console.warn(
122
- `pixi-effects: ${chunkFJA5VUOF_cjs.describeLayer(this.seq.spec)}: part of it (a corner) reaches the camera plane (z = ${(target.z ?? 0).toFixed(0)}, rotationX ${(target.rotationX ?? 0).toFixed(0)}\xB0, rotationY ${(target.rotationY ?? 0).toFixed(0)}\xB0), and a layer is hidden whole when any corner is at or behind it. Tilt it less (rotationX / rotationY), make it smaller, or keep the camera further away from it (the default camera sits at z = (height/2)/tan(fov/2), about 989 for 720p at fov 40).`
122
+ `pixi-effects: ${chunkLBJRXU7N_cjs.describeLayer(this.seq.spec)}: part of it (a corner) reaches the camera plane (z = ${(target.z ?? 0).toFixed(0)}, rotationX ${(target.rotationX ?? 0).toFixed(0)}\xB0, rotationY ${(target.rotationY ?? 0).toFixed(0)}\xB0), and a layer is hidden whole when any corner is at or behind it. Tilt it less (rotationX / rotationY), make it smaller, or keep the camera further away from it (the default camera sits at z = (height/2)/tan(fov/2), about 989 for 720p at fov 40).`
123
123
  );
124
124
  } else {
125
125
  console.warn(
126
- `pixi-effects: ${chunkFJA5VUOF_cjs.describeLayer(this.seq.spec)} is at or behind the camera plane (z = ${(target.z ?? 0).toFixed(0)}) and is hidden. If that is unintended, lower its z or move the camera back (the default camera sits at z = (height/2)/tan(fov/2), about 989 for 720p at fov 40).`
126
+ `pixi-effects: ${chunkLBJRXU7N_cjs.describeLayer(this.seq.spec)} is at or behind the camera plane (z = ${(target.z ?? 0).toFixed(0)}) and is hidden. If that is unintended, lower its z or move the camera back (the default camera sits at z = (height/2)/tan(fov/2), about 989 for 720p at fov 40).`
127
127
  );
128
128
  }
129
129
  }
@@ -211,8 +211,8 @@ var CAMERA_PROPS = ["x", "y", "z", "fov", "lookAtX", "lookAtY", "lookAtZ", "offs
211
211
  var NEEDS_THREE_D = ["z", "rotationX", "rotationY"];
212
212
  var SKEW_KEYS = ["skew", "skewX", "skewY"];
213
213
  function lintSequence(spec, warn = defaultWarn) {
214
- const who = chunkFJA5VUOF_cjs.describeLayer(spec);
215
- const keys = chunkFJA5VUOF_cjs.collectPropKeys(spec);
214
+ const who = chunkLBJRXU7N_cjs.describeLayer(spec);
215
+ const keys = chunkLBJRXU7N_cjs.collectPropKeys(spec);
216
216
  const explained = /* @__PURE__ */ new Set();
217
217
  for (const k of keys) {
218
218
  const hint = LAYER_ALIASES[k] ?? (spec.type === "camera" ? CAMERA_ALIASES[k] : void 0);
@@ -258,7 +258,7 @@ function applyBlendMode(spec, display) {
258
258
  const mode = spec.blendMode;
259
259
  if (mode === void 0) return;
260
260
  if (!MODES.includes(mode)) {
261
- console.warn(`pixi-effects: ${chunkFJA5VUOF_cjs.describeLayer(spec)}: blendMode "${mode}" is not supported (use ${MODES.join(", ")})`);
261
+ console.warn(`pixi-effects: ${chunkLBJRXU7N_cjs.describeLayer(spec)}: blendMode "${mode}" is not supported (use ${MODES.join(", ")})`);
262
262
  return;
263
263
  }
264
264
  const filters = display.filters;
@@ -270,7 +270,7 @@ function applyBlendMode(spec, display) {
270
270
  }
271
271
 
272
272
  // src/sequences/Composition.ts
273
- var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
273
+ var CompositionSequence = class extends chunkLBJRXU7N_cjs.Sequence {
274
274
  _innerContainer = null;
275
275
  _compositionShape = null;
276
276
  _children = [];
@@ -304,18 +304,18 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
304
304
  const lateKeyframes = [];
305
305
  for (const s of this.spec.sequences ?? []) {
306
306
  lintSequence(s);
307
- chunkFJA5VUOF_cjs.lintTiming(s, this.duration, (message, kind) => kind === "late-keyframe" ? lateKeyframes.push(message) : console.warn(message));
308
- chunkFJA5VUOF_cjs.lintText(s);
307
+ chunkLBJRXU7N_cjs.lintTiming(s, this.duration, (message, kind) => kind === "late-keyframe" ? lateKeyframes.push(message) : console.warn(message));
308
+ chunkLBJRXU7N_cjs.lintText(s);
309
309
  }
310
- for (const message of chunkFJA5VUOF_cjs.summarizeWarnings(lateKeyframes)) console.warn(message);
311
- this._children = await chunkFJA5VUOF_cjs.buildSequenceTree(
310
+ for (const message of chunkLBJRXU7N_cjs.summarizeWarnings(lateKeyframes)) console.warn(message);
311
+ this._children = await chunkLBJRXU7N_cjs.buildSequenceTree(
312
312
  this.spec.sequences ?? [],
313
313
  this._compositionShape,
314
314
  this.root
315
315
  );
316
316
  this._resolveParents();
317
317
  for (const child of this._children) {
318
- if (child instanceof chunkFJA5VUOF_cjs.CameraSequence) {
318
+ if (child instanceof chunkLBJRXU7N_cjs.CameraSequence) {
319
319
  this._cameras.push(child);
320
320
  continue;
321
321
  }
@@ -330,7 +330,7 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
330
330
  this._visual.push({ seq: child, layer });
331
331
  applyBlendMode(child.spec, layer.display);
332
332
  if (maskSpec) {
333
- console.warn(`pixi-effects: ${chunkFJA5VUOF_cjs.describeLayer(child.spec)}: mask is not supported on threeD layers yet; ignored`);
333
+ console.warn(`pixi-effects: ${chunkLBJRXU7N_cjs.describeLayer(child.spec)}: mask is not supported on threeD layers yet; ignored`);
334
334
  }
335
335
  continue;
336
336
  }
@@ -340,13 +340,18 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
340
340
  applyBlendMode(child.spec, child.target);
341
341
  if (maskSpec) {
342
342
  const lifetime = maskSpec.at === void 0 ? { at: child.at, duration: maskSpec.duration ?? child.duration } : {};
343
- const built = await chunkFJA5VUOF_cjs.buildSequenceTree([{ ...maskSpec, ...lifetime }], this._compositionShape, this.root);
343
+ const built = await chunkLBJRXU7N_cjs.buildSequenceTree([{ ...maskSpec, ...lifetime }], this._compositionShape, this.root);
344
344
  const maskSeq = built[0];
345
345
  if (maskSeq?.target) {
346
346
  into.addChild(maskSeq.target);
347
347
  const inverse = child.spec.maskInverted ?? false;
348
348
  const t = child.target;
349
- if (typeof t.setMask === "function") {
349
+ if (inverse || maskSpec.type === "text") {
350
+ const effect = new pixi_js.AlphaMask({ mask: maskSeq.target });
351
+ effect.inverse = inverse;
352
+ t.setMask?.({ mask: void 0, inverse });
353
+ t.addEffect(effect);
354
+ } else if (typeof t.setMask === "function") {
350
355
  t.setMask({ mask: maskSeq.target, inverse });
351
356
  } else {
352
357
  t.mask = maskSeq.target;
@@ -390,7 +395,7 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
390
395
  for (let p = this._parentOf.get(seq); p; p = this._parentOf.get(p)) out.push(p);
391
396
  return out;
392
397
  };
393
- return [...this._visual.map((v) => v.seq), ...this._nested].filter((seq) => !(seq instanceof chunkFJA5VUOF_cjs.NullSequence)).map((seq) => {
398
+ return [...this._visual.map((v) => v.seq), ...this._nested].filter((seq) => !(seq instanceof chunkLBJRXU7N_cjs.NullSequence)).map((seq) => {
394
399
  const layer = this._visual.find((v) => v.seq === seq)?.layer ?? null;
395
400
  return {
396
401
  seq,
@@ -402,14 +407,14 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
402
407
  }
403
408
  /** Match every `parent: 'name'` to a null layer; anything that cannot work is said out loud and drawn unparented. */
404
409
  _resolveParents() {
405
- const nulls = this._children.filter((c) => c instanceof chunkFJA5VUOF_cjs.NullSequence);
410
+ const nulls = this._children.filter((c) => c instanceof chunkLBJRXU7N_cjs.NullSequence);
406
411
  const nullNames = nulls.map((n) => n.spec.name).filter((n) => !!n);
407
412
  const warned = /* @__PURE__ */ new Set();
408
413
  for (const child of this._children) {
409
414
  const want = child.spec.parent;
410
415
  if (want === void 0) continue;
411
- const who = chunkFJA5VUOF_cjs.describeLayer(child.spec);
412
- if (child instanceof chunkFJA5VUOF_cjs.CameraSequence) {
416
+ const who = chunkLBJRXU7N_cjs.describeLayer(child.spec);
417
+ if (child instanceof chunkLBJRXU7N_cjs.CameraSequence) {
413
418
  console.warn(`pixi-effects: ${who}: a camera cannot have a parent; ignored`);
414
419
  continue;
415
420
  }
@@ -423,7 +428,7 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
423
428
  console.warn(`pixi-effects: ${who}: parent "${want}": no layer with that name${hint ? `; did you mean "${hint}"?` : ""} (the layer is drawn without a parent)`);
424
429
  continue;
425
430
  }
426
- const holder = same.find((c) => c instanceof chunkFJA5VUOF_cjs.NullSequence);
431
+ const holder = same.find((c) => c instanceof chunkLBJRXU7N_cjs.NullSequence);
427
432
  if (!holder) {
428
433
  console.warn(`pixi-effects: ${who}: parent "${want}" is a ${same[0].spec.type} layer; only { type: 'null' } layers can be parents (the layer is drawn without a parent)`);
429
434
  continue;
@@ -466,7 +471,7 @@ var CompositionSequence = class extends chunkFJA5VUOF_cjs.Sequence {
466
471
  t,
467
472
  compEnd
468
473
  );
469
- const basis = chunkFJA5VUOF_cjs.cameraBasis(active ? active.cam.state() : chunkFJA5VUOF_cjs.homeCamera(width, height), width, height);
474
+ const basis = chunkLBJRXU7N_cjs.cameraBasis(active ? active.cam.state() : chunkLBJRXU7N_cjs.homeCamera(width, height), width, height);
470
475
  for (const layer of this._layers3d) layer.update(host, basis);
471
476
  const order = assignDepthOrder(
472
477
  this._visual.map((v, i) => ({ stackIndex: i, threeD: v.layer !== null, depth: v.layer?.depth ?? 0 }))
@@ -495,5 +500,5 @@ function frameOf(child) {
495
500
  }
496
501
 
497
502
  exports.CompositionSequence = CompositionSequence;
498
- //# sourceMappingURL=chunk-2ROVCPSL.cjs.map
499
- //# sourceMappingURL=chunk-2ROVCPSL.cjs.map
503
+ //# sourceMappingURL=chunk-ETAIVOHP.cjs.map
504
+ //# sourceMappingURL=chunk-ETAIVOHP.cjs.map