@pixerate/editor 0.233.0 → 0.235.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 +25 -0
- package/dist/animation-editor/controller.d.ts +25 -0
- package/dist/animation-editor/index.cjs +159 -50
- package/dist/animation-editor/index.d.cts +1 -0
- package/dist/animation-editor/index.d.ts +1 -0
- package/dist/animation-editor/index.js +115 -6
- package/dist/animation-editor/layers.d.cts +2 -0
- package/dist/animation-editor/layers.d.ts +2 -0
- package/dist/animation-editor/sequence.d.cts +33 -0
- package/dist/animation-editor/sequence.d.ts +33 -0
- package/dist/canvas/editorDocument.d.cts +49 -4
- package/dist/canvas/editorDocument.d.ts +49 -4
- package/dist/canvas/index.cjs +47 -20
- package/dist/canvas/index.js +40 -13
- package/dist/{chunk-I277KJLQ.js → chunk-G3RUKJ5C.js} +105 -1
- package/dist/{chunk-6RZVFYGJ.cjs → chunk-JWCKJNDR.cjs} +105 -1
- package/dist/effects/index.cjs +19 -11
- package/dist/effects/index.d.cts +1 -0
- package/dist/effects/index.d.ts +1 -0
- package/dist/effects/index.js +9 -1
- package/dist/effects/template-tools.d.cts +25 -0
- package/dist/effects/template-tools.d.ts +25 -0
- package/dist/effects/templates.d.cts +30 -1
- package/dist/effects/templates.d.ts +30 -1
- package/dist/video-editor/controller.d.cts +26 -0
- package/dist/video-editor/controller.d.ts +26 -0
- package/dist/video-editor/index.cjs +187 -42
- package/dist/video-editor/index.d.cts +1 -0
- package/dist/video-editor/index.d.ts +1 -0
- package/dist/video-editor/index.js +145 -0
- package/dist/video-editor/sequence.d.cts +38 -0
- package/dist/video-editor/sequence.d.ts +38 -0
- package/package.json +1 -1
- package/skills/pixerate-animation-editor/SKILL.md +10 -0
- package/skills/pixerate-animation-motion-graphics/SKILL.md +4 -0
- package/skills/pixerate-canvas/SKILL.md +2 -2
- package/skills/pixerate-video-editor/SKILL.md +11 -0
- package/skills/pixerate-video-montage/SKILL.md +2 -0
|
@@ -153,6 +153,13 @@ import {
|
|
|
153
153
|
import {
|
|
154
154
|
namedWithSuggestion
|
|
155
155
|
} from "../chunk-EUKY4KFX.js";
|
|
156
|
+
import {
|
|
157
|
+
effectLayerRole,
|
|
158
|
+
effectLayerSpan,
|
|
159
|
+
effectSequenceTiming,
|
|
160
|
+
effectTemplateTools,
|
|
161
|
+
fillEffectTemplate
|
|
162
|
+
} from "../chunk-G3RUKJ5C.js";
|
|
156
163
|
import "../chunk-EHLOCVWB.js";
|
|
157
164
|
import {
|
|
158
165
|
CAMERA_MOVE_MAX_MS,
|
|
@@ -3454,6 +3461,106 @@ var VideoPreviewEngine = class {
|
|
|
3454
3461
|
}
|
|
3455
3462
|
};
|
|
3456
3463
|
|
|
3464
|
+
// src/video-editor/sequence.ts
|
|
3465
|
+
var svgUrl = (svg) => svg.trimStart().startsWith("<") ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}` : svg;
|
|
3466
|
+
var GROUPS = ["base", "background", "subject", "overlay", "text"];
|
|
3467
|
+
function videoEditsFromSequence(project, sequence, options = {}) {
|
|
3468
|
+
const at = options.at ?? videoProjectDuration(project);
|
|
3469
|
+
const W = project.width;
|
|
3470
|
+
const H = project.height;
|
|
3471
|
+
const sx = W / sequence.stage.width;
|
|
3472
|
+
const sy = H / sequence.stage.height;
|
|
3473
|
+
const timing = effectSequenceTiming(sequence);
|
|
3474
|
+
const provider = options.provider ? { provider: options.provider } : {};
|
|
3475
|
+
const notes = [];
|
|
3476
|
+
const usedClips = new Set(allVideoClipIds(project));
|
|
3477
|
+
const usedTracks = new Set(project.tracks.map((t) => t.id));
|
|
3478
|
+
const usedAssets = new Set(project.assets.map((a) => a.id));
|
|
3479
|
+
const fresh = (used, base) => {
|
|
3480
|
+
for (let i = 1; ; i++) if (!used.has(`${base}${i}`)) return used.add(`${base}${i}`), `${base}${i}`;
|
|
3481
|
+
};
|
|
3482
|
+
const edits = [];
|
|
3483
|
+
const assets = [];
|
|
3484
|
+
const placeholders = {};
|
|
3485
|
+
const items = [];
|
|
3486
|
+
const placement = (r) => {
|
|
3487
|
+
if (!r) return {};
|
|
3488
|
+
const x = (r.x + r.width / 2) * sx - W / 2;
|
|
3489
|
+
const y = (r.y + r.height / 2) * sy - H / 2;
|
|
3490
|
+
const scale = Math.min(r.width * sx / W, r.height * sy / H);
|
|
3491
|
+
return { transform: { x: Math.round(x * 100) / 100, y: Math.round(y * 100) / 100, scale: Math.round(scale * 1e3) / 1e3 } };
|
|
3492
|
+
};
|
|
3493
|
+
const clipOf = (layer, start, duration, shot) => {
|
|
3494
|
+
const id = fresh(usedClips, "tpl-c");
|
|
3495
|
+
const common = { id, start, duration, ...layer.opacity !== void 0 && layer.opacity < 1 ? { opacity: layer.opacity } : {}, ...layer.fade ? { fadeIn: layer.fade, fadeOut: layer.fade } : {}, ...placement(layer.rect) };
|
|
3496
|
+
const effects = layer.effect ? { effects: [{ id: "fx1", effect: layer.effect, ...provider, ...layer.values ? { values: layer.values } : {} }] } : {};
|
|
3497
|
+
const s = layer.subject;
|
|
3498
|
+
if (!s) return { ...common, type: "effect", effect: layer.effect, ...provider, ...layer.values ? { values: layer.values } : {} };
|
|
3499
|
+
if ("slot" in s) {
|
|
3500
|
+
(placeholders[s.slot] ??= []).push(id);
|
|
3501
|
+
if (layer.effect) notes.push(`Shot ${shot + 1}: the "${s.slot}" slot is a grey placeholder; when you swap in the picture, apply ${layer.effect} to it again (apply_effect).`);
|
|
3502
|
+
return { ...common, type: "color", color: "#475569", name: `Slot "${s.slot}"` };
|
|
3503
|
+
}
|
|
3504
|
+
if ("text" in s) return { ...common, type: "text", text: s.text, ...s.font ? { fontFamily: s.font } : {}, ...typeof s.weight === "number" ? { fontWeight: s.weight } : {}, ...effects };
|
|
3505
|
+
const asset = fresh(usedAssets, "tpl-a");
|
|
3506
|
+
assets.push(asset);
|
|
3507
|
+
if ("video" in s) {
|
|
3508
|
+
notes.push(`Shot ${shot + 1}: a video's length is not known here, so its asset is given the clip's length (${Math.round(duration)} ms); correct it with update_video_asset if it is longer.`);
|
|
3509
|
+
edits.push({ op: "add_asset", asset: { id: asset, kind: "video", src: s.video, duration } });
|
|
3510
|
+
} else edits.push({ op: "add_asset", asset: { id: asset, kind: "image", src: "image" in s ? s.image : svgUrl(s.svg) } });
|
|
3511
|
+
return { ...common, type: "media", asset, fit: "contain", ...effects };
|
|
3512
|
+
};
|
|
3513
|
+
sequence.shots.forEach((shot, i) => {
|
|
3514
|
+
const shotStart = timing.starts[i];
|
|
3515
|
+
const shotEnd = timing.starts[i + 1] ?? timing.total;
|
|
3516
|
+
let base;
|
|
3517
|
+
shot.layers.forEach((layer, j) => {
|
|
3518
|
+
const span = effectLayerSpan(layer, i, timing.starts, timing.total);
|
|
3519
|
+
if (span.end - span.start <= 0) return void notes.push(`Shot ${i + 1}, layer ${j + 1} lasts no time, so it was left out.`);
|
|
3520
|
+
const clip = clipOf(layer, at + span.start, span.end - span.start, i);
|
|
3521
|
+
const isBase = j === 0 && !base && span.start === shotStart && span.end === shotEnd && effectLayerRole(layer) === "background" && !layer.rect;
|
|
3522
|
+
const item = { group: isBase ? "base" : effectLayerRole(layer), clip };
|
|
3523
|
+
if (isBase) base = item;
|
|
3524
|
+
items.push(item);
|
|
3525
|
+
});
|
|
3526
|
+
if (!base) {
|
|
3527
|
+
base = { group: "base", clip: { id: fresh(usedClips, "tpl-c"), type: "color", color: "#000000", name: `Shot ${i + 1}`, start: at + shotStart, duration: shotEnd - shotStart } };
|
|
3528
|
+
items.push(base);
|
|
3529
|
+
}
|
|
3530
|
+
if (i > 0 && shot.transition) {
|
|
3531
|
+
base.clip.transition = { type: "effect", duration: shot.transition.ms ?? 800, effect: { effect: shot.transition.effect, ...provider, ...shot.transition.values ? { values: shot.transition.values } : {} } };
|
|
3532
|
+
if (shot.layers.length > 1) notes.push(`Shot ${i + 1}: its transition blends the clips on the base track; the layers above it cut.`);
|
|
3533
|
+
}
|
|
3534
|
+
const words = (text, y, size, name) => items.push({ group: "text", clip: { id: fresh(usedClips, "tpl-c"), type: "text", text, name, fontSize: Math.round(size), start: at + shotStart, duration: shotEnd - shotStart, transform: { x: 0, y: Math.round(y) } } });
|
|
3535
|
+
if (shot.title) words(shot.title, 0, H * 0.09, "Title");
|
|
3536
|
+
if (shot.caption) words(shot.caption, H * 0.35, H * 0.045, "Caption");
|
|
3537
|
+
if (shot.camera?.in !== void 0 || shot.camera?.during !== void 0 || shot.camera?.framing) notes.push(`Shot ${i + 1}: its camera framing and moves were not carried over; set them with set_clip_camera_move on its clips.`);
|
|
3538
|
+
});
|
|
3539
|
+
const lanes = [];
|
|
3540
|
+
for (const group of GROUPS) {
|
|
3541
|
+
const mine = [];
|
|
3542
|
+
for (const item of items.filter((x) => x.group === group).sort((a, b) => a.clip.start - b.clip.start)) {
|
|
3543
|
+
let lane = group === "base" ? mine[0] : mine.find((l) => l.ends.every((e) => e <= item.clip.start + 1e-6));
|
|
3544
|
+
if (!lane) mine.push(lane = { group, id: fresh(usedTracks, "tpl-v"), ends: [], clips: [] });
|
|
3545
|
+
lane.ends.push(item.clip.start + item.clip.duration);
|
|
3546
|
+
lane.clips.push(item);
|
|
3547
|
+
}
|
|
3548
|
+
lanes.push(...mine);
|
|
3549
|
+
}
|
|
3550
|
+
for (const lane of lanes) edits.push({ op: "add_track", track: { id: lane.id, kind: "video", name: `Template ${lane.group}` }, at: 0 });
|
|
3551
|
+
for (const lane of lanes) for (const item of lane.clips) edits.push({ op: "add_clip", track: lane.id, clip: item.clip });
|
|
3552
|
+
return {
|
|
3553
|
+
edits,
|
|
3554
|
+
clips: lanes.flatMap((l) => l.clips.map((c) => c.clip.id)),
|
|
3555
|
+
tracks: lanes.map((l) => l.id).reverse(),
|
|
3556
|
+
assets,
|
|
3557
|
+
start: at,
|
|
3558
|
+
end: at + timing.total,
|
|
3559
|
+
placeholders,
|
|
3560
|
+
notes
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3457
3564
|
// src/video-editor/controller.ts
|
|
3458
3565
|
var VideoEditorController = class _VideoEditorController {
|
|
3459
3566
|
history;
|
|
@@ -3818,6 +3925,29 @@ var VideoEditorController = class _VideoEditorController {
|
|
|
3818
3925
|
if (place) checkVideoEffectPlace(d, place);
|
|
3819
3926
|
return d;
|
|
3820
3927
|
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Fills a provider sequence template and puts it on the timeline at `at` (default the end), on
|
|
3930
|
+
* new tracks above the others (see `videoEditsFromSequence`). Values are checked first and every
|
|
3931
|
+
* problem is refused at once. One undo step. Returns the new tracks, clips and assets, where it
|
|
3932
|
+
* starts and ends, the placeholder clips standing in for open slots, and notes.
|
|
3933
|
+
*/
|
|
3934
|
+
async fillTemplate(template, values = {}, options = {}) {
|
|
3935
|
+
const reason = this.readOnlyReason();
|
|
3936
|
+
if (reason) return this.refused(reason);
|
|
3937
|
+
if (options.at !== void 0 && !(Number.isFinite(options.at) && options.at >= 0)) return this.refused(`"at" is a time in ms, 0 or more (got ${options.at}).`);
|
|
3938
|
+
let filled;
|
|
3939
|
+
try {
|
|
3940
|
+
filled = await fillEffectTemplate(this.effects.provider, template, values, options.name ? { name: options.name } : {});
|
|
3941
|
+
} catch (error) {
|
|
3942
|
+
if (isEffectError(error) || error instanceof Error) return this.refused(error.message);
|
|
3943
|
+
throw error;
|
|
3944
|
+
}
|
|
3945
|
+
const made = videoEditsFromSequence(this.getState().project, filled.sequence, { at: options.at, provider: this.effects.provider.id });
|
|
3946
|
+
const r = this.apply(made.edits, `Template ${filled.template.name}`);
|
|
3947
|
+
if (!r.ok) return r;
|
|
3948
|
+
const { edits: _edits, notes, ...rest } = made;
|
|
3949
|
+
return { ok: true, template, ...rest, openSlots: filled.openSlots, notes: [...filled.notes, ...notes] };
|
|
3950
|
+
}
|
|
3821
3951
|
refused(message) {
|
|
3822
3952
|
return this.refuse(message);
|
|
3823
3953
|
}
|
|
@@ -5923,6 +6053,20 @@ function createVideoEffectsToolProvider(controller, options = {}) {
|
|
|
5923
6053
|
subscribe: (notify) => controller.subscribe(() => notify()),
|
|
5924
6054
|
extraTools: (ctx) => [
|
|
5925
6055
|
...videoCameraMoveTools(controller, ctx),
|
|
6056
|
+
...effectTemplateTools(
|
|
6057
|
+
{
|
|
6058
|
+
provider: () => controller.effectsProvider,
|
|
6059
|
+
fillDetails: `It goes on the timeline at "at" (default the end of the project), on new tracks above the others: each shot's bottom layer on a base track (with the shot's transition), other layers as effect, picture and text clips on tracks by role, titles and captions as text clips; an open subject slot becomes a grey placeholder clip named after it. Camera moves are not carried over. Returns { template, tracks, clips, assets, start, end (ms), placeholders (clip ids by open slot), openSlots, notes }. To fill an open slot, add the picture with add_video_asset and put it on the placeholder's track at its start with add_video_clip in overwrite mode.`,
|
|
6060
|
+
fillOptions: { at: { type: "number", minimum: 0, description: "Where on the timeline it starts, ms (default the end of the project)." } },
|
|
6061
|
+
fill: async (template, values, options2) => {
|
|
6062
|
+
const r = await controller.fillTemplate(template, values, { name: options2.name, at: options2.at });
|
|
6063
|
+
if (!r.ok) throw new Error(r.error);
|
|
6064
|
+
const { ok: _ok, ...rest } = r;
|
|
6065
|
+
return rest;
|
|
6066
|
+
}
|
|
6067
|
+
},
|
|
6068
|
+
ctx
|
|
6069
|
+
),
|
|
5926
6070
|
{
|
|
5927
6071
|
name: "add_video_effect_clip",
|
|
5928
6072
|
title: "Add an effect clip",
|
|
@@ -6684,6 +6828,7 @@ export {
|
|
|
6684
6828
|
videoEaseBezier,
|
|
6685
6829
|
videoEaseProgress,
|
|
6686
6830
|
videoEaseToAnimationEasing,
|
|
6831
|
+
videoEditsFromSequence,
|
|
6687
6832
|
videoEffectFrameTime,
|
|
6688
6833
|
videoEffectLayerKey,
|
|
6689
6834
|
videoEffectValuesAt,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An effects provider's `EffectSequence` (what a sequence template fills to) as video edits, so a
|
|
3
|
+
* template lands on the timeline as one undo step:
|
|
4
|
+
*
|
|
5
|
+
* - Each shot's bottom layer, when it lasts exactly the shot, goes on a base track; otherwise the
|
|
6
|
+
* shot gets a black colour clip there. A shot's transition goes on its base clip (a provider
|
|
7
|
+
* `effect` transition from the base clip before), so it blends the base pictures.
|
|
8
|
+
* - Other layers go on tracks above by role (background, subject, overlay), packed so clips of a
|
|
9
|
+
* track never overlap: an effect with no subject is an effect clip; a picture or SVG is a media
|
|
10
|
+
* clip on a new image asset, carrying the layer's effect as a clip effect; words are a text clip;
|
|
11
|
+
* an open slot is a grey placeholder colour clip named after it.
|
|
12
|
+
* - A layer's `rect` becomes the clip's placement; `opacity` and `fade` carry over.
|
|
13
|
+
* - Titles and captions are text clips on the top track.
|
|
14
|
+
*
|
|
15
|
+
* Camera moves are not carried over (notes say so); set them with `set_clip_camera_move`.
|
|
16
|
+
*/
|
|
17
|
+
import type { EffectSequence } from '../effects/types.cjs';
|
|
18
|
+
import type { VideoEdit } from './edit.cjs';
|
|
19
|
+
import type { VideoProject } from './types.cjs';
|
|
20
|
+
export interface VideoSequenceEdits {
|
|
21
|
+
edits: VideoEdit[];
|
|
22
|
+
/** The new clips' ids, base clips first. */
|
|
23
|
+
clips: string[];
|
|
24
|
+
/** The new tracks' ids, top to bottom. */
|
|
25
|
+
tracks: string[];
|
|
26
|
+
assets: string[];
|
|
27
|
+
/** Where the sequence starts and ends on the timeline (ms). */
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
/** Placeholder clips standing in for open subject slots, by slot. */
|
|
31
|
+
placeholders: Record<string, string[]>;
|
|
32
|
+
notes: string[];
|
|
33
|
+
}
|
|
34
|
+
/** The edits that put a sequence on a project's timeline at `at` (default its end), on new tracks above the others. */
|
|
35
|
+
export declare function videoEditsFromSequence(project: VideoProject, sequence: EffectSequence, options?: {
|
|
36
|
+
at?: number;
|
|
37
|
+
provider?: string;
|
|
38
|
+
}): VideoSequenceEdits;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An effects provider's `EffectSequence` (what a sequence template fills to) as video edits, so a
|
|
3
|
+
* template lands on the timeline as one undo step:
|
|
4
|
+
*
|
|
5
|
+
* - Each shot's bottom layer, when it lasts exactly the shot, goes on a base track; otherwise the
|
|
6
|
+
* shot gets a black colour clip there. A shot's transition goes on its base clip (a provider
|
|
7
|
+
* `effect` transition from the base clip before), so it blends the base pictures.
|
|
8
|
+
* - Other layers go on tracks above by role (background, subject, overlay), packed so clips of a
|
|
9
|
+
* track never overlap: an effect with no subject is an effect clip; a picture or SVG is a media
|
|
10
|
+
* clip on a new image asset, carrying the layer's effect as a clip effect; words are a text clip;
|
|
11
|
+
* an open slot is a grey placeholder colour clip named after it.
|
|
12
|
+
* - A layer's `rect` becomes the clip's placement; `opacity` and `fade` carry over.
|
|
13
|
+
* - Titles and captions are text clips on the top track.
|
|
14
|
+
*
|
|
15
|
+
* Camera moves are not carried over (notes say so); set them with `set_clip_camera_move`.
|
|
16
|
+
*/
|
|
17
|
+
import type { EffectSequence } from '../effects/types.js';
|
|
18
|
+
import type { VideoEdit } from './edit.js';
|
|
19
|
+
import type { VideoProject } from './types.js';
|
|
20
|
+
export interface VideoSequenceEdits {
|
|
21
|
+
edits: VideoEdit[];
|
|
22
|
+
/** The new clips' ids, base clips first. */
|
|
23
|
+
clips: string[];
|
|
24
|
+
/** The new tracks' ids, top to bottom. */
|
|
25
|
+
tracks: string[];
|
|
26
|
+
assets: string[];
|
|
27
|
+
/** Where the sequence starts and ends on the timeline (ms). */
|
|
28
|
+
start: number;
|
|
29
|
+
end: number;
|
|
30
|
+
/** Placeholder clips standing in for open subject slots, by slot. */
|
|
31
|
+
placeholders: Record<string, string[]>;
|
|
32
|
+
notes: string[];
|
|
33
|
+
}
|
|
34
|
+
/** The edits that put a sequence on a project's timeline at `at` (default its end), on new tracks above the others. */
|
|
35
|
+
export declare function videoEditsFromSequence(project: VideoProject, sequence: EffectSequence, options?: {
|
|
36
|
+
at?: number;
|
|
37
|
+
provider?: string;
|
|
38
|
+
}): VideoSequenceEdits;
|
package/package.json
CHANGED
|
@@ -172,6 +172,9 @@ refuse.
|
|
|
172
172
|
| `remove_shot_layer` | Remove a layer. |
|
|
173
173
|
| `set_shot_transition` | Set the transition into a shot from the one before (a provider effect with use `transition`, its `ms`, `preset`, `values`). Not on the first shot. Returns the transition with `start` / `end`. |
|
|
174
174
|
| `clear_shot_transition` | Remove a shot's transition, so it cuts (or the camera eases) again. |
|
|
175
|
+
| `list_templates` | List the provider's sequence templates (`query`, `tags`): ids, names, shot counts, lengths. Returns `available: false` without templates. Call before `describe_template`. |
|
|
176
|
+
| `describe_template` | Read a template's slots (type, label, default, limits), its `valuesSchema` and `exampleValues`. Call before `fill_template`. |
|
|
177
|
+
| `fill_template` | Fill a template (`template`, `values`, `name`, `mode` replace or append) and load its shots as one undo step. Every value that does not fit is refused at once. Returns the new `shots`, `layers`, `openSlots` and `notes`. |
|
|
175
178
|
|
|
176
179
|
### Save and open
|
|
177
180
|
|
|
@@ -228,6 +231,13 @@ Flair's scenes do:
|
|
|
228
231
|
`layer-not-drawn` and `transition-not-played` (no provider: effect layers are left out and
|
|
229
232
|
transitions crossfade).
|
|
230
233
|
|
|
234
|
+
A provider's **sequence templates** are whole scenes of such shots with slots to fill (a
|
|
235
|
+
product reveal, before and after, a stat countdown): `list_templates`, `describe_template`, then
|
|
236
|
+
`fill_template` with `values` (start from `exampleValues`). `mode` replace (default) makes a new
|
|
237
|
+
scene the size of the template's stage; append adds its shots after yours, scaled to the
|
|
238
|
+
artwork. Titles and captions become overlay word layers. Fill the `openSlots` it reports with
|
|
239
|
+
`update_shot_layer` (`subject`), then animate artwork over the shots as usual.
|
|
240
|
+
|
|
231
241
|
Layers are pixels: the Flair `SvgMotion` export leaves them out. Use them for backgrounds, looks
|
|
232
242
|
and titles, and keep what should animate node by node in the artwork.
|
|
233
243
|
|
|
@@ -68,6 +68,8 @@ This skill is the method. `pixerate-animation-editor` has the full tool referenc
|
|
|
68
68
|
| A set length | `fit_animation` with `seconds`, `keep` for fixed shots |
|
|
69
69
|
| The camera travels to the next part | `update_shot` camera `frame`, then `set_shot_camera_move` role `transition` (a glide; a whip pan for energy) |
|
|
70
70
|
| A slow push while a shot holds | `set_shot_camera_move` role `hold` (a Ken Burns drift), with a longer `hold` |
|
|
71
|
+
| A background, look or title behind or over a shot | `add_shot_layer` (a generator for a background, an overlay or title over it); `set_shot_transition` between shots |
|
|
72
|
+
| A whole ready-made sequence (a reveal, a countdown) | `list_templates`, `describe_template`, `fill_template` (mode append keeps your shots), then animate artwork over it |
|
|
71
73
|
|
|
72
74
|
## Recipes
|
|
73
75
|
|
|
@@ -98,6 +100,8 @@ This skill is the method. `pixerate-animation-editor` has the full tool referenc
|
|
|
98
100
|
- `edit_animation`: several edits as one undo step
|
|
99
101
|
- `fit_animation`, `set_animation_settings`, `get_timing_grid`: length, settings and cues
|
|
100
102
|
- `list_camera_moves`, `set_shot_camera_move`: provider camera moves into and during a shot
|
|
103
|
+
- `add_shot_layer`, `set_shot_transition`: provider effect layers and transitions on shots
|
|
104
|
+
- `list_templates`, `describe_template`, `fill_template`: a provider's sequence template as shots
|
|
101
105
|
- `check_animation`, `get_motion_path`, `render_animation_frame`: check and look
|
|
102
106
|
- `export_lottie`, `export_flair_motion`: deliver
|
|
103
107
|
- `undo_animation`: take back a step
|
|
@@ -168,12 +168,12 @@ the reason (for example a variant combination that already exists, or a cycle).
|
|
|
168
168
|
|
|
169
169
|
### Save and open
|
|
170
170
|
|
|
171
|
-
The host may add the shared document tools (see `pixerate-agent-tools`). A canvas document is `{ nodes, edges, scenes }`: every node (`id`, `position`, `data`, `type`, `parentId`...) and edge without selection or
|
|
171
|
+
The host may add the shared document tools (see `pixerate-agent-tools`). A canvas document is `{ nodes, edges, scenes, viewport }`: every node (`id`, `position`, `data`, `type`, `parentId`...) and edge without selection, measurement or the fields the host leaves out, plus the saved scenes and, when the host saves it, the `viewport` (`{ x, y, zoom }`) the canvas was looking at. Large inline images in node data may come back as `pixerate-asset:` references: pass them back unchanged, they are only valid in this session.
|
|
172
172
|
|
|
173
173
|
| Tool | Use |
|
|
174
174
|
| --- | --- |
|
|
175
175
|
| `get_canvas_document` | Take a copy of the whole canvas as `{ kind, formatVersion, revision, document }` before a risky rewrite, or to hand it on. To read the canvas, use `get_editor_state`: it is smaller and says more. Refuses very large canvases. |
|
|
176
|
-
| `load_canvas_document` | Put back a copy from `get_canvas_document` (its `document`, or the whole result). Checked first: duplicate ids, edges to missing nodes
|
|
176
|
+
| `load_canvas_document` | Put back a copy from `get_canvas_document` (its `document`, or the whole result). Checked first: duplicate ids, edges to missing nodes, parents that do not exist, a bad `viewport` and anything the host's own node check rejects (the error names the node and says why) are refused and nothing changes. A `viewport` in the document moves the view; leave it out to keep the current view. One undo step (`undo_canvas` brings the previous canvas back). To add or change a few nodes use `canvas_add_node` / `canvas_update_node`. |
|
|
177
177
|
| `undo_canvas` | Undo the last canvas change (one tool call, command or user gesture such as a drag). Returns `undone` (the step's name) and whether more can be undone. Fails with "Nothing to undo." when there is nothing. |
|
|
178
178
|
| `redo_canvas` | Redo the last undone canvas change. |
|
|
179
179
|
| `save_canvas_document` | Save the canvas under a `name` in the host's storage (the same name replaces that copy). Returns the `key`. Only listed when the host has storage. Save when the user asks, not after every edit. |
|
|
@@ -89,6 +89,14 @@ the pitfalls, and uses the tools below.
|
|
|
89
89
|
plays across the rest of the clip at `framing` (a Ken Burns drift on a still). The moves are
|
|
90
90
|
baked into the clip's x, y, scale and rotation keys at the project's fps, two keys at a cut,
|
|
91
91
|
so the preview and exports play them without the provider. Check with `get_video_frame`.
|
|
92
|
+
13. **Sequence templates** (a provider with templates): for a ready-made opening, reveal or
|
|
93
|
+
countdown, `list_templates`, then `describe_template` for its slots, then `fill_template`
|
|
94
|
+
with `values` (start from its `exampleValues`) and `at`. It lands on new tracks above the
|
|
95
|
+
others as one undo step: each shot's bottom layer on a base track carrying the shot's
|
|
96
|
+
transition, other layers as effect, picture and text clips by role. Open slots come back as
|
|
97
|
+
grey placeholder clips (`placeholders`); put the real picture over one with
|
|
98
|
+
`add_video_asset` and `add_video_clip` in `overwrite` mode. Camera moves are not carried
|
|
99
|
+
over: add them with `set_clip_camera_move`.
|
|
92
100
|
|
|
93
101
|
## Best practices
|
|
94
102
|
|
|
@@ -302,6 +310,9 @@ and every write tool is refused with the reason.
|
|
|
302
310
|
| `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`. |
|
|
303
311
|
| `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. |
|
|
304
312
|
| `clear_clip_camera_move` | Remove a clip's move for a `role` and re-bake the other; with none left the transform goes back to what it was. |
|
|
313
|
+
| `list_templates` | List the provider's sequence templates (`query`, `tags`): ids, names, shot counts, lengths. Returns `available: false` without templates. Call before `describe_template`. |
|
|
314
|
+
| `describe_template` | Read a template's slots (type, label, default, limits), its `valuesSchema` and `exampleValues`. Call before `fill_template`. |
|
|
315
|
+
| `fill_template` | Fill a template (`template`, `values`, `name`, `at` ms) and put it on new tracks at `at` as one undo step. Every value that does not fit is refused at once. Returns the new `tracks`, `clips`, `assets`, `start`, `end`, `placeholders` by open slot, and `notes`. |
|
|
305
316
|
|
|
306
317
|
### Save and open
|
|
307
318
|
|
|
@@ -66,6 +66,7 @@ music track and cutting to them, see `pixerate-bgm-alignment`.
|
|
|
66
66
|
| A section change | Dip to black or a held shot, then a new rhythm |
|
|
67
67
|
| Continuity of motion | Cut on action (`roll_video_edit` onto the movement) |
|
|
68
68
|
| Photos in a slideshow | Image assets, a slow push-in: with a provider that has camera moves, `set_clip_camera_move` role `hold` (a Ken Burns drift) on each photo; otherwise `set_video_keyframes` scale 1 to 1.08 |
|
|
69
|
+
| An opening or closing sequence the provider has as a template | `list_templates`, `describe_template`, then `fill_template` with `at` the start; put the real pictures over its placeholders |
|
|
69
70
|
|
|
70
71
|
## Recipes
|
|
71
72
|
|
|
@@ -97,4 +98,5 @@ start: 0, duration: <sequence>, fadeOut: 2000 }`; `update_video_clip` on the las
|
|
|
97
98
|
- `set_video_transition`: crossfades and dips
|
|
98
99
|
- `update_video_clip`, `set_video_keyframes`: fades, volume and push-ins
|
|
99
100
|
- `list_video_camera_moves`, `set_clip_camera_move`: provider camera moves (a drift across a photo)
|
|
101
|
+
- `list_templates`, `describe_template`, `fill_template`: a provider's sequence template as clips on new tracks
|
|
100
102
|
- `undo_video`: take back a step
|