@pixerate/editor 0.62.0 → 0.63.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/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/animation-editor/index.cjs +156 -33
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +64 -3
- package/dist/animation-editor/index.d.ts +64 -3
- package/dist/animation-editor/index.js +131 -8
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,6 +36,10 @@ interface MotionScene {
|
|
|
36
36
|
* per second ("on twos" at 12), for a hand-drawn look. Default: smooth.
|
|
37
37
|
*/
|
|
38
38
|
steps?: number;
|
|
39
|
+
/** A beat grid to cue shots to (`MotionShot.at.beat`): music or a voiceover's rhythm. */
|
|
40
|
+
beat?: MotionBeatGrid;
|
|
41
|
+
/** A voiceover's words with their times, to cue shots to (`MotionShot.at.word`). */
|
|
42
|
+
narration?: MotionNarration;
|
|
39
43
|
/** Wanted length in seconds `[min, max]`; `check` warns outside it. */
|
|
40
44
|
target?: [number, number];
|
|
41
45
|
/** Parameters a Flair effect made from this scene exposes (see `toFlairVectorEffect`). */
|
|
@@ -63,6 +67,31 @@ type MotionCamera = (MotionRect | {
|
|
|
63
67
|
/** Zoom at a steady rate (the size changes by the same factor each moment) instead of linearly. */
|
|
64
68
|
geometric?: boolean;
|
|
65
69
|
};
|
|
70
|
+
/** A steady beat: `bpm` beats a minute, the first at `offset` ms, `meter` beats a bar. */
|
|
71
|
+
interface MotionBeatGrid {
|
|
72
|
+
bpm: number;
|
|
73
|
+
/** When beat 0 falls, ms (default 0). */
|
|
74
|
+
offset?: number;
|
|
75
|
+
/** Beats in a bar (default 4), for reading beats as bar and beat. */
|
|
76
|
+
meter?: number;
|
|
77
|
+
}
|
|
78
|
+
/** A voiceover: its words in order, each with when it is said (ms), from a TTS or speech-to-text word timing. */
|
|
79
|
+
interface MotionNarration {
|
|
80
|
+
words: {
|
|
81
|
+
text: string;
|
|
82
|
+
start: number;
|
|
83
|
+
end?: number;
|
|
84
|
+
}[];
|
|
85
|
+
}
|
|
86
|
+
/** When a shot is cued to start: on a beat, on a narration word, or at a time. */
|
|
87
|
+
interface MotionCue {
|
|
88
|
+
/** Beat number on the scene's beat grid, from 0 (fractions for off-beats: 2.5 is the "and" of beat 3). */
|
|
89
|
+
beat?: number;
|
|
90
|
+
/** Index of a word in the scene's narration, from 0. */
|
|
91
|
+
word?: number;
|
|
92
|
+
/** A time in ms. */
|
|
93
|
+
ms?: number;
|
|
94
|
+
}
|
|
66
95
|
/** One beat of the animation. */
|
|
67
96
|
interface MotionShot {
|
|
68
97
|
/** Stable id (unique in the scene), e.g. `intro`. */
|
|
@@ -71,6 +100,12 @@ interface MotionShot {
|
|
|
71
100
|
label?: string;
|
|
72
101
|
/** Omit to keep the current framing. */
|
|
73
102
|
camera?: MotionCamera;
|
|
103
|
+
/**
|
|
104
|
+
* Start on a cue: a beat, a narration word or a time. The shot before holds until then; a
|
|
105
|
+
* cue that comes before the shot before has ended is missed (`check` says by how much).
|
|
106
|
+
* Give one of `beat`, `word` or `ms`.
|
|
107
|
+
*/
|
|
108
|
+
at?: MotionCue;
|
|
74
109
|
/** Commands, played one after another (unless a command says `with: "previous"`). */
|
|
75
110
|
run: MotionCommand[];
|
|
76
111
|
/** Milliseconds to hold after the commands finish (default 600). */
|
|
@@ -497,6 +532,14 @@ declare const MOTION_ISSUE_CODES: {
|
|
|
497
532
|
readonly severity: "warning";
|
|
498
533
|
readonly wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end.";
|
|
499
534
|
};
|
|
535
|
+
readonly 'unknown-cue': {
|
|
536
|
+
readonly severity: "error";
|
|
537
|
+
readonly wording: "A shot is cued to a beat with no beat grid, or to a word the narration does not have.";
|
|
538
|
+
};
|
|
539
|
+
readonly 'cue-missed': {
|
|
540
|
+
readonly severity: "warning";
|
|
541
|
+
readonly wording: "A shot starts after its cue, because the shot before runs past it.";
|
|
542
|
+
};
|
|
500
543
|
readonly 'scene-too-long': {
|
|
501
544
|
readonly severity: "warning";
|
|
502
545
|
readonly wording: "The scene runs past its target length.";
|
|
@@ -631,6 +674,10 @@ interface MotionShotTiming {
|
|
|
631
674
|
/** When the last command ends (the hold follows). */
|
|
632
675
|
commandsEnd: number;
|
|
633
676
|
end: number;
|
|
677
|
+
/** The time the shot is cued to start (`at`), when it has a cue that resolves. */
|
|
678
|
+
cue?: number;
|
|
679
|
+
/** How late the shot starts after its cue (ms), when the shot before ran past it. */
|
|
680
|
+
missedBy?: number;
|
|
634
681
|
}
|
|
635
682
|
interface MotionCommandTiming {
|
|
636
683
|
shot: string;
|
|
@@ -737,6 +784,16 @@ declare function resolveMotionCamera(camera: MotionCamera, scene: MotionScene):
|
|
|
737
784
|
declare function motionPathLength(node: VectorNode, parentWorld?: AffineMatrix): number;
|
|
738
785
|
/** How much a shot's `speed` stretches its time (1 / speed; 1 when unset or not above 0). */
|
|
739
786
|
declare function motionShotTimeScale(shot: MotionShot): number;
|
|
787
|
+
/** When beat `beat` falls on a grid, ms: `offset + beat · 60000 / bpm`. */
|
|
788
|
+
declare function motionBeatTime(grid: MotionBeatGrid, beat: number): number;
|
|
789
|
+
/** The beat (fractional) at `ms` on a grid, and its bar and beat in the bar (both from 1). */
|
|
790
|
+
declare function motionBeatAt(grid: MotionBeatGrid, ms: number): {
|
|
791
|
+
beat: number;
|
|
792
|
+
bar: number;
|
|
793
|
+
beatInBar: number;
|
|
794
|
+
};
|
|
795
|
+
/** The time a cue names, ms, or undefined when it names a beat with no grid or a word the narration lacks. */
|
|
796
|
+
declare function motionCueTime(scene: Pick<MotionScene, 'beat' | 'narration'>, cue: MotionCue): number | undefined;
|
|
740
797
|
/** How many times a command plays (1 for `enter`, `exit` and `wait`, which do not repeat). */
|
|
741
798
|
declare function motionCommandRepeat(command: MotionCommand): number;
|
|
742
799
|
/** How long one target of a command takes (ms), every repeat included. */
|
|
@@ -875,6 +932,8 @@ type MotionEdit = {
|
|
|
875
932
|
loop?: MotionScene['loop'];
|
|
876
933
|
fps?: number;
|
|
877
934
|
steps?: number | null;
|
|
935
|
+
beat?: MotionScene['beat'] | null;
|
|
936
|
+
narration?: MotionScene['narration'] | null;
|
|
878
937
|
target?: [number, number] | null;
|
|
879
938
|
} | {
|
|
880
939
|
op: 'insert_shots';
|
|
@@ -884,7 +943,7 @@ type MotionEdit = {
|
|
|
884
943
|
op: 'update_shot';
|
|
885
944
|
shot: string;
|
|
886
945
|
set?: Partial<MotionShot>;
|
|
887
|
-
remove?: ('label' | 'camera' | 'hold' | 'speed')[];
|
|
946
|
+
remove?: ('label' | 'camera' | 'hold' | 'speed' | 'at')[];
|
|
888
947
|
} | {
|
|
889
948
|
op: 'delete_shots';
|
|
890
949
|
shots: string[];
|
|
@@ -1683,6 +1742,8 @@ declare function describeMotionShots(scene: MotionScene): {
|
|
|
1683
1742
|
note?: string;
|
|
1684
1743
|
index: number;
|
|
1685
1744
|
})[];
|
|
1745
|
+
missedBy?: number | undefined;
|
|
1746
|
+
cue?: number | undefined;
|
|
1686
1747
|
hold?: number | undefined;
|
|
1687
1748
|
camera?: MotionCamera | undefined;
|
|
1688
1749
|
start: number;
|
|
@@ -1703,6 +1764,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
|
|
|
1703
1764
|
* A short authoring guide for a model writing vector animations with the animation editor's
|
|
1704
1765
|
* tools. Static, so it caches well as part of a system prompt.
|
|
1705
1766
|
*/
|
|
1706
|
-
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1767
|
+
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- To time shots to music or a voiceover: set a \"beat\" grid or \"narration\" word timings with set_animation_settings, read them with get_timing_grid, and give a shot \"at\": { \"beat\": n } or { \"word\": i }. The shot before holds until the cue; check_animation warns when a cue is missed.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1707
1768
|
|
|
1708
|
-
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
1769
|
+
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
@@ -36,6 +36,10 @@ interface MotionScene {
|
|
|
36
36
|
* per second ("on twos" at 12), for a hand-drawn look. Default: smooth.
|
|
37
37
|
*/
|
|
38
38
|
steps?: number;
|
|
39
|
+
/** A beat grid to cue shots to (`MotionShot.at.beat`): music or a voiceover's rhythm. */
|
|
40
|
+
beat?: MotionBeatGrid;
|
|
41
|
+
/** A voiceover's words with their times, to cue shots to (`MotionShot.at.word`). */
|
|
42
|
+
narration?: MotionNarration;
|
|
39
43
|
/** Wanted length in seconds `[min, max]`; `check` warns outside it. */
|
|
40
44
|
target?: [number, number];
|
|
41
45
|
/** Parameters a Flair effect made from this scene exposes (see `toFlairVectorEffect`). */
|
|
@@ -63,6 +67,31 @@ type MotionCamera = (MotionRect | {
|
|
|
63
67
|
/** Zoom at a steady rate (the size changes by the same factor each moment) instead of linearly. */
|
|
64
68
|
geometric?: boolean;
|
|
65
69
|
};
|
|
70
|
+
/** A steady beat: `bpm` beats a minute, the first at `offset` ms, `meter` beats a bar. */
|
|
71
|
+
interface MotionBeatGrid {
|
|
72
|
+
bpm: number;
|
|
73
|
+
/** When beat 0 falls, ms (default 0). */
|
|
74
|
+
offset?: number;
|
|
75
|
+
/** Beats in a bar (default 4), for reading beats as bar and beat. */
|
|
76
|
+
meter?: number;
|
|
77
|
+
}
|
|
78
|
+
/** A voiceover: its words in order, each with when it is said (ms), from a TTS or speech-to-text word timing. */
|
|
79
|
+
interface MotionNarration {
|
|
80
|
+
words: {
|
|
81
|
+
text: string;
|
|
82
|
+
start: number;
|
|
83
|
+
end?: number;
|
|
84
|
+
}[];
|
|
85
|
+
}
|
|
86
|
+
/** When a shot is cued to start: on a beat, on a narration word, or at a time. */
|
|
87
|
+
interface MotionCue {
|
|
88
|
+
/** Beat number on the scene's beat grid, from 0 (fractions for off-beats: 2.5 is the "and" of beat 3). */
|
|
89
|
+
beat?: number;
|
|
90
|
+
/** Index of a word in the scene's narration, from 0. */
|
|
91
|
+
word?: number;
|
|
92
|
+
/** A time in ms. */
|
|
93
|
+
ms?: number;
|
|
94
|
+
}
|
|
66
95
|
/** One beat of the animation. */
|
|
67
96
|
interface MotionShot {
|
|
68
97
|
/** Stable id (unique in the scene), e.g. `intro`. */
|
|
@@ -71,6 +100,12 @@ interface MotionShot {
|
|
|
71
100
|
label?: string;
|
|
72
101
|
/** Omit to keep the current framing. */
|
|
73
102
|
camera?: MotionCamera;
|
|
103
|
+
/**
|
|
104
|
+
* Start on a cue: a beat, a narration word or a time. The shot before holds until then; a
|
|
105
|
+
* cue that comes before the shot before has ended is missed (`check` says by how much).
|
|
106
|
+
* Give one of `beat`, `word` or `ms`.
|
|
107
|
+
*/
|
|
108
|
+
at?: MotionCue;
|
|
74
109
|
/** Commands, played one after another (unless a command says `with: "previous"`). */
|
|
75
110
|
run: MotionCommand[];
|
|
76
111
|
/** Milliseconds to hold after the commands finish (default 600). */
|
|
@@ -497,6 +532,14 @@ declare const MOTION_ISSUE_CODES: {
|
|
|
497
532
|
readonly severity: "warning";
|
|
498
533
|
readonly wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end.";
|
|
499
534
|
};
|
|
535
|
+
readonly 'unknown-cue': {
|
|
536
|
+
readonly severity: "error";
|
|
537
|
+
readonly wording: "A shot is cued to a beat with no beat grid, or to a word the narration does not have.";
|
|
538
|
+
};
|
|
539
|
+
readonly 'cue-missed': {
|
|
540
|
+
readonly severity: "warning";
|
|
541
|
+
readonly wording: "A shot starts after its cue, because the shot before runs past it.";
|
|
542
|
+
};
|
|
500
543
|
readonly 'scene-too-long': {
|
|
501
544
|
readonly severity: "warning";
|
|
502
545
|
readonly wording: "The scene runs past its target length.";
|
|
@@ -631,6 +674,10 @@ interface MotionShotTiming {
|
|
|
631
674
|
/** When the last command ends (the hold follows). */
|
|
632
675
|
commandsEnd: number;
|
|
633
676
|
end: number;
|
|
677
|
+
/** The time the shot is cued to start (`at`), when it has a cue that resolves. */
|
|
678
|
+
cue?: number;
|
|
679
|
+
/** How late the shot starts after its cue (ms), when the shot before ran past it. */
|
|
680
|
+
missedBy?: number;
|
|
634
681
|
}
|
|
635
682
|
interface MotionCommandTiming {
|
|
636
683
|
shot: string;
|
|
@@ -737,6 +784,16 @@ declare function resolveMotionCamera(camera: MotionCamera, scene: MotionScene):
|
|
|
737
784
|
declare function motionPathLength(node: VectorNode, parentWorld?: AffineMatrix): number;
|
|
738
785
|
/** How much a shot's `speed` stretches its time (1 / speed; 1 when unset or not above 0). */
|
|
739
786
|
declare function motionShotTimeScale(shot: MotionShot): number;
|
|
787
|
+
/** When beat `beat` falls on a grid, ms: `offset + beat · 60000 / bpm`. */
|
|
788
|
+
declare function motionBeatTime(grid: MotionBeatGrid, beat: number): number;
|
|
789
|
+
/** The beat (fractional) at `ms` on a grid, and its bar and beat in the bar (both from 1). */
|
|
790
|
+
declare function motionBeatAt(grid: MotionBeatGrid, ms: number): {
|
|
791
|
+
beat: number;
|
|
792
|
+
bar: number;
|
|
793
|
+
beatInBar: number;
|
|
794
|
+
};
|
|
795
|
+
/** The time a cue names, ms, or undefined when it names a beat with no grid or a word the narration lacks. */
|
|
796
|
+
declare function motionCueTime(scene: Pick<MotionScene, 'beat' | 'narration'>, cue: MotionCue): number | undefined;
|
|
740
797
|
/** How many times a command plays (1 for `enter`, `exit` and `wait`, which do not repeat). */
|
|
741
798
|
declare function motionCommandRepeat(command: MotionCommand): number;
|
|
742
799
|
/** How long one target of a command takes (ms), every repeat included. */
|
|
@@ -875,6 +932,8 @@ type MotionEdit = {
|
|
|
875
932
|
loop?: MotionScene['loop'];
|
|
876
933
|
fps?: number;
|
|
877
934
|
steps?: number | null;
|
|
935
|
+
beat?: MotionScene['beat'] | null;
|
|
936
|
+
narration?: MotionScene['narration'] | null;
|
|
878
937
|
target?: [number, number] | null;
|
|
879
938
|
} | {
|
|
880
939
|
op: 'insert_shots';
|
|
@@ -884,7 +943,7 @@ type MotionEdit = {
|
|
|
884
943
|
op: 'update_shot';
|
|
885
944
|
shot: string;
|
|
886
945
|
set?: Partial<MotionShot>;
|
|
887
|
-
remove?: ('label' | 'camera' | 'hold' | 'speed')[];
|
|
946
|
+
remove?: ('label' | 'camera' | 'hold' | 'speed' | 'at')[];
|
|
888
947
|
} | {
|
|
889
948
|
op: 'delete_shots';
|
|
890
949
|
shots: string[];
|
|
@@ -1683,6 +1742,8 @@ declare function describeMotionShots(scene: MotionScene): {
|
|
|
1683
1742
|
note?: string;
|
|
1684
1743
|
index: number;
|
|
1685
1744
|
})[];
|
|
1745
|
+
missedBy?: number | undefined;
|
|
1746
|
+
cue?: number | undefined;
|
|
1686
1747
|
hold?: number | undefined;
|
|
1687
1748
|
camera?: MotionCamera | undefined;
|
|
1688
1749
|
start: number;
|
|
@@ -1703,6 +1764,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
|
|
|
1703
1764
|
* A short authoring guide for a model writing vector animations with the animation editor's
|
|
1704
1765
|
* tools. Static, so it caches well as part of a system prompt.
|
|
1705
1766
|
*/
|
|
1706
|
-
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1767
|
+
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- To time shots to music or a voiceover: set a \"beat\" grid or \"narration\" word timings with set_animation_settings, read them with get_timing_grid, and give a shot \"at\": { \"beat\": n } or { \"word\": i }. The shot before holds until the cue; check_animation warns when a cue is missed.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1707
1768
|
|
|
1708
|
-
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
1769
|
+
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
@@ -149,6 +149,8 @@ var MOTION_ISSUE_CODES = {
|
|
|
149
149
|
"out-of-frame": { severity: "warning", wording: "A node a command animates ends up outside the camera." },
|
|
150
150
|
"too-fast": { severity: "warning", wording: "A change is too quick to follow." },
|
|
151
151
|
"spring-cut-short": { severity: "warning", wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end." },
|
|
152
|
+
"unknown-cue": { severity: "error", wording: "A shot is cued to a beat with no beat grid, or to a word the narration does not have." },
|
|
153
|
+
"cue-missed": { severity: "warning", wording: "A shot starts after its cue, because the shot before runs past it." },
|
|
152
154
|
"scene-too-long": { severity: "warning", wording: "The scene runs past its target length." },
|
|
153
155
|
"scene-too-short": { severity: "warning", wording: "The scene ends before its target length." },
|
|
154
156
|
"unknown-control-color": { severity: "warning", wording: "A control stands for a colour the scene never uses." },
|
|
@@ -206,8 +208,8 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
206
208
|
this.problems = problems;
|
|
207
209
|
}
|
|
208
210
|
};
|
|
209
|
-
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "target", "controls", "presets"];
|
|
210
|
-
var SHOT_KEYS = ["id", "label", "camera", "run", "hold", "speed"];
|
|
211
|
+
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
|
|
212
|
+
var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
|
|
211
213
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
212
214
|
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
|
|
213
215
|
var COMMAND_KEYS = {
|
|
@@ -361,6 +363,16 @@ function shotShapeProblems(shot, where) {
|
|
|
361
363
|
if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
|
|
362
364
|
if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
|
|
363
365
|
if (shot.speed !== void 0 && !(isNum(shot.speed) && shot.speed >= 0.05 && shot.speed <= 20)) problems.push(`${name}: "speed" must be a number from 0.05 to 20 (2 plays it twice as fast).`);
|
|
366
|
+
if (shot.at !== void 0) {
|
|
367
|
+
const at = shot.at;
|
|
368
|
+
const given = isObject(at) ? ["beat", "word", "ms"].filter((k) => at[k] !== void 0) : [];
|
|
369
|
+
if (!isObject(at) || given.length !== 1) problems.push(`${name}: "at" must give one of { "beat": number }, { "word": index } or { "ms": number }.`);
|
|
370
|
+
else {
|
|
371
|
+
unknownKeys(at, ["beat", "word", "ms"], `${name} at`, problems);
|
|
372
|
+
const k = given[0];
|
|
373
|
+
if (!isNum(at[k]) || at[k] < 0 || k === "word" && !Number.isInteger(at[k])) problems.push(`${name}: "at.${k}" must be ${k === "word" ? "a word index \u2265 0" : "a number \u2265 0"}.`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
364
376
|
if (shot.camera !== void 0) {
|
|
365
377
|
const c = shot.camera;
|
|
366
378
|
if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
|
|
@@ -391,6 +403,16 @@ function motionSceneShapeProblems(input) {
|
|
|
391
403
|
problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
|
|
392
404
|
if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
|
|
393
405
|
if (input.fps !== void 0 && !(isNum(input.fps) && input.fps > 0 && input.fps <= 120)) problems.push('Scene: "fps" must be a number from 1 to 120.');
|
|
406
|
+
if (input.beat !== void 0) {
|
|
407
|
+
const b = input.beat;
|
|
408
|
+
if (!isObject(b) || !(isNum(b.bpm) && b.bpm >= 1 && b.bpm <= 1e3) || b.offset !== void 0 && !isNum(b.offset) || b.meter !== void 0 && !(Number.isInteger(b.meter) && b.meter >= 1))
|
|
409
|
+
problems.push('Scene: "beat" must be { "bpm": 1\u20131000, "offset"?: ms, "meter"?: beats a bar }.');
|
|
410
|
+
}
|
|
411
|
+
if (input.narration !== void 0) {
|
|
412
|
+
const n = input.narration;
|
|
413
|
+
if (!isObject(n) || !Array.isArray(n.words) || !n.words.every((w, i, all) => isObject(w) && typeof w.text === "string" && isNum(w.start) && w.start >= 0 && (w.end === void 0 || isNum(w.end) && w.end >= w.start) && (i === 0 || w.start >= (all[i - 1].start ?? 0))))
|
|
414
|
+
problems.push('Scene: "narration" must be { "words": [{ "text", "start": ms, "end"?: ms }, \u2026] } in time order.');
|
|
415
|
+
}
|
|
394
416
|
if (input.steps !== void 0 && !(isNum(input.steps) && input.steps > 0 && input.steps <= 120)) problems.push('Scene: "steps" must be frames per second from above 0 to 120.');
|
|
395
417
|
if (input.target !== void 0 && !(Array.isArray(input.target) && input.target.length === 2 && input.target.every(isNum) && input.target[0] <= input.target[1]))
|
|
396
418
|
problems.push('Scene: "target" must be [min, max] seconds.');
|
|
@@ -699,6 +721,21 @@ function motionPathLength(node, parentWorld = IDENTITY_MATRIX) {
|
|
|
699
721
|
function motionShotTimeScale(shot) {
|
|
700
722
|
return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
|
|
701
723
|
}
|
|
724
|
+
function motionBeatTime(grid, beat) {
|
|
725
|
+
return (grid.offset ?? 0) + beat * 6e4 / grid.bpm;
|
|
726
|
+
}
|
|
727
|
+
function motionBeatAt(grid, ms) {
|
|
728
|
+
const beat = (ms - (grid.offset ?? 0)) * grid.bpm / 6e4;
|
|
729
|
+
const meter = Math.max(1, Math.round(grid.meter ?? 4));
|
|
730
|
+
const whole = Math.floor(beat + 1e-9);
|
|
731
|
+
return { beat, bar: Math.floor(whole / meter) + 1, beatInBar: (whole % meter + meter) % meter + 1 };
|
|
732
|
+
}
|
|
733
|
+
function motionCueTime(scene, cue) {
|
|
734
|
+
if (typeof cue.ms === "number") return Math.max(0, cue.ms);
|
|
735
|
+
if (typeof cue.beat === "number") return scene.beat && scene.beat.bpm > 0 ? Math.max(0, motionBeatTime(scene.beat, cue.beat)) : void 0;
|
|
736
|
+
if (typeof cue.word === "number") return scene.narration?.words[cue.word]?.start;
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
702
739
|
function motionCommandRepeat(command2) {
|
|
703
740
|
if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
|
|
704
741
|
const n = command2.repeat ?? 1;
|
|
@@ -736,6 +773,12 @@ function compileMotionScene(scene) {
|
|
|
736
773
|
const cameraMoves = [];
|
|
737
774
|
let t = 0;
|
|
738
775
|
scene.shots.forEach((shot, index) => {
|
|
776
|
+
const cue = shot.at ? motionCueTime(scene, shot.at) : void 0;
|
|
777
|
+
if (cue !== void 0 && cue > t) {
|
|
778
|
+
if (shots.length) shots[shots.length - 1].end = cue;
|
|
779
|
+
t = cue;
|
|
780
|
+
}
|
|
781
|
+
const missedBy = cue !== void 0 && t - cue > 0.5 ? t - cue : void 0;
|
|
739
782
|
const start = t;
|
|
740
783
|
let cursor = start;
|
|
741
784
|
const f = motionShotTimeScale(shot);
|
|
@@ -762,7 +805,7 @@ function compileMotionScene(scene) {
|
|
|
762
805
|
runEnd = Math.max(runEnd, end2);
|
|
763
806
|
});
|
|
764
807
|
const end = runEnd + (shot.hold ?? MOTION_DEFAULT_HOLD) * f;
|
|
765
|
-
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end });
|
|
808
|
+
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end, ...cue !== void 0 ? { cue } : {}, ...missedBy !== void 0 ? { missedBy } : {} });
|
|
766
809
|
t = end;
|
|
767
810
|
});
|
|
768
811
|
const tracks = /* @__PURE__ */ new Map();
|
|
@@ -1366,6 +1409,17 @@ function checkMotionScene(scene) {
|
|
|
1366
1409
|
}
|
|
1367
1410
|
});
|
|
1368
1411
|
});
|
|
1412
|
+
scene.shots.forEach((shot, i) => {
|
|
1413
|
+
if (!shot.at) return;
|
|
1414
|
+
const timing = timeline.shots[i];
|
|
1415
|
+
if (timing.cue === void 0) {
|
|
1416
|
+
const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${scene.narration?.words.length ?? 0} words (indexes from 0)`;
|
|
1417
|
+
issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
|
|
1418
|
+
} else if (timing.missedBy !== void 0) {
|
|
1419
|
+
const late = Math.round(timing.missedBy);
|
|
1420
|
+
issues.push(motionIssue("cue-missed", `Shot "${shot.id}" is cued at ${Math.round(timing.cue)} ms but starts ${late} ms late, because the shot before runs until ${Math.round(timing.start)} ms; shorten that shot (its hold, durations or speed) or cue this one later.`, { shot: shot.id, measured: late, needed: 0, unit: "ms" }));
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1369
1423
|
for (const [key, track] of timeline.tracks) {
|
|
1370
1424
|
if (key.endsWith("\0add")) continue;
|
|
1371
1425
|
for (let i = 1; i < track.length; i++) {
|
|
@@ -1467,6 +1521,14 @@ function applyOne(scene, edit) {
|
|
|
1467
1521
|
if (!(typeof edit.steps === "number" && edit.steps > 0 && edit.steps <= 120)) fail('"steps" must be frames per second from above 0 to 120 (12 is "on twos" at 24 fps), or null for smooth.');
|
|
1468
1522
|
next.steps = edit.steps;
|
|
1469
1523
|
}
|
|
1524
|
+
for (const key of ["beat", "narration"]) {
|
|
1525
|
+
if (edit[key] === null) delete next[key];
|
|
1526
|
+
else if (edit[key] !== void 0) next[key] = edit[key];
|
|
1527
|
+
}
|
|
1528
|
+
{
|
|
1529
|
+
const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"/.test(p));
|
|
1530
|
+
if (problems.length) fail(problems.join(" "));
|
|
1531
|
+
}
|
|
1470
1532
|
if (edit.target === null) delete next.target;
|
|
1471
1533
|
else if (edit.target !== void 0) next.target = edit.target;
|
|
1472
1534
|
return next;
|
|
@@ -2495,7 +2557,12 @@ var SHOT_FIELDS = {
|
|
|
2495
2557
|
camera: CAMERA,
|
|
2496
2558
|
run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
|
|
2497
2559
|
hold: MS("Hold after the commands (default 600)"),
|
|
2498
|
-
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." }
|
|
2560
|
+
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." },
|
|
2561
|
+
at: object(
|
|
2562
|
+
{ beat: { type: "number", minimum: 0, description: `Beat number on the scene's beat grid, from 0 (2.5 is the "and" of beat 3).` }, word: { type: "integer", minimum: 0, description: "Index of a narration word, from 0." }, ms: { type: "number", minimum: 0, description: "A time in ms." } },
|
|
2563
|
+
[],
|
|
2564
|
+
"Start this shot on a cue (give one of beat, word or ms); the shot before holds until then. get_timing_grid lists beats and words."
|
|
2565
|
+
)
|
|
2499
2566
|
};
|
|
2500
2567
|
var SHOT = object({ id: { type: "string", pattern: "^[A-Za-z0-9_][\\w.-]*$", description: 'Unique shot id, e.g. "intro".' }, ...SHOT_FIELDS }, ["id", "run"], "A shot.");
|
|
2501
2568
|
var CONTROL = object(
|
|
@@ -2515,6 +2582,20 @@ var SETTINGS = {
|
|
|
2515
2582
|
loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
|
|
2516
2583
|
fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
|
|
2517
2584
|
steps: { anyOf: [{ type: "number", exclusiveMinimum: 0, maximum: 120 }, { type: "null" }], description: 'Stepped time for every command without its own: changes hold on a grid of this many frames per second (12 = "on twos" at 24 fps) for a hand-drawn look; null makes it smooth again (default).' },
|
|
2585
|
+
beat: {
|
|
2586
|
+
anyOf: [
|
|
2587
|
+
object({ bpm: { type: "number", minimum: 1, maximum: 1e3, description: "Beats a minute." }, offset: { type: "number", description: "When beat 0 falls, ms (default 0)." }, meter: { type: "integer", minimum: 1, description: "Beats a bar (default 4)." } }, ["bpm"]),
|
|
2588
|
+
{ type: "null" }
|
|
2589
|
+
],
|
|
2590
|
+
description: `A beat grid (music or a voiceover's rhythm) that shots can be cued to with "at": { "beat": n }; null clears.`
|
|
2591
|
+
},
|
|
2592
|
+
narration: {
|
|
2593
|
+
anyOf: [
|
|
2594
|
+
object({ words: { type: "array", items: object({ text: { type: "string", description: "The word." }, start: { type: "number", minimum: 0, description: "When it is said, ms." }, end: { type: "number", minimum: 0, description: "When it ends, ms." } }, ["text", "start"]), description: "Words in time order, from a TTS or speech-to-text word timing." } }, ["words"]),
|
|
2595
|
+
{ type: "null" }
|
|
2596
|
+
],
|
|
2597
|
+
description: `A voiceover's word timings that shots can be cued to with "at": { "word": index }; null clears.`
|
|
2598
|
+
},
|
|
2518
2599
|
target: { anyOf: [{ type: "array", items: { type: "number", minimum: 0 }, minItems: 2, maxItems: 2 }, { type: "null" }], description: "Wanted length [min, max] in seconds; null clears." }
|
|
2519
2600
|
};
|
|
2520
2601
|
var EDIT_OP = {
|
|
@@ -2522,7 +2603,7 @@ var EDIT_OP = {
|
|
|
2522
2603
|
oneOf: [
|
|
2523
2604
|
object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
|
|
2524
2605
|
object({ op: { const: "insert_shots", description: "Like insert_shots." }, shots: { type: "array", items: SHOT, minItems: 1, description: "Shots to add." }, at: { type: "integer", minimum: 0, description: "Position (default: the end)." } }, ["op", "shots"]),
|
|
2525
|
-
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2606
|
+
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2526
2607
|
object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
|
|
2527
2608
|
object({ op: { const: "move_shot", description: "Like move_shot." }, shot: SHOT_ID, to: { type: "integer", minimum: 0, description: "New position (0 = first)." } }, ["op", "shot", "to"]),
|
|
2528
2609
|
object({ op: { const: "add_commands", description: "Like add_commands." }, shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the shot's run (default: the end)." } }, ["op", "shot", "commands"]),
|
|
@@ -2567,6 +2648,8 @@ function describeMotionShots(scene) {
|
|
|
2567
2648
|
end: t.end,
|
|
2568
2649
|
...shot.camera ? { camera: shot.camera } : {},
|
|
2569
2650
|
...shot.hold !== void 0 ? { hold: shot.hold } : {},
|
|
2651
|
+
...t.cue !== void 0 ? { cue: Math.round(t.cue) } : {},
|
|
2652
|
+
...t.missedBy !== void 0 ? { missedBy: Math.round(t.missedBy) } : {},
|
|
2570
2653
|
commands: shot.run.map((c, index) => {
|
|
2571
2654
|
const ct = timeline.commands.find((x) => x.shotIndex === i && x.command === index);
|
|
2572
2655
|
const primitive = motionFlairPrimitive(c);
|
|
@@ -2695,6 +2778,42 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2695
2778
|
const CURVES_DESCRIPTION = "Write cubic-bezier easings (ease, ease-in, ease-out, ease-in-out, back eases, custom curves) as one change with ease: [x1, y1, x2, y2] (default true). Needs Flair 0.36 or later; false samples them into linear steps for older Flair.";
|
|
2696
2779
|
const FLAIR_DESCRIPTION = "The Flair version to export for (default 0.39): springs and stepped time need 0.39, behaviours 0.38, cubic-bezier curves 0.36; older versions get those sampled into linear steps.";
|
|
2697
2780
|
const BEHAVIORS_DESCRIPTION = "Write pulses, shakes, teeters, waves, wiggles and colour pulses as one change with a Flair behavior, so they play exactly (default true; needs Flair 0.38 or later). False samples them into linear steps for older Flair.";
|
|
2781
|
+
const getTimingGrid = {
|
|
2782
|
+
name: "get_timing_grid",
|
|
2783
|
+
title: "Read the beat grid and narration",
|
|
2784
|
+
description: 'Returns the beats (number, time in ms, bar and beat in the bar) and narration words (index, text, start, end) between two times, to cue shots to with "at". Also says which shot plays at each. Without a beat grid or narration, that part is empty and says how to add one.',
|
|
2785
|
+
inputSchema: object({
|
|
2786
|
+
from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
|
|
2787
|
+
to: { type: "number", minimum: 0, description: "Up to this time, ms (default: the end of the animation, or of the narration if later)." },
|
|
2788
|
+
limit: { type: "integer", minimum: 1, maximum: 1e3, description: "At most this many beats and words each (default 200)." }
|
|
2789
|
+
}),
|
|
2790
|
+
annotations: { title: "Read the beat grid and narration", ...READ },
|
|
2791
|
+
handler: ({ from = 0, to, limit = 200 }) => {
|
|
2792
|
+
const s = scene();
|
|
2793
|
+
const timeline = compileMotionScene(s);
|
|
2794
|
+
const words = s.narration?.words ?? [];
|
|
2795
|
+
const end = to ?? Math.max(timeline.duration, words.length ? words[words.length - 1].end ?? words[words.length - 1].start : 0);
|
|
2796
|
+
const shotAt = (ms) => timeline.shots.find((x) => ms >= x.start && ms < x.end)?.id;
|
|
2797
|
+
const beats = [];
|
|
2798
|
+
if (s.beat && s.beat.bpm > 0) {
|
|
2799
|
+
const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
|
|
2800
|
+
for (let b = first; beats.length < limit; b++) {
|
|
2801
|
+
const ms = motionBeatTime(s.beat, b);
|
|
2802
|
+
if (ms > end) break;
|
|
2803
|
+
const at = motionBeatAt(s.beat, ms);
|
|
2804
|
+
const shot = shotAt(ms);
|
|
2805
|
+
beats.push({ beat: b, ms: Math.round(ms), bar: at.bar, beatInBar: at.beatInBar, ...shot ? { shot } : {} });
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
const listed = words.map((w, index) => ({ index, text: w.text, start: w.start, ...w.end !== void 0 ? { end: w.end } : {}, ...shotAt(w.start) ? { shot: shotAt(w.start) } : {} })).filter((w) => w.start >= from && w.start <= end).slice(0, limit);
|
|
2809
|
+
return {
|
|
2810
|
+
from,
|
|
2811
|
+
to: Math.round(end),
|
|
2812
|
+
...s.beat ? { grid: s.beat, beats } : { beats: [], hint: 'No beat grid: set_animation_settings with { "beat": { "bpm": 120 } }.' },
|
|
2813
|
+
...words.length ? { words: listed } : { words: [], narrationHint: 'No narration: set_animation_settings with { "narration": { "words": [{ "text", "start" }, \u2026] } }.' }
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2698
2817
|
const exportFlairMotion = {
|
|
2699
2818
|
name: "export_flair_motion",
|
|
2700
2819
|
title: "Export as Flair vector motion",
|
|
@@ -2806,7 +2925,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2806
2925
|
{
|
|
2807
2926
|
shot: SHOT_ID,
|
|
2808
2927
|
set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."),
|
|
2809
|
-
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." }
|
|
2928
|
+
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." }
|
|
2810
2929
|
},
|
|
2811
2930
|
["shot"]
|
|
2812
2931
|
),
|
|
@@ -2872,7 +2991,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2872
2991
|
const setSettings = {
|
|
2873
2992
|
name: "set_animation_settings",
|
|
2874
2993
|
title: "Change animation settings",
|
|
2875
|
-
description: "Sets the name, description, loop mode, export frame rate, stepped time or target length. Returns the timing.",
|
|
2994
|
+
description: "Sets the name, description, loop mode, export frame rate, stepped time, beat grid, narration word timings or target length. Returns the timing.",
|
|
2876
2995
|
inputSchema: object(SETTINGS),
|
|
2877
2996
|
annotations: { title: "Change animation settings", ...WRITE_IDEMPOTENT },
|
|
2878
2997
|
handler: (args) => apply({ op: "set_settings", ...args }, "Change settings")
|
|
@@ -2950,7 +3069,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2950
3069
|
return { redone, ...redone ? { label } : {} };
|
|
2951
3070
|
}
|
|
2952
3071
|
};
|
|
2953
|
-
const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
3072
|
+
const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
2954
3073
|
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2955
3074
|
return {
|
|
2956
3075
|
id: options.id ?? "animation",
|
|
@@ -2989,6 +3108,7 @@ Commands
|
|
|
2989
3108
|
- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale "blend": "add" to stack them too (what they add stays); "blend": "replace" makes a change take over instead.
|
|
2990
3109
|
- Any command but enter and exit can "repeat" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); "duration" is one cycle.
|
|
2991
3110
|
- Springs ("spring-gentle", "spring-quick", "spring-bouncy", "spring-slow" or { "type": "spring", \u2026 }) feel physical; leave "duration" out and the spring sets it.
|
|
3111
|
+
- To time shots to music or a voiceover: set a "beat" grid or "narration" word timings with set_animation_settings, read them with get_timing_grid, and give a shot "at": { "beat": n } or { "word": i }. The shot before holds until the cue; check_animation warns when a cue is missed.
|
|
2992
3112
|
- "steps": 12 (on a command, or for the whole scene with set_animation_settings) holds poses "on twos" for a hand-drawn look. "geometric": true on scale or a camera zooms at an even rate.
|
|
2993
3113
|
- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's "flairPrimitive"), and emphasis, reveals and follows export to Flair exactly.
|
|
2994
3114
|
- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).
|
|
@@ -3052,9 +3172,12 @@ export {
|
|
|
3052
3172
|
isMotionShot,
|
|
3053
3173
|
matchMotionNodes,
|
|
3054
3174
|
motionAddTrackKey,
|
|
3175
|
+
motionBeatAt,
|
|
3176
|
+
motionBeatTime,
|
|
3055
3177
|
motionClipAttrs,
|
|
3056
3178
|
motionCommandDuration,
|
|
3057
3179
|
motionCommandRepeat,
|
|
3180
|
+
motionCueTime,
|
|
3058
3181
|
motionCycleDuration,
|
|
3059
3182
|
motionFlairPrimitive,
|
|
3060
3183
|
motionFollowRoute,
|