frameyard 0.4.0 → 0.6.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 (175) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/GLOSSARY.md +156 -24
  3. package/README.md +103 -31
  4. package/dist/cli.js +112 -19
  5. package/dist/cli.js.map +1 -1
  6. package/dist/composition/adapter.d.ts +30 -0
  7. package/dist/composition/adapter.d.ts.map +1 -0
  8. package/dist/composition/adapter.js +2 -0
  9. package/dist/composition/adapter.js.map +1 -0
  10. package/dist/composition/animation.d.ts +19 -0
  11. package/dist/composition/animation.d.ts.map +1 -0
  12. package/dist/composition/animation.js +55 -0
  13. package/dist/composition/animation.js.map +1 -0
  14. package/dist/composition/attributes.d.ts +7 -0
  15. package/dist/composition/attributes.d.ts.map +1 -0
  16. package/dist/composition/attributes.js +24 -0
  17. package/dist/composition/attributes.js.map +1 -0
  18. package/dist/composition/descriptor.d.ts +30 -0
  19. package/dist/composition/descriptor.d.ts.map +1 -0
  20. package/dist/composition/descriptor.js +188 -0
  21. package/dist/composition/descriptor.js.map +1 -0
  22. package/dist/composition/diagnostics.d.ts +12 -0
  23. package/dist/composition/diagnostics.d.ts.map +1 -0
  24. package/dist/composition/diagnostics.js +38 -0
  25. package/dist/composition/diagnostics.js.map +1 -0
  26. package/dist/composition/index.d.ts +12 -0
  27. package/dist/composition/index.d.ts.map +1 -0
  28. package/dist/composition/index.js +7 -0
  29. package/dist/composition/index.js.map +1 -0
  30. package/dist/composition/media.d.ts +27 -0
  31. package/dist/composition/media.d.ts.map +1 -0
  32. package/dist/composition/media.js +162 -0
  33. package/dist/composition/media.js.map +1 -0
  34. package/dist/composition/paths.d.ts +5 -0
  35. package/dist/composition/paths.d.ts.map +1 -0
  36. package/dist/composition/paths.js +16 -0
  37. package/dist/composition/paths.js.map +1 -0
  38. package/dist/composition/transcripts.d.ts +19 -0
  39. package/dist/composition/transcripts.d.ts.map +1 -0
  40. package/dist/composition/transcripts.js +57 -0
  41. package/dist/composition/transcripts.js.map +1 -0
  42. package/dist/composition/walk.d.ts +10 -0
  43. package/dist/composition/walk.d.ts.map +1 -0
  44. package/dist/composition/walk.js +32 -0
  45. package/dist/composition/walk.js.map +1 -0
  46. package/dist/contracts.d.ts +14 -0
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js.map +1 -1
  49. package/dist/errors.d.ts +1 -1
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js.map +1 -1
  52. package/dist/html/document-adapter.d.ts +5 -0
  53. package/dist/html/document-adapter.d.ts.map +1 -0
  54. package/dist/html/document-adapter.js +17 -0
  55. package/dist/html/document-adapter.js.map +1 -0
  56. package/dist/html/parse.d.ts +2 -6
  57. package/dist/html/parse.d.ts.map +1 -1
  58. package/dist/html/parse.js.map +1 -1
  59. package/dist/html/validate.d.ts.map +1 -1
  60. package/dist/html/validate.js +92 -375
  61. package/dist/html/validate.js.map +1 -1
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/preview/main.d.ts.map +1 -1
  67. package/dist/preview/main.js +93 -13
  68. package/dist/preview/main.js.map +1 -1
  69. package/dist/preview/timeline-data.d.ts +85 -0
  70. package/dist/preview/timeline-data.d.ts.map +1 -0
  71. package/dist/preview/timeline-data.js +2 -0
  72. package/dist/preview/timeline-data.js.map +1 -0
  73. package/dist/preview/timeline-host.d.ts +19 -0
  74. package/dist/preview/timeline-host.d.ts.map +1 -0
  75. package/dist/preview/timeline-host.js +297 -0
  76. package/dist/preview/timeline-host.js.map +1 -0
  77. package/dist/preview/timeline-main.d.ts +17 -0
  78. package/dist/preview/timeline-main.d.ts.map +1 -0
  79. package/dist/preview/timeline-main.js +364 -0
  80. package/dist/preview/timeline-main.js.map +1 -0
  81. package/dist/project.d.ts +16 -1
  82. package/dist/project.d.ts.map +1 -1
  83. package/dist/project.js +81 -26
  84. package/dist/project.js.map +1 -1
  85. package/dist/renderer/browser.d.ts +2 -0
  86. package/dist/renderer/browser.d.ts.map +1 -1
  87. package/dist/renderer/browser.js +38 -6
  88. package/dist/renderer/browser.js.map +1 -1
  89. package/dist/renderer/ffmpeg.d.ts +61 -0
  90. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  91. package/dist/renderer/ffmpeg.js +198 -0
  92. package/dist/renderer/ffmpeg.js.map +1 -1
  93. package/dist/renderer/index.d.ts.map +1 -1
  94. package/dist/renderer/index.js +18 -7
  95. package/dist/renderer/index.js.map +1 -1
  96. package/dist/renderer/timeline.d.ts +109 -0
  97. package/dist/renderer/timeline.d.ts.map +1 -0
  98. package/dist/renderer/timeline.js +984 -0
  99. package/dist/renderer/timeline.js.map +1 -0
  100. package/dist/runtime/document-adapter.d.ts +4 -0
  101. package/dist/runtime/document-adapter.d.ts.map +1 -0
  102. package/dist/runtime/document-adapter.js +19 -0
  103. package/dist/runtime/document-adapter.js.map +1 -0
  104. package/dist/runtime/host.d.ts +2 -0
  105. package/dist/runtime/host.d.ts.map +1 -1
  106. package/dist/runtime/host.js +78 -183
  107. package/dist/runtime/host.js.map +1 -1
  108. package/dist/runtime/media.d.ts.map +1 -1
  109. package/dist/runtime/media.js +19 -50
  110. package/dist/runtime/media.js.map +1 -1
  111. package/dist/scaffold.d.ts.map +1 -1
  112. package/dist/scaffold.js +93 -23
  113. package/dist/scaffold.js.map +1 -1
  114. package/dist/server/preview-html.d.ts.map +1 -1
  115. package/dist/server/preview-html.js +15 -1
  116. package/dist/server/preview-html.js.map +1 -1
  117. package/dist/server/server.d.ts.map +1 -1
  118. package/dist/server/server.js +39 -1
  119. package/dist/server/server.js.map +1 -1
  120. package/dist/server/timeline-preview-data.d.ts +4 -0
  121. package/dist/server/timeline-preview-data.d.ts.map +1 -0
  122. package/dist/server/timeline-preview-data.js +97 -0
  123. package/dist/server/timeline-preview-data.js.map +1 -0
  124. package/dist/server/timeline-preview-html.d.ts +4 -0
  125. package/dist/server/timeline-preview-html.d.ts.map +1 -0
  126. package/dist/server/timeline-preview-html.js +227 -0
  127. package/dist/server/timeline-preview-html.js.map +1 -0
  128. package/dist/timeline/plan.d.ts +32 -0
  129. package/dist/timeline/plan.d.ts.map +1 -0
  130. package/dist/timeline/plan.js +68 -0
  131. package/dist/timeline/plan.js.map +1 -0
  132. package/dist/timeline/resolve.d.ts +60 -0
  133. package/dist/timeline/resolve.d.ts.map +1 -0
  134. package/dist/timeline/resolve.js +523 -0
  135. package/dist/timeline/resolve.js.map +1 -0
  136. package/dist/timeline/schema.d.ts +71 -0
  137. package/dist/timeline/schema.d.ts.map +1 -0
  138. package/dist/timeline/schema.js +425 -0
  139. package/dist/timeline/schema.js.map +1 -0
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +2 -0
  143. package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +2 -0
  144. package/docs/adr/0006-seek-embedded-media-from-the-frame-clock.md +4 -0
  145. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  146. package/docs/adr/0008-share-the-composition-document-grammar.md +18 -0
  147. package/docs/adr/0009-composite-production-frames-in-software.md +37 -0
  148. package/docs/adr/0010-make-timelines-the-render-unit.md +24 -0
  149. package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +22 -0
  150. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +31 -0
  151. package/docs/v0.1-design.md +135 -85
  152. package/examples/demo/README.md +11 -4
  153. package/examples/demo/timelines/demo.timeline.json +12 -0
  154. package/examples/demo/video.config.js +4 -0
  155. package/examples/video/README.md +105 -0
  156. package/examples/video/assets/clip.mp4 +0 -0
  157. package/examples/video/assets/clip.webm +0 -0
  158. package/examples/video/compositions/video-mp4.html +362 -0
  159. package/examples/video/compositions/video-overlay.html +350 -0
  160. package/examples/video/compositions/video.html +362 -0
  161. package/examples/video/props/video-timeline.json +10 -0
  162. package/examples/video/props/video.json +8 -0
  163. package/examples/video/scripts/make-clip.sh +109 -0
  164. package/examples/video/timelines/video-timeline-mp4.timeline.json +44 -0
  165. package/examples/video/timelines/video-timeline.timeline.json +44 -0
  166. package/examples/video/video.config.js +12 -0
  167. package/examples/webgl/README.md +30 -0
  168. package/examples/webgl/compositions/webgl.controller.js +200 -0
  169. package/examples/webgl/compositions/webgl.html +215 -0
  170. package/examples/webgl/props/webgl.json +6 -0
  171. package/examples/webgl/video.config.js +5 -0
  172. package/package.json +4 -2
  173. package/skills/frameyard/SKILL.md +18 -13
  174. package/skills/frameyard/references/json-results.md +2 -2
  175. package/skills/frameyard/references/timelines.md +141 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frameyard",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Local-first, deterministic video composition framework for HTML, CSS, and JavaScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -66,11 +66,13 @@
66
66
  "build": "tsc -p tsconfig.json",
67
67
  "typecheck": "tsc -p tsconfig.json --noEmit",
68
68
  "typecheck:test": "tsc -p tsconfig.test.json",
69
+ "typecheck:browser": "tsc -p tsconfig.browser.json",
69
70
  "check:scripts": "node scripts/check-scripts.mjs",
70
71
  "lint": "oxlint",
71
72
  "format": "oxfmt",
72
73
  "format:check": "oxfmt --check",
73
- "check:static": "pnpm check:scripts && pnpm lint && pnpm format:check && pnpm typecheck && pnpm typecheck:test",
74
+ "check:static": "pnpm check:scripts && pnpm lint && pnpm format:check && pnpm typecheck && pnpm typecheck:test && pnpm typecheck:browser",
75
+ "compare-stills": "node scripts/compare-stills.mjs",
74
76
  "check": "pnpm check:static && pnpm test",
75
77
  "test": "pnpm build && vitest run",
76
78
  "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",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: frameyard
3
- description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML, CSS, and an optional JavaScript controller. 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.4.0
3
+ description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML compositions and JSON timelines. Use when a user asks to build or operate a Frameyard project, produce parameterized video, assemble clips into a timeline, 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.6.0
5
5
  ---
6
6
 
7
7
  # Frameyard
@@ -11,19 +11,20 @@ Use Frameyard's public project format and CLI. Do not introduce an agent-only ru
11
11
  ## First decisions
12
12
 
13
13
  1. Find the project: look for `video.config.js` from the working directory upward, or run `npx frameyard init <directory>` to scaffold one.
14
- 2. Read the composition entry points listed in `video.config.js`. They are `.html` documents; there is no directory scanning.
15
- 3. Read the composition HTML, its optional controller, and the props file before changing behavior.
14
+ 2. Read the `format`, `compositions`, and `timelines` maps in `video.config.js`. Entries are explicit `.html` and `.json` documents; there is no directory scanning.
15
+ 3. Read the timeline JSON, the referenced composition HTML, its optional controller, and the props files before changing behavior.
16
16
  4. If the task needs speech or transcription, confirm that inference is requested or explicitly approved before producing assets.
17
17
 
18
18
  ## Workflow
19
19
 
20
- 1. **Author** the composition as a full HTML document with one `<fy-composition>` root. Put metadata, default props, markup, CSS animation, bindings, audio, and transcripts in the document. Use an optional controller for Canvas 2D, WebGL, karaoke, or per-frame computation CSS cannot express. Read [deterministic authoring](references/deterministic-authoring.md), [bindings](references/bindings.md), and [controllers](references/controllers.md).
21
- 2. **Provide props** as JSON. Validate domain-specific requirements with a controller's `validateProps()`.
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
- 4. **Validate** with `npx frameyard check <id> --json`. Static validation runs before Chromium; then every frame is evaluated. Read the structured `diagnostics` array. Read [diagnostics](references/diagnostics.md) and [common mistakes](references/common-mistakes.md).
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>] [--concurrency <n>] --json`.
26
- 7. **Report** the project path, composition entry, controller, props, generated assets, provenance manifest, validation results, diagnostics, still paths and inspection status, final video path, and any failures.
20
+ 1. **Author compositions** as full HTML documents with one `<fy-composition>` root. Put metadata, default props, markup, CSS animation, bindings, audio, and transcripts in the document. Use an optional controller for Canvas 2D, WebGL, karaoke, or per-frame computation CSS cannot express. Read [deterministic authoring](references/deterministic-authoring.md), [bindings](references/bindings.md), and [controllers](references/controllers.md).
21
+ 2. **Assemble the timeline** in JSON: project format, video/audio tracks, composition, media, and nested-timeline clips, source ranges, static transforms, and audio. Read [timelines](references/timelines.md).
22
+ 3. **Provide props** as JSON. Composition props merge over defaults; timeline render props are a map keyed by composition clip ID. Validate domain-specific requirements with a controller's `validateProps()`.
23
+ 4. **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.
24
+ 5. **Validate** with `npx frameyard check <id> --json`, where `<id>` is a timeline or composition. Static validation runs before Chromium; then every frame is evaluated. Read the structured `diagnostics` array. Read [diagnostics](references/diagnostics.md) and [common mistakes](references/common-mistakes.md).
25
+ 6. **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.
26
+ 7. **Render** with `npx frameyard render <id> [--out <file.mp4>] [--props <file>] --json`. Composition renders accept `--concurrency <n>`; timeline renders accept `--workers <n>` and `--scratch <dir>`. The default output is `out/<id>.mp4` in the project root.
27
+ 8. **Report** the project path, timeline and composition entries, controller, props, generated assets, provenance manifest, validation results, diagnostics, still paths and inspection status, final video path, and any failures.
27
28
 
28
29
  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.
29
30
 
@@ -32,13 +33,16 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
32
33
  - Use `--json` for every non-interactive command. Parse stdout as the structured result; treat stderr as progress and diagnostics.
33
34
  - Keep the current output files unless the user explicitly approves replacement. Use `--force` only with that approval.
34
35
  - 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.
36
+ - `render` writes to `out/<id>.mp4` in the project root unless `--out` is passed. Explicit `--out` paths are relative to the current working directory.
37
+ - Use `--concurrency <n>` only for composition renders; the default is `1` and the maximum is `16`. Use `--workers <n>` for timeline renders; the default is `8`. Timeline renders also accept `--scratch <dir>` for FFV1 intermediates.
38
+ - For a timeline, `--props` is a JSON object keyed by top-level composition clip ID. For a composition, it is the props object.
36
39
  - Use `npx frameyard doctor --json` before reporting an environment problem. It inspects without repairing or downloading.
37
40
  - Read [JSON results](references/json-results.md) for result and error shapes, and [failure recovery](references/failure-recovery.md) for actionable error codes.
38
41
 
39
42
  ## Authoring rules
40
43
 
41
44
  - A composition is a full HTML document with one `<fy-composition width height fps duration-in-frames>` root and a `data-fy="props"` default-props block.
45
+ - A timeline is a versioned JSON document with explicit tracks and clips. Sources are composition IDs, nested timeline IDs, or local video/image/audio paths. Timeline frames, sourceIn, and duration are integer frames. No remote URLs.
42
46
  - Bindings are explicit: `data-fy-text`, `data-fy-attr-*`, `data-fy-class-*`, `data-fy-if`, and `data-fy-repeat` on `<template>`. Primitive top-level props are also `--fy-prop-<name>` CSS variables.
43
47
  - CSS `@keyframes` and the Web Animations API are seeked from the frame clock. `data-fy-anim-start`, `data-fy-anim-duration`, and `data-fy-anim-easing` schedule one animation per element in frames.
44
48
  - Frame-state variables on `:root`: `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames`.
@@ -49,7 +53,7 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
49
53
 
50
54
  ## Determinism guardrails
51
55
 
52
- - A frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production.
56
+ - A composition frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production. A timeline frame derives from its active clips and the same composition rules.
53
57
  - Use `random("stable:key")` from `frameyard/runtime`; never use `Math.random()`.
54
58
  - Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
55
59
  - Finish remote data, fonts, and image loading during the loading phase. Network requests are blocked during frame capture.
@@ -79,6 +83,7 @@ When the CLI version differs from `frameyard-api-version`, pin it with `npx fram
79
83
 
80
84
  ## References
81
85
 
86
+ - [Timelines](references/timelines.md)
82
87
  - [Deterministic HTML authoring](references/deterministic-authoring.md)
83
88
  - [Bindings](references/bindings.md)
84
89
  - [Controllers](references/controllers.md)
@@ -118,7 +118,7 @@ Static validation diagnostics include source locations. Runtime diagnostics may
118
118
  "composition": "hello",
119
119
  "entry": "compositions/hello.html",
120
120
  "controller": { "kind": "inline" },
121
- "path": "/absolute/path/hello.mp4",
121
+ "path": "/absolute/path/out/hello.mp4",
122
122
  "width": 1920,
123
123
  "height": 1080,
124
124
  "fps": 30,
@@ -134,7 +134,7 @@ Static validation diagnostics include source locations. Runtime diagnostics may
134
134
  }
135
135
  ```
136
136
 
137
- 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.
137
+ A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published. Without `--out`, the final path is `out/<composition-id>.mp4` inside the project root. `concurrency` is the number of browser sessions actually used; it is capped by `durationInFrames` and by the CLI maximum of 16.
138
138
 
139
139
  ## tts
140
140
 
@@ -0,0 +1,141 @@
1
+ # Timelines
2
+
3
+ A timeline is a project-local JSON document that assembles composition and media clips on tracks and renders as one video. Compositions remain reusable HTML sources; a composition clip is one placement of a composition with its own source range and props.
4
+
5
+ ## Project configuration
6
+
7
+ ```js
8
+ export default {
9
+ format: { width: 1920, height: 1080, fps: 30 },
10
+ compositions: {
11
+ intro: "./compositions/intro.html",
12
+ },
13
+ timelines: {
14
+ main: "./timelines/main.timeline.json",
15
+ },
16
+ };
17
+ ```
18
+
19
+ - `format` fixes width, height, and fps for every timeline. It is required when the project lists timelines.
20
+ - Composition and timeline IDs share one namespace and must be unique.
21
+ - A composition used in a timeline must match the project fps exactly. A different size is fitted by the clip.
22
+ - Composition-only projects remain valid without `format` or `timelines`.
23
+
24
+ ## Timeline document
25
+
26
+ ```json
27
+ {
28
+ "version": 1,
29
+ "name": "Main edit",
30
+ "tracks": [
31
+ { "id": "v1", "kind": "video" },
32
+ { "id": "v2", "kind": "video" },
33
+ { "id": "a1", "kind": "audio", "gain": 1, "muted": false }
34
+ ],
35
+ "clips": [
36
+ {
37
+ "id": "intro",
38
+ "track": "v1",
39
+ "source": { "kind": "composition", "id": "intro" },
40
+ "start": 0,
41
+ "duration": 150,
42
+ "sourceIn": 0,
43
+ "props": { "title": "Hello" },
44
+ "transform": { "fit": "contain", "x": 0, "y": 0, "scale": 1, "rotation": 0, "opacity": 1 },
45
+ "audio": { "muted": false, "gain": 1 }
46
+ },
47
+ {
48
+ "id": "logo",
49
+ "track": "v2",
50
+ "source": { "kind": "image", "src": "assets/logo.png" },
51
+ "start": 30,
52
+ "duration": 60,
53
+ "transform": { "fit": "contain", "width": 480, "height": 270, "x": 640, "y": -320, "rotation": 0, "opacity": 0.9 }
54
+ },
55
+ {
56
+ "id": "music",
57
+ "track": "a1",
58
+ "source": { "kind": "audio", "src": "assets/music.wav" },
59
+ "start": 0,
60
+ "duration": 210,
61
+ "audio": { "gain": 0.5 }
62
+ }
63
+ ]
64
+ }
65
+ ```
66
+
67
+ - `version` is `1`.
68
+ - `tracks` and `clips` are required. Track order is bottom-to-top for video and irrelevant for audio.
69
+ - `background` is an optional color; the final video composites over black.
70
+ - `durationInFrames` is optional. The duration is derived from the latest clip end unless declared; a declared duration may extend but not cut clips.
71
+ - Unknown fields are errors. No transitions, effects, keyframes, or track solo/visibility in 0.6.
72
+
73
+ ## Sources
74
+
75
+ | `source.kind` | Fields | Notes |
76
+ | --- | --- | --- |
77
+ | `composition` | `id` | Project composition ID. The clip may set `props`. |
78
+ | `timeline` | `id` | Nested timeline ID. Rendered as a group. |
79
+ | `video` | `src` | Local MP4/WebM, project-relative. |
80
+ | `image` | `src` | Local PNG/JPEG/WebP. `duration` is required. |
81
+ | `audio` | `src` | Local WAV/MP3/M4A/AAC or a video file used only for audio. |
82
+
83
+ Media paths resolve from the project root. No remote URLs.
84
+
85
+ ## Time and trims
86
+
87
+ - `start` is the timeline frame where the clip begins.
88
+ - `sourceIn` is the first source frame. It defaults to `0` and must be `0` for images.
89
+ - `duration` is the number of timeline frames. It is optional for compositions, nested timelines, video, and audio; it defaults to the rest of the source. Images require it.
90
+ - A clip that exceeds its source is an error.
91
+ - Clips on one track cannot overlap. Touching endpoints are allowed. Use separate tracks for overlays.
92
+
93
+ ## Presentation
94
+
95
+ Visual clips accept a static transform:
96
+
97
+ - `fit`: `contain`, `cover`, or `stretch`.
98
+ - `width`, `height`: optional clip box in output pixels. Both are required together and cannot be combined with `scale`. `fit` applies inside the box, and `x`/`y` offset the box center. Without them, the project frame is the box.
99
+ - `x`, `y`: pixel offsets from the centered fitted result.
100
+ - `scale`: multiplier after fitting to the project frame; an alternative to `width`/`height`.
101
+ - `rotation`: degrees around the center.
102
+ - `opacity`: 0–1.
103
+
104
+ No keyframes or animated transforms in 0.6.
105
+
106
+ ## Audio
107
+
108
+ - Composition, video, and nested-timeline clips contribute audio automatically unless `audio.muted` is true.
109
+ - Timeline audio clips live on audio tracks. Audio tracks support `gain` and `muted`.
110
+ - `audio.gain` on a clip is 0–1 and multiplies the source audio.
111
+ - There are no linked A/V pairs. For a J/L cut, mute the visual clip and add an audio clip with a different `sourceIn`.
112
+ - Audio is mixed once for the whole timeline and muxed into the final MP4.
113
+
114
+ ## Nested timelines
115
+
116
+ A nested timeline clip behaves as one layer: its internal tracks are composited first, then the parent clip's opacity and transform apply to the whole group. Nested audio is included. Cycles are rejected statically. Timeline clips do not override props of compositions inside a nested timeline in 0.6.
117
+
118
+ ## Commands
119
+
120
+ ```sh
121
+ npx frameyard check main --json
122
+ npx frameyard still main --frame 42 --out main-42.png --json
123
+ npx frameyard render main --props props/main.json --workers 8 --scratch /path/to/scratch --json
124
+ ```
125
+
126
+ - `--props` on a timeline is a JSON object keyed by top-level composition clip ID; values merge over the clip's `props`.
127
+ - `--workers` is 1–16. The default is 8.
128
+ - `--scratch` selects the FFV1 intermediate directory. Use a disk with enough space; intermediates are cleaned after the render.
129
+ - `--concurrency` is composition-only.
130
+ - `render` defaults to `out/<timeline-id>.mp4`.
131
+ - `check` evaluates every timeline frame and composition frame. `still` captures one frame through the same clip capture and composite path.
132
+ - `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.
133
+
134
+ ## Determinism and performance
135
+
136
+ - A timeline frame derives only from its active clips, their composition frames, props, assets, and the render plan.
137
+ - Composition frames are captured with Chromium; transparent compositions preserve alpha.
138
+ - Timeline production captures active composition and nested-timeline clips to lossless FFV1, composites segments with FFmpeg, concatenates the ordered segments, and mixes audio once.
139
+ - Preview composites in Chromium; production composites with FFmpeg. Small scaling, alpha, and video color differences are expected. Parity is guaranteed at clip capture, not at final encoded pixels.
140
+ - Final MP4 bytes are not a reproducibility guarantee.
141
+ - 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`.