Reference
Recipes
The call shapes for common jobs — titles, fades, lower thirds, logos, sound on a cut, crops, transitions, looks and captions.
Replace angle brackets with returned ids: new layers are under made → layerIds, tracks under trackIds.
A few things hold throughout:
- Layers on screen at the same time go on separate tracks; layers that follow one another share one. Tracks are drawn in order, the newest on top.
addLayerandplaceIconreplace whatever already shares their track at that time. With"mode":"fit"they leave it alone, and if the time asked for is taken the new layer moves later, to the first gap long enough for it. Giving titles, graphics and captions tracks of their own —{"verb":"addTrack","args":{"name":"Titles"}}— keeps them off the footage.- Keyframe times are seconds from the layer's own start.
- A
ymotion_editcall is applied as one step: all its ops or none, and one undo.
Building a passage
Build a source-supported opening, development and payoff from the transcript and received frames. Preserve order and words unless asked otherwise; an illustrative graphic is never new evidence.
Share type, palette, alignment and motion. Captions carry speech; titles orient; graphics explain. Keep roles on separate tracks, ordinary lines consistent and decisive lines distinct. A real demonstration can need unobstructed controls more than a headline.
An approach, a hold and a return
A motivated punch-in can bring a decisive statement closer, then return to the ordinary frame for what follows. Read the footage's transform and keys first. This example is for an unparented footage layer starting at 0, fitted with resting scale 1 and lasting 4 seconds; the decisive moment is at 2.4. The fit stays in scaleX and scaleY. For other footage, use its resting scale and subtract its start from the timeline times:
ymotion_edit {"label":"emphasise the decisive moment","ops":[{"verb":"putTracks","args":{"layerId":"<footage layer id>","tracks":[{"path":"transform.scale","keys":[{"t":0,"value":1},{"t":2,"value":1,"ease":"cubicOut"},{"t":2.4,"value":1.08},{"t":3,"value":1.08,"ease":"cubicInOut"},{"t":3.6,"value":1}]}]}}]}
putTracks merges with existing keys, so preserve the movement already intended on that path and adjust this window around the source's actual phrase. The example's times and scale describe that one event, not a repeating zoom schedule. Check the closest frame: a speaker's face, hands in use, a product detail or a complete movement must still be readable. A whoosh can reinforce this gesture when it suits the passage; its peak goes where the move is fastest, as Sound effects for motion describes.
A reveal that can be understood
Reveal a source-supported relationship in spoken or action order, then hold the completed view. Allow about 2–3 seconds of settled reading time for short labels, longer for dense text; count after the final label arrives. Shorter beats need simpler labels or treatment: a one-second multi-label flash does not explain. Read placeTemplate parameters and adapt palette, placement and motion; Templates and Templates that move give the calls.
Inspect ymotion_render {"count":24}, then the entrance, reveal and return in detail. Check changed sound with the waveform. Stagger competing hooks, captions, zooms and hits; review the whole film before making its preview.
A title that fades and rises
On a track of its own, above the footage:
ymotion_edit {"label":"title","ops":[{"verb":"addLayer","args":{"trackId":"<title track id>","start":0,"duration":3,"name":"Title","content":{"kind":"text","text":{"text":"Hello","align":"centre"}}}}]}
ymotion_edit {"label":"title in","ops":[{"verb":"setTextStyle","args":{"layerId":"<layer id>","patch":{"size":9,"paint":"#FFFFFF"}}},{"verb":"keyRamp","args":{"layerId":"<layer id>","path":"opacity","from":0,"to":1,"opts":{"start":0,"end":0.4,"ease":"cubicOut"}}},{"verb":"keyRamp","args":{"layerId":"<layer id>","path":"transform.y","from":0.04,"to":0,"opts":{"start":0,"end":0.5,"ease":"cubicOut"}}}]}
Size 9 is nine percent of the frame's short edge; written as 0.09 it would be kept as written and draw type about a pixel tall, and the reply's check: line would say you probably meant 9. The riseByWord preset does a similar entrance in one op, and a headline that shouldn't wrap is point text, setText with {"point":true}. See Type and titles.
Fade in and out
keyRamp writes two keys and replaces the path's whole track, so a second keyRamp for the fade-out would delete the fade-in. Write both at once with putTracks:
ymotion_edit {"label":"fade in and out","ops":[{"verb":"putTracks","args":{"layerId":"<layer id>","tracks":[{"path":"opacity","keys":[{"t":0,"value":0,"ease":"cubicOut"},{"t":0.4,"value":1},{"t":2.5,"value":1,"ease":"cubicIn"},{"t":3,"value":0}]}]}}]}
putTracks takes any number of keys, with eases, handles, holds and loops; ymotion_help {"topic":"types","name":"Keyframe"} lists a key's fields. The fadeInOut preset writes the same shape:
ymotion_edit {"ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"fadeInOut","params":{"over":0.5}}}]}
A lower third
A bar and a name on two tracks, sliding in from the left and back out. The tracks:
ymotion_edit {"label":"lower third tracks","ops":[{"verb":"addTrack","args":{"name":"Lower third bar"}},{"verb":"addTrack","args":{"name":"Lower third text"}}]}
The two layers, the second line of text styled as its own segment:
ymotion_edit {"label":"lower third","ops":[{"verb":"addLayer","args":{"trackId":"<bar track id>","start":1,"duration":5,"name":"Bar","content":{"kind":"shape","shape":{"kind":"rect","width":0.4,"height":0.12,"radius":0.15,"fill":"#111111@85"}}}},{"verb":"addLayer","args":{"trackId":"<text track id>","start":1,"duration":5,"name":"Name","content":{"kind":"text","text":{"align":"left","paragraph":{"indent":0.08},"segments":[{"text":"Ada Lovelace","breakAfter":true},{"text":"Mathematician","style":{"size":3,"paint":"#FFFFFF@70"}}]}}}}]}
Then position them, parent the name to the bar, and move the bar:
ymotion_edit {"label":"lower third motion","ops":[{"verb":"setTransform","args":{"layerId":"<bar id>","patch":{"x":-0.24,"y":0.3}}},{"verb":"setTextStyle","args":{"layerId":"<name id>","patch":{"size":4.5}}},{"verb":"setTransform","args":{"layerId":"<name id>","patch":{"y":0.3}}},{"verb":"setParent","args":{"layerId":"<name id>","parentId":"<bar id>","opts":{"keep":true}}},{"verb":"putTracks","args":{"layerId":"<bar id>","tracks":[{"path":"transform.x","keys":[{"t":0,"value":-0.74,"ease":"cubicOut"},{"t":0.5,"value":-0.24},{"t":4.5,"value":-0.24,"ease":"cubicIn"},{"t":5,"value":-0.74}]}]}}]}
On a 1920x1080 film the bar runs from 6% to 46% of the width and the name sits 8% in, both centred at 80% of the height. A shape's width and height are fractions of the frame's width and height, so on a vertical film adjust the numbers by eye.
Moving several layers as one
A null layer is a transform with a name, there to be a parent:
ymotion_edit {"ops":[{"verb":"addLayer","args":{"trackId":"<rig track id>","content":{"kind":"null"},"start":0,"duration":3,"name":"Rig"}}]}
ymotion_edit {"ops":[{"verb":"setParent","args":{"layerId":"<layer id>","parentId":"<rig id>"}}]}
Animate the null and its children travel with it, each keeping its own motion. Parenting to a layer that has already moved, turned or scaled makes the child jump by that much; "opts":{"keep":true} keeps the child where it is on screen, keys and all, as After Effects' parenting does.
A logo or an icon
A logo is an upload, and a placed image arrives at its own size in pixels, so scale it and move it — here towards the top-right corner. How big scale makes it depends on the file, so check the result with a render or ymotion_read {"what":"box","layerId":"<layer id>"}:
ymotion_media {"action":"place","id":"<media id>","start":0,"duration":3}
ymotion_edit {"ops":[{"verb":"setTransform","args":{"layerId":"<layer id>","patch":{"scale":0.3,"x":0.38,"y":-0.4}}}]}
An icon from the catalogue lands as an ordinary shape layer, coloured and sized in one call; ymotion_find {"catalogue":"icons","text":"heart"} searches them. Give it a track of its own:
ymotion_edit {"ops":[{"verb":"placeIcon","args":{"trackId":"<icon track id>","icon":"heart","paint":"#FF3B30","start":0,"duration":2,"place":{"size":0.18,"x":-0.3,"y":0.35}}}]}
In place, size, x and y are all fractions of the frame's short edge, so the icon stays square and a nudge is the same distance either way; on a 1920x1080 frame, x −0.3 is 324 pixels left of centre, not 30% of the width.
The reply's rig names the paths of the icon's parts, its paint among them; an icon drawn in several parts lists each under parts, and paint is the first part's. A shape layer's box is the whole frame, so it turns about the frame's centre until centerAnchor moves the anchor onto the icon — without it, a spin swings the icon round the middle of the frame. To spin it in place and change its colour:
ymotion_edit {"label":"spin and recolour","ops":[{"verb":"centerAnchor","args":{"layerId":"<layer id>"}},{"verb":"keyRamp","args":{"layerId":"<layer id>","path":"transform.rotation","from":0,"to":360,"opts":{"start":0,"end":2,"ease":"cubicInOut"}}},{"verb":"paintRamp","args":{"layerId":"<layer id>","path":"<the rig's paint path>","from":"#FF3B30","to":"#34C759","opts":{"start":0,"end":2}}}]}
Key colours with keyPaint or paintRamp. keyOn and the other numeric writers refuse a colour row and name keyPaint or paintRamp. Paint keys retain their colour data when written whole, pasted or copied.
A camera shutter on a chosen transition
The shared library needs nothing uploaded. Each transition takes a shutter. A transition sits on a cut, and the cuts are the layer starts in ymotion_read {"what":"tracks"}. Search once:
ymotion_find {"catalogue":"sfx","group":"camera"}
Then one edit for the transitions chosen as audible accents, here at 2.4 and 5.1:
ymotion_edit {"label":"shutters","ops":[{"verb":"placeSfx","args":{"sfx":"<sfx id>","at":2.4}},{"verb":"placeSfx","args":{"sfx":"<sfx id>","at":5.1}}]}
at is the cut, not where the file starts: a shutter's click lands on it, as a whoosh's peak and a riser's end do. A zoom transition is the exception and takes a whoosh, like every zoom; Sound effects for motion pairs each animation with its sound. Sound effects get a track of their own and never cut or move another layer; two that overlap get a second track, and twelve placings of one sound share one asset. "place":{"level":-6} sets a level in decibels, and "place":{"align":"start"} puts the file's first sample on at. See Sound, music and sound effects.
Footage, placed and fitted
Footage comes from the person's uploads. A ChatGPT attachment or a file you can reach from a shell can be brought in too (Getting a file in); otherwise, if what you need isn't listed, ask the person to upload it at /uploads and give you its id.
ymotion_media {"action":"list"}
ymotion_media {"action":"place","id":"<media id>","start":0}
In a film made with no size, the first video gives the film its size, shape and frame rate and sits in it at 100%. Otherwise a video arrives fitted, whole and centred, with bars where the shapes differ. mode on place is about the timeline — the default, fit, removes nothing — not about framing. To fill the frame, cropping what overflows:
ymotion_edit {"ops":[{"verb":"fitToComp","args":{"layerId":"<layer id>","how":"cover"}}]}
Any shape, and any crop
A film can change shape at any time:
ymotion_edit {"ops":[{"verb":"setCanvas","args":{"patch":{"width":1080,"height":1350}}}]}
Placed footage keeps its scale when it does. A vertical or 3:4 shot still fills a 4:5 or square frame of the same width, and a fit that would change nothing is refused with its whole batch, so fit again only footage that no longer covers the frame, such as landscape footage that was a band, in a call of its own. ymotion_read {"what":"box","layerId":"<layer id>"} says: it covers while onCanvas.fraction starts at or before 0 and reaches 1 or past on both axes.
ymotion_edit {"ops":[{"verb":"fitToComp","args":{"layerId":"<layer id>","how":"cover"}}]}
contain shows the whole shot with bars; cover fills the frame and crops. Then choose what the crop keeps: x and y move the shot (fractions of the frame from its centre, positive y down) and scale zooms in on top of the fit:
ymotion_edit {"ops":[{"verb":"setTransform","args":{"layerId":"<layer id>","patch":{"x":0.12,"scale":1.15}}}]}
setCrop trims the footage's own edges, as fractions of it:
ymotion_edit {"ops":[{"verb":"setCrop","args":{"layerId":"<layer id>","patch":{"left":0.1,"right":0.1}}}]}
A crop is best judged by rendering a frame. See Formats and layout.
A transition between two shots
A transition sits on a cut: two layers on one track, the second starting where the first ends. Splitting a video makes a cut, and the reply's made is the half after it — the layer the transition goes on:
ymotion_edit {"ops":[{"verb":"splitLayer","args":{"layerId":"<video layer id>","t":4.2}}]}
ymotion_edit {"ops":[{"verb":"applyTransition","args":{"layerId":"<layer after the cut>","transition":"dissolve"}}]}
Every transition has over, its length in seconds, and some a direction — 0 left, 1 right, 2 up, 3 down:
ymotion_edit {"ops":[{"verb":"applyTransition","args":{"layerId":"<layer after the cut>","transition":"whip","params":{"over":0.3,"direction":1}}}]}
Each is one of two kinds — an overlap, with both shots on screen at once, or a cover:
dissolve— Dissolve: both shots on screen at once, 0.6 s by defaultblurDissolve— Blur: both shots on screen at once, 0.7 s by default; also amountslide— Slide: both shots on screen at once, 0.5 s by default; also directionwhip— Whip: both shots on screen at once, 0.35 s by default; also direction, amountwipe— Wipe: both shots on screen at once, 0.6 s by default; also direction, softnessiris— Iris: both shots on screen at once, 0.7 s by default; also softnessstack— Stack: both shots on screen at once, 0.6 s by default; also directionzoom— Zoom: covers the cut, 0.5 s by default; also amountflash— Flash: covers the cut, 0.45 s by defaultblackout— Blackout: covers the cut, 0.8 s by default; also holdfilmBurn— Film burn: covers the cut, 0.9 s by default
A cover hides the cut and needs nothing more. An overlap shows both shots at once, so the layers are extended into each other with footage from past the cut, which a split always has. Two whole files end to end have none, so the outgoing shot holds its last frame instead; a cover avoids that. Nothing moves in time and the sound still cuts where it did. A cut takes one transition, so undo the last to change it. See Cuts and transitions.
A colour look
The quickest look is a grade preset, which writes an ordinary filter stack; ymotion_find {"catalogue":"presets","group":"Grades"} lists them:
ymotion_edit {"ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"film"}}]}
To grade by hand, add filters with their knobs set. Warm highlights and cool shadows is lift's blue up and gain's red up; addFilter takes the knobs with it. These filters default to full strength; the explicit "amount":1 below makes that choice clear:
ymotion_edit {"label":"warm highs, cool lows","ops":[{"verb":"addFilter","args":{"layerId":"<layer id>","id":"lift","amount":1,"params":{"blue":0.15}}},{"verb":"addFilter","args":{"layerId":"<layer id>","id":"gain","amount":1,"params":{"red":0.2}}}]}
To change a knob later, setParam names the filter by its place in the layer's stack:
ymotion_edit {"ops":[{"verb":"setParam","args":{"layerId":"<layer id>","what":"filters","index":1,"param":"red","value":0.1}}]}
Filters are added to the end of the stack, and transitions and presets add filters too — a whip adds a blur, a grade preset several — so read the indices first with ymotion_read {"what":"properties","layerId":"<layer id>","like":"filters"}. setParam doesn't check that the filter at that index has the knob named, so a wrong index writes a value nothing reads, with no error. A filter's knobs and ranges come from ymotion_find {"catalogue":"filters","id":"curves"}, with its id. setEffectEnabled is the fx switch, turning one row off while keeping its settings and keys; duplicateEffect copies a row with its keys, and resetEffect restores its defaults:
ymotion_edit {"ops":[{"verb":"setEffectEnabled","args":{"layerId":"<layer id>","what":"filters","index":0,"on":false}}]}
See Colour and grading.
A preset with its knobs
ymotion_edit {"ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"slideIn","params":{"over":0.6,"vertical":1}}}]}
ymotion_find {"catalogue":"presets","id":"slideIn"} gives each knob's label, range, default and unit. Pass only what you change: over is seconds, span a share of the layer, angle degrees, and a knob that runs 0 to 1 in steps of 1, like vertical, is a switch. On text, an In, a Loop and an Out stack without erasing each other, and moving letters individually is a text animator; Text animators and presets covers both and lists every preset.
Footage inside type
setMatte cuts one layer to another's shape. With the footage and the text on two tracks over the same time:
ymotion_edit {"ops":[{"verb":"setMatte","args":{"layerId":"<footage layer id>","matteLayerId":"<text layer id>","kind":"alpha"}}]}
The text stops drawing on its own and the footage shows through its letters, so a large, heavy face works best — anton at 30 or more on a short word, checked with a render, since at that size a long word runs past the edge of a vertical frame. "kind":"luma" cuts by brightness, and "invert":true keeps what's outside the shape. See Shapes, icons, masks and mattes.
Captions of what's said
Anything uploaded with sound was transcribed on arrival, with times on every word:
ymotion_media {"action":"transcript","id":"<media id>"}
Transcript times are seconds into the file. At forward constant positive speed, a moment in the file lands on the timeline at the video layer's start plus (file time − trimIn) ÷ speed.rate (1 at normal speed), all from ymotion_read {"what":"layer","layerId":"<layer id>"}: a layer placed at 10 s that begins 12 s into its file shows a word spoken at 14.0 s at 12.0. At normal forward speed, footage placed at 0 and not trimmed has matching clocks. Ramps, native rate keys, reverse and holds need their source-clock mapping instead; captions with source apply it for you.
Each line becomes a text layer from its start to its end — a line at 6.20 to 9.20, on footage placed at 0 and not trimmed, is start 6.2, duration 3. Where two lines would overlap, end the earlier where the later begins; "mode":"fit" would instead start the later one late:
ymotion_edit {"label":"captions","ops":[{"verb":"addLayer","args":{"trackId":"<caption track id>","start":6.2,"duration":3,"name":"cap 2","content":{"kind":"text","text":{"text":"Number two, take responsibility","align":"centre","anchor":"bottom","maxWidth":0.8}}}}]}
"anchor":"bottom" sits the block on the frame's bottom edge. Ordinary captions stay below the face, never in the upper hook/graphics region. Lift them with setTransform — a y of about −0.08 on a landscape film, or about −0.25 on a vertical one to clear the buttons a phone app draws — and style them together with restyleAll. Captions placed on the file's clock rather than the timeline's still render, just at the wrong moments, so a frame at a known word is the quick check. See Captions and What an upload says.
Captions below a person
Render the phrase's start, middle, end and biggest face move; measure box after settling. Its full ink bounds must stay below the chin, above app controls and clear of hands/products. Check the longest phrase and closest crop. If it fails, shorten, reflow, reduce motion or make a lower band; never move it above the face.
ymotion_render {"layerId":"<caption id>","count":4}
Hooks and graphics use clear upper or side space. Ordinary speech captions stay below the face. Captions and Formats and layout give measurements and roles.
Checking, and showing someone
A render shows what the document looks like, which is how to know an edit did what was meant:
ymotion_render {"count":4}
"times":[0.2,0.5,1] picks the moments, and "layerId" spreads count across one layer. To give a person something to open:
ymotion_render {"what":"frame","t":1.5,"link":true,"public":true}
That link is stable and opens for anyone; without public it's private, lasts about fifteen minutes, and is a cheap way to check your own work. The same moment of the same document returns the same URL without rendering again. Turn a public link off with ymotion_media {"action":"share","id":"<the id you were given>","public":false}. See Looking at your work.
See also
- Getting started for the order to work in.
- Units, paints and time for every unit above.
- Keyframes and easing for keys beyond two.