frameyard 0.8.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 +110 -14
- package/GLOSSARY.md +112 -2
- package/README.md +302 -265
- package/dist/assets/integrations.d.ts +8 -4
- package/dist/assets/integrations.d.ts.map +1 -1
- package/dist/assets/integrations.js +8 -4
- package/dist/assets/integrations.js.map +1 -1
- package/dist/assets/provenance.d.ts +2 -2
- package/dist/assets/provenance.d.ts.map +1 -1
- package/dist/assets/provenance.js +5 -3
- package/dist/assets/provenance.js.map +1 -1
- package/dist/assets/tts-supertonic.d.ts +28 -0
- package/dist/assets/tts-supertonic.d.ts.map +1 -0
- package/dist/assets/tts-supertonic.js +156 -0
- package/dist/assets/tts-supertonic.js.map +1 -0
- package/dist/assets/tts.d.ts +18 -6
- package/dist/assets/tts.d.ts.map +1 -1
- package/dist/assets/tts.js +219 -97
- package/dist/assets/tts.js.map +1 -1
- package/dist/cli.js +1685 -93
- package/dist/cli.js.map +1 -1
- package/dist/composition/format.d.ts +1 -1
- package/dist/composition/format.d.ts.map +1 -1
- package/dist/composition/format.js +1 -1
- package/dist/composition/format.js.map +1 -1
- package/dist/context.d.ts +82 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +25 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts +14 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +166 -7
- package/dist/doctor.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts/add.d.ts +41 -0
- package/dist/fonts/add.d.ts.map +1 -0
- package/dist/fonts/add.js +259 -0
- package/dist/fonts/add.js.map +1 -0
- package/dist/fonts/binary.d.ts +4 -0
- package/dist/fonts/binary.d.ts.map +1 -0
- package/dist/fonts/binary.js +21 -0
- package/dist/fonts/binary.js.map +1 -0
- package/dist/fonts/css.d.ts +4 -0
- package/dist/fonts/css.d.ts.map +1 -0
- package/dist/fonts/css.js +50 -0
- package/dist/fonts/css.js.map +1 -0
- package/dist/fonts/fontsource.d.ts +107 -0
- package/dist/fonts/fontsource.d.ts.map +1 -0
- package/dist/fonts/fontsource.js +565 -0
- package/dist/fonts/fontsource.js.map +1 -0
- package/dist/fonts/index.d.ts +11 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/fonts/index.js +11 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/fonts/install.d.ts +24 -0
- package/dist/fonts/install.d.ts.map +1 -0
- package/dist/fonts/install.js +145 -0
- package/dist/fonts/install.js.map +1 -0
- package/dist/fonts/manifest.d.ts +21 -0
- package/dist/fonts/manifest.d.ts.map +1 -0
- package/dist/fonts/manifest.js +96 -0
- package/dist/fonts/manifest.js.map +1 -0
- package/dist/fonts/options.d.ts +9 -0
- package/dist/fonts/options.d.ts.map +1 -0
- package/dist/fonts/options.js +66 -0
- package/dist/fonts/options.js.map +1 -0
- package/dist/fonts/schema.d.ts +4 -0
- package/dist/fonts/schema.d.ts.map +1 -0
- package/dist/fonts/schema.js +316 -0
- package/dist/fonts/schema.js.map +1 -0
- package/dist/fonts/summary.d.ts +16 -0
- package/dist/fonts/summary.d.ts.map +1 -0
- package/dist/fonts/summary.js +22 -0
- package/dist/fonts/summary.js.map +1 -0
- package/dist/fonts/types.d.ts +56 -0
- package/dist/fonts/types.d.ts.map +1 -0
- package/dist/fonts/types.js +8 -0
- package/dist/fonts/types.js.map +1 -0
- package/dist/fonts/verify.d.ts +33 -0
- package/dist/fonts/verify.d.ts.map +1 -0
- package/dist/fonts/verify.js +200 -0
- package/dist/fonts/verify.js.map +1 -0
- package/dist/html/validate.d.ts.map +1 -1
- package/dist/html/validate.js +59 -0
- package/dist/html/validate.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +61 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +325 -0
- package/dist/inspect.js.map +1 -0
- package/dist/lint.d.ts +63 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +265 -0
- package/dist/lint.js.map +1 -0
- package/dist/policy.d.ts +69 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +361 -0
- package/dist/policy.js.map +1 -0
- package/dist/preview/timeline-data.d.ts +17 -69
- package/dist/preview/timeline-data.d.ts.map +1 -1
- package/dist/preview/timeline-host.d.ts.map +1 -1
- package/dist/preview/timeline-host.js +70 -45
- package/dist/preview/timeline-host.js.map +1 -1
- package/dist/preview/timeline-main.d.ts.map +1 -1
- package/dist/preview/timeline-main.js +47 -4
- package/dist/preview/timeline-main.js.map +1 -1
- package/dist/project.d.ts +10 -3
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +20 -17
- package/dist/project.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +42 -2
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +254 -22
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.js +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyframe-expressions.d.ts +39 -0
- package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
- package/dist/renderer/keyframe-expressions.js +180 -0
- package/dist/renderer/keyframe-expressions.js.map +1 -0
- package/dist/renderer/timeline.d.ts +2 -0
- package/dist/renderer/timeline.d.ts.map +1 -1
- package/dist/renderer/timeline.js +89 -116
- package/dist/renderer/timeline.js.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/host.js +4 -0
- package/dist/runtime/host.js.map +1 -1
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +48 -320
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +2 -0
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/composition-html.js +7 -0
- package/dist/server/composition-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +31 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts.map +1 -1
- package/dist/server/timeline-preview-data.js +12 -34
- package/dist/server/timeline-preview-data.js.map +1 -1
- package/dist/server/timeline-preview-html.d.ts.map +1 -1
- package/dist/server/timeline-preview-html.js +18 -2
- package/dist/server/timeline-preview-html.js.map +1 -1
- package/dist/templates.d.ts +16 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +186 -0
- package/dist/templates.js.map +1 -0
- package/dist/timeline/easing.d.ts +36 -0
- package/dist/timeline/easing.d.ts.map +1 -0
- package/dist/timeline/easing.js +225 -0
- package/dist/timeline/easing.js.map +1 -0
- package/dist/timeline/keyframes.d.ts +23 -0
- package/dist/timeline/keyframes.d.ts.map +1 -0
- package/dist/timeline/keyframes.js +95 -0
- package/dist/timeline/keyframes.js.map +1 -0
- package/dist/timeline/plan.d.ts +6 -12
- package/dist/timeline/plan.d.ts.map +1 -1
- package/dist/timeline/plan.js +17 -30
- package/dist/timeline/plan.js.map +1 -1
- package/dist/timeline/resolve.d.ts +12 -0
- package/dist/timeline/resolve.d.ts.map +1 -1
- package/dist/timeline/resolve.js +105 -9
- package/dist/timeline/resolve.js.map +1 -1
- package/dist/timeline/scene.d.ts +126 -0
- package/dist/timeline/scene.d.ts.map +1 -0
- package/dist/timeline/scene.js +109 -0
- package/dist/timeline/scene.js.map +1 -0
- package/dist/timeline/schema.d.ts +31 -0
- package/dist/timeline/schema.d.ts.map +1 -1
- package/dist/timeline/schema.js +240 -9
- package/dist/timeline/schema.js.map +1 -1
- package/dist/timeline/transitions.d.ts +21 -0
- package/dist/timeline/transitions.d.ts.map +1 -0
- package/dist/timeline/transitions.js +23 -0
- package/dist/timeline/transitions.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
- package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
- package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
- package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
- package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
- package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
- package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
- package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
- package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
- package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
- package/docs/migration-0.4.md +2 -2
- package/docs/preview-testing.md +19 -6
- package/docs/{v0.1-design.md → v0.x-design.md} +55 -30
- package/examples/blend/README.md +3 -3
- package/examples/demo/README.md +2 -2
- package/examples/intro/README.md +71 -0
- package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
- package/examples/intro/assets/grain.png +0 -0
- package/examples/intro/assets/score.ogg +0 -0
- package/examples/intro/compositions/brand.html +412 -0
- package/examples/intro/compositions/chrome.controller.js +63 -0
- package/examples/intro/compositions/chrome.html +256 -0
- package/examples/intro/compositions/lattice.controller.js +148 -0
- package/examples/intro/compositions/lattice.html +308 -0
- package/examples/intro/compositions/opening.html +504 -0
- package/examples/intro/compositions/studio.html +930 -0
- package/examples/intro/compositions/theme.css +218 -0
- package/examples/intro/fy.config.js +13 -0
- package/examples/intro/fy.fonts.json +100 -0
- package/examples/intro/scripts/make-grain.mjs +79 -0
- package/examples/intro/scripts/make-score.mjs +702 -0
- package/examples/intro/timelines/intro.timeline.json +60 -0
- package/examples/keyframes/README.md +76 -0
- package/examples/keyframes/compositions/backdrop.html +60 -0
- package/examples/keyframes/compositions/card.html +99 -0
- package/examples/keyframes/fy.config.js +10 -0
- package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
- package/examples/nested/README.md +112 -0
- package/examples/nested/assets/pulse.wav +0 -0
- package/examples/nested/compositions/backdrop.html +84 -0
- package/examples/nested/compositions/card.html +275 -0
- package/examples/nested/compositions/overlay.html +307 -0
- package/examples/nested/fy.config.js +13 -0
- package/examples/nested/scripts/make-pulse.sh +20 -0
- package/examples/nested/timelines/chapter-a.timeline.json +39 -0
- package/examples/nested/timelines/chapter-b.timeline.json +39 -0
- package/examples/nested/timelines/nested-reel.timeline.json +52 -0
- package/examples/static/README.md +2 -2
- package/examples/transitions/README.md +128 -0
- package/examples/transitions/assets/alpha.wav +0 -0
- package/examples/transitions/assets/beta.wav +0 -0
- package/examples/transitions/assets/gamma.wav +0 -0
- package/examples/transitions/compositions/alpha.html +56 -0
- package/examples/transitions/compositions/beta.html +56 -0
- package/examples/transitions/compositions/gamma.html +56 -0
- package/examples/transitions/fy.config.js +12 -0
- package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
- package/examples/transitions/timelines/transitions.timeline.json +31 -0
- package/examples/video/README.md +4 -4
- package/examples/webgl/README.md +1 -1
- package/package.json +12 -5
- package/scaffolds/init/AGENTS.md +131 -0
- package/scaffolds/init/README.md +56 -0
- package/scaffolds/init/compositions/hello.html +79 -0
- package/scaffolds/init/fy.config.js +9 -0
- package/scaffolds/init/gitignore.template +1 -0
- package/scaffolds/init/package.json +11 -0
- package/scaffolds/init/props/hello.json +4 -0
- package/scaffolds/init/props/main.json +6 -0
- package/scaffolds/init/timelines/main.timeline.json +20 -0
- package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
- package/scaffolds/local-models/fy.policy.json +5 -0
- package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
- package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
- package/scaffolds/local-models/pi/sandbox.json +15 -0
- package/scaffolds/local-models/pi/settings.json +15 -0
- package/skills/frameyard/README.md +1 -0
- package/skills/frameyard/SKILL.md +59 -61
- package/skills/frameyard/references/audio-assets.md +32 -3
- package/skills/frameyard/references/common-mistakes.md +53 -2
- package/skills/frameyard/references/deterministic-authoring.md +10 -3
- package/skills/frameyard/references/diagnostics.md +31 -1
- package/skills/frameyard/references/failure-recovery.md +32 -6
- package/skills/frameyard/references/fonts.md +132 -0
- package/skills/frameyard/references/json-results.md +119 -6
- package/skills/frameyard/references/quick-reference.md +222 -0
- package/skills/frameyard/references/timelines.md +97 -7
- /package/examples/blend/{video.config.js → fy.config.js} +0 -0
- /package/examples/demo/{video.config.js → fy.config.js} +0 -0
- /package/examples/static/{video.config.js → fy.config.js} +0 -0
- /package/examples/video/{video.config.js → fy.config.js} +0 -0
- /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Quick reference
|
|
2
|
+
|
|
3
|
+
Use this page when context is limited or when you need one canonical skeleton.
|
|
4
|
+
Read a topic reference only when the task needs it; the full contract is in
|
|
5
|
+
`SKILL.md` and the linked references.
|
|
6
|
+
|
|
7
|
+
## Choose the smallest authoring level
|
|
8
|
+
|
|
9
|
+
1. **Static composition** for a poster, card, or thumbnail: no `duration-in-frames`, no `fps`, renders to PNG.
|
|
10
|
+
2. **CSS animation** for timed reveals, wipes, and simple motion: `@keyframes` plus `data-fy-anim-*`.
|
|
11
|
+
3. **Bindings** for props-driven text, attributes, classes, conditionals, and repeats.
|
|
12
|
+
4. **Controller** for Canvas 2D, WebGL, karaoke, or per-frame math CSS cannot express.
|
|
13
|
+
5. **Timeline** when several compositions, media clips, or audio tracks must share one deliverable.
|
|
14
|
+
|
|
15
|
+
Do not use a controller when CSS or bindings are enough.
|
|
16
|
+
|
|
17
|
+
## Animated composition
|
|
18
|
+
|
|
19
|
+
A composition is a partial HTML fragment, never a full document. Props, the
|
|
20
|
+
controller, import maps, and styles are top-level siblings of the root.
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script type="application/json" data-fy="props">{ "title": "Hello" }</script>
|
|
24
|
+
<style>
|
|
25
|
+
html, body { margin: 0; width: 100%; height: 100%; }
|
|
26
|
+
@keyframes rise { from { opacity: 0; } to { opacity: 1; } }
|
|
27
|
+
h1 { animation: rise 1s linear both; font: 8vmin system-ui; color: white; }
|
|
28
|
+
body { background: #101820; display: grid; place-items: center; }
|
|
29
|
+
</style>
|
|
30
|
+
<fy-composition duration-in-frames="150">
|
|
31
|
+
<h1 data-fy-text="title" data-fy-anim-start="0" data-fy-anim-duration="30"></h1>
|
|
32
|
+
</fy-composition>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`width`, `height`, and `fps` are optional and inherit the project `format`.
|
|
36
|
+
Declare both `width` and `height` together. Never derive metadata from props.
|
|
37
|
+
|
|
38
|
+
## Static composition
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script type="application/json" data-fy="props">{ "title": "Poster" }</script>
|
|
42
|
+
<style>
|
|
43
|
+
body { margin: 0; background: #0c1220; color: #ff7a45; display: grid; place-items: center; }
|
|
44
|
+
h1 { font: 8vmin system-ui; }
|
|
45
|
+
</style>
|
|
46
|
+
<fy-composition>
|
|
47
|
+
<h1 data-fy-text="title"></h1>
|
|
48
|
+
</fy-composition>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
A static composition has no frame clock: no `data-fy-anim-*`, no `<video>`,
|
|
52
|
+
`<audio>`, or `<fy-transcript>`. Its controller runs `mount()` only.
|
|
53
|
+
|
|
54
|
+
## Fonts
|
|
55
|
+
|
|
56
|
+
Fontsource is the default catalog. Search, then install a pinned family:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npx frameyard font search "space grotesk" --subset latin --json
|
|
60
|
+
npx frameyard font add space-grotesk --subset latin
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Variable families install the `wght` face by default. Use `--static --weight
|
|
64
|
+
400,700` for static files. A local or brand font uses `font add --file`; an
|
|
65
|
+
explicit HTTPS source uses `font add --url` and may pass `--sha256`. A URL
|
|
66
|
+
import without `--license` or `--license-file` is recorded as unknown and
|
|
67
|
+
requires `full`.
|
|
68
|
+
|
|
69
|
+
Then use the family name normally:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
h1 { font-family: "Space Grotesk", system-ui, sans-serif; font-weight: 700; }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The server injects `@font-face` before author styles, uses `font-display: block`,
|
|
76
|
+
and the host waits for `document.fonts.ready`. Run `font verify` after moving
|
|
77
|
+
files. Remote font URLs produce `FONT_REMOTE`; a used face that fails to load
|
|
78
|
+
produces `FONT_LOAD_FAILED`. See [fonts](fonts.md).
|
|
79
|
+
|
|
80
|
+
## Timeline
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"version": 1,
|
|
85
|
+
"tracks": [
|
|
86
|
+
{ "id": "v1", "kind": "video" },
|
|
87
|
+
{ "id": "a1", "kind": "audio" }
|
|
88
|
+
],
|
|
89
|
+
"clips": [
|
|
90
|
+
{
|
|
91
|
+
"id": "intro",
|
|
92
|
+
"track": "v1",
|
|
93
|
+
"source": { "kind": "composition", "id": "intro" },
|
|
94
|
+
"start": 0
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "music",
|
|
98
|
+
"track": "a1",
|
|
99
|
+
"source": { "kind": "audio", "src": "assets/music.wav" },
|
|
100
|
+
"start": 0,
|
|
101
|
+
"duration": 150,
|
|
102
|
+
"audio": { "gain": 0.6 }
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`start`, `sourceIn`, and `duration` are timeline frames. Image clips and static
|
|
109
|
+
composition clips require an explicit `duration`; a static composition clip
|
|
110
|
+
needs `sourceIn: 0` and contributes no audio. Source kinds are `composition`,
|
|
111
|
+
`timeline`, `video`, `image`, and `audio`.
|
|
112
|
+
|
|
113
|
+
## Bindings
|
|
114
|
+
|
|
115
|
+
- `data-fy-text="path"` sets text.
|
|
116
|
+
- `data-fy-attr-src="path"` sets an attribute.
|
|
117
|
+
- `data-fy-class-active="path"` toggles a class.
|
|
118
|
+
- `data-fy-if="path"` removes the element when false.
|
|
119
|
+
- `<template data-fy-repeat="items">` clones content; use `item` and `index`.
|
|
120
|
+
- Paths are dot-separated; missing paths warn and resolve empty.
|
|
121
|
+
|
|
122
|
+
Bindings apply once per load, before `mount()`. Controller-created DOM is not
|
|
123
|
+
re-bound.
|
|
124
|
+
|
|
125
|
+
## Controller
|
|
126
|
+
|
|
127
|
+
```html
|
|
128
|
+
<script type="text/fy-controller">
|
|
129
|
+
export default {
|
|
130
|
+
mount({ root, props }) {},
|
|
131
|
+
update({ frame, props }) {},
|
|
132
|
+
dispose() {},
|
|
133
|
+
};
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Exactly one controller is allowed, inline or external. `mount` may be async;
|
|
138
|
+
`update` is synchronous and seek-safe. The controller is the only executable
|
|
139
|
+
code. Use `random("stable:key")` from `frameyard/runtime`, never `Math.random()`.
|
|
140
|
+
|
|
141
|
+
## Commands
|
|
142
|
+
|
|
143
|
+
| Command | Use |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `npx frameyard context --json` | One compact project digest: format, props, timelines, assets, policy, next commands |
|
|
146
|
+
| `npx frameyard lint <id> --json` | Fast edit loop: static validation, scene/keyframe projection, controller determinism |
|
|
147
|
+
| `npx frameyard check <id> --json` | Full gate: props hooks and every animated frame through Chromium |
|
|
148
|
+
| `npx frameyard still <id> --frame <spec> --json` | Inspect representative frames; policy caps the count |
|
|
149
|
+
| `npx frameyard inspect <image> --json` | Blank, alpha, colors, content bbox, luminance grid, edge density |
|
|
150
|
+
| `npx frameyard compare <a> <b> --json` | Pixel and 3×3 region diff; exits 1 when different |
|
|
151
|
+
| `npx frameyard render <id> --dry-run --json` | Handoff plan without Chromium, FFmpeg, or a model |
|
|
152
|
+
| `npx frameyard render <id> --json` | Produce the video when the policy allows it |
|
|
153
|
+
| `npx frameyard add --list --json` | List project templates |
|
|
154
|
+
| `npx frameyard add composition <id> --template <name> --json` | Write a valid fragment and print the config snippet |
|
|
155
|
+
| `npx frameyard font search <query> --json` | Search Fontsource by name, category, subset, style, or variable |
|
|
156
|
+
| `npx frameyard font add <fontsource-id> --json` | Pin and vendor a Fontsource family; variable `wght` by default |
|
|
157
|
+
| `npx frameyard font add <fontsource-id> --static --weight 400,700 --json` | Install explicit static weights |
|
|
158
|
+
| `npx frameyard font add --file <path> --family <name> --json` | Vendor a local font into `assets/fonts/` and update `fy.fonts.json` |
|
|
159
|
+
| `npx frameyard font add --url <https-url> --family <name> --json` | Download and vendor an HTTPS font, optionally with `--sha256` |
|
|
160
|
+
| `npx frameyard font list --json` | Installed families, subsets, licenses, files, and status |
|
|
161
|
+
| `npx frameyard font verify --json` | Manifest paths, hashes, licenses, and orphaned directories |
|
|
162
|
+
| `npx frameyard font remove <id> --json` | Remove a font and its directory; `--keep-files` keeps the binaries |
|
|
163
|
+
| `npx frameyard doctor --json` | Environment, integrations, policy, fonts, and resources |
|
|
164
|
+
|
|
165
|
+
Every non-interactive command accepts `--json`. Parse stdout; read stderr for
|
|
166
|
+
progress and diagnostics.
|
|
167
|
+
|
|
168
|
+
## Resource policy
|
|
169
|
+
|
|
170
|
+
`fy.policy.json` is discovered from the working directory upward,
|
|
171
|
+
independent of `fy.config.js`.
|
|
172
|
+
|
|
173
|
+
| Tier | `still` | `check` | `tts`/`transcribe`/`render` | overwrite |
|
|
174
|
+
| --- | --- | --- | --- | --- |
|
|
175
|
+
| `light` | 1 frame | blocked | blocked | denied |
|
|
176
|
+
| `authoring` | 4 frames | low priority | blocked | denied |
|
|
177
|
+
| `full` | 32 frames | allowed | allowed | allowed |
|
|
178
|
+
|
|
179
|
+
`lint`, `context`, `doctor`, `add`, `inspect`, `compare`, and `--dry-run` are
|
|
180
|
+
available in every tier. `font search`, `font list`, and `font verify` are
|
|
181
|
+
available in every tier; `font add` requires `authoring` or `full`; `font
|
|
182
|
+
remove`, `font add --force`, and `font add --url` without
|
|
183
|
+
`--license`/`--license-file` require `full`. When a heavy command is blocked, run it with `--dry-run`, report `deferred: true`, and give the user
|
|
184
|
+
`commandLine`, `prerequisites`, and `blockers`.
|
|
185
|
+
|
|
186
|
+
## Ten diagnostics that matter
|
|
187
|
+
|
|
188
|
+
| Code | Fix |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `COMPOSITION_DOCUMENT_SHELL` | Remove `<!doctype>`, `<html>`, `<head>`, and `<body>`; compositions are fragments |
|
|
191
|
+
| `COMPOSITION_FORMAT_UNRESOLVED` | Declare `width`/`height` or add project `format` |
|
|
192
|
+
| `COMPOSITION_INVALID` | Check the single `<fy-composition>` root and metadata |
|
|
193
|
+
| `CONTROLLER_INVALID` | Exactly one controller; inline XOR `src`; at least one export |
|
|
194
|
+
| `SCRIPT_NOT_ALLOWED` | Move executable code into the controller |
|
|
195
|
+
| `BINDING_PATH_MISSING` | Fix the props path; use the diagnostic suggestion |
|
|
196
|
+
| `BINDING_INVALID` | Unknown `data-fy-*` attribute; use a documented binding |
|
|
197
|
+
| `TIMELINE_CLIP_OUT_OF_RANGE` | Keyframes cannot extend a clip; lengthen the clip or move the key |
|
|
198
|
+
| `TIMELINE_CLIP_OVERLAP` | Declare a transition or move the clip |
|
|
199
|
+
| `RESOURCE_POLICY_BLOCKED` | Use `--dry-run`, or ask the user for `FRAMEYARD_POLICY=full` |
|
|
200
|
+
|
|
201
|
+
Font diagnostics: `FONT_HASH_MISMATCH` means the vendored file changed; restore
|
|
202
|
+
it or reinstall with `font add --force`. `FONT_LOAD_FAILED` means a used face
|
|
203
|
+
did not load; check the manifest and `font verify`. `FONT_REMOTE` is a warning
|
|
204
|
+
that the composition still depends on a remote font URL.
|
|
205
|
+
|
|
206
|
+
## Do not
|
|
207
|
+
|
|
208
|
+
- Use `Date.now()`, `performance.now()`, timers, `requestAnimationFrame` loops, or CSS transitions as animation inputs.
|
|
209
|
+
- Call `play()`, `pause()`, or set `currentTime` on embedded media.
|
|
210
|
+
- Fetch remote data from `update()`; load during `mount()`.
|
|
211
|
+
- Put visual content outside `<fy-composition>`.
|
|
212
|
+
- Edit `fy.policy.json`, host configuration, or the skill source.
|
|
213
|
+
- Claim success when `check` fails, a still is missing, or a render did not run.
|
|
214
|
+
|
|
215
|
+
## Next
|
|
216
|
+
|
|
217
|
+
- Full authoring rules: [deterministic authoring](deterministic-authoring.md)
|
|
218
|
+
- Fonts: [fonts](fonts.md)
|
|
219
|
+
- Timelines and keyframes: [timelines](timelines.md)
|
|
220
|
+
- Error codes: [diagnostics](diagnostics.md)
|
|
221
|
+
- Result shapes: [JSON results](json-results.md)
|
|
222
|
+
- Recovery loops: [failure recovery](failure-recovery.md)
|
|
@@ -42,7 +42,13 @@ export default {
|
|
|
42
42
|
"sourceIn": 0,
|
|
43
43
|
"props": { "title": "Hello" },
|
|
44
44
|
"blend": "screen",
|
|
45
|
-
"transform": { "fit": "contain", "x": 0, "y": 0, "scale": 1, "rotation": 0
|
|
45
|
+
"transform": { "fit": "contain", "x": 0, "y": 0, "scale": 1, "rotation": 0 },
|
|
46
|
+
"keyframes": {
|
|
47
|
+
"opacity": [
|
|
48
|
+
{ "frame": 0, "value": 0 },
|
|
49
|
+
{ "frame": 20, "value": 1, "easing": "ease-out" }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
46
52
|
"audio": { "muted": false, "gain": 1 }
|
|
47
53
|
},
|
|
48
54
|
{
|
|
@@ -69,7 +75,7 @@ export default {
|
|
|
69
75
|
- `tracks` and `clips` are required. Track order is bottom-to-top for video and irrelevant for audio.
|
|
70
76
|
- `background` is an optional color; the final video composites over black.
|
|
71
77
|
- `durationInFrames` is optional. The duration is derived from the latest clip end unless declared; a declared duration may extend but not cut clips.
|
|
72
|
-
- Unknown fields are errors. Blend modes are static per clip; no
|
|
78
|
+
- Unknown fields are errors. Blend modes are static per clip; there are no animated blend modes, other effects, or track solo/visibility. Clip transitions are the one same-track overlap (see [Transitions](#transitions)).
|
|
73
79
|
|
|
74
80
|
## Sources
|
|
75
81
|
|
|
@@ -90,7 +96,7 @@ Media paths resolve from the project root. No remote URLs.
|
|
|
90
96
|
- `duration` is the number of timeline frames. It is optional for animated compositions, nested timelines, video, and audio; it defaults to the rest of the source. Images and static compositions require it.
|
|
91
97
|
- A clip that exceeds its source is an error. Static compositions have no intrinsic duration, so the clip duration is the only bound.
|
|
92
98
|
- A static composition clip is captured once per props variant as a transparent PNG and reused across segments. A clip whose source is static and whose `audio` settings are non-default receives an `AUDIO_WARNING` because there is no source audio.
|
|
93
|
-
- Clips on one track cannot overlap. Touching endpoints are allowed. Use separate tracks for overlays.
|
|
99
|
+
- Clips on one track cannot overlap unless a timeline-level `transitions` entry pairs them. Touching endpoints are allowed but are not a transition. Use separate tracks for overlays that are not transitions.
|
|
94
100
|
|
|
95
101
|
## Presentation
|
|
96
102
|
|
|
@@ -104,7 +110,90 @@ Visual clips accept a static transform:
|
|
|
104
110
|
- `opacity`: 0–1.
|
|
105
111
|
- `blend`: CSS `mix-blend-mode`, defaulting to `normal`. Supported values are `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, and `exclusion`. `hue`, `saturation`, `color`, and `luminosity` are rejected.
|
|
106
112
|
|
|
107
|
-
A blend mode composites the clip against everything below it in the stage, including the timeline background, and only where the clip's source alpha is non-zero. A composition or nested timeline that paints an opaque background blocks the lower layers; source alpha is what lets a blend reach them. A top-level timeline with no declared background renders over black; a nested or embedded timeline stays transparent. Blend modes are static; there are no
|
|
113
|
+
A blend mode composites the clip against everything below it in the stage, including the timeline background, and only where the clip's source alpha is non-zero. A composition or nested timeline that paints an opaque background blocks the lower layers; source alpha is what lets a blend reach them. A top-level timeline with no declared background renders over black; a nested or embedded timeline stays transparent. Blend modes are static; there are no animated blend modes.
|
|
114
|
+
|
|
115
|
+
Rotation keeps the full rotated bounds. Production expands the rotate filter's output to a fixed bound: static rotation uses its exact rotated bounding box, while keyed rotation uses the diagonal of the fitted box so every segment uses the same size. The filter fills the padding with transparent pixels, so rotated corners are visible instead of being cut off.
|
|
116
|
+
|
|
117
|
+
## Keyframes
|
|
118
|
+
|
|
119
|
+
Visual clips may animate `x`, `y`, `scale`, `rotation`, and `opacity` with a top-level `keyframes` object keyed by channel:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
"keyframes": {
|
|
123
|
+
"opacity": [
|
|
124
|
+
{ "frame": 0, "value": 0 },
|
|
125
|
+
{ "frame": 24, "value": 1, "easing": "ease-out" },
|
|
126
|
+
{ "frame": 120, "value": 1 },
|
|
127
|
+
{ "frame": 150, "value": 0, "easing": "ease-in" }
|
|
128
|
+
],
|
|
129
|
+
"x": [
|
|
130
|
+
{ "frame": 0, "value": -80, "easing": "ease-out" },
|
|
131
|
+
{ "frame": 24, "value": 0 }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `frame` is clip-local. `0` is the clip's start boundary and `duration` is its end boundary. Moving `start` moves the animation with the clip; `sourceIn` does not shift it.
|
|
137
|
+
- Keys are integers in `0..duration`, strictly increasing, with at least two per channel and a required `value`.
|
|
138
|
+
- A channel holds its first value before the first key and its last value after the last key. Keyframes never extend a clip's duration.
|
|
139
|
+
- `easing` applies to the interval after that key. It defaults to `linear`; the final key's easing has no effect.
|
|
140
|
+
- Supported easings are `linear`, `ease`, `ease-in`, `ease-out`, `ease-in-out`, `step-start`, `step-end`, `hold`, `cubic-bezier(x1, y1, x2, y2)`, and `steps(n, jump-start|jump-end|jump-none|jump-both)`.
|
|
141
|
+
- A channel cannot also be an explicitly declared scalar in `transform`. Defaults do not conflict; unkeyed channels keep their `transform` value.
|
|
142
|
+
- `opacity` values are `0..1` and `scale` values are `>= 0`. Interpolated opacity and scale are clamped after easing; `x`, `y`, and `rotation` may overshoot.
|
|
143
|
+
- `keyframes.scale` cannot be combined with a `transform.width`/`height` box.
|
|
144
|
+
- `keyframes` is only valid on visual clips. A static composition is captured once as an image and then animated like any other visual layer.
|
|
145
|
+
|
|
146
|
+
Preview evaluates keyframes on every seek with the exact CSS timing function. Production generates per-frame FFmpeg expressions from the same evaluator and uses an alpha mask for animated opacity. Floating-point easing in the filter graph approximates the CSS curve; parity remains at clip capture, not at the final encoded pixel. Fractional `x`/`y` values may differ by a subpixel between preview and production.
|
|
147
|
+
|
|
148
|
+
The read-only timeline preview draws a marker for each distinct keyframe frame on a clip. Hovering a marker lists the channels at that frame; clicking it seeks the playhead there.
|
|
149
|
+
|
|
150
|
+
## Transitions
|
|
151
|
+
|
|
152
|
+
A timeline may declare a `transitions` array. Each entry pairs two clips on one track across their overlap:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
"clips": [
|
|
156
|
+
{ "id": "alpha", "track": "main", "source": { "kind": "composition", "id": "alpha" }, "start": 0, "duration": 60 },
|
|
157
|
+
{ "id": "beta", "track": "main", "source": { "kind": "composition", "id": "beta" }, "start": 45, "duration": 60 }
|
|
158
|
+
],
|
|
159
|
+
"transitions": [
|
|
160
|
+
{ "from": "alpha", "to": "beta", "kind": "dissolve", "easing": "ease-in-out" }
|
|
161
|
+
]
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- `from` and `to` must be clip IDs on the same track. A transition never spans tracks.
|
|
165
|
+
- `kind` is required. `dissolve` is a visual fade on a video track and also crossfades the audio the two clips contribute. `crossfade` is an audio gain ramp on an audio track. Using the wrong kind for the track is an error.
|
|
166
|
+
- `easing` defaults to `linear` and accepts the same CSS timing functions as keyframes, including `hold` and `steps(...)`.
|
|
167
|
+
- There is no `duration`. The window is the overlap `to.start .. from.end`. Move `to.start` earlier to lengthen the transition; the outgoing clip's declared duration already covers its side, so no media handles are needed.
|
|
168
|
+
- Valid geometry is `from.start < to.start < from.end < to.end`. The incoming clip must start inside the outgoing clip and end after it. Reversed order, a contained clip, a touching or later start, and cross-track pairs are errors.
|
|
169
|
+
- A clip may be the `to` of one transition and the `from` of the next, so transitions chain. A clip may not participate twice on the same side, and the same pair may not be declared twice.
|
|
170
|
+
- Overlap without a declared transition is still `TIMELINE_CLIP_OVERLAP`. A third clip that overlaps either side is an error, so at most two clips are active on a track.
|
|
171
|
+
- During a dissolve the incoming clip composites directly above the outgoing clip with alpha `clipOpacity × eased progress`. Its static or keyframed `opacity` multiplies the transition. `blend` works normally because transition alpha is part of the source alpha.
|
|
172
|
+
- Both kinds use equal power: the outgoing clip ramps as `sqrt(1 - t)` and the incoming as `sqrt(t)`, after easing. For a dissolve this applies to contributed audio; for a crossfade it applies to the two audio clips. `audio.muted` still zeroes its side. Transitions on a nested-timeline clip fade that group's audio.
|
|
173
|
+
- Preview stacks the incoming visual layer above the outgoing layer, applies the same alpha, ramps `<audio>` volume during playback, and draws a read-only hatched band over the overlap on either track.
|
|
174
|
+
|
|
175
|
+
`check` exposes the resolved transition count and validates every window. `still` and `render` share the composite path, so a still captured inside a dissolve matches the rendered frame. A crossfade changes no pixels. See `examples/transitions`.
|
|
176
|
+
|
|
177
|
+
### Audio on the timeline
|
|
178
|
+
|
|
179
|
+
Audio can live directly on an audio track and still crossfade:
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
"tracks": [
|
|
183
|
+
{ "id": "v1", "kind": "video" },
|
|
184
|
+
{ "id": "a1", "kind": "audio" }
|
|
185
|
+
],
|
|
186
|
+
"clips": [
|
|
187
|
+
{ "id": "bed", "track": "v1", "source": { "kind": "composition", "id": "bed" }, "start": 0, "duration": 105 },
|
|
188
|
+
{ "id": "chord-a", "track": "a1", "source": { "kind": "audio", "src": "assets/a.wav" }, "start": 0, "duration": 60 },
|
|
189
|
+
{ "id": "chord-b", "track": "a1", "source": { "kind": "audio", "src": "assets/b.wav" }, "start": 45, "duration": 60 }
|
|
190
|
+
],
|
|
191
|
+
"transitions": [
|
|
192
|
+
{ "from": "chord-a", "to": "chord-b", "kind": "crossfade", "easing": "linear" }
|
|
193
|
+
]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Mute composition audio with `audio.muted: true` when the sound comes from audio-track clips, so it does not play twice. `examples/transitions` has an `audio-crossfade` timeline that pairs a visual dissolve with an audio crossfade over the same 15 frames.
|
|
108
197
|
|
|
109
198
|
## Audio
|
|
110
199
|
|
|
@@ -133,15 +222,16 @@ npx frameyard render main --props props/main.json --workers 8 --scratch /path/to
|
|
|
133
222
|
- `--container <mp4|mov|mkv|webm>` selects the delivery container (default `mp4`); `--codec <h264|hevc|vp9>` selects the codec, and `--encoder <auto|software|nvenc|qsv>` selects how it is encoded. MP4 and MOV hold H.264/HEVC with AAC; MKV holds all three; WebM holds VP9 with Opus and defaults to VP9. `auto` is the default and probes NVENC, then Quick Sync, then software; `software` forces `libx264`, `libx265`, or `libvpx-vp9`. FFV1 intermediates stay lossless and software-encoded, HEVC keeps the `hvc1` tag in MP4/MOV, and the output extension must match the container.
|
|
134
223
|
- `--concurrency` is composition-only.
|
|
135
224
|
- `render` defaults to `out/<timeline-id>.mp4`.
|
|
136
|
-
- `check` evaluates every timeline frame and every animated composition frame; a static composition layer is inspected once and not seeked per frame. `still` captures one or more requested frames through the same clip capture and composite path; a multi-frame still resolves the timeline, starts one server, and reuses one scratch directory. The `--frame` spec and 32-frame cap are the same as for compositions.
|
|
225
|
+
- `check` evaluates every timeline frame and every animated composition frame; a static composition layer is inspected once and not seeked per frame. It also evaluates every keyframe channel at every timeline frame. `still` captures one or more requested frames through the same clip capture and composite path; a multi-frame still resolves the timeline, starts one server, and reuses one scratch directory. The `--frame` spec and 32-frame cap are the same as for compositions.
|
|
137
226
|
- `npx frameyard dev` lists timelines in the preview; the timeline view has a program monitor, read-only tracks and clips, playhead, playback, audio controls, and diagnostics.
|
|
138
227
|
|
|
139
228
|
## Determinism and performance
|
|
140
229
|
|
|
141
|
-
- A timeline frame derives only from its active clips, their composition frames, props, assets, and the render plan.
|
|
230
|
+
- A timeline frame derives only from its active clips, their composition frames, keyframe values, props, assets, and the render plan.
|
|
142
231
|
- Composition frames are captured with Chromium; transparent compositions preserve alpha.
|
|
143
232
|
- Timeline production captures active composition and nested-timeline clips to lossless FFV1, composites segments with FFmpeg, concatenates the ordered segments, and mixes audio once.
|
|
144
|
-
-
|
|
233
|
+
- Keyframe and transition values are evaluated against absolute timeline frames, so segment boundaries do not change them. Position, scale, and rotation use per-frame filter expressions; animated opacity and incoming transition alpha use an alpha mask. Animated opacity, animated scale, and a dissolve add a mask/scale pass per affected layer.
|
|
234
|
+
- Preview composites in Chromium with CSS `mix-blend-mode` and CSS `opacity`; production composites with FFmpeg's `blend`, `maskedmerge`, and an alpha mask. CSS is the reference; small scaling, alpha, color, blend-math, and subpixel-position differences are expected. Easing curves in the filter graph approximate the CSS curve; parity is guaranteed at clip capture, not at final encoded pixels.
|
|
145
235
|
- Alpha-correct blending costs roughly 1.7× per blended 1080p layer in the production composite. Budget for it when a timeline stacks several blended layers.
|
|
146
236
|
- Final output bytes are not a reproducibility guarantee.
|
|
147
237
|
- Long timelines are segmented and bounded. The reference target and measured worst cases are documented in `docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|