@pixerate/editor 0.240.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 +195 -146
- package/dist/animation-editor/index.js +63 -14
- package/dist/{brushstrokeRender-WB67GMUP.js → brushstrokeRender-MOYGEXOC.js} +2 -2
- package/dist/{brushstrokeRender-MPOM7BFW.cjs → brushstrokeRender-UKADOV5D.cjs} +5 -5
- package/dist/canvas/index.cjs +15 -15
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-NKTNLJUJ.cjs → chunk-5BR7LC24.cjs} +99 -11
- package/dist/{chunk-ZXNO3ZOX.cjs → chunk-7F4G74UQ.cjs} +35 -35
- package/dist/{chunk-A7PQNIBX.cjs → chunk-7V7QMFTI.cjs} +14 -14
- package/dist/{chunk-AD7GA3TL.cjs → chunk-AFLMXITL.cjs} +23 -23
- package/dist/{chunk-JAOSL334.cjs → chunk-GHCSC2WP.cjs} +38 -16
- package/dist/{chunk-PP6BXVRL.cjs → chunk-HGGJ36HR.cjs} +1 -1
- package/dist/{chunk-BBUE3UJL.cjs → chunk-HW72ABGR.cjs} +86 -86
- package/dist/{chunk-7YGBSFC6.js → chunk-ILM6GYAX.js} +1 -1
- package/dist/{chunk-JWCKJNDR.cjs → chunk-K5DS2JTA.cjs} +6 -6
- package/dist/{chunk-THXFU3JB.cjs → chunk-KCMV2534.cjs} +2 -2
- package/dist/{chunk-WDDUPWVG.js → chunk-L3JFOTBQ.js} +1 -1
- package/dist/{chunk-3E36VDMJ.js → chunk-NE6OOKWJ.js} +1 -1
- package/dist/{chunk-SIK3LT6M.js → chunk-OFH6Q67U.js} +28 -6
- package/dist/{chunk-RV5DKZHU.js → chunk-QP2B3MID.js} +1 -1
- package/dist/{chunk-G3RUKJ5C.js → chunk-RXZL4NG5.js} +1 -1
- package/dist/{chunk-UUBO6RB5.js → chunk-UDBDYZET.js} +1 -1
- package/dist/{chunk-TEFQISBY.js → chunk-X3ZINLIP.js} +1 -1
- package/dist/{chunk-PJYG6FCF.js → chunk-XBZK47LV.js} +98 -10
- package/dist/{chunk-ZQIWBJGH.cjs → chunk-XNZFVYC2.cjs} +7 -7
- package/dist/{chunk-XK4SST43.js → chunk-ZDPLZDTT.js} +1 -1
- 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 +788 -788
- package/dist/image-editor/index.js +5 -5
- package/dist/paint-editor/controller.d.cts +67 -0
- package/dist/paint-editor/controller.d.ts +67 -0
- package/dist/paint-editor/effect-paint.d.cts +60 -3
- package/dist/paint-editor/effect-paint.d.ts +60 -3
- package/dist/paint-editor/index.cjs +682 -436
- package/dist/paint-editor/index.js +284 -38
- package/dist/paint-editor/stickers.d.cts +6 -0
- package/dist/paint-editor/stickers.d.ts +6 -0
- package/dist/paint-editor/thick.d.cts +4 -0
- package/dist/paint-editor/thick.d.ts +4 -0
- package/dist/paint-editor/wet.d.cts +8 -0
- package/dist/paint-editor/wet.d.ts +8 -0
- package/dist/vector/index.cjs +968 -968
- package/dist/vector/index.js +5 -5
- 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-paint-editor/SKILL.md +17 -21
- package/skills/pixerate-paint-editor/references/effect-paint.md +25 -0
- 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). |
|
|
@@ -235,21 +235,19 @@ for the controls and presets.
|
|
|
235
235
|
|
|
236
236
|
## Effect paint
|
|
237
237
|
|
|
238
|
-
`set_effect_paint` makes a generator, overlay or title effect the paint
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
an overlay paints only where it has pixels.
|
|
238
|
+
`set_effect_paint` makes a generator, overlay or title effect the paint: rendered once, every
|
|
239
|
+
stroke takes each pixel's colour from it, fixed to the canvas (painting reveals a hidden image).
|
|
240
|
+
Its transparency paints nothing.
|
|
242
241
|
|
|
243
242
|
1. `search_effects` (`use` generator, overlay or title) and `describe_effect` for the controls.
|
|
244
|
-
2. `set_effect_paint` with `effect`, `values
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
impasto brushes take it per pixel too, shifted by any paint they pick up: in watercolour and
|
|
249
|
-
gouache the pattern then flows, blooms and granulates with the water; impasto keeps its relief.
|
|
243
|
+
2. `set_effect_paint` with `effect`, `values`, `time` (ms, default 0). Wait for it to return.
|
|
244
|
+
3. Paint: dab, particle and bristle brushes take the colour per pixel; the Mixer Brush and pick-up
|
|
245
|
+
dab brushes take it per dab at its centre. Wet and impasto brushes take it per pixel, shifted by
|
|
246
|
+
paint they pick up (in watercolour and gouache it flows with the water; impasto keeps its relief).
|
|
250
247
|
Sprayed stickers each take the colour at their centre (they stay recolourable).
|
|
251
|
-
4. `set_color
|
|
252
|
-
|
|
248
|
+
4. `set_color`, `set_paint` or `remove_effect` (target and effect `paint`) go back to colour;
|
|
249
|
+
`update_effect` (target and effect `paint`, `values`) renders new values.
|
|
250
|
+
5. Along each stroke (`map`) or playing on stroke layers (`live`): [references/effect-paint.md](references/effect-paint.md).
|
|
253
251
|
|
|
254
252
|
When to use which:
|
|
255
253
|
|
|
@@ -259,6 +257,7 @@ When to use which:
|
|
|
259
257
|
| The effect in a wash that flows, or in thick paint | `set_effect_paint` and a watercolour, gouache, oil or impasto brush |
|
|
260
258
|
| The effect inside an exact shape, unchanged by water | Paint the shape, dry it, then `fill_layer_with_effect` with `lockTransparency: true` |
|
|
261
259
|
| The effect over the whole sheet | `set_paper_effect` or `add_effect_layer` |
|
|
260
|
+
| A pattern that follows the stroke, or paint that moves | `map: { by: "stroke" }`, `live: true` on a stroke layer |
|
|
262
261
|
|
|
263
262
|
## Canvas-hosted layer
|
|
264
263
|
|
|
@@ -402,7 +401,8 @@ view), so work with the tools you are given.
|
|
|
402
401
|
| `set_paper_effect` | Draw a generator over the paper (`values`, `opacity`, `blend` default `multiply`, `time`); `effect: null` removes it. One undo step. Returns the effect paper. |
|
|
403
402
|
| `add_effect_layer` | Add a layer drawn by a generator, overlay or title effect (`values`, `name`, `aboveId`, `opacity`, `blend`, `time`); it becomes active. Returns the layer and its effect. |
|
|
404
403
|
| `fill_layer_with_effect` | Composite a generator, overlay or title effect onto a layer (`layerId`, `values`, `opacity`, `lockTransparency`, `time`). One undo step. Returns the layer id and the effect. |
|
|
405
|
-
| `set_effect_paint` | Paint with a generator, overlay or title effect (`effect`, `values`, `time` ms default 0): rendered once at the canvas size, strokes take each pixel's colour from it. Call after `describe_effect`, then `paint_stroke
|
|
404
|
+
| `set_effect_paint` | Paint with a generator, overlay or title effect (`effect`, `values`, `time` ms default 0): rendered once at the canvas size, strokes take each pixel's colour from it; `map` `{ by: "stroke", length, repeat }` lays it along each stroke; `live` keeps it playing on stroke layers. Call after `describe_effect`, then `paint_stroke`. Editor state, not an undo step. Returns `effectPaint`. |
|
|
405
|
+
| `set_effect_playback` | Play or pause live effect paint in the view (`playing`) and cap its frame rate (`fps` 1..60). Editor state. Returns `playing`, `fps`, `time` and `animation` (what plays, or null). Export a moment with `export_image` / `export_psd` `time`. |
|
|
406
406
|
|
|
407
407
|
### Guides
|
|
408
408
|
|
|
@@ -495,13 +495,9 @@ The host may add the shared document tools (see `pixerate-agent-tools`). A paint
|
|
|
495
495
|
strokes record it, and replays render it again first, so they need the same provider (an
|
|
496
496
|
error names the effect and provider when it is missing; `color` on `replay_strokes` repaints
|
|
497
497
|
in one colour instead).
|
|
498
|
-
- **Effect paint
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
`set_paint` paints colour instead.
|
|
503
|
-
- **Effect paint is fixed to the canvas, not the stroke**: a replayed or moved stroke shows the
|
|
504
|
-
part of the effect under its new place. Wait for `set_effect_paint` to return before painting;
|
|
505
|
-
strokes are refused while it renders.
|
|
498
|
+
- **Effect paint is fixed to the canvas by default** (`map: { by: "stroke" }` follows the stroke),
|
|
499
|
+
and in wet paint it flows with the water; for a crisp pattern in an exact shape, paint and dry
|
|
500
|
+
the shape, then `fill_layer_with_effect` (`lockTransparency: true`). Erasers, blenders and
|
|
501
|
+
smudges ignore it. Wait for `set_effect_paint` to return; strokes are refused while it renders.
|
|
506
502
|
- **Recorded strokes are kept in saved documents** (`save_paint_document`, `get_paint_document`),
|
|
507
503
|
so a painting can be replayed and repainted at a higher resolution.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Effect paint along the stroke, and live effect paint
|
|
2
|
+
|
|
3
|
+
- **Along the stroke** (`set_effect_paint` `map: { by: "stroke", length, repeat }`): the effect is
|
|
4
|
+
rendered as a strip `length` px long (16..4096, default 300) and 256 px across. Each pixel takes
|
|
5
|
+
the colour at its distance along the stroke and its place across the tip, so the pattern follows
|
|
6
|
+
the stroke's turns instead of staying fixed to the canvas. `repeat` starts the strip again
|
|
7
|
+
(default), `mirror` runs it back, `clamp` holds the last colour. Use it for ribbons, ropes,
|
|
8
|
+
flames and textures that should flow with the mark; use the default canvas mapping to reveal one
|
|
9
|
+
hidden picture across many strokes. Every colour-laying brush takes it (watercolour, gouache, oil
|
|
10
|
+
and impasto too; sprayed stickers at their centre); particle and bristle marks take the place of
|
|
11
|
+
their pen dab.
|
|
12
|
+
Scaled replays (`export_high_res`) scale `length` with the canvas.
|
|
13
|
+
- **Live** (`live: true`): on a stroke layer (`add_layer {type: "strokes"}`) each stroke keeps
|
|
14
|
+
playing the effect, so the paint moves. Paint the strokes, then `set_effect_playback`
|
|
15
|
+
(`playing`, `fps` 1..60, default 30) plays or pauses them in the view. The document keeps the
|
|
16
|
+
frame at the effect's own `time`; `export_image` and `export_psd` take `time` (ms) to export
|
|
17
|
+
another moment. Live strokes stay editable like any stroke on a stroke layer
|
|
18
|
+
(`update_stroke` with `paint` changes the effect).
|
|
19
|
+
- **Where it applies**: live only plays on stroke layers; on a pixel layer the stroke bakes the
|
|
20
|
+
frame at `time`. A painting plays at most 8 distinct live effects (each needs a renderer per
|
|
21
|
+
frame); paint more strokes with one of them, or bake new ones (`live: false`).
|
|
22
|
+
- **Cost**: each frame redraws the live strokes (and any stroke overlapping them), and renders
|
|
23
|
+
each live effect once. On a large canvas with many live strokes, lower `fps`.
|
|
24
|
+
- **Without the provider** (a document opened elsewhere): the saved pixels show the frame as
|
|
25
|
+
painted, and nothing plays until the provider is registered.
|
|
@@ -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.
|