reframe-video 0.6.12 → 0.6.14
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/dist/bin.js +38 -7
- package/dist/browserEntry.js +3 -2
- package/dist/cli.js +3 -2
- package/dist/diff.js +1189 -0
- package/dist/index.js +4 -2
- package/dist/labels.js +3 -2
- package/dist/types/ir.d.ts +3 -0
- package/guides/directing-guide.md +101 -0
- package/guides/edsl-guide.md +6 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var DEFAULT_TO_DURATION = 0.5;
|
|
|
4
4
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
5
5
|
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
6
6
|
var DEFAULT_FPS = 30;
|
|
7
|
+
var DEFAULT_STILL_DURATION = 1;
|
|
7
8
|
|
|
8
9
|
// ../core/src/path.ts
|
|
9
10
|
function pathBBox(d) {
|
|
@@ -316,14 +317,14 @@ function compileScene(ir) {
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
};
|
|
319
|
-
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
320
|
+
const inferredEnd = (ir.timeline ? walk(ir.timeline, 0) : 0) || 0;
|
|
320
321
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
321
322
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
322
323
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
323
324
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
324
325
|
return {
|
|
325
326
|
ir,
|
|
326
|
-
duration: ir.duration ?? inferredEnd,
|
|
327
|
+
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
327
328
|
segments,
|
|
328
329
|
motionPaths,
|
|
329
330
|
initialValues,
|
|
@@ -3505,6 +3506,7 @@ export {
|
|
|
3505
3506
|
DEFAULT_CROSSFADE,
|
|
3506
3507
|
DEFAULT_FPS,
|
|
3507
3508
|
DEFAULT_MOTIONPATH_DURATION,
|
|
3509
|
+
DEFAULT_STILL_DURATION,
|
|
3508
3510
|
DEFAULT_TO_DURATION,
|
|
3509
3511
|
DEFAULT_TWEEN_DURATION,
|
|
3510
3512
|
DEVICE_PRESET_NAMES,
|
package/dist/labels.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
var DEFAULT_TO_DURATION = 0.5;
|
|
5
5
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
6
6
|
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
7
|
+
var DEFAULT_STILL_DURATION = 1;
|
|
7
8
|
|
|
8
9
|
// ../core/src/path.ts
|
|
9
10
|
function locate(segCount, u) {
|
|
@@ -300,14 +301,14 @@ function compileScene(ir) {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
};
|
|
303
|
-
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
304
|
+
const inferredEnd = (ir.timeline ? walk(ir.timeline, 0) : 0) || 0;
|
|
304
305
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
305
306
|
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
306
307
|
const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
|
|
307
308
|
const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
|
|
308
309
|
return {
|
|
309
310
|
ir,
|
|
310
|
-
duration: ir.duration ?? inferredEnd,
|
|
311
|
+
duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
|
|
311
312
|
segments,
|
|
312
313
|
motionPaths,
|
|
313
314
|
initialValues,
|
package/dist/types/ir.d.ts
CHANGED
|
@@ -508,3 +508,6 @@ export declare const DEFAULT_TO_DURATION = 0.5;
|
|
|
508
508
|
export declare const DEFAULT_TWEEN_DURATION = 0.5;
|
|
509
509
|
export declare const DEFAULT_MOTIONPATH_DURATION = 1;
|
|
510
510
|
export declare const DEFAULT_FPS = 30;
|
|
511
|
+
/** Fallback length (seconds) for a scene with no animating timeline — a static
|
|
512
|
+
* frame still needs a positive duration to render. Override with scene `duration`. */
|
|
513
|
+
export declare const DEFAULT_STILL_DURATION = 1;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# reframe directing guide — high-end, reference-heavy pieces
|
|
2
|
+
|
|
3
|
+
Read this (after the syntax guide, `reframe guide`) when the ask is a CINEMATIC or
|
|
4
|
+
REFERENCE-FAITHFUL piece — a product teaser, a UI/session reproduction, a title
|
|
5
|
+
sequence, a data story — not a simple lower-third or KPI card (those you just write).
|
|
6
|
+
Simple jobs render first-try; the ceiling needs a process. This is that process.
|
|
7
|
+
|
|
8
|
+
## What to get from the user (before writing anything)
|
|
9
|
+
|
|
10
|
+
Ask for / confirm these — vague prompts are why these pieces take many rounds:
|
|
11
|
+
|
|
12
|
+
- **Concept** in one line ("a faithful Claude Code session that builds a logo", "an app
|
|
13
|
+
that goes viral, everywhere").
|
|
14
|
+
- **References** — screenshots / a reference video / pasted real content (terminal output,
|
|
15
|
+
copy, data). For fidelity work, the reference IS the spec. Save them to disk so you can
|
|
16
|
+
`diff` against them.
|
|
17
|
+
- **Brand** — exact colors (hex), the wordmark, the font feel.
|
|
18
|
+
- **Format** — length (~10–20s is a good ceiling clip), aspect (16:9 / 9:16), with or
|
|
19
|
+
without sound.
|
|
20
|
+
- **Tone** — "Apple teaser" (slow, premium, lots of negative space) vs "faithful UI sim"
|
|
21
|
+
(exact, dense) vs "kinetic/energetic". This sets pacing and camera.
|
|
22
|
+
|
|
23
|
+
## The loop
|
|
24
|
+
|
|
25
|
+
### 1. Storyboard the beats FIRST (structure, not a flat timeline)
|
|
26
|
+
|
|
27
|
+
Name the acts with `beat("...", {}, [ ... ])` before animating. A beat is a labeled,
|
|
28
|
+
retimable narrative unit; its label anchors audio and lets you restructure whole sections.
|
|
29
|
+
A reliable arc: **setup → inciting beat → rising → climax → resolution.** Decide what each
|
|
30
|
+
beat shows and how long, THEN fill in motion. (See `device-hero.ts`: `beat("ki"/"seung"/
|
|
31
|
+
"jeon"/"gyeol", …)` — entrance → it-takes-off → everywhere → resolve.)
|
|
32
|
+
|
|
33
|
+
### 2. Match references with `diff` (stop eyeballing)
|
|
34
|
+
|
|
35
|
+
Reproducing a screenshot pixel-faithfully is the hardest part. Use the tool:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
reframe diff ref.png --mode grid # labelled 100px grid over the screenshot → read coords, place nodes
|
|
39
|
+
reframe diff ref.png scene.ts --mode side # reference | your render, side by side
|
|
40
|
+
reframe diff ref.png scene.ts --mode diff # absolute difference — bright where you're off
|
|
41
|
+
reframe diff ref.png scene.ts --mode blend # 50% overlay — spot drift
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Loop: `--mode grid` to measure → write the node tree → `--mode side`/`diff` to compare →
|
|
45
|
+
fix coordinates/sizes/colors → repeat until faithful. Pick the frame with `--t <sec>`.
|
|
46
|
+
The grid is rendered at the reference's **full resolution** — the printed numbers are
|
|
47
|
+
exact scene pixels, so place nodes at the labelled coordinates directly (no scaling).
|
|
48
|
+
`diff`/`blend` are sharpest on hard edges (type, icons, frames); over large **soft
|
|
49
|
+
gradients/glows** they always light up "different" even when close, so tune those by
|
|
50
|
+
eye with `--mode side` rather than chasing the diff to black.
|
|
51
|
+
|
|
52
|
+
### 3. Apply the cinematic-craft checklist
|
|
53
|
+
|
|
54
|
+
These are what make a piece read as premium, not a slideshow. Patterns proven in the
|
|
55
|
+
flagship scenes — reuse the technique, vary the content:
|
|
56
|
+
|
|
57
|
+
- **Camera moves with the story.** Push in on each beat: a `cameraTo(...)` running in `par`
|
|
58
|
+
with the beat's content. Frame the detail that matters, pull back to resolve. (See
|
|
59
|
+
`terminal-claude.ts` helpers `cam()`/`scroll()`/`show()` — focus + scroll + reveal as
|
|
60
|
+
parameterized eased moves.)
|
|
61
|
+
- **Curved entrances, not straight slides.** A hero enters on a `motionPath` arc with
|
|
62
|
+
`easeOutBack` (overshoot, then settle). (`device-hero.ts` `motionPath("phone-cam", [[…]],
|
|
63
|
+
{ ease: "easeOutBack" })`.)
|
|
64
|
+
- **Fake depth.** Layer a backdrop of many faint concentric ellipses (a smooth glow, no hard
|
|
65
|
+
edge) + a spotlight + a cast shadow that tracks the hero + an impact ring on landing.
|
|
66
|
+
(`device-hero.ts` backdrop/spot/shadow/ring rig.) Or use real depth: `camera.perspective`
|
|
67
|
+
+ per-node `z` (see the syntax guide's "Depth & perspective").
|
|
68
|
+
- **Layered idle motion.** Nothing should sit perfectly still. `oscillate` a few nodes at
|
|
69
|
+
DIFFERENT frequencies (slow float, slower tilt, a fast accent) for life during holds.
|
|
70
|
+
- **Sound on the beats.** `scene.audio` cues anchor to your beat/timeline labels, so they
|
|
71
|
+
survive retiming: `{ at: "land", file: "bong_001.ogg" }`, `{ at: "viral", offset: 0.4,
|
|
72
|
+
sfx: "pop" }`. An `ambient-pad` bgm with `duck` under the hits. Quote `reframe labels` to
|
|
73
|
+
see exact seconds.
|
|
74
|
+
|
|
75
|
+
### 4. Verify objectively (don't argue about "more dynamic")
|
|
76
|
+
|
|
77
|
+
- `reframe labels scene.ts` — every label → exact seconds. The timing source for audio + a
|
|
78
|
+
sanity check that beats land when you think.
|
|
79
|
+
- `reframe motion out.mp4` — speeds, static fraction, oscillation rhythm, spikes. A vague
|
|
80
|
+
note like "make it punchier" becomes measurable: compare `meanSpeed`/`peakSpeed` before
|
|
81
|
+
and after; `staticFraction` too high = it drags.
|
|
82
|
+
- `reframe trace ref.mp4 --apply scene.ts` — when you have a reference VIDEO (not image),
|
|
83
|
+
extract its timing/easing and re-apply it onto YOUR node ids. Borrow the motion, keep your
|
|
84
|
+
assets.
|
|
85
|
+
|
|
86
|
+
### 5. Hand-tune via preview → overlay
|
|
87
|
+
|
|
88
|
+
`reframe preview` to scrub, drag motionPath waypoints, and retime steps; export the overlay
|
|
89
|
+
JSON and render with `--overlay`. Those nudges survive a later regeneration (stable
|
|
90
|
+
addresses), so the human's polish isn't lost when you redo the base.
|
|
91
|
+
|
|
92
|
+
## Pitfalls
|
|
93
|
+
|
|
94
|
+
- Don't animate before the structure is right — fixing pacing after everything is keyframed
|
|
95
|
+
is painful. Beats first.
|
|
96
|
+
- Reference fidelity is coordinates + color + type, mostly STATIC layout; get the held frame
|
|
97
|
+
matching with `diff` before adding motion.
|
|
98
|
+
- Keep `id`s/labels stable across rewrites (see `reframe guide --regen`) so the user's
|
|
99
|
+
overlay edits survive.
|
|
100
|
+
- It's still iterative. The tools cut the rounds; they don't remove the loop. Render, look,
|
|
101
|
+
adjust — the agent should render frames and read them, not guess.
|
package/guides/edsl-guide.md
CHANGED
|
@@ -68,6 +68,9 @@ Factories return plain data. Every node needs a unique `id`.
|
|
|
68
68
|
`"top-left"` (default) | `"top-center"` | `"top-right"` | `"center-left"` |
|
|
69
69
|
`"center"` | `"center-right"` | `"bottom-left"` | `"bottom-center"` | `"bottom-right"`.
|
|
70
70
|
Example: a bar that grows upward = `anchor: "bottom-left"` + animate `height`.
|
|
71
|
+
**Text alignment is `anchor`, not a separate `align` prop:** the anchor's horizontal
|
|
72
|
+
half sets the text align — `"…-left"` left-aligns, `"…-center"`/`"center"` centers,
|
|
73
|
+
`"…-right"` right-aligns (a right-aligned wordmark in a corner = `anchor: "bottom-right"`).
|
|
71
74
|
Font: use `fontFamily: "Inter"` (weights 400/700/800 are available).
|
|
72
75
|
|
|
73
76
|
### Layout helpers (evenly spacing things)
|
|
@@ -133,7 +136,9 @@ target then settles — a pop/snap), `easeIn/Out/InOutElastic` (rings around the
|
|
|
133
136
|
target — a playful spring), `easeIn/Out/InOutBounce` (drops and bounces to rest).
|
|
134
137
|
A logo or card "popping" in usually wants `easeOutBack`; a stamp landing,
|
|
135
138
|
`easeOutBounce`.
|
|
136
|
-
Scene duration is inferred from the timeline.
|
|
139
|
+
Scene duration is inferred from the timeline. For a **static frame** you can omit
|
|
140
|
+
`timeline` entirely (or set scene `duration: <seconds>`) — a still defaults to a 1s
|
|
141
|
+
render; no throwaway `wait` is needed.
|
|
137
142
|
|
|
138
143
|
## Behaviors: continuous motion during holds
|
|
139
144
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
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",
|