Ymotion
Help
Dashboard
All pages

Guides

Captions

Captions from a transcript: phrasing, timing, one layer per phrase, placement, legibility, emphasis and word-by-word highlights.

Captions in Ymotion are ordinary text layers: one layer per phrase, timed from the words of a transcript, on a track of their own above the footage. Because they're ordinary type, everything that works on text works on a caption: styles, animators, presets and keyframes.

This page walks through a caption pass from transcript to checked render, with starting values that hold up on real footage. Reading the transcript and converting its times is covered in What an upload says.

A caption pass is one part of an edit. When the person asks for the whole film to be edited, the words support its argument or action alongside framing, pacing, graphics and sound; adding readable captions alone does not finish that work. When they ask for only captions, that narrower request decides the scope. Recipes shows how those parts form a complete passage.

A captions template

The captions template category phrases, times and styles a whole take in one call, with the footage or voice-over layer as its source:

ymotion_edit {"ops":[{"verb":"useTemplate","args":{"category":"captions","config":{"source":"<footage layer id>","style":"<look id>"}}}]}

ymotion_find {"catalogue":"templates","group":"captions"} lists the looks. The rest of this page is the pass by hand, for words the person gave or a look no template has, and the rules a template follows as well.

The shape of a caption pass

  1. Read the words: ymotion_media {"action":"transcript","id":"<upload id>","from":0,"to":60}. Convert their file times to timeline times if the footage doesn't start at 0 or is trimmed, split or sped up.
  2. Split the words into short phrases and work out each phrase's start and end.
  3. Add a caption track, then add every caption in one batch, already styled.
  4. In a second batch, position the captions and give them entrances chosen for the film ("Entrances and exits" below). This takes the layer ids, which come back in the reply to the first batch.
  5. Render a few moments and look.

Splitting speech into phrases

A caption reads best as a phrase someone would say in one breath. Some starting points:

  • Vertical short-form video: two to five words a caption, on one or two short lines.
  • Landscape subtitles: up to about 40 characters a line, two lines at most.
  • Break where the speaker does. Punctuation is the strongest signal; after that, a gap of a quarter second or more between one word's end and the next word's start.
  • Keep units together. Avoid ending a line on "the", "a", "of" or "to", and avoid splitting a name or a number across captions.

Choose the line break yourself: a newline in the text is a line break, so "nobody tells you\nthis part" sets two balanced lines, where wrapping at maxWidth can strand "part" alone. Keep maxWidth set as a safety net.

Timing

Each caption starts on its first word's start time. For the end:

  • If the next phrase begins within about half a second, run the caption right up to the next one's start. A gap of a few frames between captions reads as a flicker.
  • Otherwise, end it about 0.3 seconds after its last word, so it doesn't vanish mid-thought.
  • Keep every caption on screen for at least about 0.8 seconds, at a reading pace of no more than about 15–20 characters a second.

Captions on one track shouldn't overlap in time. If two do, addLayer's default "overwrite" mode shortens the earlier caption to end where the new one starts, while "fit" moves the new caption later, to the next free gap, which makes it late. Working out non-overlapping times yourself avoids both.

One text layer per phrase, on a caption track

Each phrase gets its own layer because each has its own timing, its own words to correct and its own entrance. They can all share one track, because they never overlap in time. That track is separate from the footage's, because captions and footage are on screen together and one track holds one layer at a time.

ymotion_edit {"ops":[{"verb":"addTrack","args":{"kind":"visual","name":"Captions"}}]}

A new track goes on top of the stack, so the captions draw over the footage. Then add the captions in one batch. Given as an object, the text carries its alignment, anchor, width and style, so each layer arrives finished:

ymotion_edit {"label":"captions","ops":[
  {"verb":"addLayer","args":{"trackId":"<captions track id>","start":0.42,"duration":1.28,"name":"cap 1",
    "content":{"kind":"text","text":{"text":"So here's the thing","align":"centre","anchor":"bottom","maxWidth":0.75,
      "style":{"size":6.5,"weight":800,"stroke":{"width":0.12,"paint":"#000000"}}}}}},
  {"verb":"addLayer","args":{"trackId":"<captions track id>","start":2.0,"duration":1.9,"name":"cap 2",
    "content":{"kind":"text","text":{"text":"nobody tells you\nthis part","align":"centre","anchor":"bottom","maxWidth":0.75,
      "style":{"size":6.5,"weight":800,"stroke":{"width":0.12,"paint":"#000000"}}}}}}
]}

Each op's line in the reply names the layer it made. Naming the captions (cap 1, cap 2) makes them easy to find again in ymotion_read {"what":"tracks"}.

Placement

Ordinary speech captions belong below the face for their complete span, including entrances, exits, camera moves and punch-ins. Keep the upper region for hook text, titles and explanatory graphics.

"anchor":"bottom" sets the text block against the bottom of the frame, so a caption's last line stays put and a two-line caption grows upward. transform.y then lifts it, as a fraction of the frame's height (negative is up):

  • Landscape: y around −0.08 puts the caption's bottom at about 92% of the height, where subtitles usually sit.
  • Vertical: y around −0.25 puts its bottom at about 75%; this is a starting position, not proof that the words sit below the face. Platform buttons and descriptions cover roughly the bottom fifth and a column down the right edge, so the middle-lower band stays clear; a maxWidth of about 0.75 keeps even a long centred line clear of the right-hand column. Formats and layout shows how to draw temporary guides to check.
ymotion_edit {"label":"place captions","ops":[
  {"verb":"setTransform","args":{"layerId":"<cap 1 id>","patch":{"y":-0.25}}},
  {"verb":"setTransform","args":{"layerId":"<cap 2 id>","patch":{"y":-0.25}}}
]}

After rendering, read ymotion_read {"what":"box","layerId":"<caption id>","t":0.5}. Compare onCanvas.fraction.top with the lowest chin position in the frames you received; the entire caption, including outline and shadow, needs a gap below it. Check the longest two-line phrase, the biggest face move and the closest crop. A bottom anchor alone cannot guarantee that a tall text block stays below the face.

If the lower band is too short, use fewer words per phrase, balance line breaks, reduce the entrance's travel or make a dedicated lower band by reframing the footage. Keep hands, product details and app controls clear. Do not move captions above the face as a workaround. When no image reached you, report placement as unchecked.

Size and weight

Type size is a percentage of the canvas's short edge, unlike positions and shape sizes, which are fractions of the frame. Good starting sizes:

  • Vertical (1080×1920): 6–7.
  • Landscape (1920×1080): 4.5–5.5.
  • Square or 4:5: 5–6.

The default face, Inter, is a bold (800), and a heavy weight like that holds up over moving footage far better than a regular one. restyleAll changes every caption's style at once from a list of layer ids. Faces are covered in Type and titles.

Legibility over footage

Footage changes under a caption from frame to frame, so give the type something of its own that separates it from the picture. From lightest to heaviest:

An outline

stroke on the text style, with width as a multiple of the type size. Black at 0.08–0.15 is a good range for captions. It's drawn under the fill by default, which keeps thin letters from closing up.

A shadow

shadow on the text style, with blur, x and y also in multiples of the type size. A soft one lifts type off bright backgrounds:

ymotion_edit {"ops":[{"verb":"restyleAll","args":{"layerIds":["<cap 1 id>","<cap 2 id>"],"patch":{"shadow":{"blur":0.2,"x":0,"y":0.05,"paint":"#000000@60"}}}}]}

A plate behind the words

After Effects has no text background; a shape layer behind the type is how it's done there and here. Add a track directly over the footage, so it sits under the captions:

ymotion_edit {"ops":[{"verb":"addTrack","args":{"kind":"visual","name":"Plates","above":"<footage track id>"}}]}

ymotion_read {"what":"box","layerId":"<cap 1 id>","t":0.5} answers where the caption lands, as fractions of the frame (onCanvas.fraction). t is seconds into the caption; measure after any entrance has settled, since an entrance changes the box while it plays. If it answers zeros, render a frame first: type is measured with its real face, which loads when a frame is drawn. For cap 1 above, that's left 0.201, top 0.713, width 0.596 and height 0.036, so the plate's centre is y = 0.713 + 0.036 / 2 − 0.5 = 0.231, and its size is the caption's plus a little padding. A shape's width is a fraction of the frame's width, and its height a fraction of the frame's height; radius rounds the corners as a share of half the shorter side, so 1 makes a pill:

ymotion_edit {"ops":[{"verb":"addLayer","args":{"trackId":"<plates track id>","start":0.42,"duration":1.28,"name":"plate 1",
  "content":{"kind":"shape","shape":{"kind":"rect","width":0.66,"height":0.07,"radius":0.3,"fill":"#000000@60"}}}}]}
ymotion_edit {"ops":[
  {"verb":"setTransform","args":{"layerId":"<plate 1 id>","patch":{"y":0.231}}},
  {"verb":"keyRamp","args":{"layerId":"<plate 1 id>","path":"opacity","from":0,"to":1,"opts":{"start":0,"end":0.25}}}
]}

The keyRamp fades the plate in with the caption's entrance. When captions vary a lot in width, one plate per caption looks best; for a uniform look, one full-width band behind all of them is simpler.

Depth belongs to titles and graphics

A hook or explanatory graphic can pass behind the speaker when depth helps its composition; Putting things behind a person describes the cut-out. Ordinary captions stay in their lower region. Keep meaningful letters readable and inspect the full span before using depth.

Emphasis on key words

A caption's text is a list of segments, and each segment can carry a style of its own on top of the layer's. Colour or weight the one or two words that carry the meaning; emphasising more than that flattens it again.

Choose emphasis from the thought: the contrast in an argument, the result of a demonstration, or the word that resolves a question. An accent on every noun gives all of them equal importance. Keep the same accent paint and treatment for the same role across the film, and let an ordinary phrase stay ordinary when it has no word to single out. The wording and its meaning remain the speaker's.

ymotion_edit {"ops":[{"verb":"setText","args":{"layerId":"<cap 2 id>","patch":{"segments":[
  {"text":"nobody tells you","breakAfter":true},
  {"text":"this part","style":{"paint":"#FFD60A"}}]}}}]}

Separate segments are separate words with a space between them. To style part of a word, give the second piece "join":true and it continues the word before it with no gap.

Word-by-word highlight

To light up each word as it's spoken, add a text animator: a few properties, plus a selector saying which characters get them. A range selector counting words ("units":"index","basedOn":"words") from 0 to 1 selects exactly one word, and its offset slides that selection along. Key the offset with hold keys at each word's start, measured from the caption's own start:

ymotion_edit {"label":"highlight the spoken word","ops":[
  {"verb":"addAnimator","args":{"layerId":"<cap 2 id>","animator":{"name":"spoken word","properties":{"fill":"#FFD60A"},
    "selectors":[{"kind":"range","units":"index","basedOn":"words","start":0,"end":1}]}}},
  {"verb":"putTracks","args":{"layerId":"<cap 2 id>","tracks":[{"path":"content.text.animators.0.selectors.0.offset","keys":[
    {"t":0,"value":0,"ease":"hold"},{"t":0.35,"value":1,"ease":"hold"},{"t":0.6,"value":2,"ease":"hold"},
    {"t":0.95,"value":3,"ease":"hold"},{"t":1.3,"value":4,"ease":"hold"}]}]}}
]}

Here the caption starts at 2.0 and its five words at 2.0, 2.35, 2.6, 2.95 and 3.3. Words are counted across line breaks. The 0 after animators is the index addAnimator answered. The same idea can scale the spoken word instead of colouring it; see Text animators and presets.

Entrances and exits

An entrance is part of the film's look, so it is chosen for the film, the way the face and the colour are. Text presets write ordinary keyframes onto the layer, and most take their length in seconds as over.

Choosing for the film

Every text animation says what it looks like and what it suits, its character (feel) and what moves (unit: block, line, word or character). The list of entrances carries all of it:

ymotion_find {"catalogue":"presets","group":"Animate In"}

A search finds them by any of those words, such as a character, subtitles, beat or letters:

ymotion_find {"catalogue":"presets","text":"punchy"}

There are six characters:

  • calm: quiet arrivals that don't pull focus. Subtitles, interviews, slow music.
  • lively: a friendly bounce, pop or spring. Vlogs, upbeat music, social.
  • punchy: hard and fast, landing like a hit. Beats, drops, a word to stress.
  • playful: letters that hop, rain, spin or wave. Fun, kids, comedy.
  • elegant: slow tracking, opening and outlined reveals. Titles, fashion, cinematic.
  • techy: typed, decoded, glitched or flickered. Tech, gaming, mystery.

Text animators and presets lists every one by character. Speech under a talking head usually wants calm or lively. Over music, the music decides: a dance track's drop is punchy, a ballad is elegant, a game montage is techy.

A family, used with intent

Repetition makes a caption system familiar; contrast makes a particular line matter. Choose a quiet default arrival for ordinary phrases, then a companion gesture from the same character for a genuine change of role. A different entrance on every caption reads as a demo reel, while an identical large bounce on every phrase spends the emphasis before the important one:

  • Keep ordinary captions consistent and reserve the biggest move for the line that matters: the hook, the chorus, the drop, the stressed word. Repeating that default is useful; rotating presets to fill a pattern is not a reason to change it.
  • Choose what moves from what must be read. A block arrival exposes the whole phrase together; words arriving in turn reveal a sequence; a short display word can carry a character gesture. The units of popUp, wordPop and scaleUpCharacters offer those choices without changing the lively character.
  • Keep a direction for a stretch. Entrances that all rise read as one gesture; up, then down, then left, then a spin reads as noise.
  • Change the family where the film changes, such as a calm intro before the drop, or a verse and its chorus.
  • Leave enough still reading time after the entrance. When the phrase is short, shorten the gesture rather than letting its last words arrive as the next caption replaces it. A hook, a caption highlight and a punch-in need not all move on the same word: let the strongest treatment lead and keep the others still.

Four captions over an upbeat track, with the third carrying the decisive phrase: the ordinary ones share an arrival and the bigger bounce is kept for that phrase:

ymotion_edit {"label":"entrances","ops":[
  {"verb":"applyPreset","args":{"layerId":"<cap 1 id>","preset":"popUp","params":{"over":0.26}}},
  {"verb":"applyPreset","args":{"layerId":"<cap 2 id>","preset":"popUp","params":{"over":0.26}}},
  {"verb":"applyPreset","args":{"layerId":"<cap 3 id>","preset":"spring","params":{"over":0.5}}},
  {"verb":"applyPreset","args":{"layerId":"<cap 4 id>","preset":"popUp","params":{"over":0.26}}}
]}

A dance track's drop would take the punchy family the same way: skewIn on the ordinary lines and slam on the hit. ymotion_find {"catalogue":"presets","id":"spring"} gives one preset's knobs and their ranges, and the knobs vary the gesture within a preset too: a shorter over, a smaller by.

For speech

Captions under speech appear often, so their entrances are quick and quiet:

  • textFadeIn with over 0.15–0.2: the least noticeable.
  • popUp with over around 0.25: the line grows in from 60% with a small rise and overshoot, a livelier arrival.
  • riseByWord or wordPop with over 0.4–0.6: the words arrive one after another in a quick cascade. It doesn't follow the speech word for word (the highlight above does that), so keep over well inside the caption's length.

A calm interview can use a fade as its recurring default instead of the lively family's bounce. Choose that system for the film, then apply it to the ordinary phrases:

ymotion_edit {"label":"calm caption arrival","ops":[{"verb":"applyPreset","args":{"layerId":"<cap 1 id>","preset":"textFadeIn","params":{"over":0.18}}}]}

On the beat

Over music with a steady beat, first inspect uploads with ymotion_media {"action":"list"}; then, if your tool list declares the beats action and layerId, ymotion_media {"action":"beats","layerId":"<music layer id>"} gives where each beat and downbeat lands on the timeline: ask the layer the music plays from, which is the footage itself when the music is its own sound. An entrance reads as part of the music when it lands on a beat: make over a beat or half of one (at 120 beats a minute a beat is 0.5 seconds), and start the caption over seconds before the downbeat it lands on. The accents the same reply lists are where the biggest move of the family pays off. Beats in sound has more.

Exits

Back-to-back captions look calmer without exits, since each is replaced by the next. A caption followed by a gap, and the last one, leave better than they vanish: an Out from the same family, with over 0.2–0.4, applied to the same layer as its entrance. A caption that holds for several seconds can also take a Loop between the two; Text animators and presets shows the three stacked.

Checking

Render a moment inside several captions, especially the longest ones and any over bright footage:

ymotion_render {"times":[1.0,3.0,5.0]}

To watch an entrance, ask for frames close together from the caption's start, since a quarter-second entrance is over long before the caption is: for cap 1, which starts at 0.42, ymotion_render {"times":[0.42,0.5,0.58,0.67]}. ymotion_render {"count":4,"layerId":"<cap 1 id>"} spreads frames over the whole caption instead, which suits checking its words rather than its entrance. Look for stranded words, captions clipped at the frame's edge, and words that sink into the background. Looking at your work has more.

Common pitfalls

  • A size of 0.065 instead of 6.5. Type size is a percentage, so 0.065 is kept as written and draws type under a pixel tall. setTextStyle's reply adds a check: line saying you probably meant 6.5; a size inside addLayer's content is kept without a word, so render to be sure.
  • Captions late by whole seconds. Transcript times are the file's. A trimmed or split footage layer needs converting first; see What an upload says.
  • A caption on the footage's track. In "overwrite" mode it cuts a hole in the footage: the footage is split around the caption, and the reply's made lists the extra piece beside the caption. Undo with ymotion_doc {"action":"undo"} and add it to the caption track.

See also