frameyard 0.0.0-stage → 0.2.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 +69 -0
- package/GLOSSARY.md +145 -0
- package/LICENSE +21 -0
- package/README.md +311 -2
- package/dist/assets/executable.d.ts +16 -0
- package/dist/assets/executable.d.ts.map +1 -0
- package/dist/assets/executable.js +59 -0
- package/dist/assets/executable.js.map +1 -0
- package/dist/assets/files.d.ts +4 -0
- package/dist/assets/files.d.ts.map +1 -0
- package/dist/assets/files.js +30 -0
- package/dist/assets/files.js.map +1 -0
- package/dist/assets/integrations.d.ts +15 -0
- package/dist/assets/integrations.d.ts.map +1 -0
- package/dist/assets/integrations.js +15 -0
- package/dist/assets/integrations.js.map +1 -0
- package/dist/assets/process.d.ts +29 -0
- package/dist/assets/process.d.ts.map +1 -0
- package/dist/assets/process.js +97 -0
- package/dist/assets/process.js.map +1 -0
- package/dist/assets/provenance.d.ts +77 -0
- package/dist/assets/provenance.d.ts.map +1 -0
- package/dist/assets/provenance.js +302 -0
- package/dist/assets/provenance.js.map +1 -0
- package/dist/assets/transcribe.d.ts +38 -0
- package/dist/assets/transcribe.d.ts.map +1 -0
- package/dist/assets/transcribe.js +357 -0
- package/dist/assets/transcribe.js.map +1 -0
- package/dist/assets/tts.d.ts +29 -0
- package/dist/assets/tts.d.ts.map +1 -0
- package/dist/assets/tts.js +180 -0
- package/dist/assets/tts.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +795 -0
- package/dist/cli.js.map +1 -0
- package/dist/contracts.d.ts +106 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +297 -0
- package/dist/contracts.js.map +1 -0
- package/dist/doctor.d.ts +56 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +476 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/main.d.ts +19 -0
- package/dist/preview/main.d.ts.map +1 -0
- package/dist/preview/main.js +480 -0
- package/dist/preview/main.js.map +1 -0
- package/dist/project.d.ts +16 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +88 -0
- package/dist/project.js.map +1 -0
- package/dist/renderer/audio.d.ts +31 -0
- package/dist/renderer/audio.d.ts.map +1 -0
- package/dist/renderer/audio.js +88 -0
- package/dist/renderer/audio.js.map +1 -0
- package/dist/renderer/browser.d.ts +22 -0
- package/dist/renderer/browser.d.ts.map +1 -0
- package/dist/renderer/browser.js +175 -0
- package/dist/renderer/browser.js.map +1 -0
- package/dist/renderer/ffmpeg.d.ts +58 -0
- package/dist/renderer/ffmpeg.d.ts.map +1 -0
- package/dist/renderer/ffmpeg.js +268 -0
- package/dist/renderer/ffmpeg.js.map +1 -0
- package/dist/renderer/index.d.ts +72 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +281 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/transcripts.d.ts +30 -0
- package/dist/renderer/transcripts.d.ts.map +1 -0
- package/dist/renderer/transcripts.js +86 -0
- package/dist/renderer/transcripts.js.map +1 -0
- package/dist/runtime/clock.d.ts +5 -0
- package/dist/runtime/clock.d.ts.map +1 -0
- package/dist/runtime/clock.js +16 -0
- package/dist/runtime/clock.js.map +1 -0
- package/dist/runtime/index.d.ts +29 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +289 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/random.d.ts +8 -0
- package/dist/runtime/random.d.ts.map +1 -0
- package/dist/runtime/random.js +21 -0
- package/dist/runtime/random.js.map +1 -0
- package/dist/scaffold.d.ts +3 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +176 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/server/preview-html.d.ts +2 -0
- package/dist/server/preview-html.d.ts.map +1 -0
- package/dist/server/preview-html.js +229 -0
- package/dist/server/preview-html.js.map +1 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +237 -0
- package/dist/server/server.js.map +1 -0
- package/dist/transcript.d.ts +44 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +130 -0
- package/dist/transcript.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/docs/adr/0001-keep-composition-authoring-web-native.md +3 -0
- package/docs/adr/0002-make-frame-and-props-the-only-animation-state.md +3 -0
- package/docs/adr/0003-render-browser-frames-offline.md +3 -0
- package/docs/adr/0004-produce-local-ai-assets-outside-rendering.md +3 -0
- package/docs/v0.1-design.md +238 -0
- package/examples/demo/README.md +24 -0
- package/examples/demo/assets/chime.wav +0 -0
- package/examples/demo/compositions/demo.js +206 -0
- package/examples/demo/props/demo.json +5 -0
- package/examples/demo/video.config.js +5 -0
- package/package.json +73 -3
- package/skills/frameyard/README.md +35 -0
- package/skills/frameyard/SKILL.md +60 -0
- package/skills/frameyard/references/audio-assets.md +86 -0
- package/skills/frameyard/references/deterministic-authoring.md +107 -0
- package/skills/frameyard/references/failure-recovery.md +57 -0
- package/skills/frameyard/references/json-results.md +203 -0
- package/skills/frameyard/references/subtitles-karaoke.md +95 -0
- package/skills/frameyard/scripts/frameyard.mjs +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frameyard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Local-first, deterministic video composition framework for HTML, CSS, and JavaScript.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/vladiantio/frameyard.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/vladiantio/frameyard#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/vladiantio/frameyard/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"video",
|
|
17
|
+
"video-rendering",
|
|
18
|
+
"html",
|
|
19
|
+
"css",
|
|
20
|
+
"javascript",
|
|
21
|
+
"deterministic",
|
|
22
|
+
"ffmpeg",
|
|
23
|
+
"playwright",
|
|
24
|
+
"agent-skill"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"frameyard": "./dist/cli.js"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./runtime": {
|
|
38
|
+
"types": "./dist/runtime/index.d.ts",
|
|
39
|
+
"default": "./dist/runtime/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"examples",
|
|
45
|
+
"docs",
|
|
46
|
+
"skills",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"GLOSSARY.md"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22.12"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"playwright": "1.64.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "26.6.5",
|
|
59
|
+
"oxfmt": "0.75.0",
|
|
60
|
+
"oxlint": "1.90.0",
|
|
61
|
+
"typescript": "5.9.3",
|
|
62
|
+
"vitest": "5.0.5"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsc -p tsconfig.json",
|
|
66
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
67
|
+
"typecheck:test": "tsc -p tsconfig.test.json",
|
|
68
|
+
"lint": "oxlint",
|
|
69
|
+
"format": "oxfmt",
|
|
70
|
+
"format:check": "oxfmt --check",
|
|
71
|
+
"test": "pnpm build && vitest run",
|
|
72
|
+
"test:unit": "vitest run test/contracts.test.ts test/docs.test.ts test/random.test.ts test/scaffold.test.ts test/transcript.test.ts",
|
|
73
|
+
"bench": "pnpm build && node scripts/bench-render.mjs",
|
|
74
|
+
"set-version": "node scripts/set-version.mjs"
|
|
75
|
+
}
|
|
6
76
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Frameyard agent skill
|
|
2
|
+
|
|
3
|
+
This directory is the optional, host-neutral Frameyard agent skill. Its API version matches the npm package version. The npm package includes this directory.
|
|
4
|
+
|
|
5
|
+
## Install or reference
|
|
6
|
+
|
|
7
|
+
Install from the repository with the open agent-skills CLI:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx skills add vladiantio/frameyard
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The skill lives at `skills/frameyard` in the repository and npm package. Alternatively, copy or symlink the whole `skills/frameyard/` directory into the skill location your agent host uses. Examples:
|
|
14
|
+
|
|
15
|
+
- Claude Code project skills: `.claude/skills/frameyard`
|
|
16
|
+
- Agent hosts that read `.agents/skills`: `.agents/skills/frameyard`
|
|
17
|
+
- Global installations: place it under your host's documented personal skills directory
|
|
18
|
+
|
|
19
|
+
The skill is host-neutral: it uses only the normal `frameyard` project format and CLI. There is no agent-only runtime.
|
|
20
|
+
|
|
21
|
+
Run the CLI with `npx frameyard`. It resolves the latest published version; pin it with `npx frameyard@<version>` or install the package locally when the CLI version must match the skill. When the skill is copied outside the npm package, `scripts/frameyard.mjs` falls back to the `frameyard` command on `PATH`; if that command is unavailable, set `FRAMEYARD_CLI` to an installed CLI path.
|
|
22
|
+
|
|
23
|
+
## Contents
|
|
24
|
+
|
|
25
|
+
- `SKILL.md`: trigger description and operating workflow
|
|
26
|
+
- `references/deterministic-authoring.md`
|
|
27
|
+
- `references/audio-assets.md`
|
|
28
|
+
- `references/subtitles-karaoke.md`
|
|
29
|
+
- `references/json-results.md`
|
|
30
|
+
- `references/failure-recovery.md`
|
|
31
|
+
- `scripts/frameyard.mjs`: thin fallback wrapper for the installed CLI
|
|
32
|
+
|
|
33
|
+
## Version matching
|
|
34
|
+
|
|
35
|
+
`SKILL.md` records `frameyard-api-version`. If `npx frameyard --version` reports a different version, pin the CLI (`npx frameyard@<version>`) to the skill version, or use the skill files shipped inside the installed package. Contract changes are documented in the repository README and design baseline.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frameyard
|
|
3
|
+
description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML, CSS, and native JavaScript. Use when a user asks to build or operate a Frameyard project, produce parameterized video, add a local TTS or transcription asset through Frameyard's wrappers, or diagnose a Frameyard render. Do not use for general-purpose media generation outside a Frameyard project.
|
|
4
|
+
frameyard-api-version: 0.2.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frameyard
|
|
8
|
+
|
|
9
|
+
Use Frameyard's public project format and CLI. Do not introduce an agent-only runtime, a hosted service, or direct calls to `qwen-tts` or `whisper-cli`.
|
|
10
|
+
|
|
11
|
+
## First decisions
|
|
12
|
+
|
|
13
|
+
1. Find the project: look for `video.config.js` from the working directory upward, or run `npx frameyard init <directory>` to scaffold one.
|
|
14
|
+
2. Read the composition entry points listed in `video.config.js`. There is no directory scanning.
|
|
15
|
+
3. Read the existing props file and composition source before changing behavior.
|
|
16
|
+
4. If the task needs speech or transcription, confirm that inference is requested or explicitly approved before producing assets.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. **Author** the composition with HTML, CSS, SVG, Canvas 2D, and native JavaScript ES modules. Keep `width`, `height`, `fps`, and `durationInFrames` fixed. Read [deterministic authoring](references/deterministic-authoring.md).
|
|
21
|
+
2. **Provide props** as JSON. Validate domain-specific requirements with `validateProps()`.
|
|
22
|
+
3. **Produce optional assets** with `npx frameyard tts` and `npx frameyard transcribe`, never with the upstream executables. Read [audio assets](references/audio-assets.md). Do not overwrite or regenerate existing generated assets without permission.
|
|
23
|
+
4. **Validate** with `npx frameyard check <id> --json`. It evaluates every frame, checks audio and declared transcripts, and does not encode.
|
|
24
|
+
5. **Inspect** representative frames with `npx frameyard still <id> --frame <n> --out <file.png> --json`. View the PNGs when the host supports image viewing. If visual inspection is impossible, say so explicitly.
|
|
25
|
+
6. **Render** with `npx frameyard render <id> --out <file.mp4> [--props <file>] --json`.
|
|
26
|
+
7. **Report** the project path, composition entry, props, generated assets, provenance manifest, validation results, still paths and inspection status, final video path, and any failures.
|
|
27
|
+
|
|
28
|
+
Never claim success when `check` fails, a still is missing, a render exits nonzero, or visual inspection was skipped. A partial or unpublished render is not a successful result.
|
|
29
|
+
|
|
30
|
+
## Command rules
|
|
31
|
+
|
|
32
|
+
- Use `--json` for every non-interactive command. Parse stdout as the structured result; treat stderr as progress and diagnostics.
|
|
33
|
+
- Keep the current output files unless the user explicitly approves replacement. Use `--force` only with that approval.
|
|
34
|
+
- Use `--project <dir>` when not running from the project root.
|
|
35
|
+
- Use `npx frameyard doctor --json` before reporting an environment problem. It inspects without repairing or downloading.
|
|
36
|
+
- Read [JSON results](references/json-results.md) for result and error shapes, and [failure recovery](references/failure-recovery.md) for actionable error codes.
|
|
37
|
+
|
|
38
|
+
## Determinism guardrails
|
|
39
|
+
|
|
40
|
+
- A frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production.
|
|
41
|
+
- Use `random("stable:key")` from `frameyard/runtime`; never use `Math.random()`.
|
|
42
|
+
- Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
|
|
43
|
+
- Finish remote data, fonts, and image loading during `mount()` or before the first frame. Network requests are blocked during frame capture.
|
|
44
|
+
- Keep `update({ frame, props })` synchronous and seek-safe. Do not carry state between frames.
|
|
45
|
+
- Do not run model inference inside `mount()`, `update()`, or frame production.
|
|
46
|
+
|
|
47
|
+
## Subtitle and karaoke rule
|
|
48
|
+
|
|
49
|
+
Use canonical transcripts produced by `npx frameyard transcribe`. Convert integer source-relative milliseconds to composition frames with the audio clip's `startFrame`, `trimStart`, and the composition `fps`. Karaoke highlighting requires actual `words` timings; never interpolate segment timings. Read [subtitles and karaoke](references/subtitles-karaoke.md).
|
|
50
|
+
|
|
51
|
+
## CLI invocation
|
|
52
|
+
|
|
53
|
+
Use `npx frameyard` (or an installed `frameyard` binary) directly. `scripts/frameyard.mjs` is a fallback for hosts that cannot resolve package binaries: it prefers the package-local `dist/cli.js`, then the `frameyard` command on `PATH`. When the skill is copied out of the package, set `FRAMEYARD_CLI` to an installed CLI path if that command is unavailable.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
npx frameyard check <id> --json
|
|
57
|
+
node scripts/frameyard.mjs check <id> --json # fallback
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
When the CLI version differs from `frameyard-api-version`, pin it with `npx frameyard@<version>`.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Audio and generated assets
|
|
2
|
+
|
|
3
|
+
Audio clips are a declarative timeline. Generated speech and transcripts are frozen local inputs produced before composition loading.
|
|
4
|
+
|
|
5
|
+
## Audio timeline
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
audio: [
|
|
9
|
+
{
|
|
10
|
+
src: "assets/voice.wav", // project-relative local file
|
|
11
|
+
startFrame: 30, // composition timeline position
|
|
12
|
+
trimStart: 0.25, // seconds trimmed from the source
|
|
13
|
+
trimDuration: 4.2, // optional seconds to use
|
|
14
|
+
volume: 0.8, // 0..1
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Placement is in composition frames.
|
|
20
|
+
- Trimming is in seconds from the source.
|
|
21
|
+
- Frameyard never time-stretches audio or changes composition duration to fit it.
|
|
22
|
+
- `check` reports clip durations and overrun warnings.
|
|
23
|
+
|
|
24
|
+
## Local speech synthesis
|
|
25
|
+
|
|
26
|
+
Use Frameyard, not `qwen-tts` directly:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx frameyard tts \
|
|
30
|
+
--speaker vivian \
|
|
31
|
+
--talker-model models/qwen-talker-1.7b-customvoice-Q8_0.gguf \
|
|
32
|
+
--codec-model models/qwen-tokenizer-12hz-Q8_0.gguf \
|
|
33
|
+
--language English \
|
|
34
|
+
--text-file script.txt \
|
|
35
|
+
--out assets/voice.wav \
|
|
36
|
+
--json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- CustomVoice only: `--speaker` is required.
|
|
40
|
+
- Text comes from `--text-file` or stdin.
|
|
41
|
+
- `--style` maps to the upstream style instruction.
|
|
42
|
+
- `--language` maps to `--lang`.
|
|
43
|
+
- `--device CPU` forces qwentts.cpp onto the CPU when the GPU does not have enough VRAM.
|
|
44
|
+
- Output is one frozen 24 kHz mono WAV.
|
|
45
|
+
- Talker and codec GGUF paths are required.
|
|
46
|
+
- `--force` is required to replace an existing asset. Do not pass it without user approval.
|
|
47
|
+
- Executable resolution: `--executable`, then `FRAMEYARD_QWEN_TTS`, then `PATH`.
|
|
48
|
+
- Frameyard does not build qwentts.cpp, download models, or redistribute weights.
|
|
49
|
+
|
|
50
|
+
## Local transcription
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx frameyard transcribe assets/voice.wav \
|
|
54
|
+
--model models/ggml-base.en.bin \
|
|
55
|
+
--out assets/voice.transcript.json \
|
|
56
|
+
--words \
|
|
57
|
+
--export srt,vtt \
|
|
58
|
+
--json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- The source is normalized with FFmpeg to 16 kHz mono PCM WAV before `whisper-cli` runs.
|
|
62
|
+
- The canonical output is Frameyard transcript JSON.
|
|
63
|
+
- `--words` requests actual word timings. It fails rather than fabricate them.
|
|
64
|
+
- `--export srt,vtt` writes derived subtitle files next to the transcript.
|
|
65
|
+
- `--force` is required to replace existing transcript or export files.
|
|
66
|
+
- Executable resolution: `--executable`, then `FRAMEYARD_WHISPER_CLI`, then `PATH`.
|
|
67
|
+
- Frameyard does not download Whisper models or execute Whisper's generated karaoke shell script.
|
|
68
|
+
|
|
69
|
+
## Provenance manifest
|
|
70
|
+
|
|
71
|
+
Both commands update `frameyard.assets.json` in the project root unless `--manifest <file>` is supplied. The manifest records:
|
|
72
|
+
|
|
73
|
+
- artifact path, content hash, and size;
|
|
74
|
+
- generator command, Frameyard version, executable, and integration revision;
|
|
75
|
+
- model identities and sizes;
|
|
76
|
+
- operation parameters and, for transcription, source identity and derived exports.
|
|
77
|
+
|
|
78
|
+
Commit the manifest with the project. It explains how generated inputs were produced. Do not edit hashes by hand.
|
|
79
|
+
|
|
80
|
+
## Regeneration rules
|
|
81
|
+
|
|
82
|
+
- Do not regenerate or overwrite an existing generated asset without permission.
|
|
83
|
+
- Do not silently switch models, speakers, language, or text.
|
|
84
|
+
- Report the manifest path and the exact asset paths after success.
|
|
85
|
+
- `npx frameyard doctor` verifies manifest integrity, including artifact, source, model, and derived-export hashes, and optional integration executables; it does not download or repair anything. Pass `--talker-model`, `--codec-model`, or `--model` to inspect model readability and identity before generation.
|
|
86
|
+
- Once assets are frozen, checking and rendering do not need the model or inference executable.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Deterministic authoring
|
|
2
|
+
|
|
3
|
+
Frameyard compositions are ordinary native JavaScript ES modules. The preview server injects an import map for `frameyard` and `frameyard/runtime`; do not add a bundling step or import from `node_modules` in composition code.
|
|
4
|
+
|
|
5
|
+
## Project configuration
|
|
6
|
+
|
|
7
|
+
`video.config.js` lists every composition explicitly:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
export default {
|
|
11
|
+
compositions: {
|
|
12
|
+
hello: "./compositions/hello.js",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
There is no directory scanning. A new composition file is invisible until it is listed.
|
|
18
|
+
|
|
19
|
+
## Composition shape
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { defineComposition } from "frameyard";
|
|
23
|
+
import { random } from "frameyard/runtime";
|
|
24
|
+
|
|
25
|
+
let title;
|
|
26
|
+
|
|
27
|
+
export default defineComposition({
|
|
28
|
+
width: 1920,
|
|
29
|
+
height: 1080,
|
|
30
|
+
fps: 30,
|
|
31
|
+
durationInFrames: 900,
|
|
32
|
+
defaultProps: { title: "Hello" },
|
|
33
|
+
validateProps(props) {
|
|
34
|
+
return typeof props.title === "string" ? [] : ["title must be a string"];
|
|
35
|
+
},
|
|
36
|
+
mount({ container, props }) {
|
|
37
|
+
container.innerHTML = "<h1 id='title'></h1>";
|
|
38
|
+
title = container.querySelector("#title");
|
|
39
|
+
title.textContent = props.title;
|
|
40
|
+
},
|
|
41
|
+
update({ frame, props }) {
|
|
42
|
+
title.textContent = props.title;
|
|
43
|
+
title.style.transform = `translateX(${random("title:" + frame) * 40}px)`;
|
|
44
|
+
},
|
|
45
|
+
dispose() {
|
|
46
|
+
title = undefined;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `mount()` runs once and may await loading work.
|
|
52
|
+
- `update({ frame, props })` is synchronous and seek-safe.
|
|
53
|
+
- `dispose()` releases resources and module-scope references.
|
|
54
|
+
- Props are JSON-serializable, deep-frozen, and merged shallowly over `defaultProps`.
|
|
55
|
+
- Keep mutable state in module-scope variables, never on `this`.
|
|
56
|
+
|
|
57
|
+
## Forbidden animation inputs
|
|
58
|
+
|
|
59
|
+
Do not use any of these as animation state:
|
|
60
|
+
|
|
61
|
+
- `Math.random()`
|
|
62
|
+
- `Date.now()` or `performance.now()`
|
|
63
|
+
- `setTimeout()` or `setInterval()`
|
|
64
|
+
- independent `requestAnimationFrame()` loops
|
|
65
|
+
- CSS transitions
|
|
66
|
+
- state left from a previously visited frame
|
|
67
|
+
|
|
68
|
+
Use `random("stable:key")` for deterministic randomness. The value depends only on the key, not call order.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { random } from "frameyard/runtime";
|
|
72
|
+
|
|
73
|
+
const x = random("star:17:x") * width;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## CSS and Canvas
|
|
77
|
+
|
|
78
|
+
CSS `@keyframes` and the Web Animations API are paused and seeked by the runtime inside the composition container. They are valid animation drivers. CSS transitions are diagnosed as non-deterministic and should not be used.
|
|
79
|
+
|
|
80
|
+
Canvas 2D must be redrawn from the current frame:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
update({ frame }) {
|
|
84
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
85
|
+
context.fillStyle = frame % 2 === 0 ? "#101820" : "#203040";
|
|
86
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Loading assets
|
|
91
|
+
|
|
92
|
+
Remote data, images, fonts, and other assets must resolve during `mount()` or before the first frame. The production browser blocks network requests after loading begins. A fetch from `update()` is a bug.
|
|
93
|
+
|
|
94
|
+
Images inserted in `mount()` are awaited by the runtime. Static fonts should be loaded with `document.fonts.ready` or an explicit `FontFace` before frame production.
|
|
95
|
+
|
|
96
|
+
## Validation and inspection
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
npx frameyard check hello --json
|
|
100
|
+
npx frameyard still hello --frame 42 --out hello-42.png --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`check` mounts the composition, evaluates every frame, and validates audio and declared transcripts without capturing or encoding. Do not replace it with a single still.
|
|
104
|
+
|
|
105
|
+
## Metadata is fixed
|
|
106
|
+
|
|
107
|
+
`width`, `height`, `fps`, and `durationInFrames` cannot be derived from props. Choose a reusable resolution, frame rate, and duration; put variation in props. Frameyard does not change duration to fit audio.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Failure recovery
|
|
2
|
+
|
|
3
|
+
Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and only then render.
|
|
4
|
+
|
|
5
|
+
## Common codes
|
|
6
|
+
|
|
7
|
+
| Code | Meaning | Action |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `USAGE` | Invalid flags or missing required arguments | Read `npx frameyard <command> --help`. |
|
|
10
|
+
| `CONFIG_NOT_FOUND` | No `video.config.js` found | Run from the project root or pass `--project`. |
|
|
11
|
+
| `CONFIG_INVALID` | Invalid config or composition entry | Fix `video.config.js`; entries must be explicit, local, and exist. |
|
|
12
|
+
| `COMPOSITION_NOT_FOUND` | ID is not listed | Add the entry to `video.config.js`. |
|
|
13
|
+
| `COMPOSITION_INVALID` | Invalid metadata or lifecycle shape | Fix metadata and ensure `update()` is synchronous. |
|
|
14
|
+
| `COMPOSITION_ERROR` | Mount, update, or browser page error | Read the frame location in the message; remove forbidden clocks/randomness. |
|
|
15
|
+
| `COMPOSITION_UPDATE_ASYNC` | `update()` returned a promise | Move async work to `mount()`. |
|
|
16
|
+
| `PROPS_INVALID` | Props are not JSON or fail `validateProps()` | Fix the props file or validator. |
|
|
17
|
+
| `FRAME_OUT_OF_RANGE` | Frame is outside `0..durationInFrames - 1` | Choose a valid frame. |
|
|
18
|
+
| `AUDIO_INVALID` | Bad clip or unreadable/unsupported audio | Use a local project-relative file with a decodable audio stream. |
|
|
19
|
+
| `ASSET_NOT_FOUND` | Required file is missing or unreadable | Restore the asset, model, or text file. |
|
|
20
|
+
| `OUTPUT_EXISTS` | Output exists and replacement was not approved | Reuse it, choose another path, or pass `--overwrite`/`--force` with permission. |
|
|
21
|
+
| `OUTPUT_FAILED` | An output or manifest could not be written atomically | Check directory permissions and free space; rerun. |
|
|
22
|
+
| `EXECUTABLE_NOT_FOUND` | `qwen-tts` or `whisper-cli` could not be resolved | Pass `--executable`, set the environment variable, or fix `PATH`. |
|
|
23
|
+
| `TTS_FAILED` | Speech synthesis failed or produced an invalid WAV | Read stderr; verify the talker/codec models and CLI compatibility. |
|
|
24
|
+
| `TRANSCRIBE_FAILED` | Normalization or `whisper-cli` failed | Verify FFmpeg, the source media, and the Whisper model. |
|
|
25
|
+
| `TRANSCRIPT_INVALID` | Canonical transcript JSON is malformed | Regenerate it with `npx frameyard transcribe`; do not hand-edit timestamps. |
|
|
26
|
+
| `TRANSCRIPT_WORDS_UNAVAILABLE` | Karaoke requested without measured word timings | Re-run transcription with `--words` or remove `words: true`. |
|
|
27
|
+
| `MANIFEST_INVALID` | Provenance manifest is corrupt or unsupported | Restore or regenerate the manifest; do not invent hashes. |
|
|
28
|
+
| `FFMPEG_FAILED` | FFmpeg is missing, lacks encoders, or failed | Run `npx frameyard doctor --json`; install FFmpeg with libx264 and AAC. |
|
|
29
|
+
| `FFPROBE_FAILED` | FFprobe is missing or failed | Install FFmpeg/FFprobe together. |
|
|
30
|
+
| `BROWSER_FAILED` | Chromium could not start | Install the Playwright Chromium build. |
|
|
31
|
+
| `RENDER_FAILED` | Capture or encoding failed | Read stderr; rerun `check` and a representative `still`. |
|
|
32
|
+
| `CANCELLED` | The operation was interrupted | Rerun when ready. Partial outputs are removed. |
|
|
33
|
+
|
|
34
|
+
## Behavior to rely on
|
|
35
|
+
|
|
36
|
+
- A failed render writes to a temporary file and does not publish a partial MP4.
|
|
37
|
+
- Failed TTS/transcription operations leave no artifact and no manifest entry.
|
|
38
|
+
- Existing outputs are replaced only with `--overwrite` (renders/stills) or `--force` (generated assets).
|
|
39
|
+
- Cancellation terminates the child inference process and cleans temporary work.
|
|
40
|
+
- `check` validates every frame and declared transcripts without encoding.
|
|
41
|
+
- `doctor` inspects but never repairs, downloads, or modifies the environment.
|
|
42
|
+
|
|
43
|
+
## When visual inspection is impossible
|
|
44
|
+
|
|
45
|
+
If the host cannot display images, say explicitly that representative stills could not be visually inspected. Report their paths and the fact that inspection was skipped. Do not describe visual correctness you did not observe.
|
|
46
|
+
|
|
47
|
+
## Reporting a completed result
|
|
48
|
+
|
|
49
|
+
Report all of:
|
|
50
|
+
|
|
51
|
+
- source project path and composition entry;
|
|
52
|
+
- props file or inline props used;
|
|
53
|
+
- generated audio/transcript paths and provenance manifest path;
|
|
54
|
+
- `check` result, including warnings;
|
|
55
|
+
- still paths and whether they were viewed;
|
|
56
|
+
- final video path, frame count, and audio/transcript summary;
|
|
57
|
+
- any skipped step or unresolved warning.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# JSON command results
|
|
2
|
+
|
|
3
|
+
Every non-interactive command supports `--json`. Stdout contains one JSON document; progress and human diagnostics go to stderr. Failures exit nonzero.
|
|
4
|
+
|
|
5
|
+
## Success envelope
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"ok": true,
|
|
10
|
+
"command": "check"
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Additional fields are command-specific and stable within the 0.1 contract.
|
|
15
|
+
|
|
16
|
+
## Error envelope
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"ok": false,
|
|
21
|
+
"command": "render",
|
|
22
|
+
"error": {
|
|
23
|
+
"code": "OUTPUT_EXISTS",
|
|
24
|
+
"message": "Output already exists: ..."
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use the error `code` for decisions, not message text. `command` is `null` only for usage failures that occur before a command is known. Exit code `2` means usage; `130` means cancellation; other failures exit `1`.
|
|
30
|
+
|
|
31
|
+
## check
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"ok": true,
|
|
36
|
+
"command": "check",
|
|
37
|
+
"composition": "hello",
|
|
38
|
+
"metadata": { "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 900 },
|
|
39
|
+
"framesChecked": 900,
|
|
40
|
+
"audio": {
|
|
41
|
+
"clips": 1,
|
|
42
|
+
"warnings": [],
|
|
43
|
+
"details": [
|
|
44
|
+
{
|
|
45
|
+
"src": "assets/voice.wav",
|
|
46
|
+
"startFrame": 30,
|
|
47
|
+
"startSeconds": 1,
|
|
48
|
+
"endSeconds": 5.2,
|
|
49
|
+
"sourceDurationSeconds": 4.95,
|
|
50
|
+
"trimStart": 0.25,
|
|
51
|
+
"volume": 0.8
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"transcripts": {
|
|
56
|
+
"clips": 1,
|
|
57
|
+
"warnings": [],
|
|
58
|
+
"details": [
|
|
59
|
+
{ "src": "assets/voice.transcript.json", "language": "en", "segments": 3, "words": 17, "durationMs": 5200, "bytes": 2048 }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"elapsedMs": 1200
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## still
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"ok": true,
|
|
71
|
+
"command": "still",
|
|
72
|
+
"composition": "hello",
|
|
73
|
+
"frame": 42,
|
|
74
|
+
"path": "/absolute/path/hello-42.png",
|
|
75
|
+
"width": 1920,
|
|
76
|
+
"height": 1080,
|
|
77
|
+
"elapsedMs": 700
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## render
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"ok": true,
|
|
86
|
+
"command": "render",
|
|
87
|
+
"composition": "hello",
|
|
88
|
+
"path": "/absolute/path/hello.mp4",
|
|
89
|
+
"width": 1920,
|
|
90
|
+
"height": 1080,
|
|
91
|
+
"fps": 30,
|
|
92
|
+
"durationInFrames": 900,
|
|
93
|
+
"durationSeconds": 30,
|
|
94
|
+
"framesRendered": 900,
|
|
95
|
+
"bytes": 1234567,
|
|
96
|
+
"audio": { "clips": 1, "warnings": [] },
|
|
97
|
+
"transcripts": { "clips": 1, "warnings": [] },
|
|
98
|
+
"elapsedMs": 99000
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A successful render means the final path exists and was published atomically. A nonzero exit means no new partial video was published.
|
|
103
|
+
|
|
104
|
+
## tts
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"ok": true,
|
|
109
|
+
"command": "tts",
|
|
110
|
+
"path": "/absolute/path/assets/voice.wav",
|
|
111
|
+
"bytes": 480000,
|
|
112
|
+
"sha256": "64-character hex digest",
|
|
113
|
+
"sampleRate": 24000,
|
|
114
|
+
"channels": 1,
|
|
115
|
+
"speaker": "vivian",
|
|
116
|
+
"language": "English",
|
|
117
|
+
"manifest": "/absolute/path/frameyard.assets.json",
|
|
118
|
+
"elapsedMs": 12000
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`language` is omitted when it was not supplied.
|
|
123
|
+
|
|
124
|
+
## transcribe
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"ok": true,
|
|
129
|
+
"command": "transcribe",
|
|
130
|
+
"path": "/absolute/path/assets/voice.transcript.json",
|
|
131
|
+
"bytes": 2048,
|
|
132
|
+
"sha256": "64-character hex digest",
|
|
133
|
+
"source": {
|
|
134
|
+
"path": "assets/voice.wav",
|
|
135
|
+
"sha256": "64-character hex digest",
|
|
136
|
+
"bytes": 480000
|
|
137
|
+
},
|
|
138
|
+
"language": "en",
|
|
139
|
+
"segments": 3,
|
|
140
|
+
"words": 17,
|
|
141
|
+
"exports": [
|
|
142
|
+
{
|
|
143
|
+
"format": "srt",
|
|
144
|
+
"path": "/absolute/path/assets/voice.transcript.srt",
|
|
145
|
+
"bytes": 220,
|
|
146
|
+
"sha256": "64-character hex digest"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"manifest": "/absolute/path/frameyard.assets.json",
|
|
150
|
+
"elapsedMs": 30000
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`words` is `0` when word timings were not requested. `language` is omitted when it could not be determined.
|
|
155
|
+
|
|
156
|
+
## doctor
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"ok": true,
|
|
163
|
+
"command": "doctor",
|
|
164
|
+
"healthy": true,
|
|
165
|
+
"checks": [
|
|
166
|
+
{ "name": "node", "required": true, "status": "ok", "detail": "v22.12.0" },
|
|
167
|
+
{ "name": "ffmpeg", "required": true, "status": "ok", "detail": "ffmpeg version ..." },
|
|
168
|
+
{ "name": "ffmpeg-encoders", "required": true, "status": "ok", "detail": "libx264 and aac are available." },
|
|
169
|
+
{ "name": "ffprobe", "required": true, "status": "ok", "detail": "ffprobe version ..." },
|
|
170
|
+
{ "name": "chromium", "required": true, "status": "ok", "detail": "/path/to/chrome" },
|
|
171
|
+
{ "name": "manifest", "required": false, "status": "ok", "detail": "2 artifacts verified." }
|
|
172
|
+
],
|
|
173
|
+
"integrations": {
|
|
174
|
+
"tts": {
|
|
175
|
+
"name": "qwen-tts",
|
|
176
|
+
"status": "ok",
|
|
177
|
+
"requested": false,
|
|
178
|
+
"executable": "/path/to/qwen-tts",
|
|
179
|
+
"capabilities": ["--model", "--codec", "--speaker", "--lang", "--device", "-o"],
|
|
180
|
+
"version": "5916bf7c79dbb4e66fc059cdc507a79a23a28bb4 (2026-10-28)",
|
|
181
|
+
"referenceRevision": "..."
|
|
182
|
+
},
|
|
183
|
+
"transcribe": {
|
|
184
|
+
"name": "whisper-cli",
|
|
185
|
+
"status": "missing",
|
|
186
|
+
"requested": false,
|
|
187
|
+
"referenceRevision": "..."
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"models": [
|
|
191
|
+
{
|
|
192
|
+
"role": "talker",
|
|
193
|
+
"path": "/absolute/path/models/talker.gguf",
|
|
194
|
+
"status": "ok",
|
|
195
|
+
"bytes": 1234567,
|
|
196
|
+
"sha256": "64-character hex digest"
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
"manifest": { "path": "/absolute/path/frameyard.assets.json", "exists": true, "valid": true, "artifacts": 2 }
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`models` is populated only for model paths passed explicitly with `--talker-model`, `--codec-model`, or `--model`. `healthy: false` exits `1`. Missing optional integrations do not fail, but a found executable with incompatible capabilities does.
|