frameyard 0.5.0 → 0.7.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 +100 -0
- package/GLOSSARY.md +152 -24
- package/README.md +99 -31
- package/dist/assets/process.js.map +1 -1
- package/dist/assets/provenance.js.map +1 -1
- package/dist/cli.js +326 -40
- package/dist/cli.js.map +1 -1
- package/dist/composition/attributes.d.ts.map +1 -1
- package/dist/composition/diagnostics.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/doctor.d.ts.map +1 -1
- package/dist/doctor.js +79 -0
- 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/html/validate.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 +96 -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 +21 -0
- package/dist/preview/timeline-host.d.ts.map +1 -0
- package/dist/preview/timeline-host.js +308 -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 +368 -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 +126 -1
- package/dist/renderer/ffmpeg.d.ts.map +1 -1
- package/dist/renderer/ffmpeg.js +450 -6
- package/dist/renderer/ffmpeg.js.map +1 -1
- package/dist/renderer/index.d.ts +28 -3
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +138 -38
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/timeline.d.ts +146 -0
- package/dist/renderer/timeline.d.ts.map +1 -0
- package/dist/renderer/timeline.js +1098 -0
- package/dist/renderer/timeline.js.map +1 -0
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/media.d.ts.map +1 -1
- package/dist/runtime/media.js +3 -6
- package/dist/runtime/media.js.map +1 -1
- package/dist/runtime/video.d.ts +13 -0
- package/dist/runtime/video.d.ts.map +1 -0
- package/dist/runtime/video.js +41 -0
- package/dist/runtime/video.js.map +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +90 -24
- package/dist/scaffold.js.map +1 -1
- package/dist/server/composition-html.d.ts.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/transcript.js.map +1 -1
- 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/adr/0013-capture-bounded-multi-frame-stills-for-inspection.md +33 -0
- package/docs/preview-testing.md +53 -0
- package/docs/v0.1-design.md +138 -87
- 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 +5 -3
- package/skills/frameyard/README.md +7 -5
- package/skills/frameyard/SKILL.md +19 -13
- package/skills/frameyard/references/common-mistakes.md +1 -0
- package/skills/frameyard/references/deterministic-authoring.md +2 -1
- package/skills/frameyard/references/diagnostics.md +1 -1
- package/skills/frameyard/references/failure-recovery.md +3 -2
- package/skills/frameyard/references/json-results.md +15 -6
- package/skills/frameyard/references/subtitles-karaoke.md +2 -2
- package/skills/frameyard/references/timelines.md +143 -0
- package/skills/frameyard/scripts/frameyard.mjs +20 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: frameyard
|
|
3
|
-
description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML
|
|
4
|
-
frameyard-api-version: 0.
|
|
3
|
+
description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML compositions and JSON timelines. Use when a user asks to build or operate a Frameyard project, produce parameterized video, assemble clips into a timeline, add a local TTS or transcription asset through Frameyard's wrappers, or diagnose a Frameyard render. Do not use for general-purpose media generation outside a Frameyard project.
|
|
4
|
+
frameyard-api-version: 0.7.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Frameyard
|
|
@@ -11,19 +11,20 @@ Use Frameyard's public project format and CLI. Do not introduce an agent-only ru
|
|
|
11
11
|
## First decisions
|
|
12
12
|
|
|
13
13
|
1. Find the project: look for `video.config.js` from the working directory upward, or run `npx frameyard init <directory>` to scaffold one.
|
|
14
|
-
2. Read the
|
|
15
|
-
3. Read the composition HTML, its optional controller, and the props
|
|
14
|
+
2. Read the `format`, `compositions`, and `timelines` maps in `video.config.js`. Entries are explicit `.html` and `.json` documents; there is no directory scanning.
|
|
15
|
+
3. Read the timeline JSON, the referenced composition HTML, its optional controller, and the props files before changing behavior.
|
|
16
16
|
4. If the task needs speech or transcription, confirm that inference is requested or explicitly approved before producing assets.
|
|
17
17
|
|
|
18
18
|
## Workflow
|
|
19
19
|
|
|
20
|
-
1. **Author**
|
|
21
|
-
2. **
|
|
22
|
-
3. **
|
|
23
|
-
4. **
|
|
24
|
-
5. **
|
|
25
|
-
6. **
|
|
26
|
-
7. **
|
|
20
|
+
1. **Author compositions** as full HTML documents with one `<fy-composition>` root. Put metadata, default props, markup, CSS animation, bindings, audio, and transcripts in the document. Use an optional controller for Canvas 2D, WebGL, karaoke, or per-frame computation CSS cannot express. Read [deterministic authoring](references/deterministic-authoring.md), [bindings](references/bindings.md), and [controllers](references/controllers.md).
|
|
21
|
+
2. **Assemble the timeline** in JSON: project format, video/audio tracks, composition, media, and nested-timeline clips, source ranges, static transforms, and audio. Read [timelines](references/timelines.md).
|
|
22
|
+
3. **Provide props** as JSON. Composition props merge over defaults; timeline render props are a map keyed by composition clip ID. Validate domain-specific requirements with a controller's `validateProps()`.
|
|
23
|
+
4. **Produce optional assets** with `npx frameyard tts` and `npx frameyard transcribe`, never with the upstream executables. Read [audio assets](references/audio-assets.md). Do not overwrite or regenerate existing generated assets without permission.
|
|
24
|
+
5. **Validate** with `npx frameyard check <id> --json`, where `<id>` is a timeline or composition. Static validation runs before Chromium; then every frame is evaluated. Read the structured `diagnostics` array. Read [diagnostics](references/diagnostics.md) and [common mistakes](references/common-mistakes.md).
|
|
25
|
+
6. **Inspect** representative frames with `npx frameyard still <id> --frame <spec> --out <path> --json`, where `<spec>` is a frame number, an inclusive range like `2-12`, or a comma-separated list with at most 32 unique frames. With one frame, `--out` is a `.png` file; with multiple frames, it is a directory and the files are `still-<id>-<frame>.png`. View the PNGs when the host supports image viewing. If visual inspection is impossible, say so explicitly.
|
|
26
|
+
7. **Render** with `npx frameyard render <id> [--out <file>] [--props <file>] --json`. Composition renders accept `--concurrency <n>`; timeline renders accept `--workers <n>` and `--scratch <dir>`. `--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, defaulting to `auto`. The output extension must match the container. The default output is `out/<id>.<ext>` in the project root.
|
|
27
|
+
8. **Report** the project path, timeline and composition entries, controller, props, generated assets, provenance manifest, validation results, diagnostics, still paths and inspection status, final video path, and any failures.
|
|
27
28
|
|
|
28
29
|
Never claim success when `check` fails, a still is missing, a render exits nonzero, or visual inspection was skipped. A partial or unpublished render is not a successful result.
|
|
29
30
|
|
|
@@ -33,13 +34,17 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
|
|
|
33
34
|
- Keep the current output files unless the user explicitly approves replacement. Use `--force` only with that approval.
|
|
34
35
|
- Use `--project <dir>` when not running from the project root.
|
|
35
36
|
- `render` writes to `out/<id>.mp4` in the project root unless `--out` is passed. Explicit `--out` paths are relative to the current working directory.
|
|
36
|
-
- Use `--concurrency <n>` only
|
|
37
|
+
- Use `--concurrency <n>` only for composition renders; the default is `1` and the maximum is `16`. Use `--workers <n>` for timeline renders; the default is `8`. Timeline renders also accept `--scratch <dir>` for FFV1 intermediates.
|
|
38
|
+
- `still --frame <spec>` accepts `n`, `n-m`, or a comma-separated list; ranges are inclusive, duplicates are removed preserving the first occurrence, order is preserved, and one call captures at most 32 unique frames. Do not use `still` as an image-sequence exporter. With multiple frames, `--out` is a directory and files are named `still-<id>-<frame>.png`; with one frame, `--out` is a `.png` file.
|
|
39
|
+
- `--encoder` selects the encoder: `auto` (default) probes NVENC, then Quick Sync, then the codec's software encoder; `software` forces `libx264`, `libx265`, or `libvpx-vp9`; `nvenc` and `qsv` force a vendor. `--codec <h264|hevc|vp9>` and `--container <mp4|mov|mkv|webm>` select the delivery pair: MP4 and MOV hold H.264/HEVC with AAC, MKV holds H.264/HEVC/VP9, and WebM holds VP9 with Opus and defaults to VP9. `--out` must use the container's extension. The probe is a real one-frame encode. Encoders, codecs, and containers change encoded bytes and file size, not captured frames; do not switch them to fix a visual difference. With `nvenc`, timeline `--workers` is capped at eight, the consumer driver session limit, and a larger value reports an `ENCODER_WARNING`. HEVC MP4/MOV files use the `hvc1` tag and do not play in browsers without platform HEVC decoding.
|
|
40
|
+
- For a timeline, `--props` is a JSON object keyed by top-level composition clip ID. For a composition, it is the props object.
|
|
37
41
|
- Use `npx frameyard doctor --json` before reporting an environment problem. It inspects without repairing or downloading.
|
|
38
42
|
- Read [JSON results](references/json-results.md) for result and error shapes, and [failure recovery](references/failure-recovery.md) for actionable error codes.
|
|
39
43
|
|
|
40
44
|
## Authoring rules
|
|
41
45
|
|
|
42
46
|
- A composition is a full HTML document with one `<fy-composition width height fps duration-in-frames>` root and a `data-fy="props"` default-props block.
|
|
47
|
+
- A timeline is a versioned JSON document with explicit tracks and clips. Sources are composition IDs, nested timeline IDs, or local video/image/audio paths. Timeline frames, sourceIn, and duration are integer frames. No remote URLs.
|
|
43
48
|
- Bindings are explicit: `data-fy-text`, `data-fy-attr-*`, `data-fy-class-*`, `data-fy-if`, and `data-fy-repeat` on `<template>`. Primitive top-level props are also `--fy-prop-<name>` CSS variables.
|
|
44
49
|
- CSS `@keyframes` and the Web Animations API are seeked from the frame clock. `data-fy-anim-start`, `data-fy-anim-duration`, and `data-fy-anim-easing` schedule one animation per element in frames.
|
|
45
50
|
- Frame-state variables on `:root`: `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames`.
|
|
@@ -50,7 +55,7 @@ Never claim success when `check` fails, a still is missing, a render exits nonze
|
|
|
50
55
|
|
|
51
56
|
## Determinism guardrails
|
|
52
57
|
|
|
53
|
-
- A frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production.
|
|
58
|
+
- A composition frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production. A timeline frame derives from its active clips and the same composition rules.
|
|
54
59
|
- Use `random("stable:key")` from `frameyard/runtime`; never use `Math.random()`.
|
|
55
60
|
- Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
|
|
56
61
|
- Finish remote data, fonts, and image loading during the loading phase. Network requests are blocked during frame capture.
|
|
@@ -80,6 +85,7 @@ When the CLI version differs from `frameyard-api-version`, pin it with `npx fram
|
|
|
80
85
|
|
|
81
86
|
## References
|
|
82
87
|
|
|
88
|
+
- [Timelines](references/timelines.md)
|
|
83
89
|
- [Deterministic HTML authoring](references/deterministic-authoring.md)
|
|
84
90
|
- [Bindings](references/bindings.md)
|
|
85
91
|
- [Controllers](references/controllers.md)
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
## Validation
|
|
55
55
|
|
|
56
56
|
- Replacing `check` with a single `still`.
|
|
57
|
+
- Using `still` as an image-sequence exporter, adding `--step`-style workflows, or requesting more than 32 unique frames in one call.
|
|
57
58
|
- Ignoring warnings or `diagnostics` in `--json` output.
|
|
58
59
|
- Claiming success after a nonzero exit, a missing still, or skipped visual inspection.
|
|
59
60
|
- Using `--overwrite` or `--force` without permission.
|
|
@@ -217,9 +217,10 @@ Images declared in the document are awaited by the host. Static fonts should be
|
|
|
217
217
|
```sh
|
|
218
218
|
npx frameyard check hello --json
|
|
219
219
|
npx frameyard still hello --frame 42 --out hello-42.png --json
|
|
220
|
+
npx frameyard still hello --frame 0,118-126 --out out/stills --json
|
|
220
221
|
```
|
|
221
222
|
|
|
222
|
-
`check` runs static validation before Chromium, then evaluates every frame and validates audio and declared transcripts without capturing or encoding. Read the structured `diagnostics` array. Do not replace `check` with a single still.
|
|
223
|
+
`check` runs static validation before Chromium, then evaluates every frame and validates audio and declared transcripts without capturing or encoding. Read the structured `diagnostics` array. Do not replace `check` with a single still. Inspect several representative frames in one `still` call with a range or list; the batch seeks sequentially in one session and is limited to 32 unique frames.
|
|
223
224
|
|
|
224
225
|
## Metadata is fixed
|
|
225
226
|
|
|
@@ -75,7 +75,7 @@ Static validation covers:
|
|
|
75
75
|
- `controller`: `{ kind: "inline" }`, `{ kind: "external", src }`, or `null`
|
|
76
76
|
- `diagnostics`: all static and runtime diagnostics, deduplicated
|
|
77
77
|
|
|
78
|
-
`check` also exposes `metadata`, `framesChecked`, `audio.details`, and `transcripts.details`. `still` exposes `
|
|
78
|
+
`check` also exposes `metadata`, `framesChecked`, `audio.details`, and `transcripts.details`. `still` exposes `frames`, `width`, and `height`, plus `frame` and `path` when exactly one frame was captured. `render` exposes the output path, dimensions, frame count, concurrency, bytes, and audio/transcript clip counts.
|
|
79
79
|
|
|
80
80
|
Failures use the same envelope with `error.code`, `error.message`, optional `error.details`, and top-level `diagnostics`.
|
|
81
81
|
|
|
@@ -32,7 +32,7 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
|
|
|
32
32
|
| `ELEMENT_UNKNOWN` | Unknown `fy-*` element | Use `fy-composition`/`fy-transcript` or a normal element. |
|
|
33
33
|
| `CONTENT_OUTSIDE_ROOT` | Visual content outside `<fy-composition>` | Move it inside the root. |
|
|
34
34
|
| `PROPS_INVALID` | Invalid props JSON or `validateProps()` failure | Fix the props file or validator. |
|
|
35
|
-
| `FRAME_OUT_OF_RANGE` | Frame outside `0..durationInFrames - 1` | Choose a valid
|
|
35
|
+
| `FRAME_OUT_OF_RANGE` | Frame outside `0..durationInFrames - 1` | Choose valid frames; a batch error lists every invalid frame and the valid range. |
|
|
36
36
|
| `AUDIO_INVALID` | Bad clip or unreadable/unsupported audio | Use a local project-root-relative file with a decodable audio stream. |
|
|
37
37
|
| `AUDIO_WARNING` | Audio timeline overrun or silent embedded media | Adjust timing/trim or remove the silent source. |
|
|
38
38
|
| `MEDIA_INVALID` | Invalid media source or attributes | Use local, seekable files and valid `data-fy-*` values. |
|
|
@@ -57,7 +57,8 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
|
|
|
57
57
|
|
|
58
58
|
## Behavior to rely on
|
|
59
59
|
|
|
60
|
-
- A failed render writes to a temporary file and does not publish a partial
|
|
60
|
+
- A failed render writes to a temporary file and does not publish a partial output.
|
|
61
|
+
- A multi-frame still captures every frame to a temporary file first; a failed capture publishes no stills. `OUTPUT_EXISTS` lists every conflicting output path before capture starts.
|
|
61
62
|
- Failed TTS/transcription operations leave no artifact and no manifest entry.
|
|
62
63
|
- Existing outputs are replaced only with `--overwrite` (renders/stills) or `--force` (generated assets).
|
|
63
64
|
- Cancellation terminates the child inference process and cleans temporary work.
|
|
@@ -11,7 +11,7 @@ Every non-interactive command supports `--json`. Stdout contains one JSON docume
|
|
|
11
11
|
}
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Additional fields are command-specific and stable within the 0.4 contract.
|
|
14
|
+
Additional fields are command-specific and stable within the 0.4 contract. `check`, `still`, and `render` report `elapsedMs`, the wall time of the operation from static validation and preflight through the final frame or publication; CLI argument parsing is excluded.
|
|
15
15
|
|
|
16
16
|
## Error envelope
|
|
17
17
|
|
|
@@ -100,15 +100,19 @@ Static validation diagnostics include source locations. Runtime diagnostics may
|
|
|
100
100
|
"composition": "hello",
|
|
101
101
|
"entry": "compositions/hello.html",
|
|
102
102
|
"controller": { "kind": "external", "src": "compositions/hello.controller.js" },
|
|
103
|
-
"
|
|
104
|
-
|
|
103
|
+
"frames": [
|
|
104
|
+
{ "frame": 0, "path": "/absolute/path/out/stills/still-hello-0.png" },
|
|
105
|
+
{ "frame": 42, "path": "/absolute/path/out/stills/still-hello-42.png" }
|
|
106
|
+
],
|
|
105
107
|
"width": 1920,
|
|
106
108
|
"height": 1080,
|
|
107
109
|
"diagnostics": [],
|
|
108
|
-
"elapsedMs":
|
|
110
|
+
"elapsedMs": 1400
|
|
109
111
|
}
|
|
110
112
|
```
|
|
111
113
|
|
|
114
|
+
`frames` is always present and follows the expanded `--frame` order. `frame` and `path` are also present only when exactly one frame was captured. `--frame` accepts a number, an inclusive range (`2-12`), or a comma-separated list; at most 32 unique frames are captured per invocation. With multiple frames, `--out` is a directory and the files are named `still-<id>-<frame>.png`.
|
|
115
|
+
|
|
112
116
|
## render
|
|
113
117
|
|
|
114
118
|
```json
|
|
@@ -126,6 +130,9 @@ Static validation diagnostics include source locations. Runtime diagnostics may
|
|
|
126
130
|
"durationSeconds": 30,
|
|
127
131
|
"framesRendered": 900,
|
|
128
132
|
"concurrency": 1,
|
|
133
|
+
"codec": "h264",
|
|
134
|
+
"container": "mp4",
|
|
135
|
+
"encoder": "libx264",
|
|
129
136
|
"bytes": 1234567,
|
|
130
137
|
"audio": { "clips": 1 },
|
|
131
138
|
"transcripts": { "clips": 1 },
|
|
@@ -134,7 +141,7 @@ Static validation diagnostics include source locations. Runtime diagnostics may
|
|
|
134
141
|
}
|
|
135
142
|
```
|
|
136
143
|
|
|
137
|
-
A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published. Without `--out`, the final path is `out/<composition-id
|
|
144
|
+
A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published. Without `--out`, the final path is `out/<composition-id>.<ext>` inside the project root, using the container extension. `concurrency` is the number of browser sessions actually used; it is capped by `durationInFrames` and by the CLI maximum of 16. `container` is `mp4`, `mov`, `mkv`, or `webm`; `codec` is `h264`, `hevc`, or `vp9`; and `encoder` is the resolved encoder (`libx264`, `libx265`, `libvpx-vp9`, `h264_nvenc`, `hevc_nvenc`, `h264_qsv`, or `hevc_qsv`). Timeline results also report the effective `workers` count after the encoder session cap, with an `ENCODER_WARNING` diagnostic when it was reduced.
|
|
138
145
|
|
|
139
146
|
## tts
|
|
140
147
|
|
|
@@ -190,7 +197,7 @@ A successful render means the final path exists and was published atomically. A
|
|
|
190
197
|
|
|
191
198
|
## doctor
|
|
192
199
|
|
|
193
|
-
Doctor reports findings rather than throwing for environment problems. `ok: true` means the inspection completed; `healthy` says whether required checks, manifest integrity, explicitly supplied models, and integration compatibility passed.
|
|
200
|
+
Doctor reports findings rather than throwing for environment problems. `ok: true` means the inspection completed; `healthy` says whether required checks, manifest integrity, explicitly supplied models, and integration compatibility passed. `ffmpeg-opus-encoder` and `ffmpeg-video-encoders` are optional: the video check probes each supported encoder with a one-frame encode and lists any that FFmpeg advertises but cannot open (an old driver, for example), and neither check affects `healthy`.
|
|
194
201
|
|
|
195
202
|
```json
|
|
196
203
|
{
|
|
@@ -201,6 +208,8 @@ Doctor reports findings rather than throwing for environment problems. `ok: true
|
|
|
201
208
|
{ "name": "node", "required": true, "status": "ok", "detail": "v22.12.0" },
|
|
202
209
|
{ "name": "ffmpeg", "required": true, "status": "ok", "detail": "ffmpeg version ..." },
|
|
203
210
|
{ "name": "ffmpeg-encoders", "required": true, "status": "ok", "detail": "libx264 and aac are available." },
|
|
211
|
+
{ "name": "ffmpeg-opus-encoder", "required": false, "status": "ok", "detail": "libopus is available for VP9/WebM audio." },
|
|
212
|
+
{ "name": "ffmpeg-video-encoders", "required": false, "status": "error", "detail": "Unavailable: h264_nvenc (Driver does not support the required nvenc API version); hevc_nvenc (Driver does not support the required nvenc API version). Available: libx264, libx265, libvpx-vp9, h264_qsv, hevc_qsv." },
|
|
204
213
|
{ "name": "ffprobe", "required": true, "status": "ok", "detail": "ffprobe version ..." },
|
|
205
214
|
{ "name": "chromium", "required": true, "status": "ok", "detail": "/path/to/chrome" },
|
|
206
215
|
{ "name": "manifest", "required": false, "status": "ok", "detail": "2 artifacts verified." }
|
|
@@ -72,10 +72,10 @@ Apply that value to text styling inside the controller's `update({ frame })`. Do
|
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
74
|
npx frameyard check karaoke --json
|
|
75
|
-
npx frameyard still karaoke --frame
|
|
75
|
+
npx frameyard still karaoke --frame 118-126 --out karaoke-frames --json
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Inspect several frames across a word boundary. A single still cannot prove that highlighting is synchronized.
|
|
78
|
+
Inspect several frames across a word boundary in one batch. A single still cannot prove that highlighting is synchronized.
|
|
79
79
|
|
|
80
80
|
## Derived exports
|
|
81
81
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Timelines
|
|
2
|
+
|
|
3
|
+
A timeline is a project-local JSON document that assembles composition and media clips on tracks and renders as one video. Compositions remain reusable HTML sources; a composition clip is one placement of a composition with its own source range and props.
|
|
4
|
+
|
|
5
|
+
## Project configuration
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
export default {
|
|
9
|
+
format: { width: 1920, height: 1080, fps: 30 },
|
|
10
|
+
compositions: {
|
|
11
|
+
intro: "./compositions/intro.html",
|
|
12
|
+
},
|
|
13
|
+
timelines: {
|
|
14
|
+
main: "./timelines/main.timeline.json",
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `format` fixes width, height, and fps for every timeline. It is required when the project lists timelines.
|
|
20
|
+
- Composition and timeline IDs share one namespace and must be unique.
|
|
21
|
+
- A composition used in a timeline must match the project fps exactly. A different size is fitted by the clip.
|
|
22
|
+
- Composition-only projects remain valid without `format` or `timelines`.
|
|
23
|
+
|
|
24
|
+
## Timeline document
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"version": 1,
|
|
29
|
+
"name": "Main edit",
|
|
30
|
+
"tracks": [
|
|
31
|
+
{ "id": "v1", "kind": "video" },
|
|
32
|
+
{ "id": "v2", "kind": "video" },
|
|
33
|
+
{ "id": "a1", "kind": "audio", "gain": 1, "muted": false }
|
|
34
|
+
],
|
|
35
|
+
"clips": [
|
|
36
|
+
{
|
|
37
|
+
"id": "intro",
|
|
38
|
+
"track": "v1",
|
|
39
|
+
"source": { "kind": "composition", "id": "intro" },
|
|
40
|
+
"start": 0,
|
|
41
|
+
"duration": 150,
|
|
42
|
+
"sourceIn": 0,
|
|
43
|
+
"props": { "title": "Hello" },
|
|
44
|
+
"transform": { "fit": "contain", "x": 0, "y": 0, "scale": 1, "rotation": 0, "opacity": 1 },
|
|
45
|
+
"audio": { "muted": false, "gain": 1 }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "logo",
|
|
49
|
+
"track": "v2",
|
|
50
|
+
"source": { "kind": "image", "src": "assets/logo.png" },
|
|
51
|
+
"start": 30,
|
|
52
|
+
"duration": 60,
|
|
53
|
+
"transform": { "fit": "contain", "width": 480, "height": 270, "x": 640, "y": -320, "rotation": 0, "opacity": 0.9 }
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "music",
|
|
57
|
+
"track": "a1",
|
|
58
|
+
"source": { "kind": "audio", "src": "assets/music.wav" },
|
|
59
|
+
"start": 0,
|
|
60
|
+
"duration": 210,
|
|
61
|
+
"audio": { "gain": 0.5 }
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `version` is `1`.
|
|
68
|
+
- `tracks` and `clips` are required. Track order is bottom-to-top for video and irrelevant for audio.
|
|
69
|
+
- `background` is an optional color; the final video composites over black.
|
|
70
|
+
- `durationInFrames` is optional. The duration is derived from the latest clip end unless declared; a declared duration may extend but not cut clips.
|
|
71
|
+
- Unknown fields are errors. No transitions, effects, keyframes, or track solo/visibility in 0.6.
|
|
72
|
+
|
|
73
|
+
## Sources
|
|
74
|
+
|
|
75
|
+
| `source.kind` | Fields | Notes |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `composition` | `id` | Project composition ID. The clip may set `props`. |
|
|
78
|
+
| `timeline` | `id` | Nested timeline ID. Rendered as a group. |
|
|
79
|
+
| `video` | `src` | Local MP4/WebM, project-relative. |
|
|
80
|
+
| `image` | `src` | Local PNG/JPEG/WebP. `duration` is required. |
|
|
81
|
+
| `audio` | `src` | Local WAV/MP3/M4A/AAC or a video file used only for audio. |
|
|
82
|
+
|
|
83
|
+
Media paths resolve from the project root. No remote URLs.
|
|
84
|
+
|
|
85
|
+
## Time and trims
|
|
86
|
+
|
|
87
|
+
- `start` is the timeline frame where the clip begins.
|
|
88
|
+
- `sourceIn` is the first source frame. It defaults to `0` and must be `0` for images.
|
|
89
|
+
- `duration` is the number of timeline frames. It is optional for compositions, nested timelines, video, and audio; it defaults to the rest of the source. Images require it.
|
|
90
|
+
- A clip that exceeds its source is an error.
|
|
91
|
+
- Clips on one track cannot overlap. Touching endpoints are allowed. Use separate tracks for overlays.
|
|
92
|
+
|
|
93
|
+
## Presentation
|
|
94
|
+
|
|
95
|
+
Visual clips accept a static transform:
|
|
96
|
+
|
|
97
|
+
- `fit`: `contain`, `cover`, or `stretch`.
|
|
98
|
+
- `width`, `height`: optional clip box in output pixels. Both are required together and cannot be combined with `scale`. `fit` applies inside the box, and `x`/`y` offset the box center. Without them, the project frame is the box.
|
|
99
|
+
- `x`, `y`: pixel offsets from the centered fitted result.
|
|
100
|
+
- `scale`: multiplier after fitting to the project frame; an alternative to `width`/`height`.
|
|
101
|
+
- `rotation`: degrees around the center.
|
|
102
|
+
- `opacity`: 0–1.
|
|
103
|
+
|
|
104
|
+
No keyframes or animated transforms in 0.6.
|
|
105
|
+
|
|
106
|
+
## Audio
|
|
107
|
+
|
|
108
|
+
- Composition, video, and nested-timeline clips contribute audio automatically unless `audio.muted` is true.
|
|
109
|
+
- Timeline audio clips live on audio tracks. Audio tracks support `gain` and `muted`.
|
|
110
|
+
- `audio.gain` on a clip is 0–1 and multiplies the source audio.
|
|
111
|
+
- There are no linked A/V pairs. For a J/L cut, mute the visual clip and add an audio clip with a different `sourceIn`.
|
|
112
|
+
- Audio is mixed once for the whole timeline and muxed into the final output.
|
|
113
|
+
|
|
114
|
+
## Nested timelines
|
|
115
|
+
|
|
116
|
+
A nested timeline clip behaves as one layer: its internal tracks are composited first, then the parent clip's opacity and transform apply to the whole group. Nested audio is included. Cycles are rejected statically. Timeline clips do not override props of compositions inside a nested timeline in 0.6.
|
|
117
|
+
|
|
118
|
+
## Commands
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
npx frameyard check main --json
|
|
122
|
+
npx frameyard still main --frame 42 --out main-42.png --json
|
|
123
|
+
npx frameyard still main --frame 0,118-126 --out out/stills --json
|
|
124
|
+
npx frameyard render main --props props/main.json --workers 8 --scratch /path/to/scratch --json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `--props` on a timeline is a JSON object keyed by top-level composition clip ID; values merge over the clip's `props`.
|
|
128
|
+
- `--workers` is 1–16. The default is 8. With `--encoder nvenc` it is capped at 8 concurrent encode sessions and a larger value reports an `ENCODER_WARNING`.
|
|
129
|
+
- `--scratch` selects the FFV1 intermediate directory. Use a disk with enough space; intermediates are cleaned after the render.
|
|
130
|
+
- `--container <mp4|mov|mkv|webm>` selects the delivery container (default `mp4`); `--codec <h264|hevc|vp9>` selects the codec, and `--encoder <auto|software|nvenc|qsv>` selects how it is encoded. MP4 and MOV hold H.264/HEVC with AAC; MKV holds all three; WebM holds VP9 with Opus and defaults to VP9. `auto` is the default and probes NVENC, then Quick Sync, then software; `software` forces `libx264`, `libx265`, or `libvpx-vp9`. FFV1 intermediates stay lossless and software-encoded, HEVC keeps the `hvc1` tag in MP4/MOV, and the output extension must match the container.
|
|
131
|
+
- `--concurrency` is composition-only.
|
|
132
|
+
- `render` defaults to `out/<timeline-id>.mp4`.
|
|
133
|
+
- `check` evaluates every timeline frame and composition frame. `still` captures one or more requested frames through the same clip capture and composite path; a multi-frame still resolves the timeline, starts one server, and reuses one scratch directory. The `--frame` spec and 32-frame cap are the same as for compositions.
|
|
134
|
+
- `npx frameyard dev` lists timelines in the preview; the timeline view has a program monitor, read-only tracks and clips, playhead, playback, audio controls, and diagnostics.
|
|
135
|
+
|
|
136
|
+
## Determinism and performance
|
|
137
|
+
|
|
138
|
+
- A timeline frame derives only from its active clips, their composition frames, props, assets, and the render plan.
|
|
139
|
+
- Composition frames are captured with Chromium; transparent compositions preserve alpha.
|
|
140
|
+
- Timeline production captures active composition and nested-timeline clips to lossless FFV1, composites segments with FFmpeg, concatenates the ordered segments, and mixes audio once.
|
|
141
|
+
- Preview composites in Chromium; production composites with FFmpeg. Small scaling, alpha, and video color differences are expected. Parity is guaranteed at clip capture, not at final encoded pixels.
|
|
142
|
+
- Final output bytes are not a reproducibility guarantee.
|
|
143
|
+
- Long timelines are segmented and bounded. The reference target and measured worst cases are documented in `docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md`.
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const
|
|
8
|
+
const packageRoot = path.resolve(scriptDir, "../../..");
|
|
9
9
|
const configured = process.env.FRAMEYARD_CLI;
|
|
10
10
|
|
|
11
|
+
// Only prefer a sibling build inside the frameyard package itself. A copied
|
|
12
|
+
// skill sits three levels below its host directory, which may hold an
|
|
13
|
+
// unrelated dist/cli.js.
|
|
14
|
+
function packageCli() {
|
|
15
|
+
const manifestPath = path.join(packageRoot, "package.json");
|
|
16
|
+
const cliPath = path.join(packageRoot, "dist", "cli.js");
|
|
17
|
+
if (!existsSync(manifestPath) || !existsSync(cliPath)) return undefined;
|
|
18
|
+
try {
|
|
19
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
20
|
+
return manifest.name === "frameyard" ? cliPath : undefined;
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const localCli = packageCli();
|
|
27
|
+
|
|
11
28
|
let command;
|
|
12
29
|
let args;
|
|
13
30
|
if (configured) {
|
|
@@ -22,7 +39,7 @@ if (configured) {
|
|
|
22
39
|
command = configured;
|
|
23
40
|
args = process.argv.slice(2);
|
|
24
41
|
}
|
|
25
|
-
} else if (
|
|
42
|
+
} else if (localCli) {
|
|
26
43
|
command = process.execPath;
|
|
27
44
|
args = [localCli, ...process.argv.slice(2)];
|
|
28
45
|
} else {
|