pixi-effects 0.44.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +117 -2
- package/ai/tools/lookup.mjs +1 -0
- package/dist/Composition-NSY7A32Y.js +7 -0
- package/dist/{Composition-YGBQX3HZ.js.map → Composition-NSY7A32Y.js.map} +1 -1
- package/dist/Composition-PME3P5LP.cjs +16 -0
- package/dist/{Composition-N6BOROJ2.cjs.map → Composition-PME3P5LP.cjs.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-B3VOAgwc.d.cts → Movie-BqrAWN0g.d.cts} +13 -1
- package/dist/{Movie-CQCVUqRs.d.ts → Movie-sV8nJr_f.d.ts} +13 -1
- package/dist/{Spans-XTIWYSNO.cjs → Spans-2TZCNREQ.cjs} +9 -9
- package/dist/{Spans-XTIWYSNO.cjs.map → Spans-2TZCNREQ.cjs.map} +1 -1
- package/dist/{Spans-6L2ZVECX.js → Spans-TESJPQKX.js} +4 -4
- package/dist/{Spans-6L2ZVECX.js.map → Spans-TESJPQKX.js.map} +1 -1
- package/dist/{chunk-4MBTYREC.js → chunk-2E74SP63.js} +72 -17
- package/dist/chunk-2E74SP63.js.map +1 -0
- package/dist/{chunk-MAAM45ZX.cjs → chunk-7M63Y6MO.cjs} +114 -59
- package/dist/chunk-7M63Y6MO.cjs.map +1 -0
- package/dist/{chunk-3HZV3UHU.cjs → chunk-DKCMJILW.cjs} +67 -65
- package/dist/chunk-DKCMJILW.cjs.map +1 -0
- package/dist/{chunk-4QVQ5UJJ.js → chunk-DM2OLQOJ.js} +5 -6
- package/dist/chunk-DM2OLQOJ.js.map +1 -0
- package/dist/{chunk-ICI6ACHY.js → chunk-FPV3NPYC.js} +8 -6
- package/dist/chunk-FPV3NPYC.js.map +1 -0
- package/dist/{chunk-UHNSL2KJ.js → chunk-K24FF4BT.js} +10 -5
- package/dist/chunk-K24FF4BT.js.map +1 -0
- package/dist/{chunk-X62H3RBG.cjs → chunk-LALEBV63.cjs} +5 -6
- package/dist/chunk-LALEBV63.cjs.map +1 -0
- package/dist/{chunk-LQDDDIC4.cjs → chunk-O66JGPVV.cjs} +10 -4
- package/dist/chunk-O66JGPVV.cjs.map +1 -0
- package/dist/index.cjs +35 -35
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/loader.css +1 -1
- package/dist/present.cjs +4 -4
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +4 -4
- package/dist/sound.cjs +2 -2
- package/dist/sound.js +1 -1
- package/dist/three.cjs +194 -15
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +75 -4
- package/dist/three.d.ts +75 -4
- package/dist/three.js +191 -13
- package/dist/three.js.map +1 -1
- package/dist/{types-C5xo0fzb.d.cts → types-CTSrPvY3.d.cts} +64 -58
- package/dist/{types-C5xo0fzb.d.ts → types-CTSrPvY3.d.ts} +64 -58
- package/docs/api.md +2 -0
- package/docs/dsl.md +26 -0
- package/llms-full.txt +66 -4
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +2 -2
- package/skills/pixi-effects/reference/cheatsheet.md +2 -2
- package/skills/pixi-effects/reference/recipes.md +34 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-N6BOROJ2.cjs +0 -16
- package/dist/Composition-YGBQX3HZ.js +0 -7
- package/dist/chunk-3HZV3UHU.cjs.map +0 -1
- package/dist/chunk-4MBTYREC.js.map +0 -1
- package/dist/chunk-4QVQ5UJJ.js.map +0 -1
- package/dist/chunk-ICI6ACHY.js.map +0 -1
- package/dist/chunk-LQDDDIC4.cjs.map +0 -1
- package/dist/chunk-MAAM45ZX.cjs.map +0 -1
- package/dist/chunk-UHNSL2KJ.js.map +0 -1
- package/dist/chunk-X62H3RBG.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.46.0
|
|
4
|
+
|
|
5
|
+
A live 2D layer can be the picture on a mesh of a `three` layer (a phone screen with an app running, a television, a label), clicks and hovers reach it, and a hidden source layer is no longer left culled. Nothing breaks.
|
|
6
|
+
|
|
7
|
+
- **New: `threeLayerTexture(ctx, 'screen')` puts a live 2D layer on a mesh of a `three` layer.** A phone or laptop screen with an app running, a television, a billboard, a label on a bottle: a `composition` listed before the three layer is drawn into a texture just before the three layer draws, and `screen.material` shows it on any mesh (or one face of a box). The 2D layer keeps its timeline, keyframes and filters and is no longer drawn on the stage. Colours equal the 2D layer channel for channel, on both routes: when the three layer is on PixiJS's GPU device the texture is PixiJS's own GPU texture used through three's `ExternalTexture` (no copy, 0.3 ms a frame), otherwise the pixels are read back (1.6 ms at 480 x 270, 4 ms at 1280 x 720). A function of the time (seek, export, `check`), kept across `setVars`. `{ transparent: true }` lets the empty and half-transparent parts show what is behind the mesh (same colours as the 2D layer over the same background, on every route); a texture seen small or at a slant is filtered, not aliased (mipmaps made after every draw on the shared GPU, anisotropy 4: 1-pixel stripes at a quarter size went from black and white stripes, spread 127, to one even grey, spread 0). **A click or hover on the mesh reaches the 2D layer:** `movie.pick(x, y)` on a point of the mesh answers for the layers inside the screen at the point of its picture under the pointer (`screen/button`, `local` in the layer's own pixels, `via` naming the three layer and the mesh), above the mesh itself; `only` / `ignore`, `movie.hover` and a page's click handler work through it. Found while reading the PixiJS 3D beta (its `ContainerTexture`). `tests/films/three-layer-texture`, recipe `three-screen`, `docs/dsl.md` "A 2D layer on a mesh".
|
|
8
|
+
## 0.45.0
|
|
9
|
+
|
|
10
|
+
`check` stops waiting in silence when a page cannot load, says how the picture moves and ends, and the whole mix can be set to a loudness in one line. Nothing breaks.
|
|
11
|
+
|
|
12
|
+
- **Changed: `check` / `inspectAudio()` call a mix quiet from -20 LUFS down (it was -24).** The note says `movie.init({ loudness: -16 })` is the one-line fix. In a second trial of Haiku films the note never fired for mixes at -20.8 and -22.8 LUFS, so nobody met the new option (the agent that read the -14 to -16 norm raised `volume` by hand, to -15.4). A mix of -20 to -24 is still 4 to 8 dB under what web video usually has; the note is advice, never a failure. `tests/core/inspectAudio.test.ts`.
|
|
13
|
+
- **New: `check` says how the picture moves and how it ends, in numbers.** A new `motion` line: how much of the movie moves, when the last movement ends, the longest stretch that does not move, and (for review, never a failure) a last frame that is one plain colour (with the colour and since when), nothing on screen for the first second or more, and a hold of 3 s or a third of the movie (`the picture does not move from 3.1 s to 10.4 s`). `check` only said `OK — nothing to fix` for a picture that fades in, holds still for nine seconds and ends empty, so an agent stopped there (in a trial of ten Haiku films a blind judge named thin motion, long holds and empty endings as the common weakness; the new numbers name the same three things in eight of them). A presentation (stops) is not reviewed. Sampled at 10 % size, up to 90 frames: 2–3 s more. `tests/tools/checkMotion.test.ts`, `tests/films/check-motion`.
|
|
14
|
+
- **New: `movie.init({ loudness: -16 })` brings the whole mix to a loudness.** One line instead of trial and error with `volume`: the finished mix gets one gain (up or down) to reach the target in LUFS (ITU-R BS.1770), the same for playback and export; the loudest sample is kept at -1 dBFS or under, so a mix with one loud click in a quiet bed stops short and `inspectAudio()` / `check` say how far it got and why. Off by default; `check`'s "the mix is quiet" note now names the option. Found in a trial of Haiku films: eight of nine with sound were -22 to -26 LUFS. `tests/core/loudnessTarget.test.ts`, `tests/films/audio-loudness`.
|
|
15
|
+
- **Fixed: `check` no longer waits a silent minute when the page's module cannot be loaded.** A wrong path in the import map or a CDN that is down raises nothing in the page, so a page that never got ready ended with only `window.movie is missing` after 60 s, and an AI agent could not tell why (seen in a Haiku trial, five times in one earlier session, and in the claude.ai sandbox). `check` now listens to the browser's requests: after a failed script or page load it stops within seconds and names the address and how it failed (`…/no-such-module.js answered 404 (not found)`, or `could not be fetched (net::ERR_CONNECTION_RESET)`), says whether it is a file of the page's own server or another site, and tries once more, from the start, when it was another site's network failure or a 5xx (a hiccup of a CDN); the report says `retried` and lists `loadFailures`. 61 s → about 4 s for a 404, about 6 s with the retry. `tests/tools/checkLoadFailures.test.ts`.
|
|
16
|
+
- **Changed: `pixi-effects new` says where the file is, in full** (`in the folder …: /full/path.html`), so an agent whose shell stands in another folder finds the file again (two of five agents in a trial edited or checked in the wrong folder).
|
|
17
|
+
|
|
3
18
|
## 0.44.0
|
|
4
19
|
|
|
5
20
|
An AI can now click, hover and drag on a page it made and see what happened (`check --do`), a text layer can be picked by its letters, a rebuild keeps a `three` layer that did not change, and the Interactive page has three samples that click, hover and drag. Nothing breaks.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pixi-effects
|
|
2
2
|
|
|
3
|
-
> **Status**: experimental — current release `0.
|
|
3
|
+
> **Status**: experimental — current release `0.46.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
|
|
4
4
|
|
|
5
5
|
**[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 43 pieces written as plain data by AI models · **[For AI →](https://yjmtmtk.github.io/pixi-effects/for-ai.html)** · what an AI reads and runs · **[Playground →](https://yjmtmtk.github.io/pixi-effects/examples/playground.html)** in the browser.
|
|
6
6
|
|
|
@@ -49,8 +49,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
49
49
|
"imports": {
|
|
50
50
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
51
51
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
52
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
53
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
52
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/index.js",
|
|
53
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/Controller.js"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
</script>
|
|
@@ -61,7 +61,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
61
61
|
</script>
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.
|
|
64
|
+
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.46.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
|
|
65
65
|
|
|
66
66
|
> **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
|
|
67
67
|
>
|
|
@@ -78,7 +78,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
|
|
|
78
78
|
"...": "(everything from the importmap above)",
|
|
79
79
|
"three": "https://esm.sh/three@0.178.0",
|
|
80
80
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",
|
|
81
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
81
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/three.js"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
</script>
|
package/ai/chat-template.html
CHANGED
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"imports": {
|
|
60
60
|
"pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.22.0/dist/pixi.min.mjs",
|
|
61
61
|
"mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
|
|
62
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
63
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
64
|
-
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
62
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/index.js",
|
|
63
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/Controller.js",
|
|
64
|
+
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/present.js"
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
</script>
|
package/ai/tools/check.mjs
CHANGED
|
@@ -87,6 +87,20 @@ export function findChrome(env = process.env, exists = fs.existsSync, platform =
|
|
|
87
87
|
|
|
88
88
|
export const USAGE = 'usage: node ai/tools/check.mjs <page.html> [--out DIR] [--frames N] [--formats mp4,webm] [--no-export] [--draft] [--at 3.5,title@end] [--crop X,Y,W,H] [--pick X,Y] [--do "click Lime; drag card > 770,240"] [--onion 1:3] [--seam] [--query a=1&b=2] [--var name=value] [--size 1920x1080,1080x1920] [--timeout S] [--root DIR] [--chrome PATH] [--strict] [--help]\n(the header of ai/tools/check.mjs explains each option)';
|
|
89
89
|
|
|
90
|
+
/** One failed request, as a sentence the person (or the AI) can act on. `f` is { url, status? , error?, type? }. */
|
|
91
|
+
export function describeLoadFailure(f) {
|
|
92
|
+
const how = f.status ? `answered ${f.status}${f.status === 404 ? ' (not found)' : ''}` : `could not be fetched (${f.error || 'a network error'})`;
|
|
93
|
+
return `${f.url} ${how}`;
|
|
94
|
+
}
|
|
95
|
+
/** A failure worth one more try: the network failed or the server had a bad moment (5xx, 429) on another site (a CDN); a missing file of the page's own server never gets better. */
|
|
96
|
+
export function isTransientLoadFailure(f, ownOrigin) {
|
|
97
|
+
const foreign = !String(f.url).startsWith(ownOrigin);
|
|
98
|
+
if (f.error) return foreign;
|
|
99
|
+
return foreign && (f.status >= 500 || f.status === 429);
|
|
100
|
+
}
|
|
101
|
+
/** The kinds of request without which the page cannot start. */
|
|
102
|
+
const CRITICAL_TYPES = new Set(['Script', 'Document', 'Stylesheet', 'Other', undefined]);
|
|
103
|
+
|
|
90
104
|
const DO_VERBS = ['click', 'hover', 'drag', 'wait', 'key'];
|
|
91
105
|
const pointOf = (text) => { const m = text.trim().match(/^(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)$/); return m ? { x: Number(m[1]), y: Number(m[2]) } : { name: text.trim() }; };
|
|
92
106
|
/**
|
|
@@ -419,13 +433,25 @@ export async function runCheck(opts, log = console.log) {
|
|
|
419
433
|
const { proc, cdp } = await launchChrome(chrome, userDataDir);
|
|
420
434
|
chromeProc = proc;
|
|
421
435
|
const consoleLines = [];
|
|
436
|
+
// the requests that failed (a wrong path in the import map, a CDN that is down): a module that cannot be loaded raises nothing in the page, so without this the only sign is a page that never gets ready
|
|
437
|
+
const requests = new Map(), loadFailures = [];
|
|
438
|
+
let failureAt = 0;
|
|
422
439
|
cdp.on(m => {
|
|
423
440
|
if (m.method === 'Runtime.exceptionThrown') consoleLines.push(`uncaught: ${m.params.exceptionDetails.exception?.description || m.params.exceptionDetails.text}`);
|
|
441
|
+
else if (m.method === 'Network.requestWillBeSent') requests.set(m.params.requestId, { url: m.params.request.url, type: m.params.type });
|
|
442
|
+
else if (m.method === 'Network.responseReceived' && m.params.response.status >= 400) {
|
|
443
|
+
loadFailures.push({ url: m.params.response.url, status: m.params.response.status, type: m.params.type }); failureAt = Date.now();
|
|
444
|
+
} else if (m.method === 'Network.loadingFailed' && !m.params.canceled) {
|
|
445
|
+
const r = requests.get(m.params.requestId) ?? {};
|
|
446
|
+
if (r.url && !/favicon\.ico/.test(r.url)) { loadFailures.push({ url: r.url, error: m.params.errorText, type: m.params.type ?? r.type }); failureAt = Date.now(); }
|
|
447
|
+
}
|
|
424
448
|
});
|
|
425
449
|
await cdp.send('Runtime.enable');
|
|
426
450
|
await cdp.send('Page.enable');
|
|
451
|
+
await cdp.send('Network.enable');
|
|
452
|
+
const pageUrl = `http://127.0.0.1:${port}/${path.relative(root, pagePath).split(path.sep).join('/')}${opts.query ? `?${opts.query}` : ''}`;
|
|
427
453
|
const t0 = Date.now();
|
|
428
|
-
await cdp.send('Page.navigate', { url:
|
|
454
|
+
await cdp.send('Page.navigate', { url: pageUrl });
|
|
429
455
|
|
|
430
456
|
// wait for the movie to be ready (or the page to say it failed)
|
|
431
457
|
let info = null;
|
|
@@ -438,6 +464,16 @@ export async function runCheck(opts, log = console.log) {
|
|
|
438
464
|
const failed = (info.logs || []).some(l => /^(init:|uncaught)/.test(l));
|
|
439
465
|
if (ready || failed || consoleLines.length) break;
|
|
440
466
|
} catch { /* the page is still loading */ }
|
|
467
|
+
// a script or the page itself could not be loaded and nothing has started since: no use in waiting out the minute
|
|
468
|
+
const critical = loadFailures.filter(f => CRITICAL_TYPES.has(f.type) && !/\.(png|jpe?g|gif|webp|svg|woff2?|ttf|otf|mp3|mp4|wav)(\?|$)/i.test(f.url));
|
|
469
|
+
if (critical.length && Date.now() - failureAt > 2500) {
|
|
470
|
+
if (critical.some(f => isTransientLoadFailure(f, `http://127.0.0.1:${port}`)) && !report.retried) { // a hiccup of a CDN: once more, from the start
|
|
471
|
+
report.retried = true; loadFailures.length = 0; failureAt = 0; requests.clear();
|
|
472
|
+
await cdp.send('Page.navigate', { url: pageUrl });
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
441
477
|
if (Date.now() - t0 > 60000) break;
|
|
442
478
|
}
|
|
443
479
|
info = await cdp.eval(INFO).catch(() => info) ?? info;
|
|
@@ -445,8 +481,15 @@ export async function runCheck(opts, log = console.log) {
|
|
|
445
481
|
report.readySeconds = Math.round((Date.now() - t0) / 100) / 10;
|
|
446
482
|
report.loadStages = await cdp.eval('(window.movie && window.movie.loadStages) || null').catch(() => null);
|
|
447
483
|
report.logs = [...new Set([...(info?.logs ?? []), ...consoleLines])];
|
|
484
|
+
if (loadFailures.length) report.loadFailures = loadFailures.map(({ url, status, error, type }) => ({ url, ...(status ? { status } : {}), ...(error ? { error } : {}), ...(type ? { type } : {}) }));
|
|
448
485
|
if (!report.ready) {
|
|
449
|
-
|
|
486
|
+
const lost = (report.loadFailures ?? []).filter(f => CRITICAL_TYPES.has(f.type));
|
|
487
|
+
if (lost.length) {
|
|
488
|
+
const own = lost.every(f => String(f.url).startsWith(`http://127.0.0.1:${port}`));
|
|
489
|
+
report.problems.push(`the page could not be loaded completely: ${lost.slice(0, 3).map(describeLoadFailure).join('; ')}${lost.length > 3 ? ` (+${lost.length - 3} more)` : ''}${report.retried ? ' — tried twice' : ''}. `
|
|
490
|
+
+ (own ? `That is a file of the page's own server (the folder served is ${root}): check the path in the page's import map or its <script src>.` : 'That address is not served here (a CDN or another site): check it is spelled right and reachable, then run the check again.'));
|
|
491
|
+
}
|
|
492
|
+
report.problems.push(`the page did not become ready (window.__ready !== true${lost.length ? ', because of the failed load above' : ' within 60 s'}${info?.hasMovie ? '' : '; window.movie is missing'})`);
|
|
450
493
|
return finish(report, outDir, log);
|
|
451
494
|
}
|
|
452
495
|
// --var / --size: the page is rebuilt once with what the command line gave (the first size; the others are looked at after the export)
|
|
@@ -546,6 +589,15 @@ export async function runCheck(opts, log = console.log) {
|
|
|
546
589
|
}
|
|
547
590
|
}
|
|
548
591
|
|
|
592
|
+
// the motion: how much of the movie moves, when the last movement ends, the longest hold, and how it starts and ends (numbers for review, never a failure)
|
|
593
|
+
{
|
|
594
|
+
const m = await cdp.eval(MOTION_SCRIPT).catch(() => null);
|
|
595
|
+
if (m && !m.error && m.samples) {
|
|
596
|
+
report.motion = motionVerdict(m.samples, { duration: m.duration, hasStops: m.hasStops });
|
|
597
|
+
report.motion.line = motionLine(report.motion, m.duration);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
549
601
|
// a presentation (composition.stops): one picture of every stop, in order, so the pages and steps can be read at a glance
|
|
550
602
|
const stops = await cdp.eval(`(() => { const s = movie.stops || []; return s.length ? { count: s.length, pages: new Set(s.map(x => x.pageIndex)).size, items: s.map(x => ({ at: Math.round(x.at * 100) / 100, frame: x.frame, page: x.page, pageStart: x.pageStart, notes: !!x.notes, pdf: x.pdf })) } : null; })()`).catch(() => null);
|
|
551
603
|
if (stops) {
|
|
@@ -691,6 +743,68 @@ export function seamVerdict({ seam, steps }) {
|
|
|
691
743
|
}
|
|
692
744
|
|
|
693
745
|
/** In the page: the seam (last frame against the first) and nine ordinary steps (the first and the last of them next to the seam), on pictures a quarter of the size. */
|
|
746
|
+
const MOVING = 0.001; // the mean change of a pixel between two samples above which the picture is moving (0..1)
|
|
747
|
+
const PLAIN = 0.012; // the spread of the picture below which it is one plain colour (0..1)
|
|
748
|
+
const hex2 = v => Math.max(0, Math.min(255, Math.round(v * 255))).toString(16).padStart(2, '0');
|
|
749
|
+
const sec = x => +x.toFixed(1);
|
|
750
|
+
/**
|
|
751
|
+
* `check`'s motion line: from samples of the picture over the movie (`{ t, diff, spread, mean }`: how much it changed since the sample before, how much is on screen, its average colour) say how much of the
|
|
752
|
+
* movie moves, when the last movement ends, the longest stretch that does not move, and whether the first and last frames are one plain colour. These are for review, never a failure: a hold or a fade to black
|
|
753
|
+
* may be the point. A presentation (stops) is meant to stand still and is not reviewed.
|
|
754
|
+
*/
|
|
755
|
+
export function motionVerdict(samples, { duration, hasStops = false }) {
|
|
756
|
+
const steps = samples.slice(1);
|
|
757
|
+
const moving = steps.filter(s => s.diff > MOVING);
|
|
758
|
+
const out = { steps: steps.length, movingShare: steps.length ? moving.length / steps.length : 0, lastMotionAt: moving.length ? moving[moving.length - 1].t : 0, longestStill: null, first: { plain: false, visibleFrom: 0 }, last: { plain: false, plainFrom: null, color: null }, review: [], presentation: hasStops };
|
|
759
|
+
let run = null;
|
|
760
|
+
for (let i = 1; i < samples.length; i++) {
|
|
761
|
+
if (samples[i].diff <= MOVING) { if (!run) run = { from: samples[i - 1].t, i0: i - 1 }; run.to = samples[i].t; run.i1 = i; }
|
|
762
|
+
else run = null;
|
|
763
|
+
if (run && (!out.longestStill || run.to - run.from > out.longestStill.seconds)) out.longestStill = { from: sec(run.from), to: sec(run.to), seconds: sec(run.to - run.from), i0: run.i0, i1: run.i1 };
|
|
764
|
+
}
|
|
765
|
+
const visible = samples.find(s => s.spread >= PLAIN);
|
|
766
|
+
out.first = { plain: samples[0].spread < PLAIN, visibleFrom: visible ? visible.t : null };
|
|
767
|
+
const lastS = samples[samples.length - 1];
|
|
768
|
+
if (lastS && lastS.spread < PLAIN) {
|
|
769
|
+
let k = samples.length - 1;
|
|
770
|
+
while (k > 0 && samples[k - 1].spread < PLAIN) k--;
|
|
771
|
+
out.last = { plain: true, plainFrom: samples[k].t, color: `#${hex2(lastS.mean[0])}${hex2(lastS.mean[1])}${hex2(lastS.mean[2])}` };
|
|
772
|
+
}
|
|
773
|
+
if (!hasStops) {
|
|
774
|
+
if (out.last.plain && duration - out.last.plainFrom >= 0.4) out.review.push(`the last frame is one plain colour (${out.last.color}): nothing on screen from ${sec(out.last.plainFrom)} s to the end (on purpose for a fade to black or a held background; otherwise the ending is missing)`);
|
|
775
|
+
if (out.first.visibleFrom !== null && out.first.visibleFrom >= 1) out.review.push(`nothing is on screen until ${sec(out.first.visibleFrom)} s (the first second or more is a plain colour: a viewer may think the video is broken)`);
|
|
776
|
+
const still = out.longestStill;
|
|
777
|
+
if (still && still.seconds >= Math.max(3, duration * 0.35)) {
|
|
778
|
+
const emptyInside = samples.slice(still.i0, still.i1 + 1).every(s => s.spread < PLAIN); // an empty picture is told by its first or last frame above, not twice
|
|
779
|
+
const inEmptyTail = out.last.plain && still.from >= out.last.plainFrom - 0.5;
|
|
780
|
+
if (!emptyInside && !inEmptyTail) out.review.push(`the picture does not move from ${still.from} s to ${still.to} s (${still.seconds} s of ${sec(duration)} s): a long hold; give it a slow drift, a second movement, or a reason to stay (a pause for the music or the reading is fine)`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (out.longestStill) { delete out.longestStill.i0; delete out.longestStill.i1; }
|
|
784
|
+
return out;
|
|
785
|
+
}
|
|
786
|
+
export function motionLine(v, duration) {
|
|
787
|
+
if (v.presentation) return ' motion presentation: its stops are meant to stand still, so the motion is not reviewed';
|
|
788
|
+
const still = v.longestStill && v.longestStill.seconds >= 1 ? ` · longest still stretch ${v.longestStill.seconds} s (${v.longestStill.from}–${v.longestStill.to} s)` : '';
|
|
789
|
+
return ` motion moves in ${Math.round(v.movingShare * 100)} % of the movie · last movement ends at ${sec(v.lastMotionAt)} s of ${sec(duration)} s${still}`;
|
|
790
|
+
}
|
|
791
|
+
const MOTION_SCRIPT = `(async () => {
|
|
792
|
+
const total = movie.totalFrames, fps = movie.frameRate;
|
|
793
|
+
if (total < 3) return { error: 'a movie of fewer than 3 frames has no motion to measure' };
|
|
794
|
+
const n = Math.min(90, Math.max(3, Math.round(movie.duration * 10) + 1));
|
|
795
|
+
const frames = [...new Set(Array.from({ length: n }, (_, i) => Math.round(i / (n - 1) * (total - 1))))];
|
|
796
|
+
const out = []; let prev = null;
|
|
797
|
+
for (const f of frames) {
|
|
798
|
+
const d = (await movie.snapshot(f, { as: 'imageData', scale: 0.1, motionBlur: false })).data;
|
|
799
|
+
let r = 0, g = 0, b = 0, l = 0, l2 = 0, diff = null; const px = d.length / 4;
|
|
800
|
+
for (let i = 0; i < d.length; i += 4) { r += d[i]; g += d[i + 1]; b += d[i + 2]; const y = (0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2]) / 255; l += y; l2 += y * y; }
|
|
801
|
+
if (prev) { let s = 0; for (let i = 0; i < d.length; i += 4) s += Math.abs(d[i] - prev[i]) + Math.abs(d[i + 1] - prev[i + 1]) + Math.abs(d[i + 2] - prev[i + 2]); diff = s / (px * 3 * 255); }
|
|
802
|
+
out.push({ t: f / fps, diff, spread: Math.sqrt(Math.max(0, l2 / px - (l / px) ** 2)), mean: [r / px / 255, g / px / 255, b / px / 255] });
|
|
803
|
+
prev = d;
|
|
804
|
+
}
|
|
805
|
+
return { samples: out, hasStops: !!(movie.stops && movie.stops.length), duration: movie.duration };
|
|
806
|
+
})()`;
|
|
807
|
+
|
|
694
808
|
const SEAM_SCRIPT = `(async () => {
|
|
695
809
|
const total = movie.totalFrames;
|
|
696
810
|
if (total < 4) return { error: 'a movie of fewer than 4 frames has no loop to measure' };
|
|
@@ -872,6 +986,7 @@ function finish(report, outDir, log) {
|
|
|
872
986
|
for (const l of formatMusic(a?.sources)) L.push(l);
|
|
873
987
|
for (const n of a?.notes ?? []) L.push(` note ${n}`);
|
|
874
988
|
}
|
|
989
|
+
if (report.motion) { L.push(report.motion.line); for (const r of report.motion.review) L.push(` - ${r}`); }
|
|
875
990
|
if (report.seam) L.push(report.seam.line);
|
|
876
991
|
for (const ex of report.exports ?? []) L.push(exportLine(ex));
|
|
877
992
|
L.push(` files ${Object.values(report.files).join(' ')}`);
|
package/ai/tools/lookup.mjs
CHANGED
|
@@ -243,6 +243,7 @@ export function runLookup(cmd, args, { cwd = process.cwd(), root = PACKAGE_ROOT
|
|
|
243
243
|
const f = shownPath(file, cwd);
|
|
244
244
|
return ok([
|
|
245
245
|
`created ${f} (the template: a ${size ? `${size.width}x${size.height}` : '1280x720'} page, ${duration ?? 6} s, that collects every warning in window.__logs)`,
|
|
246
|
+
`in the folder ${path.dirname(file)}: ${file} (the next commands take this path; a file edited from another folder is another file)`,
|
|
246
247
|
`next: npx pixi-effects view ${f} look at it as you edit (it says when the file changed)`,
|
|
247
248
|
` npx pixi-effects check ${f} review it: warnings, layout, sound, a contact sheet, a real export`,
|
|
248
249
|
' npx pixi-effects recipe tested pieces to copy · npx pixi-effects help <word>',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CompositionSequence } from './chunk-FPV3NPYC.js';
|
|
2
|
+
import './chunk-DM2OLQOJ.js';
|
|
3
|
+
import './chunk-VNP7QCJE.js';
|
|
4
|
+
import './chunk-32MMNDDN.js';
|
|
5
|
+
import './chunk-BXOCEZ4U.js';
|
|
6
|
+
//# sourceMappingURL=Composition-NSY7A32Y.js.map
|
|
7
|
+
//# sourceMappingURL=Composition-NSY7A32Y.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-NSY7A32Y.js"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkDKCMJILW_cjs = require('./chunk-DKCMJILW.cjs');
|
|
4
|
+
require('./chunk-LALEBV63.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 chunkDKCMJILW_cjs.CompositionSequence; }
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=Composition-PME3P5LP.cjs.map
|
|
16
|
+
//# sourceMappingURL=Composition-PME3P5LP.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-PME3P5LP.cjs"}
|
package/dist/Controller.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { M as Movie } from './Movie-
|
|
1
|
+
import { M as Movie } from './Movie-BqrAWN0g.cjs';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import './soundEntry-DT13DNt8.cjs';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-CTSrPvY3.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
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
|
package/dist/Controller.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { M as Movie } from './Movie-
|
|
1
|
+
import { M as Movie } from './Movie-sV8nJr_f.js';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import './soundEntry-DT13DNt8.js';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-CTSrPvY3.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
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,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.cjs';
|
|
3
|
-
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-
|
|
3
|
+
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-CTSrPvY3.cjs';
|
|
4
4
|
|
|
5
5
|
type EaseFn = (p: number) => number;
|
|
6
6
|
|
|
@@ -255,6 +255,11 @@ interface PickHit {
|
|
|
255
255
|
x: number;
|
|
256
256
|
y: number;
|
|
257
257
|
};
|
|
258
|
+
/** The layer is shown on a mesh of a `three` layer (`threeLayerTexture`) and the point is on that mesh: the layer (`path` of the three layer) and the object (its name) it came through. `point` is still the canvas point; `local` is the point in the layer's own pixels. */
|
|
259
|
+
via?: {
|
|
260
|
+
path: string;
|
|
261
|
+
object: string;
|
|
262
|
+
};
|
|
258
263
|
/** A `three` layer: what the ray through that point met first: the object (its name, or `''`), where (three's own units), and how far from the camera. */
|
|
259
264
|
three?: {
|
|
260
265
|
object: unknown;
|
|
@@ -428,6 +433,12 @@ interface MovieOptions {
|
|
|
428
433
|
loader?: LoaderOption;
|
|
429
434
|
/** Start with `movie.loop` on: playing goes back to the start at the end instead of stopping. Default false (like a `<video>`). */
|
|
430
435
|
loop?: boolean;
|
|
436
|
+
/**
|
|
437
|
+
* The loudness of the whole mix in LUFS (ITU-R BS.1770): `-16` is usual for web video. The finished mix gets one gain (up or down) so its integrated loudness is that number, but the loudest sample
|
|
438
|
+
* never passes -1 dBFS: a mix with one loud click in a quiet bed stops short, and `inspectAudio()` / `check` say how far it got. Default: off (the mix is what the layers make; `check` says when it is quiet).
|
|
439
|
+
* A number of 0 or below, or `false`.
|
|
440
|
+
*/
|
|
441
|
+
loudness?: number | false;
|
|
431
442
|
}
|
|
432
443
|
|
|
433
444
|
interface RenderOptions {
|
|
@@ -611,6 +622,7 @@ declare class Movie {
|
|
|
611
622
|
/** What went into the mix, and its peak before limiting — kept for inspectAudio(). */
|
|
612
623
|
private _audioSources;
|
|
613
624
|
private _mixStats;
|
|
625
|
+
private _loudness;
|
|
614
626
|
on(event: 'ready', fn: () => void): this;
|
|
615
627
|
on(event: 'frame', fn: (e: FrameEvent) => void): this;
|
|
616
628
|
/** A jump to another frame (`gotoFrame`, a seek bar) starts / finishes. Not emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()`. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.js';
|
|
3
|
-
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-
|
|
3
|
+
import { a as Stop, A as AssetSpec, C as CompositionSpec } from './types-CTSrPvY3.js';
|
|
4
4
|
|
|
5
5
|
type EaseFn = (p: number) => number;
|
|
6
6
|
|
|
@@ -255,6 +255,11 @@ interface PickHit {
|
|
|
255
255
|
x: number;
|
|
256
256
|
y: number;
|
|
257
257
|
};
|
|
258
|
+
/** The layer is shown on a mesh of a `three` layer (`threeLayerTexture`) and the point is on that mesh: the layer (`path` of the three layer) and the object (its name) it came through. `point` is still the canvas point; `local` is the point in the layer's own pixels. */
|
|
259
|
+
via?: {
|
|
260
|
+
path: string;
|
|
261
|
+
object: string;
|
|
262
|
+
};
|
|
258
263
|
/** A `three` layer: what the ray through that point met first: the object (its name, or `''`), where (three's own units), and how far from the camera. */
|
|
259
264
|
three?: {
|
|
260
265
|
object: unknown;
|
|
@@ -428,6 +433,12 @@ interface MovieOptions {
|
|
|
428
433
|
loader?: LoaderOption;
|
|
429
434
|
/** Start with `movie.loop` on: playing goes back to the start at the end instead of stopping. Default false (like a `<video>`). */
|
|
430
435
|
loop?: boolean;
|
|
436
|
+
/**
|
|
437
|
+
* The loudness of the whole mix in LUFS (ITU-R BS.1770): `-16` is usual for web video. The finished mix gets one gain (up or down) so its integrated loudness is that number, but the loudest sample
|
|
438
|
+
* never passes -1 dBFS: a mix with one loud click in a quiet bed stops short, and `inspectAudio()` / `check` say how far it got. Default: off (the mix is what the layers make; `check` says when it is quiet).
|
|
439
|
+
* A number of 0 or below, or `false`.
|
|
440
|
+
*/
|
|
441
|
+
loudness?: number | false;
|
|
431
442
|
}
|
|
432
443
|
|
|
433
444
|
interface RenderOptions {
|
|
@@ -611,6 +622,7 @@ declare class Movie {
|
|
|
611
622
|
/** What went into the mix, and its peak before limiting — kept for inspectAudio(). */
|
|
612
623
|
private _audioSources;
|
|
613
624
|
private _mixStats;
|
|
625
|
+
private _loudness;
|
|
614
626
|
on(event: 'ready', fn: () => void): this;
|
|
615
627
|
on(event: 'frame', fn: (e: FrameEvent) => void): this;
|
|
616
628
|
/** A jump to another frame (`gotoFrame`, a seek bar) starts / finishes. Not emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()`. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkDKCMJILW_cjs = require('./chunk-DKCMJILW.cjs');
|
|
4
|
+
var chunkLALEBV63_cjs = require('./chunk-LALEBV63.cjs');
|
|
5
5
|
require('./chunk-JN7JHNZR.cjs');
|
|
6
6
|
require('./chunk-3SAS3ZE5.cjs');
|
|
7
7
|
require('./chunk-TWZNOUM7.cjs');
|
|
@@ -12,12 +12,12 @@ var strip = (props) => {
|
|
|
12
12
|
const { visibleChars: _gone, ...rest } = props;
|
|
13
13
|
return rest;
|
|
14
14
|
};
|
|
15
|
-
var SpansSequence = class extends
|
|
15
|
+
var SpansSequence = class extends chunkDKCMJILW_cjs.CompositionSequence {
|
|
16
16
|
_meta;
|
|
17
17
|
_count = { value: 0, visibleChars: 0 };
|
|
18
18
|
constructor(spec, parent, root) {
|
|
19
19
|
super(spec, parent, root);
|
|
20
|
-
this._meta =
|
|
20
|
+
this._meta = chunkLALEBV63_cjs.spansMeta.get(spec);
|
|
21
21
|
}
|
|
22
22
|
/** `visibleChars` is the group's own counter, not a property to write on the container. */
|
|
23
23
|
displayProps() {
|
|
@@ -33,8 +33,8 @@ var SpansSequence = class extends chunk3HZV3UHU_cjs.CompositionSequence {
|
|
|
33
33
|
if (!this._meta?.typed) return;
|
|
34
34
|
const scope = this.scope();
|
|
35
35
|
const first = this.spec.initial?.visibleChars;
|
|
36
|
-
this._count.visibleChars = first === void 0 ? 0 :
|
|
37
|
-
|
|
36
|
+
this._count.visibleChars = first === void 0 ? 0 : chunkLALEBV63_cjs.resolveNumber(first, scope);
|
|
37
|
+
chunkLALEBV63_cjs.bindCounterKeyframes(timeline, this._count, "visibleChars", () => this._distribute(), this.spec.keyframes ?? [], this.duration, offset + this.at, scope);
|
|
38
38
|
this._distribute();
|
|
39
39
|
}
|
|
40
40
|
/** Every piece shows its part of the first N characters of the whole text. */
|
|
@@ -43,7 +43,7 @@ var SpansSequence = class extends chunk3HZV3UHU_cjs.CompositionSequence {
|
|
|
43
43
|
const total = Math.max(0, Math.floor(this._count.visibleChars));
|
|
44
44
|
let i = 0;
|
|
45
45
|
for (const child of this._children) {
|
|
46
|
-
if (!(child instanceof
|
|
46
|
+
if (!(child instanceof chunkLALEBV63_cjs.TextSequence)) continue;
|
|
47
47
|
const p = pieces[i++];
|
|
48
48
|
if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));
|
|
49
49
|
}
|
|
@@ -51,5 +51,5 @@ var SpansSequence = class extends chunk3HZV3UHU_cjs.CompositionSequence {
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
exports.SpansSequence = SpansSequence;
|
|
54
|
-
//# sourceMappingURL=Spans-
|
|
55
|
-
//# sourceMappingURL=Spans-
|
|
54
|
+
//# sourceMappingURL=Spans-2TZCNREQ.cjs.map
|
|
55
|
+
//# sourceMappingURL=Spans-2TZCNREQ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sequences/Spans.ts"],"names":["CompositionSequence","spansMeta","resolveNumber","bindCounterKeyframes","TextSequence"],"mappings":";;;;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4BA,qCAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQC,2BAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAIC,+BAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAAC,sCAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiBC,8BAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-
|
|
1
|
+
{"version":3,"sources":["../src/sequences/Spans.ts"],"names":["CompositionSequence","spansMeta","resolveNumber","bindCounterKeyframes","TextSequence"],"mappings":";;;;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4BA,qCAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQC,2BAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAIC,+BAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAAC,sCAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiBC,8BAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-2TZCNREQ.cjs","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CompositionSequence } from './chunk-
|
|
2
|
-
import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-
|
|
1
|
+
import { CompositionSequence } from './chunk-FPV3NPYC.js';
|
|
2
|
+
import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-DM2OLQOJ.js';
|
|
3
3
|
import './chunk-VNP7QCJE.js';
|
|
4
4
|
import './chunk-32MMNDDN.js';
|
|
5
5
|
import './chunk-BXOCEZ4U.js';
|
|
@@ -49,5 +49,5 @@ var SpansSequence = class extends CompositionSequence {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export { SpansSequence };
|
|
52
|
-
//# sourceMappingURL=Spans-
|
|
53
|
-
//# sourceMappingURL=Spans-
|
|
52
|
+
//# sourceMappingURL=Spans-TESJPQKX.js.map
|
|
53
|
+
//# sourceMappingURL=Spans-TESJPQKX.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sequences/Spans.ts"],"names":[],"mappings":";;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4B,mBAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAI,aAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAA,oBAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiB,YAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-
|
|
1
|
+
{"version":3,"sources":["../src/sequences/Spans.ts"],"names":[],"mappings":";;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4B,mBAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAI,aAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAA,oBAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiB,YAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-TESJPQKX.js","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}
|