reframe-video 0.1.3 → 0.3.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/assets/sfx/LICENSE.md +2 -1
- package/assets/sfx/bong_001.ogg +0 -0
- package/assets/sfx/click_001.ogg +0 -0
- package/assets/sfx/confirmation_002.ogg +0 -0
- package/assets/sfx/confirmation_003.ogg +0 -0
- package/assets/sfx/confirmation_004.ogg +0 -0
- package/assets/sfx/footstep_001.ogg +0 -0
- package/assets/sfx/footstep_002.ogg +0 -0
- package/assets/sfx/footstep_003.ogg +0 -0
- package/assets/sfx/glass_001.ogg +0 -0
- package/assets/sfx/maximize_001.ogg +0 -0
- package/assets/sfx/maximize_002.ogg +0 -0
- package/assets/sfx/maximize_005.ogg +0 -0
- package/assets/sfx/maximize_009.ogg +0 -0
- package/assets/sfx/open_001.ogg +0 -0
- package/assets/sfx/pluck_001.ogg +0 -0
- package/assets/sfx/pluck_002.ogg +0 -0
- package/assets/sfx/select_001.ogg +0 -0
- package/assets/sfx/select_002.ogg +0 -0
- package/assets/sfx/select_003.ogg +0 -0
- package/dist/bin.js +271 -49
- package/dist/browserEntry.js +179 -68
- package/dist/cli.js +445 -85
- package/dist/index.js +1187 -116
- package/dist/labels.js +606 -0
- package/dist/renderer-canvas.js +15 -0
- package/dist/trace-cli.js +9 -9
- package/dist/types/audio.d.ts +9 -0
- package/dist/types/characterPreset.d.ts +39 -0
- package/dist/types/compile.d.ts +1 -0
- package/dist/types/compose.d.ts +18 -2
- package/dist/types/composeComposition.d.ts +27 -0
- package/dist/types/devicePreset.d.ts +65 -0
- package/dist/types/dsl.d.ts +12 -1
- package/dist/types/evaluate.d.ts +32 -0
- package/dist/types/figure.d.ts +32 -0
- package/dist/types/index.d.ts +9 -3
- package/dist/types/interpolate.d.ts +3 -2
- package/dist/types/ir.d.ts +68 -0
- package/dist/types/motionOps.d.ts +36 -0
- package/dist/types/path.d.ts +7 -3
- package/dist/types/rig.d.ts +87 -0
- package/dist/types/validate.d.ts +4 -1
- package/guides/edsl-guide.md +54 -1
- package/guides/regen-contract.md +11 -0
- package/package.json +1 -1
- package/preview/index.html +56 -3
- package/preview/src/main.ts +1132 -46
- package/preview/src/panel.ts +478 -8
- package/preview/src/store.ts +323 -6
package/guides/edsl-guide.md
CHANGED
|
@@ -41,6 +41,12 @@ Factories return plain data. Every node needs a unique `id`.
|
|
|
41
41
|
the art's centre (e.g. the viewBox centre) so `scale`/`rotation` happen about the
|
|
42
42
|
middle. `d` is drawn in its own coords; `x`/`y` place that pivot. Classic logo
|
|
43
43
|
reveal: a stroke path drawing on, then a fill path fading in over it.
|
|
44
|
+
**`d` is animatable (shape morph):** `tween(id, { d: otherShape }, …)` morphs
|
|
45
|
+
the path vertex-by-vertex (the Lottie-style shape tween) when both `d` strings
|
|
46
|
+
share the same command sequence and arg counts — author the two poses with the
|
|
47
|
+
same structure (e.g. both 4-cubic ovals). Arcs (`A`) can't morph (their 0/1
|
|
48
|
+
flags aren't interpolable) and incompatible shapes snap at the midpoint; build
|
|
49
|
+
morph targets from `M/L/C/Q/Z` only.
|
|
44
50
|
- `image({ id, src, x, y, width, height, opacity?, rotation?, scale?, anchor? })` —
|
|
45
51
|
`src` is a file path, absolute or relative to the scene file; drawn stretched
|
|
46
52
|
to `width`×`height` (png/jpg/webp). `src` switches discretely (no crossfade) —
|
|
@@ -82,13 +88,14 @@ them with normal TS (`Object.fromEntries`, `.map`) for data-driven scenes.
|
|
|
82
88
|
- `to(stateName, opts)` — transition into a named state (see above).
|
|
83
89
|
- `tween(nodeId, { prop: value, ... }, { duration, ease })` — low-level escape hatch
|
|
84
90
|
for one node. Colors (`"#rrggbb"`) interpolate; numbers interpolate.
|
|
85
|
-
- `motionPath(nodeId, [[x,y], ...], { duration, ease, autoRotate?, rotateOffset?, closed? })`
|
|
91
|
+
- `motionPath(nodeId, [[x,y], ...], { duration, ease, curviness?, autoRotate?, rotateOffset?, closed? })`
|
|
86
92
|
— drive a node's `x`/`y` along a smooth Catmull-Rom curve through the waypoints
|
|
87
93
|
(parent-space coords). `autoRotate: true` banks the node along the path tangent
|
|
88
94
|
(`rotateOffset` degrees if the art faces "up", e.g. `-90`). The node HOLDS at the
|
|
89
95
|
final point after the path finishes (a positioning move, not a one-shot), so a
|
|
90
96
|
later `tween` can chain from there. Use it for swoops/arcs/orbits — straight
|
|
91
97
|
`tween`s on x and y can't curve. `closed: true` loops the waypoints (orbit).
|
|
98
|
+
`curviness` shapes the path: `1` smooth (default), `0` sharp corners, `>1` loopier.
|
|
92
99
|
- `wait(seconds)` — hold.
|
|
93
100
|
|
|
94
101
|
Eases: `linear`, `easeIn/Out/InOutQuad`, `easeIn/Out/InOutCubic`,
|
|
@@ -114,6 +121,52 @@ bound — e.g. a pulse only during the hold:
|
|
|
114
121
|
`oscillate("title", "scale", { amplitude: 0.04, frequency: 1.2 }, { from: 1.5, until: 3.5 })`.
|
|
115
122
|
Omit the window to run for the whole scene.
|
|
116
123
|
|
|
124
|
+
## Character rig (skeleton, poses, IK)
|
|
125
|
+
|
|
126
|
+
A first-class, declarative character rig that **compiles to plain IR** (nested
|
|
127
|
+
`group` joints + bone paths) — the character analog of `devicePreset`. It needs
|
|
128
|
+
no new renderer concept, so overlays/preview/determinism all apply.
|
|
129
|
+
|
|
130
|
+
- `humanoid({ id, x, y, scale, opacity?, color?, fill?, glow? })` → a NodeIR: a
|
|
131
|
+
ready upright body. Joints (stable ids `${id}-${name}`): `chest`, `head`,
|
|
132
|
+
`armUpperL/armLowerL`, `armUpperR/armLowerR`, `legUpperL/legLowerL`,
|
|
133
|
+
`legUpperR/legLowerR`. Drop it in `nodes`.
|
|
134
|
+
- `rig(boneTree, opts)` → build your own skeleton. A `Bone` is
|
|
135
|
+
`{ name, at:[x,y], length?, width?, rotation?, shape?, children? }`. The joint
|
|
136
|
+
sits at the group origin; the bone extends **+Y at rotation 0**; a child's `at`
|
|
137
|
+
pivot is in the PARENT bone's local space (e.g. an elbow at `[0, upperLength]`).
|
|
138
|
+
Nested groups give forward kinematics — a child's rotation composes on its
|
|
139
|
+
parent's. Default bone = a bezier capsule (morphable); pass `shape` for custom art.
|
|
140
|
+
- A **pose** is `{ jointName: angleDeg }` (0 = bone points down). Animate it:
|
|
141
|
+
- `poseTo(id, pose, { duration, ease, stagger? })` → a timeline step (a `par`
|
|
142
|
+
of rotation tweens). Sequence poses for wave/jump/run.
|
|
143
|
+
- `rigPose(id, pose)` → a `states` fragment, to transition with `to(state, …)`.
|
|
144
|
+
- `ikReach(upper, lower, dx, dy, flip?)` → `[shoulderDeg, elbowDeg]` that place a
|
|
145
|
+
2-bone limb's tip at `(dx,dy)` relative to its shoulder joint (law of cosines;
|
|
146
|
+
clamps when out of reach). Feed the two angles into a pose.
|
|
147
|
+
- Joint names are the **stable regen addresses** — never rename them across a
|
|
148
|
+
regen; each rig instance needs a distinct `id` (duplicates collide via scene
|
|
149
|
+
validation). Squash/stretch and expressions are per-bone `d` morphs (above),
|
|
150
|
+
composed on top of FK posing. Idle sway/breathing = `oscillate` on a joint.
|
|
151
|
+
- `figure(opts)` — a **dressed** character (the styled sibling of `humanoid`):
|
|
152
|
+
same skeleton, but coloured flat-design shapes. `style: "clean"` (corporate-flat
|
|
153
|
+
/ undraw register, the default) or `"cute"` (mascot); `palette` knobs
|
|
154
|
+
(`skin`/`hair`/`top`/`pants`/`shoe`/`accent`) re-skin it — for `clean` the top
|
|
155
|
+
follows `accent`, so `figure({ palette: { accent: "#3B82F6" } })` recolours the
|
|
156
|
+
whole figure; `face: false` makes it faceless. It exposes the humanoid joint
|
|
157
|
+
ids, so `characterPreset` / `ikReach` drive it unchanged. Use it as the
|
|
158
|
+
supporting actor in a product promo (gesturing at a `devicePreset`), not the hero.
|
|
159
|
+
- `characterPreset(name, opts)` — a **seeded motion generator** for a `humanoid`
|
|
160
|
+
or `figure` rig (the character analog of `motionPreset`). Returns a composable `beat`;
|
|
161
|
+
drop it in the timeline: `seq(characterPreset("walk", { target: "hero", at:
|
|
162
|
+
[cx, cy], cycles: 4 }))`. Names: `walk`, `run`, `jump`, `dance`, `wave`,
|
|
163
|
+
`cheer`. Knobs: `target` (rig id), `energy` 0..1, `speed` (>0, divides
|
|
164
|
+
durations), `seed` (varies within the family), `cycles` (walk/run/dance),
|
|
165
|
+
`facing` (±1), `at: [x,y]` (the rig's scene position — needed for walk travel
|
|
166
|
+
& jump lift), `travel` (px/cycle, 0 = in place), `label` (unique beat name —
|
|
167
|
+
set it when the same preset is used more than once in a scene). Legs use
|
|
168
|
+
`ikReach`, arms FK; pure keyframes, so add continuous idle yourself with `oscillate`.
|
|
169
|
+
|
|
117
170
|
## Audio (optional)
|
|
118
171
|
|
|
119
172
|
Label-anchored sound design — cues follow retiming and regeneration:
|
package/guides/regen-contract.md
CHANGED
|
@@ -16,3 +16,14 @@ source):
|
|
|
16
16
|
When the contract is broken anyway, `composeScene` skips the affected edits
|
|
17
17
|
and reports them as orphans with the known-ids list — loud, diagnosable,
|
|
18
18
|
never a silent drop and never a render failure.
|
|
19
|
+
|
|
20
|
+
## Generated subtrees (devicePreset, rig/humanoid)
|
|
21
|
+
|
|
22
|
+
Generators emit nodes with deterministic ids under an instance prefix, and those
|
|
23
|
+
ids are stable addresses too. For `devicePreset(name,{id})` the screen/content
|
|
24
|
+
parts are `${id}-screen` / `${id}-content`. For `rig(...)` / `humanoid({id})`
|
|
25
|
+
each joint is `${id}-${jointName}` (e.g. `hero-armUpperR`) and its bone art is
|
|
26
|
+
`${id}-${jointName}-shape`. Across a regen, **keep the instance `id` and the
|
|
27
|
+
joint `name`s** for any character/device that survives the redesign — overlay
|
|
28
|
+
edits (a retimed wave, a nudged limb angle) reference those exact ids. Renaming a
|
|
29
|
+
joint orphans the edit, exactly like renaming a hand-authored node id.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"motion-graphics",
|
package/preview/index.html
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
#content { flex: 1; display: flex; min-height: 0; }
|
|
12
12
|
#stage-wrap { flex: 1; display: flex; align-items: center; justify-content: center; min-width: 0; padding: 16px; }
|
|
13
13
|
canvas { max-width: 100%; max-height: 100%; box-shadow: 0 4px 32px rgba(0,0,0,.5); }
|
|
14
|
-
#bar { display: flex; gap:
|
|
15
|
-
#scrub { flex: 1; }
|
|
14
|
+
#bar { display: flex; gap: 8px; align-items: center; padding: 12px 16px; background: #232329; }
|
|
15
|
+
#scrub-wrap { flex: 1; position: relative; display: flex; align-items: center; }
|
|
16
|
+
#scrub { width: 100%; }
|
|
17
|
+
#loop-band { position: absolute; top: 50%; transform: translateY(-50%); height: 6px; background: rgba(125,154,255,.35); border-radius: 3px; pointer-events: none; display: none; }
|
|
18
|
+
button.on { background: #3a4a86; border-color: #7d9aff; color: #fff; }
|
|
16
19
|
select, button, input[type=text], input[type=number] { background: #2e2e36; color: #ddd; border: 1px solid #444; border-radius: 4px; padding: 3px 8px; font: 12px system-ui; }
|
|
17
20
|
input[type=number] { width: 64px; }
|
|
18
21
|
input[type=color] { width: 40px; height: 22px; padding: 0; border: 1px solid #444; background: none; }
|
|
@@ -42,6 +45,42 @@
|
|
|
42
45
|
#report details { color: #8a8a96; }
|
|
43
46
|
#io { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
44
47
|
#overlay-name { width: 100%; margin-bottom: 6px; box-sizing: border-box; }
|
|
48
|
+
.beat-group { margin: 4px 0 2px; border-left: 2px solid #3a4a86; padding-left: 6px; }
|
|
49
|
+
.beat-lane { padding: 1px 4px; border-radius: 3px; cursor: pointer; color: #b9c2da; font-size: 12px; }
|
|
50
|
+
.beat-lane:hover { background: #2a2a32; }
|
|
51
|
+
.beat-lane.selected { background: #31313c; color: #fff; }
|
|
52
|
+
.beat-lane.missing { color: #ff7b72; cursor: default; }
|
|
53
|
+
.beat-markers { color: #7d9aff; font-size: 11px; margin-top: 3px; opacity: 0.85; }
|
|
54
|
+
/* bottom timeline: scene bands (composition) or top-level beat bands (one scene) */
|
|
55
|
+
#comp-timeline { display: none; padding: 8px 16px 10px; background: #1f1f25; border-top: 1px solid #333; }
|
|
56
|
+
#comp-timeline.on { display: block; }
|
|
57
|
+
#comp-timeline .ct-title { color: #8a8a96; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
|
58
|
+
.ct-bandrow { display: flex; align-items: flex-start; }
|
|
59
|
+
.ct-bandrow .tk-label { padding-top: 4px; }
|
|
60
|
+
#comp-track { position: relative; flex: 1; min-width: 0; background: #16161b; border-radius: 6px; }
|
|
61
|
+
.ct-scene { position: absolute; background: #2a2a32; border: 1px solid #3a3a44; border-radius: 5px; cursor: pointer; overflow: hidden; box-sizing: border-box; padding: 4px 8px; white-space: nowrap; }
|
|
62
|
+
.ct-scene:hover { border-color: #7d9aff; }
|
|
63
|
+
.ct-scene.active { background: #31313c; border-color: #7d9aff; color: #fff; }
|
|
64
|
+
.ct-scene.beat { border-style: dashed; }
|
|
65
|
+
.ct-scene .ct-range { color: #8a8a96; font-size: 10px; margin-left: 6px; font-variant-numeric: tabular-nums; }
|
|
66
|
+
#ct-playhead { position: absolute; top: -2px; bottom: -2px; width: 2px; background: #ff4d00; pointer-events: none; }
|
|
67
|
+
/* node tracks (dope sheet): each node a lane, its motion segments as bars */
|
|
68
|
+
.tk-toggle { background: none; border: 1px solid #3a3a44; color: #8a8a96; font-size: 11px; border-radius: 4px; padding: 2px 8px; cursor: pointer; margin-top: 8px; }
|
|
69
|
+
.tk-toggle:hover { border-color: #7d9aff; color: #ddd; }
|
|
70
|
+
#comp-tracks { position: relative; margin-top: 6px; max-height: 150px; overflow-y: auto; display: none; }
|
|
71
|
+
#comp-tracks.on { display: block; }
|
|
72
|
+
.tk-row { display: flex; align-items: center; height: 18px; }
|
|
73
|
+
.tk-label { width: 120px; flex: none; color: #99a; font-size: 11px; padding: 0 6px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; }
|
|
74
|
+
.tk-label:hover { color: #fff; }
|
|
75
|
+
.tk-row.selected .tk-label { color: #fff; font-weight: 600; }
|
|
76
|
+
.tk-lane { position: relative; flex: 1; height: 12px; background: #16161b; border-radius: 3px; }
|
|
77
|
+
.tk-bar { position: absolute; top: 1px; height: 10px; background: #3a4a86; border-radius: 2px; cursor: pointer; min-width: 3px; box-sizing: border-box; }
|
|
78
|
+
.tk-bar:hover { background: #7d9aff; }
|
|
79
|
+
.tk-bar.path { background: #b06a2a; }
|
|
80
|
+
.tk-bar.group { background: #4a4a58; }
|
|
81
|
+
.tk-bar.group:hover { background: #5e5e70; }
|
|
82
|
+
.tk-row.selected .tk-bar { background: #7d9aff; }
|
|
83
|
+
#tk-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: #ff4d00; pointer-events: none; }
|
|
45
84
|
</style>
|
|
46
85
|
</head>
|
|
47
86
|
<body>
|
|
@@ -52,9 +91,23 @@
|
|
|
52
91
|
<div id="bar">
|
|
53
92
|
<select id="scene-select"></select>
|
|
54
93
|
<button id="play">play</button>
|
|
55
|
-
<
|
|
94
|
+
<button id="play-all" title="play the whole composition across scenes" style="display:none">play all</button>
|
|
95
|
+
<button id="mark-in" title="set loop start to current time">[</button>
|
|
96
|
+
<button id="loop" title="loop the in/out range">loop</button>
|
|
97
|
+
<button id="mark-out" title="set loop end to current time">]</button>
|
|
98
|
+
<select id="speed" title="playback speed">
|
|
99
|
+
<option value="0.25">0.25×</option>
|
|
100
|
+
<option value="0.5">0.5×</option>
|
|
101
|
+
<option value="1" selected>1×</option>
|
|
102
|
+
<option value="2">2×</option>
|
|
103
|
+
</select>
|
|
104
|
+
<div id="scrub-wrap">
|
|
105
|
+
<div id="loop-band"></div>
|
|
106
|
+
<input id="scrub" type="range" min="0" max="1" step="0.001" value="0" />
|
|
107
|
+
</div>
|
|
56
108
|
<span id="time">0.000 / 0.000</span>
|
|
57
109
|
</div>
|
|
110
|
+
<div id="comp-timeline"></div>
|
|
58
111
|
<script type="module" src="/src/main.ts"></script>
|
|
59
112
|
</body>
|
|
60
113
|
</html>
|