@pixerate/editor 0.241.0 → 0.242.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/animation-editor/controller.d.cts +10 -0
- package/dist/animation-editor/controller.d.ts +10 -0
- package/dist/animation-editor/effects.d.cts +1 -1
- package/dist/animation-editor/effects.d.ts +1 -1
- package/dist/animation-editor/index.cjs +137 -88
- package/dist/animation-editor/index.js +60 -11
- package/dist/canvas/index.cjs +7 -7
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-A7PQNIBX.cjs → chunk-7V7QMFTI.cjs} +14 -14
- package/dist/{chunk-JAOSL334.cjs → chunk-GHCSC2WP.cjs} +38 -16
- package/dist/{chunk-JWCKJNDR.cjs → chunk-K5DS2JTA.cjs} +6 -6
- package/dist/{chunk-WDDUPWVG.js → chunk-L3JFOTBQ.js} +1 -1
- package/dist/{chunk-SIK3LT6M.js → chunk-OFH6Q67U.js} +28 -6
- package/dist/{chunk-G3RUKJ5C.js → chunk-RXZL4NG5.js} +1 -1
- package/dist/{chunk-TEFQISBY.js → chunk-X3ZINLIP.js} +1 -1
- package/dist/{chunk-ZQIWBJGH.cjs → chunk-XNZFVYC2.cjs} +7 -7
- package/dist/effects/fake.d.cts +4 -0
- package/dist/effects/fake.d.ts +4 -0
- package/dist/effects/helpers.d.cts +6 -0
- package/dist/effects/helpers.d.ts +6 -0
- package/dist/effects/index.cjs +222 -94
- package/dist/effects/index.js +131 -3
- package/dist/effects/types.d.cts +31 -0
- package/dist/effects/types.d.ts +31 -0
- package/dist/image-editor/index.cjs +13 -13
- package/dist/image-editor/index.js +1 -1
- package/dist/paint-editor/index.cjs +7 -7
- package/dist/paint-editor/index.js +2 -2
- package/dist/vector/index.cjs +6 -6
- package/dist/vector/index.js +1 -1
- package/dist/video-editor/controller.d.cts +49 -1
- package/dist/video-editor/controller.d.ts +49 -1
- package/dist/video-editor/effect-path.d.cts +66 -0
- package/dist/video-editor/effect-path.d.ts +66 -0
- package/dist/video-editor/index.cjs +616 -427
- package/dist/video-editor/index.d.cts +1 -0
- package/dist/video-editor/index.d.ts +1 -0
- package/dist/video-editor/index.js +193 -4
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +1 -1
- package/skills/pixerate-video-editor/SKILL.md +9 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createEffectAuthoringSession
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-X3ZINLIP.js";
|
|
4
4
|
import {
|
|
5
5
|
VIDEO_ADJUSTMENT_SCOPES,
|
|
6
6
|
VIDEO_BLEND_MODES,
|
|
@@ -149,7 +149,7 @@ import {
|
|
|
149
149
|
videoTransitionEase,
|
|
150
150
|
videoValueAt,
|
|
151
151
|
videoWipeRect
|
|
152
|
-
} from "../chunk-
|
|
152
|
+
} from "../chunk-L3JFOTBQ.js";
|
|
153
153
|
import {
|
|
154
154
|
namedWithSuggestion
|
|
155
155
|
} from "../chunk-EUKY4KFX.js";
|
|
@@ -159,7 +159,7 @@ import {
|
|
|
159
159
|
effectSequenceTiming,
|
|
160
160
|
effectTemplateTools,
|
|
161
161
|
fillEffectTemplate
|
|
162
|
-
} from "../chunk-
|
|
162
|
+
} from "../chunk-RXZL4NG5.js";
|
|
163
163
|
import "../chunk-EHLOCVWB.js";
|
|
164
164
|
import {
|
|
165
165
|
CAMERA_MOVE_MAX_MS,
|
|
@@ -168,9 +168,10 @@ import {
|
|
|
168
168
|
cameraMoveParts,
|
|
169
169
|
createEffectToolProvider,
|
|
170
170
|
isEffectError,
|
|
171
|
+
pathControlsOf,
|
|
171
172
|
resolveCameraMoveSpec,
|
|
172
173
|
sampleEffectCameraMove
|
|
173
|
-
} from "../chunk-
|
|
174
|
+
} from "../chunk-OFH6Q67U.js";
|
|
174
175
|
import "../chunk-V2ZCJRDM.js";
|
|
175
176
|
import {
|
|
176
177
|
defineEditorDocument
|
|
@@ -2205,6 +2206,73 @@ var VideoAudioPeaksCache = class {
|
|
|
2205
2206
|
}
|
|
2206
2207
|
};
|
|
2207
2208
|
|
|
2209
|
+
// src/video-editor/effect-path.ts
|
|
2210
|
+
function videoClipPicture(project, clip) {
|
|
2211
|
+
const asset = clip.type === "media" ? project.assets.find((a) => a.id === clip.asset) : void 0;
|
|
2212
|
+
const size = asset ? { width: asset.width ?? project.width, height: asset.height ?? project.height } : { width: project.width, height: project.height };
|
|
2213
|
+
const fit = clip.fit ?? (clip.type === "media" || clip.type === "sequence" ? "contain" : "fill");
|
|
2214
|
+
const rect = placeVideoRect(project, size, clip.type === "text" ? "fill" : fit, clip.transform, 0);
|
|
2215
|
+
return { size, rect, rotation: videoTransformAt(clip.transform, 0).rotation };
|
|
2216
|
+
}
|
|
2217
|
+
function turn(x, y, cx, cy, deg) {
|
|
2218
|
+
if (!deg) return [x, y];
|
|
2219
|
+
const a = deg * Math.PI / 180;
|
|
2220
|
+
const dx = x - cx;
|
|
2221
|
+
const dy = y - cy;
|
|
2222
|
+
return [cx + dx * Math.cos(a) - dy * Math.sin(a), cy + dx * Math.sin(a) + dy * Math.cos(a)];
|
|
2223
|
+
}
|
|
2224
|
+
function framePointsToPicture(points, picture) {
|
|
2225
|
+
const { rect, size, rotation } = picture;
|
|
2226
|
+
const cx = rect.x + rect.width / 2;
|
|
2227
|
+
const cy = rect.y + rect.height / 2;
|
|
2228
|
+
const out = [];
|
|
2229
|
+
for (let i = 0; i + 1 < points.length; i += 2) {
|
|
2230
|
+
const [x, y] = turn(points[i], points[i + 1], cx, cy, -rotation);
|
|
2231
|
+
out.push(Math.round((x - rect.x) / rect.width * size.width * 100) / 100, Math.round((y - rect.y) / rect.height * size.height * 100) / 100);
|
|
2232
|
+
}
|
|
2233
|
+
return out;
|
|
2234
|
+
}
|
|
2235
|
+
function videoClipMaskOutline(project, clip) {
|
|
2236
|
+
if (!clip.mask) return null;
|
|
2237
|
+
const picture = videoClipPicture(project, clip);
|
|
2238
|
+
const { rect, rotation } = picture;
|
|
2239
|
+
const cx = rect.x + rect.width / 2;
|
|
2240
|
+
const cy = rect.y + rect.height / 2;
|
|
2241
|
+
const polygon = videoMaskPolygon(clip.mask, rect).map(([x, y]) => turn(x, y, cx, cy, rotation));
|
|
2242
|
+
if (polygon.length < 2) return null;
|
|
2243
|
+
return [...polygon, polygon[0]].flatMap(([x, y]) => [Math.round(x * 100) / 100, Math.round(y * 100) / 100]);
|
|
2244
|
+
}
|
|
2245
|
+
var VideoPathGesture = class {
|
|
2246
|
+
constructor(frame, spacing = 3) {
|
|
2247
|
+
this.frame = frame;
|
|
2248
|
+
this.spacing = spacing;
|
|
2249
|
+
}
|
|
2250
|
+
frame;
|
|
2251
|
+
spacing;
|
|
2252
|
+
points = [];
|
|
2253
|
+
drawing = false;
|
|
2254
|
+
at(e, box) {
|
|
2255
|
+
return [Math.round((e.clientX - box.left) / box.width * this.frame.width * 100) / 100, Math.round((e.clientY - box.top) / box.height * this.frame.height * 100) / 100];
|
|
2256
|
+
}
|
|
2257
|
+
down(e, box) {
|
|
2258
|
+
this.drawing = true;
|
|
2259
|
+
this.points = this.at(e, box);
|
|
2260
|
+
}
|
|
2261
|
+
move(e, box) {
|
|
2262
|
+
if (!this.drawing) return false;
|
|
2263
|
+
const [x, y] = this.at(e, box);
|
|
2264
|
+
const n = this.points.length;
|
|
2265
|
+
if (Math.hypot(x - this.points[n - 2], y - this.points[n - 1]) < this.spacing) return false;
|
|
2266
|
+
this.points.push(x, y);
|
|
2267
|
+
return true;
|
|
2268
|
+
}
|
|
2269
|
+
/** Ends the stroke; returns its points, or `null` for a tap (fewer than two points). */
|
|
2270
|
+
up() {
|
|
2271
|
+
this.drawing = false;
|
|
2272
|
+
return this.points.length >= 4 ? [...this.points] : null;
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2208
2276
|
// src/video-editor/plan.ts
|
|
2209
2277
|
function clipIndex(project) {
|
|
2210
2278
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3660,6 +3728,7 @@ var VideoEditorController = class _VideoEditorController {
|
|
|
3660
3728
|
previewScale: this.previewScale,
|
|
3661
3729
|
effectsProvider: this.effects.provider,
|
|
3662
3730
|
plans: this.plans,
|
|
3731
|
+
pathDrawing: this.pathDrawing,
|
|
3663
3732
|
revision: this.revision
|
|
3664
3733
|
};
|
|
3665
3734
|
}
|
|
@@ -4176,6 +4245,81 @@ var VideoEditorController = class _VideoEditorController {
|
|
|
4176
4245
|
return { edit: { op: "set_transition", clip, transition: { ...t, effect: { ...t.effect, values: merged } } }, label: `Change transition ${d.name}`, clip };
|
|
4177
4246
|
}, options.merge);
|
|
4178
4247
|
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Gives an effect that draws along a path (see `pathControlsOf`) its path: on an effect clip
|
|
4250
|
+
* (`instance` "generator") or a clip effect (its id). The path is frame points (`points`
|
|
4251
|
+
* `[x0, y0, …]`, mapped into the pixels of the picture the effect renders: the project frame for
|
|
4252
|
+
* an effect clip, the clip's own picture for a clip effect), SVG path data (`d`, which the effect
|
|
4253
|
+
* fits as its control says; `""` clears it), the outline of a clip's mask (`mask`), or words the
|
|
4254
|
+
* provider writes as a single-stroke path (`text`). `control` picks the path control when there
|
|
4255
|
+
* are several. Checked before anything changes; one undo step. Returns the edit, the control and
|
|
4256
|
+
* the value it took.
|
|
4257
|
+
*/
|
|
4258
|
+
async setEffectPath(clip, instance, path, options = {}) {
|
|
4259
|
+
const fail2 = (error) => ({ ok: false, error });
|
|
4260
|
+
try {
|
|
4261
|
+
const current = this.clipOrThrow(clip);
|
|
4262
|
+
const effect = instance === "generator" ? current.type === "effect" ? current.effect : null : current.effects?.find((e) => e.id === instance)?.effect;
|
|
4263
|
+
if (!effect) {
|
|
4264
|
+
const ids = ["generator"].filter(() => current.type === "effect").concat((current.effects ?? []).map((e) => e.id));
|
|
4265
|
+
return fail2(`Clip "${clip}" has no effect "${instance}"; its effects are ${ids.map((i) => `"${i}"`).join(", ") || "none"} ("generator" is an effect clip's own picture).`);
|
|
4266
|
+
}
|
|
4267
|
+
const d = await this.describeEffect(effect);
|
|
4268
|
+
const controls = pathControlsOf(d);
|
|
4269
|
+
if (!controls.length) return fail2(`"${d.id}" does not draw along a path. Find one that does with search_effects accepts ["path"].`);
|
|
4270
|
+
const control = options.control ?? (controls.length === 1 ? controls[0] : void 0);
|
|
4271
|
+
if (!control) return fail2(`"${d.id}" has several path controls (${controls.join(", ")}); pass control.`);
|
|
4272
|
+
if (!controls.includes(control)) return fail2(`"${control}" is not a path control of "${d.id}"; its path controls are ${controls.join(", ")}.`);
|
|
4273
|
+
const picture = videoClipPicture(this.project, current);
|
|
4274
|
+
let value;
|
|
4275
|
+
if ("points" in path) {
|
|
4276
|
+
const pts = path.points;
|
|
4277
|
+
if (!Array.isArray(pts) || pts.length < 4 || pts.length % 2 || !pts.every(Number.isFinite)) return fail2("points must be at least two points as [x0, y0, x1, y1, \u2026] in frame pixels.");
|
|
4278
|
+
value = framePointsToPicture(pts, picture);
|
|
4279
|
+
} else if ("d" in path) {
|
|
4280
|
+
if (typeof path.d !== "string") return fail2('d must be SVG path data (a string), or "" to clear the path.');
|
|
4281
|
+
value = path.d;
|
|
4282
|
+
} else if ("mask" in path) {
|
|
4283
|
+
const from = findVideoClip(this.project, path.mask.clip)?.clip;
|
|
4284
|
+
if (!from) return fail2(`There is no clip "${path.mask.clip}" to take a mask from.`);
|
|
4285
|
+
const outline = videoClipMaskOutline(this.project, from);
|
|
4286
|
+
if (!outline) return fail2(`Clip "${path.mask.clip}" has no mask; add one with set_video_clip_mask, or pass points or d.`);
|
|
4287
|
+
value = framePointsToPicture(outline, picture);
|
|
4288
|
+
} else if ("text" in path) {
|
|
4289
|
+
const provider = this.effects.require();
|
|
4290
|
+
if (!provider.pathFromText) return fail2(`The effects provider "${provider.id}" cannot write words as a path; pass points or d.`);
|
|
4291
|
+
if (typeof path.text !== "string" || !path.text.trim()) return fail2("text is empty; pass the words to write.");
|
|
4292
|
+
value = await provider.pathFromText(path.text, { ...path.font ? { font: path.font } : {}, ...path.align ? { align: path.align } : {} });
|
|
4293
|
+
} else return fail2("Pass the path as one of points, d, mask or text.");
|
|
4294
|
+
const r = instance === "generator" ? await this.updateEffectValues(clip, "generator", { [control]: value }) : await this.updateClipEffect(clip, instance, { values: { [control]: value } });
|
|
4295
|
+
return r.ok ? { ...r, control, value } : r;
|
|
4296
|
+
} catch (e) {
|
|
4297
|
+
return fail2(e instanceof Error ? e.message : String(e));
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
pathDrawing = null;
|
|
4301
|
+
/**
|
|
4302
|
+
* Starts drawing a path on the preview for an effect that draws along one (the preview then
|
|
4303
|
+
* takes the pointer; `finishPathDrawing` with the frame points applies it). Editor state.
|
|
4304
|
+
*/
|
|
4305
|
+
startPathDrawing(target) {
|
|
4306
|
+
this.pathDrawing = { ...target };
|
|
4307
|
+
this.notify();
|
|
4308
|
+
}
|
|
4309
|
+
/** Stops drawing a path without changing anything. */
|
|
4310
|
+
cancelPathDrawing() {
|
|
4311
|
+
if (!this.pathDrawing) return;
|
|
4312
|
+
this.pathDrawing = null;
|
|
4313
|
+
this.notify();
|
|
4314
|
+
}
|
|
4315
|
+
/** Applies the drawn path (frame points) to the effect being drawn for, and stops drawing. One undo step. */
|
|
4316
|
+
async finishPathDrawing(points) {
|
|
4317
|
+
const target = this.pathDrawing;
|
|
4318
|
+
this.pathDrawing = null;
|
|
4319
|
+
this.notify();
|
|
4320
|
+
if (!target) return { ok: false, error: "No path is being drawn; start one from the effect's Draw path button." };
|
|
4321
|
+
return this.setEffectPath(target.clip, target.instance, { points }, { control: target.control });
|
|
4322
|
+
}
|
|
4179
4323
|
/** Keys for one numeric control of a clip effect (clip-relative ms), or `null` to stop animating it (keeping its value at the clip's start). */
|
|
4180
4324
|
setClipEffectKeyframes(clip, instance, control, keys) {
|
|
4181
4325
|
const current = findVideoClip(this.project, clip)?.clip;
|
|
@@ -6117,6 +6261,47 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
6117
6261
|
return { clip: args.clip, transition: clipOf(args.clip).transition, effects: videoClipAppliedEffects(clipOf(args.clip)) };
|
|
6118
6262
|
}
|
|
6119
6263
|
},
|
|
6264
|
+
{
|
|
6265
|
+
name: "set_video_effect_path",
|
|
6266
|
+
title: "Run an effect along a path",
|
|
6267
|
+
description: `Gives an effect that draws along a path (search_effects with accepts ["path"]; describe_effect lists its pathControls) the path to draw along: on an effect clip (instance "generator", added with add_video_effect_clip) or a clip effect (its id, from apply_effect). Pass exactly one of: points (frame pixels from the top left, as on the preview; mapped into the pixels of the picture the effect renders, so a clip effect lines up with its moved or scaled clip), d (SVG path data the effect fits into its picture; "" clears the path), mask (use the outline of a clip's mask, in frame pixels), or text (words the provider writes as a single-stroke path, when it can). Refuses an effect without a path control, naming how to find one. One undo step. Returns the clip, instance, the control set and the value it took (points in picture pixels, or path data).`,
|
|
6268
|
+
inputSchema: {
|
|
6269
|
+
type: "object",
|
|
6270
|
+
properties: {
|
|
6271
|
+
clip: { type: "string", minLength: 1, description: "The clip id from get_video_project." },
|
|
6272
|
+
instance: { type: "string", minLength: 1, description: `"generator" for an effect clip's own picture, or a clip effect id from list_applied_effects.` },
|
|
6273
|
+
control: { type: "string", minLength: 1, description: "Which path control (default: the effect's only one; describe_effect pathControls lists them)." },
|
|
6274
|
+
points: { type: "array", minItems: 4, maxItems: 4096, items: { type: "number", description: "x or y, frame pixels." }, description: "The path as frame points [x0, y0, x1, y1, \u2026] in pixels from the frame's top left." },
|
|
6275
|
+
d: { type: "string", description: `SVG path data, fitted into the effect's picture as its control says; "" clears the path.` },
|
|
6276
|
+
mask: { type: "object", properties: { clip: { type: "string", minLength: 1, description: "The clip whose mask outline to use." } }, required: ["clip"], additionalProperties: false, description: "Use the outline of this clip's mask (set_video_clip_mask), in frame pixels." },
|
|
6277
|
+
text: {
|
|
6278
|
+
type: "object",
|
|
6279
|
+
properties: {
|
|
6280
|
+
text: { type: "string", minLength: 1, description: "The words to write." },
|
|
6281
|
+
font: { type: "string", minLength: 1, description: "A single-stroke font the provider offers (default its first)." },
|
|
6282
|
+
align: { type: "string", enum: ["start", "middle", "end"], description: "Where the line sits (default start)." }
|
|
6283
|
+
},
|
|
6284
|
+
required: ["text"],
|
|
6285
|
+
additionalProperties: false,
|
|
6286
|
+
description: "Words the provider writes as a single-stroke path (a signature, handwriting)."
|
|
6287
|
+
}
|
|
6288
|
+
},
|
|
6289
|
+
required: ["clip", "instance"],
|
|
6290
|
+
oneOf: [{ required: ["points"] }, { required: ["d"] }, { required: ["mask"] }, { required: ["text"] }],
|
|
6291
|
+
additionalProperties: false
|
|
6292
|
+
},
|
|
6293
|
+
annotations: { title: "Run an effect along a path", ...ctx.annotations.write },
|
|
6294
|
+
handler: async (args) => {
|
|
6295
|
+
ctx.requireProvider();
|
|
6296
|
+
const given = ["points", "d", "mask", "text"].filter((k2) => args[k2] !== void 0);
|
|
6297
|
+
if (given.length !== 1) throw new Error(`Pass exactly one of points, d, mask or text${given.length ? ` (got ${given.join(", ")})` : ""}.`);
|
|
6298
|
+
const k = given[0];
|
|
6299
|
+
const path = k === "text" ? { ...args.text } : { [k]: args[k] };
|
|
6300
|
+
const r = await controller.setEffectPath(args.clip, args.instance, path, { control: args.control });
|
|
6301
|
+
if (!r.ok) throw new Error(r.error);
|
|
6302
|
+
return { clip: args.clip, instance: args.instance, control: r.control, value: r.value };
|
|
6303
|
+
}
|
|
6304
|
+
},
|
|
6120
6305
|
{
|
|
6121
6306
|
name: "set_video_effect_keyframes",
|
|
6122
6307
|
title: "Keyframe an effect control",
|
|
@@ -6675,6 +6860,7 @@ export {
|
|
|
6675
6860
|
VideoEffectsEngine,
|
|
6676
6861
|
VideoFrameCache,
|
|
6677
6862
|
VideoLutCache,
|
|
6863
|
+
VideoPathGesture,
|
|
6678
6864
|
VideoPreviewEngine,
|
|
6679
6865
|
VideoPreviewPixels,
|
|
6680
6866
|
allVideoClipIds,
|
|
@@ -6718,6 +6904,7 @@ export {
|
|
|
6718
6904
|
formatVideoSeconds,
|
|
6719
6905
|
formatVideoTimecode,
|
|
6720
6906
|
formatVideoTimecodeFrames,
|
|
6907
|
+
framePointsToPicture,
|
|
6721
6908
|
isVideoDropFrameRate,
|
|
6722
6909
|
isVideoKeyframes,
|
|
6723
6910
|
measureVideoLoudness,
|
|
@@ -6809,7 +6996,9 @@ export {
|
|
|
6809
6996
|
videoClipHeading,
|
|
6810
6997
|
videoClipKeyframes,
|
|
6811
6998
|
videoClipLabel,
|
|
6999
|
+
videoClipMaskOutline,
|
|
6812
7000
|
videoClipOverhang,
|
|
7001
|
+
videoClipPicture,
|
|
6813
7002
|
videoClipShowsWaveform,
|
|
6814
7003
|
videoClipSoundGain,
|
|
6815
7004
|
videoClipSourceRange,
|
package/package.json
CHANGED
|
@@ -161,7 +161,7 @@ refuse.
|
|
|
161
161
|
| `apply_effect` | Animate a node (`target`, a group animates as one) with a motion effect: the changes become ordinary commands, tagged with the effect, in a new shot after the last. Returns the instance id. |
|
|
162
162
|
| `update_effect` | Make an applied effect again with new `values`, in the same place (`effect` is its instance id). |
|
|
163
163
|
| `remove_effect` | Remove an applied effect: its commands, shots it alone filled and a vector effect's group. |
|
|
164
|
-
| `add_vector_effect` | Add a vector effect's own picture as an artwork group (fitted into `box`) with its motion as new shots. Returns the instance and group ids. |
|
|
164
|
+
| `add_vector_effect` | Add a vector effect's own picture as an artwork group (fitted into `box`) with its motion as new shots. For an effect that draws along a path (`search_effects` `accepts: ["path"]`: a signature, a route), `path: { node }` runs it along a path node and lands it on the node, `path: { text }` writes words. Use it when the effect should trace the path itself; use `follow` or a draw-on to move or reveal your own art. Returns the instance and group ids. |
|
|
165
165
|
| `list_camera_moves` | List the provider's camera moves for a `role` (`transition`: how the camera gets into a shot; `hold`: how it moves while the shot holds), with `durationMs` and `cut`. Returns `available: false` without camera moves. Call before `set_shot_camera_move`; `describe_effect` gives a move's controls and presets. |
|
|
166
166
|
| `set_shot_camera_move` | Give a shot a camera move (`shot`, `role`, `move`, optional `ms` for a transition, `preset`, `values`): it is saved on the shot's camera as `in` or `during`. Returns the shot's camera and the `window` (ms) the move plays in. A wrong-role id is refused naming the moves that fit. |
|
|
167
167
|
| `clear_shot_camera_move` | Remove a shot's move for a `role`, keeping its framing. Returns the shot's camera (null when nothing is left). |
|
|
@@ -82,7 +82,9 @@ the pitfalls, and uses the tools below.
|
|
|
82
82
|
controls, then `apply_effect` (filters and overlays on a clip), `set_video_effect_transition`
|
|
83
83
|
or `add_video_effect_clip` (generators and titles). Tune with `update_effect` and
|
|
84
84
|
`set_video_effect_keyframes`; check with `get_video_frame` (layers list `effects`) and
|
|
85
|
-
`list_applied_effects`.
|
|
85
|
+
`list_applied_effects`. **An effect along a path** (a beam along a border, a trail, words
|
|
86
|
+
written on): `search_effects` with `accepts: ["path"]`, add it as a clip or clip effect, then
|
|
87
|
+
`set_video_effect_path` with frame `points`, path data `d`, a clip's `mask` outline or `text`.
|
|
86
88
|
12. **Camera moves** (a provider with camera moves, such as Flair): a clip is the shot.
|
|
87
89
|
`list_video_camera_moves` with a `role`, then `set_clip_camera_move`: `transition` moves
|
|
88
90
|
from `from` to `framing` from the clip's start (a glide, whip pan, punch zoom); `hold`
|
|
@@ -298,7 +300,7 @@ and every write tool is refused with the reason.
|
|
|
298
300
|
|
|
299
301
|
| Tool | Use it to |
|
|
300
302
|
| --- | --- |
|
|
301
|
-
| `search_effects` | Find effects: words in `query`, filtered by `kind`, `category
|
|
303
|
+
| `search_effects` | Find effects: words in `query`, filtered by `kind`, `category`, `use` (filter, overlay for clips; transition; generator, title) or `accepts: ["path"]` (effects that draw along a path you give). Returns ids, names, uses, whether they are transparent or loop, and their length. Camera moves (`kind` camera) are listed but go on clips with `set_clip_camera_move`, not `apply_effect`. Call `describe_effect` next. |
|
|
302
304
|
| `describe_effect` | Read one effect's `controls` (a strict JSON Schema: names, types, ranges, units), `defaults`, `presets` and `inputs`, and whether it `appliesHere` (as a clip effect). Use the names and ranges for values. |
|
|
303
305
|
| `list_applied_effects` | See the effects in the project per clip, in order: clip effects (`fx1`...), an effect clip's own effect (`generator`) and an effect transition (`transition`), with their saved values and `available` (false when the provider lacks it: drawn as a placeholder). |
|
|
304
306
|
| `apply_effect` | Add a filter or overlay effect to a clip's stack (`target` clip id, `effect`, `values`, `index`, `enabled`, `opacity`). Effects run in order on the clip's picture before its grade and placement. Returns the effect's new id and the clip's effects. |
|
|
@@ -306,6 +308,7 @@ and every write tool is refused with the reason.
|
|
|
306
308
|
| `remove_effect` | Take an effect off a clip (`transition` removes an effect transition; an effect clip is deleted with `delete_video_clips`). Returns what was removed and what is left. |
|
|
307
309
|
| `add_video_effect_clip` | Put a generator, title or overlay effect on a video `track` as a clip filling the frame (`start`, `duration` default the effect's own length, `values`, `mode`). Transparent effects show the tracks beneath. Returns the clip id. |
|
|
308
310
|
| `set_video_effect_transition` | Use a transition effect from the effects provider into a `clip` from the one ending where it starts (`duration` centred on the cut, `values`). Needs handles like a crossfade. For a cross dissolve, dip, wipe, push, slide or iris use the built-in `set_video_transition` instead. |
|
|
311
|
+
| `set_video_effect_path` | Give an effect that draws along a path (`describe_effect` `pathControls`) its path, on an effect clip (`instance` "generator") or a clip effect: frame `points` `[x0, y0, …]` (mapped into the picture the effect renders, so a clip effect follows its moved or scaled clip), path data `d` (`""` clears), a clip's `mask` outline, or `text` the provider writes. One undo step. Returns the control and the value it took. |
|
|
309
312
|
| `set_video_effect_keyframes` | Animate one number `control` of a clip effect (or an effect clip's `generator`) with `keys` in clip ms, or `null` to stop animating it. |
|
|
310
313
|
| `list_video_camera_moves` | List the provider's camera moves for a `role` (`transition` into a clip, `hold` across it) with `durationMs` and `cut`. Returns `available: false` without camera moves. Call before `set_clip_camera_move`. |
|
|
311
314
|
| `set_clip_camera_move` | Play a camera move on a clip's picture (`clip`, `role`, `move`, `ms` for a transition, `preset`, `values`, `from` and `framing` as `{ zoom, x, y }`): baked into its transform keys, one per frame, two at a cut. Returns the camera, key counts, `cuts`, `windows` and `notes` (tilt, turn and blur cannot be baked). Refuses a clip keyed by hand. |
|
|
@@ -328,6 +331,10 @@ The host may add the shared document tools (see `pixerate-agent-tools`). A video
|
|
|
328
331
|
|
|
329
332
|
## Pitfalls
|
|
330
333
|
|
|
334
|
+
- **Path points are frame pixels**, as on the preview: `set_video_effect_path` maps them into the
|
|
335
|
+
picture the effect renders. Do not pass a clip effect points in its source's pixels yourself, and
|
|
336
|
+
give it the path again after moving or scaling the clip a lot (it maps the clip's first frame).
|
|
337
|
+
|
|
331
338
|
- **Camera moves are baked keys.** After trimming or splitting a clip, `check_video_project`
|
|
332
339
|
reports `camera-stale`: set the move again to re-bake it. Editing the clip's x, y, scale or
|
|
333
340
|
rotation keys by hand leaves the moves no longer describing them; clear the moves first.
|