Brio Script runs JavaScript written the way After Effects scripts are: app.project.activeItem is the open comp, layers and properties have AE's names, and values are in AE units — position in top-left, Y-down comp pixels; scale and opacity in percent; rotation in degrees clockwise. Run a script from File ▸ Scripts; one undo takes back the whole run. AE scripts can be brought over with File ▸ Scripts ▸ Convert AE Script….
A script runs in a sandbox: it reads and edits the project — the open comp, other comps, footage, the render queue — and nothing else. It can't read or write files, reach the network, run shell commands or AE menu commands; a file it imports or a render it queues goes through Brio, which asks you first when the script didn't get the file from a dialog you answered. It reads a snapshot taken just before it starts, so what it reads is synchronous and a read after a write sees the write. Its changes are applied when it finishes; a script that throws changes nothing.
Globals
| Name | What it is |
|---|---|
app | the application (below) |
$.writeln(...), $.write(...) | print a line to the Script Console |
alert(message) | prints to the Script Console (dialogs are ScriptUI windows — see Panels and dialogs) |
confirm(message) | returns true; prompt(message, default) returns default |
KeyframeInterpolationType.LINEAR / BEZIER / HOLD | for setInterpolationTypeAtKey |
new KeyframeEase(speed, influence) | for setTemporalEaseAtKey |
new TextDocument(text) | for Source Text and layers.addText |
ParagraphJustification.LEFT_JUSTIFY / CENTER_JUSTIFY / RIGHT_JUSTIFY, PropertyValueType.* | AE's enums |
CompItem, AVLayer, ShapeLayer, TextLayer, CameraLayer, LightLayer, Property, PropertyGroup | for instanceof checks (they can't be constructed) |
$.fileName is the script's file name. Scripts run in non-strict mode, as ExtendScript does. File and Folder exist but can't read or write anything; File.openDialog() / file.openDlg() (and Folder.selectDialog()) show Brio's file dialog and return what the user picks. system.callSystem, $.evalFile and #include at run time are not available. ScriptUI is — see Panels and dialogs.
app
| Member | Notes |
|---|---|
app.project | the project |
app.beginUndoGroup(name) / app.endUndoGroup() | names the run's undo step (the run is always one step) |
app.settings.saveSetting(section, key, value), getSetting, haveSetting | saved per script, between runs |
app.version | "Brio Script 1.0" |
app.executeCommand, app.findMenuCommandId, app.scheduleTask | throw — no equivalent |
app.project
| Member | Notes |
|---|---|
activeItem | the open comp (CompItem), or null |
numItems, item(i) | the project's comps, then its footage, 1-based |
importFile(new ImportOptions(file)) | imports a file (sequence = true for a numbered image sequence) and returns its FootageItem. A file the user picked in a dialog imports straight away; a path the script names asks the user first |
importFileWithDialog() | Brio's import dialog; returns the imported items (or null) |
items.addComp(name, width, height, pixelAspect, duration, frameRate) | a new comp; it is not opened — its layers can still be added and edited |
CompItem
| Member | Notes |
|---|---|
name, width, height, duration, frameRate (29.97 / 23.976 / 59.94 kept exact), frameDuration | get/set — on any comp, open or not |
bgColor | [r, g, b] 0–1, get/set |
workAreaStart, workAreaDuration | seconds, get/set (kept inside the comp) |
motionBlur, motionBlurSamplesPerFrame | get/set |
threeD | get/set — Brio's 2D/3D comp mode (going 3D adds the Scene layer) |
id, pixelAspect (1), shutterAngle (180), displayStartTime (0) | read-only (setting another value says so) |
time | the playhead — set it to move it (the open comp; not an undoable edit, as in AE) |
numLayers | |
layer(index) / layer(name) | 1-based, top first; throws if missing. Works on any comp, open or not: the first time a script touches a closed comp's layers, the run pauses while Brio reads them and starts again (so code before it runs twice) |
layers[index] | 1-based, like layer(index) |
layers.byName(name) | null if missing |
layers.addNull() | a new null at the comp centre, on top |
layers.addSolid([r, g, b], name, width, height) | colour 0–1 |
layers.addText(text) | text is a string or a TextDocument (its font, size, colours apply); addBoxText(size, text) makes point text |
selectedLayers | array (0-based), primary first — the open comp only |
openInViewer() | opens another comp; the script can't edit further after it |
layers.add(item) | places a FootageItem (or nests another comp) in the open comp |
layers.addShape() | a shape layer; fill it through property("Contents") (below) |
layers.addCamera(name) | a camera; the comp turns 3D and it becomes the active camera |
layers.addLight(name) | a light (spot, as in AE); set lightType in the same run: LightType.PARALLEL / SPOT / POINT / AREA |
layers.precompose() | throws — not available yet |
Render queue
app.project.renderQueue: numItems, item(i) (Brio's queue as it was when the script started — those can't be changed), items.add(comp) → a RenderQueueItem, render(). A new item renders the comp's work area (else the whole comp) as H.264 MP4 until told otherwise: timeSpanStart / timeSpanDuration, render (false = skip), remove(), duplicate(), status (RQItemStatus.NEEDS_OUTPUT until it has a file). outputModule(1): file = new File(path) (the extension picks the format — .mp4, .mov, .png / .exr sequences, .gif, .json Lottie, .svg), applyTemplate(name) (AE's names map to Brio's formats: H.264 … Mbps, H.265, Lossless (with Alpha), High Quality (with Alpha), ProRes 422 / HQ / LT / 4444, PNG Sequence, EXR Sequence, Animated GIF, Lottie, Animated SVG), templates, getSettings / setSettings for "Output File Info" → "Full Flat Path". When the script ends, Brio shows the items and their output files and renders (or queues) them only if the user agrees; an item without a file goes to the last render folder.
FootageItem
name, id, width, height, duration and frameRate (0 for stills), frameDuration, pixelAspect (1), hasVideo, hasAudio, file (its path; not readable), mainSource.isStill. Removing or replacing footage from a script isn't supported yet. A file dialog or an import pauses the run while Brio asks, then the run starts again from the top with the answer — so code before it runs twice; keep it free of side effects. An import is its own undo step.
Layer
| Member | Notes |
|---|---|
name | get/set |
index | 1-based |
inPoint, outPoint | get/set (seconds; setting trims) |
startTime | read-only |
parent | get/set a layer or null; the layer keeps its place on screen |
selected | get/set |
enabled, nullLayer, hasVideo | read-only |
transform | the Transform group |
Effects / property("Effects") | the Effects group |
property(name) | a Transform property by name, "Transform", "Effects", "Text" or "Source Text"; null if the layer has none (as in AE) |
text, sourceText | text layers: the Text group and its Source Text (null on other layers) |
position, scale, rotation, opacity, anchorPoint, xRotation, yRotation | shortcuts to Transform properties |
remove(), duplicate() | |
moveToBeginning(), moveToEnd(), moveBefore(layer), moveAfter(layer) | |
setParentWithJump, sourceRectAtTime | throw — not available |
Property groups
group.property(name | matchName | index) (null for a name it doesn't have), group.numProperties, group.name, group.matchName.
Transform: Anchor Point, Position, Scale, Rotation (also Z Rotation), Opacity, and on 3D layers Z Position, X Rotation, Y Rotation. Attribute shortcuts: transform.position, transform.anchorPoint, transform.scale, transform.rotation, transform.zRotation, transform.opacity, transform.xRotation, transform.yRotation. AE matchNames (ADBE Position, ADBE Rotate Z, …) work too.
Effects: Effects.property(effectName | index) → an effect group; effect.property(paramName | index) → its parameter. Effects.addProperty(matchName | name) adds an effect and returns its group; effect.name = "…" renames it (expressions find effects by name — effect("Bounce")("Slider")); effect.remove(). Parameters of a just-added effect can be set in the same run only for the Expression Controls.
Effects addProperty knows by AE matchName:
| AE matchName | Brio effect |
|---|---|
ADBE Slider Control | Slider Control (param Slider) |
ADBE Angle Control | Angle Control (Angle) |
ADBE Checkbox Control | Checkbox Control (Checkbox) |
ADBE Point Control | Point Control (Point) |
ADBE Color Control | Color Control (Color) |
ADBE Gaussian Blur 2 | Gaussian Blur |
ADBE Glo2 | Glow |
ADBE Drop Shadow | Drop Shadow |
ADBE HUE SATURATION | Hue/Saturation |
ADBE Easy Levels2, ADBE Pro Levels2 | Levels |
ADBE CurvesCustom | Curves |
ADBE Exposure2 | Exposure |
ADBE Invert | Invert |
ADBE Fractal Noise | Fractal Noise |
ADBE Posterize Time | Frame Rate |
Brio's own effect names work too (e.g. "Bloom", "Roughen Edges", "Bend"). Other AE effects are not available.
Text: layer.property("Source Text") (also layer.text.sourceText, property("Text").property("Source Text"), matchName ADBE Text Document). Its value is a TextDocument; change it and give it back with setValue(doc) (or setValue("new text")):
var st = layer.property("Source Text");
var doc = st.value;
doc.text = doc.text.replace(/old/g, "new");
doc.fontSize = 96;
st.setValue(doc);
TextDocument fields: text (lines split by \r or \n), font / fontFamily (a family name, or a PostScript name like "Arial-BoldMT" — its Bold/Italic is applied), fontStyle (read-only), fontSize, fillColor [r, g, b] 0–1 with applyFill, strokeColor, strokeWidth, applyStroke, strokeOverFill, justification (left / center / right), allCaps, fauxBold, fauxItalic, tracking (1/1000 em). Read-only: leading, autoLeading. A font Brio can't draw throws, naming close ones. Source Text can't be keyframed or given an expression from a script.
Text animators: property("Text").property("Animators").addProperty("ADBE Text Animator"), then animator.property("Properties").addProperty(…) for Position, Anchor Point, Scale, Rotation, Skew, Opacity and Tracking Amount (ADBE Text Position 3D, ADBE Text Anchor Point 3D, ADBE Text Scale 3D, ADBE Text Rotation, ADBE Text Skew, ADBE Text Opacity, ADBE Text Tracking Amount), and animator.property("Selectors").addProperty("ADBE Text Selector") (or "ADBE Text Wiggly Selector"). Range selectors: Start / End / Offset (keyable), and under Advanced: Units, Based On (characters, words or lines), Mode, Amount (keyable), Shape (Square, Ramp Up, Ramp Down, Triangle, Round, Smooth), Smoothness, Randomize Order, Random Seed; Ease High / Low only at 0. Wiggly: Max / Min Amount, Wiggles/Second, Correlation (keyable), Random Seed. animator.name, enabled, remove(). Animators already on a layer read back the same way. Brio creates the animator when its first property arrives; until a selector is added it selects everything, as in AE. Fill / stroke colour, character offset, blur and expression selectors aren't in Brio and throw.
Shape layers made by addShape(): layer.property("Contents").addProperty(matchName) builds AE's contents — ADBE Vector Group (its property("Contents") and property("Transform"): Position, Anchor Point), ADBE Vector Shape - Rect (Size, Position, Roundness), ADBE Vector Shape - Ellipse (Size, Position), ADBE Vector Shape - Star (Type 1 star / 2 polygon, Points, Position, Rotation, Inner Radius, Outer Radius), ADBE Vector Graphic - Fill (Color, Opacity) and ADBE Vector Graphic - Stroke (Color, Opacity, Stroke Width). A group's Fill and Stroke paint every path in it and in its subgroups. Set values with setValue; contents of a new layer can't be keyframed in the same run, except Repeaters and Trim Paths (below).
Existing shape layers: layer.property("Contents") lists the layer's groups (ADBE Vector Group, by name or 1-based index); each group's property("Contents") holds its paths (Rectangle / Ellipse / Polystar Path, named as in AE, or by the name you gave them), then a Fill 1 and Stroke 1 when the group shows them. Read and setValue or setValueAtTime (keyframes): Rect Size, Position, Roundness; Ellipse Size, Position; Polystar Points, Position, Rotation, Outer/Inner Radius; Fill Color, Opacity; Stroke Color, Opacity, Stroke Width. A group's Fill/Stroke paints every shape in it. addProperty adds a group to the layer, and rectangles, ellipses, polystars, a Fill or a Stroke to a group (Brio's groups don't nest); remove() removes a group, a path, or a Fill/Stroke; groups and paths can be renamed; a group's enabled hides it; moving a group's Transform Position moves its shapes. Keyframes already on contents aren't read (numKeys is 0).
Repeaters and Trim Paths (ADBE Vector Filter - Repeater, ADBE Vector Filter - Trim) become Brio's group Shape Ops, on new layers and existing ones. Repeater: Copies, and under Transform: Anchor Point, Position, Scale (even x and y), Rotation, End Opacity (a fade per copy); Offset, Composite and Start Opacity only at their defaults (0, Below, 100%). Trim Paths: Start, End, Offset — measured along AE's outline (an ellipse from the top, a rectangle from the top of its right edge), so the same values trim the same part of the shape — and Trim Multiple Shapes, accepted but Brio always trims each path on its own (AE's Individually); a group uses its first Trim Paths. All of them can be keyframed with setValueAtTime, on a new layer too. One added to a group works on that group; one added to the layer's Contents (or, on a new layer, a parent group) works on every group under it — on an existing layer it shows after the groups for the rest of the run, and as one per group after that. Brio's own Grid and Transform Shape Ops list as BRIO Shape Op - Grid / - Transform (on/off and remove() only).
Drawn paths (ADBE Vector Shape - Group, "Path 1"): property("Path").value is an AE Shape — vertices, inTangents, outTangents (offsets from their vertex) and closed, Y-down and relative to the group, at the playhead. setValue(shape) replaces the points (any number; a path with keyframes needs setValueAtTime, as in AE). setValueAtTime(t, shape) keys the pose — with the same number of points as the path has; a still path can also take a different count (its points are replaced, then keyed). new Shape() makes one; addProperty("ADBE Vector Shape - Group") adds a drawn path to a group, on new layers (addShape()) and existing ones. A path made of several subpaths shows its first one; the others are kept as they are.
Masks: layer.mask / layer.property("Masks") (ADBE Mask Parade) lists the layer's masks (by name or 1-based index); addProperty("Mask") (or "ADBE Mask Atom") adds one. Each mask has name, maskMode (MaskMode.NONE, ADD, SUBTRACT, DIFFERENCE), inverted, locked, remove(), moveTo(index), and properties Mask Path (an AE Shape in layer space — read, setValue, setValueAtTime, as for drawn shape paths), Mask Feather ([x, y]; Brio has one feather, so the larger is used), Mask Opacity (0–100) — both keyframable — and Mask Expansion (0 only). A new mask starts as a 100 px square until you set its path.
Cameras: layer.property("Camera Options") (cameraOption, ADBE Camera Options Group): Zoom (px) and Focus Distance (px) — keyframable — plus Depth of Field (0/1), Aperture and Blur Level (100). The Transform group's Point of Interest (ADBE Anchor Point on a camera, as in AE; also layer.pointOfInterest) is [x, y, z] in comp coordinates and keyframable.
Lights: layer.property("Light Options") (lightOption, ADBE Light Options Group): Intensity (%), Color, Cone Angle (the full angle), Cone Feather (%), Radius, Casts Shadows (0/1); Shadow Darkness (100), Shadow Diffusion (0) and Falloff (1) stay at those values. Light settings aren't keyframable in Brio. layer.lightType changes any light's type (Parallel, Spot, Point, Area).
Markers: comp.markerProperty and layer.property("Marker") (also layer.marker, ADBE Marker) — numKeys, keyTime(i), keyValue(i) (a MarkerValue), nearestKeyIndex(t), setValueAtTime(t, new MarkerValue("comment")) (replaces a marker at that time), setValuesAtTimes, addKey(t), removeKey(i). Times are comp seconds, layer markers too. Brio keeps a marker's comment, duration and label (0–16); chapter, url, frameTarget, cue points and parameters work during the run but aren't saved.
Layers and property groups can be called like property(), as in ExtendScript: layer("Transform")("Position"), layer.mask(1), layer("Effects")("Blur").
Property
| Member | Notes |
|---|---|
name, matchName | |
value | at the playhead, AE units |
valueAtTime(t, preExpression) | the keyframed value (expressions run in the render, not in scripts) |
setValue(v) | throws if the property has keyframes (AE's rule) |
setValueAtTime(t, v), setValuesAtTimes(times, values) | add or replace keys |
numKeys, keyTime(i), keyValue(i), nearestKeyIndex(t) | 1-based |
addKey(t) | returns the new key's index |
removeKey(i) | |
setInterpolationTypeAtKey(i, inType, outType) | Brio eases per segment: the out type wins |
keyInInterpolationType(i), keyOutInterpolationType(i) | |
setTemporalEaseAtKey(i, inEase[], outEase[]) | becomes one curve for the segment leaving key i |
expression | get/set the property's expression ( "" removes it) |
expressionEnabled | get/set |
isTimeVarying, canSetExpression |
Effect parameters: numbers as numbers, checkboxes as 1/0, points as [x, y], colours as [r, g, b, a] 0–1, menus as their 1-based choice. Only number parameters can be keyframed from a script.
Panels and dialogs (ScriptUI)
A script's ScriptUI opens as a Brio panel you can dock: the script's top-level this is a Panel (as for a script in AE's ScriptUI Panels folder), so the usual var w = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Title") docks; a new Window("palette") that is show()n opens as a panel too. Each button click or edit runs its handler as one run — on the comp that is open at that moment, as one undo step. The panel stays until it's closed (its tab, or win.close()).
- Building:
container.add(type, bounds, text, properties)and resource strings (w.add("group { orientation: 'row', ok: Button { text: 'OK' } }"),new Window("palette { … }")); named children become properties (w.grp.ok). - Containers:
group,panel,tabbedpanel/tab. Layout:orientation(row / column / stack),alignChildren,alignment,spacing,margins,preferredSize(orsize/boundsfor the size),characterson text fields. Pixel positions inboundsare ignored. - Controls:
statictext,edittext(multiline,readonly,noecho),button,iconbutton(shows its text),checkbox,radiobutton(siblings exclusive),slider/scrollbar(value,minvalue,maxvalue),dropdownlist/listbox(items,add("item", text),selection,removeAll(),find(text); listboxmultiselect),progressbar. - Common:
text,value,enabled,visible,helpTip,name,properties,parent,children,remove(),removeAll(),layout.layout()(no-op),show(),hide(),close(),center(). - Events:
onClick,onChange(field left or Enter pressed, slider released, list picked),onChanging(as you type or drag),onDoubleClick(listbox),onShow,onClose,addEventListener("click", fn),notify(). - Dialogs:
new Window("dialog")+show()returns1(OK) or2(Cancel) as in AE; a button named or labelledok/cancelcloses it without a handler, andclose(n)returnsn. Brio shows the dialog and then runs the script again from the top with the answers filled in, so code beforeshow()runs twice — keep it free of side effects (it is, in almost every script). - Not drawn:
imageandgraphics(custom drawing, fonts, colours) — they're accepted and ignored.ScriptUI.environment.keyboardStatereads all keys up.
Expressions
prop.expression = "…" adds a live expression — the After Effects expression language: value, time, wiggle(f, a), loopOut() / loopIn() (cycle, pingpong, offset, continue), linear / ease / easeIn / easeOut / clamp, random / seedRandom / noise, valueAtTime, thisComp.layer("Name"), thisLayer, index, inPoint / outPoint, .transform.*, effect("Name")("Param"), Math.*, var, if/else, ? :, arrays as vectors, strings with toFixed. No loops (for, while) and no user functions inside an expression.
Not available (yet)
Keying a different number of path points on an animated path, shape group scale/rotation, per-character text styles, mask expansion and the Intersect/Lighten/Darken modes, file access, ScriptUI panels' custom graphics.