pixi-effects 0.36.0 → 0.38.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 (97) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +6 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +12 -2
  5. package/ai/tools/lookup.mjs +7 -3
  6. package/ai/tools/media-page.html +122 -0
  7. package/ai/tools/media.mjs +748 -0
  8. package/ai/tools/mediaBrowser.mjs +47 -0
  9. package/ai/tools/pixi-effects.mjs +11 -2
  10. package/dist/{Base-CmiFiwQM.d.cts → Base-BKKwxavW.d.cts} +1 -1
  11. package/dist/{Base-CdF7AJop.d.ts → Base-ioh_AjLB.d.ts} +1 -1
  12. package/dist/Composition-23KGIJX4.cjs +16 -0
  13. package/dist/{Composition-XRLYR6GL.cjs.map → Composition-23KGIJX4.cjs.map} +1 -1
  14. package/dist/Composition-5SQXSZNA.js +7 -0
  15. package/dist/{Composition-BMRCE5FH.js.map → Composition-5SQXSZNA.js.map} +1 -1
  16. package/dist/Controller.d.cts +3 -2
  17. package/dist/Controller.d.ts +3 -2
  18. package/dist/{Movie-D5WFfZdZ.d.ts → Movie-BtwFI2ne.d.ts} +23 -162
  19. package/dist/{Movie-Bh1HPdKw.d.cts → Movie-CSWUTnMd.d.cts} +23 -162
  20. package/dist/{Spans-MWP2FX3T.js → Spans-4IBQASGK.js} +6 -6
  21. package/dist/{Spans-MWP2FX3T.js.map → Spans-4IBQASGK.js.map} +1 -1
  22. package/dist/{Spans-4IRBSOLI.cjs → Spans-WJFPHMSQ.cjs} +11 -11
  23. package/dist/{Spans-4IRBSOLI.cjs.map → Spans-WJFPHMSQ.cjs.map} +1 -1
  24. package/dist/{blendModes-KU7XLV7U.cjs → blendModes-FD3THLNC.cjs} +5 -5
  25. package/dist/{blendModes-KU7XLV7U.cjs.map → blendModes-FD3THLNC.cjs.map} +1 -1
  26. package/dist/{blendModes-OOI3T6BE.js → blendModes-FMYO6SQN.js} +4 -4
  27. package/dist/{blendModes-OOI3T6BE.js.map → blendModes-FMYO6SQN.js.map} +1 -1
  28. package/dist/{chunk-2JFFKHXO.js → chunk-32MMNDDN.js} +4 -3
  29. package/dist/chunk-32MMNDDN.js.map +1 -0
  30. package/dist/{chunk-42ML5AKN.cjs → chunk-3SAS3ZE5.cjs} +4 -3
  31. package/dist/chunk-3SAS3ZE5.cjs.map +1 -0
  32. package/dist/{chunk-AZ6AINSW.js → chunk-4CSL5QSL.js} +6 -6
  33. package/dist/chunk-4CSL5QSL.js.map +1 -0
  34. package/dist/{chunk-KJSEQVBV.cjs → chunk-7NXKBRT7.cjs} +333 -26
  35. package/dist/chunk-7NXKBRT7.cjs.map +1 -0
  36. package/dist/{chunk-Y5UWFH6Y.cjs → chunk-FE42PKHP.cjs} +92 -92
  37. package/dist/chunk-FE42PKHP.cjs.map +1 -0
  38. package/dist/{chunk-NEUPEENI.cjs → chunk-JN7JHNZR.cjs} +5 -5
  39. package/dist/{chunk-NEUPEENI.cjs.map → chunk-JN7JHNZR.cjs.map} +1 -1
  40. package/dist/{chunk-CC7SUR7Q.cjs → chunk-KNMDDUA3.cjs} +129 -453
  41. package/dist/chunk-KNMDDUA3.cjs.map +1 -0
  42. package/dist/chunk-LQDDDIC4.cjs +431 -0
  43. package/dist/chunk-LQDDDIC4.cjs.map +1 -0
  44. package/dist/{chunk-G3DX65HO.js → chunk-UDTLYYGY.js} +311 -6
  45. package/dist/chunk-UDTLYYGY.js.map +1 -0
  46. package/dist/chunk-UHNSL2KJ.js +425 -0
  47. package/dist/chunk-UHNSL2KJ.js.map +1 -0
  48. package/dist/{chunk-WIIU42FH.js → chunk-VNP7QCJE.js} +4 -4
  49. package/dist/{chunk-WIIU42FH.js.map → chunk-VNP7QCJE.js.map} +1 -1
  50. package/dist/{chunk-VF56PRAA.js → chunk-XOCRS5MW.js} +92 -417
  51. package/dist/chunk-XOCRS5MW.js.map +1 -0
  52. package/dist/index.cjs +46 -33
  53. package/dist/index.d.cts +33 -6
  54. package/dist/index.d.ts +33 -6
  55. package/dist/index.js +6 -5
  56. package/dist/loader.css +1 -1
  57. package/dist/present.cjs +6 -5
  58. package/dist/present.cjs.map +1 -1
  59. package/dist/present.d.cts +3 -2
  60. package/dist/present.d.ts +3 -2
  61. package/dist/present.js +6 -5
  62. package/dist/present.js.map +1 -1
  63. package/dist/sound.cjs +12 -0
  64. package/dist/sound.cjs.map +1 -0
  65. package/dist/sound.d.cts +1 -0
  66. package/dist/sound.d.ts +1 -0
  67. package/dist/sound.js +3 -0
  68. package/dist/sound.js.map +1 -0
  69. package/dist/soundEntry-DT13DNt8.d.cts +191 -0
  70. package/dist/soundEntry-DT13DNt8.d.ts +191 -0
  71. package/dist/three.cjs +6 -6
  72. package/dist/three.d.cts +2 -2
  73. package/dist/three.d.ts +2 -2
  74. package/dist/three.js +2 -2
  75. package/dist/{types-B6zDTU2V.d.ts → types-BXi-0iZS.d.cts} +53 -2
  76. package/dist/{types-B6zDTU2V.d.cts → types-BXi-0iZS.d.ts} +53 -2
  77. package/docs/api.md +19 -0
  78. package/docs/dsl.md +35 -0
  79. package/docs/media.md +136 -0
  80. package/llms-full.txt +385 -6
  81. package/llms.txt +1 -0
  82. package/package.json +2 -1
  83. package/skills/pixi-effects/SKILL.md +7 -2
  84. package/skills/pixi-effects/reference/cheatsheet.md +2 -0
  85. package/skills/pixi-effects/reference/pitfalls.md +4 -0
  86. package/skills/pixi-effects/reference/recipes.md +177 -3
  87. package/skills/pixi-effects/template.html +6 -6
  88. package/dist/Composition-BMRCE5FH.js +0 -7
  89. package/dist/Composition-XRLYR6GL.cjs +0 -16
  90. package/dist/chunk-2JFFKHXO.js.map +0 -1
  91. package/dist/chunk-42ML5AKN.cjs.map +0 -1
  92. package/dist/chunk-AZ6AINSW.js.map +0 -1
  93. package/dist/chunk-CC7SUR7Q.cjs.map +0 -1
  94. package/dist/chunk-G3DX65HO.js.map +0 -1
  95. package/dist/chunk-KJSEQVBV.cjs.map +0 -1
  96. package/dist/chunk-VF56PRAA.js.map +0 -1
  97. package/dist/chunk-Y5UWFH6Y.cjs.map +0 -1
@@ -0,0 +1,47 @@
1
+ // The browser engine of the media commands: the tool's local server, a headless Chrome (the one `check` and `render` use) and the page ai/tools/media-page.html.
2
+ // await withBrowser({ chrome, inputs: Map(id -> path or URL), outDir }, async ({ call }) => call('frames', { id, times, size, sheet }))
3
+ // `call` runs `window.media.<name>(args)` in the page and returns its JSON, or throws the sentence the page threw.
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+
9
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
10
+
11
+ export async function withBrowser({ chrome, inputs, outDir, requestLog = null, timeoutSec = 180 }, fn) {
12
+ const check = await import(pathToFileURL(path.join(HERE, 'check.mjs')).href);
13
+ const { serveMedia } = await import(pathToFileURL(path.join(HERE, 'media.mjs')).href);
14
+ const chromePath = chrome ?? check.findChrome();
15
+ if (!chromePath || !fs.existsSync(chromePath)) throw new Error(`Chrome was not found${chromePath ? ` at ${chromePath}` : ''}: this command decodes video in a browser; install Chrome, or pass --chrome PATH (or set CHROME_PATH) with the right path`);
16
+ const srv = await serveMedia({ inputs, outDir, requestLog });
17
+ const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pixi-effects-media-'));
18
+ let proc = null;
19
+ try {
20
+ const launched = await check.launchChrome(chromePath, userDataDir);
21
+ proc = launched.proc;
22
+ const cdp = launched.cdp;
23
+ await cdp.send('Runtime.enable');
24
+ await cdp.send('Page.enable');
25
+ await cdp.send('Page.navigate', { url: `http://127.0.0.1:${srv.port}/ai/tools/media-page.html` });
26
+ const deadline = Date.now() + timeoutSec * 1000;
27
+ while (!(await cdp.eval('window.__mediaReady === true').catch(() => false))) {
28
+ if (Date.now() > deadline) throw new Error('the media page did not start in the browser');
29
+ await check.sleep(100);
30
+ }
31
+ const call = async (name, args) => {
32
+ const run = cdp.eval(`(async () => { try { return { ok: await window.media.${name}(${JSON.stringify(args)}) }; } catch (e) { return { error: String(e && e.message || e) }; } })()`);
33
+ let timer;
34
+ const timeout = new Promise((_, bad) => { timer = setTimeout(() => bad(new Error(`timed out after ${timeoutSec} s`)), Math.max(1000, deadline - Date.now())); });
35
+ try {
36
+ const r = await Promise.race([run, timeout]);
37
+ if (r.error) throw new Error(r.error);
38
+ return r.ok;
39
+ } finally { clearTimeout(timer); }
40
+ };
41
+ return await fn({ call });
42
+ } finally {
43
+ try { proc?.kill(); } catch { /* gone */ }
44
+ await srv.close();
45
+ try { fs.rmSync(userDataDir, { recursive: true, force: true }); } catch { /* held a moment longer */ }
46
+ }
47
+ }
@@ -15,11 +15,19 @@ import { dirname, resolve } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { runLookup, LOOKUP_COMMANDS } from './lookup.mjs';
17
17
 
18
+ const MEDIA = ['probe', 'trim', 'frames', 'sound', 'audio', 'concat']; // footage tools: one file, `media.mjs`, which gets the command word too
19
+
18
20
  const TOOLS = { check: 'check.mjs', render: 'render.mjs', view: 'view.mjs' };
19
21
  const USAGE = `usage:
20
22
  pixi-effects check <page.html> [--at 3,6] [--out dir] review a video page: warnings, layout, sound, contact sheet, real export
21
23
  pixi-effects render <page.html> -o <file.mp4> export the video
22
24
  pixi-effects view <page.html> look at it in a browser, with a timeline
25
+ pixi-effects probe <file|url> what is in a video or audio file (codecs, size, duration), with no ffmpeg
26
+ pixi-effects trim <file|url> <start> <end> -o <out> cut a file (by copy when it can) and say the real times
27
+ pixi-effects frames <file|url> [--at 1,50%,end] [--sheet] pictures of a video at those times
28
+ pixi-effects sound <file|url> [--waveform] loudness, peak, silences of a file's sound
29
+ pixi-effects audio <file|url> -o <out.m4a> the sound of a file, copied out
30
+ pixi-effects concat <a> <b> … -o <out> join clips by copy and say where each starts
23
31
  pixi-effects new <name> [--size 1080x1920] [--duration 20] a page to start from (<name>.html), 1280x720 and 6 s unless asked
24
32
  pixi-effects recipe [id] the tested recipes; with an id, one of them with its code
25
33
  pixi-effects help <word> the docs about a word (mask, threeD, keyframes, music …)
@@ -28,7 +36,7 @@ const USAGE = `usage:
28
36
  function nearest(word) {
29
37
  const d = (a, b) => { const m = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]); for (let j = 1; j <= b.length; j++) m[0][j] = j;
30
38
  for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)); return m[a.length][b.length]; };
31
- const best = [...Object.keys(TOOLS), ...LOOKUP_COMMANDS].map(k => [k, d(word, k)]).sort((x, y) => x[1] - y[1])[0];
39
+ const best = [...Object.keys(TOOLS), ...MEDIA, ...LOOKUP_COMMANDS].map(k => [k, d(word, k)]).sort((x, y) => x[1] - y[1])[0];
32
40
  return best[1] <= 2 ? best[0] : null;
33
41
  }
34
42
 
@@ -37,9 +45,10 @@ export function route(argv) {
37
45
  const [cmd, ...args] = argv;
38
46
  if (!cmd || cmd === '--help' || cmd === '-h') return { usage: USAGE };
39
47
  if (TOOLS[cmd]) return { tool: TOOLS[cmd], args };
48
+ if (MEDIA.includes(cmd)) return { tool: 'media.mjs', args: [cmd, ...args] };
40
49
  if (LOOKUP_COMMANDS.includes(cmd)) return { inline: cmd, args };
41
50
  const guess = /\.html?$/i.test(cmd) ? 'check' : nearest(cmd);
42
- return { error: `pixi-effects: unknown command "${cmd}"${guess ? `; did you mean "${guess}"?` : ''} The commands are: ${[...Object.keys(TOOLS), ...LOOKUP_COMMANDS].join(', ')}.\n${USAGE}` };
51
+ return { error: `pixi-effects: unknown command "${cmd}"${guess ? `; did you mean "${guess}"?` : ''} The commands are: ${[...Object.keys(TOOLS), ...MEDIA, ...LOOKUP_COMMANDS].join(', ')}.\n${USAGE}` };
43
52
  }
44
53
 
45
54
  const self = fileURLToPath(import.meta.url);
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape } from './types-B6zDTU2V.cjs';
2
+ import { a as SequenceSpec, C as CompositionShape } from './types-BXi-0iZS.cjs';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape } from './types-B6zDTU2V.js';
2
+ import { a as SequenceSpec, C as CompositionShape } from './types-BXi-0iZS.js';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var chunkFE42PKHP_cjs = require('./chunk-FE42PKHP.cjs');
4
+ require('./chunk-7NXKBRT7.cjs');
5
+ require('./chunk-JN7JHNZR.cjs');
6
+ require('./chunk-3SAS3ZE5.cjs');
7
+ require('./chunk-TWZNOUM7.cjs');
8
+
9
+
10
+
11
+ Object.defineProperty(exports, "CompositionSequence", {
12
+ enumerable: true,
13
+ get: function () { return chunkFE42PKHP_cjs.CompositionSequence; }
14
+ });
15
+ //# sourceMappingURL=Composition-23KGIJX4.cjs.map
16
+ //# sourceMappingURL=Composition-23KGIJX4.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-XRLYR6GL.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-23KGIJX4.cjs"}
@@ -0,0 +1,7 @@
1
+ export { CompositionSequence } from './chunk-4CSL5QSL.js';
2
+ import './chunk-UDTLYYGY.js';
3
+ import './chunk-VNP7QCJE.js';
4
+ import './chunk-32MMNDDN.js';
5
+ import './chunk-BXOCEZ4U.js';
6
+ //# sourceMappingURL=Composition-5SQXSZNA.js.map
7
+ //# sourceMappingURL=Composition-5SQXSZNA.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-BMRCE5FH.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-5SQXSZNA.js"}
@@ -1,6 +1,7 @@
1
- import { l as Movie } from './Movie-Bh1HPdKw.cjs';
1
+ import { M as Movie } from './Movie-CSWUTnMd.cjs';
2
2
  import 'pixi.js';
3
- import './types-B6zDTU2V.cjs';
3
+ import './soundEntry-DT13DNt8.cjs';
4
+ import './types-BXi-0iZS.cjs';
4
5
 
5
6
  /**
6
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,6 +1,7 @@
1
- import { l as Movie } from './Movie-D5WFfZdZ.js';
1
+ import { M as Movie } from './Movie-BtwFI2ne.js';
2
2
  import 'pixi.js';
3
- import './types-B6zDTU2V.js';
3
+ import './soundEntry-DT13DNt8.js';
4
+ import './types-BXi-0iZS.js';
4
5
 
5
6
  /**
6
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,5 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
- import { Z as Stop, c as AssetSpec, k as CompositionSpec } from './types-B6zDTU2V.js';
2
+ import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.js';
3
+ import { d as Stop, A as AssetSpec, c as CompositionSpec } from './types-BXi-0iZS.js';
3
4
 
4
5
  type EaseFn = (p: number) => number;
5
6
 
@@ -126,165 +127,6 @@ declare class PureTimeline {
126
127
  */
127
128
  type Timeline = PureTimeline;
128
129
 
129
- /** One note or chord of a track, as a score reader sees it. */
130
- interface MusicNote {
131
- /** Where it starts: beats from the start, and the same moment in seconds (the tempo map applied; swing and humanize are not). */
132
- beat: number;
133
- time: number;
134
- /** How long it is held, in beats and in seconds. */
135
- duration: number;
136
- seconds: number;
137
- /** The bar it starts in (1-based, from `meter`) and its beat inside that bar (1 = the downbeat, 1.5 = the "and" of 1). */
138
- bar: number;
139
- beatInBar: number;
140
- /** The audible MIDI notes (`music.transpose` and the track's `transpose` are in). */
141
- midi: number[];
142
- /** What the score called it: `Am7`, `c4`, `[c4 e4 g4]`. */
143
- name: string;
144
- /** More than one note at once. */
145
- chord: boolean;
146
- /** 0–1 (0.8 normal, 1 accented with `!`, 0.55 soft with `,`). */
147
- velocity: number;
148
- }
149
- interface MusicTrackEvents {
150
- inst: string;
151
- /** The track's length in beats and in bars (not a whole number when a bar is short: that is what to look for). */
152
- beats: number;
153
- bars: number;
154
- notes: MusicNote[];
155
- }
156
- interface MusicDrumHit {
157
- beat: number;
158
- time: number;
159
- bar: number;
160
- beatInBar: number;
161
- velocity: number;
162
- }
163
- interface MusicDrumEvents {
164
- kind: string;
165
- hits: MusicDrumHit[];
166
- }
167
- interface MusicEvents {
168
- /** The tempo at the start. */
169
- bpm: number;
170
- meter: number;
171
- beats: number;
172
- bars: number;
173
- /** The music without its reverb tail. */
174
- seconds: number;
175
- tracks: MusicTrackEvents[];
176
- /** Every drum that is hit, over all sections, with every hit written out. */
177
- drums: MusicDrumEvents[];
178
- }
179
- /** What the `check` tool prints about a music layer: lengths in bars (a short track shows), what starts in each bar, where the drums play. */
180
- interface MusicSummary {
181
- bpm: number;
182
- meter: number;
183
- /** The score set `meter`; when false 4 is only assumed. */
184
- meterSet: boolean;
185
- beats: number;
186
- bars: number;
187
- seconds: number;
188
- tail: number;
189
- tracks: Array<{
190
- inst: string;
191
- beats: number;
192
- bars: number;
193
- events: number;
194
- perBar: string[];
195
- }>;
196
- drums: Array<{
197
- from: number;
198
- to: number;
199
- kinds: string[];
200
- }>;
201
- }
202
-
203
- interface AudioInspectOptions {
204
- /** Width of each loudness window in seconds. Default 0.1. */
205
- window?: number;
206
- /** 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. */
207
- scenes?: Array<{
208
- name: string;
209
- start: number;
210
- end: number;
211
- }>;
212
- }
213
- /** One sound measured ON ITS OWN (not mixed), so overlapping layers do not blur it. */
214
- interface SoundMeasure {
215
- /** Seconds of audio the source has (an sfx: its length; a file: the file's length). */
216
- length: number;
217
- /** Its own loudest sample, dBFS (before the layer's volume). */
218
- peakDb: number;
219
- /** Seconds from its start to its loudest sample — the moment to line up with the picture. */
220
- loudestAt: number;
221
- /** Spectral centroid in Hz: < 500 dark / boomy, 1–4 k mid, > 8 k airy / hissy. */
222
- brightnessHz: number;
223
- }
224
- interface AudioSourceReport {
225
- /** `layer "pop-1"` (or `unnamed audio layer`). */
226
- layer: string;
227
- /** `sfx "pop"`, `asset "bgm"`, `video "green"`. */
228
- source: string;
229
- /** Movie time, seconds. */
230
- start: number;
231
- end: number;
232
- /** Loudest sample of the MIX while this source plays, dBFS (−120 = silence). */
233
- peakDb: number;
234
- sound: SoundMeasure;
235
- /**
236
- * How far below the loudest sample of the MIX around its own loudest moment this source is, dB (0 = it is the loudest thing there; 20 = the mix is 20 dB louder, so
237
- * the source is hardly heard). Absent for a sound inside a remapped composition (its time is not the movie's).
238
- */
239
- belowMixDb?: number;
240
- /** For a `music` layer: its score read back (bars per track, what starts in each bar, the drum spans). */
241
- music?: MusicSummary;
242
- }
243
- interface AudioReport {
244
- duration: number;
245
- sampleRate: number;
246
- /** Loudest sample of the mix before limiting, dBFS. Above 0 the mix was limited. */
247
- peakDb: number;
248
- peakAt: number;
249
- /** Every audio layer / video soundtrack in the mix, in start order. */
250
- sources: AudioSourceReport[];
251
- /** The mix over time: one row per window, `t` = window start. */
252
- windows: Array<{
253
- t: number;
254
- rmsDb: number;
255
- peakDb: number;
256
- brightnessHz: number;
257
- }>;
258
- /** 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. */
259
- issues: string[];
260
- /** 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). */
261
- loudness: {
262
- integratedLufs: number | null;
263
- truePeakDb: number;
264
- clippedSamples: number;
265
- silences: Array<{
266
- from: number;
267
- to: number;
268
- }>;
269
- };
270
- /** The same measure for each scene (`silent`: nothing above −60 dBFS). */
271
- scenes: Array<{
272
- name: string;
273
- from: number;
274
- to: number;
275
- lufs: number | null;
276
- peakDb: number;
277
- silent: boolean;
278
- }>;
279
- /** When each sound starts: what a waveform marks. */
280
- cues: Array<{
281
- t: number;
282
- name: string;
283
- }>;
284
- /** Advice, not faults: a mix that is quiet or loud for web video, little headroom, silent stretches. */
285
- notes: string[];
286
- }
287
-
288
130
  interface FontReport {
289
131
  /** Text layers none of whose fonts is available here: they are drawn in a fallback. */
290
132
  missing: Array<{
@@ -297,12 +139,25 @@ interface FontReport {
297
139
  failedUnused: string[];
298
140
  }
299
141
 
142
+ /** One canvas layer as the check saw it. */
143
+ interface CanvasRow {
144
+ name: string;
145
+ /** The moments (seconds of the layer's own time) it was drawn at, in two orders. */
146
+ moments: number[];
147
+ /** The usual time of one draw on the CPU canvas of the check (the browser's GPU may be faster or slower). */
148
+ drawMs: number;
149
+ ok: boolean;
150
+ /** Why not, in a sentence (empty when ok). */
151
+ problem: string;
152
+ }
153
+
300
154
  /** One kind of issue, with how many frames it was seen in. Issues that differ only by numbers are one kind. */
301
155
  interface IssueGroup {
302
156
  message: string;
303
157
  count: number;
304
158
  firstFrame: number;
305
- lastFrame: number;
159
+ lastFrame: number; /** `'canvas'`: a canvas layer that does not draw the same picture for the same time (not about the layout; `check` lists it under its own heading). */
160
+ kind?: 'canvas';
306
161
  }
307
162
  interface ReviewOptions {
308
163
  /** Moments to resolve and report: `3.5`, `50%`, `f120`, `title@end` (comma-separated). */
@@ -317,6 +172,8 @@ interface ReviewReport {
317
172
  problems: IssueGroup[];
318
173
  /** Text overlaps and fonts that are not available: often intentional, look at them on the contact sheet. */
319
174
  review: IssueGroup[];
175
+ /** Every canvas layer as the determinism check saw it (a failing one is also in `problems` with `kind: 'canvas'`). */
176
+ canvas: CanvasRow[];
320
177
  fonts: FontReport;
321
178
  /** The sound as numbers (`movie.inspectAudio()`), or null for a movie with no audio. */
322
179
  audio: AudioReport | null;
@@ -817,6 +674,10 @@ declare class Movie {
817
674
  * the canvas, cut by an edge, empty, or overlapping other text. Seeks there and stays there.
818
675
  */
819
676
  inspect(frame?: number, opts?: InspectOptions): Promise<InspectReport>;
677
+ /** The canvas layers whose same frame is not the same picture (what `check` reports as a problem): one sentence each. */
678
+ canvasProblems(): string[];
679
+ /** Every canvas layer as the determinism check saw it: the moments drawn, the usual time of a draw in ms, and whether the same time gave the same picture (`check` prints it even when all pass). */
680
+ canvasReport(): CanvasRow[];
820
681
  /**
821
682
  * Every layer as a row with absolute start / end / keyframe times (seconds), plus the transition windows. Runs of
822
683
  * similar layers (`pop-1` … `pop-12`) are one row with a `parts` span each. Read it, or draw it with `timelineChart()`.
@@ -902,4 +763,4 @@ declare class Movie {
902
763
  destroy(): Promise<void>;
903
764
  }
904
765
 
905
- export { type AudioInspectOptions as A, type TimelineHtmlOptions as B, type ContactSheetOptions as C, type TimelineRow as D, type TimelineSvgOptions as E, type FontReport as F, type TimelineTransition as G, dismissLoader as H, type InspectOptions as I, failLoader as J, resolveLoader as K, type LayerInfo as L, type MusicEvents as M, type OnionSkinOptions as O, type ProgressEvent as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type LoadProgressState as f, type LoadStage as g, type LoaderOption as h, type LoopEvent as i, type MotionBlurOptions as j, type MotionBlurSpec as k, Movie as l, type MovieErrorEvent as m, type MovieOptions as n, type MusicDrumEvents as o, type MusicDrumHit as p, type MusicNote as q, type MusicSummary as r, type MusicTrackEvents as s, type RebuildReason as t, type RebuildReport as u, type RenderOptions as v, type ReviewOptions as w, type ReviewReport as x, type SoundMeasure as y, type TimelineData as z };
766
+ export { type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type ProgressEvent as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type RebuildReason as l, type RebuildReport as m, type RenderOptions as n, type ReviewOptions as o, type ReviewReport as p, type TimelineData as q, type TimelineHtmlOptions as r, type TimelineRow as s, type TimelineSvgOptions as t, type TimelineTransition as u, dismissLoader as v, failLoader as w, resolveLoader as x };
@@ -1,5 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
- import { Z as Stop, c as AssetSpec, k as CompositionSpec } from './types-B6zDTU2V.cjs';
2
+ import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.cjs';
3
+ import { d as Stop, A as AssetSpec, c as CompositionSpec } from './types-BXi-0iZS.cjs';
3
4
 
4
5
  type EaseFn = (p: number) => number;
5
6
 
@@ -126,165 +127,6 @@ declare class PureTimeline {
126
127
  */
127
128
  type Timeline = PureTimeline;
128
129
 
129
- /** One note or chord of a track, as a score reader sees it. */
130
- interface MusicNote {
131
- /** Where it starts: beats from the start, and the same moment in seconds (the tempo map applied; swing and humanize are not). */
132
- beat: number;
133
- time: number;
134
- /** How long it is held, in beats and in seconds. */
135
- duration: number;
136
- seconds: number;
137
- /** The bar it starts in (1-based, from `meter`) and its beat inside that bar (1 = the downbeat, 1.5 = the "and" of 1). */
138
- bar: number;
139
- beatInBar: number;
140
- /** The audible MIDI notes (`music.transpose` and the track's `transpose` are in). */
141
- midi: number[];
142
- /** What the score called it: `Am7`, `c4`, `[c4 e4 g4]`. */
143
- name: string;
144
- /** More than one note at once. */
145
- chord: boolean;
146
- /** 0–1 (0.8 normal, 1 accented with `!`, 0.55 soft with `,`). */
147
- velocity: number;
148
- }
149
- interface MusicTrackEvents {
150
- inst: string;
151
- /** The track's length in beats and in bars (not a whole number when a bar is short: that is what to look for). */
152
- beats: number;
153
- bars: number;
154
- notes: MusicNote[];
155
- }
156
- interface MusicDrumHit {
157
- beat: number;
158
- time: number;
159
- bar: number;
160
- beatInBar: number;
161
- velocity: number;
162
- }
163
- interface MusicDrumEvents {
164
- kind: string;
165
- hits: MusicDrumHit[];
166
- }
167
- interface MusicEvents {
168
- /** The tempo at the start. */
169
- bpm: number;
170
- meter: number;
171
- beats: number;
172
- bars: number;
173
- /** The music without its reverb tail. */
174
- seconds: number;
175
- tracks: MusicTrackEvents[];
176
- /** Every drum that is hit, over all sections, with every hit written out. */
177
- drums: MusicDrumEvents[];
178
- }
179
- /** What the `check` tool prints about a music layer: lengths in bars (a short track shows), what starts in each bar, where the drums play. */
180
- interface MusicSummary {
181
- bpm: number;
182
- meter: number;
183
- /** The score set `meter`; when false 4 is only assumed. */
184
- meterSet: boolean;
185
- beats: number;
186
- bars: number;
187
- seconds: number;
188
- tail: number;
189
- tracks: Array<{
190
- inst: string;
191
- beats: number;
192
- bars: number;
193
- events: number;
194
- perBar: string[];
195
- }>;
196
- drums: Array<{
197
- from: number;
198
- to: number;
199
- kinds: string[];
200
- }>;
201
- }
202
-
203
- interface AudioInspectOptions {
204
- /** Width of each loudness window in seconds. Default 0.1. */
205
- window?: number;
206
- /** 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. */
207
- scenes?: Array<{
208
- name: string;
209
- start: number;
210
- end: number;
211
- }>;
212
- }
213
- /** One sound measured ON ITS OWN (not mixed), so overlapping layers do not blur it. */
214
- interface SoundMeasure {
215
- /** Seconds of audio the source has (an sfx: its length; a file: the file's length). */
216
- length: number;
217
- /** Its own loudest sample, dBFS (before the layer's volume). */
218
- peakDb: number;
219
- /** Seconds from its start to its loudest sample — the moment to line up with the picture. */
220
- loudestAt: number;
221
- /** Spectral centroid in Hz: < 500 dark / boomy, 1–4 k mid, > 8 k airy / hissy. */
222
- brightnessHz: number;
223
- }
224
- interface AudioSourceReport {
225
- /** `layer "pop-1"` (or `unnamed audio layer`). */
226
- layer: string;
227
- /** `sfx "pop"`, `asset "bgm"`, `video "green"`. */
228
- source: string;
229
- /** Movie time, seconds. */
230
- start: number;
231
- end: number;
232
- /** Loudest sample of the MIX while this source plays, dBFS (−120 = silence). */
233
- peakDb: number;
234
- sound: SoundMeasure;
235
- /**
236
- * How far below the loudest sample of the MIX around its own loudest moment this source is, dB (0 = it is the loudest thing there; 20 = the mix is 20 dB louder, so
237
- * the source is hardly heard). Absent for a sound inside a remapped composition (its time is not the movie's).
238
- */
239
- belowMixDb?: number;
240
- /** For a `music` layer: its score read back (bars per track, what starts in each bar, the drum spans). */
241
- music?: MusicSummary;
242
- }
243
- interface AudioReport {
244
- duration: number;
245
- sampleRate: number;
246
- /** Loudest sample of the mix before limiting, dBFS. Above 0 the mix was limited. */
247
- peakDb: number;
248
- peakAt: number;
249
- /** Every audio layer / video soundtrack in the mix, in start order. */
250
- sources: AudioSourceReport[];
251
- /** The mix over time: one row per window, `t` = window start. */
252
- windows: Array<{
253
- t: number;
254
- rmsDb: number;
255
- peakDb: number;
256
- brightnessHz: number;
257
- }>;
258
- /** 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. */
259
- issues: string[];
260
- /** 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). */
261
- loudness: {
262
- integratedLufs: number | null;
263
- truePeakDb: number;
264
- clippedSamples: number;
265
- silences: Array<{
266
- from: number;
267
- to: number;
268
- }>;
269
- };
270
- /** The same measure for each scene (`silent`: nothing above −60 dBFS). */
271
- scenes: Array<{
272
- name: string;
273
- from: number;
274
- to: number;
275
- lufs: number | null;
276
- peakDb: number;
277
- silent: boolean;
278
- }>;
279
- /** When each sound starts: what a waveform marks. */
280
- cues: Array<{
281
- t: number;
282
- name: string;
283
- }>;
284
- /** Advice, not faults: a mix that is quiet or loud for web video, little headroom, silent stretches. */
285
- notes: string[];
286
- }
287
-
288
130
  interface FontReport {
289
131
  /** Text layers none of whose fonts is available here: they are drawn in a fallback. */
290
132
  missing: Array<{
@@ -297,12 +139,25 @@ interface FontReport {
297
139
  failedUnused: string[];
298
140
  }
299
141
 
142
+ /** One canvas layer as the check saw it. */
143
+ interface CanvasRow {
144
+ name: string;
145
+ /** The moments (seconds of the layer's own time) it was drawn at, in two orders. */
146
+ moments: number[];
147
+ /** The usual time of one draw on the CPU canvas of the check (the browser's GPU may be faster or slower). */
148
+ drawMs: number;
149
+ ok: boolean;
150
+ /** Why not, in a sentence (empty when ok). */
151
+ problem: string;
152
+ }
153
+
300
154
  /** One kind of issue, with how many frames it was seen in. Issues that differ only by numbers are one kind. */
301
155
  interface IssueGroup {
302
156
  message: string;
303
157
  count: number;
304
158
  firstFrame: number;
305
- lastFrame: number;
159
+ lastFrame: number; /** `'canvas'`: a canvas layer that does not draw the same picture for the same time (not about the layout; `check` lists it under its own heading). */
160
+ kind?: 'canvas';
306
161
  }
307
162
  interface ReviewOptions {
308
163
  /** Moments to resolve and report: `3.5`, `50%`, `f120`, `title@end` (comma-separated). */
@@ -317,6 +172,8 @@ interface ReviewReport {
317
172
  problems: IssueGroup[];
318
173
  /** Text overlaps and fonts that are not available: often intentional, look at them on the contact sheet. */
319
174
  review: IssueGroup[];
175
+ /** Every canvas layer as the determinism check saw it (a failing one is also in `problems` with `kind: 'canvas'`). */
176
+ canvas: CanvasRow[];
320
177
  fonts: FontReport;
321
178
  /** The sound as numbers (`movie.inspectAudio()`), or null for a movie with no audio. */
322
179
  audio: AudioReport | null;
@@ -817,6 +674,10 @@ declare class Movie {
817
674
  * the canvas, cut by an edge, empty, or overlapping other text. Seeks there and stays there.
818
675
  */
819
676
  inspect(frame?: number, opts?: InspectOptions): Promise<InspectReport>;
677
+ /** The canvas layers whose same frame is not the same picture (what `check` reports as a problem): one sentence each. */
678
+ canvasProblems(): string[];
679
+ /** Every canvas layer as the determinism check saw it: the moments drawn, the usual time of a draw in ms, and whether the same time gave the same picture (`check` prints it even when all pass). */
680
+ canvasReport(): CanvasRow[];
820
681
  /**
821
682
  * Every layer as a row with absolute start / end / keyframe times (seconds), plus the transition windows. Runs of
822
683
  * similar layers (`pop-1` … `pop-12`) are one row with a `parts` span each. Read it, or draw it with `timelineChart()`.
@@ -902,4 +763,4 @@ declare class Movie {
902
763
  destroy(): Promise<void>;
903
764
  }
904
765
 
905
- export { type AudioInspectOptions as A, type TimelineHtmlOptions as B, type ContactSheetOptions as C, type TimelineRow as D, type TimelineSvgOptions as E, type FontReport as F, type TimelineTransition as G, dismissLoader as H, type InspectOptions as I, failLoader as J, resolveLoader as K, type LayerInfo as L, type MusicEvents as M, type OnionSkinOptions as O, type ProgressEvent as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type LoadProgressState as f, type LoadStage as g, type LoaderOption as h, type LoopEvent as i, type MotionBlurOptions as j, type MotionBlurSpec as k, Movie as l, type MovieErrorEvent as m, type MovieOptions as n, type MusicDrumEvents as o, type MusicDrumHit as p, type MusicNote as q, type MusicSummary as r, type MusicTrackEvents as s, type RebuildReason as t, type RebuildReport as u, type RenderOptions as v, type ReviewOptions as w, type ReviewReport as x, type SoundMeasure as y, type TimelineData as z };
766
+ export { type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type ProgressEvent as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type RebuildReason as l, type RebuildReport as m, type RenderOptions as n, type ReviewOptions as o, type ReviewReport as p, type TimelineData as q, type TimelineHtmlOptions as r, type TimelineRow as s, type TimelineSvgOptions as t, type TimelineTransition as u, dismissLoader as v, failLoader as w, resolveLoader as x };
@@ -1,7 +1,7 @@
1
- import { CompositionSequence } from './chunk-AZ6AINSW.js';
2
- import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-G3DX65HO.js';
3
- import './chunk-WIIU42FH.js';
4
- import './chunk-2JFFKHXO.js';
1
+ import { CompositionSequence } from './chunk-4CSL5QSL.js';
2
+ import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-UDTLYYGY.js';
3
+ import './chunk-VNP7QCJE.js';
4
+ import './chunk-32MMNDDN.js';
5
5
  import './chunk-BXOCEZ4U.js';
6
6
 
7
7
  // src/sequences/Spans.ts
@@ -49,5 +49,5 @@ var SpansSequence = class extends CompositionSequence {
49
49
  };
50
50
 
51
51
  export { SpansSequence };
52
- //# sourceMappingURL=Spans-MWP2FX3T.js.map
53
- //# sourceMappingURL=Spans-MWP2FX3T.js.map
52
+ //# sourceMappingURL=Spans-4IBQASGK.js.map
53
+ //# sourceMappingURL=Spans-4IBQASGK.js.map