frameyard 0.2.0 → 0.3.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 (46) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/GLOSSARY.md +16 -0
  3. package/README.md +41 -12
  4. package/dist/cli.js +12 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/errors.d.ts +1 -1
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js.map +1 -1
  9. package/dist/preview/main.d.ts +2 -0
  10. package/dist/preview/main.d.ts.map +1 -1
  11. package/dist/preview/main.js +28 -3
  12. package/dist/preview/main.js.map +1 -1
  13. package/dist/renderer/audio.d.ts +5 -1
  14. package/dist/renderer/audio.d.ts.map +1 -1
  15. package/dist/renderer/audio.js +4 -0
  16. package/dist/renderer/audio.js.map +1 -1
  17. package/dist/renderer/browser.d.ts +1 -0
  18. package/dist/renderer/browser.d.ts.map +1 -1
  19. package/dist/renderer/browser.js +7 -1
  20. package/dist/renderer/browser.js.map +1 -1
  21. package/dist/renderer/index.d.ts +5 -0
  22. package/dist/renderer/index.d.ts.map +1 -1
  23. package/dist/renderer/index.js +141 -18
  24. package/dist/renderer/index.js.map +1 -1
  25. package/dist/runtime/index.d.ts +3 -0
  26. package/dist/runtime/index.d.ts.map +1 -1
  27. package/dist/runtime/index.js +82 -8
  28. package/dist/runtime/index.js.map +1 -1
  29. package/dist/runtime/media.d.ts +16 -0
  30. package/dist/runtime/media.d.ts.map +1 -0
  31. package/dist/runtime/media.js +307 -0
  32. package/dist/runtime/media.js.map +1 -0
  33. package/dist/scaffold.d.ts.map +1 -1
  34. package/dist/scaffold.js +6 -3
  35. package/dist/scaffold.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/docs/adr/0003-render-browser-frames-offline.md +2 -0
  39. package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +3 -0
  40. package/docs/adr/0006-seek-embedded-media-from-the-frame-clock.md +9 -0
  41. package/docs/v0.1-design.md +14 -10
  42. package/package.json +7 -3
  43. package/skills/frameyard/SKILL.md +6 -3
  44. package/skills/frameyard/references/deterministic-authoring.md +30 -2
  45. package/skills/frameyard/references/failure-recovery.md +3 -0
  46. package/skills/frameyard/references/json-results.md +2 -1
@@ -0,0 +1,9 @@
1
+ # Seek embedded media from the frame clock
2
+
3
+ Embedded video and WebGL are supported without giving compositions a second clock. A composition places local `<video>` elements in `mount()`, describes their timeline position with `data-frameyard-*` attributes, and the runtime pauses and seeks them to the frame-derived source time after every synchronous `update()`. The renderer and `check` await that media settle before capturing or advancing.
4
+
5
+ The browser remains the source of truth for video presentation, while production audio stays offline and deterministic: an unmuted video's audio track is inspected and mixed by FFmpeg alongside declarative audio clips. Video elements are silenced in the browser. Live streams, remote media, and sources that cannot be seeked are rejected because they cannot be reproduced from a frame number.
6
+
7
+ WebGL canvases are redrawn from the current frame and captured through the existing screenshot path. Production Chromium pins SwiftShader so WebGL pixels do not vary with the host GPU. The runtime waits two animation frames after a media seek to ensure the decoded video frame and WebGL drawing are composited before capture.
8
+
9
+ `update()` stays synchronous and seek-safe. Media preparation happens during loading, and only the settle step is asynchronous. This preserves the existing lifecycle contract while extending deterministic frame production to embedded video and WebGL.
@@ -47,7 +47,9 @@ The only sources of animation state are the integer frame number and immutable p
47
47
  - Assets, remote data, images, fonts, generated audio, and transcripts are resolved during an explicit loading phase before frame production.
48
48
  - Required network access is limited to the loading phase. Network requests are blocked during frame capture.
49
49
  - AI model inference is never part of `mount()`, `update()`, or frame production.
50
- - v0.1 supports DOM, CSS, SVG, and Canvas 2D that is redrawn from the current frame.
50
+ - v0.1 supports DOM, CSS, SVG, Canvas 2D, WebGL, and frame-seeked embedded video.
51
+ - Embedded video is loaded during the loading phase, paused, and seeked from the frame-derived source time before capture. Its source must be a local, seekable project file. An unmuted embedded video contributes its audio track to the offline mix unless the composition opts out.
52
+ - WebGL 1 and WebGL 2 canvases are redrawn from the current frame and captured through the normal screenshot path. Production sessions pin software WebGL so pixel output does not depend on the host GPU.
51
53
  - Composition code is trusted local developer code. Frameyard does not claim to render untrusted compositions safely.
52
54
 
53
55
  ### Reproducibility guarantee
@@ -74,15 +76,15 @@ Production rendering:
74
76
 
75
77
  1. Uses a pinned, Playwright-managed Chromium through the Chrome DevTools Protocol.
76
78
  2. Seeks explicitly to each requested frame.
77
- 3. Waits for composition readiness.
79
+ 3. Waits for composition readiness, embedded media decode, and browser paint.
78
80
  4. Captures the frame.
79
- 5. Pipes frames to FFmpeg for encoding and audio muxing.
81
+ 5. Pipes frames in timeline order to FFmpeg for encoding and audio muxing.
80
82
 
81
- The reference renderer is sequential and has no frame cache. Its design nevertheless permits future parallel and out-of-order rendering because each frame is independently seekable. Simple scenes target real-time preview, but v0.1 has no production rendering-speed guarantee; it reports progress, current frame, and elapsed time.
83
+ The renderer is sequential by default and has no frame cache. Optional `--concurrency <n>` rendering, with `n` from 1 to 16, opens up to `n` independent browser sessions, each loading the same composition and props from the same preview server. Workers seek and capture assigned frames, while a single ordered writer pipes them to FFmpeg with bounded lookahead. Parallelism therefore changes scheduling only: every frame remains derivable from its integer frame number and immutable props, and output order is unchanged. The default stays at one session for predictable resource use, and the upper bound limits accidental browser-process and memory growth. Simple scenes target real-time preview, but there is no production rendering-speed guarantee; the CLI reports progress, current frame, and elapsed time.
82
84
 
83
85
  ## 5. Audio and output
84
86
 
85
- Audio is a declarative timeline of clips. Each clip supports a source, start frame, source trim, and volume. Preview synchronizes this timeline in the browser; production mixes it offline with FFmpeg rather than treating live `<audio>` or Web Audio playback as the production source of truth.
87
+ Audio is a declarative timeline of clips. Each clip supports a source, start frame, source trim, and volume. Preview synchronizes this timeline in the browser; production mixes it offline with FFmpeg rather than treating live `<audio>` or Web Audio playback as the production source of truth. Unmuted embedded videos contribute their own audio tracks to the same mix, placed by the video's frame and trim attributes. Muting a video or setting `data-frameyard-audio="off"` keeps it visual-only.
86
88
 
87
89
  Generated speech behaves like any other audio clip. Frameyard does not automatically time-stretch it, change composition duration, or regenerate it to fit. `check` reports clip durations and overrun warnings so users and agents can revise text, timing, or composition deliberately.
88
90
 
@@ -96,7 +98,7 @@ Project and composition commands are:
96
98
 
97
99
  - `init`: scaffold a minimal project, including an agent guide (`AGENTS.md`) and a private `package.json` with pinned `dev`, `check`, `render`, and `still` scripts
98
100
  - `dev`: start the preview
99
- - `render <composition-id> --props props.json --out video.mp4`: render a composition
101
+ - `render <composition-id> --props props.json --out video.mp4`: render a composition, optionally with `--concurrency <n>` from 1 to 16
100
102
  - `check <composition-id>`: validate configuration, props, assets, audio, and transcript requirements, then mount the composition and evaluate every frame without capturing or encoding
101
103
  - `still <composition-id> --frame <n>`: capture one requested frame as PNG through the production rendering path
102
104
  - `doctor`: inspect core requirements and optional integrations without repairing or downloading anything
@@ -120,7 +122,7 @@ Frameyard ships an optional, host-neutral agent skill with a `SKILL.md`, support
120
122
  The skill:
121
123
 
122
124
  - Uses the normal project format, composition API, and CLI available to humans.
123
- - Does not introduce an agent-only runtime, MCP server, hosted control plane, or parallel rendering API.
125
+ - Does not introduce an agent-only runtime, MCP server, hosted control plane, or separate rendering API.
124
126
  - Triggers for Frameyard authoring and operation rather than claiming to be a general-purpose media-generation skill.
125
127
  - Uses Frameyard's audio wrappers instead of invoking `qwen-tts` or `whisper-cli` directly.
126
128
  - Includes references for deterministic authoring, audio assets, subtitle and karaoke recipes, JSON command results, and failure recovery.
@@ -198,10 +200,12 @@ Release documentation must identify the exact `qwentts.cpp` and `whisper.cpp` re
198
200
 
199
201
  Tests prioritize public behavior and end-to-end evidence over implementation-only unit coverage:
200
202
 
201
- - Sequential, individual, and random-order frame renders produce equivalent images.
203
+ - Sequential, individual, random-order, and parallel frame renders produce equivalent images.
202
204
  - Determinism checks compare captured pixels or stills; encoded MP4 bytes are not a reproducibility oracle.
203
205
  - Preview and renderer use the same runtime.
204
206
  - CSS, JavaScript, SVG, and Canvas animations seek correctly.
207
+ - Embedded video frames seek deterministically, including under parallel capture, and their audio appears in the offline mix.
208
+ - WebGL canvases produce deterministic stills and rendered frames under the pinned software renderer.
205
209
  - Asset and runtime failures produce nonzero CLI exits.
206
210
  - Rendered MP4 files have the expected dimensions, frame count, duration, and audio stream.
207
211
  - Golden-frame tests cover representative visual behavior.
@@ -224,9 +228,9 @@ These are scope boundaries, not permanent rejections:
224
228
  - React or other UI-framework adapters in the core
225
229
  - Public plugin APIs and a broad integration ecosystem
226
230
  - Dynamic composition metadata derived from props
227
- - Embedded video, WebGL, live streams, and other independently timed visual content
231
+ - Live streams, remote media, and other visual sources that cannot be deterministically seeked
228
232
  - Dynamic per-frame asset loading or AI inference
229
- - Parallel rendering and frame caches
233
+ - Frame caches
230
234
  - Additional final-video formats, general image-sequence output, and arbitrary FFmpeg argument passthrough
231
235
  - Built-in TypeScript transpilation and npm dependency optimization
232
236
  - Rendering untrusted compositions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frameyard",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Local-first, deterministic video composition framework for HTML, CSS, and JavaScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -65,12 +65,16 @@
65
65
  "build": "tsc -p tsconfig.json",
66
66
  "typecheck": "tsc -p tsconfig.json --noEmit",
67
67
  "typecheck:test": "tsc -p tsconfig.test.json",
68
+ "check:scripts": "node scripts/check-scripts.mjs",
68
69
  "lint": "oxlint",
69
70
  "format": "oxfmt",
70
71
  "format:check": "oxfmt --check",
72
+ "check:static": "pnpm check:scripts && pnpm lint && pnpm format:check && pnpm typecheck && pnpm typecheck:test",
73
+ "check": "pnpm check:static && pnpm test",
71
74
  "test": "pnpm build && vitest run",
72
- "test:unit": "vitest run test/contracts.test.ts test/docs.test.ts test/random.test.ts test/scaffold.test.ts test/transcript.test.ts",
75
+ "test:unit": "vitest run test/contracts.test.ts test/docs.test.ts test/random.test.ts test/release.test.ts test/scaffold.test.ts test/transcript.test.ts",
73
76
  "bench": "pnpm build && node scripts/bench-render.mjs",
74
- "set-version": "node scripts/set-version.mjs"
77
+ "set-version": "node scripts/set-version.mjs",
78
+ "verify-release": "node scripts/verify-release.mjs"
75
79
  }
76
80
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: frameyard
3
3
  description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML, CSS, and native JavaScript. Use when a user asks to build or operate a Frameyard project, produce parameterized video, add a local TTS or transcription asset through Frameyard's wrappers, or diagnose a Frameyard render. Do not use for general-purpose media generation outside a Frameyard project.
4
- frameyard-api-version: 0.2.0
4
+ frameyard-api-version: 0.3.0
5
5
  ---
6
6
 
7
7
  # Frameyard
@@ -17,12 +17,12 @@ Use Frameyard's public project format and CLI. Do not introduce an agent-only ru
17
17
 
18
18
  ## Workflow
19
19
 
20
- 1. **Author** the composition with HTML, CSS, SVG, Canvas 2D, and native JavaScript ES modules. Keep `width`, `height`, `fps`, and `durationInFrames` fixed. Read [deterministic authoring](references/deterministic-authoring.md).
20
+ 1. **Author** the composition with HTML, CSS, SVG, Canvas 2D, WebGL, embedded video, and native JavaScript ES modules. Keep `width`, `height`, `fps`, and `durationInFrames` fixed. Read [deterministic authoring](references/deterministic-authoring.md).
21
21
  2. **Provide props** as JSON. Validate domain-specific requirements with `validateProps()`.
22
22
  3. **Produce optional assets** with `npx frameyard tts` and `npx frameyard transcribe`, never with the upstream executables. Read [audio assets](references/audio-assets.md). Do not overwrite or regenerate existing generated assets without permission.
23
23
  4. **Validate** with `npx frameyard check <id> --json`. It evaluates every frame, checks audio and declared transcripts, and does not encode.
24
24
  5. **Inspect** representative frames with `npx frameyard still <id> --frame <n> --out <file.png> --json`. View the PNGs when the host supports image viewing. If visual inspection is impossible, say so explicitly.
25
- 6. **Render** with `npx frameyard render <id> --out <file.mp4> [--props <file>] --json`.
25
+ 6. **Render** with `npx frameyard render <id> --out <file.mp4> [--props <file>] [--concurrency <n>] --json`.
26
26
  7. **Report** the project path, composition entry, props, generated assets, provenance manifest, validation results, still paths and inspection status, final video path, and any failures.
27
27
 
28
28
  Never claim success when `check` fails, a still is missing, a render exits nonzero, or visual inspection was skipped. A partial or unpublished render is not a successful result.
@@ -32,6 +32,7 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
32
32
  - Use `--json` for every non-interactive command. Parse stdout as the structured result; treat stderr as progress and diagnostics.
33
33
  - Keep the current output files unless the user explicitly approves replacement. Use `--force` only with that approval.
34
34
  - Use `--project <dir>` when not running from the project root.
35
+ - Use `--concurrency <n>` only when faster local rendering is useful; the default is `1` and the maximum is `16`. It changes capture scheduling only, not composition behavior or output frame order.
35
36
  - Use `npx frameyard doctor --json` before reporting an environment problem. It inspects without repairing or downloading.
36
37
  - Read [JSON results](references/json-results.md) for result and error shapes, and [failure recovery](references/failure-recovery.md) for actionable error codes.
37
38
 
@@ -42,6 +43,8 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
42
43
  - Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
43
44
  - Finish remote data, fonts, and image loading during `mount()` or before the first frame. Network requests are blocked during frame capture.
44
45
  - Keep `update({ frame, props })` synchronous and seek-safe. Do not carry state between frames.
46
+ - Let the runtime seek embedded `<video>` elements. Do not play, pause, or set `currentTime` in `update()`; use the `data-frameyard-*` attributes described in [deterministic authoring](references/deterministic-authoring.md).
47
+ - Redraw WebGL canvases from the current frame. Production pins software WebGL; do not depend on GPU-specific results or the previous frame's drawing buffer.
45
48
  - Do not run model inference inside `mount()`, `update()`, or frame production.
46
49
 
47
50
  ## Subtitle and karaoke rule
@@ -73,20 +73,48 @@ import { random } from "frameyard/runtime";
73
73
  const x = random("star:17:x") * width;
74
74
  ```
75
75
 
76
- ## CSS and Canvas
76
+ ## Canvas and WebGL
77
77
 
78
78
  CSS `@keyframes` and the Web Animations API are paused and seeked by the runtime inside the composition container. They are valid animation drivers. CSS transitions are diagnosed as non-deterministic and should not be used.
79
79
 
80
- Canvas 2D must be redrawn from the current frame:
80
+ Canvas 2D and WebGL must be redrawn from the current frame:
81
81
 
82
82
  ```js
83
83
  update({ frame }) {
84
84
  context.clearRect(0, 0, canvas.width, canvas.height);
85
85
  context.fillStyle = frame % 2 === 0 ? "#101820" : "#203040";
86
86
  context.fillRect(0, 0, canvas.width, canvas.height);
87
+
88
+ gl.clearColor(frame % 2 === 0 ? 0.1 : 0.2, 0.1, 0.13, 1);
89
+ gl.clear(gl.COLOR_BUFFER_BIT);
87
90
  }
88
91
  ```
89
92
 
93
+ Do not preserve and accumulate WebGL drawing across frames unless that accumulation is itself deterministic. Production sessions use software WebGL so output does not depend on the host GPU.
94
+
95
+ ## Embedded video
96
+
97
+ Place local `<video>` elements in `mount()` and let the runtime seek them:
98
+
99
+ ```html
100
+ <video
101
+ src="assets/clip.webm"
102
+ data-frameyard-start="30"
103
+ data-frameyard-trim="0.5"
104
+ data-frameyard-duration="4"
105
+ data-frameyard-volume="0.8"
106
+ ></video>
107
+ ```
108
+
109
+ - `data-frameyard-start` is the composition frame where the source starts. Default `0`.
110
+ - `data-frameyard-trim` is seconds trimmed from the source start. Default `0`.
111
+ - `data-frameyard-duration` optionally limits how many source seconds are used.
112
+ - `data-frameyard-volume` sets embedded-audio volume from `0` to `1`. Default `1`.
113
+
114
+ Do not call `play()`, `pause()`, or set `currentTime` in `update()`. Create video elements and set the `data-frameyard-*` attributes in `mount()`. Videos must be in the composition container's light DOM; shadow roots are not scanned. The runtime pauses every video, loads the complete local file during the loading phase, and seeks it after each `update()` before capture. Video sources must be local, seekable project files; live streams, remote URLs, and videos added or changed during `update()` are not supported.
115
+
116
+ An unmuted video's audio is extracted and mixed into the output timeline. Set `muted` or `data-frameyard-audio="off"` for a silent visual asset. The same start, trim, duration, and volume attributes place its audio.
117
+
90
118
  ## Loading assets
91
119
 
92
120
  Remote data, images, fonts, and other assets must resolve during `mount()` or before the first frame. The production browser blocks network requests after loading begins. A fetch from `update()` is a bug.
@@ -16,6 +16,8 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
16
16
  | `PROPS_INVALID` | Props are not JSON or fail `validateProps()` | Fix the props file or validator. |
17
17
  | `FRAME_OUT_OF_RANGE` | Frame is outside `0..durationInFrames - 1` | Choose a valid frame. |
18
18
  | `AUDIO_INVALID` | Bad clip or unreadable/unsupported audio | Use a local project-relative file with a decodable audio stream. |
19
+ | `MEDIA_INVALID` | Embedded video has invalid attributes, is remote, or cannot be decoded | Use a local seekable video file and valid `data-frameyard-*` values. |
20
+ | `MEDIA_SEEK_FAILED` | Embedded video did not reach the requested frame time | Re-encode the source with a regular keyframe interval or remove the bad source. |
19
21
  | `ASSET_NOT_FOUND` | Required file is missing or unreadable | Restore the asset, model, or text file. |
20
22
  | `OUTPUT_EXISTS` | Output exists and replacement was not approved | Reuse it, choose another path, or pass `--overwrite`/`--force` with permission. |
21
23
  | `OUTPUT_FAILED` | An output or manifest could not be written atomically | Check directory permissions and free space; rerun. |
@@ -38,6 +40,7 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
38
40
  - Existing outputs are replaced only with `--overwrite` (renders/stills) or `--force` (generated assets).
39
41
  - Cancellation terminates the child inference process and cleans temporary work.
40
42
  - `check` validates every frame and declared transcripts without encoding.
43
+ - `check`, `still`, and `render` seek embedded videos and wait for decode before evaluating or capturing a frame.
41
44
  - `doctor` inspects but never repairs, downloads, or modifies the environment.
42
45
 
43
46
  ## When visual inspection is impossible
@@ -92,6 +92,7 @@ Use the error `code` for decisions, not message text. `command` is `null` only f
92
92
  "durationInFrames": 900,
93
93
  "durationSeconds": 30,
94
94
  "framesRendered": 900,
95
+ "concurrency": 1,
95
96
  "bytes": 1234567,
96
97
  "audio": { "clips": 1, "warnings": [] },
97
98
  "transcripts": { "clips": 1, "warnings": [] },
@@ -99,7 +100,7 @@ Use the error `code` for decisions, not message text. `command` is `null` only f
99
100
  }
100
101
  ```
101
102
 
102
- A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published.
103
+ A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published. `concurrency` is the number of browser sessions actually used; it is capped by `durationInFrames` and by the CLI maximum of 16.
103
104
 
104
105
  ## tts
105
106