frameyard 0.3.0 → 0.5.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/GLOSSARY.md +46 -2
  3. package/README.md +174 -106
  4. package/dist/bindings.d.ts +42 -0
  5. package/dist/bindings.d.ts.map +1 -0
  6. package/dist/bindings.js +144 -0
  7. package/dist/bindings.js.map +1 -0
  8. package/dist/cli.js +32 -21
  9. package/dist/cli.js.map +1 -1
  10. package/dist/composition/adapter.d.ts +30 -0
  11. package/dist/composition/adapter.d.ts.map +1 -0
  12. package/dist/composition/adapter.js +2 -0
  13. package/dist/composition/adapter.js.map +1 -0
  14. package/dist/composition/animation.d.ts +19 -0
  15. package/dist/composition/animation.d.ts.map +1 -0
  16. package/dist/composition/animation.js +55 -0
  17. package/dist/composition/animation.js.map +1 -0
  18. package/dist/composition/attributes.d.ts +7 -0
  19. package/dist/composition/attributes.d.ts.map +1 -0
  20. package/dist/composition/attributes.js +24 -0
  21. package/dist/composition/attributes.js.map +1 -0
  22. package/dist/composition/descriptor.d.ts +30 -0
  23. package/dist/composition/descriptor.d.ts.map +1 -0
  24. package/dist/composition/descriptor.js +188 -0
  25. package/dist/composition/descriptor.js.map +1 -0
  26. package/dist/composition/diagnostics.d.ts +12 -0
  27. package/dist/composition/diagnostics.d.ts.map +1 -0
  28. package/dist/composition/diagnostics.js +38 -0
  29. package/dist/composition/diagnostics.js.map +1 -0
  30. package/dist/composition/index.d.ts +12 -0
  31. package/dist/composition/index.d.ts.map +1 -0
  32. package/dist/composition/index.js +7 -0
  33. package/dist/composition/index.js.map +1 -0
  34. package/dist/composition/media.d.ts +27 -0
  35. package/dist/composition/media.d.ts.map +1 -0
  36. package/dist/composition/media.js +162 -0
  37. package/dist/composition/media.js.map +1 -0
  38. package/dist/composition/paths.d.ts +5 -0
  39. package/dist/composition/paths.d.ts.map +1 -0
  40. package/dist/composition/paths.js +16 -0
  41. package/dist/composition/paths.js.map +1 -0
  42. package/dist/composition/transcripts.d.ts +19 -0
  43. package/dist/composition/transcripts.d.ts.map +1 -0
  44. package/dist/composition/transcripts.js +57 -0
  45. package/dist/composition/transcripts.js.map +1 -0
  46. package/dist/composition/walk.d.ts +10 -0
  47. package/dist/composition/walk.d.ts.map +1 -0
  48. package/dist/composition/walk.js +32 -0
  49. package/dist/composition/walk.js.map +1 -0
  50. package/dist/contracts.d.ts +75 -47
  51. package/dist/contracts.d.ts.map +1 -1
  52. package/dist/contracts.js +14 -159
  53. package/dist/contracts.js.map +1 -1
  54. package/dist/errors.d.ts +15 -4
  55. package/dist/errors.d.ts.map +1 -1
  56. package/dist/errors.js +8 -0
  57. package/dist/errors.js.map +1 -1
  58. package/dist/html/document-adapter.d.ts +5 -0
  59. package/dist/html/document-adapter.d.ts.map +1 -0
  60. package/dist/html/document-adapter.js +17 -0
  61. package/dist/html/document-adapter.js.map +1 -0
  62. package/dist/html/index.d.ts +5 -0
  63. package/dist/html/index.d.ts.map +1 -0
  64. package/dist/html/index.js +3 -0
  65. package/dist/html/index.js.map +1 -0
  66. package/dist/html/parse.d.ts +26 -0
  67. package/dist/html/parse.d.ts.map +1 -0
  68. package/dist/html/parse.js +80 -0
  69. package/dist/html/parse.js.map +1 -0
  70. package/dist/html/validate.d.ts +14 -0
  71. package/dist/html/validate.d.ts.map +1 -0
  72. package/dist/html/validate.js +648 -0
  73. package/dist/html/validate.js.map +1 -0
  74. package/dist/index.d.ts +2 -2
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/preview/main.d.ts +10 -3
  79. package/dist/preview/main.d.ts.map +1 -1
  80. package/dist/preview/main.js +189 -89
  81. package/dist/preview/main.js.map +1 -1
  82. package/dist/project.d.ts +2 -0
  83. package/dist/project.d.ts.map +1 -1
  84. package/dist/project.js +4 -0
  85. package/dist/project.js.map +1 -1
  86. package/dist/renderer/browser.d.ts +11 -1
  87. package/dist/renderer/browser.d.ts.map +1 -1
  88. package/dist/renderer/browser.js +93 -14
  89. package/dist/renderer/browser.js.map +1 -1
  90. package/dist/renderer/index.d.ts +25 -5
  91. package/dist/renderer/index.d.ts.map +1 -1
  92. package/dist/renderer/index.js +82 -16
  93. package/dist/renderer/index.js.map +1 -1
  94. package/dist/runtime/bindings.d.ts +11 -0
  95. package/dist/runtime/bindings.d.ts.map +1 -0
  96. package/dist/runtime/bindings.js +164 -0
  97. package/dist/runtime/bindings.js.map +1 -0
  98. package/dist/runtime/diagnostics.d.ts +4 -0
  99. package/dist/runtime/diagnostics.d.ts.map +1 -0
  100. package/dist/runtime/diagnostics.js +107 -0
  101. package/dist/runtime/diagnostics.js.map +1 -0
  102. package/dist/runtime/document-adapter.d.ts +4 -0
  103. package/dist/runtime/document-adapter.d.ts.map +1 -0
  104. package/dist/runtime/document-adapter.js +19 -0
  105. package/dist/runtime/document-adapter.js.map +1 -0
  106. package/dist/runtime/host.d.ts +14 -0
  107. package/dist/runtime/host.d.ts.map +1 -0
  108. package/dist/runtime/host.js +596 -0
  109. package/dist/runtime/host.js.map +1 -0
  110. package/dist/runtime/index.d.ts +0 -29
  111. package/dist/runtime/index.d.ts.map +1 -1
  112. package/dist/runtime/index.js +0 -360
  113. package/dist/runtime/index.js.map +1 -1
  114. package/dist/runtime/media.d.ts.map +1 -1
  115. package/dist/runtime/media.js +20 -51
  116. package/dist/runtime/media.js.map +1 -1
  117. package/dist/scaffold.d.ts.map +1 -1
  118. package/dist/scaffold.js +177 -84
  119. package/dist/scaffold.js.map +1 -1
  120. package/dist/server/composition-html.d.ts +20 -0
  121. package/dist/server/composition-html.d.ts.map +1 -0
  122. package/dist/server/composition-html.js +45 -0
  123. package/dist/server/composition-html.js.map +1 -0
  124. package/dist/server/preview-html.d.ts.map +1 -1
  125. package/dist/server/preview-html.js +19 -14
  126. package/dist/server/preview-html.js.map +1 -1
  127. package/dist/server/server.d.ts.map +1 -1
  128. package/dist/server/server.js +94 -6
  129. package/dist/server/server.js.map +1 -1
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/docs/adr/0001-keep-composition-authoring-web-native.md +2 -0
  133. package/docs/adr/0006-seek-embedded-media-from-the-frame-clock.md +7 -1
  134. package/docs/adr/0007-make-html-documents-the-composition-unit.md +31 -0
  135. package/docs/adr/0008-share-the-composition-document-grammar.md +18 -0
  136. package/docs/adr/0009-composite-production-frames-in-software.md +37 -0
  137. package/docs/migration-0.4.md +279 -0
  138. package/docs/v0.1-design.md +78 -56
  139. package/examples/demo/README.md +8 -4
  140. package/examples/demo/compositions/demo.controller.js +120 -0
  141. package/examples/demo/compositions/demo.html +253 -0
  142. package/examples/demo/video.config.js +1 -1
  143. package/examples/video/README.md +61 -0
  144. package/examples/video/assets/clip.mp4 +0 -0
  145. package/examples/video/assets/clip.webm +0 -0
  146. package/examples/video/compositions/video-mp4.html +362 -0
  147. package/examples/video/compositions/video.html +362 -0
  148. package/examples/video/props/video.json +8 -0
  149. package/examples/video/scripts/make-clip.sh +109 -0
  150. package/examples/video/video.config.js +6 -0
  151. package/examples/webgl/README.md +30 -0
  152. package/examples/webgl/compositions/webgl.controller.js +200 -0
  153. package/examples/webgl/compositions/webgl.html +215 -0
  154. package/examples/webgl/props/webgl.json +6 -0
  155. package/examples/webgl/video.config.js +5 -0
  156. package/package.json +7 -3
  157. package/skills/frameyard/README.md +4 -0
  158. package/skills/frameyard/SKILL.md +40 -12
  159. package/skills/frameyard/references/audio-assets.md +25 -14
  160. package/skills/frameyard/references/bindings.md +73 -0
  161. package/skills/frameyard/references/common-mistakes.md +59 -0
  162. package/skills/frameyard/references/controllers.md +126 -0
  163. package/skills/frameyard/references/deterministic-authoring.md +143 -52
  164. package/skills/frameyard/references/diagnostics.md +90 -0
  165. package/skills/frameyard/references/failure-recovery.md +37 -14
  166. package/skills/frameyard/references/json-results.md +43 -9
  167. package/skills/frameyard/references/subtitles-karaoke.md +10 -18
  168. package/examples/demo/compositions/demo.js +0 -206
@@ -0,0 +1,362 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <script type="application/json" data-fy="props">
6
+ {
7
+ "kicker": "Frameyard / Clip Study 001",
8
+ "title": "Moving",
9
+ "titleAccent": "Image",
10
+ "subtitle": "A local clip, loaded once and seeked twice from the composition clock.",
11
+ "detail": "Detail / second pass",
12
+ "accent": "#ffc37a"
13
+ }
14
+ </script>
15
+ <style>
16
+ @keyframes fade-in {
17
+ from {
18
+ opacity: 0;
19
+ }
20
+ to {
21
+ opacity: 1;
22
+ }
23
+ }
24
+ @keyframes rise {
25
+ from {
26
+ opacity: 0;
27
+ transform: translateY(42px);
28
+ }
29
+ to {
30
+ opacity: 1;
31
+ transform: translateY(0);
32
+ }
33
+ }
34
+ @keyframes draw {
35
+ from {
36
+ opacity: 0;
37
+ transform: scaleX(0);
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: scaleX(1);
42
+ }
43
+ }
44
+ @keyframes outro-fade {
45
+ from {
46
+ opacity: 0;
47
+ }
48
+ to {
49
+ opacity: 1;
50
+ }
51
+ }
52
+ html,
53
+ body,
54
+ fy-composition {
55
+ background: #04080b;
56
+ }
57
+ #scene {
58
+ position: relative;
59
+ width: 100%;
60
+ height: 100%;
61
+ overflow: hidden;
62
+ isolation: isolate;
63
+ background: #04080b;
64
+ color: #eef4f1;
65
+ font-family: Inter, "Helvetica Neue", ui-sans-serif, system-ui, sans-serif;
66
+ }
67
+ #hero {
68
+ position: absolute;
69
+ inset: 0;
70
+ display: block;
71
+ width: 100%;
72
+ height: 100%;
73
+ object-fit: cover;
74
+ transform: scale(calc(1 + var(--fy-progress) * 0.035));
75
+ transform-origin: center;
76
+ }
77
+ #scrim {
78
+ position: absolute;
79
+ inset: 0;
80
+ background:
81
+ linear-gradient(
82
+ 90deg,
83
+ rgba(4, 8, 11, 0.94) 0%,
84
+ rgba(4, 8, 11, 0.8) 24%,
85
+ rgba(4, 8, 11, 0.24) 52%,
86
+ rgba(4, 8, 11, 0) 74%
87
+ ),
88
+ linear-gradient(0deg, rgba(4, 8, 11, 0.9) 0%, rgba(4, 8, 11, 0) 34%);
89
+ }
90
+ #vignette {
91
+ position: absolute;
92
+ inset: 0;
93
+ background: radial-gradient(ellipse at 62% 44%, transparent 42%, rgba(0, 0, 0, 0.3) 100%);
94
+ pointer-events: none;
95
+ }
96
+ #frame-border {
97
+ position: absolute;
98
+ inset: 36px;
99
+ border: 1px solid rgba(238, 244, 241, 0.16);
100
+ pointer-events: none;
101
+ }
102
+ #header {
103
+ position: absolute;
104
+ top: 80px;
105
+ left: 148px;
106
+ right: 148px;
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ gap: 32px;
111
+ animation: fade-in 1s linear both;
112
+ }
113
+ #kicker {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 15px;
117
+ font:
118
+ 600 14px/1 ui-monospace,
119
+ monospace;
120
+ letter-spacing: 0.24em;
121
+ text-transform: uppercase;
122
+ color: var(--fy-prop-accent);
123
+ }
124
+ #kicker::before {
125
+ content: "";
126
+ width: 8px;
127
+ height: 8px;
128
+ background: var(--fy-prop-accent);
129
+ box-shadow: 0 0 16px var(--fy-prop-accent);
130
+ }
131
+ #format {
132
+ font:
133
+ 500 12px/1 ui-monospace,
134
+ monospace;
135
+ letter-spacing: 0.16em;
136
+ text-transform: uppercase;
137
+ color: #8b999c;
138
+ }
139
+ #copy {
140
+ position: absolute;
141
+ top: 278px;
142
+ left: 148px;
143
+ width: 860px;
144
+ }
145
+ #title {
146
+ margin: 0;
147
+ font-size: 174px;
148
+ line-height: 0.84;
149
+ letter-spacing: -0.064em;
150
+ font-weight: 660;
151
+ }
152
+ #title span,
153
+ #title em {
154
+ display: block;
155
+ animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
156
+ }
157
+ #title em {
158
+ margin-top: 0.06em;
159
+ font-family: "Iowan Old Style", "Palatino Linotype", Georgia, ui-serif, serif;
160
+ font-style: italic;
161
+ font-weight: 400;
162
+ letter-spacing: -0.035em;
163
+ color: var(--fy-prop-accent);
164
+ }
165
+ #subtitle {
166
+ width: 720px;
167
+ margin: 38px 0 0;
168
+ font-size: 29px;
169
+ line-height: 1.5;
170
+ font-weight: 420;
171
+ color: #a6b3b6;
172
+ animation: rise 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
173
+ }
174
+ #footer {
175
+ position: absolute;
176
+ left: 148px;
177
+ right: 148px;
178
+ bottom: 70px;
179
+ contain: layout paint style;
180
+ animation: fade-in 1s linear both;
181
+ }
182
+ #rule {
183
+ position: relative;
184
+ height: 1px;
185
+ background: rgba(238, 244, 241, 0.16);
186
+ transform-origin: left center;
187
+ animation: draw 1s cubic-bezier(0.215, 0.61, 0.355, 1) both;
188
+ }
189
+ #progress {
190
+ position: absolute;
191
+ inset: 0 auto 0 0;
192
+ width: calc(var(--fy-progress) * 100%);
193
+ background: var(--fy-prop-accent);
194
+ box-shadow: 0 0 14px var(--fy-prop-accent);
195
+ }
196
+ #footer-row {
197
+ display: flex;
198
+ justify-content: space-between;
199
+ align-items: baseline;
200
+ gap: 24px;
201
+ margin-top: 18px;
202
+ font:
203
+ 500 12px/1 ui-monospace,
204
+ monospace;
205
+ letter-spacing: 0.14em;
206
+ color: #879598;
207
+ text-transform: uppercase;
208
+ }
209
+ #footer-row strong {
210
+ color: #edf5f2;
211
+ font-weight: 600;
212
+ }
213
+ #footer-row .accent {
214
+ color: var(--fy-prop-accent);
215
+ }
216
+ #frame-counter {
217
+ display: inline-block;
218
+ min-width: 3ch;
219
+ text-align: right;
220
+ }
221
+ #frame-counter::after {
222
+ counter-reset: fy-frame var(--fy-frame);
223
+ content: counter(fy-frame);
224
+ }
225
+ #detail {
226
+ position: absolute;
227
+ right: 148px;
228
+ bottom: 154px;
229
+ width: 440px;
230
+ }
231
+ #detail-bar {
232
+ display: flex;
233
+ justify-content: space-between;
234
+ align-items: baseline;
235
+ gap: 24px;
236
+ margin-bottom: 10px;
237
+ contain: layout paint style;
238
+ font:
239
+ 600 11px/1 ui-monospace,
240
+ monospace;
241
+ letter-spacing: 0.18em;
242
+ text-transform: uppercase;
243
+ }
244
+ #detail-bar > span:first-child {
245
+ color: var(--fy-prop-accent);
246
+ }
247
+ #detail-time {
248
+ color: #98a5a6;
249
+ font-weight: 500;
250
+ }
251
+ #detail-window {
252
+ position: relative;
253
+ width: 440px;
254
+ height: 248px;
255
+ overflow: hidden;
256
+ border: 1px solid rgba(255, 195, 122, 0.65);
257
+ background: #000;
258
+ box-shadow: 0 24px 90px rgba(0, 0, 0, 0.55);
259
+ }
260
+ #detail-clip {
261
+ display: block;
262
+ width: 100%;
263
+ height: 100%;
264
+ object-fit: cover;
265
+ object-position: 68% 50%;
266
+ transform: scale(1.3);
267
+ }
268
+ #detail-tint {
269
+ position: absolute;
270
+ inset: 0;
271
+ background: linear-gradient(0deg, rgba(4, 8, 11, 0.28) 0%, rgba(4, 8, 11, 0) 45%);
272
+ pointer-events: none;
273
+ }
274
+ #outro-fade {
275
+ position: absolute;
276
+ inset: 0;
277
+ background: #04080b;
278
+ pointer-events: none;
279
+ opacity: 0;
280
+ animation: outro-fade 1s linear both;
281
+ }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <fy-composition width="1920" height="1080" fps="30" duration-in-frames="360">
286
+ <div id="scene">
287
+ <video
288
+ id="hero"
289
+ src="assets/clip.webm"
290
+ data-fy-start="0"
291
+ data-fy-trim="0"
292
+ data-fy-duration="12"
293
+ data-fy-volume="0.85"
294
+ ></video>
295
+ <div id="scrim"></div>
296
+ <div id="vignette"></div>
297
+ <div id="frame-border"></div>
298
+
299
+ <header id="header" data-fy-anim-start="8" data-fy-anim-duration="28">
300
+ <div id="kicker" data-fy-text="kicker"></div>
301
+ <div id="format">Embedded video / VP9 + Opus / 30 FPS</div>
302
+ </header>
303
+
304
+ <div id="copy">
305
+ <h1 id="title">
306
+ <span
307
+ data-fy-text="title"
308
+ data-fy-anim-start="14"
309
+ data-fy-anim-duration="44"
310
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
311
+ ></span>
312
+ <em
313
+ data-fy-text="titleAccent"
314
+ data-fy-anim-start="26"
315
+ data-fy-anim-duration="44"
316
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
317
+ ></em>
318
+ </h1>
319
+ <p
320
+ id="subtitle"
321
+ data-fy-text="subtitle"
322
+ data-fy-anim-start="40"
323
+ data-fy-anim-duration="40"
324
+ data-fy-anim-easing="cubic-bezier(0.215, 0.61, 0.355, 1)"
325
+ ></p>
326
+ </div>
327
+
328
+ <div id="detail">
329
+ <div id="detail-bar">
330
+ <span data-fy-text="detail"></span>
331
+ <span id="detail-time">SRC 5.8–10.8 S</span>
332
+ </div>
333
+ <div id="detail-window">
334
+ <video
335
+ id="detail-clip"
336
+ src="assets/clip.webm"
337
+ data-fy-start="120"
338
+ data-fy-trim="5.8"
339
+ data-fy-duration="5"
340
+ data-fy-audio="off"
341
+ ></video>
342
+ <div id="detail-tint"></div>
343
+ </div>
344
+ </div>
345
+
346
+ <div id="footer" data-fy-anim-start="46" data-fy-anim-duration="30">
347
+ <div id="rule" data-fy-anim-start="52" data-fy-anim-duration="50">
348
+ <div id="progress"></div>
349
+ </div>
350
+ <div id="footer-row">
351
+ <span>Hero / SRC <strong>0.0–12.0 S</strong></span>
352
+ <span>Frame <strong id="frame-counter"></strong> / 359</span>
353
+ <span class="accent">Frame-seeked</span>
354
+ <span>12.00 S · 30 FPS</span>
355
+ </div>
356
+ </div>
357
+
358
+ <div id="outro-fade" data-fy-anim-start="329" data-fy-anim-duration="30"></div>
359
+ </div>
360
+ </fy-composition>
361
+ </body>
362
+ </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "kicker": "Frameyard / Clip Study 001",
3
+ "title": "Moving",
4
+ "titleAccent": "Image",
5
+ "subtitle": "A local clip, loaded once and seeked twice from the composition clock.",
6
+ "detail": "Detail / second pass",
7
+ "accent": "#ffc37a"
8
+ }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Regenerate examples/video/assets/clip.webm and clip.mp4.
4
+ #
5
+ # The visual is a procedural gradient field warped by two sinusoidal
6
+ # displacement maps. The audio is an additive sine pad with pink-noise air.
7
+ # Fixed seeds keep the generated grain stable across runs on the same FFmpeg.
8
+ #
9
+ # Requires FFmpeg with libvpx-vp9, libx264, Opus, and AAC support.
10
+ #
11
+ # Usage:
12
+ # examples/video/scripts/make-clip.sh
13
+ # examples/video/scripts/make-clip.sh --out-dir /tmp/frameyard-clip
14
+
15
+ set -euo pipefail
16
+
17
+ usage() {
18
+ cat <<'EOF'
19
+ Usage: make-clip.sh [--out-dir <directory>]
20
+
21
+ Writes clip.webm (VP9 + Opus) and clip.mp4 (H.264 + AAC) to
22
+ examples/video/assets by default.
23
+ EOF
24
+ }
25
+
26
+ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
27
+ out_dir="$(cd -- "$script_dir/.." && pwd)/assets"
28
+
29
+ while [ "$#" -gt 0 ]; do
30
+ case "$1" in
31
+ --out-dir)
32
+ if [ "$#" -lt 2 ]; then
33
+ echo "make-clip: --out-dir requires a directory." >&2
34
+ usage >&2
35
+ exit 2
36
+ fi
37
+ out_dir="$2"
38
+ shift 2
39
+ ;;
40
+ -h | --help)
41
+ usage
42
+ exit 0
43
+ ;;
44
+ *)
45
+ echo "make-clip: unknown argument: $1" >&2
46
+ usage >&2
47
+ exit 2
48
+ ;;
49
+ esac
50
+ done
51
+
52
+ for tool in ffmpeg ffprobe; do
53
+ if ! command -v "$tool" >/dev/null 2>&1; then
54
+ echo "make-clip: $tool is required on PATH." >&2
55
+ exit 1
56
+ fi
57
+ done
58
+
59
+ mkdir -p "$out_dir"
60
+ out_dir="$(cd -- "$out_dir" && pwd)"
61
+ work_dir="$(mktemp -d "$out_dir/.make-clip.XXXXXX")"
62
+ trap 'rm -rf "$work_dir"' EXIT
63
+
64
+ # 12 seconds of a slow circular gradient, warped by two radial sine/cosine
65
+ # displacement maps. Scaling up and cropping hides the displacement edges.
66
+ ffmpeg -hide_banner -loglevel error -y \
67
+ -f lavfi -i "gradients=s=1280x720:r=30:d=12:c0=0x071827:c1=0x0d3a3a:c2=0x2f8a7e:c3=0xffd7a0:c4=0x123a58:c5=0x8fe6c8:n=6:type=circular:speed=0.02:seed=9" \
68
+ -f lavfi -i "nullsrc=s=1280x720:r=30:d=12,geq=lum='128+72*sin(hypot(X-640,Y-360)/42-T*0.55)':cb=128:cr=128" \
69
+ -f lavfi -i "nullsrc=s=1280x720:r=30:d=12,geq=lum='128+68*cos(hypot(X-640,Y-360)/55+T*0.47)':cb=128:cr=128" \
70
+ -filter_complex "[0:v][1:v][2:v]displace=edge=smear,scale=1440:810,crop=1280:720,gblur=sigma=6,eq=contrast=1.26:brightness=0.015:saturation=1.06,noise=alls=4:allf=t+u:all_seed=7,vignette=PI/3.6,format=yuv420p[v]" \
71
+ -map "[v]" -t 12 -an \
72
+ -c:v libvpx-vp9 -crf 31 -b:v 0 -row-mt 1 -tile-columns 2 -cpu-used 2 -g 60 \
73
+ "$work_dir/clip-video.webm"
74
+
75
+ # A slow A-minor-ish pad with movement from chorus/echo and a filtered
76
+ # pink-noise bed. The clip fades in and out so the timeline edit is soft.
77
+ ffmpeg -hide_banner -loglevel error -y \
78
+ -f lavfi -i "aevalsrc=exprs='0.24*sin(2*PI*110*t)+0.12*sin(2*PI*164.81*t)+0.10*sin(2*PI*220*t)*(0.8+0.2*sin(2*PI*0.07*t))+0.06*sin(2*PI*261.63*t)+0.04*sin(2*PI*329.63*t)+0.018*sin(2*PI*440*t)':s=48000:d=12" \
79
+ -f lavfi -i "anoisesrc=color=pink:sample_rate=48000:duration=12:amplitude=0.08:seed=7" \
80
+ -filter_complex "[0:a]chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.3:2|1.3,aecho=0.8:0.6:280|520:0.25|0.16,lowpass=f=4200,volume=0.9[pad];[1:a]lowpass=f=700,volume=0.075[air];[pad][air]amix=inputs=2:duration=longest:normalize=0,afade=t=in:st=0:d=2,afade=t=out:st=9.4:d=2.6,alimiter=limit=0.9,pan=stereo|c0=c0|c1=c0[a]" \
81
+ -map "[a]" -t 12 -c:a libopus -b:a 96k \
82
+ "$work_dir/clip-audio.webm"
83
+
84
+ ffmpeg -hide_banner -loglevel error -y \
85
+ -i "$work_dir/clip-video.webm" \
86
+ -i "$work_dir/clip-audio.webm" \
87
+ -map 0:v:0 -map 1:a:0 -c copy \
88
+ "$work_dir/clip.webm"
89
+
90
+ # The MP4 variant is a transcode of the WebM source with the same timeline.
91
+ ffmpeg -hide_banner -loglevel error -y \
92
+ -i "$work_dir/clip.webm" \
93
+ -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p -g 60 \
94
+ -c:a aac -b:a 128k -movflags +faststart \
95
+ "$work_dir/clip.mp4"
96
+
97
+ for file in "$work_dir/clip.webm" "$work_dir/clip.mp4"; do
98
+ ffprobe -v error -select_streams v:0 \
99
+ -show_entries stream=codec_name,width,height,r_frame_rate \
100
+ -of default=noprint_wrappers=1 "$file" >/dev/null
101
+ ffprobe -v error -select_streams a:0 -show_entries stream=codec_name \
102
+ -of default=noprint_wrappers=1 "$file" >/dev/null
103
+ done
104
+
105
+ mv -f "$work_dir/clip.webm" "$out_dir/clip.webm"
106
+ mv -f "$work_dir/clip.mp4" "$out_dir/clip.mp4"
107
+
108
+ echo "Wrote $out_dir/clip.webm (VP9 + Opus)"
109
+ echo "Wrote $out_dir/clip.mp4 (H.264 + AAC)"
@@ -0,0 +1,6 @@
1
+ export default {
2
+ compositions: {
3
+ video: "./compositions/video.html",
4
+ "video-mp4": "./compositions/video-mp4.html",
5
+ },
6
+ };
@@ -0,0 +1,30 @@
1
+ # Frameyard WebGL example
2
+
3
+ A 12-second, 1920×1080 composition that renders a procedural light field with a
4
+ WebGL fragment shader and layers HTML/CSS typography over it:
5
+
6
+ - a WebGL 1 fullscreen triangle with one cached program and cached uniforms
7
+ - shader motion derived only from the composition frame
8
+ - props for the kicker, title, subtitle, and accent color
9
+ - frame-scheduled CSS typography and a frame-state progress bar
10
+ - controller-updated frame and time readouts
11
+
12
+ The composition lives in `compositions/webgl.html`; the external controller is
13
+ `compositions/webgl.controller.js`. Draw every frame from `update()` and do not
14
+ rely on the previous drawing buffer. Production sessions pin software WebGL so
15
+ the captured pixels do not depend on the host GPU.
16
+
17
+ From the repository root:
18
+
19
+ ```sh
20
+ pnpm build
21
+ node dist/cli.js check webgl --project examples/webgl
22
+ node dist/cli.js still webgl --frame 210 --project examples/webgl --out webgl-still.png
23
+ node dist/cli.js render webgl --project examples/webgl --props examples/webgl/props/webgl.json
24
+ ```
25
+
26
+ Start the interactive preview with:
27
+
28
+ ```sh
29
+ node dist/cli.js dev --project examples/webgl
30
+ ```