pixi-effects 0.16.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +7 -7
- package/ai/CHAT.md +7 -0
- package/ai/SKILL.md +3 -2
- package/ai/chat-template.html +8 -4
- package/ai/reference/cheatsheet.md +6 -2
- package/ai/reference/pitfalls.md +3 -0
- package/ai/reference/recipes.md +67 -0
- package/ai/template.html +5 -5
- package/ai/tools/check.mjs +119 -59
- package/ai/tools/render.mjs +22 -7
- package/dist/Composition-6JMXWNVV.cjs +14 -0
- package/dist/{Composition-3WWNVRR5.cjs.map → Composition-6JMXWNVV.cjs.map} +1 -1
- package/dist/Composition-MLBHYYFF.js +5 -0
- package/dist/{Composition-VJWY2NDL.js.map → Composition-MLBHYYFF.js.map} +1 -1
- package/dist/Controller.d.cts +1 -1
- package/dist/Controller.d.ts +1 -1
- package/dist/{Movie-DilBC348.d.ts → Movie-DlHQ9C02.d.ts} +126 -2
- package/dist/{Movie-oA0SUAyb.d.cts → Movie-K2YuVAdg.d.cts} +126 -2
- package/dist/Presenter.d.cts +1 -1
- package/dist/Presenter.d.ts +1 -1
- package/dist/{chunk-FJA5VUOF.cjs → chunk-ANO5R2E6.cjs} +25 -3
- package/dist/chunk-ANO5R2E6.cjs.map +1 -0
- package/dist/{chunk-2ROVCPSL.cjs → chunk-JW67MDHQ.cjs} +32 -27
- package/dist/chunk-JW67MDHQ.cjs.map +1 -0
- package/dist/{chunk-ZNKQH356.js → chunk-SG3Y66FN.js} +25 -4
- package/dist/chunk-SG3Y66FN.js.map +1 -0
- package/dist/{chunk-XTIHKODH.js → chunk-UXDR3ZDU.js} +10 -5
- package/dist/chunk-UXDR3ZDU.js.map +1 -0
- package/dist/index.cjs +464 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +442 -21
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/dsl.md +2 -1
- package/llms-full.txt +81 -5
- package/llms.txt +1 -1
- package/package.json +6 -3
- package/dist/Composition-3WWNVRR5.cjs +0 -14
- package/dist/Composition-VJWY2NDL.js +0 -5
- package/dist/chunk-2ROVCPSL.cjs.map +0 -1
- package/dist/chunk-FJA5VUOF.cjs.map +0 -1
- package/dist/chunk-XTIHKODH.js.map +0 -1
- package/dist/chunk-ZNKQH356.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
**Fixed**
|
|
6
|
+
|
|
7
|
+
- `pixi-effects-render --range 5:2` is a usage error (exit code 2, with what to write), not a failed render (exit code 1).
|
|
8
|
+
- `pixi-effects-check`: a mistake in `--at` or `--onion` is reported before any of the slow work (no sheet or report is written); a `--at` label such as `a/b@end` can no longer make a folder or an illegal file name.
|
|
9
|
+
- A mistyped `ease` that is empty (`ease: ''`) or a family name (`ease: 'Power2'`, which GSAP answers with an object, not an ease) now warns with what to write; before, `Power2` passed silently.
|
|
10
|
+
- `movie.inspectFonts()` and `check`: a web font that failed to load fails the check only when a text layer uses it; an unused broken `@font-face` (`failedUnused`) is listed for review. An offline CI page that links web fonts it never uses no longer fails.
|
|
11
|
+
- After a range export with motion blur the stage is left on the last frame that was exported, not on the end of the movie.
|
|
12
|
+
- A layer the timeline folds into a family row (`pop-1` … `pop-12` → `pop-# ×12`) can be named in `render({ range })`, `--scene` and `--at name@end` (`movie.timelineData()` rows carry `partNames`).
|
|
13
|
+
- The docs' motion-blur claim is now a measurement: 4 s of `hanabi-night` (a piece that asks for motion blur) took 14.0 s and 8.2 s as a draft, about 7 s of each being the browser's start-up.
|
|
14
|
+
|
|
15
|
+
**Checked, nothing to change:** the true peak reads 0.00 dB for a faded 0 dBFS sine from 1 to 18 kHz and +0.06 dB at 20 kHz (a sine switched on abruptly reads up to 0.4 dB higher at 20 kHz: that is real ringing at the switch-on); a test pins it.
|
|
16
|
+
|
|
17
|
+
**Added**
|
|
18
|
+
|
|
19
|
+
- **The Playground is rebuilt** ([guide](https://yjmtmtk.github.io/pixi-effects/guide/playground.html)). The editor holds the same block an AI edits in a chat (`ai/chat-template.html` between `EDIT FROM HERE` and `EDIT UNTIL HERE`), so what an AI wrote can be pasted in and what you wrote can be saved as the chat template's page. The movie runs in an `iframe` with no origin (`sandbox="allow-scripts allow-downloads"`: it cannot reach the page or the site's stored data; proved on a real browser) and the page talks to it through eight fixed commands. A problems panel shows what `movie.review()` finds (click to jump to the frame). **Share link** packs the code into the address (`#code=`, deflate + base64url, no server; a link is read, never run, until you press Run; one that unpacks to more than 200 KB is refused). **Save HTML** downloads one page that runs on its own (examples that use the site's own files load them from the site); **Copy for AI** copies the code with a sentence for an AI. Light and dark. The twelve examples are rewritten in the edit-block form and each runs with no warning. Twenty runs in a row leave one frame and raise no WebGL-context warning (a calibration warning raised in the frame proves the test can see it); a run takes 0.2 to 0.3 s for a title and 0.4 to 0.6 s for a piece with a video file (headless Chrome, libraries cached).
|
|
20
|
+
- **Ten WebMCP tools** on the Playground (`get_docs`, `list_examples`, `load_example`, `get_code`, `set_code`, `run`, `check`, `look`, `onion`, `render_draft`), registered with `document.modelContext` where the browser has it, so an AI agent in the browser writes, runs, reads the warnings and looks at the picture without a person pasting a red box back. They run one at a time (Chrome runs calls in parallel), a movie that never starts (an endless loop) or never answers is given up on after 70 s / 120 s so the queue cannot stall, content the page's own code can write (warnings, the code) is marked `untrustedContentHint`, and a mistake in an argument is an error result that says what is wrong. Checked on Chrome 154 with `--enable-features=WebMCP`: results are `{ content: [...] }` (text and image), `executeTool` takes the arguments as a JSON string, a tool that throws reaches the agent with no message, so tools return `isError` results instead.
|
|
21
|
+
- **`movie.review({ at, strict })` and `movie.resolveAt(list)`**: what `pixi-effects-check` looks at, in the library (frames inspected, problems, things to look at, fonts, the sound, the moments of `at`), so the `check` command, the Playground and the WebMCP tools share one judgement. Running it leaves the movie as it was (playhead and poster). The report of `check` is the same before and after the move: no difference in `inspect`, fonts, `at`, problems and audio on six gallery pieces.
|
|
22
|
+
- The chat template accepts an optional `const INIT = { assets: [...], composition: { transitions: [...] } }` in its edit block, merged into `movie.init`.
|
|
23
|
+
|
|
24
|
+
**Changed**
|
|
25
|
+
|
|
26
|
+
- `pixi-effects-check` needs a page that loads pixi-effects **0.18 or newer** (it calls `movie.review()`); an older page says so instead of being checked.
|
|
27
|
+
- The old `examples/_presets/` is gone (the presets live in `examples/playground/presets/`, in the edit-block form).
|
|
28
|
+
|
|
29
|
+
## 0.17.0
|
|
30
|
+
|
|
31
|
+
**Fixed**
|
|
32
|
+
|
|
33
|
+
- **A text layer used as a mask is the letters, not their bounding box.** `mask: { type: 'text', … }` cut out a plain rectangle (a stencil ignores the letters' alpha), although the docs say any layer works as a mask. It is now an alpha mask: drawn into a texture, so the letters (soft, antialiased edges) are what is kept.
|
|
34
|
+
- **`maskInverted` inside a masked group no longer shows the layer outside the outer mask.** PIXI's inverted stencil tests against the empty level, so a layer with an inverted mask inside a masked composition or `null` layer was drawn everywhere the outer mask hides. An inverted mask is now an alpha mask too (inverse = 1 − alpha). Both fixes cost one extra offscreen pass for the masked layer; ordinary shape masks are unchanged (still a stencil), and the two gallery pieces that use `maskInverted` (`ma`, `liquid-flow-title`) render the same before and after in the six frames compared of each (no pixel differs by more than 29 of 255, and almost all by 0).
|
|
35
|
+
|
|
36
|
+
**Added**
|
|
37
|
+
|
|
38
|
+
- **Part of the movie, and drafts.** `movie.render({ range: [10, 15] })` (seconds) or `range: 'title'` (a top-level layer's span) exports only that part, picture and sound (the file starts at 0; a cut edge of the sound fades over 10 ms). `scale` (above 0, at most 1) and `draft: true` (half size, low quality, no motion blur) make the picture smaller. `pixi-effects-render` takes `--range 10:15`, `--scene title`, `--scale 0.5` and `--draft`. Measured with the 48 s 1080p film on an M1 Pro (whole command, about 7 s of it the browser's start-up): the whole film 20.8 s, `--range 10:15` 10.4 s, `--draft` 20.6 s with a file 7 times smaller (12.0 MB to 1.7 MB), `--scale 0.5` 19.0 s. **A range saves time; a draft or a scale saves file size** (each frame is still drawn at full size, then copied smaller).
|
|
39
|
+
- **`pixi-effects-check` looks harder, and shows a moment or a movement.** It inspects the first and last frame of every scene (a named top-level composition of a second or more) and a frame every 0.25 s, up to 240 frames (a gallery piece went from 59 to 86 inspected frames in the same 3 s; measuring the sound adds time on sound-heavy pieces: `movie.inspectAudio()` takes 1.4 s on the 22 s `christmas-eve` and 3.3 s on the 48 s `ma`, and a whole `check --no-export` takes 3.2 s, 5.5 s and 10 s on `quiet-hours`, `christmas-eve` and `ma`). `--at 3.5,title@end,50%,f120` writes the pictures at those moments (`frames/*.png`, `at.png`); `--onion 1:3` writes `onion.png` (below); `--draft` makes the export a draft; `--query "name=Aiko"` adds to the page address (for a page that reads it, such as a batch of videos). A web font whose file did not load fails the check, and a text layer none of whose fonts is installed is listed for review (`movie.inspectFonts()`).
|
|
40
|
+
- **`movie.onionSkin({ from, to, count, scale, as })`**: frames laid over one another into one picture of a movement, the later the stronger. A thing that moves leaves a trail (its path and its easing); what stays still stays itself.
|
|
41
|
+
- **Loudness.** `movie.inspectAudio()` also returns `loudness` (`integratedLufs`, `truePeakDb`, `clippedSamples`, `silences`; ITU-R BS.1770, no dependency), `scenes` (the same per named top-level layer), `cues` (when each sound starts) and `notes` (advice that does not fail the check: quiet below −24 or loud above −9 LUFS, a true peak above −1 dBTP, silent stretches of a second or more; web video is typically −14 to −16 LUFS). Clipping, or a true peak above 0 dBTP, is an issue and fails the check. `pixi-effects-check` prints it and writes `waveform.png` (the mix as bars and a peak line, scene edges, numbered ticks where each sound starts). The default level of `music` measures about −19 to −20 LUFS (`christmas-eve` −18.8, a lone tune −19.9), inside that range.
|
|
42
|
+
- **Two recipes in the cookbook:** subtitles from an SRT file, and a batch of videos from a table (a page that reads its address plus a shell loop over `pixi-effects-render --query`); both are tested.
|
|
43
|
+
|
|
44
|
+
**Site**
|
|
45
|
+
|
|
46
|
+
- **One look for the whole site.** The landing page, Guide, Gallery, Examples and Playground share one set of colours, fonts, header and footer (`site/shared/`), with the landing page's palette as the reference and both the dark and the light theme everywhere. The navigation is the same five entrances on every page (Home, Guide, Gallery, Examples, Playground); on a phone the header is two rows so nothing falls off the screen. `site/README.md` says where everything is and how to change it.
|
|
47
|
+
- **The Gallery is rebuilt on those parts.** Same behaviour (filters, the theatre with next / previous, `#id` links); it no longer loads a web font, and its theatre stays a dark room in the light theme. The total running time in its intro no longer prints `47.700000000000045 s`.
|
|
48
|
+
- **The Examples entrance is back, as a list of cards** (`examples/examples.json`, including the music lab and the playground). When the landing page became the Pages root it replaced the old list of the numbered examples, and the README's "Live demos" link pointed at the landing page; the README and `llms.txt` now link to `examples/`. A test fails if a numbered example is missing from the list.
|
|
49
|
+
- **The landing page moved to `index.html` at the repository root** (the repository root is the site root), so it opens from any static server without symlinks; the preview script and `npm run landing` are gone.
|
|
50
|
+
- **The deployed layout is code** (`scripts/stage-site.mjs`, which the Pages workflow runs) and a checker (`scripts/site-links.mjs`) fails the tests when a page has an `href` / `src` / `srcset`, an import-map target or an inline-module `import` that does not resolve to a file of the site (it does not follow links inside separate script or style files). `npm run site` builds that layout and serves it locally. The header, footer and `<head>` links of every page are written from one place (`scripts/site-parts.mjs`, `npm run site:sync`).
|
|
51
|
+
|
|
52
|
+
**Changed**
|
|
53
|
+
|
|
54
|
+
- **A mistyped `ease` warns once, with a guess** (`power3.outt`: did you mean `power3.out`). GSAP used to run an unknown ease as its default (`power1.out`): the motion changed and nothing said so.
|
|
55
|
+
- **The browser tools no longer make a sound.** `pixi-effects-check`, `pixi-effects-render` and the test suite start their private Chrome with `--mute-audio`, so a piece with sound no longer plays through the speakers while it is checked or exported (the audio is still analysed and encoded as before).
|
|
56
|
+
- **The release check runs once.** `npm run release:check` builds, type-checks and runs every test, then stamps the exact committed tree. `npm publish` (`prepublishOnly`) sees the stamp and only rebuilds instead of running the whole suite a second time; with no stamp, or with uncommitted changes, it runs everything as before. `npm run test:fast` runs the suite without the real-browser tests (about 10 s).
|
|
57
|
+
|
|
3
58
|
## 0.16.3
|
|
4
59
|
|
|
5
60
|
**Site**
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# pixi-effects
|
|
2
2
|
|
|
3
|
-
> **Status**: experimental — current release `0.
|
|
3
|
+
> **Status**: experimental — current release `0.18.0`. The API may still change between minor versions (see [CHANGELOG](./CHANGELOG.md): `0.18.0` adds a rebuilt Playground (an editor on the chat template's edit block, a sandboxed run, a problems panel, share links, save as HTML), ten WebMCP tools for an AI agent in the browser, `movie.review()` and `movie.resolveAt()` (and `check` needs a page with 0.18 or newer), `0.17.0` adds part-of-the-movie and draft exports (`range`, `scale`, `draft`), a stronger `check` (`--at`, `--onion`, fonts, loudness and a waveform), `movie.onionSkin()`, a warning for a mistyped `ease`, two cookbook recipes, and fixes text masks and inverted masks, `0.16.3` fixes the export progress bar disappearing while the mouse is still and updates the landing page's speed figures, `0.16.2` adds a landing page, `AGENTS.md` and a one-sentence start for AI, camera flights (`cameraPath`, shake offsets), `trimEach`, the showpiece `ma` and a fix for 3D cards after a seek, `0.16.1` adds `musicBuffer()` and a music lab page with eight tunes, `0.16.0` adds music written as text (`{ type: 'audio', music }`, `musicEnvelope()`), `0.15.0` adds a way in for an AI in a browser chat (`ai/CHAT.md` and `ai/chat-template.html`), `0.14.0` adds `movie.inspectStops()` (and a check review) for stops that land mid-animation, `0.13.1` fixes decks starting past their first stop in the guide and gallery and draws the stop marks as dots, `0.13.0` makes the player bar play a deck stop by stop (`pauseAtStops`), marks the stops more clearly, adds PDF to the download panel and a `pdf` flag on stops, `0.12.0` adds presentations (`stops`, `Presenter`, `deck()`, page overview, presenter view, PDF export) and audio-reactive visuals (`audioEnvelope()`, `react()`), `0.11.0` adds particles, motion blur, a shared loader and a much faster build for pieces with many layers, `0.10.0` adds seeded randomness, `wiggle` and `stagger`, null layers with `parent`, `animateText`, `followPath` and path morphing, and click-to-play in the player, `0.9.0` adds a poster time (`movie.init({ poster })`) and a guide site, `0.8.0` adds events and a theme for building your own player, `0.7.0` adds filters by name, the `pixi-effects-render` and `pixi-effects-view` commands and the timeline chart, `0.6.0` adds draw-on strokes (`trimEnd`) and text that changes over time, `0.5.0` adds synthesised sound effects (`sfx`, no files) and the `check` tool, `0.4.0` makes a mask share its layer's lifetime, `0.3.0` made keyframe `at` relative to the layer).
|
|
4
4
|
|
|
5
|
-
**[Guide →](https://yjmtmtk.github.io/pixi-effects/guide/)** · **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 39 portfolio pieces written as plain data by AI models · **[
|
|
5
|
+
**[Guide →](https://yjmtmtk.github.io/pixi-effects/guide/)** · **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 39 portfolio pieces written as plain data by AI models · **[Examples →](https://yjmtmtk.github.io/pixi-effects/examples/)** · 15 numbered examples, a music lab + an in-browser playground.
|
|
6
6
|
|
|
7
7
|
## Make a video by asking an AI
|
|
8
8
|
|
|
@@ -51,8 +51,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
51
51
|
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
52
52
|
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
53
53
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
54
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
55
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
54
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/index.js",
|
|
55
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/Controller.js"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
</script>
|
|
@@ -63,7 +63,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
63
63
|
</script>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.
|
|
66
|
+
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.18.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.
|
|
67
67
|
|
|
68
68
|
> **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
|
|
69
69
|
>
|
|
@@ -79,7 +79,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
|
|
|
79
79
|
"imports": {
|
|
80
80
|
"...": "(everything from the importmap above)",
|
|
81
81
|
"three": "https://esm.sh/three@0.178.0",
|
|
82
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
82
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/three.js"
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
</script>
|
|
@@ -204,7 +204,7 @@ They ship in the npm package (`node_modules/pixi-effects/ai/`).
|
|
|
204
204
|
- `15-custom-player.html` — a complete player of your own in about 40 lines (play, seek, volume, replay) using the movie's events
|
|
205
205
|
- `14-draw-on.html` — draw-on strokes (`trimStart` / `trimEnd`) and text that changes over time (`visibleChars`, `set: { text }`)
|
|
206
206
|
- `music-lab.html` — eight tunes written as text (no audio files): press play, read each score; `musicBuffer()` plays music with no movie
|
|
207
|
-
- `playground.html` — in-browser editor
|
|
207
|
+
- `playground.html` — in-browser editor on the chat template's edit block: twelve examples, a problems panel from `movie.review()`, share links, save as HTML, and WebMCP tools for an AI agent
|
|
208
208
|
|
|
209
209
|
## Browser support
|
|
210
210
|
|
package/ai/CHAT.md
CHANGED
|
@@ -9,6 +9,8 @@ You write ONE HTML file. The person opens it (or your chat previews it), watches
|
|
|
9
9
|
3. Give the person the whole file. Tell them: open it, press play (sound starts after the first click), use the download button (bottom right of the picture) for the MP4, and **if a red box appears, copy its text back to you**.
|
|
10
10
|
4. When they paste warnings, fix exactly what each one names. The messages say what to change ("did you mean …").
|
|
11
11
|
|
|
12
|
+
If you are an AI agent in a browser that offers WebMCP tools (`document.modelContext`), you do not need to hand over a file: open `https://yjmtmtk.github.io/pixi-effects/examples/playground.html` and use its tools. `get_docs` (read `cheatsheet` first) → `set_code` (the same edit block as below) → read the warnings it returns → `check` → `look` (a picture: look at it) → fix → `set_code` again. Nobody has to paste a red box back. Tools: `get_docs list_examples load_example get_code set_code run check look onion render_draft`.
|
|
13
|
+
|
|
12
14
|
## The idea
|
|
13
15
|
|
|
14
16
|
A video is **data**: a list of layers in `sequences`. Each layer has `type`, a start `at` and a `duration` (seconds), `initial` values and `keyframes`. Repeated structure is a JS loop or function that returns layers. There is no per-frame code and no `Math.random()`.
|
|
@@ -59,6 +61,7 @@ That is a complete, working video (the block between the two markers). Change th
|
|
|
59
61
|
- **Sound with no files:** `{ type: 'audio', sfx: 'swoosh', at: 2 }`. Presets: `click pop swoosh swipe hit riser chime beep coin glitch typewriter`; optional `pitch`, `brightness`. A `riser` is loudest at its **end**: start it `duration` seconds before the hit. This page cannot load music or images from your own files; use shapes, text, sfx and `music` (below).
|
|
60
62
|
- **Repeat with code, never by copying:** `Array.from({ length: 12 }, (_, i) => ({ type: 'shape', shape: 'circle', radius: 20, at: i * 0.15, duration: DURATION - i * 0.15, initial: { x: 120 + i * 90, y: 360, fillColor: '#ffd166' }, keyframes: [{ at: 0, from: { scale: 0 }, to: { scale: 1 }, duration: 0.4, ease: 'back.out(2)' }] }))` returns twelve layers: spread them into `sequences` with `...`.
|
|
61
63
|
- **Helpers already imported** in the template: `kenBurns` (slow zoom for an image layer), `withFade`, `wiggle` (seeded shake as keyframes), `stagger`, `animateText` (per letter / word entrances), `followPath`, `particles` (seeded confetti / snow / sparks as layers), `deck` (slides). Each returns keyframes or layers: `keyframes: wiggle(...)`, `...particles(...)`. The full signatures are in `https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/ai/reference/cheatsheet.md` (22 KB; read it only if you need a helper).
|
|
64
|
+
- **Advanced:** `const INIT = { assets: [...], composition: { transitions: [...] } }` in the edit block adds `movie.init` options (the page merges it); most videos need nothing here.
|
|
62
65
|
- **Not available on this page:** named filters beyond `blur`, `noise`, `alpha`, `colorMatrix` (the other 38 need the `pixi-filters` package), three.js layers, your own image / video / music files. Say so rather than guessing.
|
|
63
66
|
|
|
64
67
|
## Music with no file
|
|
@@ -96,6 +99,10 @@ Every token is a note (`c4`, `f#3`, `Bb2`) with its length in beats after a colo
|
|
|
96
99
|
|
|
97
100
|
Read your file once for these: every layer has a `type`; every keyframe `at` is inside its layer's lifetime; text is anchored where you meant; layer `duration`s reach the end of their scene; colours have enough contrast; nothing important sits near the edges. A mistake in the data prints a warning in the red box; the person's paste is your test run.
|
|
98
101
|
|
|
102
|
+
## Useful, if the person can open the browser console
|
|
103
|
+
|
|
104
|
+
`movie.inspectAudio()` has `notes` (is the mix quiet or loud for web video, silent stretches) and `loudness.integratedLufs` (typical web video: −14 to −16); `await movie.onionSkin({ from: 1, to: 3, as: 'dataURL' })` is one picture of a movement. A mistyped `ease` prints a warning in the red box.
|
|
105
|
+
|
|
99
106
|
## If the preview is blank
|
|
100
107
|
|
|
101
108
|
A blank preview with no red box usually means the chat's preview window blocks external scripts or does not allow `eval`. Tell the person to **save the file as `video.html` and open it in a browser** (Chrome, Edge or Firefox on a computer); that always works.
|
package/ai/SKILL.md
CHANGED
|
@@ -12,16 +12,17 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
12
12
|
1. **Start from `template.html`** (copy it; it also has the shared loader (`<div class="pe-loader" data-label="LOADING">` + `pixi-effects/loader.css`: HTML and CSS only, animating just `transform` / `opacity`, so it keeps moving while the page builds the video; `movie.init()` fades it out). Keep it; a loader of your own is fine (`pe-loader--custom`) as long as it only animates `transform` / `opacity`, never with JavaScript or `requestAnimationFrame`; it loads the library from a CDN, collects every console warning into `window.__logs`, and exposes `window.movie`). One HTML file, plain JS. Default canvas 1280×720 @ 30 fps.
|
|
13
13
|
2. **Give the movie a poster time.** In `movie.init` add `poster: <seconds>`: the most striking moment (a title fully on screen, not the first frame, which is often black). The canvas shows that picture before play (the playhead stays at 0 and play starts from 0), and the check writes it as `poster.jpg`.
|
|
14
14
|
3. **Write the composition as data.** Anything repeated (letters, bars, particles, scenes) is a JS function or loop that returns specs — never copy-paste objects. Derive coordinates and times from constants/data, not magic numbers.
|
|
15
|
-
4. **Show the human:** `node ai/tools/view.mjs my-video.html` opens their browser on the page with a scrubbable, zoomable timeline under it (click / drag to seek, + / − or Ctrl/⌘ + wheel to zoom) (`npx pixi-effects-view …` in an installed package); it keeps running until Ctrl-C. **Deliver the file:** `node ai/tools/render.mjs my-video.html -o my-video.mp4` (`npx pixi-effects-render …` in an installed package) renders the page headless to mp4 / webm / mov / mkv and exits 0 only when the file is written; run it AFTER the check below passes. **Run the check — one command does steps 3, 4 and the export:** `node ai/tools/check.mjs my-video.html` (Node ≥ 22 and Chrome installed; no npm install; in an installed package it is `npx pixi-effects-check my-video.html`). It starts its own private Chrome (nothing is shared with other sessions), waits for `window.__ready`, and writes `timeline.html` (every layer as a bar on a time axis: ◆ keyframes, shaded transitions; hand it to the human to open) and prints: console warnings, `movie.inspect` over the WHOLE timeline (grouped), `movie.inspectAudio()`, then exports an mp4 and decodes it again (video size and length, audio length, loudness per second, silence). It writes `check-out/<name>/sheet.png` — **open that picture and look at it** — plus the mp4 and `report.json`. Exit 0 = nothing to fix; exit 1 = a list of problems; fix them and run it again. Text that overlaps text is listed under `review` (not a failure: ghost layers, glow copies and per-letter boxes are often intentional; look at the sheet; `--strict` makes them fail). The manual steps below are for looking at one frame closely.
|
|
15
|
+
4. **Show the human:** `node ai/tools/view.mjs my-video.html` opens their browser on the page with a scrubbable, zoomable timeline under it (click / drag to seek, + / − or Ctrl/⌘ + wheel to zoom) (`npx pixi-effects-view …` in an installed package); it keeps running until Ctrl-C. **Deliver the file:** `node ai/tools/render.mjs my-video.html -o my-video.mp4` (`npx pixi-effects-render …` in an installed package) renders the page headless to mp4 / webm / mov / mkv and exits 0 only when the file is written; run it AFTER the check below passes. **Run the check — one command does steps 3, 4 and the export:** `node ai/tools/check.mjs my-video.html` (Node ≥ 22 and Chrome installed; no npm install; in an installed package it is `npx pixi-effects-check my-video.html`). It starts its own private Chrome (nothing is shared with other sessions), waits for `window.__ready`, and writes `timeline.html` (every layer as a bar on a time axis: ◆ keyframes, shaded transitions; hand it to the human to open) and prints: console warnings, `movie.inspect` over the WHOLE timeline (grouped), `movie.inspectAudio()`, then exports an mp4 and decodes it again (video size and length, audio length, loudness per second, silence). It writes `check-out/<name>/sheet.png` — **open that picture and look at it** — plus the mp4 and `report.json`. Exit 0 = nothing to fix; exit 1 = a list of problems; fix them and run it again. Text that overlaps text is listed under `review` (not a failure: ghost layers, glow copies and per-letter boxes are often intentional; look at the sheet; `--strict` makes them fail). The manual steps below are for looking at one frame closely. The same judgement is in the page: `await movie.review({ at: 'title@end' })` returns `{ frames, problems, review, fonts, audio, at }` (what `check` prints, as data) and `movie.resolveAt('3.5, title@end')` turns moments into frames.
|
|
16
16
|
**Or by hand: run it and read the logs first.** `window.__ready` must be `true` and `window.__logs` should be `[]`. A pixi-effects warning is an instruction: fix what it names.
|
|
17
17
|
5. **Look at it** — you cannot judge motion from code. The library has the tools built in:
|
|
18
18
|
- `await movie.contactSheet({ count: 6, as: 'dataURL' })` → ONE image of several labelled frames (pass `frames: [...]` / `times: [...]` to choose; include the middle of every transition and the last second). Save it and view it.
|
|
19
19
|
- `await movie.inspect(frame)` → every layer's canvas bounds and visibility, plus `issues` (text off the canvas, cut off by an edge, empty, or overlapping other text). Run it at several frames; fix every issue it names.
|
|
20
20
|
- `await movie.snapshot(frame, { as: 'dataURL' })` → one frame, canvas only (no player bar).
|
|
21
21
|
- `movie.inspectAudio()` → **hear with numbers**: for every sound its `start`, `sound.length`, `sound.peakDb`, `sound.loudestAt` (line this up with the picture) and `sound.brightnessHz` (< 500 dark, > 8000 airy), plus `issues` (limited mix, inaudible or cut-off sounds). Check it whenever the movie has audio; `render()` encodes this same mix.
|
|
22
|
+
- **A moment, a movement, the sound (`pixi-effects-check` options):** `--at 3.5,title@end,50%,f120` writes those pictures to `check-out/<name>/frames/*.png` (and `at.png`): the last frame of a layer is `name@end`, the middle `name@mid`. `--onion 1:3` writes `onion.png`, eight frames of seconds 1–3 overlaid (the later the stronger): a trail that shows a movement's path and easing (even steps = constant speed, bunched = slow). `report.audio` now has `loudness` (`integratedLufs`, `truePeakDb`, `clippedSamples`, `silences`), `scenes` (per named top-level composition), `cues` (when each sound starts) and `notes` (advice: quiet or loud for web video, little headroom, silent stretches; web video is typically −14 to −16 LUFS); `waveform.png` draws the mix with the scene edges and the cues. A web font that failed to load fails the check; a text layer none of whose fonts is installed is listed for review.
|
|
22
23
|
- **A deck** (`composition.stops`, `deck()`): `await movie.contactSheet({ frames: movie.stops.map(s => s.frame), as: 'dataURL' })` is one picture of every stop (the check writes it as `stops.png`); what the audience reads while a page waits is that picture, so check every one. `await movie.inspectStops()` lists the stops where the picture is still changing (a stop landing before its animation ends); the check prints it as a review list: move the stop later or flag a settled one `pdf: true`. `await movie.stopImages({ which: 'pages' })` / `movie.exportPDF()` give the pages themselves.
|
|
23
24
|
From a browser tool the result is a `data:` URL string: `agent-browser eval "movie.contactSheet({ count: 6, as: 'dataURL' })" | python3 ai/tools/save-image.py /absolute/sheet.png`, then open the PNG. Sheet tiles are small (~480 px): use `snapshot` for detail. `inspect` output is long on busy scenes: pass `{ layers: 'none' }` and read `issues` first. Give layers a `name` so inspect paths are readable. A moving ticker is not flagged as cut off.
|
|
24
|
-
6. **Iterate** on what you saw (clipped text, collisions, wrong timing). Fast motion looks choppy: add `motionBlur: true` to `movie.init` (render, snapshot and contact sheet then show a film-style blur; renders take ~8× longer). Then export: `const blob = await movie.render({ format: 'mp4' })` (about real time; `movie.on('progress', …)` reports 0–100).
|
|
25
|
+
6. **Iterate** on what you saw (clipped text, collisions, wrong timing). Fast motion looks choppy: add `motionBlur: true` to `movie.init` (render, snapshot and contact sheet then show a film-style blur; renders take ~8× longer). Then export: `const blob = await movie.render({ format: 'mp4' })` (about real time; `movie.on('progress', …)` reports 0–100). **While you iterate, do not export the whole movie:** `check --no-export --at <the moment you changed>` takes seconds (3 to 10 s on the gallery pieces: most of it is loading the page and measuring the sound); to see motion, `render({ range: [from, to] })` / `pixi-effects-render --range 10:15` / `--scene name` exports only that part (picture and sound); `draft: true` / `--draft` is half size, low quality and no motion blur: a much smaller file (a piece that asks for motion blur renders much faster as a draft: 4 s of `hanabi-night` took 14.0 s, and 8.2 s as a draft, about 7 s of either being the browser's start-up), but the drawing itself is not faster. Export the whole movie once, at the end.
|
|
25
26
|
|
|
26
27
|
## The rules that cause most failures
|
|
27
28
|
|
package/ai/chat-template.html
CHANGED
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"gsap": "https://cdn.jsdelivr.net/npm/gsap@3.12.5/+esm",
|
|
62
62
|
"gsap/PixiPlugin": "https://cdn.jsdelivr.net/npm/gsap@3.12.5/PixiPlugin/+esm",
|
|
63
63
|
"mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
|
|
64
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
65
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
66
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
64
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/index.js",
|
|
65
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/Controller.js",
|
|
66
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/Presenter.js"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
</script>
|
|
@@ -95,13 +95,17 @@
|
|
|
95
95
|
window.movie = movie;
|
|
96
96
|
new Controller(movie, { canvas: document.getElementById('stage') });
|
|
97
97
|
|
|
98
|
+
// Optional, for advanced pages: const INIT = { assets: [...], composition: { transitions: [...] }, motionBlur: true } adds movie.init options.
|
|
99
|
+
const EXTRA = typeof INIT === 'object' && INIT ? INIT : {};
|
|
100
|
+
|
|
98
101
|
try {
|
|
99
102
|
await movie.init({
|
|
100
103
|
canvas: document.getElementById('stage'),
|
|
101
104
|
width: W, height: H, duration: DURATION, frameRate: FPS,
|
|
102
105
|
poster: POSTER,
|
|
103
106
|
background: BACKGROUND,
|
|
104
|
-
|
|
107
|
+
...EXTRA,
|
|
108
|
+
composition: { sequences, ...(EXTRA.composition || {}) },
|
|
105
109
|
});
|
|
106
110
|
window.__ready = true;
|
|
107
111
|
} catch (e) {
|
|
@@ -163,12 +163,16 @@ const movie = new Movie();
|
|
|
163
163
|
await movie.init({ canvas, width, height, duration, frameRate, background, assets, composition });
|
|
164
164
|
movie.play(); movie.pause();
|
|
165
165
|
await movie.gotoFrame(n, true); // seek (frames; 30 fps => t = n/30)
|
|
166
|
-
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
166
|
+
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. Part of it: `range: [2, 5]` (seconds) or `range: 'title'` (a top-level layer's span; picture and sound); `scale: 0.5` / `draft: true` (half size, low quality, no motion blur: a smaller file, not a faster drawing). Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
167
167
|
movie.on('progress', e => e.progress /* 0–100 */);
|
|
168
168
|
movie.audioBuffer // mixed audio (after init), if any audio layers
|
|
169
169
|
await movie.contactSheet({ count: 6, as: 'dataURL' }) // ONE image of several labelled frames — look at it. Options: frames | times | count, columns, cellWidth, as
|
|
170
170
|
await movie.snapshot(60, { as: 'dataURL' }) // one frame, canvas only (no player bar) — use it for detail; sheet tiles are small
|
|
171
171
|
await movie.inspect(60, { layers: 'none' }) // issues only (default lists the visible layers); text off-canvas / cut off / empty / overlapping text. Name your layers so paths are readable
|
|
172
|
-
movie.
|
|
172
|
+
await movie.onionSkin({ from: 1, to: 3, count: 8, as: 'dataURL' }) // frames overlaid into ONE picture of a movement (later = stronger): path and easing at a glance
|
|
173
|
+
movie.review({ at: 'title@end' }) // what `pixi-effects-check` looks at, as data: { frames, problems (text cut off, off-canvas, no size), review (text overlaps: look at them), fonts, audio, at }; leaves the movie as it was. `strict: true` makes every issue a problem
|
|
174
|
+
movie.resolveAt('3.5, 50%, f120, title@end') // moments → [{ label, frame }]; a mistake says what could not be read
|
|
175
|
+
movie.inspectFonts() // { missing: [{ layer, family }] (no font of the list is installed), failed: [web fonts whose file did not load] }
|
|
176
|
+
movie.inspectAudio() // the soundtrack as numbers; also loudness { integratedLufs, truePeakDb, clippedSamples, silences }, scenes, cues, notes (advice: -14 to -16 LUFS is typical for web video) — — you cannot listen: sources[i] = { layer, start, end, peakDb, sound: { length, peakDb, loudestAt, brightnessHz } }, windows (rms / peak / brightness per 0.1 s), issues (limited mix, inaudible / cut-off sounds)
|
|
173
177
|
new Controller(movie, { canvas }) // optional player bar (overlays the canvas bottom ~60px; not in the export)
|
|
174
178
|
```
|
package/ai/reference/pitfalls.md
CHANGED
|
@@ -53,6 +53,9 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
53
53
|
30c. **Look with the built-in tools, not OS screenshots**: `await movie.contactSheet({ count: 6, as: 'dataURL' })` returns one labelled image of the whole animation; `await movie.inspect(frame)` returns every layer's canvas bounds and `issues`. If you do use a browser tool, `screenshot` needs an **absolute path**, macOS `sed -i` needs `''`, and a static server may redirect `/x.html` → `/x` and drop `?query`. Keep the template's `try/catch` around `init` so failures land in `window.__logs`. Check ≥ 5 timestamps including mid-transition. *(fixed: tools built in)*
|
|
54
54
|
31. **Check audio with `movie.inspectAudio()`**: every sound measured on its own (`sound.length`, `peakDb`, `loudestAt`, `brightnessHz`), when it plays, loudness and brightness per 0.1 s window, and `issues` (the mix was limited, a layer is inaudible, an sfx is cut off by the end). Decoding a render by hand is no longer needed: `render()` encodes the same mix, on time in mp4 / mov / webm / mkv. *(fixed)*
|
|
55
55
|
|
|
56
|
+
31b. **A mistyped `ease` now warns** (`ease: 'power3.outt'` → did you mean `power3.out`). It used to run silently as GSAP's default (`power1.out`), so the motion changed and nothing said so.
|
|
57
|
+
31c. **`draft` and `scale` make the file smaller, not the drawing faster** (measured: a 48 s 1080p film exports in about the same time as a draft; the file is 7× smaller). To save time, export a part (`range` / `--range` / `--scene`), look at single moments (`check --no-export --at …`), and keep motion blur off while you iterate.
|
|
58
|
+
|
|
56
59
|
## Round 2 — found by building 16 portfolio pieces (examples/gallery)
|
|
57
60
|
|
|
58
61
|
32. **Seeking is safe now** *(fixed)*: a `set` keyframe (counter value, text fill, image tint, shape style) is undone when you seek back before it, and shapes are redrawn before they are culled, so a rect growing from `width: 0` at the left edge draws after any jump seek (contact sheet, snapshot, scrubbing, export). The old workarounds (`width: 0.01`, a one-frame `to` instead of `set`) are not needed.
|
package/ai/reference/recipes.md
CHANGED
|
@@ -760,3 +760,70 @@ three({
|
|
|
760
760
|
keyframes: [{ at: 0, to: { 'three.knot.rotation.y': Math.PI * 2 }, duration: 6 }],
|
|
761
761
|
})
|
|
762
762
|
```
|
|
763
|
+
|
|
764
|
+
## Subtitles from an SRT file
|
|
765
|
+
|
|
766
|
+
Subtitles are just text layers. The function reads a `.srt` (numbers, `00:00:01,000 --> 00:00:03,500`, the words) and returns one layer per cue: it appears and disappears over 0.12 s, sits 90 px above the bottom edge, wraps at 1000 px (about 80 % of a 1280 px picture) and has a black outline so it reads on any picture. Two cues that overlap in time are drawn on top of each other: split a long line into two lines in the `.srt` rather than relying on wrapping. Check the result with `pixi-effects-check`; it reports text that is cut off by the edge.
|
|
767
|
+
|
|
768
|
+
```js
|
|
769
|
+
// @recipe subtitles
|
|
770
|
+
// SRT text -> one text layer per cue. Paste your .srt into SRT; the function does the rest.
|
|
771
|
+
const SRT = `1
|
|
772
|
+
00:00:00,500 --> 00:00:02,800
|
|
773
|
+
Hello, and welcome.
|
|
774
|
+
|
|
775
|
+
2
|
|
776
|
+
00:00:03,000 --> 00:00:05,500
|
|
777
|
+
Subtitles are just text layers
|
|
778
|
+
that appear and disappear.`;
|
|
779
|
+
|
|
780
|
+
function srt(text, { bottom = 90, size = 44, color = '#ffffff', width = 1000 } = {}) {
|
|
781
|
+
const sec = (h, m, s, ms) => +h * 3600 + +m * 60 + +s + +ms / 1000;
|
|
782
|
+
return text.replace(/\r/g, '').trim().split(/\n\s*\n/).flatMap((block) => {
|
|
783
|
+
const lines = block.split('\n');
|
|
784
|
+
const i = lines.findIndex((l) => l.includes('-->'));
|
|
785
|
+
const t = i < 0 ? null : lines[i].match(/(\d+):(\d+):(\d+)[,.](\d+)\s*-->\s*(\d+):(\d+):(\d+)[,.](\d+)/);
|
|
786
|
+
if (!t) return [];
|
|
787
|
+
const at = sec(t[1], t[2], t[3], t[4]), end = sec(t[5], t[6], t[7], t[8]);
|
|
788
|
+
return [{
|
|
789
|
+
type: 'text', name: `sub ${at.toFixed(2)}`, text: lines.slice(i + 1).join('\n'), at, duration: end - at,
|
|
790
|
+
style: { fontFamily: 'Arial, sans-serif', fontSize: size, fontWeight: 'bold', fill: color, align: 'center', wordWrap: true, wordWrapWidth: width, stroke: { color: '#000000', width: 6 } },
|
|
791
|
+
initial: { x: 'GW/2', y: `GH - ${bottom}`, anchorX: 0.5, anchorY: 1 },
|
|
792
|
+
keyframes: [{ at: 0, from: { alpha: 0 }, to: { alpha: 1 }, duration: 0.12 }, { at: -0.12, to: { alpha: 0 }, duration: 0.12 }],
|
|
793
|
+
}];
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
return [
|
|
798
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#1d2433' } },
|
|
799
|
+
...srt(SRT),
|
|
800
|
+
];
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
## A batch of videos from a table (one video per row)
|
|
804
|
+
|
|
805
|
+
A batch of videos is a loop outside the video. The page reads what changes from its address (`new URLSearchParams(location.search)`), and `pixi-effects-render --query "name=Aiko&score=92"` opens it with those values. Names with spaces or non-Latin letters must be URL-encoded (`jq -sRr @uri` as below, or `encodeURIComponent` in Node). Each video starts its own browser, so N videos cost N start-ups: try one with `--draft` (half size, fast), then render the real ones. To make the pieces of one row differ in more than text, use the same values to pick colours or an image name.
|
|
806
|
+
|
|
807
|
+
```js
|
|
808
|
+
// @docs-only batch-page
|
|
809
|
+
// batch.html: the same video, with the words taken from the address (?name=Aiko&score=92)
|
|
810
|
+
const q = new URLSearchParams(location.search);
|
|
811
|
+
const NAME = q.get('name') ?? 'World';
|
|
812
|
+
const SCORE = q.get('score') ?? '0';
|
|
813
|
+
// ...then use NAME and SCORE in the text layers of your composition:
|
|
814
|
+
const sequences = [
|
|
815
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#1d2433' } },
|
|
816
|
+
{ type: 'text', text: `Well done, ${NAME}!`, at: 0.3, duration: 3.7, style: { fontSize: 96, fontWeight: 'bold', fill: '#ffffff' },
|
|
817
|
+
initial: { x: 'GW/2', y: 300, anchorX: 0.5, anchorY: 0.5 }, keyframes: [{ at: 0, from: { alpha: 0, y: 340 }, to: { alpha: 1, y: 300 }, duration: 0.6 }] },
|
|
818
|
+
{ type: 'text', text: `${SCORE} points`, at: 0.9, duration: 3.1, style: { fontSize: 64, fill: '#ffd166' },
|
|
819
|
+
initial: { x: 'GW/2', y: 420, anchorX: 0.5, anchorY: 0.5 }, keyframes: [{ at: 0, from: { alpha: 0 }, to: { alpha: 1 }, duration: 0.6 }] },
|
|
820
|
+
];
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
```sh
|
|
824
|
+
# batch.sh: one video per row of table.csv (name,score). --draft first, to look; remove it for the real files.
|
|
825
|
+
mkdir -p out
|
|
826
|
+
while IFS=, read -r name score; do
|
|
827
|
+
npx pixi-effects-render batch.html --query "name=$(printf %s "$name" | jq -sRr @uri)&score=$score" -o "out/$name.mp4" --quiet
|
|
828
|
+
done < table.csv
|
|
829
|
+
```
|
package/ai/template.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>pixi-effects video</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/loader.css" /> <!-- the loader: HTML + CSS only, so it is on screen at once and keeps moving while the page builds the video -->
|
|
8
8
|
<style>
|
|
9
9
|
body { background: #0d1220; margin: 0; padding: 16px; }
|
|
10
10
|
.stage { position: relative; width: min(960px, 100%); } /* the player and the loader both sit inside this box */
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
40
40
|
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
41
41
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
42
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
44
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
45
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
42
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/index.js",
|
|
43
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/Controller.js",
|
|
44
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/Presenter.js",
|
|
45
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.18.0/dist/three.js",
|
|
46
46
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
47
47
|
"three": "https://esm.sh/three@0.178.0"
|
|
48
48
|
}
|