@bycrux/montaj-skills 0.1.0 → 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/package.json +1 -1
- package/skills/image-search.md +4 -0
- package/skills/overlay.md +16 -12
- package/skills/select-takes.md +9 -5
- package/skills/write-overlay.md +109 -43
package/package.json
CHANGED
package/skills/image-search.md
CHANGED
|
@@ -44,6 +44,10 @@ Run step `fetch_image` with the following args. Downloads one HTTPS URL to a wor
|
|
|
44
44
|
|
|
45
45
|
Output: `{ "path": "/abs/path/to/file.jpg" }`. Some hosts return `403` to non-browser fetches — if one fails, run step `fetch_image` with the next candidate instead of fighting it.
|
|
46
46
|
|
|
47
|
+
> To register a fetched image into Hub Media (R2-backed, survives in the library), call
|
|
48
|
+
> `hub.ingest_media({ projectId, items: [{ workspacePath: "<absolute fetch_image out path>" }] })`.
|
|
49
|
+
> Do not hand-roll `create_media` + presigned PUT.
|
|
50
|
+
|
|
47
51
|
## Picking the right image
|
|
48
52
|
|
|
49
53
|
Search returns more than you need. Before committing:
|
package/skills/overlay.md
CHANGED
|
@@ -64,22 +64,25 @@ See skill `write-overlay` for the full authoring reference.
|
|
|
64
64
|
|
|
65
65
|
### 5. Save overlays to the project
|
|
66
66
|
|
|
67
|
-
Overlays live in `tracks[1+]` — overlay tracks in the unified tracks array. Each
|
|
67
|
+
Overlays live in `tracks[1+]` — overlay tracks in the unified tracks array. Each track is an object (`{id, items, ...}`); its `items` array holds the track's clips/overlays. Items in the same track cannot overlap in time; items in different tracks are z-ordered (higher indexes render on top). `tracks[0]` is always the primary footage track.
|
|
68
68
|
|
|
69
69
|
```json
|
|
70
70
|
{
|
|
71
71
|
"tracks": [
|
|
72
|
-
[],
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
{ "id": "trk-0", "items": [] },
|
|
73
|
+
{
|
|
74
|
+
"id": "trk-1",
|
|
75
|
+
"items": [
|
|
76
|
+
{
|
|
77
|
+
"id": "ov-0",
|
|
78
|
+
"type": "overlay",
|
|
79
|
+
"src": "/abs/path/to/project/overlays/hook.jsx",
|
|
80
|
+
"props": { "text": "The source code got leaked" },
|
|
81
|
+
"start": 0.0,
|
|
82
|
+
"end": 3.0
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
83
86
|
]
|
|
84
87
|
}
|
|
85
88
|
```
|
|
@@ -100,6 +103,7 @@ Follow save discipline: **read the project**, merge the updated `tracks` array i
|
|
|
100
103
|
- **Keep text short** — 2–6 words for lower-thirds, 4–8 for hooks. Short + large beats long + small
|
|
101
104
|
- **Leave `offsetX`, `offsetY`, `scale` at defaults** (`0`, `0`, `1`) — the human positions overlays via the UI drag tool after preview
|
|
102
105
|
- **Use assets from `project.assets`** — pass asset `src` paths as `props`, don't hardcode paths inside JSX
|
|
106
|
+
- **Expose text styling as props** — a text overlay you want editable in the editor's properties panel must READ its font, size, weight, style, color, alignment, transform, and background from `props` (the nine standard text props) with sensible defaults, not hardcode them in the JSX. A hardcoded style shows no control in the panel. See skill `write-overlay` → "Make text overlays editable in the properties panel"
|
|
103
107
|
|
|
104
108
|
## Render Constraints
|
|
105
109
|
|
package/skills/select-takes.md
CHANGED
|
@@ -6,7 +6,7 @@ step: true
|
|
|
6
6
|
|
|
7
7
|
# Select Takes
|
|
8
8
|
|
|
9
|
-
`montaj/select_takes` is an agent-authored task — no CLI step, no API call. You reason across all clip transcripts and make editorial decisions. The output is a set of cropped trim specs ready for `rm_fillers` and
|
|
9
|
+
`montaj/select_takes` is an agent-authored task — no CLI step, no API call. You reason across all clip transcripts and make editorial decisions. The output is a set of cropped trim specs ready for `rm_fillers`. Nothing joins them into a file: the surviving keeps become `tracks[0]` items with their own `inPoint`/`outPoint`, and the render engine assembles them in one pass at the end.
|
|
10
10
|
|
|
11
11
|
## Core Purpose
|
|
12
12
|
|
|
@@ -82,11 +82,15 @@ For each flagged seam, fix it by trimming the crop window of whichever section i
|
|
|
82
82
|
|
|
83
83
|
**This check is required before writing any spec files.** Seam problems cannot be caught by any automated step downstream.
|
|
84
84
|
|
|
85
|
-
### 9. Crop the trim specs —
|
|
85
|
+
### 9. Crop the trim specs — never encode an intermediate
|
|
86
86
|
|
|
87
|
-
For each selected take,
|
|
87
|
+
For each selected take, the trim spec JSON comes back **inline** (on stdout / in the step result) from the preceding `waveform_trim` step — it is NOT written to disk automatically. The `crop_spec` step requires an on-disk file (its `--input` argument). Before calling `crop_spec`, write the inline spec to the project scratch dir using the `write_file` tool (e.g. save it as `<clip>_spec.json`), then pass that path as `crop_spec`'s `input`.
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Crop the written spec to the selected take's virtual-timeline window using the `crop_spec` step.
|
|
90
|
+
|
|
91
|
+
**Never encode an intermediate video file here.** There is no `trim` step to call, and reaching for `materialize_cut` at this point would break the single-encode chain for no gain. Cropping the spec keeps `tracks[0].items[*].src` pointing at the original source all the way through to the final render, which is what lets the operator re-trim any cut later without losing quality — and what keeps each item's `proxySrc` valid, since a proxy covers the original file.
|
|
92
|
+
|
|
93
|
+
**Note:** the `input` path in all `crop_spec` calls below must point to a spec file you've written to disk via `write_file` first (see above).
|
|
90
94
|
|
|
91
95
|
Run step `crop_spec` with `{"input": "/path/IMG_4893_spec.json", "keeps": [[8.5, 34.1]]}` → returns `{"path": "/path/IMG_4893_spec_cropped.json"}` (single window).
|
|
92
96
|
|
|
@@ -116,7 +120,7 @@ Run step `virtual_to_original` with `{"input": "spec.json", "inverse": true, "ti
|
|
|
116
120
|
|
|
117
121
|
### 10. Output
|
|
118
122
|
|
|
119
|
-
An ordered list of `_selected.json` trim spec paths — one per selected section, in narrative order. These become the inputs to `rm_fillers
|
|
123
|
+
An ordered list of `_selected.json` trim spec paths — one per selected section, in narrative order. These become the inputs to `rm_fillers`, and the keeps that survive it become the `tracks[0]` items the render engine assembles.
|
|
120
124
|
|
|
121
125
|
## What to Log
|
|
122
126
|
|
package/skills/write-overlay.md
CHANGED
|
@@ -66,7 +66,7 @@ export default function List() {
|
|
|
66
66
|
|
|
67
67
|
## Writing the JSX
|
|
68
68
|
|
|
69
|
-
> **Carousel text overlays follow a stricter
|
|
69
|
+
> **Expose text styling as props to make an overlay editable.** A text overlay is only restyleable in the editor's properties panel for the props it declares — see "Make text overlays editable in the properties panel" below, which applies to video overlays too. **Carousel text overlays follow a stricter, required version of that contract:** every text-bearing overlay must accept its font size, family, weight, style, color, alignment, transform, and background as props with string defaults — see skill `editable-text`. The "go large — for video" guidance below, and the hardcoded-style `Hook` example just under it, **do not apply** to carousel editable-text overlays.
|
|
70
70
|
|
|
71
71
|
The default aesthetic is **plain bold text directly on video** — no card, no background, just a text shadow for legibility. Big text (96–160px) that covers the footage, including the speaker's face if needed.
|
|
72
72
|
|
|
@@ -111,6 +111,58 @@ Only add a card or background when the prompt explicitly asks, or when a specifi
|
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
114
|
+
## Make text overlays editable in the properties panel
|
|
115
|
+
|
|
116
|
+
The editor's right-hand properties panel (and the floating text toolbar) can restyle a text overlay **only for the props it actually declares**. It reads the nine standard text props off the item's `props` object:
|
|
117
|
+
|
|
118
|
+
`text`, `fontSize`, `fontFamily`, `fontWeight`, `fontStyle`, `color`, `textAlign`, `textTransform`, `bgColor`
|
|
119
|
+
|
|
120
|
+
A control appears for each of those that is present (non-null) on `props`; anything the overlay **hardcodes** in its JSX style instead of reading from `props` shows **no control at all**, and the operator can't change it. This is exactly why an overlay that writes `textTransform: 'uppercase'` straight into its style has no text-transform control in the editor.
|
|
121
|
+
|
|
122
|
+
**So a text overlay you want a human to be able to restyle must READ its text styling from `props`, with sensible defaults — not hardcode it.** Same house style (big, bold, plain on video), just sourced from props:
|
|
123
|
+
|
|
124
|
+
```jsx
|
|
125
|
+
// Editable Hook — every text property is adjustable in the panel.
|
|
126
|
+
export default function Hook() {
|
|
127
|
+
const progress = interpolate(frame, [0, 8], [0, 1], { extrapolateRight: 'clamp' })
|
|
128
|
+
const slideY = interpolate(frame, [0, 10], [40, 0], { extrapolateRight: 'clamp' })
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div style={{ position: 'absolute', bottom: 180, left: 48, right: 48, opacity: progress, transform: `translateY(${slideY}px)` }}>
|
|
132
|
+
<div style={{
|
|
133
|
+
fontFamily: props.fontFamily ?? 'Anton, Impact, sans-serif',
|
|
134
|
+
fontSize: props.fontSize ?? 120,
|
|
135
|
+
fontWeight: props.fontWeight ?? 900,
|
|
136
|
+
fontStyle: props.fontStyle ?? 'normal',
|
|
137
|
+
color: props.color ?? '#fff',
|
|
138
|
+
textAlign: props.textAlign ?? 'left',
|
|
139
|
+
textTransform: props.textTransform ?? 'uppercase',
|
|
140
|
+
background: props.bgColor ?? 'transparent',
|
|
141
|
+
lineHeight: 1.05, letterSpacing: '-1px',
|
|
142
|
+
textShadow: '0 2px 24px rgba(0,0,0,0.9), 0 0 60px rgba(0,0,0,0.5)',
|
|
143
|
+
}}>
|
|
144
|
+
{props.text}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Declare those same values in the item's `props` too, so the panel opens on the real values rather than blank controls:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
"props": {
|
|
155
|
+
"text": "She built an AI employee",
|
|
156
|
+
"fontSize": 120, "fontFamily": "Anton, Impact, sans-serif",
|
|
157
|
+
"fontWeight": 900, "fontStyle": "normal", "color": "#ffffff",
|
|
158
|
+
"textAlign": "left", "textTransform": "uppercase", "bgColor": "transparent"
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This now applies to **video** text overlays, not just carousel slides — the video editor has the same properties panel. For the full contract (types and validation) see skill `editable-text`. A non-text overlay (logo, image card, chart) has no text props to expose; the same principle still holds for whatever a human would want to tweak — pass it through `props`, don't bury it in the JSX.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
114
166
|
## Splitting background from content across tracks
|
|
115
167
|
|
|
116
168
|
The most reliable way to use frosted-glass / blurred card backgrounds is to **put the background on a separate, lower track** and the animated content on a higher track. The render pipeline composites tracks in order, so the content renders on top.
|
|
@@ -129,25 +181,31 @@ The most reliable way to use frosted-glass / blurred card backgrounds is to **pu
|
|
|
129
181
|
```json
|
|
130
182
|
{
|
|
131
183
|
"tracks": [
|
|
132
|
-
[],
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
{ "id": "trk-0", "items": [] },
|
|
185
|
+
{
|
|
186
|
+
"id": "trk-1",
|
|
187
|
+
"items": [
|
|
188
|
+
{
|
|
189
|
+
"id": "ov-card-bg",
|
|
190
|
+
"type": "overlay",
|
|
191
|
+
"src": "/path/overlays/card-bg.jsx",
|
|
192
|
+
"start": 2.0,
|
|
193
|
+
"end": 6.0
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "trk-2",
|
|
199
|
+
"items": [
|
|
200
|
+
{
|
|
201
|
+
"id": "ov-card-content",
|
|
202
|
+
"type": "overlay",
|
|
203
|
+
"src": "/path/overlays/card-content.jsx",
|
|
204
|
+
"start": 2.0,
|
|
205
|
+
"end": 6.0
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
151
209
|
]
|
|
152
210
|
}
|
|
153
211
|
```
|
|
@@ -446,29 +504,32 @@ Place overlay items in `tracks[1+]` in `project.json`. Each item must have `type
|
|
|
446
504
|
```json
|
|
447
505
|
{
|
|
448
506
|
"tracks": [
|
|
449
|
-
[],
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
"
|
|
507
|
+
{ "id": "trk-0", "items": [] },
|
|
508
|
+
{
|
|
509
|
+
"id": "trk-1",
|
|
510
|
+
"items": [
|
|
511
|
+
{
|
|
512
|
+
"id": "ov-hook",
|
|
513
|
+
"type": "overlay",
|
|
514
|
+
"src": "/abs/path/to/project/overlays/hook.jsx",
|
|
515
|
+
"start": 0.0,
|
|
516
|
+
"end": 3.0,
|
|
517
|
+
"props": {
|
|
518
|
+
"text": "She built an AI employee"
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
"id": "ov-logo",
|
|
523
|
+
"type": "overlay",
|
|
524
|
+
"src": "/abs/path/to/project/overlays/logo.jsx",
|
|
525
|
+
"start": 0.0,
|
|
526
|
+
"end": 999.0,
|
|
527
|
+
"props": {
|
|
528
|
+
"logoSrc": "/abs/path/to/project/assets/logo.png"
|
|
529
|
+
}
|
|
459
530
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
"id": "ov-logo",
|
|
463
|
-
"type": "overlay",
|
|
464
|
-
"src": "/abs/path/to/project/overlays/logo.jsx",
|
|
465
|
-
"start": 0.0,
|
|
466
|
-
"end": 999.0,
|
|
467
|
-
"props": {
|
|
468
|
-
"logoSrc": "/abs/path/to/project/assets/logo.png"
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
]
|
|
531
|
+
]
|
|
532
|
+
}
|
|
472
533
|
]
|
|
473
534
|
}
|
|
474
535
|
```
|
|
@@ -576,7 +637,12 @@ Common overlay set for a social reel:
|
|
|
576
637
|
|
|
577
638
|
**Write all of your overlay JSX first. Then sample them in a single batch pass — do not sample after each file.** Per-file sampling stalls authoring and spins up a fresh Puppeteer process each time; one pass at the end over the finished set is faster and just as safe, since nothing downstream consumes an overlay until you save the project with the whole batch.
|
|
578
639
|
|
|
579
|
-
Once every JSX file is written, loop over them in one pass — for each JSX file, **run step `sample_overlay`** with args `{
|
|
640
|
+
Once every JSX file is written, loop over them in one pass — for each JSX file, **run step `sample_overlay`** with args `{ overlay, out, measure: true, google_fonts, props }`:
|
|
641
|
+
|
|
642
|
+
- `overlay` — absolute path to the JSX file
|
|
643
|
+
- `out` — absolute path where the step writes the sample PNG (required)
|
|
644
|
+
- `google_fonts` — the overlay's declared `googleFonts` value (snake_case step arg)
|
|
645
|
+
- `props` — representative props for the overlay
|
|
580
646
|
|
|
581
647
|
Pass each overlay's declared `googleFonts` (and representative `props`) so the step measures with the real render-time font — see the Syne case study below.
|
|
582
648
|
|