@pixerate/editor 0.222.2 → 0.222.3
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/README.md +5 -2
- package/skills/pixerate-paint-watercolour/evals/sky-wash.json +1 -1
- package/skills/pixerate-video-colour-grade/SKILL.md +105 -0
- package/skills/pixerate-video-colour-grade/evals/look-in-one-place.json +16 -0
- package/skills/pixerate-video-colour-grade/evals/lut-keep-skin.json +132 -0
- package/skills/pixerate-video-colour-grade/evals/match-exposure.json +14 -0
- package/skills/pixerate-video-editor/SKILL.md +3 -0
- package/skills/pixerate-video-montage/SKILL.md +99 -0
- package/skills/pixerate-video-montage/evals/assemble-on-beats.json +231 -0
- package/skills/pixerate-video-montage/evals/music-and-ending.json +210 -0
- package/skills/pixerate-video-montage/evals/short-crossfades.json +190 -0
- package/skills/pixerate-video-social-cut/SKILL.md +105 -0
- package/skills/pixerate-video-social-cut/evals/hook-first.json +19 -0
- package/skills/pixerate-video-social-cut/evals/opening-title.json +14 -0
- package/skills/pixerate-video-social-cut/evals/vertical-reframe.json +15 -0
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -50,6 +50,9 @@ request like "clean up this export" finds it.
|
|
|
50
50
|
| [`pixerate-paint-watercolour`](pixerate-paint-watercolour/SKILL.md) | Paint (`pixerate-paint-editor`) | Watercolour paper, light to dark with the paper's white kept, wet-in-wet and wet-on-dry washes, drying before glazing, steering the water. |
|
|
51
51
|
| [`pixerate-paint-oil-impasto`](pixerate-paint-oil-impasto/SKILL.md) | Paint (`pixerate-paint-editor`) | A toned ground, thin block-in to thick paint, pigment mixing, impasto relief and its light. |
|
|
52
52
|
| [`pixerate-paint-sketch-to-finish`](pixerate-paint-sketch-to-finish/SKILL.md) | Paint (`pixerate-paint-editor`) | Sketch, ink, flats under the lines, clipped shading and light, one named layer per pass. |
|
|
53
|
+
| [`pixerate-video-social-cut`](pixerate-video-social-cut/SKILL.md) | Video (`pixerate-video-editor`) | Vertical or square reframes that fill the frame, the hook first, platform lengths, titles in the safe area. |
|
|
54
|
+
| [`pixerate-video-colour-grade`](pixerate-video-colour-grade/SKILL.md) | Video (`pixerate-video-editor`) | Correct and match shots, then one look on an adjustment clip; LUTs at moderate strength with skin protected. |
|
|
55
|
+
| [`pixerate-video-montage`](pixerate-video-montage/SKILL.md) | Video (`pixerate-video-editor`) | Selects on the beat, varied pacing, short transitions with handles, music under everything and a clean ending. |
|
|
53
56
|
| [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
|
|
54
57
|
| [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
|
|
55
58
|
|
|
@@ -113,8 +116,8 @@ same change.
|
|
|
113
116
|
`packages/core/tests/agent-skill-evals.test.ts`. An eval has a `prompt` (the task, in the
|
|
114
117
|
user's words), a `fixture` (the starting document: `{ "editor": "spreadsheet", "rows" }`,
|
|
115
118
|
`{ "editor": "image", "width", "height", "background", "shapes" }` or `{ "editor":
|
|
116
|
-
"vector", "width", "height", "setup" }` with commands that draw the starting art,
|
|
117
|
-
`{ "editor": "paint", "width", "height", "setup" }` with tool calls), `checks` (a read-only
|
|
119
|
+
"vector", "width", "height", "setup" }` with commands that draw the starting art,
|
|
120
|
+
`{ "editor": "paint", "width", "height", "setup" }` or `{ "editor": "video", "setup" }` with tool calls), `checks` (a read-only
|
|
118
121
|
tool, or `run_command` with a read-only command such as `vector.list`, its `args`, what its result must match in `expect`, and `why`) and a `solution` (the
|
|
119
122
|
tool calls an expert would make). The test proves each eval: some check fails on the fixture,
|
|
120
123
|
and all pass after the solution. A model harness runs the same file with the model in place
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{ "tool": "set_brush", "args": { "id": "watercolor-wash" } },
|
|
13
13
|
{ "tool": "set_color", "args": { "color": "#6fa8dc" } },
|
|
14
14
|
{ "tool": "paint_stroke", "args": { "points": [{ "x": 0, "y": 6 }, { "x": 64, "y": 6 }, { "x": 64, "y": 14 }, { "x": 0, "y": 14 }] } },
|
|
15
|
-
{ "tool": "advance_wet_paint", "args": { "seconds":
|
|
15
|
+
{ "tool": "advance_wet_paint", "args": { "seconds": 3 } },
|
|
16
16
|
{ "tool": "dry_paint" },
|
|
17
17
|
{ "tool": "get_wet_paint" }
|
|
18
18
|
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-video-colour-grade
|
|
3
|
+
description: The colour grading workflow in the Pixerate video editor - correcting each shot first (exposure, contrast, white balance) so a scene matches, then one look over the whole piece on an adjustment clip, LUTs at a sensible strength with skin tones protected, masks for spot changes, and checking with scopes and frames. Use when asked to colour correct, colour grade, match shots, fix exposure or white balance in a video, add a look, a LUT, a film or cinematic look, warm or cool a video, or protect skin tones, or when update_video_clip grade, add_video_adjustment_clip, set_video_lut or get_video_scopes are available. Load with pixerate-video-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-video-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Grading a video
|
|
10
|
+
|
|
11
|
+
A grade has two passes. **Correction** makes each shot right and makes shots in a scene match:
|
|
12
|
+
exposure, contrast and white balance on the clip's own `grade`. **The look** is one creative
|
|
13
|
+
decision over everything: an adjustment clip above the timeline with its own grade or a LUT,
|
|
14
|
+
so it is changed in one place. Faces are the reference: people notice skin that is wrong before
|
|
15
|
+
anything else.
|
|
16
|
+
|
|
17
|
+
This skill is the method. `pixerate-video-editor` has the full tool reference.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- Shots in a scene match in brightness and colour; cuts between them do not jump.
|
|
22
|
+
- Corrections are moderate: exposure within about ±1 stop, contrast and saturation within
|
|
23
|
+
±0.3, white balance within ±0.3, unless a shot is badly off.
|
|
24
|
+
- One look, in one place: an adjustment clip over the whole timeline (or one per scene), not
|
|
25
|
+
the same settings pasted on every clip.
|
|
26
|
+
- Skin stays natural under strong looks (skin protection 0.5-0.8 on LUTs).
|
|
27
|
+
- The original corrections are not overwritten by the look.
|
|
28
|
+
|
|
29
|
+
## Inputs to confirm
|
|
30
|
+
|
|
31
|
+
- **The look:** natural, warm, cool, desaturated, high contrast, a named LUT; or a reference.
|
|
32
|
+
- **Which shot is the reference** for matching (the best-exposed, or the one the user likes).
|
|
33
|
+
- **Where the LUT file is** (a `.cube` path or URL the host can load).
|
|
34
|
+
|
|
35
|
+
## Working method
|
|
36
|
+
|
|
37
|
+
1. **Read.** `get_video_project` for clips and their `grade`; where the host has a preview,
|
|
38
|
+
`get_video_scopes` at a frame of each shot reads levels and colour in numbers.
|
|
39
|
+
**Check:** you know which shots are off and by how much.
|
|
40
|
+
2. **Correct the reference shot** (`update_video_clip` `set.grade`): exposure first, then
|
|
41
|
+
contrast, then white balance (`temperature`, `tint`). The grade object replaces the clip's
|
|
42
|
+
whole grade, so pass every setting you want to keep.
|
|
43
|
+
3. **Match the others to it.** A shot one stop under gets `exposure: 1`; a cooler shot more
|
|
44
|
+
`temperature`. `copy_video_grade` when shots from one camera setup need the same correction;
|
|
45
|
+
`apply_video_grade_preset` for a common starting point.
|
|
46
|
+
**Check:** the clip `grade` values; scopes or `get_video_frame` at both sides of each cut.
|
|
47
|
+
4. **The look over everything.** `add_video_track` at the top (`at: 0`) and
|
|
48
|
+
`add_video_adjustment_clip` on it from 0 for the whole duration with the look's `grade`
|
|
49
|
+
(`scope: "underneath"`).
|
|
50
|
+
**Check:** one adjustment clip covering the timeline; media clips keep their corrections.
|
|
51
|
+
5. **LUTs.** `set_video_lut` with `strength` 0.3-0.7 for a look (1 only for a technical
|
|
52
|
+
conversion LUT) and skin protection (`preserve_skin_tones: 0.7`, 0.5-0.8) when people are on screen;
|
|
53
|
+
`set_video_skin_protection` to change the protection later.
|
|
54
|
+
**Check:** each clip's `grade.lut` has the strength and protection you meant.
|
|
55
|
+
6. **Spot changes.** `set_video_clip_mask` on a duplicate clip or an adjustment clip for a sky,
|
|
56
|
+
a face or a vignette.
|
|
57
|
+
7. **Review.** `render_video_frame` with `times` across the piece for a contact sheet; fix any
|
|
58
|
+
shot that stands out.
|
|
59
|
+
|
|
60
|
+
## Decision table
|
|
61
|
+
|
|
62
|
+
| Problem | Fix |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Shot too dark or bright | `grade.exposure` in stops (+1 doubles the light) |
|
|
65
|
+
| Flat, low contrast | `grade.contrast` +0.1-0.3 |
|
|
66
|
+
| Blue or orange cast | `grade.temperature` against it |
|
|
67
|
+
| Green or magenta cast | `grade.tint` against it |
|
|
68
|
+
| Same correction on many shots from one setup | `copy_video_grade` |
|
|
69
|
+
| One look for the whole piece | Adjustment clip on a top track |
|
|
70
|
+
| A LUT look | `set_video_lut`, strength 0.3-0.7 |
|
|
71
|
+
| Faces off under the look | Skin protection 0.5-0.8 (`set_video_lut` or `set_video_skin_protection`), or lower the strength |
|
|
72
|
+
| Change only part of the frame | `set_video_clip_mask` |
|
|
73
|
+
|
|
74
|
+
## Recipes
|
|
75
|
+
|
|
76
|
+
**Match an underexposed shot.** `update_video_clip` `{ clip, set: { grade: { exposure: 1 } } }`
|
|
77
|
+
for one stop under; check with scopes or frames on both sides of the cut.
|
|
78
|
+
|
|
79
|
+
**Warm, slightly desaturated look.** `add_video_track` `{ kind: "video", at: 0 }` →
|
|
80
|
+
`add_video_adjustment_clip` `{ track, start: 0, duration: <project duration>, grade:
|
|
81
|
+
{ temperature: 0.2, saturation: -0.15 } }`.
|
|
82
|
+
|
|
83
|
+
**Tame a LUT.** `set_video_lut` `{ clips, src, strength: 0.6, preserve_skin_tones: 0.7 }`.
|
|
84
|
+
|
|
85
|
+
## Pitfalls
|
|
86
|
+
|
|
87
|
+
- **Grading before correcting.** A look over unmatched shots makes the mismatch stronger.
|
|
88
|
+
- **Partial grade objects.** `set.grade` replaces the whole grade; read it first and include
|
|
89
|
+
the settings you keep.
|
|
90
|
+
- **The look on every clip.** Pasting it per clip means every change is many edits; use an
|
|
91
|
+
adjustment clip.
|
|
92
|
+
- **Full-strength creative LUTs** turn faces orange or green and crush detail.
|
|
93
|
+
- **ffmpeg and skin protection.** The export's `lut3d` cannot protect skin; the export notes
|
|
94
|
+
it, and the preview differs from the file. Lower the strength when that matters.
|
|
95
|
+
- **Judging on one frame.** Check several frames per shot; light changes within shots.
|
|
96
|
+
|
|
97
|
+
## Tools used
|
|
98
|
+
|
|
99
|
+
- `get_video_project`, `get_video_scopes`, `get_video_frame`, `render_video_frame`: read grades, measure and look
|
|
100
|
+
- `update_video_clip`: each shot's correction (`set.grade`)
|
|
101
|
+
- `copy_video_grade`, `apply_video_grade_preset`: match and start from a preset
|
|
102
|
+
- `add_video_track`, `add_video_adjustment_clip`: one look over the timeline
|
|
103
|
+
- `set_video_lut`, `remove_video_lut`, `set_video_skin_protection`: LUT looks and skin
|
|
104
|
+
- `set_video_clip_mask`, `remove_video_clip_mask`: spot changes
|
|
105
|
+
- `undo_video`: take back a step
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Give the whole piece a warm, slightly desaturated look that I can tweak in one place later, on top of the shot corrections.",
|
|
3
|
+
"fixture": { "editor": "video", "setup": [{ "tool": "add_video_asset", "args": { "id": "a", "kind": "video", "src": "shot-a.mp4", "duration": 8000 } },
|
|
4
|
+
{ "tool": "add_video_asset", "args": { "id": "b", "kind": "video", "src": "shot-b.mp4", "duration": 8000 } },
|
|
5
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "a", "id": "c1", "start": 0, "duration": 6000 } } },
|
|
6
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "b", "id": "c2", "start": 6000, "duration": 6000 } } }] },
|
|
7
|
+
"checks": [
|
|
8
|
+
{ "why": "one adjustment clip over the whole timeline", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "type": "adjustment", "start": 0, "duration": { "$min": 12000 } } } } } } },
|
|
9
|
+
{ "why": "warm and a little desaturated", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "type": "adjustment", "grade": { "temperature": { "$min": 0.05, "$max": 0.5 }, "saturation": { "$min": -0.4, "$max": -0.02 } } } } } } } },
|
|
10
|
+
{ "why": "the shots' own grades are left alone", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$expr": "v.filter((c) => c.type === 'media').every((c) => !c.grade)" } } } } }
|
|
11
|
+
],
|
|
12
|
+
"solution": [
|
|
13
|
+
{ "tool": "add_video_track", "args": { "kind": "video", "id": "v0", "at": 0 } },
|
|
14
|
+
{ "tool": "add_video_adjustment_clip", "args": { "track": "v0", "start": 0, "duration": 12000, "grade": { "temperature": 0.2, "saturation": -0.15 } } }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "The teal-and-orange LUT on both shots is too strong and turns the faces orange. Tone the look down to a moderate strength and keep the skin tones natural.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "video",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "add_video_asset",
|
|
8
|
+
"args": {
|
|
9
|
+
"id": "a",
|
|
10
|
+
"kind": "video",
|
|
11
|
+
"src": "shot-a.mp4",
|
|
12
|
+
"duration": 8000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"tool": "add_video_asset",
|
|
17
|
+
"args": {
|
|
18
|
+
"id": "b",
|
|
19
|
+
"kind": "video",
|
|
20
|
+
"src": "shot-b.mp4",
|
|
21
|
+
"duration": 8000
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"tool": "add_video_clip",
|
|
26
|
+
"args": {
|
|
27
|
+
"track": "v1",
|
|
28
|
+
"clip": {
|
|
29
|
+
"type": "media",
|
|
30
|
+
"asset": "a",
|
|
31
|
+
"id": "c1",
|
|
32
|
+
"start": 0,
|
|
33
|
+
"duration": 6000
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"tool": "add_video_clip",
|
|
39
|
+
"args": {
|
|
40
|
+
"track": "v1",
|
|
41
|
+
"clip": {
|
|
42
|
+
"type": "media",
|
|
43
|
+
"asset": "b",
|
|
44
|
+
"id": "c2",
|
|
45
|
+
"start": 6000,
|
|
46
|
+
"duration": 6000
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"tool": "set_video_lut",
|
|
52
|
+
"args": {
|
|
53
|
+
"clips": [
|
|
54
|
+
"c1",
|
|
55
|
+
"c2"
|
|
56
|
+
],
|
|
57
|
+
"src": "data:text/plain,TITLE%20%22Teal%20orange%22%0ALUT_3D_SIZE%202%0A0%200.1%200.1%0A1%200.5%200.2%0A0%201%200.9%0A1%201%200.6%0A0%200.2%201%0A1%200.4%200.9%0A0%201%201%0A1%201%201%0A",
|
|
58
|
+
"strength": 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"checks": [
|
|
64
|
+
{
|
|
65
|
+
"why": "the LUT is still on both shots, at a moderate strength",
|
|
66
|
+
"tool": "get_video_project",
|
|
67
|
+
"expect": {
|
|
68
|
+
"tracks": {
|
|
69
|
+
"$some": {
|
|
70
|
+
"clips": {
|
|
71
|
+
"$length": 2,
|
|
72
|
+
"$every": {
|
|
73
|
+
"grade": {
|
|
74
|
+
"lut": {
|
|
75
|
+
"src": {
|
|
76
|
+
"$match": "^data:"
|
|
77
|
+
},
|
|
78
|
+
"strength": {
|
|
79
|
+
"$min": 0.3,
|
|
80
|
+
"$max": 0.85
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"why": "skin tones protected",
|
|
92
|
+
"tool": "get_video_project",
|
|
93
|
+
"expect": {
|
|
94
|
+
"tracks": {
|
|
95
|
+
"$some": {
|
|
96
|
+
"clips": {
|
|
97
|
+
"$every": {
|
|
98
|
+
"grade": {
|
|
99
|
+
"lut": {
|
|
100
|
+
"preserveSkinTones": {
|
|
101
|
+
"$min": 0.4
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"solution": [
|
|
113
|
+
{
|
|
114
|
+
"tool": "get_video_project"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"tool": "set_video_lut",
|
|
118
|
+
"args": {
|
|
119
|
+
"clips": [
|
|
120
|
+
"c1",
|
|
121
|
+
"c2"
|
|
122
|
+
],
|
|
123
|
+
"src": {
|
|
124
|
+
"$read": "get_video_project",
|
|
125
|
+
"path": "tracks.0.clips.0.grade.lut.src"
|
|
126
|
+
},
|
|
127
|
+
"strength": 0.6,
|
|
128
|
+
"preserve_skin_tones": 0.7
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Shot two (c2) was filmed one stop underexposed compared with shot one. Match it to shot one without touching shot one.",
|
|
3
|
+
"fixture": { "editor": "video", "setup": [{ "tool": "add_video_asset", "args": { "id": "a", "kind": "video", "src": "shot-a.mp4", "duration": 8000 } },
|
|
4
|
+
{ "tool": "add_video_asset", "args": { "id": "b", "kind": "video", "src": "shot-b.mp4", "duration": 8000 } },
|
|
5
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "a", "id": "c1", "start": 0, "duration": 6000 } } },
|
|
6
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "b", "id": "c2", "start": 6000, "duration": 6000 } } }] },
|
|
7
|
+
"checks": [
|
|
8
|
+
{ "why": "c2 brought up by about one stop", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "id": "c2", "grade": { "exposure": { "$min": 0.8, "$max": 1.2 } } } } } } } },
|
|
9
|
+
{ "why": "shot one is untouched", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "id": "c1", "$expr": "!v.grade || Object.values(v.grade).every((x) => x === 0)" } } } } } }
|
|
10
|
+
],
|
|
11
|
+
"solution": [
|
|
12
|
+
{ "tool": "update_video_clip", "args": { "clip": "c2", "set": { "grade": { "exposure": 1 } } } }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -33,6 +33,9 @@ the pitfalls, and uses the tools below.
|
|
|
33
33
|
|
|
34
34
|
- `pixerate-talking-head-edit`: dialogue cleanup by transcript: fillers, pauses, stutters.
|
|
35
35
|
- `pixerate-bgm-alignment`: cutting to the beat, ducking music under speech, loudness.
|
|
36
|
+
- `pixerate-video-social-cut`: vertical reframes, the hook first, platform lengths, titles in the safe area.
|
|
37
|
+
- `pixerate-video-colour-grade`: correct and match shots, one look on an adjustment clip, LUTs and skin.
|
|
38
|
+
- `pixerate-video-montage`: selects on the beat, pacing, transitions, music and the ending.
|
|
36
39
|
|
|
37
40
|
## Working method
|
|
38
41
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-video-montage
|
|
3
|
+
description: The montage workflow in the Pixerate video editor - choosing selects and their best moments, assembling them on beats or a rhythm, varying shot length for pacing, cutting on action, short transitions where they help, music under the whole sequence, and a clean start and end. Use when asked to make a montage, a highlight reel, a sizzle reel, a recap, a music video edit, a slideshow of clips or photos, or to cut a set of clips to music or a rhythm, or when edit_video, add_video_marker, set_video_transition or find_video_gaps are available. Load with pixerate-video-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-video-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Cutting a montage
|
|
10
|
+
|
|
11
|
+
A montage tells its story through rhythm: the best moment of each shot, in an order that
|
|
12
|
+
builds, cut to a beat. The work is choosing (selects and their best seconds), then placing
|
|
13
|
+
(on the beat, with shot lengths that breathe and quicken), then finishing (transitions only
|
|
14
|
+
where they help, music under everything, a clear ending).
|
|
15
|
+
|
|
16
|
+
This skill is the method. `pixerate-video-editor` has the full tool reference; for beats in a
|
|
17
|
+
music track and cutting to them, see `pixerate-bgm-alignment`.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- Each shot is its best moment: taken from the middle of the action, not its first frames
|
|
22
|
+
(`in` past the camera's settle).
|
|
23
|
+
- Cuts land on beats or on action, on frame boundaries, with no gaps (`find_video_gaps` empty).
|
|
24
|
+
- Pacing varies: longer shots to establish, shorter to build, a held shot to land.
|
|
25
|
+
- Straight cuts by default; short crossfades (300-600 ms) or dips only for a reason (time
|
|
26
|
+
passing, a change of section), each with enough media on both sides (no handle errors).
|
|
27
|
+
- Music runs under the whole sequence and ends with it (a fade out or a musical ending), and
|
|
28
|
+
the picture ends cleanly (a fade to black or a held final frame).
|
|
29
|
+
|
|
30
|
+
## Inputs to confirm
|
|
31
|
+
|
|
32
|
+
- **Selects and order:** which clips, in what order (story, chronology, energy).
|
|
33
|
+
- **Rhythm:** music with beats, markers, or a shot length (e.g. 1 s per shot).
|
|
34
|
+
- **Length** of the finished piece.
|
|
35
|
+
|
|
36
|
+
## Working method
|
|
37
|
+
|
|
38
|
+
1. **Read.** `get_video_project`: assets (with durations), markers (beats), music.
|
|
39
|
+
With music, `find_video_beats` (`pixerate-bgm-alignment`) marks the beats.
|
|
40
|
+
**Check:** you have a beat grid or a shot length, and each select's usable range.
|
|
41
|
+
2. **Pick the moments.** For each select, the `in` point of its best stretch (skip the first
|
|
42
|
+
second or two, where the camera settles).
|
|
43
|
+
3. **Assemble in one batch.** `edit_video` with one add-clip op per shot (`asset`, `id`, `in`,
|
|
44
|
+
`start` on a beat, `duration` to the next beat), `dry_run: true` first.
|
|
45
|
+
**Check:** the clips are in order, each `start` on a beat; `find_video_gaps` is empty.
|
|
46
|
+
4. **Refine the rhythm.** `roll_video_edit` to move a cut to a beat or onto action without
|
|
47
|
+
shifting anything else; `slip_video_clip` to change what a shot shows without moving it.
|
|
48
|
+
Vary lengths: build up with shorter shots before the peak.
|
|
49
|
+
5. **Transitions, sparingly.** `set_video_transition` `crossfade` 300-600 ms (or `dip` for a
|
|
50
|
+
section change); every type but dip needs half its length in media past the cut on both sides.
|
|
51
|
+
**Check:** `check_video_project` has no errors.
|
|
52
|
+
6. **Music and ending.** The music clip on an audio track from 0 for the whole duration, with a
|
|
53
|
+
`fadeOut` of 1-3 s (or cut on its last beat); the last picture clip `fadeOut` 300-1000 ms to
|
|
54
|
+
black.
|
|
55
|
+
**Check:** `get_video_project` shows both, ending together.
|
|
56
|
+
7. **Review.** `render_video_frame` with `times` at each cut for a contact sheet; watch the
|
|
57
|
+
rhythm in the preview where available.
|
|
58
|
+
|
|
59
|
+
## Decision table
|
|
60
|
+
|
|
61
|
+
| Want | Approach |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| Energy, a build | Shorter shots toward the peak, cuts on every beat |
|
|
64
|
+
| Calm, reflective | Longer shots, cuts every 2-4 beats, crossfades |
|
|
65
|
+
| Time passing | Crossfade or dip between shots |
|
|
66
|
+
| A section change | Dip to black or a held shot, then a new rhythm |
|
|
67
|
+
| Continuity of motion | Cut on action (`roll_video_edit` onto the movement) |
|
|
68
|
+
| Photos in a slideshow | Image assets, a slow push-in (`set_video_keyframes` scale 1 to 1.08) |
|
|
69
|
+
|
|
70
|
+
## Recipes
|
|
71
|
+
|
|
72
|
+
**On the beat.** Beats every `b` ms from 0: `edit_video` with `{ op: "add_clip", track: "v1", clip:
|
|
73
|
+
{ type: "media", asset, id, in, start: k * b, duration: b } }` for each shot `k`; then `find_video_gaps`.
|
|
74
|
+
|
|
75
|
+
**Crossfades.** `edit_video` with `{ op: "set_transition", clip, transition: { type: "crossfade",
|
|
76
|
+
duration: 400 } }` for every clip after the first; then `check_video_project`.
|
|
77
|
+
|
|
78
|
+
**Music and fade-out.** `add_video_clip` on the audio track `{ type: "media", asset: music,
|
|
79
|
+
start: 0, duration: <sequence>, fadeOut: 2000 }`; `update_video_clip` on the last shot
|
|
80
|
+
`{ set: { fadeOut: 500 } }`.
|
|
81
|
+
|
|
82
|
+
## Pitfalls
|
|
83
|
+
|
|
84
|
+
- **First frames of a clip** are often the camera settling; take `in` later.
|
|
85
|
+
- **Transitions everywhere** soften the rhythm and look dated; cut straight by default.
|
|
86
|
+
- **Missing handles.** A transition needs media past the cut on both sides; trimmed-to-the-edge
|
|
87
|
+
clips fail `check_video_project`.
|
|
88
|
+
- **Music that stops dead** at the end of the picture; fade it or end on a musical phrase.
|
|
89
|
+
- **Uniform shot lengths** for a long montage feel mechanical; vary them.
|
|
90
|
+
|
|
91
|
+
## Tools used
|
|
92
|
+
|
|
93
|
+
- `get_video_project`, `find_video_gaps`, `check_video_project`, `render_video_frame`: read and check
|
|
94
|
+
- `add_video_marker`: beats and sections
|
|
95
|
+
- `edit_video`, `add_video_clip`: assemble in one batch
|
|
96
|
+
- `roll_video_edit`, `slip_video_clip`, `trim_video_clip`, `close_video_gaps`: refine the rhythm
|
|
97
|
+
- `set_video_transition`: crossfades and dips
|
|
98
|
+
- `update_video_clip`, `set_video_keyframes`: fades, volume and push-ins
|
|
99
|
+
- `undo_video`: take back a step
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Assemble the five selects s1 to s5 in order on V1, one shot per beat: each cut lands on the next beat marker (they are 1 s apart, from 0). Use the middle of each select (from 2 s in). No gaps.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "video",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "add_video_asset",
|
|
8
|
+
"args": {
|
|
9
|
+
"id": "s1",
|
|
10
|
+
"kind": "video",
|
|
11
|
+
"src": "select-1.mp4",
|
|
12
|
+
"duration": 6000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"tool": "add_video_asset",
|
|
17
|
+
"args": {
|
|
18
|
+
"id": "s2",
|
|
19
|
+
"kind": "video",
|
|
20
|
+
"src": "select-2.mp4",
|
|
21
|
+
"duration": 6000
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"tool": "add_video_asset",
|
|
26
|
+
"args": {
|
|
27
|
+
"id": "s3",
|
|
28
|
+
"kind": "video",
|
|
29
|
+
"src": "select-3.mp4",
|
|
30
|
+
"duration": 6000
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"tool": "add_video_asset",
|
|
35
|
+
"args": {
|
|
36
|
+
"id": "s4",
|
|
37
|
+
"kind": "video",
|
|
38
|
+
"src": "select-4.mp4",
|
|
39
|
+
"duration": 6000
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"tool": "add_video_asset",
|
|
44
|
+
"args": {
|
|
45
|
+
"id": "s5",
|
|
46
|
+
"kind": "video",
|
|
47
|
+
"src": "select-5.mp4",
|
|
48
|
+
"duration": 6000
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"tool": "add_video_marker",
|
|
53
|
+
"args": {
|
|
54
|
+
"time": 0,
|
|
55
|
+
"label": "beat"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"tool": "add_video_marker",
|
|
60
|
+
"args": {
|
|
61
|
+
"time": 1000,
|
|
62
|
+
"label": "beat"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"tool": "add_video_marker",
|
|
67
|
+
"args": {
|
|
68
|
+
"time": 2000,
|
|
69
|
+
"label": "beat"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"tool": "add_video_marker",
|
|
74
|
+
"args": {
|
|
75
|
+
"time": 3000,
|
|
76
|
+
"label": "beat"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"tool": "add_video_marker",
|
|
81
|
+
"args": {
|
|
82
|
+
"time": 4000,
|
|
83
|
+
"label": "beat"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"tool": "add_video_marker",
|
|
88
|
+
"args": {
|
|
89
|
+
"time": 5000,
|
|
90
|
+
"label": "beat"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"checks": [
|
|
96
|
+
{
|
|
97
|
+
"why": "five shots in order on V1",
|
|
98
|
+
"tool": "get_video_project",
|
|
99
|
+
"expect": {
|
|
100
|
+
"tracks": {
|
|
101
|
+
"$some": {
|
|
102
|
+
"id": "v1",
|
|
103
|
+
"clips": {
|
|
104
|
+
"$length": 5,
|
|
105
|
+
"$expr": "v.every((c, i) => c.asset === 's' + (i + 1))"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"why": "every cut on a beat",
|
|
113
|
+
"tool": "get_video_project",
|
|
114
|
+
"expect": {
|
|
115
|
+
"tracks": {
|
|
116
|
+
"$some": {
|
|
117
|
+
"id": "v1",
|
|
118
|
+
"clips": {
|
|
119
|
+
"$every": {
|
|
120
|
+
"$expr": "v.start % 1000 === 0 && v.duration === 1000"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"why": "from the middle of each select",
|
|
129
|
+
"tool": "get_video_project",
|
|
130
|
+
"expect": {
|
|
131
|
+
"tracks": {
|
|
132
|
+
"$some": {
|
|
133
|
+
"id": "v1",
|
|
134
|
+
"clips": {
|
|
135
|
+
"$every": {
|
|
136
|
+
"in": {
|
|
137
|
+
"$min": 1500,
|
|
138
|
+
"$max": 3000
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"why": "no gaps",
|
|
148
|
+
"tool": "find_video_gaps",
|
|
149
|
+
"expect": {
|
|
150
|
+
"gaps": {
|
|
151
|
+
"$length": 0
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"solution": [
|
|
157
|
+
{
|
|
158
|
+
"tool": "get_video_project"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"tool": "edit_video",
|
|
162
|
+
"args": {
|
|
163
|
+
"ops": [
|
|
164
|
+
{
|
|
165
|
+
"op": "add_clip",
|
|
166
|
+
"track": "v1",
|
|
167
|
+
"clip": {
|
|
168
|
+
"type": "media",
|
|
169
|
+
"asset": "s1",
|
|
170
|
+
"id": "c1",
|
|
171
|
+
"in": 2000,
|
|
172
|
+
"start": 0,
|
|
173
|
+
"duration": 1000
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"op": "add_clip",
|
|
178
|
+
"track": "v1",
|
|
179
|
+
"clip": {
|
|
180
|
+
"type": "media",
|
|
181
|
+
"asset": "s2",
|
|
182
|
+
"id": "c2",
|
|
183
|
+
"in": 2000,
|
|
184
|
+
"start": 1000,
|
|
185
|
+
"duration": 1000
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"op": "add_clip",
|
|
190
|
+
"track": "v1",
|
|
191
|
+
"clip": {
|
|
192
|
+
"type": "media",
|
|
193
|
+
"asset": "s3",
|
|
194
|
+
"id": "c3",
|
|
195
|
+
"in": 2000,
|
|
196
|
+
"start": 2000,
|
|
197
|
+
"duration": 1000
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"op": "add_clip",
|
|
202
|
+
"track": "v1",
|
|
203
|
+
"clip": {
|
|
204
|
+
"type": "media",
|
|
205
|
+
"asset": "s4",
|
|
206
|
+
"id": "c4",
|
|
207
|
+
"in": 2000,
|
|
208
|
+
"start": 3000,
|
|
209
|
+
"duration": 1000
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"op": "add_clip",
|
|
214
|
+
"track": "v1",
|
|
215
|
+
"clip": {
|
|
216
|
+
"type": "media",
|
|
217
|
+
"asset": "s5",
|
|
218
|
+
"id": "c5",
|
|
219
|
+
"in": 2000,
|
|
220
|
+
"start": 4000,
|
|
221
|
+
"duration": 1000
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"tool": "find_video_gaps"
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Lay the music under the whole montage, fade the music out over the last two seconds, and end the picture with a fade to black.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "video",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "add_video_asset",
|
|
8
|
+
"args": {
|
|
9
|
+
"id": "s1",
|
|
10
|
+
"kind": "video",
|
|
11
|
+
"src": "select-1.mp4",
|
|
12
|
+
"duration": 6000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"tool": "add_video_asset",
|
|
17
|
+
"args": {
|
|
18
|
+
"id": "s2",
|
|
19
|
+
"kind": "video",
|
|
20
|
+
"src": "select-2.mp4",
|
|
21
|
+
"duration": 6000
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"tool": "add_video_asset",
|
|
26
|
+
"args": {
|
|
27
|
+
"id": "s3",
|
|
28
|
+
"kind": "video",
|
|
29
|
+
"src": "select-3.mp4",
|
|
30
|
+
"duration": 6000
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"tool": "add_video_asset",
|
|
35
|
+
"args": {
|
|
36
|
+
"id": "s4",
|
|
37
|
+
"kind": "video",
|
|
38
|
+
"src": "select-4.mp4",
|
|
39
|
+
"duration": 6000
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"tool": "add_video_asset",
|
|
44
|
+
"args": {
|
|
45
|
+
"id": "s5",
|
|
46
|
+
"kind": "video",
|
|
47
|
+
"src": "select-5.mp4",
|
|
48
|
+
"duration": 6000
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"tool": "add_video_clip",
|
|
53
|
+
"args": {
|
|
54
|
+
"track": "v1",
|
|
55
|
+
"clip": {
|
|
56
|
+
"type": "media",
|
|
57
|
+
"asset": "s1",
|
|
58
|
+
"id": "c1",
|
|
59
|
+
"in": 2000,
|
|
60
|
+
"start": 0,
|
|
61
|
+
"duration": 1000
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"tool": "add_video_clip",
|
|
67
|
+
"args": {
|
|
68
|
+
"track": "v1",
|
|
69
|
+
"clip": {
|
|
70
|
+
"type": "media",
|
|
71
|
+
"asset": "s2",
|
|
72
|
+
"id": "c2",
|
|
73
|
+
"in": 2000,
|
|
74
|
+
"start": 1000,
|
|
75
|
+
"duration": 1000
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"tool": "add_video_clip",
|
|
81
|
+
"args": {
|
|
82
|
+
"track": "v1",
|
|
83
|
+
"clip": {
|
|
84
|
+
"type": "media",
|
|
85
|
+
"asset": "s3",
|
|
86
|
+
"id": "c3",
|
|
87
|
+
"in": 2000,
|
|
88
|
+
"start": 2000,
|
|
89
|
+
"duration": 1000
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"tool": "add_video_clip",
|
|
95
|
+
"args": {
|
|
96
|
+
"track": "v1",
|
|
97
|
+
"clip": {
|
|
98
|
+
"type": "media",
|
|
99
|
+
"asset": "s4",
|
|
100
|
+
"id": "c4",
|
|
101
|
+
"in": 2000,
|
|
102
|
+
"start": 3000,
|
|
103
|
+
"duration": 1000
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"tool": "add_video_clip",
|
|
109
|
+
"args": {
|
|
110
|
+
"track": "v1",
|
|
111
|
+
"clip": {
|
|
112
|
+
"type": "media",
|
|
113
|
+
"asset": "s5",
|
|
114
|
+
"id": "c5",
|
|
115
|
+
"in": 2000,
|
|
116
|
+
"start": 4000,
|
|
117
|
+
"duration": 1000
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"tool": "add_video_asset",
|
|
123
|
+
"args": {
|
|
124
|
+
"id": "music",
|
|
125
|
+
"kind": "audio",
|
|
126
|
+
"src": "music.mp3",
|
|
127
|
+
"duration": 60000
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"checks": [
|
|
133
|
+
{
|
|
134
|
+
"why": "music under the whole montage, ending with it",
|
|
135
|
+
"tool": "get_video_project",
|
|
136
|
+
"expect": {
|
|
137
|
+
"tracks": {
|
|
138
|
+
"$some": {
|
|
139
|
+
"kind": "audio",
|
|
140
|
+
"clips": {
|
|
141
|
+
"$some": {
|
|
142
|
+
"asset": "music",
|
|
143
|
+
"start": 0,
|
|
144
|
+
"duration": 5000
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"why": "the music fades out over about two seconds",
|
|
153
|
+
"tool": "get_video_project",
|
|
154
|
+
"expect": {
|
|
155
|
+
"tracks": {
|
|
156
|
+
"$some": {
|
|
157
|
+
"kind": "audio",
|
|
158
|
+
"clips": {
|
|
159
|
+
"$some": {
|
|
160
|
+
"asset": "music",
|
|
161
|
+
"fadeOut": {
|
|
162
|
+
"$min": 1500,
|
|
163
|
+
"$max": 2500
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"why": "the last shot fades to black",
|
|
173
|
+
"tool": "get_video_project",
|
|
174
|
+
"expect": {
|
|
175
|
+
"tracks": {
|
|
176
|
+
"$some": {
|
|
177
|
+
"id": "v1",
|
|
178
|
+
"clips": {
|
|
179
|
+
"$expr": "v[v.length - 1].fadeOut >= 300"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"solution": [
|
|
187
|
+
{
|
|
188
|
+
"tool": "add_video_clip",
|
|
189
|
+
"args": {
|
|
190
|
+
"track": "a1",
|
|
191
|
+
"clip": {
|
|
192
|
+
"type": "media",
|
|
193
|
+
"asset": "music",
|
|
194
|
+
"start": 0,
|
|
195
|
+
"duration": 5000,
|
|
196
|
+
"fadeOut": 2000
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"tool": "update_video_clip",
|
|
202
|
+
"args": {
|
|
203
|
+
"clip": "c5",
|
|
204
|
+
"set": {
|
|
205
|
+
"fadeOut": 500
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Join the shots of this montage with short crossfades of about 400 ms, and make sure every transition has enough media for it.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "video",
|
|
5
|
+
"setup": [
|
|
6
|
+
{
|
|
7
|
+
"tool": "add_video_asset",
|
|
8
|
+
"args": {
|
|
9
|
+
"id": "s1",
|
|
10
|
+
"kind": "video",
|
|
11
|
+
"src": "select-1.mp4",
|
|
12
|
+
"duration": 6000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"tool": "add_video_asset",
|
|
17
|
+
"args": {
|
|
18
|
+
"id": "s2",
|
|
19
|
+
"kind": "video",
|
|
20
|
+
"src": "select-2.mp4",
|
|
21
|
+
"duration": 6000
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"tool": "add_video_asset",
|
|
26
|
+
"args": {
|
|
27
|
+
"id": "s3",
|
|
28
|
+
"kind": "video",
|
|
29
|
+
"src": "select-3.mp4",
|
|
30
|
+
"duration": 6000
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"tool": "add_video_asset",
|
|
35
|
+
"args": {
|
|
36
|
+
"id": "s4",
|
|
37
|
+
"kind": "video",
|
|
38
|
+
"src": "select-4.mp4",
|
|
39
|
+
"duration": 6000
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"tool": "add_video_asset",
|
|
44
|
+
"args": {
|
|
45
|
+
"id": "s5",
|
|
46
|
+
"kind": "video",
|
|
47
|
+
"src": "select-5.mp4",
|
|
48
|
+
"duration": 6000
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"tool": "add_video_clip",
|
|
53
|
+
"args": {
|
|
54
|
+
"track": "v1",
|
|
55
|
+
"clip": {
|
|
56
|
+
"type": "media",
|
|
57
|
+
"asset": "s1",
|
|
58
|
+
"id": "c1",
|
|
59
|
+
"in": 2000,
|
|
60
|
+
"start": 0,
|
|
61
|
+
"duration": 1000
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"tool": "add_video_clip",
|
|
67
|
+
"args": {
|
|
68
|
+
"track": "v1",
|
|
69
|
+
"clip": {
|
|
70
|
+
"type": "media",
|
|
71
|
+
"asset": "s2",
|
|
72
|
+
"id": "c2",
|
|
73
|
+
"in": 2000,
|
|
74
|
+
"start": 1000,
|
|
75
|
+
"duration": 1000
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"tool": "add_video_clip",
|
|
81
|
+
"args": {
|
|
82
|
+
"track": "v1",
|
|
83
|
+
"clip": {
|
|
84
|
+
"type": "media",
|
|
85
|
+
"asset": "s3",
|
|
86
|
+
"id": "c3",
|
|
87
|
+
"in": 2000,
|
|
88
|
+
"start": 2000,
|
|
89
|
+
"duration": 1000
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"tool": "add_video_clip",
|
|
95
|
+
"args": {
|
|
96
|
+
"track": "v1",
|
|
97
|
+
"clip": {
|
|
98
|
+
"type": "media",
|
|
99
|
+
"asset": "s4",
|
|
100
|
+
"id": "c4",
|
|
101
|
+
"in": 2000,
|
|
102
|
+
"start": 3000,
|
|
103
|
+
"duration": 1000
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"tool": "add_video_clip",
|
|
109
|
+
"args": {
|
|
110
|
+
"track": "v1",
|
|
111
|
+
"clip": {
|
|
112
|
+
"type": "media",
|
|
113
|
+
"asset": "s5",
|
|
114
|
+
"id": "c5",
|
|
115
|
+
"in": 2000,
|
|
116
|
+
"start": 4000,
|
|
117
|
+
"duration": 1000
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"checks": [
|
|
124
|
+
{
|
|
125
|
+
"why": "a short crossfade into every shot after the first",
|
|
126
|
+
"tool": "get_video_project",
|
|
127
|
+
"expect": {
|
|
128
|
+
"tracks": {
|
|
129
|
+
"$some": {
|
|
130
|
+
"id": "v1",
|
|
131
|
+
"clips": {
|
|
132
|
+
"$expr": "v.slice(1).every((c) => c.transition && c.transition.type === 'crossfade' && c.transition.duration >= 300 && c.transition.duration <= 600)"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"why": "no errors (every transition has handles)",
|
|
140
|
+
"tool": "check_video_project",
|
|
141
|
+
"expect": {
|
|
142
|
+
"errors": 0
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"solution": [
|
|
147
|
+
{
|
|
148
|
+
"tool": "edit_video",
|
|
149
|
+
"args": {
|
|
150
|
+
"ops": [
|
|
151
|
+
{
|
|
152
|
+
"op": "set_transition",
|
|
153
|
+
"clip": "c2",
|
|
154
|
+
"transition": {
|
|
155
|
+
"type": "crossfade",
|
|
156
|
+
"duration": 400
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"op": "set_transition",
|
|
161
|
+
"clip": "c3",
|
|
162
|
+
"transition": {
|
|
163
|
+
"type": "crossfade",
|
|
164
|
+
"duration": 400
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"op": "set_transition",
|
|
169
|
+
"clip": "c4",
|
|
170
|
+
"transition": {
|
|
171
|
+
"type": "crossfade",
|
|
172
|
+
"duration": 400
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"op": "set_transition",
|
|
177
|
+
"clip": "c5",
|
|
178
|
+
"transition": {
|
|
179
|
+
"type": "crossfade",
|
|
180
|
+
"duration": 400
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"tool": "check_video_project"
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-video-social-cut
|
|
3
|
+
description: The social video workflow in the Pixerate video editor - reframing for vertical (9:16) or square, opening on the hook in the first seconds, cutting to platform length, titles and captions inside the safe area, pacing for the feed, loudness for mobile and an export for Reels, TikTok, Shorts or a feed post. Use when asked to make a social cut, a vertical, Reels, TikTok, Shorts or story version, a teaser or trailer for social, a short clip from a longer video, or to reframe, caption or title a video for social, or when set_video_settings, update_video_clip and add_video_marker are available. Load with pixerate-video-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-video-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Cutting for social
|
|
10
|
+
|
|
11
|
+
A social cut is watched on a phone, often without sound, by someone who decides in the first
|
|
12
|
+
second or two whether to stay. So the frame is vertical or square and filled, the strongest
|
|
13
|
+
moment comes first, every shot earns its place, and words are on screen. Build it as its own
|
|
14
|
+
project or sequence from the long edit, never by damaging the original.
|
|
15
|
+
|
|
16
|
+
This skill is the method. `pixerate-video-editor` has the full tool reference; for music and
|
|
17
|
+
loudness see `pixerate-bgm-alignment`.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- The frame suits the platform: 1080 × 1920 (9:16) for Reels, TikTok, Shorts and stories;
|
|
22
|
+
1080 × 1080 or 1080 × 1350 for a feed post. The picture fills it (no black bars).
|
|
23
|
+
- The hook plays in the first 1-3 seconds: the most striking image or line, not a logo or a
|
|
24
|
+
slow build.
|
|
25
|
+
- The length fits the platform and the content: 7-15 s for a loop or teaser, up to 60 s for
|
|
26
|
+
most short-form, cut tight (no dead air, `find_video_gaps` empty).
|
|
27
|
+
- Text is readable without sound: titles and captions large, high-contrast, inside the safe
|
|
28
|
+
area (away from the top 250 px and bottom 350 px of a 1920-high frame, where the app's
|
|
29
|
+
interface sits).
|
|
30
|
+
- `check_video_project` has no errors; the mix is at the delivery loudness (-14 LUFS).
|
|
31
|
+
|
|
32
|
+
## Inputs to confirm
|
|
33
|
+
|
|
34
|
+
- **Platform(s)** and so the frame and length.
|
|
35
|
+
- **The hook:** a moment, a line or a marker; ask when nothing in the footage stands out.
|
|
36
|
+
- **Words on screen:** captions of the speech, a title, a call to action.
|
|
37
|
+
|
|
38
|
+
## Working method
|
|
39
|
+
|
|
40
|
+
1. **Read.** `get_video_project`: frame size, clips, markers (a "Hook" marker or the user's
|
|
41
|
+
notes), duration. With speech, `get_video_transcript` to find the best lines.
|
|
42
|
+
**Check:** you know where the hook is in the source and how long the cut should be.
|
|
43
|
+
2. **Set the frame.** `set_video_settings` `width` / `height`; then each clip `fit: "cover"`
|
|
44
|
+
(`update_video_clip`), and `transform` `x` to keep the subject in frame when it is off-centre.
|
|
45
|
+
**Check:** `get_video_project` frame size; `get_video_frame` at a few times shows the subject.
|
|
46
|
+
3. **Open on the hook.** Trim and move so the first clip's `in` is at the hook (`edit_video`
|
|
47
|
+
with its trim and move ops, or `extract_video_range` for what goes). Then the context, then
|
|
48
|
+
the payoff.
|
|
49
|
+
**Check:** the clip at 0 starts at the hook's source time.
|
|
50
|
+
4. **Cut to length.** Remove everything that does not move the story: `remove_video_ranges` for
|
|
51
|
+
pauses and fillers (`pixerate-talking-head-edit`), `extract_video_range` for whole passages.
|
|
52
|
+
Keep shots short (1-3 s for montage, as long as the line for speech).
|
|
53
|
+
**Check:** `get_video_project` `duration` is in range; `find_video_gaps` is empty.
|
|
54
|
+
5. **Words.** A video track above the footage (`add_video_track` with `at: 0`), then text clips
|
|
55
|
+
(`add_video_clip` `type: "text"`) with `transform.y` in the upper or lower safe area,
|
|
56
|
+
`animateIn` / `animateOut` for motion. Captions follow the transcript's sentences.
|
|
57
|
+
**Check:** text clips are on the top track and inside the safe area.
|
|
58
|
+
6. **Sound.** Music under, dialogue on top (`pixerate-bgm-alignment`); the first second should
|
|
59
|
+
work without sound and with it.
|
|
60
|
+
7. **Check and export.** `check_video_project`; `export_video_ffmpeg` with the loudness target.
|
|
61
|
+
|
|
62
|
+
## Decision table
|
|
63
|
+
|
|
64
|
+
| Platform | Frame | Length |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Reels, TikTok, Shorts, stories | 1080 × 1920 | 7-60 s; under 15 s loops |
|
|
67
|
+
| Feed post | 1080 × 1350 or 1080 × 1080 | 15-60 s |
|
|
68
|
+
| Landscape (YouTube, web) | 1920 × 1080 | The content's length |
|
|
69
|
+
|
|
70
|
+
| Source | Reframe |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Subject centred | `fit: "cover"` |
|
|
73
|
+
| Subject off-centre | `fit: "cover"` plus `transform.x` toward the subject |
|
|
74
|
+
| Subject moves across the frame | Keyframe `transform.x` (`set_video_keyframes`), or split and reposition per shot |
|
|
75
|
+
| Wide shot that loses everything when cropped | A blurred, scaled copy behind a `contain` copy, or choose another shot |
|
|
76
|
+
|
|
77
|
+
## Recipes
|
|
78
|
+
|
|
79
|
+
**Vertical from landscape.** `set_video_settings` `{ width: 1080, height: 1920 }` → for each
|
|
80
|
+
picture clip `update_video_clip` `{ set: { fit: "cover" } }` → `get_video_frame` to check.
|
|
81
|
+
|
|
82
|
+
**Hook first.** Source hook at `h` ms: `edit_video` `[{ op: "trim_clip", clip, edge: "start",
|
|
83
|
+
to: h }, { op: "trim_clip", clip, edge: "end", to: h + length }, { op: "move_clip", clip,
|
|
84
|
+
start: 0 }]`.
|
|
85
|
+
|
|
86
|
+
**Opening title.** `add_video_track` `{ kind: "video", at: 0 }` → `add_video_clip` on it with
|
|
87
|
+
`{ type: "text", text, start: 0, duration: 3000, transform: { y: -600 } }`.
|
|
88
|
+
|
|
89
|
+
## Pitfalls
|
|
90
|
+
|
|
91
|
+
- **Letterboxing.** A landscape clip in a vertical frame with `contain` shows black bars.
|
|
92
|
+
- **Slow openings.** A logo, a fade-in or a wide establishing shot loses the viewer.
|
|
93
|
+
- **Text in the interface zones** is covered by captions, buttons and the username.
|
|
94
|
+
- **Relying on sound.** Most feeds autoplay muted; put the message on screen too.
|
|
95
|
+
- **Editing the master.** Make the social cut separately so the long version stays intact.
|
|
96
|
+
|
|
97
|
+
## Tools used
|
|
98
|
+
|
|
99
|
+
- `get_video_project`, `get_video_transcript`, `get_video_frame`, `find_video_gaps`, `check_video_project`: read and check
|
|
100
|
+
- `set_video_settings`: the frame size
|
|
101
|
+
- `update_video_clip`, `set_video_keyframes`: fit, reframe and move the picture
|
|
102
|
+
- `edit_video`, `trim_video_clip`, `move_video_clip`, `extract_video_range`, `remove_video_ranges`: cut to the hook and to length
|
|
103
|
+
- `add_video_track`, `add_video_clip`: titles and captions on a track above
|
|
104
|
+
- `add_video_marker`: mark the hook and beats
|
|
105
|
+
- `export_video_ffmpeg`: deliver
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Cut this interview down to a 15-second clip for social that opens on the moment marked 'Hook' and runs on from there. No gaps.",
|
|
3
|
+
"fixture": { "editor": "video", "setup": [{ "tool": "add_video_asset", "args": { "id": "interview", "kind": "video", "src": "interview.mp4", "duration": 40000, "width": 1920, "height": 1080 } },
|
|
4
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "interview", "id": "c1", "start": 0, "duration": 40000 } } }, { "tool": "add_video_marker", "args": { "time": 22000, "label": "Hook" } }] },
|
|
5
|
+
"checks": [
|
|
6
|
+
{ "why": "about 15 seconds long", "tool": "get_video_project", "expect": { "duration": { "$min": 12000, "$max": 15500 } } },
|
|
7
|
+
{ "why": "the first frame is the hook", "tool": "get_video_project", "expect": { "tracks": { "$some": { "id": "v1", "clips": { "$some": { "start": 0, "in": { "$min": 21500, "$max": 22500 } } } } } } },
|
|
8
|
+
{ "why": "no gaps", "tool": "find_video_gaps", "expect": { "gaps": { "$length": 0 } } }
|
|
9
|
+
],
|
|
10
|
+
"solution": [
|
|
11
|
+
{ "tool": "get_video_project" },
|
|
12
|
+
{ "tool": "edit_video", "args": { "ops": [
|
|
13
|
+
{ "op": "trim_clip", "clip": "c1", "edge": "start", "to": 22000 },
|
|
14
|
+
{ "op": "trim_clip", "clip": "c1", "edge": "end", "to": 37000 },
|
|
15
|
+
{ "op": "move_clip", "clip": "c1", "start": 0 }
|
|
16
|
+
] } },
|
|
17
|
+
{ "tool": "find_video_gaps" }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Add a title saying 'New drop Friday' over the first three seconds, in the upper part of the frame, on top of the video.",
|
|
3
|
+
"fixture": { "editor": "video", "setup": [{ "tool": "add_video_asset", "args": { "id": "interview", "kind": "video", "src": "interview.mp4", "duration": 40000, "width": 1920, "height": 1080 } },
|
|
4
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "interview", "id": "c1", "start": 0, "duration": 40000 } } }] },
|
|
5
|
+
"checks": [
|
|
6
|
+
{ "why": "a text clip with the words, from 0 for about 3 s", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "type": "text", "text": { "$match": ["new drop friday", "i"] }, "start": 0, "duration": { "$min": 2500, "$max": 4000 } } } } } } },
|
|
7
|
+
{ "why": "in the upper part of the frame", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "type": "text", "transform": { "y": { "$max": -100 } } } } } } } },
|
|
8
|
+
{ "why": "on a video track above the footage (tracks are listed top first)", "tool": "get_video_project", "expect": { "tracks": { "$expr": "(() => { const t = v.findIndex((tr) => tr.clips.some((c) => c.type === 'text')); const m = v.findIndex((tr) => tr.clips.some((c) => c.id === 'c1')); return t >= 0 && t < m; })()" } } }
|
|
9
|
+
],
|
|
10
|
+
"solution": [
|
|
11
|
+
{ "tool": "add_video_track", "args": { "kind": "video", "id": "v2", "at": 0 } },
|
|
12
|
+
{ "tool": "add_video_clip", "args": { "track": "v2", "clip": { "type": "text", "text": "New drop Friday", "start": 0, "duration": 3000, "transform": { "y": -600 } } } }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Make this a vertical video for Reels and TikTok: 1080 by 1920, with the picture filling the whole frame (no black bars).",
|
|
3
|
+
"fixture": { "editor": "video", "setup": [{ "tool": "add_video_asset", "args": { "id": "interview", "kind": "video", "src": "interview.mp4", "duration": 40000, "width": 1920, "height": 1080 } },
|
|
4
|
+
{ "tool": "add_video_clip", "args": { "track": "v1", "clip": { "type": "media", "asset": "interview", "id": "c1", "start": 0, "duration": 40000 } } }] },
|
|
5
|
+
"checks": [
|
|
6
|
+
{ "why": "a 9:16 frame at 1080 × 1920", "tool": "get_video_project", "expect": { "width": 1080, "height": 1920 } },
|
|
7
|
+
{ "why": "the picture covers the frame", "tool": "get_video_project", "expect": { "tracks": { "$some": { "clips": { "$some": { "id": "c1", "$expr": "v.fit === 'cover' || (v.transform && v.transform.scale >= 3.15)" } } } } } },
|
|
8
|
+
{ "why": "no errors", "tool": "check_video_project", "expect": { "errors": 0 } }
|
|
9
|
+
],
|
|
10
|
+
"solution": [
|
|
11
|
+
{ "tool": "set_video_settings", "args": { "width": 1080, "height": 1920 } },
|
|
12
|
+
{ "tool": "update_video_clip", "args": { "clip": "c1", "set": { "fit": "cover" } } },
|
|
13
|
+
{ "tool": "check_video_project" }
|
|
14
|
+
]
|
|
15
|
+
}
|