reframe-video 0.6.34 → 0.6.39

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.
@@ -135,6 +135,16 @@ them with normal TS (`Object.fromEntries`, `.map`) for data-driven scenes.
135
135
  `curviness` shapes the path: `1` smooth (default), `0` sharp corners, `>1` loopier.
136
136
  - `wait(seconds, label?)` — hold; the optional `label` names the hold so audio
137
137
  cues and overlay retiming can address it.
138
+ - `beat(name, opts, children)` — a named, retimable, reorderable span (the unit
139
+ humans/AI revise; its `name` is a stable overlay address). `opts`: `parallel`,
140
+ `at` (absolute start — a NUMBER, or a **label string to anchor to**), `gap`,
141
+ `scale`/`duration` (time-stretch), `order` (reorder within a `seq`), `nodes`.
142
+ **Label anchor**: `beat("caption", { at: "shot-2" }, [...])` starts the beat at
143
+ the `shot-2` label's time (with `gap` as the offset), so a title/lower-third/
144
+ caption laid over a montage stays locked to its shot when the cut is retimed
145
+ (via an overlay or AI regen) — the same retime-survival `audio.cues` get. Put
146
+ anchored beats in a `par` branch (an overlay layer), not inside a sequential
147
+ flow. See `examples/scenes/media-story.ts`.
138
148
 
139
149
  Eases: `linear`, `easeIn/Out/InOutQuad`, `easeIn/Out/InOutCubic`,
140
150
  `easeIn/Out/InOutQuart`, `easeIn/Out/InOutExpo`, or `{ cubicBezier: [x1,y1,x2,y2] }`.
@@ -421,6 +431,37 @@ scene({ size, nodes: [...m.nodes, ...titles], timeline: par(m.timeline, titleTra
421
431
  - Seeded + pure (same `(shots, opts)` → identical IR). Note: image/video sources do
422
432
  not render in `reframe player` / artifacts — montage ships as mp4. See
423
433
  `examples/scenes/video-montage.ts`.
434
+ - **Assemble from files**: `reframe assemble <media...> [-o name] [--title "…"]
435
+ [--bgm <synth>] [--hold s] [--seed N]` probes each clip's real duration (so a
436
+ video shot's `hold` = its actual length, never a freeze) and scaffolds an editable
437
+ scene `.ts` wiring `photoMontage` + an optional `title` + a bed. The probed
438
+ numbers are baked in, so the emitted scene is a normal deterministic scene — edit
439
+ it (reorder, retime, swap a `src`), then `reframe render` it.
440
+
441
+ ## Titles & lower-thirds (`title` / `lowerThird`)
442
+
443
+ The motion-graphic overlay vocabulary for a media piece — generators that return
444
+ `{ nodes, timeline }` to compose over a montage (or anything). Stable ids so overlays
445
+ address them; pure + deterministic.
446
+
447
+ - `title({ text, id?, x?, y?, fontSize?, fontWeight?, fill?, letterSpacing?,
448
+ entrance?, exit?, speed?, seed?, hold? })` → `{ nodes, timeline, block }`. A kinetic
449
+ headline built on `splitText` + `textIn` (entrance presets: `cascade` `rise`
450
+ `bounce` `typewriter` `assemble` `decode`). Set `exit` (a `textOut` preset) and it
451
+ plays in, holds `hold`s, then exits. Glyph ids `${id}-${i}`; labels `${id}-in` /
452
+ `${id}-out`. `block` is returned so you can add `textLoop` behaviors or extra tweens.
453
+ - `lowerThird({ name, role?, id?, x?, y?, accent?, fill?, subFill?, fontSize?, hold? })`
454
+ → `{ nodes, timeline }`. A name/role strap: an accent bar grows in, the text slides +
455
+ fades. Ids `${id}` (group) / `${id}-bar` / `${id}-name` / `${id}-role`; labels
456
+ `${id}-in` / `${id}-out`. Defaults to a bottom-left title-safe position.
457
+
458
+ ```ts
459
+ const ttl = title({ text: "OUR YEAR", id: "ttl", x: 960, y: 540, fontSize: 132, entrance: "rise", exit: "dissolve", hold: 1.6 });
460
+ const lt = lowerThird({ name: "Nantes, France", role: "spring 2026", id: "lt" });
461
+ // nodes: [...m.nodes, ...ttl.nodes, ...lt.nodes]
462
+ // timeline: par(m.timeline, ttl.timeline, seq(wait(6.6), lt.timeline))
463
+ ```
464
+ See `examples/scenes/media-story.ts`.
424
465
 
425
466
  ## Video clips (`video`)
426
467
 
@@ -437,6 +478,9 @@ tween("clip", { scale: 1.08 }, { duration: 5 }) // transform composes with play
437
478
  `fit` (`"cover"` like the image node), `start` (scene-time playback begins), `rate`
438
479
  (speed), `clipStart` (source in-point s), `volume` (clip-audio gain, default 1; `0` mutes).
439
480
  Transform/opacity/effects compose as usual.
481
+ - **`start` can be a label** (not just a number): `start: "shot-2"` anchors playback to that
482
+ timeline label's time (like `beat.at`), so the clip **ripples** when its shot is retimed (by an
483
+ overlay or AI regen) instead of desyncing. `photoMontage` does this automatically for video shots.
440
484
  - **Deterministic by frame extraction**: render-cli runs `ffmpeg -vf fps=<sceneFps>` to pull
441
485
  the clip's frames, and the renderer draws frame `round(t·fps)` — no live `<video>` seek, so
442
486
  it stays byte-identical (same machine).
@@ -506,6 +550,20 @@ vector frame (bezel, rounded body, phone notch / dynamic island, browser chrome)
506
550
  (overlay/regen addresses) — keep `id` across rewrites.
507
551
  - It's one node: animate the device group for the float/entrance (`tween`/
508
552
  `motionPath` its `x`/`y`/`scale`/`rotation`, `oscillate` for an idle drift).
553
+ - **Premium by default** — `material:"premium"` (the default) gives a gradient
554
+ body, an ambient screen glow, a soft contact shadow and (glass) a sheen; the
555
+ `style` knob picks `"glass"` (realistic glass/metal, default) or `"neon"` (flat
556
+ body + additive accent edge-glow, graphic punch). `material:"flat"` opts back to
557
+ clean solid fills. All of this is purely cosmetic — the screen rect, the clip,
558
+ and the stable ids are identical across materials/styles, so `deviceScreen`
559
+ coords and existing `content`/overlays are unaffected.
560
+ - **Auto-varied per instance** — each device's look (bezel, corner, glare angle,
561
+ neon hue) is derived deterministically from its `id`, so two devices differ
562
+ while staying on-model. Pass `seed` to pin or explore a variation; same `seed`
563
+ → identical, different `seed` → same family. Reproducible (no `Math.random`).
564
+ - `notch?: "island" | "notch" | "punch" | "none"` selects the phone front-camera
565
+ treatment (default `"island"` — keep it explicit for an iOS vs Android read).
566
+ - See `examples/scenes/device-gallery.ts` for glass/neon + seed variation.
509
567
 
510
568
  ```ts
511
569
  // a phone floating centre, a chat bubble inside the screen:
@@ -27,3 +27,17 @@ each joint is `${id}-${jointName}` (e.g. `hero-armUpperR`) and its bone art is
27
27
  joint `name`s** for any character/device that survives the redesign — overlay
28
28
  edits (a retimed wave, a nudged limb angle) reference those exact ids. Renaming a
29
29
  joint orphans the edit, exactly like renaming a hand-authored node id.
30
+
31
+ ## Tooling
32
+
33
+ Three read-only commands make the address namespace queryable and the contract
34
+ checkable (no render):
35
+
36
+ - `reframe manifest <scene> [--json]` — list every editable address (nodes +
37
+ their editable/animated props, states, timeline labels with patchable params,
38
+ beats, behaviors). Read it before patching so you target real, stable addresses.
39
+ - `reframe lint <scene> [--strict]` — flag motion with no `label` (timing an
40
+ overlay can't reach and a regen can silently drop) + a `motionAddressableRatio`.
41
+ - `reframe verify-overlay <base> <overlay>...` — compose the overlay onto a base
42
+ and report applied vs orphaned. Run it against the regenerated base to prove
43
+ every edit survived; it exits non-zero if any address broke.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.34",
3
+ "version": "0.6.39",
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",
@@ -72,8 +72,34 @@ to handle explicitly:
72
72
  mask (update the scene AND remove/update the superseded overlay entry) and
73
73
  tell them why.
74
74
 
75
+ Addressability tooling (read-only, no render — use it when editing):
76
+
77
+ - `npx -y reframe-video manifest <scene> [--json]` — list the scene's editable
78
+ surface (every node + its editable/animated props, states, timeline labels
79
+ with patchable params, beats, behaviors, each with its overlay address). Read
80
+ this BEFORE patching so you target real, stable addresses instead of guessing.
81
+ - `npx -y reframe-video lint <scene> [--strict]` — flag motion with no `label`
82
+ (timing a later overlay can't reach, and a regen can silently drop) plus a
83
+ `motionAddressableRatio`. When authoring motion the user may want to tweak,
84
+ give the step a stable `label`.
85
+ - `npx -y reframe-video verify-overlay <base> <overlay>... ` — after you rewrite
86
+ a base that has overlays, run this to confirm every edit still applies (it
87
+ reports orphans and exits non-zero if any address broke). The regen-survival
88
+ check, without a full render.
89
+
75
90
  ## Other capabilities
76
91
 
92
+ - **Assemble media → scene**: when the user hands you images/videos for a piece,
93
+ `npx -y reframe-video assemble <media...> [-o name] [--title "…"] [--bgm <synth>]`
94
+ probes each clip's real duration and scaffolds an editable montage scene `.ts`
95
+ (clip-aware holds, so a short clip never freezes) wiring `photoMontage` + an
96
+ optional `title` + a bed. Then edit the `.ts` (reorder shots, retime, swap a
97
+ `src`) and `render` it. For motion-graphic overlays use the `title()` (kinetic
98
+ headline) and `lowerThird()` (name/role strap) generators — both return
99
+ `{ nodes, timeline }` you compose over the montage; see the guide. **Anchor an
100
+ overlay beat to a shot label** — `beat("cap", { at: "shot-2" }, [lt.timeline])`
101
+ in a `par` branch — so the caption stays synced to its shot if the cut is
102
+ retimed (don't pin it to a fixed `wait`).
77
103
  - **Batch**: `npx -y reframe-video batch scene.ts data.json` — one mp4 per
78
104
  data row; row keys are overlay addresses (`nodes.<id>.<prop>`,
79
105
  `timeline.<label>.duration`, ...). CSV works too (headers = addresses).