pixi-effects 0.7.0 → 0.9.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 +21 -0
- package/README.md +8 -7
- package/ai/SKILL.md +6 -5
- package/ai/reference/cheatsheet.md +9 -1
- package/ai/template.html +4 -3
- package/ai/tools/check.mjs +8 -0
- package/dist/Composition-CM62XJKT.cjs +14 -0
- package/dist/{Composition-3KHQPRBA.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
- package/dist/Composition-RJDSQYTV.js +5 -0
- package/dist/{Composition-DT657FO5.js.map → Composition-RJDSQYTV.js.map} +1 -1
- package/dist/Controller.cjs +55 -20
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +31 -2
- package/dist/Controller.d.ts +31 -2
- package/dist/Controller.js +55 -21
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-B0OUKlZI.d.ts → Movie-QMlFyHkf.d.ts} +49 -1
- package/dist/{Movie-BK9daiTt.d.cts → Movie-qGHp15c_.d.cts} +49 -1
- package/dist/{chunk-6I55T4OH.cjs → chunk-AGMNSJJH.cjs} +13 -4
- package/dist/chunk-AGMNSJJH.cjs.map +1 -0
- package/dist/{chunk-4O2NYDJG.js → chunk-FNKSXBKB.js} +13 -4
- package/dist/chunk-FNKSXBKB.js.map +1 -0
- package/dist/{chunk-36Z4RIRK.js → chunk-O4AJYUPK.js} +3 -3
- package/dist/{chunk-36Z4RIRK.js.map → chunk-O4AJYUPK.js.map} +1 -1
- package/dist/{chunk-4VSIJLU3.cjs → chunk-PDBDDGG5.cjs} +21 -21
- package/dist/{chunk-4VSIJLU3.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
- package/dist/index.cjs +141 -26
- 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 +130 -15
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +67 -0
- package/docs/dsl.md +14 -2
- package/llms-full.txt +96 -8
- package/package.json +4 -1
- package/dist/Composition-3KHQPRBA.cjs +0 -14
- package/dist/Composition-DT657FO5.js +0 -5
- package/dist/chunk-4O2NYDJG.js.map +0 -1
- package/dist/chunk-6I55T4OH.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
**Added**
|
|
6
|
+
|
|
7
|
+
- **Poster time.** `movie.init({ poster: 9.5 })` names the moment that stands for the movie (a negative value counts back from the end). Once the movie is ready the canvas shows that frame while the playhead stays at 0, and play still starts from 0, like `<video poster>` but derived from the same data, with no separate image file. `movie.poster`, `movie.posterFrame` and `movie.posterImage()` (a `snapshot()` at the poster time that leaves the movie as it was); `pixi-effects-check` writes `poster.jpg`. The gallery uses it: every piece declares `poster` once (the `?poster` handling copied into all 33 pieces and `posterFrame` in their meta are gone), `node scripts/make-posters.mjs` takes the posters and writes `posters/manifest.json` (with a hash of each page, so a poster that is out of date fails a test).
|
|
8
|
+
- A guide for people, as a standalone site on GitHub Pages (`/guide/`, next to the gallery and the live demos): your first video, how it works, guides for text, shapes, images and video, audio, filters, transitions, 2.5D, exporting, the player and reviewing, a cookbook of tested recipes with live demos, a page on working with an AI, and a FAQ. It puts the AI-development tools up front: `check`, the contact sheet, the timeline and viewer, `inspect`, `inspectAudio` and `render`. Pages are markdown in `site/guide/`, built to HTML by `scripts/build-guide.mjs` (`npm run guide` previews it in `guide-preview/`); the recipes in them are built and linted by the tests, and the first-video page is a real file checked in Chrome.
|
|
9
|
+
|
|
10
|
+
## 0.8.0
|
|
11
|
+
|
|
12
|
+
**Added**
|
|
13
|
+
|
|
14
|
+
- Movie events with the names an HTML5 `<video>` uses: `ended` (playback ran off the end, after `pause`), `seeking` / `seeked` (a `gotoFrame()` to another frame; not for playback ticks, `render()`, `snapshot()` or `contactSheet()`), `volumechange` (`{ volume, muted }`) and `error` (`{ where: 'init' | 'render' | 'playback', message, error }`; the call still rejects). A player of your own needs nothing else: `examples/15-custom-player.html` is a complete one, and `docs/api.md` has a section on it.
|
|
15
|
+
- `Controller` theme: the bar's colours and thickness are CSS custom properties (`--mc-accent`, `--mc-fg`, `--mc-track`, `--mc-bar-bg`, `--mc-track-height`, `--mc-font`) with the old values as defaults, set by `new Controller(movie, { canvas, theme: { accent: '#ff4d6d' } })`, by `controller.setTheme(…)` (`null` = default) or by your page CSS. `CONTROLLER_CSS` is exported.
|
|
16
|
+
- `{ type: 'colorGradient', gradientType: 'linear' | 'radial' | 'conic' }`: the filter's own `type` option clashes with the DSL's `type`. 40 of the named filters are now rendered and exported in a real browser (`examples/_checks/named-filters-2.html` is the second half); the table in `docs/dsl.md` lists each one's options, and which ones are driven by `time` or need a texture.
|
|
17
|
+
- CI (`.github/workflows/ci.yml`): typecheck, build and unit tests on every push and pull request (Node 22), and the real-browser tool tests in headless Chrome as a non-blocking job.
|
|
18
|
+
|
|
19
|
+
**Fixed**
|
|
20
|
+
|
|
21
|
+
- The player showed a fullscreen button on browsers without the Fullscreen API for elements (iPhone Safari, which can only fullscreen a `<video>`): a button that did nothing. It is not shown there now, and the F key does nothing.
|
|
22
|
+
- Exporting a movie WITH SOUND to mp4 / mov failed on browsers without an AAC encoder (Chrome on Linux, which is also what a Linux server or CI runs) with "This specific encoder configuration (mp4a.40.2 …) is not supported". The audio codec is now the first one the browser can encode (aac, then opus, mp3, flac for mp4; opus, vorbis for webm), with a warning that says so; a codec you name with `audio.codec` is never swapped, and its failure says which one would work. Found by the real-browser CI job; checked by making `AudioEncoder.isConfigSupported` refuse AAC: the file was h264 + opus.
|
|
23
|
+
|
|
3
24
|
## 0.7.0
|
|
4
25
|
|
|
5
26
|
**Fixed**
|
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.9.0`. The API may still change between minor versions (see [CHANGELOG](./CHANGELOG.md): `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
|
-
**[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 33 portfolio pieces written as plain data by AI models · **[Live demos →](https://yjmtmtk.github.io/pixi-effects/)** · 12 numbered examples + an in-browser playground.
|
|
5
|
+
**[Guide →](https://yjmtmtk.github.io/pixi-effects/guide/)** · **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 33 portfolio pieces written as plain data by AI models · **[Live demos →](https://yjmtmtk.github.io/pixi-effects/)** · 12 numbered examples + an in-browser playground.
|
|
6
6
|
|
|
7
7
|
Declarative composition and video rendering for the web. After Effects-style timelines on top of [PixiJS v8](https://pixijs.com/) and [GSAP](https://gsap.com/), with strict TypeScript types. Render to MP4 / WebM / MOV via [mediabunny](https://mediabunny.dev/).
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ Declarative composition and video rendering for the web. After Effects-style tim
|
|
|
15
15
|
- **Presets** — `kenBurns` for stills, `withFade` for fade-in/out.
|
|
16
16
|
- **2.5D layers & camera** — add `threeD: true`, `z`, `rotationX/Y` and a `{ type: 'camera' }` layer for parallax, flips and dolly zooms; no three.js needed.
|
|
17
17
|
- **three.js layer** *(optional)* — drop a real three.js scene in as a layer via `pixi-effects/three`; keyframes drive its objects (`three.cube.rotation.y`).
|
|
18
|
-
- **Built-in player UI** — drop-in HTML5-`<video>`-style overlay controller (play, scrub, mute, volume, fullscreen, export-to-file).
|
|
18
|
+
- **Built-in player UI — or your own** — a drop-in HTML5-`<video>`-style overlay controller (play, scrub, mute, volume, fullscreen, export-to-file) whose colours are one option (`theme: { accent: '#ff4d6d' }`); or build a player yourself on the movie's `<video>`-named events (`play pause ended seeking seeked volumechange error`).
|
|
19
19
|
- **MP4 / WebM / MOV export** — pick container and quality from the controller, call `movie.render()` from code, or render headless from the command line (`pixi-effects-render`).
|
|
20
20
|
- **A timeline you can read and scrub** — `movie.timelineChart()` draws every layer as a bar on a time axis; `pixi-effects-view` opens the page with that timeline under it, zoomable, with a playhead you can drag.
|
|
21
21
|
- **Tiny dependency surface** — only `mediabunny` (runtime) plus PixiJS and GSAP (peer deps). three.js is an *optional* peer, needed only if you import `pixi-effects/three`.
|
|
@@ -40,8 +40,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
40
40
|
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
41
41
|
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
42
42
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
43
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
44
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/index.js",
|
|
44
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/Controller.js"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
</script>
|
|
@@ -52,7 +52,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
52
52
|
</script>
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.
|
|
55
|
+
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.9.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.
|
|
56
56
|
|
|
57
57
|
> **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
|
|
58
58
|
>
|
|
@@ -68,7 +68,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
|
|
|
68
68
|
"imports": {
|
|
69
69
|
"...": "(everything from the importmap above)",
|
|
70
70
|
"three": "https://esm.sh/three@0.178.0",
|
|
71
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
71
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/three.js"
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
</script>
|
|
@@ -189,6 +189,7 @@ They ship in the npm package (`node_modules/pixi-effects/ai/`).
|
|
|
189
189
|
- `11-depth.html` — 2.5D layers and camera: parallax, spin, dolly zoom
|
|
190
190
|
- `12-title-motion.html` — a staggered 2.5D title: letters fly in from depth, particles, orbiting camera
|
|
191
191
|
- `13-sfx.html` — synthesised sound effects (no audio files), a riser into a hit, a custom voice
|
|
192
|
+
- `15-custom-player.html` — a complete player of your own in about 40 lines (play, seek, volume, replay) using the movie's events
|
|
192
193
|
- `14-draw-on.html` — draw-on strokes (`trimStart` / `trimEnd`) and text that changes over time (`visibleChars`, `set: { text }`)
|
|
193
194
|
- `playground.html` — in-browser editor with preset dropdown
|
|
194
195
|
|
package/ai/SKILL.md
CHANGED
|
@@ -10,16 +10,17 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
10
10
|
## Workflow
|
|
11
11
|
|
|
12
12
|
1. **Start from `template.html`** (copy it; 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
|
-
2. **
|
|
14
|
-
3. **
|
|
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
|
+
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
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.
|
|
16
|
-
|
|
17
|
+
5. **Look at it** — you cannot judge motion from code. The library has the tools built in:
|
|
17
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.
|
|
18
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.
|
|
19
20
|
- `await movie.snapshot(frame, { as: 'dataURL' })` → one frame, canvas only (no player bar).
|
|
20
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.
|
|
21
22
|
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.
|
|
22
|
-
|
|
23
|
+
6. **Iterate** on what you saw (clipped text, collisions, wrong timing). Then export: `const blob = await movie.render({ format: 'mp4' })` (about real time; `movie.on('progress', …)` reports 0–100).
|
|
23
24
|
|
|
24
25
|
## The rules that cause most failures
|
|
25
26
|
|
|
@@ -43,7 +44,7 @@ Centred text must be positioned by `anchorX/anchorY: 0.5`; estimate text width a
|
|
|
43
44
|
- `reference/cheatsheet.md` — every layer type, prop, default and rule on one page.
|
|
44
45
|
- `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.
|
|
45
46
|
- `reference/pitfalls.md` — the full list of real mistakes, with status.
|
|
46
|
-
- `tools/check.mjs` — the one-command review (see Workflow step
|
|
47
|
+
- `tools/check.mjs` — the one-command review (see Workflow step 5).
|
|
47
48
|
- `template.html` — the starting file. (It loads the library from a CDN; when testing against a local build, point the three `pixi-effects*` importmap entries at `../../dist/…`.)
|
|
48
49
|
- `tools/save-image.py` — save a `dataURL` result (contact sheet / snapshot) to a PNG.
|
|
49
50
|
- Full reference: `docs/dsl.md` and `docs/api.md` in the repository.
|
|
@@ -83,9 +83,17 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
|
|
|
83
83
|
|
|
84
84
|
Add `threeD: true` to any visual layer, then use `z`, `rotationX`, `rotationY` (centre rotation with `anchorX/Y: 0.5` or `pivotX/Y`). Add a `{ type: 'camera' }` layer for a view. A `threeD` layer at `z: 0` with the default camera looks identical to a 2D one. Consecutive `threeD` layers are drawn farthest-first (equal `z` keeps array order); non-`threeD` layers ignore the camera and keep array order. Layers at/behind the camera plane are hidden. Not supported on `threeD` layers: masks, transitions, `filterArea`.
|
|
85
85
|
|
|
86
|
+
## Poster
|
|
87
|
+
|
|
88
|
+
`movie.init({ …, poster: 9.5 })` names the moment (seconds; negative = from the end) that stands for the movie: the canvas shows it before play (the playhead stays at 0, play starts from 0), `movie.poster` / `movie.posterFrame` read it, `await movie.posterImage({ as: 'dataURL', type: 'image/jpeg', scale: 0.5 })` is the picture. Pick the most striking moment, not frame 0 (which is often black).
|
|
89
|
+
|
|
90
|
+
## Player
|
|
91
|
+
|
|
92
|
+
`new Controller(movie, { canvas, theme: { accent: '#ff4d6d' } })` (import from `pixi-effects/controller`; theme keys `accent foreground track barBackground trackHeight font`, or CSS `--mc-accent` …; `controller.setTheme(…)`). Your own player: `movie.play() pause() gotoFrame(f) volume muted toggleMute()`, state `currentFrame totalFrames frameRate duration isPlaying`, events `ready frame play pause ended seeking seeked volumechange error progress` (`<video>` names). `examples/15-custom-player.html` is a complete one.
|
|
93
|
+
|
|
86
94
|
## Filters by name (no import)
|
|
87
95
|
|
|
88
|
-
`filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha. **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
|
|
96
|
+
`filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha · `colorGradient` gradientType('linear'|'radial'|'conic') stops[{offset,color,alpha}] angle · `colorReplace` originalColor targetColor tolerance · `convolution` matrix(9) · `shockwave` / `godray` are driven by `time` (animate it) · `colorMap` / `simpleLightmap` need a texture (use `custom`). **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
|
|
89
97
|
|
|
90
98
|
## Transitions (on the parent composition)
|
|
91
99
|
|
package/ai/template.html
CHANGED
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
37
37
|
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
38
38
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
39
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
40
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
41
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
39
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/index.js",
|
|
40
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/Controller.js",
|
|
41
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.9.0/dist/three.js",
|
|
42
42
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
43
43
|
"three": "https://esm.sh/three@0.178.0"
|
|
44
44
|
}
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
await movie.init({
|
|
74
74
|
canvas: document.getElementById('stage'),
|
|
75
75
|
width: W, height: H, duration: DURATION, frameRate: FPS,
|
|
76
|
+
poster: DURATION * 0.75, // the moment that stands for the video before it plays (pick your most striking one)
|
|
76
77
|
background: '#0a0a0f',
|
|
77
78
|
// assets: [{ name: 'photo', src: './photo.jpg' }, { name: 'bgm', src: './bgm.mp3' }],
|
|
78
79
|
composition: { sequences },
|
package/ai/tools/check.mjs
CHANGED
|
@@ -279,6 +279,14 @@ export async function runCheck(opts, log = console.log) {
|
|
|
279
279
|
fs.writeFileSync(sheetFile, Buffer.from(sheet.slice(sheet.indexOf(',') + 1), 'base64'));
|
|
280
280
|
report.files.sheet = shown(sheetFile);
|
|
281
281
|
|
|
282
|
+
// the poster: the picture that stands for the movie (its poster time, or the first frame)
|
|
283
|
+
const poster = await cdp.eval(`movie.posterImage({ as: 'dataURL', type: 'image/jpeg', scale: 0.5 })`).catch(() => null);
|
|
284
|
+
if (poster) {
|
|
285
|
+
const posterFile = path.join(outDir, 'poster.jpg');
|
|
286
|
+
fs.writeFileSync(posterFile, Buffer.from(poster.slice(poster.indexOf(',') + 1), 'base64'));
|
|
287
|
+
report.files.poster = shown(posterFile);
|
|
288
|
+
}
|
|
289
|
+
|
|
282
290
|
// the timeline: every layer as a bar on a time axis (for a human to open in a browser)
|
|
283
291
|
const timeline = await cdp.eval(`movie.timelineChart({ title: ${JSON.stringify(name)} })`).catch(() => null);
|
|
284
292
|
if (timeline) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkPDBDDGG5_cjs = require('./chunk-PDBDDGG5.cjs');
|
|
4
|
+
require('./chunk-AGMNSJJH.cjs');
|
|
5
|
+
require('./chunk-UQBGW5DO.cjs');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, "CompositionSequence", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function () { return chunkPDBDDGG5_cjs.CompositionSequence; }
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=Composition-CM62XJKT.cjs.map
|
|
14
|
+
//# sourceMappingURL=Composition-CM62XJKT.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-CM62XJKT.cjs"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-RJDSQYTV.js"}
|
package/dist/Controller.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
|
|
4
|
+
|
|
3
5
|
// src/Controller.ts
|
|
4
6
|
var ICONS = {
|
|
5
7
|
play: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M3 2 L13 8 L3 14 Z"/></svg>',
|
|
@@ -11,12 +13,12 @@ var ICONS = {
|
|
|
11
13
|
fullscreenExit: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2 V6 H2 M10 2 V6 H14 M6 14 V10 H2 M10 14 V10 H14"/></svg>'
|
|
12
14
|
};
|
|
13
15
|
var STYLE_ATTR = "data-movie-controller";
|
|
14
|
-
var
|
|
16
|
+
var CONTROLLER_CSS = `
|
|
15
17
|
.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }
|
|
16
18
|
.movie-controller {
|
|
17
19
|
position: absolute;
|
|
18
20
|
pointer-events: none;
|
|
19
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
21
|
+
font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
20
22
|
display: flex;
|
|
21
23
|
flex-direction: column;
|
|
22
24
|
justify-content: flex-end;
|
|
@@ -36,31 +38,31 @@ var STYLE_CSS = `
|
|
|
36
38
|
outline: none;
|
|
37
39
|
}
|
|
38
40
|
.mc-progress:focus-visible {
|
|
39
|
-
outline: 2px solid #007AFF;
|
|
41
|
+
outline: 2px solid var(--mc-accent, #007AFF);
|
|
40
42
|
outline-offset: 2px;
|
|
41
43
|
}
|
|
42
44
|
.mc-progress::before {
|
|
43
45
|
content: ""; position: absolute; left: 12px; right: 12px;
|
|
44
|
-
height: 3px; background: rgba(255,255,255,0.25);
|
|
46
|
+
height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));
|
|
45
47
|
transition: height 120ms ease;
|
|
46
48
|
}
|
|
47
|
-
.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height:
|
|
49
|
+
.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }
|
|
48
50
|
.mc-progress-fill {
|
|
49
51
|
position: absolute; left: 12px; top: 50%;
|
|
50
|
-
height: 3px;
|
|
52
|
+
height: var(--mc-track-height, 3px);
|
|
51
53
|
width: calc((100% - 24px) * var(--mc-fill, 0));
|
|
52
|
-
background: #007AFF;
|
|
54
|
+
background: var(--mc-accent, #007AFF);
|
|
53
55
|
transform: translateY(-50%);
|
|
54
56
|
transition: height 120ms ease;
|
|
55
57
|
pointer-events: none;
|
|
56
58
|
}
|
|
57
59
|
.mc-progress:hover .mc-progress-fill,
|
|
58
|
-
.mc-progress.mc-scrubbing .mc-progress-fill { height:
|
|
60
|
+
.mc-progress.mc-scrubbing .mc-progress-fill { height: calc(var(--mc-track-height, 3px) + 2px); }
|
|
59
61
|
.mc-progress-thumb {
|
|
60
62
|
position: absolute; top: 50%;
|
|
61
63
|
left: calc(12px + (100% - 24px) * var(--mc-fill, 0));
|
|
62
64
|
width: 12px; height: 12px; border-radius: 50%;
|
|
63
|
-
background: #007AFF;
|
|
65
|
+
background: var(--mc-accent, #007AFF);
|
|
64
66
|
transform: translate(-50%, -50%) scale(0);
|
|
65
67
|
transition: transform 120ms ease;
|
|
66
68
|
pointer-events: none;
|
|
@@ -74,22 +76,22 @@ var STYLE_CSS = `
|
|
|
74
76
|
align-items: center;
|
|
75
77
|
gap: 12px;
|
|
76
78
|
padding: 6px 12px 8px 12px;
|
|
77
|
-
background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%);
|
|
78
|
-
color: #fff;
|
|
79
|
+
background: var(--mc-bar-bg, linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%));
|
|
80
|
+
color: var(--mc-fg, #fff);
|
|
79
81
|
line-height: 1;
|
|
80
82
|
}
|
|
81
83
|
.mc-btn {
|
|
82
84
|
background: none; border: 0; padding: 0; margin: 0;
|
|
83
85
|
width: 28px; height: 28px;
|
|
84
86
|
display: inline-flex; align-items: center; justify-content: center;
|
|
85
|
-
color: #fff; opacity: 0.85; cursor: pointer;
|
|
87
|
+
color: var(--mc-fg, #fff); opacity: 0.85; cursor: pointer;
|
|
86
88
|
transition: opacity 120ms ease;
|
|
87
89
|
}
|
|
88
90
|
.mc-btn:hover { opacity: 1; }
|
|
89
91
|
.mc-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
90
92
|
.mc-time {
|
|
91
93
|
font-size: 12px; font-family: Menlo, Monaco, monospace;
|
|
92
|
-
color: #fff; opacity: 0.85;
|
|
94
|
+
color: var(--mc-fg, #fff); opacity: 0.85;
|
|
93
95
|
min-width: 90px;
|
|
94
96
|
}
|
|
95
97
|
.mc-spacer { flex: 1; }
|
|
@@ -113,16 +115,16 @@ var STYLE_CSS = `
|
|
|
113
115
|
width: 70px;
|
|
114
116
|
margin-left: 6px;
|
|
115
117
|
}
|
|
116
|
-
.mc-vol-slider:focus-visible { outline: 2px solid #007AFF; outline-offset: 2px; }
|
|
118
|
+
.mc-vol-slider:focus-visible { outline: 2px solid var(--mc-accent, #007AFF); outline-offset: 2px; }
|
|
117
119
|
.mc-vol-slider::before {
|
|
118
120
|
content: ""; position: absolute; left: 4px; right: 4px;
|
|
119
|
-
height: 3px; background: rgba(255,255,255,0.25); border-radius: 2px;
|
|
121
|
+
height: 3px; background: var(--mc-track, rgba(255,255,255,0.25)); border-radius: 2px;
|
|
120
122
|
}
|
|
121
123
|
.mc-vol-fill {
|
|
122
124
|
position: absolute; left: 4px; top: 50%;
|
|
123
125
|
height: 3px;
|
|
124
126
|
width: calc((100% - 8px) * var(--mc-volume, 1));
|
|
125
|
-
background: #fff;
|
|
127
|
+
background: var(--mc-fg, #fff);
|
|
126
128
|
transform: translateY(-50%);
|
|
127
129
|
border-radius: 2px;
|
|
128
130
|
pointer-events: none;
|
|
@@ -131,7 +133,7 @@ var STYLE_CSS = `
|
|
|
131
133
|
position: absolute; top: 50%;
|
|
132
134
|
left: calc(4px + (100% - 8px) * var(--mc-volume, 1));
|
|
133
135
|
width: 10px; height: 10px; border-radius: 50%;
|
|
134
|
-
background: #fff;
|
|
136
|
+
background: var(--mc-fg, #fff);
|
|
135
137
|
transform: translate(-50%, -50%);
|
|
136
138
|
pointer-events: none;
|
|
137
139
|
}
|
|
@@ -185,7 +187,7 @@ var STYLE_CSS = `
|
|
|
185
187
|
.mc-export-confirm {
|
|
186
188
|
margin-top: 4px;
|
|
187
189
|
width: 100%;
|
|
188
|
-
background: #007AFF;
|
|
190
|
+
background: var(--mc-accent, #007AFF);
|
|
189
191
|
color: #fff;
|
|
190
192
|
border: 0;
|
|
191
193
|
border-radius: 4px;
|
|
@@ -218,7 +220,7 @@ var STYLE_CSS = `
|
|
|
218
220
|
}
|
|
219
221
|
.mc-export-fill {
|
|
220
222
|
height: 100%; width: 0%;
|
|
221
|
-
background: linear-gradient(90deg, #007AFF, #0056CC);
|
|
223
|
+
background: linear-gradient(90deg, var(--mc-accent, #007AFF), var(--mc-accent, #0056CC));
|
|
222
224
|
transition: width 0.3s ease; border-radius: 3px;
|
|
223
225
|
}
|
|
224
226
|
.mc-export-text {
|
|
@@ -231,7 +233,7 @@ function installStyles() {
|
|
|
231
233
|
if (styleRefCount === 0) {
|
|
232
234
|
const el = document.createElement("style");
|
|
233
235
|
el.setAttribute(STYLE_ATTR, "");
|
|
234
|
-
el.textContent =
|
|
236
|
+
el.textContent = CONTROLLER_CSS;
|
|
235
237
|
document.head.appendChild(el);
|
|
236
238
|
}
|
|
237
239
|
styleRefCount++;
|
|
@@ -273,6 +275,14 @@ function extensionForMimeType(mime) {
|
|
|
273
275
|
if (t.includes("matroska") || t.includes("mkv")) return "mkv";
|
|
274
276
|
return "mp4";
|
|
275
277
|
}
|
|
278
|
+
var THEME_PROPERTIES = {
|
|
279
|
+
accent: "--mc-accent",
|
|
280
|
+
foreground: "--mc-fg",
|
|
281
|
+
track: "--mc-track",
|
|
282
|
+
barBackground: "--mc-bar-bg",
|
|
283
|
+
trackHeight: "--mc-track-height",
|
|
284
|
+
font: "--mc-font"
|
|
285
|
+
};
|
|
276
286
|
var Controller = class _Controller {
|
|
277
287
|
movie;
|
|
278
288
|
options;
|
|
@@ -345,6 +355,7 @@ var Controller = class _Controller {
|
|
|
345
355
|
this.root.className = this.options.className;
|
|
346
356
|
this.wrapper.appendChild(this.root);
|
|
347
357
|
this.buildBar();
|
|
358
|
+
this.setTheme(options.theme ?? {});
|
|
348
359
|
this.bindFitWrap();
|
|
349
360
|
this.syncRootToCanvas();
|
|
350
361
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -424,6 +435,17 @@ var Controller = class _Controller {
|
|
|
424
435
|
if (this.settingsFormatSelect) this.settingsFormatSelect.value = this.exportFormat;
|
|
425
436
|
if (this.settingsQualitySelect) this.settingsQualitySelect.value = this.exportQuality;
|
|
426
437
|
}
|
|
438
|
+
/** Change the look of the bar (see {@link ControllerTheme}): only the keys you give change, `null` restores the default. */
|
|
439
|
+
setTheme(theme) {
|
|
440
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("Controller theme", theme, Object.keys(THEME_PROPERTIES));
|
|
441
|
+
for (const key of Object.keys(THEME_PROPERTIES)) {
|
|
442
|
+
if (!(key in theme)) continue;
|
|
443
|
+
const value = theme[key];
|
|
444
|
+
const prop = THEME_PROPERTIES[key];
|
|
445
|
+
if (value === null || value === void 0 || value === "") this.root.style.removeProperty(prop);
|
|
446
|
+
else this.root.style.setProperty(prop, typeof value === "number" ? `${value}px` : String(value));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
427
449
|
ensurePositioningContext(canvas) {
|
|
428
450
|
const parent = canvas.parentElement;
|
|
429
451
|
if (!parent) {
|
|
@@ -605,8 +627,20 @@ var Controller = class _Controller {
|
|
|
605
627
|
};
|
|
606
628
|
container.addEventListener("wheel", this.wheelHandler, { passive: false });
|
|
607
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Does this browser have the Fullscreen API for an element? iPhone Safari does not (it can only put a <video> in fullscreen),
|
|
632
|
+
* and a button that does nothing is worse than none.
|
|
633
|
+
*/
|
|
634
|
+
supportsFullscreen() {
|
|
635
|
+
return typeof this.wrapper.requestFullscreen === "function" && document.fullscreenEnabled !== false;
|
|
636
|
+
}
|
|
608
637
|
bindFullscreen() {
|
|
609
638
|
if (!this.fullscreenBtn) return;
|
|
639
|
+
if (!this.supportsFullscreen()) {
|
|
640
|
+
this.fullscreenBtn.remove();
|
|
641
|
+
this.fullscreenBtn = null;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
610
644
|
this.fullscreenBtn.addEventListener("click", () => {
|
|
611
645
|
void this.toggleFullscreen();
|
|
612
646
|
});
|
|
@@ -1029,6 +1063,7 @@ var Controller = class _Controller {
|
|
|
1029
1063
|
}
|
|
1030
1064
|
};
|
|
1031
1065
|
|
|
1066
|
+
exports.CONTROLLER_CSS = CONTROLLER_CSS;
|
|
1032
1067
|
exports.Controller = Controller;
|
|
1033
1068
|
exports.extensionForMimeType = extensionForMimeType;
|
|
1034
1069
|
exports.formatTime = formatTime;
|