pixi-effects 0.28.0 → 0.28.2
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 +22 -1
- package/README.md +6 -6
- package/ai/chat-template.html +3 -3
- package/ai/tools/view.mjs +102 -14
- package/dist/Composition-H7UNR5F5.js +7 -0
- package/dist/{Composition-HFEVIYHN.js.map → Composition-H7UNR5F5.js.map} +1 -1
- package/dist/Composition-K6B3APM5.cjs +16 -0
- package/dist/{Composition-NI74UJT3.cjs.map → Composition-K6B3APM5.cjs.map} +1 -1
- package/dist/{chunk-WEJAQAFX.js → chunk-5MZWXVTT.js} +3 -3
- package/dist/{chunk-WEJAQAFX.js.map → chunk-5MZWXVTT.js.map} +1 -1
- package/dist/{chunk-AK54FIWM.js → chunk-7VN2KI3U.js} +6 -3
- package/dist/chunk-7VN2KI3U.js.map +1 -0
- package/dist/{chunk-SBOVUZZY.cjs → chunk-KBH33IQX.cjs} +61 -61
- package/dist/{chunk-SBOVUZZY.cjs.map → chunk-KBH33IQX.cjs.map} +1 -1
- package/dist/{chunk-MZCDBRTY.cjs → chunk-MJW7KILM.cjs} +6 -3
- package/dist/chunk-MJW7KILM.cjs.map +1 -0
- package/dist/index.cjs +35 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -5
- 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/llms-full.txt +180 -2
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +12 -2
- package/skills/pixi-effects/reference/direction.md +136 -0
- package/skills/pixi-effects/reference/pitfalls.md +3 -0
- package/skills/pixi-effects/reference/recipes.md +25 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-HFEVIYHN.js +0 -7
- package/dist/Composition-NI74UJT3.cjs +0 -16
- package/dist/chunk-AK54FIWM.js.map +0 -1
- package/dist/chunk-MZCDBRTY.cjs.map +0 -1
package/dist/loader.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
|
|
3
3
|
*
|
|
4
|
-
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
4
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/loader.css">
|
|
5
5
|
* <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
|
|
6
6
|
* <canvas id="stage" width="1280" height="720"></canvas>
|
|
7
7
|
* <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
|
package/dist/three.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkMJW7KILM_cjs = require('./chunk-MJW7KILM.cjs');
|
|
4
4
|
require('./chunk-ZMAD3KJU.cjs');
|
|
5
5
|
require('./chunk-TWZNOUM7.cjs');
|
|
6
6
|
var pixi_js = require('pixi.js');
|
|
7
7
|
var three$1 = require('three');
|
|
8
8
|
|
|
9
|
-
var ThreeSequence = class extends
|
|
9
|
+
var ThreeSequence = class extends chunkMJW7KILM_cjs.Sequence {
|
|
10
10
|
_renderer = null;
|
|
11
11
|
_scene = null;
|
|
12
12
|
_camera = null;
|
|
@@ -25,7 +25,7 @@ var ThreeSequence = class extends chunkMZCDBRTY_cjs.Sequence {
|
|
|
25
25
|
if (this.duration === void 0) {
|
|
26
26
|
this.duration = this.parent?.duration ?? this.root.duration;
|
|
27
27
|
}
|
|
28
|
-
const scope =
|
|
28
|
+
const scope = chunkMJW7KILM_cjs.buildScope(this, this.parent, this.root);
|
|
29
29
|
const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
|
|
30
30
|
const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
|
|
31
31
|
const resolution = spec.resolution ?? 1;
|
|
@@ -145,12 +145,12 @@ var ThreeSequence = class extends chunkMZCDBRTY_cjs.Sequence {
|
|
|
145
145
|
function resolveDim(v, scope) {
|
|
146
146
|
if (v === void 0) return void 0;
|
|
147
147
|
if (typeof v === "number") return v;
|
|
148
|
-
return
|
|
148
|
+
return chunkMJW7KILM_cjs.evaluateExpr(v, scope);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// src/three/index.ts
|
|
152
152
|
function registerThree() {
|
|
153
|
-
|
|
153
|
+
chunkMJW7KILM_cjs.registerSequenceType("three", ThreeSequence);
|
|
154
154
|
}
|
|
155
155
|
function three(spec) {
|
|
156
156
|
return spec;
|
package/dist/three.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-
|
|
1
|
+
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-7VN2KI3U.js';
|
|
2
2
|
import './chunk-45QKLD6M.js';
|
|
3
3
|
import './chunk-BXOCEZ4U.js';
|
|
4
4
|
import { Texture, Sprite } from 'pixi.js';
|
package/llms-full.txt
CHANGED
|
@@ -6,7 +6,7 @@ Concatenation of the skill, cheatsheet, pitfalls, recipes and the DSL / API refe
|
|
|
6
6
|
|
|
7
7
|
<!-- ===== skills/pixi-effects/SKILL.md ===== -->
|
|
8
8
|
|
|
9
|
-
> Written for pixi-effects 0.28.
|
|
9
|
+
> Written for pixi-effects 0.28.2. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
10
10
|
|
|
11
11
|
# pixi-effects — write a video as data
|
|
12
12
|
|
|
@@ -17,7 +17,7 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
17
17
|
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.
|
|
18
18
|
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`.
|
|
19
19
|
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.
|
|
20
|
-
4. **Show the human
|
|
20
|
+
4. **Show the human, EARLY: the first address goes out within two minutes.** Do it right after step 1, BEFORE you write the brief or collect assets: the copied template is already a page. Run `npx pixi-effects view my-video.html` (`--no-open` only prints the address; in the background, it keeps running), tell the human the address in your first message, and keep editing the same file: it opens their browser on the page with a scrubbable, zoomable timeline under it (click / drag to seek, + / − or Ctrl/⌘ + wheel to zoom) and runs until Ctrl-C. When the folder changes the viewer shows "Updated hh:mm:ss — reload (R)" and nothing else: the picture they are looking at never switches by itself, a reload returns to the frame they were on, and the switch "auto" (off by default) reloads only while the movie is paused. Do not restart it after every edit; they see the work grow instead of waiting for the end. **Deliver the file:** `npx pixi-effects render my-video.html -o my-video.mp4` 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:** `npx pixi-effects check my-video.html` (Node ≥ 22 and Chrome installed). **If `npx` says command not found, or prints nothing** (your npm may hold back a release that is a few days old, or an old release is cached): `npm i pixi-effects`, then `node node_modules/pixi-effects/ai/tools/check.mjs my-video.html` (the same for `render.mjs` and `view.mjs`). 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. A key a layer does not have (a typo in a layer key, a keyframe property or a text style key; `anchorX` outside `initial`; `pitch` outside `sfx`) is a warning that names the key and what you probably meant: treat it like any other warning. 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.
|
|
21
21
|
**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.
|
|
22
22
|
5. **Look at it** — you cannot judge motion from code. The library has the tools built in:
|
|
23
23
|
- `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.
|
|
@@ -29,6 +29,15 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
29
29
|
From a browser tool the result is a `data:` URL string: `agent-browser eval "movie.contactSheet({ count: 6, as: 'dataURL' })" | python3 scripts/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.
|
|
30
30
|
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.
|
|
31
31
|
|
|
32
|
+
## For a film (more than a title card): direct it first
|
|
33
|
+
|
|
34
|
+
**Read `reference/direction.md` before writing a composition for a product film, a company introduction, a launch or a data story.** It is renderer-neutral: write `brief.md` (the one sentence to remember, the REAL assets with paths, what must never be invented; if a needed asset is missing, stop and ask), `style-guide.md` (palette and type taken from the real source, a motion rule per class of object) and `shotlist.md` (a row per beat with entry and exit state) next to the page; then build, then review the rendered frames cold. What is specific to this library:
|
|
35
|
+
|
|
36
|
+
- **Time and sound:** pick a tempo whose bar is a round number of seconds (96 bpm: 2.5 s; 120 bpm: 2 s), put every cut on a bar line and every sfx on its beat. `npx pixi-effects check` prints a `music` read-back (bars per track, what starts on which beat, the drum span): verify the sound with it.
|
|
37
|
+
- **Review:** `check --at 3.5,title@end,50%` writes the contact sheet and single frames; look at mid-transition frames of the exported file too. Frames are seekable and deterministic here: render the same range twice and compare the frames if you doubt it.
|
|
38
|
+
- **Formats:** make the formats the human asked for, one if they did not say. When a vertical cut is wanted it is its own composition: one source, a layout function, `?f=9x16` (recipe `two-formats`); `check` and `render` take `--query f=9x16`.
|
|
39
|
+
- Fit a picture into a rectangle with the `photo()` helper of that recipe (`scale: 'cover'` fits the PARENT only); designed pictures (posters, flyers, screenshots) are shown whole, never cropped.
|
|
40
|
+
|
|
32
41
|
## The rules that cause most failures
|
|
33
42
|
|
|
34
43
|
- **Keyframe `at` starts at 0 when the layer appears** (negative = back from the layer's end). A layer's own `at` and a transition's `at` are composition time.
|
|
@@ -56,6 +65,7 @@ Centred text must be positioned by `anchorX/anchorY: 0.5`; estimate text width a
|
|
|
56
65
|
- `reference/cheatsheet.md` — every layer type, prop, default and rule on one page.
|
|
57
66
|
- `reference/recipes.md` — tested building blocks: slam type, lower-third, marquee, count-up, bar chart, 2.5D title, camera orbit, slideshow with transitions + music, looping, particles, gradients/vignette, three.js metal.
|
|
58
67
|
- `reference/pitfalls.md` — the full list of real mistakes, with status.
|
|
68
|
+
- `reference/direction.md` — how to direct a film (any renderer): brief, style guide, shot list, assets rule, sound on the beat grid, review protocol, formats, gates.
|
|
59
69
|
- `npx pixi-effects check` — the one-command review (see Workflow step 5).
|
|
60
70
|
- `template.html` — the starting file. (It loads the released library from a CDN, so it works from any folder.)
|
|
61
71
|
- `scripts/save-image.py` — save a `dataURL` result (contact sheet / snapshot) to a PNG.
|
|
@@ -380,6 +390,9 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
380
390
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
381
391
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
382
392
|
100. **Read the score back before you trust it.** `check` prints a `music` block: every track's length in bars (a track that ends early or has a short bar is named), the names that start in each bar with their beat (`Am(2)` starts on beat 2; no mark = the downbeat; the first four names of a bar are shown, `musicEvents()` lists everything), and the drum span. Count bars by the read-back, not by hand: a bar of 3 beats in a score with no `meter` is shown as bars of 4, so set `meter: 3` for a waltz. Drums repeat by themselves and `from` / `to` are beats; `swing` only moves notes that start off the beat, so a comp on beats 2 and 4 stays straight.
|
|
393
|
+
101. **A film built from one adjective looks like every other one.** Read `reference/direction.md` and write `brief.md` / `style-guide.md` / `shotlist.md` first: real assets with paths, the one sentence to remember, a row per beat with entry and exit state. If a real asset is missing, ask: a drawn fake (a logo, a dashboard, a figure) is the failure that matters. Judge the contact sheet cold: three defects, each with a timestamp, evidence and a local fix.
|
|
394
|
+
102. **A vertical cut is a second composition, not a crop, and only when someone asked for it.** One source, a layout function, `?f=9x16`; `check` and `render` take `--query f=9x16` (recipe `two-formats`). Look at a contact sheet of each format: type that fits wide is tiny tall. Ask which formats are wanted; make one if the answer is open. In a 1080p film keep every line of text at 34 px or more; if you cannot read it in a sheet tile, a phone cannot.
|
|
395
|
+
103. **Load a web font BEFORE you measure with it.** `measureText` / `splitText` / `animateText` measure once, when the composition is built. Measured in Chrome: "Respect the Local, Loop the Value." at 80 px Lato 900 is 1487 px wide before the Google font has arrived (the fallback) and 1231 px after; Japanese text in a similar fallback was within 3 px. Every box, line break and centred position built from the wrong width stays wrong. `await document.fonts.load('900 80px "Lato"', theTextYouUse)` first (the text argument loads exactly the glyphs you need), then build the composition.
|
|
383
396
|
|
|
384
397
|
|
|
385
398
|
<!-- ===== skills/pixi-effects/reference/recipes.md ===== -->
|
|
@@ -1489,6 +1502,31 @@ return [
|
|
|
1489
1502
|
];
|
|
1490
1503
|
```
|
|
1491
1504
|
|
|
1505
|
+
## One source, two formats (16:9 and 9:16), and a photo inside a region
|
|
1506
|
+
|
|
1507
|
+
Only when a vertical cut is wanted (ask first). It is not a crop of the wide one: write each scene as a function of a layout, switch with a query (`?f=9x16`), and render each format on its own: `npx pixi-effects check film.html --query f=9x16`, `npx pixi-effects render film.html --query f=9x16 -o film-9x16.mp4`. `photo()` fits a picture into any rectangle like CSS `cover` (scale from the image's own size, a mask for the rectangle); `scale: 'cover'` only fits the PARENT.
|
|
1508
|
+
|
|
1509
|
+
```js
|
|
1510
|
+
// @docs-only two-formats
|
|
1511
|
+
const PORTRAIT = new URLSearchParams(location.search).get('f') === '9x16';
|
|
1512
|
+
const W = PORTRAIT ? 1080 : 1920, H = PORTRAIT ? 1920 : 1080, MX = PORTRAIT ? 76 : 150; // margins: nothing important in the outer 5 %
|
|
1513
|
+
const IMG = { hero: [1920, 1280] }; // each photo's size (assets: [{ name: 'hero', src: '...' }])
|
|
1514
|
+
const photo = (key, x, y, w, h, duration) => {
|
|
1515
|
+
const cover = Math.max(w / IMG[key][0], h / IMG[key][1]);
|
|
1516
|
+
return { type: 'image', asset: key, duration,
|
|
1517
|
+
mask: { type: 'shape', shape: 'rect', width: w, height: h, initial: { x: x + w / 2, y: y + h / 2, fillColor: '#fff' } }, // the mask lives in the parent's coordinates
|
|
1518
|
+
initial: { x: x + w / 2, y: y + h / 2, anchorX: 0.5, anchorY: 0.5, scale: cover }, // image anchors go in `initial`
|
|
1519
|
+
keyframes: [{ at: 0, to: { scale: cover * 1.1 }, duration, ease: 'sine.inOut' }] }; // a slow push
|
|
1520
|
+
};
|
|
1521
|
+
const scene = { type: 'composition', name: 'hook', at: 0, duration: 5.5, width: W, height: H, sequences: [
|
|
1522
|
+
photo('hero', 0, 0, W, H, 5.5),
|
|
1523
|
+
{ type: 'text', text: 'Headline', initial: { x: MX, y: PORTRAIT ? 1150 : 620 }, style: { fontSize: PORTRAIT ? 100 : 112, fill: '#fff' } }, // wide: lower left; tall: lower third
|
|
1524
|
+
] };
|
|
1525
|
+
// movie.init({ width: W, height: H, composition: { sequences: [scene] } })
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
---
|
|
1529
|
+
|
|
1492
1530
|
## A batch of videos from a table (one video per row)
|
|
1493
1531
|
|
|
1494
1532
|
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.
|
|
@@ -1518,6 +1556,146 @@ done < table.csv
|
|
|
1518
1556
|
```
|
|
1519
1557
|
|
|
1520
1558
|
|
|
1559
|
+
<!-- ===== skills/pixi-effects/reference/direction.md ===== -->
|
|
1560
|
+
|
|
1561
|
+
# Directing a film (for any AI that makes video with code)
|
|
1562
|
+
|
|
1563
|
+
Read this before you write a composition for anything longer than a title card: a product film, a company introduction, a launch, a data story. It is about the decisions around the renderer, so it names no tool; `SKILL.md` has the pixi-effects commands.
|
|
1564
|
+
|
|
1565
|
+
A one-line prompt makes the model fill every missing decision with its safest default: large text in the middle, a gradient behind it, everything fading in, the logo last. The fix is not a cleverer prompt. It is to remove the missing decisions before anything is drawn, and to judge what was drawn with evidence.
|
|
1566
|
+
|
|
1567
|
+
## The five layers (do not collapse them into one step)
|
|
1568
|
+
|
|
1569
|
+
| Layer | The question | Where the answer lives |
|
|
1570
|
+
|---|---|---|
|
|
1571
|
+
| Director | What is the film trying to say, to whom? | `brief.md` |
|
|
1572
|
+
| Reference | What should it look and feel like? | `style-guide.md` |
|
|
1573
|
+
| Timeline | What changes at each beat, and why? | `shotlist.md` |
|
|
1574
|
+
| Renderer | How is every frame produced? | `src/` (seekable code) |
|
|
1575
|
+
| Critic | What failed, exactly where, and why? | `reviews/` |
|
|
1576
|
+
|
|
1577
|
+
## Keep the decisions in files, not in the chat
|
|
1578
|
+
|
|
1579
|
+
A film whose only record is a conversation is a lucky session. Put the decisions where the next run can read them:
|
|
1580
|
+
|
|
1581
|
+
```
|
|
1582
|
+
film/
|
|
1583
|
+
brief.md the one-sentence goal, the audience, the product facts, the real assets
|
|
1584
|
+
style-guide.md palette, type, motion rules, banned defaults
|
|
1585
|
+
shotlist.md beats, states, timestamps, purpose
|
|
1586
|
+
assets/ approved logos, photos, screens, footage, sound
|
|
1587
|
+
src/ the composition and its code
|
|
1588
|
+
reviews/ contact sheets and defect notes
|
|
1589
|
+
out/ exports
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
The next film swaps the brief and the assets and keeps the rest. A run that stops can resume from the last file it finished and say which one is missing; "the last command exited 0" is never the definition of done.
|
|
1593
|
+
|
|
1594
|
+
## brief.md: facts about the product, choices about the film
|
|
1595
|
+
|
|
1596
|
+
Separate what is true (it comes from the product) from what is chosen (it comes from you). The model may invent a camera move or a stronger opening. It may not invent a dashboard, a figure, a logo or a client.
|
|
1597
|
+
|
|
1598
|
+
```
|
|
1599
|
+
FILM
|
|
1600
|
+
One sentence: what the viewer should remember
|
|
1601
|
+
Audience: who it is for
|
|
1602
|
+
Duration: seconds
|
|
1603
|
+
Formats: ask the human; one format unless they name more (see "Formats" below)
|
|
1604
|
+
|
|
1605
|
+
ASSETS (paths; everything on screen comes from here)
|
|
1606
|
+
Logo · photos · screens · copy and numbers · brand type and colours · sound reference
|
|
1607
|
+
|
|
1608
|
+
VISUAL RULES
|
|
1609
|
+
Keep: traits taken from the reference
|
|
1610
|
+
Avoid: the clichés you will not use
|
|
1611
|
+
Never: invent a product screen, a metric, a claim or a logo
|
|
1612
|
+
|
|
1613
|
+
DELIVERABLES
|
|
1614
|
+
Final video, poster frame, contact sheets, source, an asset list, a note on what a human still has to check
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
**Collect the assets first, list them, and read the list before building.** If the brief needs an asset that is missing, stop and ask. A convincing fake (a drawn logo, an invented dashboard, a guessed number) is the one failure that cannot be repaired afterwards. Words and numbers on screen are the source's own; take colours and type from the source itself (a site's CSS, a brand sheet), never from an adjective such as "premium".
|
|
1618
|
+
|
|
1619
|
+
## style-guide.md: a visual grammar written down
|
|
1620
|
+
|
|
1621
|
+
Without one the model supplies its own, which is why unrelated one-prompt films look related. Give it a frame, a film or the real site, and make it write what it saw:
|
|
1622
|
+
|
|
1623
|
+
```
|
|
1624
|
+
REFERENCE ANALYSIS
|
|
1625
|
+
Palette: the actual colours (hex), not an adjective
|
|
1626
|
+
Typography: family, weight, scale
|
|
1627
|
+
Composition: where the eye goes first
|
|
1628
|
+
Pacing: average time between meaningful changes
|
|
1629
|
+
Motion: snap, glide, overshoot, cut or hold
|
|
1630
|
+
Texture: grain, paper, glass, flat
|
|
1631
|
+
DO NOT COPY: the reference's subjects, logos, copy or product imagery
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
The result is a style guide, not a collage of borrowed shots. Add a **motion rule per class of object**, because "motion with weight" is the difference between decoration and direction: small controls snap, large panels settle, the camera is almost invisible, headlines enter strongly and then hold long enough to read, one playful object is allowed to overshoot. If everything overshoots, nothing feels precise. After each move ask: does the viewer know where to look?
|
|
1635
|
+
|
|
1636
|
+
## shotlist.md: states and beats, not effects
|
|
1637
|
+
|
|
1638
|
+
"Zoom in, then morph, then add particles" lists activity. A plan says what the viewer knows at each moment:
|
|
1639
|
+
|
|
1640
|
+
```
|
|
1641
|
+
00-02 HOOK one image that earns attention
|
|
1642
|
+
02-05 PROBLEM the friction, shown, not described in a paragraph
|
|
1643
|
+
05-09 REVEAL the product enters through a real interaction
|
|
1644
|
+
09-13 PROOF one screen or number that demonstrates the claim
|
|
1645
|
+
13-16 CLOSE a clean final state and a readable call to action
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
Each row has an entry state, an exit state and a reason to exist. For a UI film write the state list first (SEARCH → RESULT → DETAIL → ACTION → CONFIRMATION) so one component can change size, colour and content while staying recognisably the same object. The shot list is a contract: a shot nobody can justify is cut.
|
|
1649
|
+
|
|
1650
|
+
## Seekable and deterministic, or it cannot be reviewed
|
|
1651
|
+
|
|
1652
|
+
Frame 240 must render directly, without replaying frames 0 to 239: no hidden clock, no unseeded randomness, nothing that only works after eight seconds of running. Think of a frame as a query: `frame = render(time, shotlist, assets, style, seed)`. Then a revision is local (change one time value, one curve, one asset) and a reviewed frame stays reviewed. Two renders of the same moment must give the same pixels; check it.
|
|
1653
|
+
|
|
1654
|
+
## Sound is on the same timeline
|
|
1655
|
+
|
|
1656
|
+
Many AI films look finished with the sound off; then the click lands after the button, the cut misses the beat, the logo arrives before the music resolves. Write a beat grid and drive picture and sound from it: choose a tempo whose bar is a round number of seconds, put every cut on a bar line and every cue on the beat it belongs to. With a licensed or original track, measure its beats and cut against them. Test: close your eyes and listen; if the film has no shape without the picture, the sound is not finished.
|
|
1657
|
+
|
|
1658
|
+
## Review the rendered frames, not your intent
|
|
1659
|
+
|
|
1660
|
+
Do not start by watching a full export ten times, and do not describe what you meant to make. Ask the renderer for a contact sheet: one frame at every beat, a short strip around the fastest transitions, and a frame from the middle of each cut taken from the exported file. Look cold, then write the **three largest defects** into `reviews/notes.md`, each with a **timestamp**, the **evidence** (what you see) and a **local fix**; patch only those, render those frames again, and note the result. A defect you fixed but did not write down is lost for the next film. Two things are easy to miss on a sheet, so do them on purpose: **measure the smallest text** on screen (its pixel size scaled to a 1080p frame, not the look of it), and look at **both ends of every cut and at every state a card passes through** (an empty frame that holds content a beat later is a defect). Ask for a second pair of eyes on the sheet whenever one is available: the author reads their own intent into it.
|
|
1661
|
+
|
|
1662
|
+
```
|
|
1663
|
+
[ ] text readable at phone size (about 34 px or more on a 1080p frame)
|
|
1664
|
+
[ ] nothing in the outer 5 % of the frame
|
|
1665
|
+
[ ] real assets only, none redrawn or invented
|
|
1666
|
+
[ ] one type and colour system across scenes
|
|
1667
|
+
[ ] no awkward half-transition frame
|
|
1668
|
+
[ ] cuts and sound cues land together
|
|
1669
|
+
[ ] the first two seconds are a real hook
|
|
1670
|
+
[ ] the last frame works as a poster
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
"The agent made a video" stops when the file exists. "The agent directed a video" stops when the evidence says the film works.
|
|
1674
|
+
|
|
1675
|
+
## Formats
|
|
1676
|
+
|
|
1677
|
+
A 16:9 film and a 9:16 cut are different compositions: the vertical one needs larger type, fewer things at once, another layout, sometimes a tighter story. Never crop the middle of the wide one. **Do not make a format nobody asked for:** ask which formats are wanted, make one if the answer is "whatever", and offer the others. When more than one is wanted, keep the same assets, timeline and states, define the layout per format, and look at a contact sheet of each: an export is correct because the composition survived the format, not because the encoder finished.
|
|
1678
|
+
|
|
1679
|
+
## Gates, and what to do when no human is there
|
|
1680
|
+
|
|
1681
|
+
Long runs need places to stop. With a human present:
|
|
1682
|
+
|
|
1683
|
+
1. approve the references and the asset list
|
|
1684
|
+
2. approve the style guide and the shot list
|
|
1685
|
+
3. look at representative stills
|
|
1686
|
+
4. look at a rough cut and its contact sheet
|
|
1687
|
+
5. repair the three highest-impact defects
|
|
1688
|
+
6. check sound, formats and deliverables
|
|
1689
|
+
|
|
1690
|
+
Show the work early: serve the page the moment it exists (a copied template is enough, before the brief is written) and give the human the address in your first message; every later save then shows up as a notice they can act on when they choose. A first draft of the opening scene, served where the human can open it, is worth more than a perfect film at the end; a wrong direction is cheap to fix in the first minutes. Between gates the agent may work for hours; it never makes an irreversible brand decision (logo, claim, number) for the human, and a missing real asset is a blocker, not an invitation to invent one. If the reference is unclear, the right output is a question or one small test frame, not nine hundred polished wrong ones. With nobody to ask, record each decision you took as a "ruling" in the review notes, with what it costs if wrong, and finish with a short note of what still needs a human.
|
|
1691
|
+
|
|
1692
|
+
## The loop
|
|
1693
|
+
|
|
1694
|
+
PROMPT → CLIP is a demo. BRIEF → SYSTEM → FILM → REVIEW → BETTER SYSTEM is a studio: every stage leaves evidence the next one can use, and the tenth film can be better than the first.
|
|
1695
|
+
|
|
1696
|
+
*The shape of this guide follows a published breakdown of building a code-rendered video studio around an AI model ("Motion Engineering", @0xwhrrari, September 2026); the wording and the checklist are rewritten for this library's users.*
|
|
1697
|
+
|
|
1698
|
+
|
|
1521
1699
|
<!-- ===== docs/dsl.md ===== -->
|
|
1522
1700
|
|
|
1523
1701
|
# DSL Reference
|
package/llms.txt
CHANGED
|
@@ -18,6 +18,7 @@ Rules that cause most failures (details in the skill and pitfalls):
|
|
|
18
18
|
- [Cheatsheet](https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/reference/cheatsheet.md): every layer type, prop, default and convention on one page
|
|
19
19
|
- [Recipes](https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/reference/recipes.md): tested building blocks (kinetic type, lower-third, count-up, bar chart, 2.5D title, camera orbit, slideshow with transitions, sound effects without files, music written as text, three.js, a presentation deck, visuals that follow music)
|
|
20
20
|
- [Pitfalls](https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/reference/pitfalls.md): real mistakes made by AI authors, with fixes
|
|
21
|
+
- [Directing a film](https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/reference/direction.md): for a product film or company intro, before building: brief, style guide, shot list, assets rule, review protocol (any renderer)
|
|
21
22
|
- [Starter template](https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/template.html): copy this file to begin
|
|
22
23
|
|
|
23
24
|
## Reference
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ name: pixi-effects
|
|
|
3
3
|
description: Use when asked to make, animate, preview or export a video, motion graphics, an animated title, a lower third, kinetic type, a promo, a slideshow with transitions, an animated chart or data story, a social clip, a 2.5D / depth scene, or music and sound for a video, written as code in JavaScript or HTML. pixi-effects is a JS library where a video is a plain-object composition (text, shape, image, video, audio and file-free sfx / music, camera, light and shader layers with keyframes) that plays in the browser, is checked by a tool the agent can run, and exports MP4 / WebM / MOV with no server. Not for a project that already uses another video tool (Remotion, Motion Canvas, ffmpeg); follow that tool instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> Written for pixi-effects 0.28.
|
|
6
|
+
> Written for pixi-effects 0.28.2. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
7
7
|
|
|
8
8
|
# pixi-effects — write a video as data
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
14
14
|
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.
|
|
15
15
|
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`.
|
|
16
16
|
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.
|
|
17
|
-
4. **Show the human
|
|
17
|
+
4. **Show the human, EARLY: the first address goes out within two minutes.** Do it right after step 1, BEFORE you write the brief or collect assets: the copied template is already a page. Run `npx pixi-effects view my-video.html` (`--no-open` only prints the address; in the background, it keeps running), tell the human the address in your first message, and keep editing the same file: it opens their browser on the page with a scrubbable, zoomable timeline under it (click / drag to seek, + / − or Ctrl/⌘ + wheel to zoom) and runs until Ctrl-C. When the folder changes the viewer shows "Updated hh:mm:ss — reload (R)" and nothing else: the picture they are looking at never switches by itself, a reload returns to the frame they were on, and the switch "auto" (off by default) reloads only while the movie is paused. Do not restart it after every edit; they see the work grow instead of waiting for the end. **Deliver the file:** `npx pixi-effects render my-video.html -o my-video.mp4` 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:** `npx pixi-effects check my-video.html` (Node ≥ 22 and Chrome installed). **If `npx` says command not found, or prints nothing** (your npm may hold back a release that is a few days old, or an old release is cached): `npm i pixi-effects`, then `node node_modules/pixi-effects/ai/tools/check.mjs my-video.html` (the same for `render.mjs` and `view.mjs`). 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. A key a layer does not have (a typo in a layer key, a keyframe property or a text style key; `anchorX` outside `initial`; `pitch` outside `sfx`) is a warning that names the key and what you probably meant: treat it like any other warning. 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.
|
|
18
18
|
**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.
|
|
19
19
|
5. **Look at it** — you cannot judge motion from code. The library has the tools built in:
|
|
20
20
|
- `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.
|
|
@@ -26,6 +26,15 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
26
26
|
From a browser tool the result is a `data:` URL string: `agent-browser eval "movie.contactSheet({ count: 6, as: 'dataURL' })" | python3 scripts/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.
|
|
27
27
|
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.
|
|
28
28
|
|
|
29
|
+
## For a film (more than a title card): direct it first
|
|
30
|
+
|
|
31
|
+
**Read `reference/direction.md` before writing a composition for a product film, a company introduction, a launch or a data story.** It is renderer-neutral: write `brief.md` (the one sentence to remember, the REAL assets with paths, what must never be invented; if a needed asset is missing, stop and ask), `style-guide.md` (palette and type taken from the real source, a motion rule per class of object) and `shotlist.md` (a row per beat with entry and exit state) next to the page; then build, then review the rendered frames cold. What is specific to this library:
|
|
32
|
+
|
|
33
|
+
- **Time and sound:** pick a tempo whose bar is a round number of seconds (96 bpm: 2.5 s; 120 bpm: 2 s), put every cut on a bar line and every sfx on its beat. `npx pixi-effects check` prints a `music` read-back (bars per track, what starts on which beat, the drum span): verify the sound with it.
|
|
34
|
+
- **Review:** `check --at 3.5,title@end,50%` writes the contact sheet and single frames; look at mid-transition frames of the exported file too. Frames are seekable and deterministic here: render the same range twice and compare the frames if you doubt it.
|
|
35
|
+
- **Formats:** make the formats the human asked for, one if they did not say. When a vertical cut is wanted it is its own composition: one source, a layout function, `?f=9x16` (recipe `two-formats`); `check` and `render` take `--query f=9x16`.
|
|
36
|
+
- Fit a picture into a rectangle with the `photo()` helper of that recipe (`scale: 'cover'` fits the PARENT only); designed pictures (posters, flyers, screenshots) are shown whole, never cropped.
|
|
37
|
+
|
|
29
38
|
## The rules that cause most failures
|
|
30
39
|
|
|
31
40
|
- **Keyframe `at` starts at 0 when the layer appears** (negative = back from the layer's end). A layer's own `at` and a transition's `at` are composition time.
|
|
@@ -53,6 +62,7 @@ Centred text must be positioned by `anchorX/anchorY: 0.5`; estimate text width a
|
|
|
53
62
|
- `reference/cheatsheet.md` — every layer type, prop, default and rule on one page.
|
|
54
63
|
- `reference/recipes.md` — tested building blocks: slam type, lower-third, marquee, count-up, bar chart, 2.5D title, camera orbit, slideshow with transitions + music, looping, particles, gradients/vignette, three.js metal.
|
|
55
64
|
- `reference/pitfalls.md` — the full list of real mistakes, with status.
|
|
65
|
+
- `reference/direction.md` — how to direct a film (any renderer): brief, style guide, shot list, assets rule, sound on the beat grid, review protocol, formats, gates.
|
|
56
66
|
- `npx pixi-effects check` — the one-command review (see Workflow step 5).
|
|
57
67
|
- `template.html` — the starting file. (It loads the released library from a CDN, so it works from any folder.)
|
|
58
68
|
- `scripts/save-image.py` — save a `dataURL` result (contact sheet / snapshot) to a PNG.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Directing a film (for any AI that makes video with code)
|
|
2
|
+
|
|
3
|
+
Read this before you write a composition for anything longer than a title card: a product film, a company introduction, a launch, a data story. It is about the decisions around the renderer, so it names no tool; `SKILL.md` has the pixi-effects commands.
|
|
4
|
+
|
|
5
|
+
A one-line prompt makes the model fill every missing decision with its safest default: large text in the middle, a gradient behind it, everything fading in, the logo last. The fix is not a cleverer prompt. It is to remove the missing decisions before anything is drawn, and to judge what was drawn with evidence.
|
|
6
|
+
|
|
7
|
+
## The five layers (do not collapse them into one step)
|
|
8
|
+
|
|
9
|
+
| Layer | The question | Where the answer lives |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Director | What is the film trying to say, to whom? | `brief.md` |
|
|
12
|
+
| Reference | What should it look and feel like? | `style-guide.md` |
|
|
13
|
+
| Timeline | What changes at each beat, and why? | `shotlist.md` |
|
|
14
|
+
| Renderer | How is every frame produced? | `src/` (seekable code) |
|
|
15
|
+
| Critic | What failed, exactly where, and why? | `reviews/` |
|
|
16
|
+
|
|
17
|
+
## Keep the decisions in files, not in the chat
|
|
18
|
+
|
|
19
|
+
A film whose only record is a conversation is a lucky session. Put the decisions where the next run can read them:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
film/
|
|
23
|
+
brief.md the one-sentence goal, the audience, the product facts, the real assets
|
|
24
|
+
style-guide.md palette, type, motion rules, banned defaults
|
|
25
|
+
shotlist.md beats, states, timestamps, purpose
|
|
26
|
+
assets/ approved logos, photos, screens, footage, sound
|
|
27
|
+
src/ the composition and its code
|
|
28
|
+
reviews/ contact sheets and defect notes
|
|
29
|
+
out/ exports
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The next film swaps the brief and the assets and keeps the rest. A run that stops can resume from the last file it finished and say which one is missing; "the last command exited 0" is never the definition of done.
|
|
33
|
+
|
|
34
|
+
## brief.md: facts about the product, choices about the film
|
|
35
|
+
|
|
36
|
+
Separate what is true (it comes from the product) from what is chosen (it comes from you). The model may invent a camera move or a stronger opening. It may not invent a dashboard, a figure, a logo or a client.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
FILM
|
|
40
|
+
One sentence: what the viewer should remember
|
|
41
|
+
Audience: who it is for
|
|
42
|
+
Duration: seconds
|
|
43
|
+
Formats: ask the human; one format unless they name more (see "Formats" below)
|
|
44
|
+
|
|
45
|
+
ASSETS (paths; everything on screen comes from here)
|
|
46
|
+
Logo · photos · screens · copy and numbers · brand type and colours · sound reference
|
|
47
|
+
|
|
48
|
+
VISUAL RULES
|
|
49
|
+
Keep: traits taken from the reference
|
|
50
|
+
Avoid: the clichés you will not use
|
|
51
|
+
Never: invent a product screen, a metric, a claim or a logo
|
|
52
|
+
|
|
53
|
+
DELIVERABLES
|
|
54
|
+
Final video, poster frame, contact sheets, source, an asset list, a note on what a human still has to check
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Collect the assets first, list them, and read the list before building.** If the brief needs an asset that is missing, stop and ask. A convincing fake (a drawn logo, an invented dashboard, a guessed number) is the one failure that cannot be repaired afterwards. Words and numbers on screen are the source's own; take colours and type from the source itself (a site's CSS, a brand sheet), never from an adjective such as "premium".
|
|
58
|
+
|
|
59
|
+
## style-guide.md: a visual grammar written down
|
|
60
|
+
|
|
61
|
+
Without one the model supplies its own, which is why unrelated one-prompt films look related. Give it a frame, a film or the real site, and make it write what it saw:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
REFERENCE ANALYSIS
|
|
65
|
+
Palette: the actual colours (hex), not an adjective
|
|
66
|
+
Typography: family, weight, scale
|
|
67
|
+
Composition: where the eye goes first
|
|
68
|
+
Pacing: average time between meaningful changes
|
|
69
|
+
Motion: snap, glide, overshoot, cut or hold
|
|
70
|
+
Texture: grain, paper, glass, flat
|
|
71
|
+
DO NOT COPY: the reference's subjects, logos, copy or product imagery
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The result is a style guide, not a collage of borrowed shots. Add a **motion rule per class of object**, because "motion with weight" is the difference between decoration and direction: small controls snap, large panels settle, the camera is almost invisible, headlines enter strongly and then hold long enough to read, one playful object is allowed to overshoot. If everything overshoots, nothing feels precise. After each move ask: does the viewer know where to look?
|
|
75
|
+
|
|
76
|
+
## shotlist.md: states and beats, not effects
|
|
77
|
+
|
|
78
|
+
"Zoom in, then morph, then add particles" lists activity. A plan says what the viewer knows at each moment:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
00-02 HOOK one image that earns attention
|
|
82
|
+
02-05 PROBLEM the friction, shown, not described in a paragraph
|
|
83
|
+
05-09 REVEAL the product enters through a real interaction
|
|
84
|
+
09-13 PROOF one screen or number that demonstrates the claim
|
|
85
|
+
13-16 CLOSE a clean final state and a readable call to action
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each row has an entry state, an exit state and a reason to exist. For a UI film write the state list first (SEARCH → RESULT → DETAIL → ACTION → CONFIRMATION) so one component can change size, colour and content while staying recognisably the same object. The shot list is a contract: a shot nobody can justify is cut.
|
|
89
|
+
|
|
90
|
+
## Seekable and deterministic, or it cannot be reviewed
|
|
91
|
+
|
|
92
|
+
Frame 240 must render directly, without replaying frames 0 to 239: no hidden clock, no unseeded randomness, nothing that only works after eight seconds of running. Think of a frame as a query: `frame = render(time, shotlist, assets, style, seed)`. Then a revision is local (change one time value, one curve, one asset) and a reviewed frame stays reviewed. Two renders of the same moment must give the same pixels; check it.
|
|
93
|
+
|
|
94
|
+
## Sound is on the same timeline
|
|
95
|
+
|
|
96
|
+
Many AI films look finished with the sound off; then the click lands after the button, the cut misses the beat, the logo arrives before the music resolves. Write a beat grid and drive picture and sound from it: choose a tempo whose bar is a round number of seconds, put every cut on a bar line and every cue on the beat it belongs to. With a licensed or original track, measure its beats and cut against them. Test: close your eyes and listen; if the film has no shape without the picture, the sound is not finished.
|
|
97
|
+
|
|
98
|
+
## Review the rendered frames, not your intent
|
|
99
|
+
|
|
100
|
+
Do not start by watching a full export ten times, and do not describe what you meant to make. Ask the renderer for a contact sheet: one frame at every beat, a short strip around the fastest transitions, and a frame from the middle of each cut taken from the exported file. Look cold, then write the **three largest defects** into `reviews/notes.md`, each with a **timestamp**, the **evidence** (what you see) and a **local fix**; patch only those, render those frames again, and note the result. A defect you fixed but did not write down is lost for the next film. Two things are easy to miss on a sheet, so do them on purpose: **measure the smallest text** on screen (its pixel size scaled to a 1080p frame, not the look of it), and look at **both ends of every cut and at every state a card passes through** (an empty frame that holds content a beat later is a defect). Ask for a second pair of eyes on the sheet whenever one is available: the author reads their own intent into it.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
[ ] text readable at phone size (about 34 px or more on a 1080p frame)
|
|
104
|
+
[ ] nothing in the outer 5 % of the frame
|
|
105
|
+
[ ] real assets only, none redrawn or invented
|
|
106
|
+
[ ] one type and colour system across scenes
|
|
107
|
+
[ ] no awkward half-transition frame
|
|
108
|
+
[ ] cuts and sound cues land together
|
|
109
|
+
[ ] the first two seconds are a real hook
|
|
110
|
+
[ ] the last frame works as a poster
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
"The agent made a video" stops when the file exists. "The agent directed a video" stops when the evidence says the film works.
|
|
114
|
+
|
|
115
|
+
## Formats
|
|
116
|
+
|
|
117
|
+
A 16:9 film and a 9:16 cut are different compositions: the vertical one needs larger type, fewer things at once, another layout, sometimes a tighter story. Never crop the middle of the wide one. **Do not make a format nobody asked for:** ask which formats are wanted, make one if the answer is "whatever", and offer the others. When more than one is wanted, keep the same assets, timeline and states, define the layout per format, and look at a contact sheet of each: an export is correct because the composition survived the format, not because the encoder finished.
|
|
118
|
+
|
|
119
|
+
## Gates, and what to do when no human is there
|
|
120
|
+
|
|
121
|
+
Long runs need places to stop. With a human present:
|
|
122
|
+
|
|
123
|
+
1. approve the references and the asset list
|
|
124
|
+
2. approve the style guide and the shot list
|
|
125
|
+
3. look at representative stills
|
|
126
|
+
4. look at a rough cut and its contact sheet
|
|
127
|
+
5. repair the three highest-impact defects
|
|
128
|
+
6. check sound, formats and deliverables
|
|
129
|
+
|
|
130
|
+
Show the work early: serve the page the moment it exists (a copied template is enough, before the brief is written) and give the human the address in your first message; every later save then shows up as a notice they can act on when they choose. A first draft of the opening scene, served where the human can open it, is worth more than a perfect film at the end; a wrong direction is cheap to fix in the first minutes. Between gates the agent may work for hours; it never makes an irreversible brand decision (logo, claim, number) for the human, and a missing real asset is a blocker, not an invitation to invent one. If the reference is unclear, the right output is a question or one small test frame, not nine hundred polished wrong ones. With nobody to ask, record each decision you took as a "ruling" in the review notes, with what it costs if wrong, and finish with a short note of what still needs a human.
|
|
131
|
+
|
|
132
|
+
## The loop
|
|
133
|
+
|
|
134
|
+
PROMPT → CLIP is a demo. BRIEF → SYSTEM → FILM → REVIEW → BETTER SYSTEM is a studio: every stage leaves evidence the next one can use, and the tenth film can be better than the first.
|
|
135
|
+
|
|
136
|
+
*The shape of this guide follows a published breakdown of building a code-rendered video studio around an AI model ("Motion Engineering", @0xwhrrari, September 2026); the wording and the checklist are rewritten for this library's users.*
|
|
@@ -128,3 +128,6 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
128
128
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
129
129
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
130
130
|
100. **Read the score back before you trust it.** `check` prints a `music` block: every track's length in bars (a track that ends early or has a short bar is named), the names that start in each bar with their beat (`Am(2)` starts on beat 2; no mark = the downbeat; the first four names of a bar are shown, `musicEvents()` lists everything), and the drum span. Count bars by the read-back, not by hand: a bar of 3 beats in a score with no `meter` is shown as bars of 4, so set `meter: 3` for a waltz. Drums repeat by themselves and `from` / `to` are beats; `swing` only moves notes that start off the beat, so a comp on beats 2 and 4 stays straight.
|
|
131
|
+
101. **A film built from one adjective looks like every other one.** Read `reference/direction.md` and write `brief.md` / `style-guide.md` / `shotlist.md` first: real assets with paths, the one sentence to remember, a row per beat with entry and exit state. If a real asset is missing, ask: a drawn fake (a logo, a dashboard, a figure) is the failure that matters. Judge the contact sheet cold: three defects, each with a timestamp, evidence and a local fix.
|
|
132
|
+
102. **A vertical cut is a second composition, not a crop, and only when someone asked for it.** One source, a layout function, `?f=9x16`; `check` and `render` take `--query f=9x16` (recipe `two-formats`). Look at a contact sheet of each format: type that fits wide is tiny tall. Ask which formats are wanted; make one if the answer is open. In a 1080p film keep every line of text at 34 px or more; if you cannot read it in a sheet tile, a phone cannot.
|
|
133
|
+
103. **Load a web font BEFORE you measure with it.** `measureText` / `splitText` / `animateText` measure once, when the composition is built. Measured in Chrome: "Respect the Local, Loop the Value." at 80 px Lato 900 is 1487 px wide before the Google font has arrived (the fallback) and 1231 px after; Japanese text in a similar fallback was within 3 px. Every box, line break and centred position built from the wrong width stays wrong. `await document.fonts.load('900 80px "Lato"', theTextYouUse)` first (the text argument loads exactly the glyphs you need), then build the composition.
|
|
@@ -1103,6 +1103,31 @@ return [
|
|
|
1103
1103
|
];
|
|
1104
1104
|
```
|
|
1105
1105
|
|
|
1106
|
+
## One source, two formats (16:9 and 9:16), and a photo inside a region
|
|
1107
|
+
|
|
1108
|
+
Only when a vertical cut is wanted (ask first). It is not a crop of the wide one: write each scene as a function of a layout, switch with a query (`?f=9x16`), and render each format on its own: `npx pixi-effects check film.html --query f=9x16`, `npx pixi-effects render film.html --query f=9x16 -o film-9x16.mp4`. `photo()` fits a picture into any rectangle like CSS `cover` (scale from the image's own size, a mask for the rectangle); `scale: 'cover'` only fits the PARENT.
|
|
1109
|
+
|
|
1110
|
+
```js
|
|
1111
|
+
// @docs-only two-formats
|
|
1112
|
+
const PORTRAIT = new URLSearchParams(location.search).get('f') === '9x16';
|
|
1113
|
+
const W = PORTRAIT ? 1080 : 1920, H = PORTRAIT ? 1920 : 1080, MX = PORTRAIT ? 76 : 150; // margins: nothing important in the outer 5 %
|
|
1114
|
+
const IMG = { hero: [1920, 1280] }; // each photo's size (assets: [{ name: 'hero', src: '...' }])
|
|
1115
|
+
const photo = (key, x, y, w, h, duration) => {
|
|
1116
|
+
const cover = Math.max(w / IMG[key][0], h / IMG[key][1]);
|
|
1117
|
+
return { type: 'image', asset: key, duration,
|
|
1118
|
+
mask: { type: 'shape', shape: 'rect', width: w, height: h, initial: { x: x + w / 2, y: y + h / 2, fillColor: '#fff' } }, // the mask lives in the parent's coordinates
|
|
1119
|
+
initial: { x: x + w / 2, y: y + h / 2, anchorX: 0.5, anchorY: 0.5, scale: cover }, // image anchors go in `initial`
|
|
1120
|
+
keyframes: [{ at: 0, to: { scale: cover * 1.1 }, duration, ease: 'sine.inOut' }] }; // a slow push
|
|
1121
|
+
};
|
|
1122
|
+
const scene = { type: 'composition', name: 'hook', at: 0, duration: 5.5, width: W, height: H, sequences: [
|
|
1123
|
+
photo('hero', 0, 0, W, H, 5.5),
|
|
1124
|
+
{ type: 'text', text: 'Headline', initial: { x: MX, y: PORTRAIT ? 1150 : 620 }, style: { fontSize: PORTRAIT ? 100 : 112, fill: '#fff' } }, // wide: lower left; tall: lower third
|
|
1125
|
+
] };
|
|
1126
|
+
// movie.init({ width: W, height: H, composition: { sequences: [scene] } })
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1106
1131
|
## A batch of videos from a table (one video per row)
|
|
1107
1132
|
|
|
1108
1133
|
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.
|
|
@@ -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.28.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/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 */
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"imports": {
|
|
38
38
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
39
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
40
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
41
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
42
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
43
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/Controller.js",
|
|
42
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/Presenter.js",
|
|
43
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/three.js",
|
|
44
44
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
45
45
|
"three": "https://esm.sh/three@0.178.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
|
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.
|
|
49
|
+
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
50
50
|
|
|
51
51
|
<script type="module">
|
|
52
52
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-WEJAQAFX.js';
|
|
2
|
-
import './chunk-AK54FIWM.js';
|
|
3
|
-
import './chunk-TYWFKCPY.js';
|
|
4
|
-
import './chunk-45QKLD6M.js';
|
|
5
|
-
import './chunk-BXOCEZ4U.js';
|
|
6
|
-
//# sourceMappingURL=Composition-HFEVIYHN.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-HFEVIYHN.js.map
|