frameyard 0.5.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.
- package/CHANGELOG.md +55 -0
- package/GLOSSARY.md +152 -24
- package/README.md +94 -29
- package/dist/cli.js +111 -14
- package/dist/cli.js.map +1 -1
- package/dist/contracts.d.ts +12 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/preview/main.js +89 -11
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +85 -0
- package/dist/preview/timeline-data.d.ts.map +1 -0
- package/dist/preview/timeline-data.js +2 -0
- package/dist/preview/timeline-data.js.map +1 -0
- package/dist/preview/timeline-host.d.ts +19 -0
- package/dist/preview/timeline-host.d.ts.map +1 -0
- package/dist/preview/timeline-host.js +297 -0
- package/dist/preview/timeline-host.js.map +1 -0
- package/dist/preview/timeline-main.d.ts +17 -0
- package/dist/preview/timeline-main.d.ts.map +1 -0
- package/dist/preview/timeline-main.js +364 -0
- package/dist/preview/timeline-main.js.map +1 -0
- package/dist/project.d.ts +16 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +81 -26
- package/dist/project.js.map +1 -1
- package/dist/renderer/browser.d.ts +2 -0
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +6 -1
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/ffmpeg.d.ts +61 -0
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +198 -0
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/timeline.d.ts +109 -0
- package/dist/renderer/timeline.d.ts.map +1 -0
- package/dist/renderer/timeline.js +984 -0
- package/dist/renderer/timeline.js.map +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +89 -24
- package/dist/scaffold.js.map +1 -1
- package/dist/server/preview-html.d.ts.map +1 -1
- package/dist/server/preview-html.js +15 -1
- package/dist/server/preview-html.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +39 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts +4 -0
- package/dist/server/timeline-preview-data.d.ts.map +1 -0
- package/dist/server/timeline-preview-data.js +97 -0
- package/dist/server/timeline-preview-data.js.map +1 -0
- package/dist/server/timeline-preview-html.d.ts +4 -0
- package/dist/server/timeline-preview-html.d.ts.map +1 -0
- package/dist/server/timeline-preview-html.js +227 -0
- package/dist/server/timeline-preview-html.js.map +1 -0
- package/dist/timeline/plan.d.ts +32 -0
- package/dist/timeline/plan.d.ts.map +1 -0
- package/dist/timeline/plan.js +68 -0
- package/dist/timeline/plan.js.map +1 -0
- package/dist/timeline/resolve.d.ts +60 -0
- package/dist/timeline/resolve.d.ts.map +1 -0
- package/dist/timeline/resolve.js +523 -0
- package/dist/timeline/resolve.js.map +1 -0
- package/dist/timeline/schema.d.ts +71 -0
- package/dist/timeline/schema.d.ts.map +1 -0
- package/dist/timeline/schema.js +425 -0
- package/dist/timeline/schema.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +2 -0
- package/docs/adr/0005-keep-parallel-rendering-opt-in-and-frame-ordered.md +2 -0
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0009-composite-production-frames-in-software.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +24 -0
- package/docs/adr/0011-compose-timeline-frames-in-a-live-browser-host.md +22 -0
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +31 -0
- package/docs/v0.1-design.md +135 -86
- package/examples/demo/README.md +10 -3
- package/examples/demo/timelines/demo.timeline.json +12 -0
- package/examples/demo/video.config.js +4 -0
- package/examples/video/README.md +50 -6
- package/examples/video/compositions/video-overlay.html +350 -0
- package/examples/video/props/video-timeline.json +10 -0
- package/examples/video/timelines/video-timeline-mp4.timeline.json +44 -0
- package/examples/video/timelines/video-timeline.timeline.json +44 -0
- package/examples/video/video.config.js +6 -0
- package/package.json +1 -1
- package/skills/frameyard/SKILL.md +17 -13
- package/skills/frameyard/references/timelines.md +141 -0
package/docs/v0.1-design.md
CHANGED
|
@@ -2,155 +2,196 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** Accepted
|
|
4
4
|
**Initial acceptance:** 2026-10-27
|
|
5
|
-
**Last design revision:** 2026-10-
|
|
5
|
+
**Last design revision:** 2026-10-31
|
|
6
6
|
|
|
7
|
-
This document records the decisions confirmed during design sessions. It is the living product and architecture baseline; later changes revise this document explicitly rather than silently broaden the scope. The original v0.1 baseline has been revised through 0.
|
|
7
|
+
This document records the decisions confirmed during design sessions. It is the living product and architecture baseline; later changes revise this document explicitly rather than silently broaden the scope. The original v0.1 baseline has been revised through 0.6. Architecture decisions that changed the baseline are recorded in `docs/adr/`.
|
|
8
8
|
|
|
9
9
|
## 1. Product contract
|
|
10
10
|
|
|
11
|
-
Frameyard is a local-first, open-source framework for frontend developers and creative coders who build
|
|
11
|
+
Frameyard is a local-first, open-source framework for frontend developers and creative coders who build parameterized videos with HTML, CSS, and JavaScript. It separates reusable, prop-parameterized compositions from the timelines that assemble compositions and media into one video. AI agents can perform the same workflows on a user's behalf through an official skill.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Its differentiator is web-native authoring combined with deterministic, frame-accurate output and an explicit timeline model—not a React-centric composition model or a feature-for-feature clone of an existing NLE.
|
|
14
|
+
|
|
15
|
+
The target use cases are product demos, social clips, animated diagrams, data visualizations, karaoke-style subtitles, personalized videos, and long-form edits up to roughly 60 minutes. Professional collaborative editing, cloud production, and broadcast workflows remain outside the target audience.
|
|
14
16
|
|
|
15
17
|
The core success criterion is an end-to-end example in which a developer can:
|
|
16
18
|
|
|
17
|
-
1. Create
|
|
18
|
-
2. Animate
|
|
19
|
+
1. Create reusable, 1920×1080 HTML compositions with fixed metadata.
|
|
20
|
+
2. Animate them with HTML, CSS, SVG, and, where needed, a seek-safe controller for Canvas 2D and WebGL.
|
|
19
21
|
3. Supply JSON props and synchronized audio.
|
|
20
|
-
4.
|
|
21
|
-
5.
|
|
22
|
+
4. Assemble compositions and local media into a timeline with tracks, clips, trims, layers, and nested timelines.
|
|
23
|
+
5. Preview and scrub the timeline interactively.
|
|
24
|
+
6. Render it reproducibly to MP4 without dropped frames or timing drift.
|
|
25
|
+
7. Render a 60-minute timeline with up to three active layers within the long-form performance target.
|
|
22
26
|
|
|
23
|
-
The agent-enabled success criterion additionally requires an agent using the official skill to create and preserve a valid source project, optionally produce local audio and transcript assets, validate and inspect the
|
|
27
|
+
The agent-enabled success criterion additionally requires an agent using the official skill to create and preserve a valid source project, optionally produce local audio and transcript assets, validate and inspect the timeline and its compositions, render it, and report all resulting artifact paths without concealing validation failures or skipped visual review.
|
|
24
28
|
|
|
25
29
|
## 2. Project and authoring model
|
|
26
30
|
|
|
31
|
+
### Project
|
|
32
|
+
|
|
27
33
|
- Projects use ordinary HTML, CSS, static assets, and native JavaScript controllers, either inline in a composition document or in external modules.
|
|
28
|
-
- `video.config.js` explicitly
|
|
29
|
-
-
|
|
34
|
+
- `video.config.js` explicitly maps project IDs to source files. There is no directory scanning. It has three optional maps:
|
|
35
|
+
- `format`: the project's fixed `width`, `height`, and `fps`.
|
|
36
|
+
- `compositions`: IDs mapped to `.html` composition documents.
|
|
37
|
+
- `timelines`: IDs mapped to JSON timeline documents.
|
|
38
|
+
- `format` is required when a project has timelines. Every timeline uses it. A composition used in a timeline must share the project frame rate exactly; a different size is fitted and transformed by the clip. A standalone composition render keeps using the composition's own metadata.
|
|
39
|
+
- Composition and timeline IDs share one namespace so `render <id>`, `check <id>`, and `still <id>` are unambiguous. The config rejects duplicates.
|
|
40
|
+
- A project may contain any number of compositions and timelines.
|
|
41
|
+
|
|
42
|
+
### Composition
|
|
43
|
+
|
|
30
44
|
- A composition is a full HTML document with one `<fy-composition>` root. The root declares fixed `width`, `height`, `fps`, and `duration-in-frames` metadata. Props cannot change this metadata.
|
|
31
|
-
- Default props are a JSON block, `<script type="application/json" data-fy="props">`. A
|
|
45
|
+
- Default props are a JSON block, `<script type="application/json" data-fy="props">`. A load's JSON props merge shallowly over the defaults, are validated as JSON, deep-frozen, and fixed for that load.
|
|
32
46
|
- Props bind into the document through explicit `data-fy-*` attributes: `data-fy-text`, `data-fy-attr-*`, `data-fy-class-*`, `data-fy-if`, and `data-fy-repeat`. Primitive top-level props are also exposed as `--fy-prop-<name>` CSS custom properties. Bindings apply once per load; props are immutable.
|
|
33
47
|
- CSS and the Web Animations API are the animation language. `data-fy-anim-start`, `data-fy-anim-duration`, and `data-fy-anim-easing` schedule one animation per element in composition frames.
|
|
34
|
-
- An optional composition controller is declared with `<script type="text/fy-controller">`. It is either inline source or an external module referenced by `src`; a composition has exactly one controller. It may export `mount
|
|
35
|
-
- Inline controllers are the default for small, single-use controllers. Extract to an external module when the controller grows, is reused, needs separate imports or tests, or drives Canvas 2D, WebGL, or karaoke.
|
|
36
|
-
- The controller is the only executable code in a composition document. Other executable `<script>` elements and inline `on*` handlers are rejected. Libraries are imported as ES modules from the controller.
|
|
48
|
+
- An optional composition controller is declared with `<script type="text/fy-controller">`. It is either inline source or an external module referenced by `src`; a composition has exactly one controller. It may export `mount`, `update`, `dispose`, and `validateProps`; at least one is required. The controller is the only executable code in the document.
|
|
37
49
|
- `fy` is the authoring namespace: `fy-composition`, `fy-transcript`, `data-fy-*`, `--fy-*`, and `text/fy-controller`. `frameyard` remains the product, package, CLI, config, manifest, server-route, import-specifier, and error-code namespace.
|
|
38
|
-
- User controllers are native JavaScript. The framework itself is authored in TypeScript and ships declarations, but 0.
|
|
50
|
+
- User controllers are native JavaScript. The framework itself is authored in TypeScript and ships declarations, but 0.6 does not transpile user TypeScript.
|
|
51
|
+
|
|
52
|
+
### Timeline
|
|
53
|
+
|
|
54
|
+
- A timeline is a versioned JSON document. It references compositions and nested timelines by project ID and media clips by project-relative path.
|
|
55
|
+
- A timeline has a project-fixed format, an ordered list of tracks, and a list of clips. It may declare a `background` color and an optional `durationInFrames`.
|
|
56
|
+
- Tracks are `video` or `audio`. Video tracks composite from bottom to top; audio tracks mix. Each clip belongs to exactly one track. Clips on the same track cannot overlap; clips on different tracks can. Touching endpoints are allowed. Transitions are deferred, so overlap within a track would have ambiguous precedence.
|
|
57
|
+
- A clip has an ID, a track, a source, a timeline start, a source in-point, a duration, and clip-specific presentation settings. Source in-point, duration, and timeline start are integer project frames.
|
|
58
|
+
- Clip sources are:
|
|
59
|
+
- a composition, identified by its project ID;
|
|
60
|
+
- a nested timeline, identified by its project ID;
|
|
61
|
+
- a local video, image, or audio asset.
|
|
62
|
+
- A composition clip fixes props for that instance and may trim the composition's source range. The same composition may appear multiple times with different props, source ranges, transforms, and audio settings.
|
|
63
|
+
- A nested timeline clip is a visual and audio source. It uses the project format, trims with the same source in-point and duration, contributes its audio, and is rendered as a group so parent opacity and transform apply to the whole nested result. Cycles are rejected statically. Timeline clips do not override props of compositions inside the nested timeline in 0.6.
|
|
64
|
+
- Media clips use local project files only. Video and audio durations, dimensions, and audio presence are probed before frame production. Images are static and require an explicit duration. A video or audio clip that exceeds its source range is an error; an omitted duration uses the remaining source.
|
|
65
|
+
- Visual clips support a static transform: fit policy (`contain`, `cover`, `stretch`), an optional explicit `width`/`height` box (both fields together, mutually exclusive with `scale`) or a `scale` multiplier, pixel offset, rotation, and opacity. `fit` applies inside the box; without one, the project frame is the box. No keyframes, blend modes, masks, filters, or other effects in 0.6.
|
|
66
|
+
- Visual clips contribute their audio to the timeline mix unless muted: composition audio, embedded video audio, and nested timeline audio. Audio-only clips live on audio tracks and may source audio or video files. Per-clip and per-audio-track gain and mute are supported. There are no linked A/V pairs; a J/L cut is expressed by muting a video clip and placing a separate audio clip with a different source in-point.
|
|
67
|
+
- A timeline's duration is derived from the latest clip end. An explicit `durationInFrames` may extend it but not cut clips. A timeline with no clips is invalid; an audio-only timeline is valid and renders black video with audio.
|
|
68
|
+
- A timeline background is transparent by default. The final video composites it over black; a nested timeline remains transparent so lower layers can show through. A composition that paints no background is transparent and may be used as an overlay.
|
|
69
|
+
- The preview provides a read-only timeline view: tracks, clips, playhead, program monitor, and diagnostics. It is not a visual editor.
|
|
39
70
|
|
|
40
71
|
## 3. Deterministic execution
|
|
41
72
|
|
|
42
|
-
The
|
|
73
|
+
The timeline frame is the master clock. A composition's frame is derived from the clip's timeline start, source in-point, and duration. The only sources of a composition's animation state remain the integer composition frame and its immutable props.
|
|
43
74
|
|
|
44
|
-
- The runtime owns the frame clock and deterministic randomness.
|
|
45
|
-
- CSS `@keyframes` and Web Animations API animations are paused and positioned from the shared frame clock. `data-fy-anim-*`
|
|
46
|
-
- The runtime sets `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames` on the composition root every seek.
|
|
75
|
+
- The runtime owns the frame clock and deterministic randomness. A composition controller imports `random("stable:key")` from `frameyard/runtime`.
|
|
76
|
+
- CSS `@keyframes` and Web Animations API animations are paused and positioned from the shared composition frame clock. `data-fy-anim-*` schedules one animation per element in composition frames; the runtime maps frames to milliseconds.
|
|
77
|
+
- The runtime sets `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames` on the composition root every seek.
|
|
47
78
|
- JavaScript animation runs in the controller's `update()`, which must be synchronous and seek-safe. It cannot depend on previously visited frames.
|
|
48
79
|
- Declarative bindings apply once per load, before the controller mounts. Controller-created DOM is the controller's responsibility.
|
|
49
80
|
- Independent `requestAnimationFrame` loops, timers, CSS transitions, wall-clock reads, and unseeded randomness are not supported animation drivers.
|
|
50
|
-
-
|
|
51
|
-
-
|
|
81
|
+
- Timeline clips use static transforms. Timeline-level animation, transitions, effects, and keyframes are not part of 0.6.
|
|
82
|
+
- Preview and production seek composition clips explicitly. The timeline compositor is deterministic given the captured clip frames, media frames, clip settings, and segment boundaries.
|
|
83
|
+
- Assets, remote data, images, fonts, generated audio, transcripts, and media are resolved before frame production. For a segmented timeline render, each segment's composition and nested-timeline clips are loaded and settled before that segment's frames are captured; no frame production depends on a new network request. Network is blocked during capture.
|
|
52
84
|
- AI model inference is never part of the loading phase, controller lifecycle, or frame production.
|
|
53
85
|
- The baseline supports DOM, CSS, SVG, Canvas 2D, WebGL, and frame-seeked embedded video. Canvas 2D and WebGL are drawn from the current frame by the controller.
|
|
54
|
-
- Embedded video and audio are declared
|
|
55
|
-
-
|
|
86
|
+
- Embedded video and audio inside compositions are declared with `data-fy-*` attributes. The runtime loads local files, pauses them, and seeks video from the frame-derived source time before capture. An unmuted video contributes its audio track to the timeline mix unless the composition opts out or the clip mutes it.
|
|
87
|
+
- Transparent compositions are captured with an alpha-preserving browser background. Without that step, a top-level Chromium capture paints a white canvas and overlays become white rectangles.
|
|
56
88
|
- WebGL 1 and WebGL 2 canvases are captured through the normal screenshot path. Production sessions pin software WebGL so pixel output does not depend on the host GPU.
|
|
57
89
|
- Compositions are trusted local developer code. Frameyard does not claim to render untrusted compositions safely.
|
|
58
90
|
|
|
59
91
|
### Reproducibility guarantee
|
|
60
92
|
|
|
61
|
-
For the same composition revision, props,
|
|
93
|
+
For the same timeline or composition revision, props, assets, segment plan, and frame under a pinned runtime/browser/FFmpeg environment, the captured composition frames and the deterministic timeline composite are identical. The final MP4 is not promised to be bit-identical: encoded bytes depend on the encoder and its runtime.
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
Preview and production share composition behavior and capture composition frames the same way, but the timeline preview composites layers in Chromium while production composites them with FFmpeg. Small differences in scaling, alpha, and video color conversion are expected and do not violate clip-capture parity.
|
|
64
96
|
|
|
65
97
|
## 4. Preview and production rendering
|
|
66
98
|
|
|
67
|
-
Preview and production execute the same composition
|
|
99
|
+
Preview and production execute the same composition documents and host runtime. They differ in which component drives the frame clock, which compositor combines timeline layers, and whether frames are captured.
|
|
68
100
|
|
|
69
101
|
The preview provides:
|
|
70
102
|
|
|
71
|
-
- Composition selection
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- Current frame and time display
|
|
75
|
-
- Audio controls
|
|
103
|
+
- Composition preview: composition selection, JSON props editing, play/pause, frame-accurate scrubbing, frame-by-frame stepping with buttons or arrow keys, one-second jumps with buttons or Shift+arrow keys, current frame and time, audio controls, and diagnostics.
|
|
104
|
+
- Timeline preview: timeline selection, a read-only track/clip view, playhead, program monitor, frame-by-frame stepping with buttons or arrow keys, one-second jumps with buttons or Shift+arrow keys, audio controls, and diagnostics.
|
|
105
|
+
- A bounded pool of active composition and nested-timeline clips. It loads clips around the playhead instead of preloading a whole long timeline.
|
|
76
106
|
|
|
77
|
-
It
|
|
107
|
+
The preview server serves each composition HTML document at its project path and injects the framework import map and host runtime. It serves timeline documents and a timeline preview host. The timeline preview host drives active composition/nested-timeline clips, seeks them, and exposes a timeline API to the preview. Preview audio is mixed by the preview page from the resolved timeline audio clips.
|
|
78
108
|
|
|
79
|
-
|
|
109
|
+
Production composition rendering is unchanged: one Chromium session per composition, explicit frame seeks, PNG capture, and FFmpeg encoding.
|
|
80
110
|
|
|
81
|
-
Production rendering:
|
|
111
|
+
Production timeline rendering is segmented and parallel:
|
|
82
112
|
|
|
83
|
-
1.
|
|
84
|
-
2.
|
|
85
|
-
3.
|
|
86
|
-
4.
|
|
87
|
-
5.
|
|
88
|
-
6.
|
|
113
|
+
1. Run static timeline validation and resolve the render plan.
|
|
114
|
+
2. Split the timeline into segments at active-layer changes or a maximum segment duration. Each segment has a fixed set of visual layers and a fixed filter graph.
|
|
115
|
+
3. In parallel workers, capture the active composition and nested-timeline clip frames for each segment with Chromium and encode them to lossless FFV1 intermediates. Transparent clips are captured with an alpha-preserving background.
|
|
116
|
+
4. Decode media clips, apply trims, fit, static transforms, and opacity, composite the segment with FFmpeg, and encode H.264.
|
|
117
|
+
5. Join the ordered segments with a stream copy. Segment boundaries never change frame order or timing.
|
|
118
|
+
6. Mix the timeline audio once with FFmpeg and mux it into the final output.
|
|
119
|
+
7. Publish the output atomically; delete scratch intermediates on success and failure.
|
|
89
120
|
|
|
90
|
-
`
|
|
121
|
+
The default timeline worker count is 8, configurable up to the machine's capacity. Scratch is a configurable directory with a bounded budget; it must not depend on `/tmp` alone. The reference long-form target is a representative 60-minute 1920×1080 30 fps timeline with a 50/50 composition/media split by layer-seconds, 60% one active layer, 30% two, and 10% three, rendered in 30 minutes or less on the reference machine. The 10-second benchmark measured 10.7 s for two composition captures, 35.2 MB of FFV1, and 6.1 s to composite and encode. Continuous three-layer stress runs measured 52.4 s and 58.5 s for 60 s of timeline, about 1× real time, and are documented as worst-case rather than the target workload. The representative gate measured 29.39 s for 60 s with four workers, exact 1800-frame output, and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes. The full 60-minute composition-only gate rendered 108,000 frames in 35.35 minutes with exact 108,000-frame / 3,600 s output; it is stricter than the 50/50 target mix because every frame is an HTML composition capture. The fast path copies full-frame, untransformed video whose source matches the project format and has keyframes at segment boundaries; media that needs scaling or transforms is re-encoded and adds cost not included in that projection.
|
|
91
122
|
|
|
92
|
-
The renderer
|
|
123
|
+
The renderer uses a bounded segment plan, a worker pool, and a scratch budget. It never publishes a partial MP4, and it cleans scratch on success and failure. Segment boundaries preserve timeline frame order and timing; the final audio mix is muxed once.
|
|
124
|
+
|
|
125
|
+
`check` and `still` use the same clip capture and timeline composite path for a requested frame. `check` evaluates every timeline frame; on long timelines this is a validation cost, not a render. `still` captures one frame as PNG without encoding a video.
|
|
93
126
|
|
|
94
127
|
## 5. Audio and output
|
|
95
128
|
|
|
96
|
-
|
|
129
|
+
A timeline has one audio mix. It combines:
|
|
130
|
+
|
|
131
|
+
- composition audio clips declared inside composition clips, shifted and trimmed by the clip's source range;
|
|
132
|
+
- embedded video audio from composition clips and media video clips, unless muted;
|
|
133
|
+
- timeline audio clips on audio tracks;
|
|
134
|
+
- audio from nested timeline clips, recursively.
|
|
135
|
+
|
|
136
|
+
Per-clip gain and mute, per-audio-track gain and mute, and composition-level volume apply. Audio fades are deferred. The mix is produced once with FFmpeg and muxed into the final MP4. Preview mixes the same resolved clips in the browser for monitoring; it is not the production source of truth.
|
|
97
137
|
|
|
98
|
-
Audio, video, and transcript `src` values resolve from the project root. The controller `src` and ordinary HTML asset URLs
|
|
138
|
+
Audio, video, and transcript `src` values resolve from the project root. The controller `src` and ordinary HTML asset URLs resolve relative to the composition document.
|
|
99
139
|
|
|
100
|
-
Generated speech behaves like any other audio clip. Frameyard does not automatically time-stretch it, change
|
|
140
|
+
Generated speech behaves like any other audio clip. Frameyard does not automatically time-stretch it, change timeline duration, or regenerate it to fit. `check` reports clip durations and overrun warnings so users and agents can revise text, timing, or the timeline deliberately.
|
|
101
141
|
|
|
102
|
-
0.
|
|
142
|
+
0.6 produces portable MP4 files with H.264 video and AAC audio. It exposes only a small set of validated encoding options, not arbitrary FFmpeg arguments. Single-frame PNG output through `still` is a debugging artifact, not general image-sequence support.
|
|
103
143
|
|
|
104
|
-
A render fails immediately on an uncaught exception, unhandled rejection, required-asset failure,
|
|
144
|
+
A render fails immediately on an uncaught exception, unhandled rejection, required-asset failure, invalid composition output, segment encoding failure, or scratch failure. Diagnostics identify the timeline, clip, segment, and, where applicable, the frame. The CLI exits nonzero, cleans temporary files, and never reports a partial video as successful. Existing output files are not overwritten unless explicitly requested.
|
|
105
145
|
|
|
106
146
|
## 6. CLI, tooling, and package boundaries
|
|
107
147
|
|
|
108
148
|
Project and composition commands are:
|
|
109
149
|
|
|
110
|
-
- `init`: scaffold a
|
|
111
|
-
- `dev`: start the preview
|
|
112
|
-
- `render <
|
|
113
|
-
- `check <
|
|
114
|
-
- `still <
|
|
115
|
-
- `doctor`: inspect core requirements and optional integrations without repairing or downloading anything
|
|
150
|
+
- `init`: scaffold a timeline-first project with a composition, a one-clip timeline, a project format, an agent guide, a `.gitignore` for `out/`, and a private `package.json` with pinned `dev`, `check`, `render`, and `still` scripts.
|
|
151
|
+
- `dev`: start the preview with composition and timeline modes.
|
|
152
|
+
- `render <id> [--out video.mp4]`: render a timeline or a composition. Timeline renders accept `--workers <n>` and `--scratch <dir>`; composition renders accept `--concurrency <n>` from 1 to 16. The default output is `out/<id>.mp4`.
|
|
153
|
+
- `check <id>`: run static validation, then validate configuration, props, assets, media, audio, and transcript requirements, load the target, and evaluate every frame without capturing or encoding.
|
|
154
|
+
- `still <id> --frame <n>`: capture one requested frame as PNG through the clip capture and timeline composite path.
|
|
155
|
+
- `doctor`: inspect core requirements and optional integrations without repairing or downloading anything.
|
|
116
156
|
|
|
117
|
-
`check`, `still`, and `render` run a Node-side static
|
|
157
|
+
`check`, `still`, and `render` run a Node-side static validation before launching Chromium. For a timeline it validates the JSON document, tracks, clips, references, nested timelines, cycles, frame-rate compatibility, overlaps, source bounds, media, transforms, audio settings, props, and every referenced composition. Static validation and the composition host read the composition grammar through one browser-safe document reader. Timeline diagnostics carry stable codes and optional source locations or JSON paths, and composition diagnostics from inside a timeline carry the clip and track context.
|
|
118
158
|
|
|
119
159
|
Local asset-production commands are `tts` and `transcribe`. Every non-interactive command supports `--json` with stable result and error schemas. Structured results go to stdout; human diagnostics and inference progress go to stderr.
|
|
120
160
|
|
|
121
|
-
The built-in server serves native ES modules
|
|
161
|
+
The built-in server serves native ES modules, static assets, composition entries, timeline documents, and the timeline preview host. It injects an import map and composition host into composition entries and an inline controller through a virtual module URL. It performs no dependency pre-bundling. Third-party libraries must initially be browser-ready ES modules referenced explicitly. Running through the CLI server is required; direct `file://` execution is unsupported for deterministic preview or rendering.
|
|
122
162
|
|
|
123
|
-
Frameyard uses one repository and one publishable npm package with
|
|
163
|
+
Frameyard uses one repository and one publishable npm package with four internal layers:
|
|
124
164
|
|
|
125
|
-
1. Shared composition contracts and HTML
|
|
126
|
-
2. Browser host
|
|
127
|
-
3. Node-based
|
|
165
|
+
1. Shared composition and timeline contracts and HTML/JSON parsing
|
|
166
|
+
2. Browser composition host and timeline preview host
|
|
167
|
+
3. Node-based static validation and segmented timeline renderer
|
|
168
|
+
4. Node-based CLI, asset production, and doctor
|
|
128
169
|
|
|
129
|
-
These layers have clean internal boundaries but no public plugin API. FFmpeg is
|
|
170
|
+
These layers have clean internal boundaries but no public plugin API. FFmpeg is required for encoding, probing, media decoding, audio mixing, and timeline compositing. Skia is not required in 0.6. The project supports the current active Node.js LTS; Linux is the reference and CI platform, while macOS and Windows remain best-effort until covered by real rendering tests.
|
|
130
171
|
|
|
131
172
|
## 7. Official agent skill
|
|
132
173
|
|
|
133
|
-
Frameyard ships an optional, host-neutral agent skill with a `SKILL.md`, supporting references, and thin scripts. The npm package includes skill files matching its API version, and documentation explains host-specific installation or referencing. 0.
|
|
174
|
+
Frameyard ships an optional, host-neutral agent skill with a `SKILL.md`, supporting references, and thin scripts. The npm package includes skill files matching its API version, and documentation explains host-specific installation or referencing. 0.6 does not add a skill-installer command.
|
|
134
175
|
|
|
135
176
|
The skill:
|
|
136
177
|
|
|
137
|
-
- Uses the normal project format, HTML composition contract, optional controller, and CLI available to humans.
|
|
178
|
+
- Uses the normal project format, HTML composition contract, JSON timeline contract, optional controller, and CLI available to humans.
|
|
138
179
|
- Does not introduce an agent-only runtime, MCP server, hosted control plane, or separate rendering API.
|
|
139
180
|
- Triggers for Frameyard authoring and operation rather than claiming to be a general-purpose media-generation skill.
|
|
140
181
|
- Uses Frameyard's audio wrappers instead of invoking `qwen-tts` or `whisper-cli` directly.
|
|
141
|
-
- Includes references for deterministic HTML authoring,
|
|
182
|
+
- Includes references for deterministic HTML authoring, timeline JSON authoring, tracks, clips, source ranges, props, media, nested timelines, static transforms, audio, validation, diagnostics, common mistakes, subtitle and karaoke recipes, JSON command results, long-form worker/scratch settings, and failure recovery.
|
|
142
183
|
- Invokes speech synthesis or transcription only when requested or explicitly approved as part of the brief.
|
|
143
184
|
- Does not overwrite or regenerate existing generated assets without permission.
|
|
144
185
|
- Runs `check`, inspects representative `still` images when the host supports image viewing, and discloses when visual inspection was impossible.
|
|
145
186
|
- Does not claim success when checks fail or rendering is partial.
|
|
146
187
|
|
|
147
|
-
A successful agent workflow preserves and reports the source project, props, generated assets, provenance manifest, validation results, and final video path. The user must be able to inspect, modify, and reproduce the result rather than receiving only an opaque MP4.
|
|
188
|
+
A successful agent workflow preserves and reports the source project, timeline document, props, generated assets, provenance manifest, validation results, and final video path. The user must be able to inspect, modify, and reproduce the result rather than receiving only an opaque MP4.
|
|
148
189
|
|
|
149
190
|
## 8. Local speech synthesis
|
|
150
191
|
|
|
151
192
|
The `tts` command integrates [`qwentts.cpp`](https://github.com/ServeurpersoCom/qwentts.cpp) through its external `qwen-tts` executable. It does not link the native library or require the upstream server.
|
|
152
193
|
|
|
153
|
-
0.
|
|
194
|
+
0.6 supports CustomVoice mode only:
|
|
154
195
|
|
|
155
196
|
- A named speaker is required.
|
|
156
197
|
- An optional style instruction and language may be supplied.
|
|
@@ -176,7 +217,7 @@ The canonical artifact is a versioned Frameyard JSON document containing:
|
|
|
176
217
|
- Timed segments
|
|
177
218
|
- Optional word timings
|
|
178
219
|
|
|
179
|
-
All timestamps are integer milliseconds relative to the source audio, not composition frame numbers.
|
|
220
|
+
All timestamps are integer milliseconds relative to the source audio, not timeline or composition frame numbers. A controller converts them using the audio clip's start frame, source trim, and the composition frame rate.
|
|
180
221
|
|
|
181
222
|
Compositions declare required transcript assets with `<fy-transcript>` elements:
|
|
182
223
|
|
|
@@ -188,11 +229,11 @@ Compositions declare required transcript assets with `<fy-transcript>` elements:
|
|
|
188
229
|
|
|
189
230
|
SRT and VTT are derived exports, not the canonical contract. Word-level karaoke requires actual word timings and fails validation when they are unavailable. Frameyard never fabricates word timings by interpolating segment timestamps and never executes Whisper's generated bash/FFmpeg karaoke script.
|
|
190
231
|
|
|
191
|
-
The official skill documents seek-safe subtitle and karaoke recipes. Compositions retain responsibility for visual presentation; 0.
|
|
232
|
+
The official skill documents seek-safe subtitle and karaoke recipes. Compositions retain responsibility for visual presentation; 0.6 has no built-in subtitle renderer and does not automatically burn captions into videos. Karaoke presentation uses the optional controller.
|
|
192
233
|
|
|
193
234
|
## 10. Generated assets and inference operation
|
|
194
235
|
|
|
195
|
-
Speech synthesis and transcription are separate asset-production operations that complete before composition
|
|
236
|
+
Speech synthesis and transcription are separate asset-production operations that complete before a timeline or composition is loaded or rendered. Their outputs are ordinary local project assets. Once those assets are frozen, ordinary authoring, preview, checking, and rendering require neither AI executable nor model.
|
|
196
237
|
|
|
197
238
|
Both integrations are optional and local-only, with no cloud fallback. Network activity needed to acquire tools or models is a separate explicit setup operation, never a hidden side effect of generation or rendering.
|
|
198
239
|
|
|
@@ -215,19 +256,21 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
|
|
|
215
256
|
|
|
216
257
|
- HTML compositions validate metadata, default props, bindings, conditionals, repeats, and controller declarations, including inline/external parity and the exactly-one-controller rule.
|
|
217
258
|
- Static composition validation catches metadata, root/controller count, binding, animation, script, element, and asset-reference errors before Chromium.
|
|
218
|
-
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
259
|
+
- Timeline JSON validation catches schema, track, clip, reference, cycle, frame-rate, overlap, bounds, media, transform, audio, and props errors before Chromium.
|
|
260
|
+
- Diagnostics carry stable codes and source locations or JSON paths; JSON results expose the composition or timeline entry, clips, tracks, and top-level diagnostics.
|
|
261
|
+
- Sequential, individual, random-order, and parallel composition renders produce equivalent images; timeline segment workers produce ordered output and identical captured composition frames.
|
|
262
|
+
- Determinism checks compare captured composition pixels or stills. The final H.264 bytes and the FFmpeg composite versus Chromium preview are not reproducibility oracles.
|
|
263
|
+
- Preview and renderer use the same composition document and host behavior; timeline preview uses the same active clip hosts.
|
|
264
|
+
- CSS, controller JavaScript, SVG, Canvas, WebGL, embedded video, and audio frames seek correctly.
|
|
223
265
|
- Frame-scheduled `data-fy-anim-*` animations and frame-state CSS variables produce the expected frames.
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
266
|
+
- Timeline capture preserves alpha for transparent compositions; FFV1 intermediates decode pixel-identically to the captured frames.
|
|
267
|
+
- Timeline segment encoding, ordered concatenation, global audio mix, and scratch cleanup produce the expected MP4 dimensions, frame count, duration, and audio stream.
|
|
268
|
+
- Nested timelines preserve group opacity and transform semantics; cycles fail static validation.
|
|
269
|
+
- Media clips trim and fit deterministically; images require durations; source overruns fail.
|
|
270
|
+
- Props overrides for timeline composition clips merge over clip props and are reflected in the render.
|
|
271
|
+
- `check` exercises every frame; `still` follows the clip capture and timeline composite path.
|
|
272
|
+
- The 10-second and 60-second long-form benchmarks record capture, FFV1, composite, encode, disk, worker, and audio numbers. Continuous three-layer runs measured 52.4 s and 58.5 s for 60 s of timeline and are documented as worst-case (~1× real time). The representative 50/50, 60/30/10 occupancy gate measured 29.39 s for 60 s with four workers and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes with exact 1800-frame output. The full 60-minute composition-only gate rendered 108,000 frames in 35.35 minutes with exact output and clean scratch, documenting composition-heavy performance above 30 minutes.
|
|
273
|
+
- Golden-frame tests cover representative composition and timeline visual behavior.
|
|
231
274
|
- Normal CI uses fake `qwen-tts` and `whisper-cli` executables and small fixtures to verify arguments, artifacts, manifests, cancellation, and errors without model downloads or GPU hardware.
|
|
232
275
|
- Real-model integration tests are explicitly opt-in and use documented upstream revisions.
|
|
233
276
|
- Agent-workflow evaluations verify source preservation, provenance, validation, still inspection behavior, failure reporting, and final renders.
|
|
@@ -236,7 +279,14 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
|
|
|
236
279
|
|
|
237
280
|
These are scope boundaries, not permanent rejections:
|
|
238
281
|
|
|
239
|
-
-
|
|
282
|
+
- Transitions, effects, keyframes, blend modes, masks, and audio fades
|
|
283
|
+
- Full visual timeline editing, drag-and-drop, clip handles, and undo/redo
|
|
284
|
+
- Timeline-level variables and prop overrides for compositions inside nested timelines
|
|
285
|
+
- Track solo and video-track visibility
|
|
286
|
+
- Per-timeline formats and frame-rate conversion/retiming
|
|
287
|
+
- Persistent clip caches; the segmented renderer cleans its intermediates
|
|
288
|
+
- Hardware encoders, GPU compositing, and non-H.264 output
|
|
289
|
+
- Hosted rendering, collaboration, and asset marketplaces
|
|
240
290
|
- Cloud generative-AI services, text-to-video generation, and image generation
|
|
241
291
|
- `qwentts.cpp` reference-audio voice cloning and VoiceDesign
|
|
242
292
|
- Automatic long-text chunking, streaming speech synthesis, and alternative generated-audio formats
|
|
@@ -248,17 +298,16 @@ These are scope boundaries, not permanent rejections:
|
|
|
248
298
|
- Agent-only runtimes, MCP servers, hosted agent control planes, and host-specific skill installers
|
|
249
299
|
- React or other UI-framework adapters in the core
|
|
250
300
|
- Public plugin APIs and a broad integration ecosystem
|
|
251
|
-
- Dynamic composition metadata derived from props
|
|
301
|
+
- Dynamic composition or timeline metadata derived from props
|
|
252
302
|
- Live streams, remote media, and other visual sources that cannot be deterministically seeked
|
|
253
303
|
- Dynamic per-frame asset loading or AI inference
|
|
254
|
-
- Frame caches
|
|
255
304
|
- Batch still capture; agents can run `still` once per representative frame
|
|
256
305
|
- Additional final-video formats, general image-sequence output, and arbitrary FFmpeg argument passthrough
|
|
257
306
|
- Built-in TypeScript transpilation and npm dependency optimization
|
|
258
307
|
- Rendering untrusted compositions
|
|
259
308
|
- Bit-identical cross-platform encoded output or AI regeneration
|
|
260
|
-
- A minimum production rendering-speed guarantee
|
|
309
|
+
- A minimum production rendering-speed guarantee for compositions or timelines
|
|
261
310
|
|
|
262
311
|
## 13. Release posture
|
|
263
312
|
|
|
264
|
-
Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and composition, configuration, CLI, JSON-result, transcript, and skill-contract changes are documented explicitly until those contracts stabilize. 0.
|
|
313
|
+
Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and composition, timeline, configuration, CLI, JSON-result, transcript, and skill-contract changes are documented explicitly until those contracts stabilize. 0.6.0 adds timelines, project formats, segmented timeline rendering, and a timeline-first scaffold without breaking the existing HTML composition authoring contract. See [ADR 0010](./adr/0010-make-timelines-the-render-unit.md), [ADR 0011](./adr/0011-compose-timeline-frames-in-a-live-browser-host.md), and [ADR 0012](./adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md).
|
package/examples/demo/README.md
CHANGED
|
@@ -10,14 +10,21 @@ A 30-second, 1920×1080 HTML composition that exercises the current core:
|
|
|
10
10
|
- frame-state CSS variables for the progress bar
|
|
11
11
|
|
|
12
12
|
The composition lives in `compositions/demo.html`; the optional controller is
|
|
13
|
-
`compositions/demo.controller.js`.
|
|
13
|
+
`compositions/demo.controller.js`. `timelines/demo.timeline.json` wraps the
|
|
14
|
+
composition as a clip in a 1920×1080, 30 fps timeline.
|
|
14
15
|
|
|
15
16
|
From the repository root:
|
|
16
17
|
|
|
17
18
|
```sh
|
|
18
19
|
pnpm build
|
|
19
|
-
node dist/cli.js check demo --project examples/demo
|
|
20
|
-
node dist/cli.js still demo --frame 240 --project examples/demo --out demo-still.png
|
|
20
|
+
node dist/cli.js check demo-timeline --project examples/demo
|
|
21
|
+
node dist/cli.js still demo-timeline --frame 240 --project examples/demo --out demo-still.png
|
|
22
|
+
node dist/cli.js render demo-timeline --project examples/demo --props examples/demo/props/demo.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The composition remains renderable on its own:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
21
28
|
node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
|
|
22
29
|
```
|
|
23
30
|
|
package/examples/video/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Frameyard video example
|
|
2
2
|
|
|
3
|
-
A 12-second, 1920×1080
|
|
4
|
-
|
|
3
|
+
A 12-second, 1920×1080 project that treats a local clip as source footage. The
|
|
4
|
+
project includes the same layout for two source codecs:
|
|
5
5
|
|
|
6
6
|
- `video` reads `assets/clip.webm` (VP9 + Opus)
|
|
7
7
|
- `video-mp4` reads `assets/clip.mp4` (H.264 + AAC)
|
|
@@ -25,11 +25,49 @@ controller is needed. The header, copy, and footer fade individually rather
|
|
|
25
25
|
than the whole scene; a full-scene opacity animation above a video can leave
|
|
26
26
|
the load frame on screen in interactive preview after a large seek.
|
|
27
27
|
|
|
28
|
+
## Timeline with the video on a track
|
|
29
|
+
|
|
30
|
+
`video-timeline` and `video-timeline-mp4` move the footage out of the
|
|
31
|
+
composition and onto timeline tracks. The HTML is now a transparent overlay
|
|
32
|
+
above it:
|
|
33
|
+
|
|
34
|
+
- `v1` holds the hero clip: the whole 12-second source, audio at `0.85`
|
|
35
|
+
- `v2` holds the detail pass from source frame 174 (5.8 s) for 150 frames,
|
|
36
|
+
muted, placed in a 440×248 transform box in the bottom right
|
|
37
|
+
- `v3` holds `video-overlay`, an HTML-only composition with no `<video>`
|
|
38
|
+
elements and a transparent document background
|
|
39
|
+
|
|
40
|
+
`video-timeline` reads `assets/clip.webm` (VP9 + Opus) and
|
|
41
|
+
`video-timeline-mp4` reads `assets/clip.mp4` (H.264 + AAC). Both share the same
|
|
42
|
+
overlay composition and props file; the overlay clip sets its own `format` prop
|
|
43
|
+
so the header reports the codec of each timeline.
|
|
44
|
+
|
|
45
|
+
The detail clip uses `transform.width` and `transform.height` instead of
|
|
46
|
+
`scale`, with `fit: "cover"` filling that box. The box matches the overlay's
|
|
47
|
+
detail window exactly, so the video and its chrome stay aligned in production
|
|
48
|
+
and preview.
|
|
49
|
+
|
|
50
|
+
The overlay draws the scrim, typography, frame border, detail-window chrome,
|
|
51
|
+
progress bar, and outro fade. `video-overlay` declares its own frame animations,
|
|
52
|
+
so the title and footer still animate on the timeline clock. Production captures
|
|
53
|
+
the overlay with alpha and composites it over the video layers with FFmpeg; the
|
|
54
|
+
interactive timeline preview composites the same layers in Chromium.
|
|
55
|
+
|
|
56
|
+
Both timelines accept `props/video-timeline.json`, which overrides the overlay
|
|
57
|
+
composition's props. Timeline `--props` is keyed by composition clip id, so the
|
|
58
|
+
file targets the `overlay` clip.
|
|
59
|
+
|
|
60
|
+
Open the preview with `node dist/cli.js dev --project examples/video`; the
|
|
61
|
+
compositions and both timelines are listed.
|
|
62
|
+
|
|
28
63
|
The compositions live in `compositions/video.html` and
|
|
29
|
-
`compositions/video-mp4.html
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
`compositions/video-mp4.html`; the overlay lives in
|
|
65
|
+
`compositions/video-overlay.html`, and the timelines in
|
|
66
|
+
`timelines/video-timeline.timeline.json` and
|
|
67
|
+
`timelines/video-timeline-mp4.timeline.json`. Both clips are 12-second 1280×720
|
|
68
|
+
files generated procedurally for this example; the MP4 is transcoded from the
|
|
69
|
+
WebM source. Replace either asset with a local, seekable video file that is at
|
|
70
|
+
least 12 seconds long; remote URLs and live streams are not supported.
|
|
33
71
|
|
|
34
72
|
Regenerate both clips from scratch with the bundled FFmpeg script:
|
|
35
73
|
|
|
@@ -48,10 +86,16 @@ From the repository root:
|
|
|
48
86
|
pnpm build
|
|
49
87
|
node dist/cli.js check video --project examples/video
|
|
50
88
|
node dist/cli.js check video-mp4 --project examples/video
|
|
89
|
+
node dist/cli.js check video-timeline --project examples/video
|
|
90
|
+
node dist/cli.js check video-timeline-mp4 --project examples/video
|
|
51
91
|
node dist/cli.js still video --frame 210 --project examples/video --out video-still.png
|
|
52
92
|
node dist/cli.js still video-mp4 --frame 210 --project examples/video --out video-mp4-still.png
|
|
93
|
+
node dist/cli.js still video-timeline --frame 210 --project examples/video --out video-timeline-still.png
|
|
94
|
+
node dist/cli.js still video-timeline-mp4 --frame 210 --project examples/video --out video-timeline-mp4-still.png
|
|
53
95
|
node dist/cli.js render video --project examples/video --props examples/video/props/video.json
|
|
54
96
|
node dist/cli.js render video-mp4 --project examples/video --props examples/video/props/video.json
|
|
97
|
+
node dist/cli.js render video-timeline --project examples/video --props examples/video/props/video-timeline.json
|
|
98
|
+
node dist/cli.js render video-timeline-mp4 --project examples/video --props examples/video/props/video-timeline.json
|
|
55
99
|
```
|
|
56
100
|
|
|
57
101
|
Start the interactive preview with:
|