@pixerate/editor 0.222.2 → 0.223.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/dist/agent/index.cjs +13 -3
- package/dist/agent/index.js +12 -2
- package/dist/bridge/cli.cjs +3 -3
- package/dist/bridge/cli.js +2 -2
- package/dist/bridge/index.cjs +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/{chunk-I5MRJW7B.js → chunk-67JAEU53.js} +1 -1
- package/dist/{chunk-U4QN36YT.cjs → chunk-BDT2IYXK.cjs} +128 -16
- package/dist/{chunk-S4EIAIEW.cjs → chunk-OJTZKDLJ.cjs} +7 -7
- package/dist/{chunk-XZB3TJ6K.js → chunk-SAN4SBBS.js} +25 -7
- package/dist/{chunk-GI3AVES4.js → chunk-SVHXD2VH.js} +117 -5
- package/dist/{chunk-AMX23CSN.cjs → chunk-Z7BVK32C.cjs} +25 -7
- package/dist/image-editor/index.cjs +12 -11
- package/dist/image-editor/index.js +12 -11
- package/dist/image-editor/layers/panel.d.cts +6 -2
- package/dist/image-editor/layers/panel.d.ts +6 -2
- package/dist/tools/index.cjs +13 -3
- package/dist/tools/index.d.cts +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +12 -2
- package/dist/tools/skill-evals.d.cts +102 -0
- package/dist/tools/skill-evals.d.ts +102 -0
- package/dist/tools/skills.d.cts +12 -1
- package/dist/tools/skills.d.ts +12 -1
- package/package.json +1 -1
- package/skills/README.md +8 -3
- package/skills/pixerate-agent-tools/SKILL.md +5 -3
- 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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The evals that workflow skills ship in `skills/<name>/evals/*.json`, for any harness to run.
|
|
3
|
+
*
|
|
4
|
+
* An eval is a task a model would be given (`prompt`), the document it starts from
|
|
5
|
+
* (`fixture`), the checks that decide whether the job was done (`checks`: a read tool, its
|
|
6
|
+
* arguments and what its result must match) and one known-good answer (`solution`: the tool
|
|
7
|
+
* calls an expert would make). The package's own tests prove each eval with its `solution`; an
|
|
8
|
+
* agent harness builds the fixture, gives its model the `prompt` in place of the solution, then
|
|
9
|
+
* runs `failedEvalChecks`. The format is described in `skills/README.md`.
|
|
10
|
+
*/
|
|
11
|
+
import type { McpToolResult } from './registry.js';
|
|
12
|
+
export interface SkillEvalToolCall {
|
|
13
|
+
tool: string;
|
|
14
|
+
args?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillEvalCheck extends SkillEvalToolCall {
|
|
17
|
+
/** What the check is for, shown when it fails. */
|
|
18
|
+
why: string;
|
|
19
|
+
expect: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface SkillEval {
|
|
22
|
+
prompt: string;
|
|
23
|
+
fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture;
|
|
24
|
+
checks: SkillEvalCheck[];
|
|
25
|
+
solution: SkillEvalToolCall[];
|
|
26
|
+
}
|
|
27
|
+
export interface SpreadsheetEvalFixture {
|
|
28
|
+
editor: 'spreadsheet';
|
|
29
|
+
/** Raw inputs from A1, row by row, typed as a person would (`'` forces text). */
|
|
30
|
+
rows: Array<Array<string | number | boolean | null>>;
|
|
31
|
+
}
|
|
32
|
+
export interface ImageEvalFixture {
|
|
33
|
+
editor: 'image';
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
/** RGB background. */
|
|
37
|
+
background: [number, number, number];
|
|
38
|
+
/** Shapes painted over it in order, with RGB colours. */
|
|
39
|
+
shapes: Array<{
|
|
40
|
+
shape: 'rect' | 'ellipse';
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
color: [number, number, number];
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export interface VectorEvalFixture {
|
|
49
|
+
editor: 'vector';
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
/** Commands that build the starting artwork, run in order (`"$0.id"` refers to an earlier result). */
|
|
53
|
+
setup?: Array<{
|
|
54
|
+
id: string;
|
|
55
|
+
params?: Record<string, unknown>;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
export interface PaintEvalFixture {
|
|
59
|
+
editor: 'paint';
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
/** Tool calls that prepare the canvas (paper, layers, strokes), run in order. */
|
|
63
|
+
setup?: SkillEvalToolCall[];
|
|
64
|
+
}
|
|
65
|
+
export interface VideoEvalFixture {
|
|
66
|
+
editor: 'video';
|
|
67
|
+
/** Tool calls that build the starting project (settings, assets, clips, markers), run in order. */
|
|
68
|
+
setup?: SkillEvalToolCall[];
|
|
69
|
+
}
|
|
70
|
+
/** One eval file of a skill. */
|
|
71
|
+
export interface SkillEvalFile {
|
|
72
|
+
skill: string;
|
|
73
|
+
/** Path relative to the skills directory, e.g. `pixerate-image-selection-masking/evals/local-adjustment.json`. */
|
|
74
|
+
path: string;
|
|
75
|
+
eval: SkillEval;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The evals in a set of skill files (paths relative to the skills directory, contents as text,
|
|
79
|
+
* like `SkillFiles` but including `evals/*.json`), sorted by path.
|
|
80
|
+
*/
|
|
81
|
+
export declare function listSkillEvals(files: Readonly<Record<string, string>>): SkillEvalFile[];
|
|
82
|
+
/** The RGBA pixels of an image fixture: shapes painted over the background in order. */
|
|
83
|
+
export declare function imageEvalFixturePixels(f: ImageEvalFixture): {
|
|
84
|
+
width: number;
|
|
85
|
+
height: number;
|
|
86
|
+
data: Uint8ClampedArray;
|
|
87
|
+
};
|
|
88
|
+
/** Calls a tool by the bare name an eval uses; the harness maps it to its own names if it must. */
|
|
89
|
+
export type SkillEvalToolCaller = (tool: string, args: Record<string, unknown>) => Promise<McpToolResult>;
|
|
90
|
+
/** Calls one eval tool step (resolving `$read` arguments); throws with the tool's error text. */
|
|
91
|
+
export declare function callEvalTool(callTool: SkillEvalToolCaller, { tool, args }: SkillEvalToolCall): Promise<unknown>;
|
|
92
|
+
/**
|
|
93
|
+
* Whether `actual` matches `expected`. Objects match key by key (other keys are ignored),
|
|
94
|
+
* arrays element by element with the same length, other values exactly. Operators:
|
|
95
|
+
* `$min` / `$max` (numbers), `$type`, `$match` (a regular expression, or `[pattern, flags]`), `$in`, `$not`,
|
|
96
|
+
* `$every` / `$some` (array elements), `$length`, `$unique`, `$distinct` (the distinct
|
|
97
|
+
* values), `$sum`, `$column: [index, matcher]` (one column of a 2D array), `$nonEmpty`
|
|
98
|
+
* (the array without empty cells or rows) and `$expr` (a JavaScript expression over `v`).
|
|
99
|
+
*/
|
|
100
|
+
export declare function matchesEvalExpectation(actual: unknown, expected: unknown): boolean;
|
|
101
|
+
/** The checks that fail, each as its `why` with what the tool returned (or its error). */
|
|
102
|
+
export declare function failedEvalChecks(callTool: SkillEvalToolCaller, checks: readonly SkillEvalCheck[]): Promise<string[]>;
|
package/dist/tools/skills.d.cts
CHANGED
|
@@ -13,11 +13,22 @@ export interface SkillInfo {
|
|
|
13
13
|
description: string;
|
|
14
14
|
/** Other files of the skill relative to its directory, e.g. `references/commands.md`. */
|
|
15
15
|
references: string[];
|
|
16
|
+
/**
|
|
17
|
+
* `workflow` for a skill that covers one job from start to finish on a reference skill's tools
|
|
18
|
+
* (frontmatter `metadata.kind: workflow`); otherwise `reference`.
|
|
19
|
+
*/
|
|
20
|
+
kind: 'reference' | 'workflow';
|
|
21
|
+
/** A workflow skill's reference skill (frontmatter `metadata.reference`), whose tools it uses. */
|
|
22
|
+
reference?: string;
|
|
16
23
|
}
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Reads `name`, `description` and the one-level `metadata` map (`metadata.kind`,
|
|
26
|
+
* `metadata.reference`, …) from a SKILL.md's YAML frontmatter.
|
|
27
|
+
*/
|
|
18
28
|
export declare function parseSkillFrontmatter(markdown: string): {
|
|
19
29
|
name?: string;
|
|
20
30
|
description?: string;
|
|
31
|
+
metadata: Record<string, string>;
|
|
21
32
|
body: string;
|
|
22
33
|
};
|
|
23
34
|
/** The skills in a set of skill files, sorted by name. */
|
package/dist/tools/skills.d.ts
CHANGED
|
@@ -13,11 +13,22 @@ export interface SkillInfo {
|
|
|
13
13
|
description: string;
|
|
14
14
|
/** Other files of the skill relative to its directory, e.g. `references/commands.md`. */
|
|
15
15
|
references: string[];
|
|
16
|
+
/**
|
|
17
|
+
* `workflow` for a skill that covers one job from start to finish on a reference skill's tools
|
|
18
|
+
* (frontmatter `metadata.kind: workflow`); otherwise `reference`.
|
|
19
|
+
*/
|
|
20
|
+
kind: 'reference' | 'workflow';
|
|
21
|
+
/** A workflow skill's reference skill (frontmatter `metadata.reference`), whose tools it uses. */
|
|
22
|
+
reference?: string;
|
|
16
23
|
}
|
|
17
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Reads `name`, `description` and the one-level `metadata` map (`metadata.kind`,
|
|
26
|
+
* `metadata.reference`, …) from a SKILL.md's YAML frontmatter.
|
|
27
|
+
*/
|
|
18
28
|
export declare function parseSkillFrontmatter(markdown: string): {
|
|
19
29
|
name?: string;
|
|
20
30
|
description?: string;
|
|
31
|
+
metadata: Record<string, string>;
|
|
21
32
|
body: string;
|
|
22
33
|
};
|
|
23
34
|
/** The skills in a set of skill files, sorted by name. */
|
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,12 +116,14 @@ 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
|
|
121
|
-
of the solution
|
|
124
|
+
of the solution, using `@pixerate/editor/tools`: `listSkillEvals` reads the eval files,
|
|
125
|
+
`imageEvalFixturePixels` paints an image fixture, and `failedEvalChecks` runs the checks
|
|
126
|
+
through the harness's own tool calls (`matchesEvalExpectation` is the matcher on its own). In `expect`, objects match key by key and arrays element by element, with
|
|
122
127
|
operators for ranges and lists (`$min`, `$max`, `$match`, `$every`, `$some`, `$length`,
|
|
123
128
|
`$unique`, `$distinct`, `$sum`, `$column`, `$nonEmpty`, `$not`, `$in`, `$type`); a check's
|
|
124
129
|
argument can read a value the job created with `{ "$read": "list_layers", "path":
|
|
@@ -42,7 +42,9 @@ editor's, or (in discovery mode) only `list_toolsets`, `search_tools`, `enable_t
|
|
|
42
42
|
toolset when you will use many of its tools). The tools then appear in your list. Use
|
|
43
43
|
`disable_tools` to drop a toolset you are done with.
|
|
44
44
|
4. **Read the skill before working in a toolset.** `get_skill` with the skill `list_toolsets`
|
|
45
|
-
names (or the MCP prompt / `skill://` resource of the same name).
|
|
45
|
+
names (or the MCP prompt / `skill://` resource of the same name). When the task is a whole
|
|
46
|
+
job one of the toolset's `workflows` covers (a cutout, a photo correction, an icon set), read
|
|
47
|
+
that workflow skill too: it has the steps in order and the check after each.
|
|
46
48
|
5. **Read the house style when there is one.** A toolset with a `style` field in
|
|
47
49
|
`list_toolsets` (or an `enable_tools` result with a `style` line) has house-style skills:
|
|
48
50
|
call `get_style` with that domain before you make or change work there.
|
|
@@ -149,11 +151,11 @@ Never repeat an identical failing call. Never work around a read-only refusal; t
|
|
|
149
151
|
| Tool | Use it to |
|
|
150
152
|
| --- | --- |
|
|
151
153
|
| `list_editors` | See which editors are open in the browser and connected to the MCP bridge (id, name, kind, tool count, last activity). Call it first when tools come through the bridge; pass an id or part of a name as `editor` to any tool to pick one when several are open, otherwise the most recently active editor with that tool answers. If none is connected, ask the user to open the app and connect it. |
|
|
152
|
-
| `list_toolsets` | See the toolsets open now: name, title, what each covers, its skill, how many tools it has and whether they are in your tool list (`enabled`: all, some, none). Call it first in a discovery view, then `search_tools` or `enable_tools`. |
|
|
154
|
+
| `list_toolsets` | See the toolsets open now: name, title, what each covers, its skill, its workflow skills (`workflows`), how many tools it has and whether they are in your tool list (`enabled`: all, some, none). Call it first in a discovery view, then `search_tools` or `enable_tools`. |
|
|
153
155
|
| `search_tools` | Find the tool for a task: pass `query` in plain words (and `toolset` to search one editor, `limit` for more than 10). Returns the best matches first with name, toolset, title, one-sentence summary, whether it only reads and whether it is listed. Follow with `enable_tools`. |
|
|
154
156
|
| `enable_tools` | Add tools to your tool list by full name (`tools`, from `search_tools`) and/or whole `toolsets` (from `list_toolsets`). Returns the names added and how many are listed. Prefer single tools to large toolsets. Unknown names fail and change nothing. Allowed tools can also be called by name without enabling them. |
|
|
155
157
|
| `disable_tools` | Drop tools or whole toolsets you are done with from your tool list, to keep it short. Returns the names removed. |
|
|
156
|
-
| `get_skill` | Read a toolset's skill (markdown) before working with it; pass `file` for one of its reference files (listed at the top). The note at the top says how names are prefixed here. |
|
|
158
|
+
| `get_skill` | Read a toolset's skill (markdown) before working with it, and the workflow skill for the job when one fits; pass `file` for one of its reference files (listed at the top). The note at the top says how names are prefixed here. |
|
|
157
159
|
| `list_styles` | See the house-style skills here, general layer first: name, description, layer, the domains each applies to, which have a profile, whether each is on, and issues that leave all or part of one out. Pass `domain` to see one domain's. Follow with `get_style`. |
|
|
158
160
|
| `get_style` | Read the house style for a `domain` before you make or change work there: each skill's preferences as a `<style_skill>` block naming its layer, the merged profile (tokens resolved), `sources` (which layer set each field) and `issues`. Then check your work with the domain's `check_<domain>_style`. |
|
|
159
161
|
| `get_style_file` | Read one file of a house-style skill (`name`, `file`), such as a `references/*.md` its preferences point to, or its raw profile (`chart.json`) before tokens are resolved. An unknown file fails with the list of its files. |
|
|
@@ -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
|