frameyard 0.8.0 → 0.10.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 +177 -14
- package/GLOSSARY.md +112 -2
- package/README.md +302 -265
- package/dist/assets/integrations.d.ts +8 -4
- package/dist/assets/integrations.d.ts.map +1 -1
- package/dist/assets/integrations.js +8 -4
- package/dist/assets/integrations.js.map +1 -1
- package/dist/assets/provenance.d.ts +2 -2
- package/dist/assets/provenance.d.ts.map +1 -1
- package/dist/assets/provenance.js +5 -3
- package/dist/assets/provenance.js.map +1 -1
- package/dist/assets/tts-supertonic.d.ts +28 -0
- package/dist/assets/tts-supertonic.d.ts.map +1 -0
- package/dist/assets/tts-supertonic.js +156 -0
- package/dist/assets/tts-supertonic.js.map +1 -0
- package/dist/assets/tts.d.ts +18 -6
- package/dist/assets/tts.d.ts.map +1 -1
- package/dist/assets/tts.js +219 -97
- package/dist/assets/tts.js.map +1 -1
- package/dist/cli.js +1685 -93
- package/dist/cli.js.map +1 -1
- package/dist/composition/format.d.ts +1 -1
- package/dist/composition/format.d.ts.map +1 -1
- package/dist/composition/format.js +1 -1
- package/dist/composition/format.js.map +1 -1
- package/dist/context.d.ts +82 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +277 -0
- package/dist/context.js.map +1 -0
- package/dist/contracts.d.ts +25 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/doctor.d.ts +14 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +166 -7
- package/dist/doctor.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fonts/add.d.ts +41 -0
- package/dist/fonts/add.d.ts.map +1 -0
- package/dist/fonts/add.js +259 -0
- package/dist/fonts/add.js.map +1 -0
- package/dist/fonts/binary.d.ts +4 -0
- package/dist/fonts/binary.d.ts.map +1 -0
- package/dist/fonts/binary.js +21 -0
- package/dist/fonts/binary.js.map +1 -0
- package/dist/fonts/css.d.ts +4 -0
- package/dist/fonts/css.d.ts.map +1 -0
- package/dist/fonts/css.js +50 -0
- package/dist/fonts/css.js.map +1 -0
- package/dist/fonts/fontsource.d.ts +107 -0
- package/dist/fonts/fontsource.d.ts.map +1 -0
- package/dist/fonts/fontsource.js +565 -0
- package/dist/fonts/fontsource.js.map +1 -0
- package/dist/fonts/index.d.ts +11 -0
- package/dist/fonts/index.d.ts.map +1 -0
- package/dist/fonts/index.js +11 -0
- package/dist/fonts/index.js.map +1 -0
- package/dist/fonts/install.d.ts +24 -0
- package/dist/fonts/install.d.ts.map +1 -0
- package/dist/fonts/install.js +145 -0
- package/dist/fonts/install.js.map +1 -0
- package/dist/fonts/manifest.d.ts +21 -0
- package/dist/fonts/manifest.d.ts.map +1 -0
- package/dist/fonts/manifest.js +96 -0
- package/dist/fonts/manifest.js.map +1 -0
- package/dist/fonts/options.d.ts +9 -0
- package/dist/fonts/options.d.ts.map +1 -0
- package/dist/fonts/options.js +66 -0
- package/dist/fonts/options.js.map +1 -0
- package/dist/fonts/schema.d.ts +4 -0
- package/dist/fonts/schema.d.ts.map +1 -0
- package/dist/fonts/schema.js +316 -0
- package/dist/fonts/schema.js.map +1 -0
- package/dist/fonts/summary.d.ts +16 -0
- package/dist/fonts/summary.d.ts.map +1 -0
- package/dist/fonts/summary.js +22 -0
- package/dist/fonts/summary.js.map +1 -0
- package/dist/fonts/types.d.ts +56 -0
- package/dist/fonts/types.d.ts.map +1 -0
- package/dist/fonts/types.js +8 -0
- package/dist/fonts/types.js.map +1 -0
- package/dist/fonts/verify.d.ts +33 -0
- package/dist/fonts/verify.d.ts.map +1 -0
- package/dist/fonts/verify.js +200 -0
- package/dist/fonts/verify.js.map +1 -0
- package/dist/html/validate.d.ts.map +1 -1
- package/dist/html/validate.js +59 -0
- package/dist/html/validate.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +61 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +325 -0
- package/dist/inspect.js.map +1 -0
- package/dist/lint.d.ts +63 -0
- package/dist/lint.d.ts.map +1 -0
- package/dist/lint.js +265 -0
- package/dist/lint.js.map +1 -0
- package/dist/policy.d.ts +69 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +361 -0
- package/dist/policy.js.map +1 -0
- package/dist/preview/fonts/LICENSE-figtree.txt +93 -0
- package/dist/preview/fonts/LICENSE-space-mono.txt +93 -0
- package/dist/preview/fonts/README.md +23 -0
- package/dist/preview/fonts/figtree-latin-wght-normal.woff2 +0 -0
- package/dist/preview/fonts/space-mono-latin-400-normal.woff2 +0 -0
- package/dist/preview/fonts/space-mono-latin-700-normal.woff2 +0 -0
- package/dist/preview/main.js +5 -2
- package/dist/preview/main.js.map +1 -1
- package/dist/preview/timeline-data.d.ts +17 -69
- package/dist/preview/timeline-data.d.ts.map +1 -1
- package/dist/preview/timeline-host.d.ts.map +1 -1
- package/dist/preview/timeline-host.js +70 -45
- package/dist/preview/timeline-host.js.map +1 -1
- package/dist/preview/timeline-main.d.ts.map +1 -1
- package/dist/preview/timeline-main.js +88 -27
- package/dist/preview/timeline-main.js.map +1 -1
- package/dist/project.d.ts +10 -3
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +20 -17
- package/dist/project.js.map +1 -1
- package/dist/renderer/audio.d.ts +6 -0
- package/dist/renderer/audio.d.ts.map +1 -1
- package/dist/renderer/audio.js +3 -1
- package/dist/renderer/audio.js.map +1 -1
- package/dist/renderer/browser.d.ts.map +1 -1
- package/dist/renderer/browser.js +13 -0
- package/dist/renderer/browser.js.map +1 -1
- package/dist/renderer/concurrency.d.ts +6 -0
- package/dist/renderer/concurrency.d.ts.map +1 -0
- package/dist/renderer/concurrency.js +27 -0
- package/dist/renderer/concurrency.js.map +1 -0
- package/dist/renderer/ffmpeg.d.ts +104 -5
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +580 -67
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.js +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyframe-expressions.d.ts +39 -0
- package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
- package/dist/renderer/keyframe-expressions.js +180 -0
- package/dist/renderer/keyframe-expressions.js.map +1 -0
- package/dist/renderer/session-pool.d.ts +28 -0
- package/dist/renderer/session-pool.d.ts.map +1 -0
- package/dist/renderer/session-pool.js +91 -0
- package/dist/renderer/session-pool.js.map +1 -0
- package/dist/renderer/timeline.d.ts +2 -0
- package/dist/renderer/timeline.d.ts.map +1 -1
- package/dist/renderer/timeline.js +600 -147
- package/dist/renderer/timeline.js.map +1 -1
- package/dist/renderer/timings.d.ts +13 -0
- package/dist/renderer/timings.d.ts.map +1 -0
- package/dist/renderer/timings.js +29 -0
- package/dist/renderer/timings.js.map +1 -0
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/host.js +4 -0
- package/dist/runtime/host.js.map +1 -1
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +48 -320
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts +2 -0
- package/dist/server/composition-html.d.ts.map +1 -1
- package/dist/server/composition-html.js +7 -0
- package/dist/server/composition-html.js.map +1 -1
- package/dist/server/preview-html.d.ts.map +1 -1
- package/dist/server/preview-html.js +74 -188
- package/dist/server/preview-html.js.map +1 -1
- package/dist/server/preview-style.d.ts +11 -0
- package/dist/server/preview-style.d.ts.map +1 -0
- package/dist/server/preview-style.js +298 -0
- package/dist/server/preview-style.js.map +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +31 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/timeline-preview-data.d.ts.map +1 -1
- package/dist/server/timeline-preview-data.js +12 -34
- package/dist/server/timeline-preview-data.js.map +1 -1
- package/dist/server/timeline-preview-html.d.ts.map +1 -1
- package/dist/server/timeline-preview-html.js +140 -148
- package/dist/server/timeline-preview-html.js.map +1 -1
- package/dist/templates.d.ts +16 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +186 -0
- package/dist/templates.js.map +1 -0
- package/dist/timeline/easing.d.ts +36 -0
- package/dist/timeline/easing.d.ts.map +1 -0
- package/dist/timeline/easing.js +225 -0
- package/dist/timeline/easing.js.map +1 -0
- package/dist/timeline/keyframes.d.ts +23 -0
- package/dist/timeline/keyframes.d.ts.map +1 -0
- package/dist/timeline/keyframes.js +95 -0
- package/dist/timeline/keyframes.js.map +1 -0
- package/dist/timeline/plan.d.ts +6 -12
- package/dist/timeline/plan.d.ts.map +1 -1
- package/dist/timeline/plan.js +17 -30
- package/dist/timeline/plan.js.map +1 -1
- package/dist/timeline/resolve.d.ts +12 -0
- package/dist/timeline/resolve.d.ts.map +1 -1
- package/dist/timeline/resolve.js +105 -9
- package/dist/timeline/resolve.js.map +1 -1
- package/dist/timeline/scene.d.ts +126 -0
- package/dist/timeline/scene.d.ts.map +1 -0
- package/dist/timeline/scene.js +109 -0
- package/dist/timeline/scene.js.map +1 -0
- package/dist/timeline/schema.d.ts +31 -0
- package/dist/timeline/schema.d.ts.map +1 -1
- package/dist/timeline/schema.js +240 -9
- package/dist/timeline/schema.js.map +1 -1
- package/dist/timeline/transitions.d.ts +21 -0
- package/dist/timeline/transitions.d.ts.map +1 -0
- package/dist/timeline/transitions.js +23 -0
- package/dist/timeline/transitions.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
- package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
- package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
- package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
- package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
- package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
- package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
- package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
- package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
- package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
- package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
- package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
- package/docs/adr/0024-stream-composition-captures-and-skip-trivial-composites.md +86 -0
- package/docs/adr/0025-capture-timeline-layers-in-parallel-with-a-global-limit.md +67 -0
- package/docs/adr/0026-reuse-loaded-composition-sessions-across-segments.md +70 -0
- package/docs/adr/0027-encode-two-full-frame-layers-through-dual-pipes.md +76 -0
- package/docs/adr/0028-encode-full-frame-video-segments-directly.md +65 -0
- package/docs/migration-0.4.md +2 -2
- package/docs/preview-testing.md +27 -6
- package/docs/{v0.1-design.md → v0.x-design.md} +57 -32
- package/examples/blend/README.md +3 -3
- package/examples/demo/README.md +2 -2
- package/examples/intro/README.md +71 -0
- package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
- package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
- package/examples/intro/assets/grain.png +0 -0
- package/examples/intro/assets/score.ogg +0 -0
- package/examples/intro/compositions/brand.html +412 -0
- package/examples/intro/compositions/chrome.controller.js +63 -0
- package/examples/intro/compositions/chrome.html +256 -0
- package/examples/intro/compositions/lattice.controller.js +148 -0
- package/examples/intro/compositions/lattice.html +308 -0
- package/examples/intro/compositions/opening.html +504 -0
- package/examples/intro/compositions/studio.html +930 -0
- package/examples/intro/compositions/theme.css +218 -0
- package/examples/intro/fy.config.js +13 -0
- package/examples/intro/fy.fonts.json +100 -0
- package/examples/intro/scripts/make-grain.mjs +79 -0
- package/examples/intro/scripts/make-score.mjs +702 -0
- package/examples/intro/timelines/intro.timeline.json +60 -0
- package/examples/keyframes/README.md +76 -0
- package/examples/keyframes/compositions/backdrop.html +60 -0
- package/examples/keyframes/compositions/card.html +99 -0
- package/examples/keyframes/fy.config.js +10 -0
- package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
- package/examples/nested/README.md +112 -0
- package/examples/nested/assets/pulse.wav +0 -0
- package/examples/nested/compositions/backdrop.html +84 -0
- package/examples/nested/compositions/card.html +275 -0
- package/examples/nested/compositions/overlay.html +307 -0
- package/examples/nested/fy.config.js +13 -0
- package/examples/nested/scripts/make-pulse.sh +20 -0
- package/examples/nested/timelines/chapter-a.timeline.json +39 -0
- package/examples/nested/timelines/chapter-b.timeline.json +39 -0
- package/examples/nested/timelines/nested-reel.timeline.json +52 -0
- package/examples/static/README.md +2 -2
- package/examples/transitions/README.md +128 -0
- package/examples/transitions/assets/alpha.wav +0 -0
- package/examples/transitions/assets/beta.wav +0 -0
- package/examples/transitions/assets/gamma.wav +0 -0
- package/examples/transitions/compositions/alpha.html +56 -0
- package/examples/transitions/compositions/beta.html +56 -0
- package/examples/transitions/compositions/gamma.html +56 -0
- package/examples/transitions/fy.config.js +12 -0
- package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
- package/examples/transitions/timelines/transitions.timeline.json +31 -0
- package/examples/video/README.md +4 -4
- package/examples/webgl/README.md +1 -1
- package/package.json +13 -6
- package/scaffolds/init/AGENTS.md +131 -0
- package/scaffolds/init/README.md +56 -0
- package/scaffolds/init/compositions/hello.html +79 -0
- package/scaffolds/init/fy.config.js +9 -0
- package/scaffolds/init/gitignore.template +1 -0
- package/scaffolds/init/package.json +11 -0
- package/scaffolds/init/props/hello.json +4 -0
- package/scaffolds/init/props/main.json +6 -0
- package/scaffolds/init/timelines/main.timeline.json +20 -0
- package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
- package/scaffolds/local-models/fy.policy.json +5 -0
- package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
- package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
- package/scaffolds/local-models/pi/sandbox.json +15 -0
- package/scaffolds/local-models/pi/settings.json +15 -0
- package/skills/frameyard/README.md +1 -0
- package/skills/frameyard/SKILL.md +59 -61
- package/skills/frameyard/references/audio-assets.md +32 -3
- package/skills/frameyard/references/common-mistakes.md +53 -2
- package/skills/frameyard/references/deterministic-authoring.md +10 -3
- package/skills/frameyard/references/diagnostics.md +31 -1
- package/skills/frameyard/references/failure-recovery.md +32 -6
- package/skills/frameyard/references/fonts.md +132 -0
- package/skills/frameyard/references/json-results.md +119 -6
- package/skills/frameyard/references/quick-reference.md +222 -0
- package/skills/frameyard/references/timelines.md +97 -7
- /package/examples/blend/{video.config.js → fy.config.js} +0 -0
- /package/examples/demo/{video.config.js → fy.config.js} +0 -0
- /package/examples/static/{video.config.js → fy.config.js} +0 -0
- /package/examples/video/{video.config.js → fy.config.js} +0 -0
- /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Reuse loaded composition sessions across segments
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted. Extends [ADR-0025](./0025-capture-timeline-layers-in-parallel-with-a-global-limit.md). Extended by [ADR-0027](./0027-encode-two-full-frame-layers-through-dual-pipes.md): two full-frame layers encode through dual image pipes.
|
|
4
|
+
**Date:** 2026-11-06
|
|
5
|
+
|
|
6
|
+
The timeline renderer keeps a bounded pool of loaded composition sessions and
|
|
7
|
+
leases them to segment captures. A session is keyed by composition entry, props,
|
|
8
|
+
and transparent-background mode, and is reused across segments and clips that
|
|
9
|
+
share those inputs. Sessions are discarded on a capture error and all sessions
|
|
10
|
+
are closed when the render ends.
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
Every composition layer of every segment opened a new Chromium browser, loaded
|
|
15
|
+
the composition, waited for `networkidle` (roughly 450 ms), and closed it. The
|
|
16
|
+
one-layer 60-minute gate opened 360 sessions; a two-layer workload opens 720.
|
|
17
|
+
The 10-minute one-layer gate without a pool took 211.8 s, and its session stage
|
|
18
|
+
was a repeated fixed cost rather than frame work.
|
|
19
|
+
|
|
20
|
+
The host already has the right reuse semantics: `load` restores the pristine DOM
|
|
21
|
+
and mounts the controller once, while `seek` is required to be stateless. A
|
|
22
|
+
loaded session with the same props can seek to any frame of any segment.
|
|
23
|
+
|
|
24
|
+
## Decision
|
|
25
|
+
|
|
26
|
+
- `CompositionSessionPool` stores idle sessions in a map keyed by
|
|
27
|
+
`entry + props + transparentBackground`.
|
|
28
|
+
- `maxIdle` is `max(2, workers)`. Active sessions are already bounded by the
|
|
29
|
+
capture semaphore; the idle cap bounds memory for workloads with many unique
|
|
30
|
+
compositions.
|
|
31
|
+
- `captureCompositionFrames` leases a session instead of opening one directly.
|
|
32
|
+
A successful capture releases the lease; a capture, diagnostics, or encoder
|
|
33
|
+
error discards it.
|
|
34
|
+
- Nested timelines share the render's pool. Static compositions keep their
|
|
35
|
+
one-shot capture and cache because they are captured once per props variant.
|
|
36
|
+
- The render closes the pool before the preview server in its cleanup path.
|
|
37
|
+
- The session timing event records `reused: true` when the lease came from the
|
|
38
|
+
pool.
|
|
39
|
+
|
|
40
|
+
## Considered Options
|
|
41
|
+
|
|
42
|
+
- **Remove `networkidle` only.** Rejected as the primary change: it removes a
|
|
43
|
+
fixed wait but does not avoid browser launch, navigation, and host load. It
|
|
44
|
+
also weakens the asset-loading boundary for compositions with stylesheet
|
|
45
|
+
background images. Session reuse amortizes the whole startup cost instead.
|
|
46
|
+
- **Share one browser process without reusing pages.** Rejected: it saves the
|
|
47
|
+
browser launch but still pays navigation, host load, and `networkidle` per
|
|
48
|
+
segment.
|
|
49
|
+
- **Per-worker pools.** Rejected: a shared pool lets any worker reuse an idle
|
|
50
|
+
session and keeps the idle budget independent of worker count.
|
|
51
|
+
- **Unbounded idle caching.** Rejected: compositions with many unique props or
|
|
52
|
+
entries could retain an unbounded number of pages and media elements.
|
|
53
|
+
|
|
54
|
+
## Consequences
|
|
55
|
+
|
|
56
|
+
- The 10-minute one-layer gate fell from 211.8 s to 192.6 s (−9.0%) with the
|
|
57
|
+
same 8 workers and byte-identical output. The full 60-minute gate fell from
|
|
58
|
+
19.83 to 18.78 minutes (−5.3%) and opened eight sessions for 360 segments
|
|
59
|
+
instead of 360 sessions. Its output is byte-identical to the baseline.
|
|
60
|
+
- The two-layer 60-second gate did not show a clear gain (43.78 s to 45.43 s):
|
|
61
|
+
six segments are too few for reuse to pay back the pool's initial opens. The
|
|
62
|
+
long-form workload is the target.
|
|
63
|
+
- Reused sessions return accumulated diagnostics; the final merge continues to
|
|
64
|
+
deduplicate them. A page error discards the session so a broken render cannot
|
|
65
|
+
poison later segments.
|
|
66
|
+
- The pool is internal to a render. There is no public CLI or JSON contract
|
|
67
|
+
change.
|
|
68
|
+
- Tests cover same-key reuse, discard, idle-budget eviction, close, and the
|
|
69
|
+
existing timeline suite with embedded media, controllers, blends, keyframes,
|
|
70
|
+
transitions, and nested timelines.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Encode a full-frame composition and a static overlay through dual pipes
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted. Extends [ADR-0026](./0026-reuse-loaded-composition-sessions-across-segments.md).
|
|
4
|
+
**Date:** 2026-11-06
|
|
5
|
+
|
|
6
|
+
A segment with a full-frame composition base and one static composition overlay
|
|
7
|
+
now captures both layers concurrently and feeds them to one FFmpeg process
|
|
8
|
+
through two image pipes. The base has an identity transform; the overlay may use
|
|
9
|
+
a static fit, box, and position, but no keyframes, blend, transition, or
|
|
10
|
+
opacity. The process composites the background, base, and overlay and encodes
|
|
11
|
+
the delivery segment directly. The FFV1 intermediates and the separate
|
|
12
|
+
composite pass are skipped.
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
The two-layer 60-second PIP gate takes 43.78 s; the overlay is a 640×360
|
|
17
|
+
composition on a second track. A two-full-frame-layer workload (1920×1080 base
|
|
18
|
+
plus a 1920×1080 transparent overlay) took 53.27 s with the normal FFV1 and
|
|
19
|
+
composite path. Both captures already ran concurrently after ADR 0025, but the
|
|
20
|
+
segment still paid for two FFV1 encodes, two decodes, and a separate composite
|
|
21
|
+
encode.
|
|
22
|
+
|
|
23
|
+
FFmpeg can read a second image stream from `pipe:3`, and Node can provide that
|
|
24
|
+
extra stdio pipe on Linux and macOS. The two captures write to independent
|
|
25
|
+
pipes, so neither blocks the other while FFmpeg overlays frames in order.
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
- `startDualPipeSegmentEncoder` spawns one FFmpeg process with:
|
|
30
|
+
- `pipe:0` for the base composition;
|
|
31
|
+
- `pipe:3` for the overlay composition;
|
|
32
|
+
- a `lavfi color` input for the timeline background;
|
|
33
|
+
- `[2:v][0:v]overlay`, then `[base][1:v]overlay`, then the delivery encoder.
|
|
34
|
+
- `captureTwoLayerDirectComposition` leases two capture sessions, writes each
|
|
35
|
+
layer to its pipe, and waits for both frame counts before finishing.
|
|
36
|
+
- The fast path requires exactly two composition layers: a base that matches the
|
|
37
|
+
stage with an identity transform, and an overlay with no keyframes, blend,
|
|
38
|
+
transition, opacity, scale, or rotation. The overlay may use a static fit,
|
|
39
|
+
explicit box, and position. An opaque timeline background is required.
|
|
40
|
+
- Windows falls back to the FFV1 and composite path because extra stdio pipe
|
|
41
|
+
descriptors are not portable there.
|
|
42
|
+
- `FRAMEYARD_DISABLE_DUAL_PIPE=1` forces the fallback for A/B measurements.
|
|
43
|
+
- One-layer direct segments keep `startDirectSegmentEncoder`; PIP, transformed,
|
|
44
|
+
blended, keyframed, nested, static, and media layers keep the segmented
|
|
45
|
+
lossless path.
|
|
46
|
+
|
|
47
|
+
## Considered Options
|
|
48
|
+
|
|
49
|
+
- **General multi-pipe compositor for any layer count.** Rejected for now:
|
|
50
|
+
extra pipes multiply the coordination surface and make Windows support
|
|
51
|
+
harder. Two full-frame layers cover the measured multi-layer case.
|
|
52
|
+
- **Interleave two captures into one pipe.** Rejected: the filter graph would
|
|
53
|
+
need frame-splitting metadata and the capture loops would have to lockstep.
|
|
54
|
+
- **Buffer the base layer and stream only the overlay.** Rejected: buffering a
|
|
55
|
+
300-frame 1080p layer costs hundreds of megabytes and adds a copy.
|
|
56
|
+
- **Keep FFV1 and tune the composite.** Measured filters and thread knobs gave
|
|
57
|
+
no meaningful gain; the intermediate round trip is the cost.
|
|
58
|
+
|
|
59
|
+
## Consequences
|
|
60
|
+
|
|
61
|
+
- The two-full-frame-layer 60-second gate fell from 53.27 s to 34.62 s (−35.0%)
|
|
62
|
+
with byte-identical output. The full 60-minute two-layer gate completed in
|
|
63
|
+
29.69 minutes with eight workers, 108,000 exact frames, and clean scratch,
|
|
64
|
+
just inside the 30-minute target.
|
|
65
|
+
- The two-layer PIP 60-second gate fell from 43.78 s to 33.90 s (−22.6%); sampled
|
|
66
|
+
decoded frames are pixel-identical to the FFV1-and-composite path. The full
|
|
67
|
+
PIP 60-minute gate completed in 25.75 minutes with eight workers, 108,000
|
|
68
|
+
exact frames, and clean scratch.
|
|
69
|
+
- The fast path writes no FFV1 files and no PNG sequences for qualifying
|
|
70
|
+
segments.
|
|
71
|
+
- A dual-pipe failure aborts both FFmpeg inputs and discards both sessions;
|
|
72
|
+
frame counts are checked per layer before publication.
|
|
73
|
+
- Windows and non-qualifying segments keep the existing deterministic path, so
|
|
74
|
+
the capture-pixel guarantee is unchanged.
|
|
75
|
+
- Tests cover two full-frame compositions with a transparent overlay over a
|
|
76
|
+
non-black background, plus the full timeline rendering suite.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Encode full-frame video segments directly
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted. Extends [ADR-0027](./0027-encode-two-full-frame-layers-through-dual-pipes.md).
|
|
4
|
+
**Date:** 2026-11-06
|
|
5
|
+
|
|
6
|
+
A segment with one full-frame video layer, an identity transform, and an opaque
|
|
7
|
+
timeline background now decodes and re-encodes the source range directly into
|
|
8
|
+
the delivery segment. It skips the background overlay, fit, presentation, and
|
|
9
|
+
composite filter graph, but keeps the same delivery encoder and container as
|
|
10
|
+
every other segment.
|
|
11
|
+
|
|
12
|
+
## Context
|
|
13
|
+
|
|
14
|
+
The representative 60-minute 50/50 gate contains 24 seconds per minute of
|
|
15
|
+
full-frame video on a single track. The composite path still built a color
|
|
16
|
+
background, overlaid the video, normalized timestamps, fitted the frame, and
|
|
17
|
+
then encoded. The video source already matches the stage, so those filters are
|
|
18
|
+
identity work except for the fps/format normalization.
|
|
19
|
+
|
|
20
|
+
Stream-copy was considered and deferred: it requires keyframe alignment, matching
|
|
21
|
+
codec parameters, and a concat-compatible bitstream across composition and media
|
|
22
|
+
segments. A direct re-encode avoids those risks while removing the composite
|
|
23
|
+
overhead.
|
|
24
|
+
|
|
25
|
+
## Decision
|
|
26
|
+
|
|
27
|
+
- `encodeVideoSegmentDirect` seeks to the clip's source start, maps the first
|
|
28
|
+
video stream, drops audio, applies
|
|
29
|
+
`fps=<project fps>,format=rgba,format=yuv420p`, and encodes with the normal
|
|
30
|
+
delivery encoder/container settings. The RGBA round trip preserves the
|
|
31
|
+
composite path's color conversion and keeps decoded pixels identical.
|
|
32
|
+
- The fast path requires one visual layer whose source is a video matching the
|
|
33
|
+
stage dimensions, an identity transform, and no keyframes, blend, transition,
|
|
34
|
+
or opacity. The timeline background must be opaque.
|
|
35
|
+
- `FRAMEYARD_DISABLE_VIDEO_DIRECT=1` forces the composite path for A/B
|
|
36
|
+
measurements.
|
|
37
|
+
- Video with transforms, opacity, overlays, keyframes, transitions, or a
|
|
38
|
+
non-matching source/stage size keeps the composite path. Composition and
|
|
39
|
+
nested-timeline segments are unaffected.
|
|
40
|
+
|
|
41
|
+
## Considered Options
|
|
42
|
+
|
|
43
|
+
- **Stream-copy the video range.** Deferred: keyframe alignment and mixed-codec
|
|
44
|
+
concat compatibility need a larger probe and validation surface. A direct
|
|
45
|
+
re-encode reaches the target without that risk.
|
|
46
|
+
- **Keep the composite graph.** Rejected for this case: the background, fit, and
|
|
47
|
+
presentation filters are identity work for a full-frame video.
|
|
48
|
+
- **Use the dual-pipe path.** Not applicable: there is only one visual layer.
|
|
49
|
+
|
|
50
|
+
## Consequences
|
|
51
|
+
|
|
52
|
+
- Full-frame video segments still re-encode, so every delivered segment shares
|
|
53
|
+
the same codec, profile, and container settings. The conformance risk is lower
|
|
54
|
+
than stream-copy.
|
|
55
|
+
- The representative 60-minute 50/50 gate drops the composite path for its
|
|
56
|
+
single-video seconds while retaining it for overlays, PIP, images, and
|
|
57
|
+
multi-layer segments.
|
|
58
|
+
- A 60-second full-frame video-only gate fell from 10.65 s to 7.61 s (−28.5%)
|
|
59
|
+
with pixel-identical decoded frames.
|
|
60
|
+
- The representative 60-minute 50/50 gate fell from 16.82 to 15.57 minutes
|
|
61
|
+
(−7.4%) with 180 direct video segments, exact 108,000 frames, clean scratch,
|
|
62
|
+
and a byte-identical output file.
|
|
63
|
+
- Tests cover a full-frame video timeline through the direct path and the
|
|
64
|
+
existing media/blend/keyframe/timeline suites.
|
|
65
|
+
- The fast path is internal; there is no CLI or JSON contract change.
|
package/docs/migration-0.4.md
CHANGED
|
@@ -259,11 +259,11 @@ export default {
|
|
|
259
259
|
|
|
260
260
|
## 11. Update preview and render expectations
|
|
261
261
|
|
|
262
|
-
No command changes. Preview loads the composition in a same-origin iframe; `check`, `still`, and `render` navigate directly to the same document. The composition host is injected by the server, so keep composition entries listed in `
|
|
262
|
+
No command changes. Preview loads the composition in a same-origin iframe; `check`, `still`, and `render` navigate directly to the same document. The composition host is injected by the server, so keep composition entries listed in `fy.config.js` and run through `frameyard dev` or `frameyard render`, not `file://`.
|
|
263
263
|
|
|
264
264
|
## Migration checklist
|
|
265
265
|
|
|
266
|
-
- [ ] Change `
|
|
266
|
+
- [ ] Change `fy.config.js` entries to `.html`.
|
|
267
267
|
- [ ] Move metadata to `<fy-composition>`.
|
|
268
268
|
- [ ] Move `defaultProps` to the `data-fy="props"` JSON block.
|
|
269
269
|
- [ ] Move `mount()` markup into the document.
|
package/docs/preview-testing.md
CHANGED
|
@@ -25,9 +25,28 @@ expose the same seek/settle contract the renderer uses.
|
|
|
25
25
|
- Order: `await ready`, `await load()`, `await seek(frame)`, `await settle()`.
|
|
26
26
|
- The non-embedded page owns transport and audio; only the embedded host exposes
|
|
27
27
|
`window.__fyTimeline`.
|
|
28
|
-
- A static composition source carries `static: true` and no
|
|
29
|
-
`durationInFrames`. The host loads the layer once and skips per-frame
|
|
30
|
-
the layer remains visible while the timeline playhead moves.
|
|
28
|
+
- A static composition source carries `metadata.static: true` and no
|
|
29
|
+
`metadata.durationInFrames`. The host loads the layer once and skips per-frame
|
|
30
|
+
seek; the layer remains visible while the timeline playhead moves.
|
|
31
|
+
- The embedded host projects the serialized resolved timeline with the shared
|
|
32
|
+
scene module on every seek. Do not add a preview-only clip or transition
|
|
33
|
+
model; give the scene module what it needs instead.
|
|
34
|
+
- A keyframed clip updates the layer wrapper's inline `opacity` and `transform`
|
|
35
|
+
on every seek. Assert those styles or the rendered stage after `settle()`.
|
|
36
|
+
Markers are `.keyframe-marker` elements with a `data-frame` absolute timeline
|
|
37
|
+
frame and a channel/value tooltip; clicking one seeks the playhead.
|
|
38
|
+
- Inside a transition window both clips are active. The incoming layer's inline
|
|
39
|
+
`opacity` is `clipOpacity × eased progress` and it stacks above the outgoing
|
|
40
|
+
layer on the same track. The strip draws a `.transition` band with a
|
|
41
|
+
`from → to` tooltip; it is read-only.
|
|
42
|
+
- The strip opens with a `.ruler-scale` carrying five `.ruler-tick` labels at
|
|
43
|
+
0/25/50/75/100% of the frame range. One `.playhead` line spans the ruler and
|
|
44
|
+
every track, driven by a `--playhead-factor` custom property on
|
|
45
|
+
`#timeline-inner`; `--progress` on `#scrub` fills the transport rail.
|
|
46
|
+
- Both preview pages share the chrome in `src/server/preview-style.ts` and its
|
|
47
|
+
design tokens. The chrome serves its own Figtree and Space Mono from
|
|
48
|
+
`/__frameyard/framework/preview/fonts/` and does not link the project font
|
|
49
|
+
sheet. The embedded host page has none of this chrome.
|
|
31
50
|
|
|
32
51
|
## Waiting for a frame
|
|
33
52
|
|
|
@@ -51,9 +70,11 @@ the renderer, or the CLI, and before pushing or publishing.
|
|
|
51
70
|
|
|
52
71
|
## Notes
|
|
53
72
|
|
|
54
|
-
-
|
|
55
|
-
the
|
|
56
|
-
|
|
73
|
+
- Timeline layer iframes pin `color-scheme: light` on both the iframe element
|
|
74
|
+
and the embedded document root to match the production capture context.
|
|
75
|
+
The iframe element's `color-scheme` does not reach the embedded document, so a
|
|
76
|
+
composition that declares a dark scheme would otherwise paint an opaque canvas
|
|
77
|
+
base and hide every layer beneath it.
|
|
57
78
|
- A running `dev` server keeps the `dist/` modules it loaded. Restart it after
|
|
58
79
|
`pnpm build`.
|
|
59
80
|
- The timeline preview host is not the production compositor; compare pixels at
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Status:** Accepted
|
|
4
4
|
**Initial acceptance:** 2026-10-27
|
|
5
|
-
**Last design revision:** 2026-11-
|
|
5
|
+
**Last design revision:** 2026-11-04
|
|
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.
|
|
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. Architecture decisions that changed the baseline are recorded in `docs/adr/`.
|
|
8
8
|
|
|
9
9
|
## 1. Product contract
|
|
10
10
|
|
|
@@ -31,7 +31,7 @@ The agent-enabled success criterion additionally requires an agent using the off
|
|
|
31
31
|
### Project
|
|
32
32
|
|
|
33
33
|
- Projects use ordinary HTML, CSS, static assets, and native JavaScript controllers, either inline in a composition fragment or in external modules.
|
|
34
|
-
- `
|
|
34
|
+
- `fy.config.js` explicitly maps project IDs to source files. There is no directory scanning. It has three optional maps:
|
|
35
35
|
- `format`: the project's fixed `width`, `height`, and `fps`.
|
|
36
36
|
- `compositions`: IDs mapped to `.html` composition fragments.
|
|
37
37
|
- `timelines`: IDs mapped to JSON timeline documents.
|
|
@@ -55,7 +55,7 @@ The agent-enabled success criterion additionally requires an agent using the off
|
|
|
55
55
|
|
|
56
56
|
- A timeline is a versioned JSON document. It references compositions and nested timelines by project ID and media clips by project-relative path.
|
|
57
57
|
- 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`.
|
|
58
|
-
- 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
|
|
58
|
+
- 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 unless a timeline-level `transitions` entry explicitly pairs them; clips on different tracks can. Touching endpoints are allowed but are not a transition. A transition's window is the overlap, and its declared geometry is `from.start < to.start < from.end < to.end`. See [ADR 0019](./adr/0019-make-same-track-overlap-the-transition-window.md).
|
|
59
59
|
- 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.
|
|
60
60
|
- Clip sources are:
|
|
61
61
|
- a composition, identified by its project ID;
|
|
@@ -64,12 +64,14 @@ The agent-enabled success criterion additionally requires an agent using the off
|
|
|
64
64
|
- 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. A clip whose composition source is static behaves like an image: it must declare `duration`, must use `sourceIn: 0`, has no intrinsic source duration, and contributes no audio or transcripts. The renderer captures one transparent PNG per composition/props pair and reuses it across segments.
|
|
65
65
|
- 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, transform, and blend mode apply to the whole nested result. A blend mode inside a nested timeline sees only that timeline's own backdrop; a transparent nested timeline does not see the parent's layers, and a declared nested background is part of the group. Cycles are rejected statically. Timeline clips do not override props of compositions inside the nested timeline.
|
|
66
66
|
- Media clips use local project files only. Video and audio durations, dimensions, and audio presence are probed before frame production. Images and static composition clips require an explicit duration. A video or audio clip that exceeds its source range is an error; an omitted duration uses the remaining source.
|
|
67
|
-
- 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.
|
|
68
|
-
- Visual clips may
|
|
67
|
+
- 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. Rotation expands to the full rotated bounds rather than clipping corners.
|
|
68
|
+
- Visual clips may animate `x`, `y`, `scale`, `rotation`, and `opacity` with a per-channel `keyframes` object. Keys use clip-local integer frames; `0` is the clip start boundary and `duration` the end boundary. Keys hold before the first and after the last key, never extend the clip, and use a CSS timing function on the interval after each key. A keyed channel cannot also be an explicit scalar in `transform`. `opacity` and `scale` are clamped after easing; position and rotation may overshoot. `keyframes.scale` is mutually exclusive with an explicit `width`/`height` box. Timeline document version stays 1.
|
|
69
|
+
- A timeline may declare `transitions`: `{ from, to, kind, easing? }`, where `kind` is `dissolve` on a video track or `crossfade` on an audio track, and `easing` uses the keyframe CSS timing-function vocabulary. Both clips stay on one track and overlap; the window is `to.start .. from.end`. A dissolve composites the incoming clip above the outgoing clip with alpha `clipOpacity × eased progress`; both kinds apply equal-power audio gain (`sqrt(1 - t)` and `sqrt(t)`) to contributed audio or to the two audio clips. An overlap without a declared transition, a non-overlapping pair, reversed or contained geometry, cross-track pairs, a kind that does not match the track, duplicate participation, and unknown kinds are errors. Timeline document version stays 1.
|
|
70
|
+
- Visual clips may declare a static `blend` mode. It uses CSS `mix-blend-mode` semantics against the accumulated backdrop of all lower layers and the timeline background, respects the clip's source alpha, and defaults to `normal`. The supported modes are `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, and `exclusion`; `hue`, `saturation`, `color`, and `luminosity` are rejected. There are no animated blend modes, masks, filters, or other effects.
|
|
69
71
|
- Visual clips contribute their audio to the timeline mix unless muted: composition audio, embedded video audio, and nested timeline audio. Static compositions contribute no audio; clip audio settings on a static composition are ignored with a warning. 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.
|
|
70
72
|
- 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.
|
|
71
73
|
- A timeline background is transparent by default. Blend modes see the accumulated backdrop including this background. 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.
|
|
72
|
-
- The preview provides a read-only timeline view: tracks, clips, playhead, program monitor, and diagnostics. It is not a visual editor.
|
|
74
|
+
- The preview provides a read-only timeline view: tracks, clips, keyframe markers, playhead, program monitor, and diagnostics. It is not a visual editor.
|
|
73
75
|
|
|
74
76
|
## 3. Deterministic execution
|
|
75
77
|
|
|
@@ -81,10 +83,10 @@ The timeline frame is the master clock. A composition's frame is derived from th
|
|
|
81
83
|
- JavaScript animation runs in the controller's `update()`, which must be synchronous and seek-safe. It cannot depend on previously visited frames.
|
|
82
84
|
- Declarative bindings apply once per load, before the controller mounts. Controller-created DOM is the controller's responsibility.
|
|
83
85
|
- Independent `requestAnimationFrame` loops, timers, CSS transitions, wall-clock reads, and unseeded randomness are not supported animation drivers.
|
|
84
|
-
- Timeline
|
|
86
|
+
- Timeline clip keyframes animate `x`, `y`, `scale`, `rotation`, and `opacity` from the integer clip-local frame. They are evaluated against absolute timeline frames, so segment boundaries do not change values. Preview evaluates the exact CSS timing function; production generates FFmpeg expressions and approximates the curve. Blend modes remain static. Transition progress is likewise evaluated from the absolute timeline frame; only the incoming clip carries transition alpha during a dissolve, and both kinds use the same eased progress for equal-power audio gain.
|
|
85
87
|
- Preview and production seek composition clips explicitly. The timeline compositor is deterministic given the captured clip frames, media frames, clip settings, and segment boundaries.
|
|
86
88
|
- The resolved format—declared width, height, and fps combined with the project format—is part of the immutable frame environment, alongside props and loaded assets. Layouts that use viewport units or percentages therefore depend on the resolved format by design; preview and production always resolve the same values.
|
|
87
|
-
- 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.
|
|
89
|
+
- Assets, remote data, images, fonts, generated audio, transcripts, and media are resolved before frame production. Project fonts are vendored locally, recorded in `fy.fonts.json`, and injected as `@font-face` rules; the host waits for `document.fonts.ready` and fails a used face that reports an error. 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.
|
|
88
90
|
- AI model inference is never part of the loading phase, controller lifecycle, or frame production.
|
|
89
91
|
- 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.
|
|
90
92
|
- 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.
|
|
@@ -100,12 +102,12 @@ Preview and production share composition behavior and capture composition frames
|
|
|
100
102
|
|
|
101
103
|
## 4. Preview and production rendering
|
|
102
104
|
|
|
103
|
-
Preview and production execute the same composition sources and host runtime. They differ in which component drives the frame clock, which compositor combines timeline layers, and whether frames are captured.
|
|
105
|
+
Preview and production execute the same composition sources and host runtime. They differ in which component drives the frame clock, which compositor combines timeline layers, and whether frames are captured. Both also project the same timeline scene from the resolved timeline: the ordered active clips with their transition roles, composite order, frame-to-source mapping, capture modes, and presentation defaults. The scene projection is browser-safe; capture sessions, audio mixing, and both compositors stay separate.
|
|
104
106
|
|
|
105
107
|
The preview provides:
|
|
106
108
|
|
|
107
109
|
- 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. A static composition loads once and disables the transport.
|
|
108
|
-
- 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. A static composition layer stays loaded and is not seeked per frame.
|
|
110
|
+
- Timeline preview: timeline selection, a read-only track/clip view with keyframe markers, 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. A static composition layer stays loaded and is not seeked per frame.
|
|
109
111
|
- A bounded pool of active composition and nested-timeline clips. It loads clips around the playhead instead of preloading a whole long timeline.
|
|
110
112
|
|
|
111
113
|
The preview server serves each composition fragment at its project path, wraps it in the fixed document shell, 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.
|
|
@@ -114,15 +116,15 @@ Production composition rendering uses one Chromium session per composition. An a
|
|
|
114
116
|
|
|
115
117
|
Production timeline rendering is segmented and parallel:
|
|
116
118
|
|
|
117
|
-
1. Run static timeline validation and
|
|
119
|
+
1. Run static timeline validation, resolve the timeline, and project the scene and render plan.
|
|
118
120
|
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.
|
|
119
|
-
3. In parallel workers, capture the active composition and nested-timeline clip frames for each segment with Chromium and
|
|
120
|
-
4. Decode media clips, apply trims, fit,
|
|
121
|
+
3. In parallel workers, capture the active composition and nested-timeline clip frames for each segment with Chromium and stream them into lossless FFV1 intermediates. Independent layers of one segment are captured concurrently under a global FIFO session limit, and loaded composition sessions are reused across segments when their entry, props, and transparency match. Transparent clips are captured with an alpha-preserving background. A static composition is captured once per props variant as a transparent PNG and reused as an image layer across segments and workers. A segment with one full-frame, untransformed composition and no keyframes, blend, transition, or opacity skips the FFV1 intermediate and the composite, encoding the capture directly into the delivery segment over the timeline background. A full-frame composition base plus a static composition overlay (including a static PIP box) uses two image pipes into one FFmpeg process, which composites the background, base, and scaled overlay before the delivery encode; Windows keeps the segmented fallback. A single full-frame video with an identity transform re-encodes its source range directly, skipping the composite filter graph while keeping the delivery encoder settings.
|
|
122
|
+
4. Decode media clips, apply trims, fit, presentation keyframes, rotation into an expanded bound, and opacity, composite the segment with FFmpeg, and encode H.264, HEVC, or VP9.
|
|
121
123
|
5. Join the ordered segments with a stream copy. Segment boundaries never change frame order or timing.
|
|
122
124
|
6. Mix the timeline audio once with FFmpeg and mux it into the final output.
|
|
123
125
|
7. Publish the output atomically; delete scratch intermediates on success and failure.
|
|
124
126
|
|
|
125
|
-
The default timeline worker count is 8, configurable up to the machine's capacity. With `--encoder nvenc` the count is capped at eight concurrent encode sessions, the consumer driver limit; a larger `--workers` value produces an `ENCODER_WARNING` and the lower count. 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
|
|
127
|
+
The default timeline worker count is 8, configurable up to the machine's capacity. With `--encoder nvenc` the count is capped at eight concurrent encode sessions, the consumer driver limit; a larger `--workers` value produces an `ENCODER_WARNING` and the lower count. 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 37.28 minutes with four workers; it is stricter than the 50/50 target mix because every frame is an HTML composition capture. Capture was 48.9% of worker time, FFV1 23.2%, composite 24.7%, and session setup 2.8%. Composition captures now stream PNG buffers into FFV1, and a segment with one full-frame, untransformed composition skips the intermediate and composite. The 60-second one-layer gate fell from 43.27 s to 27.64 s, and the full 60-minute gate completed in 19.83 minutes with eight workers; the direct output was byte-identical to the FFV1-and-composite output. Reusing loaded sessions reduced the 60-minute gate further to 18.78 minutes with eight sessions instead of 360. The two-layer PIP 60-second gate improved from 62.79 s to 59.60 s with streaming and to 43.78 s with parallel layer capture under a global session limit. A full-frame composition base plus a static composition overlay uses dual image pipes; the full-frame 60-second workload fell from 53.27 s to 34.62 s, the PIP 60-second workload from 43.78 s to 33.90 s, and the full two-layer 60-minute gates completed in 29.69 minutes (full-frame) and 25.75 minutes (PIP) with eight workers. The production representative 60-minute gate — 50/50 layer-seconds, 60/30/10 occupancy, 360 clips, 540 segments, and video re-encoded rather than stream-copied — completed in 16.82 minutes with eight workers, exact 108,000 frames, and clean scratch. Direct full-frame video re-encoding later reduced it to 15.57 minutes with a byte-identical file; a 60-second video-only gate fell from 10.65 to 7.61 seconds. The 10-minute one-layer gate improved from 211.8 s to 192.6 s by reusing loaded composition sessions across segments. The fast path copies full-frame, untransformed video whose source matches the project format and whose source keyframes (I-frames) fall on segment boundaries; media that needs scaling or transforms is re-encoded and adds cost not included in that projection. Timeline clip keyframes add per-frame filter expressions and, for animated opacity or scale, an alpha-mask pass per affected layer; the keyframe benchmark reports current timings and is not part of the 60-minute projection.
|
|
126
128
|
|
|
127
129
|
The renderer uses a bounded segment plan, a worker pool, and a scratch budget. It never publishes a partial output, and it cleans scratch on success and failure. Segment boundaries preserve timeline frame order and timing; the final audio mix is muxed once.
|
|
128
130
|
|
|
@@ -153,16 +155,23 @@ A render fails immediately on an uncaught exception, unhandled rejection, requir
|
|
|
153
155
|
|
|
154
156
|
Project and composition commands are:
|
|
155
157
|
|
|
156
|
-
- `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.
|
|
158
|
+
- `init [directory] [--local-models]`: 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. Without a directory, an interactive terminal prompts for a project name (default `my-video`) and creates that directory; non-interactive and `--json` runs use `my-video`. `--local-models` also installs the optional workspace policy, OpenCode permissions, and Pi settings.
|
|
157
159
|
- `dev`: start the preview with composition and timeline modes.
|
|
158
160
|
- `render <id> [--out video]`: render a timeline, an animated composition, or a static composition. Timeline renders accept `--workers <n>` and `--scratch <dir>`; composition renders accept `--concurrency <n>` from 1 to 16. `--container <mp4|mov|mkv|webm>` and `--codec <h264|hevc|vp9>` select the delivery pair (MP4/H.264 by default; WebM defaults to VP9), and `--encoder <auto|software|nvenc|qsv>` selects how it is encoded; `auto` is the default and probes NVENC then Quick Sync before falling back to software. The output extension must match the container. The default output is `out/<id>.<ext>`. A static composition writes `out/<id>.png` and rejects `--container`, `--codec`, `--encoder`, and `--concurrency`. The JSON result is a discriminated union with `kind: "video"` or `kind: "image"`.
|
|
161
|
+
- `context`: print a read-only project digest with the format, composition metadata, prop keys and types, controller declarations, timeline clip summaries, unique assets, installed fonts, diagnostics, active policy, and next commands. It never launches Chromium.
|
|
162
|
+
- `lint <id>`: run static validation, timeline resolution, scene and keyframe projection for every timeline frame, a controller determinism scan, and media metadata probing without Chromium. It does not run `validateProps` or composition runtime; `check` remains the full gate.
|
|
163
|
+
- `inspect <image>` and `compare <first> <second>`: decode images through FFmpeg and report blank/alpha/color/content facts or a pixel and 3×3 region diff. These are text-only inspection aids, not a substitute for viewing a still.
|
|
164
|
+
- `add --list`, `add local-models`, and `add <composition|timeline> <id> --template <name>`: list templates and scaffolds, install the optional local-model host scaffold, or write a valid template entry and print the exact `fy.config.js` registration snippet. `add` never edits the config and never overwrites an existing file or registered id.
|
|
165
|
+
- `font search`, `font add <id>`, `font add --file`, `font add --url`, `font list`, `font verify`, and `font remove <id>`: search Fontsource, vendor a Fontsource/local/URL font into `assets/fonts/<id>/`, record it in `fy.fonts.json`, inspect its license/hash status, or remove it. The server serves the generated `@font-face` sheet at `/__frameyard/fonts.css` and injects it into composition documents. Fontsource installs pin `npmVersion` and vendor the package license; URL imports require HTTPS outside loopback tests and record an unknown license only in the `full` tier.
|
|
159
166
|
- `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. A static composition is loaded and inspected once and returns `kind: "static"` with `framesChecked: 1`.
|
|
160
167
|
- `still <id> --frame <spec>`: capture one or more requested frames as PNGs through the clip capture and timeline composite path. A static composition exposes exactly frame 0. `<spec>` is a frame number (`42`), an inclusive range (`2-12`), or a comma-separated list (`0,118-126,240`); duplicates are removed preserving the first occurrence, input order is preserved, and one invocation captures at most 32 unique frames. There is no step or sequence mode. A single frame keeps the existing `--out <file.png>` behavior; multiple frames use `--out <directory>` and write `still-<id>-<frame>.png` files, defaulting to the current working directory. Frames and output conflicts are validated before Chromium, all frames are captured to temporary files before publication, and a failed capture publishes no files. JSON results add `frames` and keep the singular `frame` and `path` only when one frame was captured. See [ADR 0013](./adr/0013-capture-bounded-multi-frame-stills-for-inspection.md).
|
|
161
|
-
- `doctor`: inspect core requirements
|
|
168
|
+
- `doctor`: inspect core requirements, optional integrations, the active resource policy, installed font manifests, and basic CPU/load/memory resources without repairing or downloading anything.
|
|
162
169
|
|
|
163
|
-
`check`, `still`, and `render` run a Node-side static validation
|
|
170
|
+
`lint`, `check`, `still`, and `render` run a Node-side static validation; `check`, `still`, and `render` then launch Chromium. For a timeline it validates the JSON document, tracks, clips, references, nested timelines, cycles, frame-rate compatibility, overlaps and transitions, source bounds, media, transforms, keyframes, audio settings, props, and every referenced composition. Static validation and the composition host read the composition grammar through one browser-safe reader with document and fragment scopes. 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.
|
|
164
171
|
|
|
165
|
-
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.
|
|
172
|
+
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. Project font acquisition is a separate authoring operation: `fy.fonts.json` records vendored files, licenses, and SHA-256 values; there is no shared font cache and no runtime font CDN. Fontsource search and download use the public Fontsource API and pinned CDN versions; local files and explicit HTTPS URLs cover other sources. The generated `@font-face` sheet is served at `/__frameyard/fonts.css` and injected before author styles.
|
|
173
|
+
|
|
174
|
+
A workspace `fy.policy.json` gates expensive operations. The nearest file from the working directory upward applies, independent of `fy.config.js`; `FRAMEYARD_POLICY` replaces it. `light` allows one still frame and blocks `check`, `tts`, `transcribe`, and `render`; `authoring` allows four still frames and `check` at low priority and blocks the three heavy operations; `full` allows everything up to the 32-frame still cap. Font list/verify/search are available in every tier; `font add` requires authoring or full; `font remove`, `font add --force`, and an unlicensed `font add --url` require full. A file can only tighten its tier. `--profile shared-cpu` caps timeline workers at two and composition concurrency at one and lowers process priority. `tts`, `transcribe`, and `render` accept `--dry-run`, which validates inputs and returns `plan`, `commandLine`, `prerequisites`, and `blockers` without writing outputs, loading a model, launching Chromium, or encoding. The policy is a guardrail, not a security boundary; see [ADR 0022](./adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md).
|
|
166
175
|
|
|
167
176
|
The built-in server serves native ES modules, static assets, composition entries, timeline documents, and the timeline preview host. It wraps each configured composition fragment in the fixed document shell, injects the framework import map and composition host, and serves 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.
|
|
168
177
|
|
|
@@ -184,20 +193,24 @@ The skill:
|
|
|
184
193
|
- Uses the normal project format, HTML composition contract, JSON timeline contract, optional controller, and CLI available to humans.
|
|
185
194
|
- Does not introduce an agent-only runtime, MCP server, hosted control plane, or separate rendering API.
|
|
186
195
|
- Triggers for Frameyard authoring and operation rather than claiming to be a general-purpose media-generation skill.
|
|
187
|
-
- Uses Frameyard's audio wrappers instead of invoking `qwen-tts` or `whisper-cli` directly.
|
|
188
|
-
- 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.
|
|
196
|
+
- Uses Frameyard's audio wrappers instead of invoking `qwen-tts`, `sherpa-onnx-offline-tts`, or `whisper-cli` directly.
|
|
197
|
+
- Includes references for deterministic HTML authoring, timeline JSON authoring, tracks, clips, source ranges, props, media, nested timelines, static transforms, clip keyframes, clip transitions, audio, validation, diagnostics, common mistakes, subtitle and karaoke recipes, JSON command results, long-form worker/scratch settings, and failure recovery, plus a compact quick reference for limited-context hosts.
|
|
189
198
|
- Invokes speech synthesis or transcription only when requested or explicitly approved as part of the brief.
|
|
190
199
|
- Does not overwrite or regenerate existing generated assets without permission.
|
|
200
|
+
- Uses `lint` for the edit loop, `check` for the full gate, and `--dry-run` to hand off heavy operations when the workspace resource policy blocks them.
|
|
201
|
+
- Respects `fy.policy.json` and does not edit the policy file, host configuration, or skill source.
|
|
191
202
|
- Runs `check`, inspects representative `still` images when the host supports image viewing, and discloses when visual inspection was impossible.
|
|
192
203
|
- Does not claim success when checks fail or rendering is partial.
|
|
193
204
|
|
|
194
205
|
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.
|
|
195
206
|
|
|
207
|
+
Local-model host setup is optional and separate from a Frameyard project: `scaffolds/local-models/` contains an `authoring` policy, OpenCode permissions, and Pi settings that a workspace can copy when a local model and rendering share the machine. Without the scaffold, the CLI defaults to `full` and no host configuration applies.
|
|
208
|
+
|
|
196
209
|
## 8. Local speech synthesis
|
|
197
210
|
|
|
198
211
|
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.
|
|
199
212
|
|
|
200
|
-
|
|
213
|
+
The qwen engine supports CustomVoice mode only:
|
|
201
214
|
|
|
202
215
|
- A named speaker is required.
|
|
203
216
|
- An optional style instruction and language may be supplied.
|
|
@@ -207,7 +220,15 @@ The `tts` command integrates [`qwentts.cpp`](https://github.com/ServeurpersoCom/
|
|
|
207
220
|
- One invocation produces one frozen 24 kHz mono WAV from one complete text input.
|
|
208
221
|
- Automatic long-text chunking, streaming, and alternative audio formats are deferred.
|
|
209
222
|
|
|
210
|
-
|
|
223
|
+
`tts --engine supertonic` integrates [`sherpa-onnx`](https://github.com/k2-fsa/sherpa-onnx) through its external `sherpa-onnx-offline-tts` executable and the Supertonic 3 model archive:
|
|
224
|
+
|
|
225
|
+
- The engine covers 31 languages and requires one of their language codes, a numeric speaker id, and a directory containing the seven model files.
|
|
226
|
+
- The engine writes its native WAV; Frameyard normalizes it to the same frozen 24 kHz mono WAV contract with FFmpeg and records `sourceSampleRate` and `sourceChannels` in the manifest.
|
|
227
|
+
- The manifest records the seven model identities, the pinned sherpa-onnx revision, and the model archive name.
|
|
228
|
+
- Supertonic expression tags belong in the text; the qwen-only style and device options are rejected for this engine.
|
|
229
|
+
- The upstream Supertonic repository is archived and its weights use the OpenRAIL-M license. Users acquire the executable and weights explicitly; Frameyard does not redistribute them.
|
|
230
|
+
|
|
231
|
+
Users acquire and provide models explicitly. Frameyard does not build `qwentts.cpp` or `sherpa-onnx`, download or redistribute models, or imply that upstream code licenses settle model-weight licensing. Executable resolution uses an explicit option, then environment configuration, then PATH.
|
|
211
232
|
|
|
212
233
|
## 9. Local transcription and timed text
|
|
213
234
|
|
|
@@ -247,14 +268,14 @@ Generated artifacts follow a shared contract:
|
|
|
247
268
|
|
|
248
269
|
- Outputs are written atomically to explicit paths.
|
|
249
270
|
- Existing outputs are not overwritten unless `--force` is supplied.
|
|
250
|
-
- A versioned project-local provenance manifest records artifact content hashes, generator details, model identities, and parameters. The default manifest is `
|
|
271
|
+
- A versioned project-local provenance manifest records artifact content hashes, generator details, model identities, and parameters. The default manifest is `fy.assets.json` in the project root; `--manifest` selects another path.
|
|
251
272
|
- Transcription provenance also identifies the source asset by content hash.
|
|
252
273
|
- Partial operations leave neither apparently valid artifacts nor manifest entries.
|
|
253
|
-
- `doctor` validates executables, model readability, capabilities, and supported integration versions without modifying the environment.
|
|
274
|
+
- `doctor` validates executables, model readability, capabilities, and supported integration versions when the upstream CLI exposes a version, without modifying the environment.
|
|
254
275
|
|
|
255
276
|
Each operation runs one inference subprocess, and the official agent workflow executes inference sequentially to avoid unexpected CPU/GPU contention. There is no arbitrary fixed timeout. Cancellation terminates the child process and removes partial outputs.
|
|
256
277
|
|
|
257
|
-
Release documentation must identify the exact `qwentts.cpp` and `whisper.cpp` revisions against which the wrappers are tested. Frameyard does not claim compatibility with arbitrary upstream revisions.
|
|
278
|
+
Release documentation must identify the exact `qwentts.cpp`, `sherpa-onnx`, and `whisper.cpp` revisions against which the wrappers are tested. Frameyard does not claim compatibility with arbitrary upstream revisions.
|
|
258
279
|
|
|
259
280
|
## 11. Verification strategy
|
|
260
281
|
|
|
@@ -262,7 +283,7 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
|
|
|
262
283
|
|
|
263
284
|
- HTML compositions validate metadata, default props, bindings, conditionals, repeats, and controller declarations, including inline/external parity and the exactly-one-controller rule.
|
|
264
285
|
- Static composition validation catches metadata, root/controller count, binding, animation, script, element, and asset-reference errors before Chromium. It resolves declared format values against the project format and reports an unresolved format to the command rather than the document. A composition with neither time attribute is static; `fps` without `duration-in-frames`, `data-fy-anim-*`, media, and transcripts on a static composition are errors.
|
|
265
|
-
- Timeline JSON validation catches schema, track, clip, reference, cycle, frame-rate, overlap, bounds, media, transform, audio, and props errors before Chromium.
|
|
286
|
+
- Timeline JSON validation catches schema, track, clip, reference, cycle, frame-rate, overlap, transition, bounds, media, transform, audio, and props errors before Chromium.
|
|
266
287
|
- Diagnostics carry stable codes and source locations or JSON paths; JSON results expose the composition or timeline entry, clips, tracks, and top-level diagnostics.
|
|
267
288
|
- Sequential, individual, random-order, and parallel composition renders produce equivalent images; timeline segment workers produce ordered output and identical captured composition frames.
|
|
268
289
|
- Determinism checks compare captured composition pixels or stills. The final encoded bytes and the FFmpeg composite versus Chromium preview are not reproducibility oracles.
|
|
@@ -274,18 +295,19 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
|
|
|
274
295
|
- Nested timelines preserve group opacity and transform semantics; cycles fail static validation.
|
|
275
296
|
- Media clips trim and fit deterministically; images require durations; source overruns fail.
|
|
276
297
|
- Props overrides for timeline composition clips merge over clip props and are reflected in the render.
|
|
277
|
-
- `check` exercises every animated frame and inspects a static composition once; `still` follows the clip capture and timeline composite path. A static composition render captures one PNG; a timeline reuses one static image capture per props variant.
|
|
278
|
-
- 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
|
|
298
|
+
- `check` exercises every animated frame and inspects a static composition once; it also evaluates every keyframe channel at every timeline frame. `still` follows the clip capture and timeline composite path. A static composition render captures one PNG; a timeline reuses one static image capture per props variant.
|
|
299
|
+
- 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 37.28 minutes with four workers before streaming and the direct-segment fast path; the same gate completed in 19.83 minutes with eight workers after ADR 0024 and 18.78 minutes after session reuse (ADR 0026), with exact output, clean scratch, and a byte-identical file. The ten-minute gate projects about 24 minutes at four workers. Independent layers are captured concurrently under a global session limit (ADR 0025); the two-layer PIP 60-second gate fell from 59.60 s to 43.78 s with parallel captures. A full-frame composition base plus a static composition overlay encodes through dual image pipes (ADR 0027); the full-frame 60-second workload fell from 53.27 s to 34.62 s, the PIP 60-second workload from 43.78 s to 33.90 s, and the full two-layer 60-minute gates completed in 29.69 minutes (full-frame) and 25.75 minutes (PIP) with eight workers. The representative 60-minute 50/50 production gate completed in 16.82 minutes with eight workers, exact 108,000 frames, clean scratch, and no video stream-copy; direct full-frame video re-encoding reduced it to 15.57 minutes with a byte-identical file. Loaded composition sessions are reused across segments (ADR 0026); the 10-minute one-layer gate fell from 211.8 s to 192.6 s, and the full gate opened eight sessions instead of 360.
|
|
279
300
|
- Golden-frame tests cover representative composition and timeline visual behavior.
|
|
280
|
-
- 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.
|
|
301
|
+
- Normal CI uses fake `qwen-tts`, `sherpa-onnx-offline-tts`, and `whisper-cli` executables and small fixtures to verify arguments, artifacts, manifests, cancellation, and errors without model downloads or GPU hardware.
|
|
281
302
|
- Real-model integration tests are explicitly opt-in and use documented upstream revisions.
|
|
282
303
|
- Agent-workflow evaluations verify source preservation, provenance, validation, still inspection behavior, failure reporting, and final renders.
|
|
304
|
+
- Local-model evals are opt-in and run against a llama.cpp router: eight authoring tasks, first-pass lint, repair turns, token and wall-time counts, and sampled server CPU/RSS/GPU. They exercise the same quick reference and CLI as an agent, never render, and stay outside `pnpm check`.
|
|
283
305
|
|
|
284
306
|
## 12. Deferred
|
|
285
307
|
|
|
286
308
|
These are scope boundaries, not permanent rejections:
|
|
287
309
|
|
|
288
|
-
-
|
|
310
|
+
- Animated blend modes, filters, masks, and audio fades
|
|
289
311
|
- Full visual timeline editing, drag-and-drop, clip handles, and undo/redo
|
|
290
312
|
- Timeline-level variables and prop overrides for compositions inside nested timelines
|
|
291
313
|
- Track solo and video-track visibility
|
|
@@ -299,9 +321,12 @@ These are scope boundaries, not permanent rejections:
|
|
|
299
321
|
- Built-in subtitle rendering, automatic caption burn-in, and declarative karaoke elements
|
|
300
322
|
- Nested `data-fy-repeat` scopes and a general template language
|
|
301
323
|
- Declarative JSON Schema validation
|
|
324
|
+
- A shared font cache, hosted font catalog, or first-class Google Fonts/Fontshare/Bunny providers; Fontsource plus local/HTTPS imports cover acquisition
|
|
325
|
+
- Typography roles, font-pair presets, a preview font picker, and variable axes other than `wght`
|
|
302
326
|
- Arbitrary executable scripts in composition HTML
|
|
303
327
|
- Automatic upstream builds, model downloads, weight redistribution, and environment repair
|
|
304
328
|
- Agent-only runtimes, MCP servers, hosted agent control planes, and host-specific skill installers
|
|
329
|
+
- Automatic idle scheduling, job queues, and OS-level resource enforcement; the workspace policy is a guardrail, not a sandbox
|
|
305
330
|
- React or other UI-framework adapters in the core
|
|
306
331
|
- Public plugin APIs and a broad integration ecosystem
|
|
307
332
|
- Dynamic composition or timeline metadata derived from props
|
|
@@ -320,4 +345,4 @@ These are scope boundaries, not permanent rejections:
|
|
|
320
345
|
|
|
321
346
|
## 13. Release posture
|
|
322
347
|
|
|
323
|
-
Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and
|
|
348
|
+
Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and features and contract changes are documented explicitly until those contracts stabilize.
|
package/examples/blend/README.md
CHANGED
|
@@ -62,11 +62,11 @@ examples/blend/
|
|
|
62
62
|
timelines/blend.timeline.json
|
|
63
63
|
assets/ink.mp4 procedural 12-second source clip
|
|
64
64
|
scripts/make-ink.sh regenerate assets/ink.mp4
|
|
65
|
-
|
|
65
|
+
fy.config.js
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
The timeline lives in `timelines/blend.timeline.json`; the project format and
|
|
69
|
-
entries are fixed in `
|
|
69
|
+
entries are fixed in `fy.config.js`. `backdrop.html` is a static composition
|
|
70
70
|
that omits all dimensions and time metadata, so it inherits the project's
|
|
71
71
|
1920×1080 size. `aurora.html` is a static composition that declares its
|
|
72
72
|
dimensions but no time metadata. `labels.html` is an animated composition and
|
|
@@ -81,7 +81,7 @@ pnpm build
|
|
|
81
81
|
node dist/cli.js check blend-timeline --project examples/blend
|
|
82
82
|
node dist/cli.js still blend-timeline --frame 15,45,75,105,135,165,195,225,255,285,315,345 --project examples/blend --out blend-stills
|
|
83
83
|
node dist/cli.js still blend-timeline --frame 285 --project examples/blend --out blend-soft-light.png
|
|
84
|
-
node dist/cli.js render blend-timeline --project examples/blend
|
|
84
|
+
FRAMEYARD_POLICY=full node dist/cli.js render blend-timeline --project examples/blend
|
|
85
85
|
node dist/cli.js dev --project examples/blend
|
|
86
86
|
```
|
|
87
87
|
|
package/examples/demo/README.md
CHANGED
|
@@ -19,13 +19,13 @@ From the repository root:
|
|
|
19
19
|
pnpm build
|
|
20
20
|
node dist/cli.js check demo-timeline --project examples/demo
|
|
21
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
|
|
22
|
+
FRAMEYARD_POLICY=full node dist/cli.js render demo-timeline --project examples/demo --props examples/demo/props/demo.json
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The composition remains renderable on its own:
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
|
|
28
|
+
FRAMEYARD_POLICY=full node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Start the interactive preview with:
|