Verticalizer

Compositionv1.9.4PANEL
Turn a landscape source into a rigged, resolution-agnostic split-screen portrait comp in one click — two independently zoomed, panned, cropped, styled, and retimed windows that can also morph into an animatable picture-in-picture layout, each with its own corner radius, edge feather, border, drop shadow, and keyframable speed ramp, drag-handle framing, and platform-aware safe zones, with zero quality loss from double-resampling.

Category: composition · Version: 1.9.4 · UI: PANEL (dockable, three-button palette)

What it does

Select a landscape video — a footage item in the Project panel, a composition, or an AV layer already placed in a comp — and press Create Portrait Rig. Verticalizer builds a complete portrait composition (1080×1920 by default, or any size chosen from the panel's Rig Size section) that shows the same source twice: once in a top window, once in a bottom window, each independently zoomed, panned, cropped to its own aspect mode, rotated, styled, and retimed, separated by a divider bar and a background stack. The two windows can also be morphed — keyframably — into a picture-in-picture layout, where one window floats as a small card over the other while the background card grows to fill the comp. Nothing is baked. Every visual decision — where the split sits, how each half is framed and cropped, whether a window's footage rescales with its window or holds a fixed punch-in, each card's own corner rounding, feather, border, shadow, retiming, colors, captions, audio routing, platform safe zones — stays live and adjustable after the build, either by dragging on-canvas handles or by dialing sliders in one effect panel.

It's built for the "landscape clip → vertical repost" workflow: reaction-style edits, before/after splits, dual-camera angles, or simply reframing a single 16:9 source to read well in a 9:16 feed without cropping away the parts that matter.

How the rig works

  • Two control surfaces, cleanly split. Nothing lives in both places. On-canvas handles — four small draggable shape layers — own everything spatial and keyframable: the split line's Y position, each window's pan offset and rotation, and the caption band's position (both axes, free). The Verticalizer pseudo effect, on the [CTRL] Verticalizer guide layer, owns everything numeric and stylistic: zoom, aspect, inset, gap, per-card corner radius/feather/opacity, per-card border/shadow, per-card speed/time-offset, divider, background mode, audio routing, caption styling, and safe-zone display. AE expressions only read forward, so anything that needs its own keyframable spatial path (drag, pan, rotate) has to live on a real transform property, not an effect slider — hence the split.
  • Fully independent cards, top to bottom. As of 1.2.0, every card-scoped control is duplicated per side and lives inside that side's own group tree (Top Card / Bottom Card) — Corner Radius, Edge Feather, Border Width/Color, and all four Shadow parameters used to be shared, which meant rounding one window forced the other to round too. Now Top Corner Radius and Bottom Corner Radius (and every other pair) are addressed independently, and no expression can accidentally read the wrong side's value because every card control name carries its side as a prefix.
  • Resolution-agnostic by construction. No expression bakes 1080/1920 as a literal. Every expression opens with var CW = thisComp.width, CH = thisComp.height; and derives entirely from those — the split clamp is the full comp height 0 .. CH (since 1.7.3 — sliding the SPLIT handle to an edge collapses that pane completely), the SPLIT handle's X rail is thisComp.width 0.05, and the safe-zone bands are stored as ratios (e.g. 380/1920) rather than pixel counts. The rig keeps working after a comp-settings resize, and Apply Rig FFX can wire the same expressions onto a non-1080×1920 composition. Only the source's* width/height are still baked as numeric literals, because they describe the footage rather than the comp.
  • One shared source precomp, three instances, two of them track-matte crops. The footage is imported once into a SRC <name> precomp at its native resolution, then the comp is instantiated three times inside the portrait comp: once behind TOP Matte (as TOP <name>), once behind BOT Matte (as BOT <name>), and once more as BG <name> for the blurred background. Cropping the two windows uses per-layer track mattes (alpha from the matte shape), not masks: masks live in layer space and would travel with the footage as it pans and zooms, whereas the matte shapes stay fixed in comp space exactly where the window should be. This is also what makes free corner rounding and edge feathering possible (a box blur on each matte's alpha).
  • Per-section window resolver. Each window's box is independently resolved from Aspect (Cover / 1:1 / 16:9 / 9:16 / Fit Height / Fit Width) and Inset, using the source's aspect ratio for the two Fit modes — Fit Height pins the window to the full section height and lets it crop the sides if the source is too wide; Fit Width pins to the full section width and letterboxes/crops vertically. The same resolver logic is re-emitted inline into every consumer (matte size/position, footage scale/position, card size/position), so they can never disagree with one another — there's no shared runtime state between expressions.
  • Keyable Aspect eases a shape change instead of cutting it (1.8.0). Aspect was always a hold-keyframeable popup, but every window quantity came from a single read of it, so a hold key changed the window box, matte, footage fit, card, border, shadow, and PiP hole all on the same frame — untenable the moment a gliding PiP Transition had an aspect change keyed inside it, since the PiP eased while the shape jumped. Each card now carries an Aspect Ease slider (in frames, default 15) directly under its Aspect: for that many frames after an Aspect keyframe, the window geometry is a straight interpolation from the previous key's aspect geometry to the new one's. The resolver emits everything downstream of the popup as one expression-local geomFor(aspectMode) function, called for both the previous and next aspect and lerped between them — the same function also returns the PiP floating box's aspect ratio, so the split-box fit and the PiP card's shape ease together rather than one snapping mid-blend. Split, Seam Mode, Inset, and PiP Transition all keep animating live underneath the blend. Ease 0, or a card whose Aspect carries no keyframes (or a first Aspect key, which has no previous key to blend from), reproduces the pre-1.8.0 behaviour to the pixel — see "Keyable aspect" below for the authoring recipe.
  • Seam Mode governs the middle; Inset only ever governs the comp-facing edges. From 1.3.0, a card's Inset shrinks its section on the comp-edge side only — the seam edge (where the window meets the split line) is owned by two new Layout controls instead. Seam Mode = Gap holds the seam at exactly Gap pixels regardless of either card's Inset. Seam Mode = Inset instead pins each card's seam edge at that card's own Inset × Seam Inset % from the split line and ignores Gap entirely — 50% means the two half-insets add up to one full inset of seam width (e.g. two 30 px insets land a 30 px seam, matching the 30 px outer framing). This fixes a 1.2.0 bug where the seam silently read Top Inset + Gap + Bottom Inset — insetting a card to frame it off the comp edge blew the seam open in the middle, and winding Gap negative until the numbers cancelled was the only workaround. Because the resulting window box is asymmetric about its section, the window centre — used by the matte, the footage, the card, and the Z TOP / Z BOT pan-null anchors alike — is the centre of that box, not the centre of the section.
  • Zero quality loss punching in. TOP <name> and BOT <name> have Collapse Transformations enabled, so their scale/position/rotation concatenate with the comp's render instead of being resampled twice. As long as effective zoom stays at or below 100% of source resolution, there's no extra resampling pass beyond the one final render. BG <name> runs without collapse — it's blurred anyway, so there's nothing to protect.
  • Source dimensions are read live, with a numeric fallback. Every scale/position expression opens with a build-time literal for sw/sh, then immediately tries to overwrite it with thisLayer.source.width / .height inside a try/catch — the literal only survives if that live read throws. In practice this means re-linking the footage in the SRC precomp to media of a different resolution does reflow the fill/zoom math, it isn't purely a frozen build-time constant.
  • Auto-fill zoom baseline. 100% zoom on a window means that window is exactly covered edge-to-edge by the source (fill = max(windowWidth / sw, windowHeight / sh) × 100). Zoom above 100% punches in further; the effective scale is always fill × Zoom / 100.
  • Scale Mode decides what that baseline is measured against. Per-card Top Scale Mode / Bottom Scale Mode (1.4.0) is Fill Window by default — the baseline is max(winW / sw, winH / sh), so any control that moves the window (SPLIT handle, Gap, Seam Mode, Seam Inset %, Inset, Aspect) also re-fits and resizes the footage, which breathes a finished punch-in every time the split moves. Fixed instead takes the baseline from the comp (max(CW / sw, CH / sh)), which nothing in the window resolver can touch, so dragging the split only re-crops the matte and the footage holds exactly the size Zoom gives it (100% = covers the whole comp). The scale expression and the pan-clamp share one builder (eSourceScale), so the clamp always agrees with the true drawn size; in Fixed mode the footage is allowed to end up smaller than its window (Zoom below that window's own fill percentage), and the clamp then centres it, showing matte void at the edges — that's the mode being used as asked, not a bug.
  • PiP Transition lerps the whole window box, it doesn't switch modes. The PiP group (1.5.0) adds PiP Panel (Top | Bottom — which window floats), PiP Transition (0–100%, keyframeable) and PiP Scale (the floating window's width as a percentage of comp width). The per-section window resolver now derives the ordinary split box first (sW/sH/sCx/sCy) and a PiP target second — the floating card's target is "comp width × PiP Scale% wide, that card's own Aspect tall, centred on the new orange PIP Handle"; the background card's target is "the whole comp shrunk by its own Inset on all four sides" — then every window quantity is the straight lerp between split and PiP target at t = PiP Transition / 100. Because it's a lerp and not a mode switch, t = 0 multiplies every delta by zero and reproduces the pre-1.5.0 split geometry bit for bit, and because every consumer (matte, card, footage scale/position, pan clamp, the Z TOP / Z BOT nulls) already re-derives that one resolver, all of them morph together for free the moment PiP Transition is keyframed. The Divider's opacity is multiplied by (1 - t) so the split line fades out as the PiP engages.
  • Stacking-independent PiP occlusion via a matte hole, not a restack. Layer order is fixed at build time (TOP Matte / TOP <name> / TOP Card always sit above BOT Matte / BOT <name> / BOT Card), so with PiP Panel = Bottom the floating card would otherwise be buried under the background card's full-comp window — and an expression cannot reorder layers at runtime. Each matte's shape group therefore carries a second "Hole" rect and a Merge Paths in Subtract mode (contents order Window, Hole, Merge Paths, Fill — the operator consumes both paths above it): the hole is [0, 0] on the floating card's own matte, and on the background card's matte it is exactly the other card's lerped window inflated by that card's Border Width and rounded by its Corner Radius + Border Width — i.e. the other card's card rect, expressed in this matte's own rect space as (otherCentre − thisCentre). The background window is cut away precisely where the floating card draws, whichever side floats, with zero runtime restacking.
  • The card's own fill gets the same hole, closing a 1.7.4 leak. TOP Card / BOT Card are filled rects, normally covered edge-to-edge by their own window's video — the matte hole above only ever removed that video, so the background card's own fill (its border rect) showed through the floating card's hole as a flat solid block instead of the floating card's video. As of 1.7.4, both cards carry the identical Window-minus-Hole subtract as their mattes — the same three hole expressions, reused verbatim — live only on whichever card is currently acting as the background card. (This also field-confirmed the one remaining unknown in the Merge Paths math: After Effects evaluates a Subtract operator as Window minus Hole, not the reverse.)
  • Stacking-independent PiP shadow, fixed in 1.6.0. A card draws its own drop shadow and can't escape its own place in the stack, so through 1.5.0 PiP Panel = Bottom buried the floating card's shadow under the background card's full-comp window and it was never seen — shipped as a known caveat. 1.6.0 lifts the shadow off the card entirely: a dedicated PiP Shadow layer (an ADBE Drop Shadow with Shadow Only on, so it contributes only the cast shadow — TOP Card / BOT Card still draw the card itself) sits above both card assemblies, sized to the floating card's own card rect and read from whichever card PiP Panel currently nominates. Because that layer sits above the floating card, its shadow's soft inner edge would otherwise spill back over the card casting it, so a PiP Shadow Matte layer — the whole comp minus that same card rect, built from the identical Merge Paths/Subtract pair the window mattes use — is applied as its alpha matte; a track matte runs after effects, so it clips the finished shadow rather than the rect that casts it. Both layers' opacity is PiP Transition, so the pair contributes exactly nothing at 0, and the floating card's own shadow is cross-faded out by (1 - t) over the same interval so the two are never drawn at once. The result: either PiP Panel choice now casts a real, visible shadow over the background card — there's no longer a "Top only" caveat.
  • Pan follows the handle 1:1 in comp pixels — dragging a PAN handle moves the footage under it like grabbing the footage directly. With that side's Clamp Pan on, the effective offset the position expression reads is clamped (using s / 100 as the scale factor) so the source can never reveal empty space at the window edge; the handle itself is unaffected by the clamp and will visually separate from where the footage stops. The clamp math ignores rotation — see Notes.
  • Card style rides the same window box. TOP Card / BOT Card draw a rounded rect sized to window + 2 × <side> Border Width with a fill driven by that side's Border Color (opacity 0 unless that side's Border Width or Shadow Opacity is nonzero) and an ADBE Drop Shadow driven by that side's Shadow Opacity / Softness / Distance / Angle — the opacity expression auto-detects whether the installed build's Drop Shadow opacity property is 0–100 or 0–255 and scales accordingly.
  • Per-card retiming without unlocking anything — integrated frame-by-frame from 1.3.0. Time Remapping is switched on for both TOP <name> and BOT <name> at build time, but the keyframes After Effects creates are left untouched and an expression on ADBE Time Remapping overrides them: it reads Top Speed / Bottom Speed (0–400%, 0 freezes the frame) off the controller and integrates it — every frame from the layer's in point contributes Speed × frameDuration — so keyframing Speed 100 → 0 → 100 freezes on a held frame and resumes from that same frame instead of jumping. (The 1.2.0 formula, (time - inPoint) × Speed, re-derived position from the current speed and the whole elapsed time, so restoring speed after a freeze made the clip jump forward to where a constant-speed clip would have been.) The integral loop only runs while Speed actually carries keyframes; a static Speed still takes the closed-form (time - inPoint) × Speed / 100 fast path, so there's no per-frame cost unless a ramp is genuinely in play. Top Time Offset / Bottom Time Offset changed from seconds to frames in 1.3.0 and adds to the integrated position — keyframing it while Speed is 0 is the manual, frame-accurate scrub workflow — and a read-only Top Source Frames / Bottom Source Frames slider bakes the source's length in frames as a numeric-literal expression, so the legal Time Offset range is visible in the same panel. This exists because the footage layers are locked at the end of the build — see the lock+shy pass below — so keyframing Time Remap by hand on the (locked) layer is no longer possible. If AE refuses to enable Time Remapping on a layer, the build still finishes but warns by name instead of silently shipping a rig where those controls do nothing. See "Per-card time remapping" below.
  • The floating window gets out of its own way. Run as a floating palette, Verticalizer closes itself automatically after a successful Create Portrait Rig / Add Caption / Apply Rig FFX — the rig it just built is the thing worth looking at, and the panel is one keystroke away. A docked panel has no API to close itself and is left alone (same split Linearizer uses). A run that fails, or has to warn about something (e.g. Time Remapping refusing to enable on a layer), leaves the window open either way, so the buttons stay reachable for a retry.
  • Audio Source is exclusive routing, not a mix. Each window's Audio Levels expression reads 0 dB when it's the selected source and -192 dB (effectively silent) otherwise; the BG <name> background instance has audioEnabled forced off at build time regardless of the popup, so only one clip (or neither, in Muted) is ever heard.
  • Safe zones live in absolute comp space. From 1.2.0 the Safe Zones layer is pinned at anchor point [0,0] / position [0,0] with no transform expression at all, which makes its layer space identical to comp space, and every one of the four bands plus the safe-rect outline carries an expression on both its Size and its Position, stated directly in comp coordinates. Previously the bands were positioned relative to a layer parked at the comp centre, which is what made the overlay land misplaced and undersized — see "Safe-zone numbers" below.
  • Lock + shy hygiene. Every structural layer (mattes, footage, cards, divider, background stack, caption box, safe zones, the two Z nulls) is shied and locked at the very end of the build, and the master comp gets Hide Shy Layers turned on — the timeline opens showing only 7 layers: [CTRL] Verticalizer, the five handles (including the new PIP Handle), and Caption Text. This runs dead last because a locked layer refuses property writes, and it can only happen once every expression, the stacking pass, and the track-matte assignment are already done. See "Comp architecture" below.
  • Handle transforms are pinned by static literal expressions (1.5.0). Every handle plus [CTRL] Verticalizer carries a constant "[100,100]" Scale expression, and SPLIT / CAPTION / PIP / [CTRL] also carry a constant "0" Rotation expression — so a stray corner-drag or rotate gesture in the viewer can no longer distort a handle glyph or silently change what the rig reads. The one deliberate exception is TOP PAN Handle / BOT PAN Handle's Rotation, which stays genuinely free and keyframeable because that value is the per-window footage rotation input. Each handle's inner Glyph group is pinned the same way (Rotation "0", Position "[0,0]"); its Scale already carries the Handle Size expression.
  • Handles auto-hide when they stop being an input (1.6.0, caption case fixed in 1.6.1). PIP Handle hides itself whenever PiP Transition is 0 — a floating-window control that can't do anything at 0 is just clutter over a plain split. Every caption handle hides itself whenever its own Show Captions is off (the first caption's handle reads the controller's Show Captions; a handle owning its own Verticalizer Caption effect reads that instance's copy instead). A caption handle's opacity expression originally addressed the caption effect by a control name instead of the effect name, which errors silently in AE and left the handle visible no matter what — 1.6.1 fixes the lookup, so a caption's own Show Captions now genuinely hides its handle. The master Show Handles still wins over both — turning it off hides every handle regardless of PiP or caption state.
  • Each added caption gets a fully independent control panel (1.6.0). Through 1.5.0 every caption read the controller's single Captions group and the single CAPTION Handle, so a second caption couldn't be resized, recoloured, or switched off without moving the first one with it. Add Caption now builds a complete, independent caption: its own Caption NN Handle carrying its own instance of a second pseudo effect, Verticalizer Caption, plus its own text and box wired to that instance instead of the controller — see "The Verticalizer Caption effect" below for its full control set.
  • Captions can be driven by markers instead of typed text, with matching fades (1.7.0). Every caption — the built-in one and every one added by Add Caption — now carries Caption Source (Manual | Markers) right under Show Captions. Manual is the untouched pre-1.7.0 behavior; Markers turns the caption into a subtitle track: markers live on the caption's own text layer (comp markers as fallback), each marker's comment shows for exactly its duration and nothing in the gaps (zero-duration markers show nothing; too-short markers with fades on throw a descriptive expression error). A new Caption Fade checkbox plus an integer Fade Frames fades the text and its box in together — inside the marker segment in Markers mode, against the text layer's own in/out points in Manual mode. See "Marker-driven captions" below for the full workflow and semantics.
  • Rig Size decides what gets built, not what the rig can survive (1.6.0). The rig was always resolution-agnostic at runtime (every expression already reads thisComp.width / .height), but Create Portrait Rig was hard-wired to build at 1080×1920. The panel now carries a Rig Size section — presets for HD / 2K / 4K portrait, a 4:5 feed size, square, and 720 portrait, plus two typeable, clamped W/H fields — so the comp AE creates can be any of those sizes or a custom one. See "Rig Size & source-coverage warning" below.
  • Building again from the same source stamps a version, never collides (1.6.0). Re-running Create Portrait Rig on a source that already has a Verticalizer/<name>/ folder no longer creates a same-named second folder and comp — the base name is version-stamped before anything is created (<name>, then <name> v2, v3, …), and every derived name (job folder, SRC <name>, <name> Portrait, the TOP / BOT / BG footage layers) is built from that one stamped base. See "Versioned rig names" below.
  • Everything else is expression-linked by exact name. Divider, BG <name> / BG Fill (blurred video / solid / gradient / transparent), the caption pair, and the four safe-zone bands plus the safe-rect outline all read the pseudo effect and the handles by layer/effect name at evaluation time — thisComp.layer("...") and C.effect("Verticalizer")("...") lookups, no baked index.
  • Build safety. registerPseudoEffect() runs before any project mutation, so a missing "Allow Scripts to Write Files" preference throws before anything is created. Every applied pseudo-effect instance is still probed for a control ("Top Aspect Ease" as of 1.8.0 — a name that exists only in the current 69-control Verticalizer9 definition) — the match name is fresh in 1.8.0 so no stale definition can already be holding it, but the probe costs nothing and stays in place as a safety net for any future in-place control change. The caption panel is probed the same way, against its own most distinctive control ("Caption Max Width"). If any build step throws, the undo group is closed and the partial build is automatically undone. A legacy-expression-engine project is switched to javascript-1.0 automatically.

Comp architecture

📁 Verticalizer/<name>/                (<name> is version-stamped: "<name>", "<name> v2", "v3", ... — see below)
  SRC <name>                    precomp at native source resolution, source's own pixel aspect ratio
  <name> Portrait                CW×CH from the panel's Rig Size (default 1080×1920), square pixels, fps/duration from source
    1  [CTRL] Verticalizer      shape, guide (label Lavender), VISIBLE — carries the "Verticalizer" pseudo effect;
                                 opacity is never wired to Show Handles, so it stays visible regardless
    2  SPLIT Handle              shape, guide (Cyan), VISIBLE — split line Y; X locked to 5% of comp width
    3  TOP PAN Handle             shape, guide (Green), VISIBLE — parented to Z TOP
    4  BOT PAN Handle             shape, guide (Fuchsia), VISIBLE — parented to Z BOT
    5  PIP Handle                 shape, guide (Orange), VISIBLE — floating-window centre, free on BOTH axes;
                                 hides itself when PiP Transition == 0 (1.6.0)
    6  CAPTION Handle             shape, guide (Yellow), VISIBLE — first caption's band position, free on BOTH
                                 axes; hides itself when the controller's Show Captions is off (1.6.0)
    7  Z TOP                      null, shy + disabled + LOCKED — top window centre (expression target only)
    8  Z BOT                      null, shy + disabled + LOCKED — bottom window centre
    9  Safe Zones                 shape, guide, SHY + LOCKED — 4 translucent red bands + a green safe-rect outline,
                                 pinned at anchor/position [0,0] so its layer space IS comp space
    10 Caption Text                point text, VISIBLE — style- and shrink-to-fit driven; stays unlocked so
                                 Add Caption can restyle it and the editor can type into it
    11 Caption Box                 shape, SHY + LOCKED — rounded box behind the text, fill opacity independent
                                 of its stroke
    12 PiP Shadow Matte            shape rect, SHY + LOCKED — whole comp MINUS the floating card's rect, applied
                                 as the alpha matte for PiP Shadow (1.6.0)
    13 PiP Shadow                  shape rect, SHY + LOCKED — ADBE Drop Shadow, Shadow Only ON, sized to whichever
                                 card PiP Panel nominates; opacity = PiP Transition (1.6.0)
    14 TOP Matte                   shape rect, SHY + LOCKED — alpha source for the top track matte; carries a
                                 second "Hole" rect + Merge Paths (Subtract), live only when TOP is the
                                 background card in a PiP morph
    15 TOP <name>                  SRC instance, SHY + LOCKED, trkMat = alpha(TOP Matte), collapse ON,
                                 Top Speed / Top Time Offset drive an expression on Time Remap
    16 TOP Card                    shape, SHY + LOCKED — border ring + drop shadow, sized to the top window box
    17 BOT Matte                   shape rect, SHY + LOCKED — alpha source for the bottom track matte; same
                                 Hole + Merge Paths (Subtract) trick, live only when BOT is the background card
    18 BOT <name>                  SRC instance, SHY + LOCKED, trkMat = alpha(BOT Matte), collapse ON,
                                 Bottom Speed / Bottom Time Offset drive an expression on Time Remap
    19 BOT Card                    shape, SHY + LOCKED — border ring + drop shadow, sized to the bottom window box
    20 Divider                     shape, SHY + LOCKED — seam bar across the split line; opacity × (1 - PiP Transition/100)
    21 BG Darken                   comp-size solid, SHY + LOCKED — darken overlay above BG <name>, own opacity control
    22 BG <name>                   SRC instance, SHY + LOCKED, cover-scaled + blur, collapse OFF, audio forced off
    23 BG Fill                     comp-size solid + Gradient Ramp, SHY + LOCKED — Solid / Gradient modes

Adding a caption beyond the first (Add Caption) inserts its own Caption NN Handle (VISIBLE, carrying its own Verticalizer Caption pseudo effect instance) plus a Caption NN Text / Caption NN Box pair, kept together with the handles at the top of the stack — see "The Verticalizer Caption effect" and "Versioned rig names" below.

<name> is the sanitized, version-stamped source name (extension stripped, trimmed, then <name>, <name> v2, <name> v3, … the first time that name is already taken — see "Versioned rig names"). The whole build — both project items, all 23 layers of the freshly-built rig (one caption pair), the pseudo effect registration — runs inside its own undo group (Verticalizer: Create Rig), and the script guards against re-rigging a comp that already has a [CTRL] Verticalizer layer. TOP Card / BOT Card sit directly below their footage layers rather than above, so they never interfere with the matte-adjacency fallback used on pre-setTrackMatte() AE versions. The layer order is fixed at build time and never changes at runtime — PIP Panel decides which card the matte hole (and, from 1.6.0, the PiP Shadow pair) favors, not which layer stacks on top (see the PiP occlusion and PiP shadow notes above).

Only 7 of the 23 layers are visible in the default timeline: [CTRL] Verticalizer, the five handles (SPLIT, TOP PAN, BOT PAN, PIP, CAPTION), and Caption Text. The other 16 (PiP Shadow, PiP Shadow Matte, both mattes, both footage layers, both cards, the divider, the three background layers, the caption box, Safe Zones, and both Z nulls) are shy and locked, and the master comp opens with Hide Shy Layers on. Turn Hide Shy Layers off in the timeline (or clear a specific layer's lock switch) if you need manual access to any of them — a locked layer refuses every property write, so nothing about the rig's expressions depends on staying unlocked.

Versioned rig names (1.6.0)

Building Create Portrait Rig twice from the same source used to file a second folder and a second <name> Portrait comp under the exact same names as the first, so the Project panel gave no way to tell the two builds apart. The base name is now version-stamped before anything is created: versionedBaseName() checks whether <name> is already taken — a candidate counts as taken if the job folder exists under Verticalizer/ or a project item named SRC <name> or <name> Portrait exists anywhere in the project (not just in that folder) — and if so tries <name> v2, then v3, and so on up to v999 before falling back to a timestamp suffix. Because the check is project-wide, a rig whose folder was deleted or whose comps were dragged elsewhere still correctly counts as taken, so a new build can never silently collide with (or get confused for) an older one. Every derived name — the job folder, SRC <name>, <name> Portrait, and the TOP <name> / BOT <name> / BG <name> footage layers — is built from that one stamped base, so a whole rig's naming stays internally consistent.

Rig Size & source-coverage warning (1.6.0, warning metric fixed in 1.6.1)

The panel's Rig Size section replaces the old hard-wired 1080×1920: a dropdown offers HD Portrait (1080×1920), 2K Portrait (1440×2560), 4K Portrait (2160×3840), Feed 4:5 (1080×1350), Square (1080×1080), 720 Portrait (720×1280), or Custom, backed by two typeable W/H pixel fields. Typing in either field switches the dropdown to Custom automatically; both fields are clamped to After Effects' own composition limits (4–30,000 px) when Create Portrait Rig is pressed. A headless run can set $.global.VERTICALIZER_WIDTH / VERTICALIZER_HEIGHT instead of driving the UI. Nothing about the rig's runtime behavior depends on this size — every expression already reads thisComp.width / .height — the panel only decides what the master comp gets built at; the one build-time pixel constant left (the caption block's starting Y) is stored as a fraction of comp height (1420/1920), so it lands in the same relative spot at any Rig Size.

Before building, the source is checked against the chosen size — measured against a window, not the whole comp (fixed in 1.6.1; see below). If the check fails, a dialog names both sizes and the cover percentage and offers Yes — build the rig at this size or No — go back and change the rig size (No is the default button, so a stray Return is the safe answer). Answering No creates nothing — the undo group is closed with no project mutation and the panel stays open on the Rig Size fields. A headless run (VERTICALIZER_SILENT / VERTICALIZER_AUTORUN) logs the same warning and proceeds automatically, since there's nobody there to answer a dialog.

The metric is the default split window, not comp cover (1.6.1). 1.6.0 shipped this check against max(rigW / sourceW, rigH / sourceH) — comp cover — which is the wrong yardstick for a split-screen rig: only the blurred background layer ever covers the whole comp, and a blur doesn't care about source resolution. What actually shows sharp pixels is a card window, roughly comp width × half the comp height at the default 50/50 split, so a 1920×1080 landscape source dropped into a 1080×1920 rig only needs about 87% of its resolution to fill its window at 100% — yet the 1.6.0 formula reported "178%" and nagged on every ordinary landscape-to-portrait build. 1.6.1 measures windowFill = max(CW / sourceW, (CH / 2) / sourceH) instead, and warns only above 110% (a real upscale, not headroom); the old "source is so much larger the rig throws most of it away" warning is gone entirely — extra source resolution is punch-in headroom, not a problem worth a dialog.

The control panel — Verticalizer effect (on [CTRL] Verticalizer)

Match name Pseudo/IVGD Verticalizer9 (bumped again in 1.8.0 — see the versioning note below). 69 controls, 105 total entries once every group, end-group, and label is counted. The tree is 9 top-level groups — Interface, Layout, PiP, Top Card, Bottom Card, Divider, Background, Captions, Safe Zones — with Top Card and Bottom Card each nesting the same four mirrored sub-groups: Framing, Style, Border & Shadow, Time. Group and control order is part of the pseudo effect's binary contract: don't reorder when hand-editing the rig; any control-set change ships under a bumped match name. This is one instance, applied once to [CTRL] Verticalizer — it does not carry per-caption controls; see "The Verticalizer Caption effect" below for those.

▸ INTERFACE

ControlTypeRangeDefaultPurpose
Show HandlesCheckboxOnToggles visibility of all four on-canvas handles (not the [CTRL] tab itself — its opacity isn't wired to this).
Handle SizeSlider25–400%100%Scales the on-canvas handle glyphs.

▸ LAYOUT

ControlTypeRangeDefaultPurpose
GapSlider0–400 px12 pxSeam spacing between the top and bottom windows when Seam Mode is Gap — centred on the split line and unaffected by either card's Inset. Ignored entirely when Seam Mode is Inset.
Seam ModePopup, 2 itemsGap · InsetGapChooses what governs the middle seam. Gap holds it at exactly the Gap value no matter what either card's Inset is (Inset only ever shrinks that card's comp-facing edges). Inset instead pins each card's seam edge at that card's own Inset × Seam Inset % from the split line and ignores Gap — this is the mode to use when you want the middle spacing to visually match the outer framing.
Seam Inset %Slider0–100%50%Only takes effect when Seam Mode is Inset. Percentage of a card's own Inset applied to its seam-facing edge. 50% means the two cards' half-insets add up to one full inset of seam width — e.g. two 30 px Insets at 50% put each card 15 px off the split line, a 30 px seam matching a 30 px outer Inset.
Audio SourcePopup, 3 itemsTop Video · Bottom Video · MutedTop VideoExclusive audio routing: the selected window plays at 0 dB, the other is forced to -192 dB. BG <name> never carries audio regardless of this setting.

▸ PIP

New in 1.5.0. Blends the whole rig, keyframably, between the ordinary split layout and a floating picture-in-picture card — see "Picture-in-picture morph" below for the mechanics.

ControlTypeRangeDefaultPurpose
PiP PanelPopup, 2 itemsTop · BottomTopWhich window floats as the PiP card as PiP Transition rises; the other window is the background card and grows to fill the comp minus its own Inset.
PiP TransitionSlider0–100%0%Keyframeable blend factor between the split layout (0) and the full PiP layout (100). Every window quantity — matte, footage, card, pan clamp, the Z TOP / Z BOT nulls — is the straight lerp between its split value and its PiP value at this fraction, so 0 reproduces the split geometry exactly and any value in between is a straight interpolation.
PiP ScaleSlider10–100% (typeable to 5–200%)40%The floating card's width as a percentage of comp width; its height follows that card's own Aspect control. Centred on the draggable orange PIP Handle.

▸ TOP CARD ▸ TOP FRAMING

ControlTypeRangeDefaultPurpose
Top AspectPopup, 6 itemsCover · 1:1 · 16:9 · 9:16 · Fit Height · Fit WidthCoverShapes the top window box within its section. Cover fills the full inset box (footage still auto-fills/crops per Zoom). The fixed-ratio items (1:1, 16:9, 9:16) center a box of that ratio inside the inset box. Fit Height / Fit Width use the SOURCE's own aspect ratio: Fit Height pins the box to the full section height (cropping sides if the source is too wide for the width available); Fit Width pins to the full section width (letterboxing/cropping vertically). Hold-keyframeable — see "Keyable aspect" below.
Top Aspect EaseSlider (integer)0–120 frames15New in 1.8.0. For this many frames after a Top Aspect keyframe, the top window's geometry (split fit and PiP floating-box aspect alike) eases linearly from the previous key's aspect to the new one instead of cutting on the keyframe's frame. 0, or an Aspect with no keyframes, is the pre-1.8.0 instant-cut behavior.
Top InsetSlider0–200 px0 pxPixel margin applied to the top section's comp-facing edges (top, left, right) before the Aspect box is resolved inside it — as of 1.3.0 the seam-facing (bottom) edge is governed separately by Layout's Seam Mode / Seam Inset %, not by this control.
Top Scale ModePopup, 2 itemsFill Window · FixedFill WindowNew in 1.4.0. Chooses what Top Zoom is a percentage of. Fill Window (the pre-1.4.0 behavior) takes the baseline from the resolved window (max(winW / sw, winH / sh)), so anything that moves the window — SPLIT, Gap, Seam Mode, Seam Inset %, Top Inset, Top Aspect — also rescales the footage. Fixed takes the baseline from the comp instead (max(CW / sw, CH / sh)), which nothing in the window resolver can touch, so dragging the split only re-crops the matte and the footage holds exactly the size Top Zoom gives it (100% = covers the whole comp). In Fixed mode the footage can end up smaller than its window if Top Zoom is below that window's own fill percentage — the pan clamp then centres it and matte void shows at the edges, which is the mode doing what it was asked to do.
Top ZoomSlider10–1000%100%Multiplier on the Scale Mode baseline — 100% exactly covers the top window box (Fill Window) or the whole comp (Fixed).
Top Clamp PanCheckboxOnClamps the effective pan offset read by the top window's position expression so the source can't expose empty space at the edge.

▸ TOP CARD ▸ TOP STYLE

ControlTypeRangeDefaultPurpose
Top Corner RadiusSlider0–200 px0 pxRounds the top window's (and TOP Card's) corners. Independent of Bottom Corner Radius since 1.2.0.
Top Edge FeatherSlider0–200 px0 pxSoftens the top window's matte edge (box blur on TOP Matte's alpha, 3 iterations). Independent of Bottom Edge Feather since 1.2.0.
Top OpacitySlider0–100%100%Top window opacity.

▸ TOP CARD ▸ TOP BORDER & SHADOW

ControlTypeRangeDefaultPurpose
Top Border WidthSlider0–50 px0 pxRing width drawn around the top window box on TOP Card (the card grows by 2 × Top Border Width beyond the window). Independent of Bottom Border Width since 1.2.0.
Top Border ColorColor#FFFFFFTOP Card fill/border color.
Top Shadow OpacitySlider0–100%0%Drop Shadow opacity on TOP Card (the underlying AE effect's own 0–100 vs 0–255 range is detected and compensated for at build time).
Top Shadow SoftnessSlider0–200 px30 pxDrop Shadow blur radius.
Top Shadow DistanceSlider0–200 px12 pxDrop Shadow offset distance.
Top Shadow AngleAngle135°Drop Shadow direction.

TOP Card is only visible (opacity 100) when Top Border Width or Top Shadow Opacity is nonzero — with both at their defaults the card layer renders nothing.

▸ TOP CARD ▸ TOP TIME

ControlTypeRangeDefaultPurpose
Top SpeedSlider0–400%100%Playback rate for TOP <name>, keyframable. 0% freezes the frame the clip is parked on; 100% is real time. As of 1.3.0 the position is the integral of Speed (accumulated frame by frame from the layer's in point), so keyframing Speed 100 → 0 → 100 resumes from the frame the freeze parked on instead of jumping. Drives an expression on the layer's Time Remap, so it works even though the layer is locked.
Top Time OffsetSlider±1000 frames (typeable to ±100,000)0Shifts TOP <name> in time, keyframable — changed from seconds to frames in 1.3.0. Adds to the integrated Speed position and clamps to the source's last addressable frame. Keyframe this while Top Speed is 0 to scrub the source frame-by-frame by hand.
Top Source FramesSlider (read-only)0–1,000,000baked at buildRead-only readout of TOP <name>'s source length in frames. The script bakes this as a numeric-literal expression at build time, which is also what makes the slider un-draggable — it exists so the legal Top Time Offset range is visible in the same panel.

▸ BOTTOM CARD ▸ BOTTOM FRAMING

ControlTypeRangeDefaultPurpose
Bottom AspectPopup, 6 itemsCover · 1:1 · 16:9 · 9:16 · Fit Height · Fit WidthCoverSame as Top Aspect, for the bottom window. Hold-keyframeable — see "Keyable aspect" below.
Bottom Aspect EaseSlider (integer)0–120 frames15New in 1.8.0. Same as Top Aspect Ease, for the bottom window.
Bottom InsetSlider0–200 px0 pxSame as Top Inset, for the bottom window (comp-facing edges are bottom/left/right; the seam-facing top edge is governed by Seam Mode / Seam Inset %).
Bottom Scale ModePopup, 2 itemsFill Window · FixedFill WindowSame as Top Scale Mode, for the bottom window.
Bottom ZoomSlider10–1000%100%Same as Top Zoom, for the bottom window.
Bottom Clamp PanCheckboxOnSame as Top Clamp Pan.

▸ BOTTOM CARD ▸ BOTTOM STYLE

ControlTypeRangeDefaultPurpose
Bottom Corner RadiusSlider0–200 px0 pxRounds the bottom window's (and BOT Card's) corners. Independent of Top Corner Radius since 1.2.0.
Bottom Edge FeatherSlider0–200 px0 pxSoftens the bottom window's matte edge. Independent of Top Edge Feather since 1.2.0.
Bottom OpacitySlider0–100%100%Bottom window opacity.

▸ BOTTOM CARD ▸ BOTTOM BORDER & SHADOW

ControlTypeRangeDefaultPurpose
Bottom Border WidthSlider0–50 px0 pxRing width drawn around the bottom window box on BOT Card. Independent of Top Border Width since 1.2.0.
Bottom Border ColorColor#FFFFFFBOT Card fill/border color.
Bottom Shadow OpacitySlider0–100%0%Drop Shadow opacity on BOT Card.
Bottom Shadow SoftnessSlider0–200 px30 pxDrop Shadow blur radius.
Bottom Shadow DistanceSlider0–200 px12 pxDrop Shadow offset distance.
Bottom Shadow AngleAngle135°Drop Shadow direction.

BOT Card is only visible (opacity 100) when Bottom Border Width or Bottom Shadow Opacity is nonzero.

▸ BOTTOM CARD ▸ BOTTOM TIME

ControlTypeRangeDefaultPurpose
Bottom SpeedSlider0–400%100%Playback rate for BOT <name>, keyframable. 0% freezes the frame. Same integrated-position behavior as Top Speed.
Bottom Time OffsetSlider±1000 frames (typeable to ±100,000)0Shifts BOT <name> in time, keyframable — changed from seconds to frames in 1.3.0. Same manual-scrub workflow as Top Time Offset (keyframe while Bottom Speed is 0).
Bottom Source FramesSlider (read-only)0–1,000,000baked at buildRead-only readout of BOT <name>'s source length in frames. Same as Top Source Frames.

▸ DIVIDER

ControlTypeRangeDefaultPurpose
Show DividerCheckboxOffShows/hides the seam line between the two windows.
ThicknessSlider0–100 px6 pxLine weight of the divider, centred on the split line. Independent of Gap — nothing clamps the two together, so a thick divider can overlap the windows if it exceeds the Gap.
Divider ColorColor#FFFFFFDivider fill color.

▸ BACKGROUND

ControlTypeRangeDefaultPurpose
BG ModePopup, 4 itemsBlurred Video · Solid · Gradient · TransparentBlurred VideoWhich background layer is visible: BG <name> opacity is 100 only in mode 1; BG Fill's Gradient Ramp is opaque only in modes 2 and 3 (mode 2 collapses both ramp stops to Color A).
BG BlurSlider0–500 px120 pxBox blur applied to BG <name>.
BG DarkenSlider0–100%30%Opacity of the dedicated BG Darken black solid layered above BG <name> (forced to 0 in Transparent mode).
BG Color AColor#101014Solid color, and the gradient's first stop.
BG Color BColor#2A2A33Gradient's second stop (ignored in Solid mode).

▸ CAPTIONS

This group styles only the first caption (Caption Text / Caption Box, driven by CAPTION Handle). Every caption added afterward with Add Caption owns an independent copy of these same ten controls on its own Verticalizer Caption effect instance instead — see "The Verticalizer Caption effect" below.

ControlTypeRangeDefaultPurpose
Show CaptionsCheckboxOnShows/hides the caption text and its background box (drives both layers' opacity, and the shared CAPTION Handle's own opacity from 1.6.0).
Caption SourcePopup, 2 itemsManual · MarkersManualNew in 1.7.0. Manual types text directly into the caption's text layer (the pre-1.7.0 behavior, untouched). Markers turns the caption into a subtitle track driven by the markers on this caption's own TEXT layer (falling back to the comp's markers if the text layer has none): the text becomes the active marker's comment for its segment, and nothing in the gaps. See "Marker-driven captions" below.
Caption FadeCheckboxOffNew in 1.7.0. Fades the caption text and its box together (outline included) over Fade Frames. In Markers mode the fade lives strictly inside the marker segment; in Manual mode it runs against the text layer's own in/out points. Off produces a fade factor of exactly 1 — bit-for-bit the 1.6.1 rig.
Fade FramesSlider (integer)0–120 frames (typeable to 999)0New in 1.7.0. Frame count for Caption Fade's in/out ramp. A segment (or trim) shorter than two fades caps its peak below full opacity instead of overshooting.
Captions SizeSlider10–300 px64 pxCaption font size (fed into text.sourceText.style.setFontSize(), rounded).
Caption Max WidthSlider10–100%86%Caption is scaled down (never up — capped at 100%) to fit within this percent of comp width.
Text ColorColor#FFFFFFCaption text fill.
Box ColorColor#000000Caption background box fill.
Box OpacitySlider0–100%65%Fill opacity of the box's rect — lives on the shape's fill, not the layer, so the outline stroke below stays fully solid at any Box Opacity.
Box Stroke WidthSlider0–20 px0 pxOutline stroke width around the caption box.
Box Stroke ColorColor#FFFFFFOutline stroke color.
Box Padding XSlider0–200 px28 pxHorizontal padding between text and box edge.
Box Padding YSlider0–200 px16 pxVertical padding between text and box edge. The box's corner roundness tracks this value (min(Box Padding Y, 24)), rather than being a fixed constant.

▸ SAFE ZONES

ControlTypeRangeDefaultPurpose
Show Safe ZonesCheckboxOnToggles the red safe-zone overlay bands and the green safe-rect outline.
Safe Zone OpacitySlider0–100%40%Opacity of the whole Safe Zones layer (bands and outline together).
PlatformPopup, 4 itemsAll (conservative) · TikTok · Reels · ShortsAll (conservative)Selects which platform's UI-clearance numbers the overlay shows.

The Verticalizer Caption effect (1.6.0, marker source + fade added 1.7.0)

A second, standalone embedded pseudo effect, applied once per caption added via Add Caption (never to [CTRL] Verticalizer — the controller only ever carries the main Verticalizer effect). Match name Pseudo/IVGD VerticalizerCap2 (bumped from VerticalizerCap1 in 1.7.0 — see the versioning note below), display name Verticalizer Caption, 13 controls across two groups (Caption, Caption Box) that exactly mirror the thirteen controls of the main effect's Captions group — same names, same ranges, same defaults — so an added caption looks and behaves identically to the first one until it's actually changed. It lives on that caption's own Caption NN Handle, which is why a second caption can be resized, recoloured, switched off, or switched to marker-driven text without touching the first: there is no shared state between the two beyond the expression body, which is identical either way (see "Independent captions" below).

ControlTypeRangeDefaultPurpose
Show CaptionsCheckboxOnShows/hides this caption's text and box, and hides Caption NN Handle itself when off (Show Handles still wins).
Caption SourcePopup, 2 itemsManual · MarkersManualNew in 1.7.0. Markers reads the markers on this caption's own Caption NN Text layer (falling back to the comp's markers if the text layer has none), so any number of standalone captions can each run their own subtitle track without colliding. See "Marker-driven captions" below.
Caption FadeCheckboxOffNew in 1.7.0. Fades this caption's text and box together over Fade Frames, same semantics as the built-in caption's Caption Fade.
Fade FramesSlider (integer)0–120 frames (typeable to 999)0New in 1.7.0. Frame count for this caption's fade ramp.
Captions SizeSlider10–300 px64 pxFont size for this caption only.
Caption Max WidthSlider10–100%86%Shrink-to-fit width cap for this caption only.
Text ColorColor#FFFFFFThis caption's text fill.
Box ColorColor#000000This caption's background box fill.
Box OpacitySlider0–100%65%This caption's box fill opacity (stroke stays solid).
Box Stroke WidthSlider0–20 px0 pxThis caption's outline stroke width.
Box Stroke ColorColor#FFFFFFThis caption's outline stroke color.
Box Padding XSlider0–200 px28 pxThis caption's horizontal text/box padding.
Box Padding YSlider0–200 px16 pxThis caption's vertical text/box padding (roundness tracks it, same as the first caption).

Independent captions, one shared expression body. The only thing that differs between the first caption and an added one is where its controls (and, from 1.7.0, its markers) come from — the prelude that binds FX (and, in Markers mode, MKSRC). ePre() binds FX to [CTRL] Verticalizer's Verticalizer effect (what the first caption's Caption Text / Caption Box / CAPTION Handle use); ePreCaption(handleName) binds FX to that named handle's own Verticalizer Caption instance. When Caption Source is Markers, ePreCaptionMk additionally binds MKSRC to the marker source — since 1.7.1, the caption's own TEXT layer, with the comp's markers as the fallback when that layer has none — so each caption track owns its own marker row. Every expression body below the prelude — text document, position, scale, opacity, box size, roundness, position, opacity, plate color/opacity, stroke color/width — is written once and shared by both paths, so the two can never drift apart from each other. Add Caption duplicates the first caption's layers (to inherit its font and any styling already applied) and then explicitly repoints every one of those duplicated expressions at the new handle; missing even one would leave a caption that looks independent in the UI while silently still following caption one underneath.

Marker-driven captions (1.7.0)

Any caption — the built-in one, or any one added by Add Caption — can be switched from typed text to a marker-fed subtitle track by setting its Caption Source to Markers.

Which markers, and why it's different per caption:

  • Since 1.7.1 every caption — built-in and standalone alike — reads the markers on its own text layer (select Caption Text / Caption NN Text and press ): the layer you already select to type into is the layer that carries its subtitles. If the text layer has no markers at all, the caption falls back to the composition's marker row (added with and no layer selected), so a classic ruler-level subtitle track still drives the built-in caption with zero setup.
  • Because each caption prefers its own text layer's markers, any number of captions can run private, parallel subtitle tracks without colliding — and a caption with no markers of its own simply mirrors the comp track (handy, but worth knowing).

Workflow:

  1. For the main subtitle track, either add markers directly on Caption Text, or add comp markers with no layer selected — the comment is what the caption displays. Give each marker a duration (drag its right edge, or set it in the marker dialog) — zero-duration markers show nothing.
  2. For an additional, independent subtitle track, select that caption's Caption NN Text layer and add layer markers on it the same way.
  3. Set the caption's Caption Source to Markers. The caption now shows each marker's comment for exactly that marker's duration, and shows nothing between markers.

Semantics (SubtitleForge's, exactly): the active segment for a marker is [marker.time, marker.time + marker.duration) — half-open, so the caption disappears on the exact frame the marker ends. A zero-duration marker never shows anything — duration is what makes a marker a caption, so give every subtitle marker a duration (drag its right edge or set it in the marker dialog). And a guard rail (1.7.2): with Caption Fade on, a marker shorter than fade-in + fade-out (2 × Fade Frames) makes the caption's opacity expression throw an error naming the marker's frame count and the required minimum — the bad marker surfaces as an expression error instead of silently mis-fading. Overlapping or adjacent markers resolve to the last one starting at or before the current time, so a new marker supersedes a still-running one instead of the two fighting over the caption.

Fades live inside the segment, not against it. With Caption Fade on, the fade ramp in Markers mode runs strictly inside the active marker's own segment — the first and last Fade Frames of [segStart, segEnd) ramp up and down — so the caption never draws a single frame outside its own marker. A segment shorter than two fade lengths simply caps its peak below full opacity rather than overshooting into the next marker. In Manual mode there's no marker segment to speak of, so the fade instead runs against the text layer's own in/out points (the box reads the text layer's in/out, not its own, so the two can never fade apart even if their trims drift). Either way, the fade factor is emitted by one builder and multiplied into the text layer's opacity and the box layer's opacity — never into the box rect's own fill opacity, because the outline stroke has no opacity of its own and can only fade by riding the layer; putting the factor on the fill as well would square it on the plate while leaving the outline linear. With Caption Fade off, the factor is exactly 1 and a Manual-mode rig is bit-for-bit what 1.6.1 produced.

Per-card time remapping

Each window's ADBE Time Remapping property carries an expression instead of manual keyframes, because the footage layers (TOP <name> / BOT <name>) end the build locked. From 1.3.0 the expression integrates Speed frame-by-frame rather than multiplying it by total elapsed time:

var C = thisComp.layer("[CTRL] Verticalizer");
var FX = C.effect("Verticalizer");
var fd = thisComp.frameDuration;
var sp = FX("Top Speed");                  // "Bottom Speed" on the bottom window
var off = FX("Top Time Offset") * fd;      // "Bottom Time Offset" on the bottom window, frames -> seconds
var acc = 0;
if (sp.numKeys > 0) {
  // Speed is keyframed: walk every frame from the layer's in point and accumulate
  // Speed(t) * frameDuration, so a freeze-and-resume holds its position exactly.
  var n = Math.round((time - inPoint) / fd);
  for (var i = 0; i < n; i++) {
    acc += (sp.valueAtTime(inPoint + i * fd) / 100) * fd;
  }
} else {
  // No keyframes: closed-form fast path, same cost as before.
  acc = (time - inPoint) * sp.value / 100;
}
clamp(acc + off, 0, <source duration in seconds> - fd)

**Why the integral, not just (time - inPoint) * Speed:** that older (1.2.0) formula re-derives position from the current speed and the whole elapsed time, which is only correct while Speed never changes. Keyframe Speed 100 → 0 → 100 to freeze on a frame and the moment Speed comes back up, the formula recomputes position from scratch and the clip jumps to wherever a constant-speed clip would be by then — the freeze itself was never accounted for. Integrating instead means every already-elapsed frame banks its own contribution, so restoring Speed after a freeze resumes exactly from the frame the freeze parked on, and a Speed ramp behaves like a genuine variable-speed transport.

The manual-scrub workflow: because Time Offset now adds frames to that integrated position, setting Speed = 0 (freezing the clip) and then keyframing Time Offset becomes a way to hand-place the source frame-by-frame — each Time Offset keyframe parks the frozen frame at a specific source frame, independent of playback speed. Top Source Frames / Bottom Source Frames show the legal range for Time Offset (the source's total frame count) directly in the same panel, as a read-only, un-draggable slider.

Performance: the per-frame loop only runs when Speed actually carries keyframes on that side — a static (un-keyframed) Speed takes the closed-form fast path shown above and costs one multiply, identical to 1.2.0. A long comp with a heavily keyframed Speed on one or both windows will re-walk the accumulated frames on every evaluation, so previewing may run slower in that case; there is no performance cost at all for rigs that never keyframe Speed.

Both Speed and Time Offset are keyframable, so a speed ramp — or a hard cut to a different moment in the source — is authored entirely on the [CTRL] Verticalizer panel, without ever unlocking the footage layer. Time Remapping is switched on for both windows at build time and each layer's out point is restored to the comp's duration afterward; if After Effects refuses to enable Time Remapping on a layer (rare, but possible depending on footage type), the build still completes and warns naming the layer, rather than silently shipping a rig where that side's Time controls do nothing.

Picture-in-picture morph (1.5.0, shadow fixed in 1.6.0)

PiP Transition is a blend, not a switch. The per-section window resolver already worked out the ordinary split box (sW / sH / sCx / sCy); 1.5.0 adds a second target box (a PiP target) and lerps between the two at t = PiP Transition / 100:

var t = FX("PiP Transition") / 100;
// ... resolve the split box (sW, sH, sCx, sCy) exactly as before ...
// ... resolve the PiP target box (pW, pH, pCx, pCy) ...
var winW  = sW  + (pW  - sW)  * t;
var winH  = sH  + (pH  - sH)  * t;
var winCx = sCx + (pCx - sCx) * t;
var winCy = sCy + (pCy - sCy) * t;

At t = 0 every delta is multiplied by zero, so the geometry is bit-for-bit the pre-1.5.0 split layout — the feature cannot alter a rig that never touches it. The floating card's PiP target is "comp width × PiP Scale% wide, that card's own Aspect tall, centred on the PIP Handle"; the background card's PiP target is "the whole comp shrunk by its own Inset on all four sides." Every consumer — matte, card, footage scale/position, the pan clamp, and the Z TOP / Z BOT pan nulls — already re-derives this one resolver, so all of them morph together automatically the moment PiP Transition is keyframed; nothing had to be wired twice.

Occlusion without restacking layers. TOP Matte / TOP <name> / TOP Card are always stacked above BOT Matte / BOT <name> / BOT Card — layer order is fixed at build time, and an expression cannot reorder layers. So with PiP Panel = Bottom, the floating bottom card would otherwise be buried under the background top card's full-comp window. Instead, each matte's shape group carries a second "Hole" rect and a Merge Paths in Subtract mode, in contents order Window, Hole, Merge Paths, Fill (the operator consumes both paths stacked above it):

  • On the floating card's own matte, the hole collapses to [0, 0] — it does nothing.
  • On the background card's matte, the hole is exactly the other card's lerped window, inflated by that card's Border Width and rounded by its Corner Radius + Border Width — i.e. the other card's card rect, expressed in this matte's own rect space as (otherCentre − thisCentre).

The background window is cut away precisely where the floating card draws, whichever side is floating, with zero runtime restacking. At PiP Transition = 0 the two windows are separated by the seam, so the subtraction removes nothing that was ever visible; the hole can only cross the seam if a Border Width exceeds half the Gap, and that strip is covered pixel-for-pixel by the floating card's own opaque border rect (which is stacked above that matte).

The card's own fill needed the identical hole (fixed in 1.7.4). TOP Card / BOT Card are filled rects — normally their entire interior is covered edge-to-edge by that window's own video, so the fill never shows. Through 1.7.3 the Window/Hole/Merge Paths/Subtract trick above lived only on the mattes, so it only ever cut the video out of the background card's window; the background card's own fill rect underneath was untouched and still solid across its whole window, and it showed straight through the hole as a flat-colored block sitting behind the floating card's video instead of behind nothing. 1.7.4 gives both cards' shape groups the same second "Hole" rect and Merge Paths (Subtract) their mattes already carry — the exact same three hole expressions, reused verbatim — live only on whichever card is currently the background card. Diagnosing this live also settled the one remaining unknown in the Merge Paths math: After Effects evaluates a Subtract operator as Window minus Hole (not Hole minus Window), which the fix's expressions now rely on directly rather than guessing.

A real shadow for either PiP Panel choice (fixed in 1.6.0). Through 1.5.0, PiP Panel = Bottom buried the floating card's own drop shadow under the background card's full-comp window — a card can't escape its own fixed place in the layer stack, so the shadow (drawn by BOT Card) was only ever visible through the same hole TOP Matte punches, with no room outside it for a shadow to fall; that shipped as a known caveat. 1.6.0 lifts the shadow off the card entirely, onto a dedicated pair sitting above both card assemblies:

  • PiP Shadow — a rect sized to the floating card's own card rect (window + that card's Border Width), carrying an ADBE Drop Shadow with Shadow Only on, so the layer contributes the cast shadow and nothing else (TOP Card / BOT Card still draw the card itself — drawing it twice would double the border).
  • PiP Shadow Matte — the whole comp minus that same card rect (the identical Merge Paths/Subtract pair the window mattes use), applied as PiP Shadow's alpha matte. A track matte runs after effects, so it clips the finished shadow rather than the rect that casts it — without it, the shadow (sitting above the card) would spill its soft inner edge back over the card supposedly casting it.

Both layers read whichever card PiP Panel currently nominates, and their opacity is PiP Transition, so the pair contributes exactly nothing at 0. The floating card's own ADBE Drop Shadow is simultaneously cross-faded out by (1 - t) over the same interval, so the two are never drawn at once and split-mode shadows stay bit-identical to before. The background card's shadow is untouched either way — it grows to fill the comp, so its own shadow simply leaves the frame. Net effect: there is no longer a "Top only" caveat — either PiP Panel choice now casts a visible shadow.

The Divider's opacity is multiplied by (1 - t), so the split line fades out smoothly as the PiP morph engages and is fully gone by PiP Transition = 100.

Keyable aspect (1.8.0)

Through 1.7.4, Top Aspect / Bottom Aspect were already hold-keyframeable popups, but nothing downstream of them treated a keyframe as anything other than "the aspect is different from this frame on" — the resolver read the popup once per evaluation and every window quantity (matte, footage fit, card, border, shadow, and the PiP hole) came out of that single read, so a hold key changed all of it on one frame. That was fine as long as nothing else was moving, but it broke the case the feature exists for: a PiP Transition keyed to glide from 0 to 100 over a range of frames, with a hold keyframe on a card's Aspect dropped somewhere inside that range so the floating card also changes shape mid-flight. The PiP glided; the aspect jumped.

What it does. Each card now has an Aspect Ease slider (Top Aspect Ease / Bottom Aspect Ease, in frames, default 15) directly under its Aspect popup. For that many frames after an Aspect keyframe, the window geometry — both the split-mode fit and the PiP floating box's aspect ratio — is a straight linear interpolation from the geometry of the previous keyframe's aspect to the geometry of the new one, so the shape change reads as a smooth move instead of a cut. Everything else stays live through the blend: the split can be dragged, Seam Mode / Inset can be anything, and PiP Transition can be mid-glide, all at the same time as the aspect eases.

Authoring recipe. Key PiP Transition across a range (say frames 1726–1756, 0 → 100), then somewhere inside that same range drop a single hold keyframe on the card's Aspect — e.g. a Cover bottom card becoming a 1:1 floating card partway through the PiP move. Give that card an Aspect Ease (15 frames is a reasonable default) and the pane reshapes smoothly mid-flight instead of snapping on the Aspect keyframe's exact frame.

How it works. The resolver emits everything downstream of the Aspect popup as one expression-local function, geomFor(aspectMode), which returns the finished window box for a given aspect mode. That function is deliberately shared by the split-box fit and the PiP floating box's aspect ratio — both read the same popup, so easing one and not the other would leave the picture-in-picture snapping halfway through its own morph. Everything the aspect does not decide — the box edges coming from the SPLIT handle, Gap, Seam Mode, and Inset; the PiP Transition factor; which card is floating — is resolved once, outside geomFor(), and captured by it, so both the previous and next aspect's geometries are evaluated against the current box at the current PiP amount. That's what lets the split be dragged and the PiP keep running while an aspect blend is in flight without either one glitching.

Inert until used. With no Aspect keyframes on a card, or that card's Aspect Ease at 0, or once playback is past the ease window, geomFor() is called exactly once per evaluation and the emitted arithmetic is the 1.7.4 resolver verbatim — an existing rig, or a card that never keyframes Aspect, is unchanged to the pixel. Blending also requires a previous keyframe to blend from, so a card's very first Aspect key never moves anything by itself, and two keyframes closer together than the ease value always blend from the immediately previous one rather than chaining through an intermediate key.

Repairing or upgrading a rig (1.9.0)

Through 1.8.0 there was exactly one answer to a rig that stopped working — a renamed layer, an expression cleared by a paste, a project round-tripped through a machine missing the pseudo effect — and to a rig that was simply built by an older script version and so is missing whatever shipped since: rebuild it, and lose every keyframe, every caption, and every tweak already made in that comp.

The fork. Pressing Create Portrait Rig on a composition that already contains [CTRL] Verticalizer — whether that comp is the active one or a CompItem selected in the Project panel — now opens a three-way dialog instead of building (or refusing to build) outright: Re-apply / Upgrade Rig, Create New Rig, or Cancel. A headless run can skip the dialog: $.global.VERTICALIZER_REPAIR = true makes an autorun choose repair whenever the target already holds a rig; without it, an autorun always builds fresh, exactly as before 1.9.0.

What Re-apply / Upgrade Rig does:

  • Rewires every expression the rig owns, on rig layers only. Every wire the build would have written — handles, mattes, cards, background, captions, safe zones, time remap, PiP occlusion — is re-derived from the current builders and written fresh. Layers the rig doesn't own (a logo, a title, an alternate take called "TOP Secret") are never touched, because repair only ever acts on a fixed inventory of rig layer names (plus every Caption NN ... set) and the three source instances — and a source instance only counts if it is both TOP/BOT/BG-named and an AV layer with a comp source, so a same-named layer of the wrong kind is left alone rather than guessed at.
  • Re-derives baked source dimensions from the actual PiP sources, so a repaired rig's scale math reflects whatever footage is actually wired in now, not whatever was true at the original build time.
  • **Upgrades a stale pseudo-effect instance to the current one — main controller and every per-caption instance alike — with full value and keyframe migration by display name.** Every control the old and new panel share by name carries its value and every keyframe across (time, value, and interpolation — hold stays hold), including the one deliberate rename this migration has to know about, PiP AmountPiP Transition (1.6.0). Control-driven expressions migrate along with the values, not just the raw numbers.
  • Adds whatever structural pieces the current version expects and an older rig doesn't have, using the exact same builders a fresh build uses — matte and card PiP holes, the PiP Shadow / PiP Shadow Matte pair, PIP Handle, and the Z TOP / Z BOT nulls are the ones an older rig is commonly missing.
  • Reports exactly what it did and what it couldn't, in one completion dialog: counts of expressions rewritten and structures added, plus every control the repair could not carry across named, never silently dropped or guessed at.

Nothing is lost, because nothing is rewritten except expressions. Wiring during a repair only ever assigns expressions; the handful of static, non-expression writes a build makes go through a helper that refuses to touch a keyframed property instead of overwriting it. The one case repair cannot carry over safely is a PAN handle whose Position already carries keyframes — re-parenting it would mean rewriting those keyframes' values, which repair will not do — so that handle is left exactly where it is and named in the completion report as something to re-parent by hand, instead of quietly leaving it half-wired.

How it works, for maintainers. The wiring logic used to live only inside each builder function; it's now pulled out into one wire* function per layer type, and both a fresh build and a repair call the same functions — so "how a matte is wired" is stated in exactly one place, and a repaired rig cannot drift from a freshly built one. That refactor is also why a fresh build is unchanged to the property: it runs the same code it always did, just from one shared place. Every expression write, in both paths, now goes through a counting setExprSafe helper, so a write that fails says so in the report instead of shipping a rig with a silently dead property.

Like a fresh build, a repair runs inside its own AE undo group and is automatically undone in full if any step throws partway through.

On-canvas handle reference

HandleGlyph / colorFree channelsDrives
SPLIT HandleCyan (~#00C8FF) rounded pill + up/down chevronsY only (X locked to thisComp.width * 0.05 via expression; Rotation locked to 0, Scale locked to [100,100])Split line Y position. Every window/matte/zero-null/divider expression clamps its read of this value to the full comp height 0 .. CH (since 1.7.3) — drag the handle to the bottom edge to hide the bottom pane entirely, or to the top to hide the top pane; past the edge the rig just stops responding (rubber-band). Default y = comp-vertical-center (50/50 split).
TOP PAN HandleGreen (~#37D67A) ring + 4-tick crosshairX, Y (joined position), Rotation — all genuinely free/keyframable; Scale locked to [100,100] via expressionTop window's pan offset (1:1 comp pixels) and rotation. Parented to shy, locked null Z TOP, whose position tracks the top window's centre — so local (0,0) always means "no pan," and the handle rides along as the split or Aspect/Inset settings move the window.
BOT PAN HandleFuchsia (~#FF5AC8), same glyphSame as Top PanBottom window's pan offset and rotation. Parented to Z BOT.
PIP HandleOrange (~#FF8A12) rounded plate with four corner ticksX and Y both free (no lock on either axis) — Rotation and Scale locked to 0 / [100,100] via expressionCentre of the floating PiP card — read by the window resolver's PiP target, so dragging it repositions the floating window in proportion to PiP Transition. Default position is the same build-time spot as SPLIT Handle's legacy default (0.72 × comp width, 0.25 × comp height), a seed only — nothing drives it afterward. Hides itself when PiP Transition is 0 (1.6.0) — Show Handles still wins.
CAPTION HandleYellow (~#FFC400) rounded plate with two text-line glyphsX and Y both free (no lock on either axis) — Rotation and Scale still locked to 0 / [100,100] via expressionThe first caption's band position — read by Caption Text / Caption Box's position expressions, which in turn read the controller's Captions group. Default position = comp horizontal center, y = 1420/1920 of comp height (a build-time seed, not expression-driven afterward). Hides itself when the controller's Show Captions is off (1.6.0) — Show Handles still wins. Does not drive any caption added via Add Caption — see Caption NN Handle below.
Caption NN HandleYellow, same glyph as CAPTION HandleX and Y both free — Rotation and Scale locked to 0 / [100,100] via expressionNew in 1.6.0. Built once per Add Caption call (Caption 02 Handle, Caption 03 Handle, …), carrying its own Verticalizer Caption pseudo-effect instance and driving that caption's own Caption NN Text / Caption NN Box pair exclusively — moving it never affects any other caption's band. Seeded just below the previous handle (90 px lower, clamped to stay on comp) so successive Add Caption calls don't stack handles exactly on top of one another. Hides itself when its own Show Captions is off, independent of every other caption's state.

Handle transform lock matrix (added 1.5.0):

Handle / layerScaleRotationNotes
SPLIT Handlelocked [100,100]locked 0X also locked to thisComp.width * 0.05
TOP PAN Handlelocked [100,100]freeRotation is the per-window footage rotation input
BOT PAN Handlelocked [100,100]freeSame as Top Pan
PIP Handlelocked [100,100]locked 0
CAPTION Handlelocked [100,100]locked 0
Caption NN Handlelocked [100,100]locked 0Same lock pattern, applied to every caption added via Add Caption (1.6.0)
[CTRL] Verticalizerlocked [100,100]locked 0The controller glyph itself, not a spatial input

Every handle's inner Glyph group is additionally pinned with Rotation 0 and Position [0,0] (its Scale already carries the Handle Size expression), so the lock applies at both the layer level and the glyph level.

Handle mechanics:

  • The five build-time handles (SPLIT, TOP PAN, BOT PAN, PIP, CAPTION) plus [CTRL] Verticalizer are guide layers (never render, never export) and are the only layers that stay unlocked and visible after the build; every Caption NN Handle added afterward by Add Caption joins that same unlocked, visible, guide-layer set. Z TOP / Z BOT are shy, disabled, and locked since 1.2.0 (not guide layers) so they render nothing either way and can't be moved by accident. Safe Zones is a guide layer that is now both shy and locked since 1.2.0 (in 1.1.0 it was locked but not shy). [CTRL] Verticalizer's opacity is left untouched by any expression, so it stays visible even with Show Handles off — it's the way back to the controls.
  • Every handle's glyph Path property carries a constant "value" expression, so it can't be reshaped by direct-select — grabbing it always moves the whole layer, never distorts the glyph. The SPLIT handle's glyph is a pill and two chevron arrows only — there is no separate full-width dashed rule line in this release. PIP Handle's glyph is a rounded plate with four corner ticks (a viewfinder-style crop mark).
  • SPLIT locks its off-axis position (thisComp.width * 0.05), Rotation, and Scale via constant expressions; CAPTION and PIP each lock only Rotation and Scale, leaving both X and Y as plain keyframable values.
  • The two PAN handles keep Position and Rotation genuinely free (dragging and keyframing both work), but Scale is locked to [100,100] by expression — so a stray scale drag can't silently do something the effect panel doesn't already expose.
  • Every handle plus [CTRL] Verticalizer now carries a locked Scale (1.5.0), and SPLIT / CAPTION / PIP / [CTRL] also carry a locked Rotation — see the lock matrix above. The two PAN handles are the sole exception, keeping Rotation free on purpose. Each handle's inner Glyph group is pinned the same way (Rotation 0, Position [0,0]), independent of the layer-level lock.
  • Every handle's own layer Opacity = Show Handles × 100. Handle glyph size follows Handle Size via the glyph subgroup's own vector-transform scale.
  • The AE label indices used are 14 Cyan (SPLIT), 9 Green (TOP PAN), 13 Fuchsia (BOT PAN), 11 Orange (PIP), 2 Yellow (CAPTION), 5 Lavender ([CTRL] Verticalizer).

Safe-zone numbers (as fractions of the comp; px values shown at the 1080×1920 default Rig Size)

PlatformTopBottomLeftRight
All (conservative)38040065140
TikTok15032060140
Reels27040065120
Shorts38040060120

The overlay is four filled red (~#FF2626) rectangles — a top band, bottom band, left rail, and right rail — plus a green (~#33FF66) stroked outline rectangle marking the actual clear area. As of 1.2.0, the Safe Zones layer is pinned at anchor point [0,0] / position [0,0] with no transform expression at all, which makes its layer space identical to comp space (layer (0,0) is the top-left comp pixel), and every one of the five shapes carries expressions on both its Size and its Position, computed directly against thisComp.width / thisComp.height and the ratio table above — for example the top band is size [CW, T] at position [CW/2, T/2], the right rail is size [R, CH] at position [CW - R/2, CH/2]. Previously the bands were rects positioned relative to a layer that was itself parked at the comp centre, which is what let the overlay land misplaced and undersized; pinning the layer's own transform and expressing every band in absolute comp coordinates fixes that for good — verified across 3 comp sizes × 4 platforms in simulation. All of it renders at Show Safe Zones × Safe Zone Opacity% layer opacity. For All (conservative) at 1080×1920 that clear rect reads as x 65–940, y 380–1520 (an 875×1140 clear rect). All four numbers switch instantly with the Platform popup.

Usage

  1. Pick a Rig Size — in the panel's Rig Size section, choose a preset (HD / 2K / 4K Portrait, Feed 4:5, Square, 720 Portrait) or type your own W/H (typing switches the dropdown to Custom automatically; values are clamped to 4–30,000 px).
  2. Select footage — a landscape footage item in the Project panel, a composition, or an AV layer already placed in an open comp — then press Create Portrait Rig. Source resolution priority: a FootageItem with video selected in the Project panel wins outright; otherwise a selected layer's source in the active comp (a CompItem, or a FootageItem with video) is used; otherwise a CompItem selected in the Project panel is used last. If the footage would need scaling up more than 110% to fill its default split window (roughly comp width × half comp height — a 1920×1080 source in a 1080×1920 rig fills its window at ~87% and builds silently), a dialog names both sizes and the needed scale-up and asks to Proceed (build anyway) or go back (create nothing and leave the panel open on the size fields); there's no warning at all for an oversized source, since extra resolution is just punch-in headroom. This builds Verticalizer/<name>/SRC <name> and Verticalizer/<name>/<name> Portrait (square pixels, fps/duration from source — stills get a 10-second rig), seeded at a 50/50 split with no pan or zoom applied. If a rig already exists for that source, the new one is automatically named <name> v2 (then v3, …) instead of colliding. The comp opens showing only 7 layers (see "Comp architecture").
  3. Select [CTRL] Verticalizer and open the Effect Controls for the Verticalizer effect: Interface, Layout (Gap, Seam Mode, Seam Inset %, Audio Source), PiP (PiP Panel, PiP Transition, PiP Scale), the mirrored Top Card / Bottom Card trees (Framing, Style, Border & Shadow, Time), Divider, Background, Captions, and Safe Zones.
  4. On the canvas, drag SPLIT Handle to move where the two windows divide, and drag TOP PAN Handle / BOT PAN Handle to frame each half — use Top Zoom / Bottom Zoom for punch-in, and rotate a PAN handle directly for a tilted crop.
  5. Choose how the middle reads with Seam Mode: leave it on Gap to hold the windows exactly Gap pixels apart at any Inset, or switch to Inset so each card steps off the split line by its own Inset × Seam Inset % (50% means the two half-insets add up to one full inset of seam, matching the comp-edge framing).
  6. Decide whether a window's footage may resize when the window does: Top / Bottom Scale Mode = Fill Window keeps the pre-1.4.0 behavior (the footage always fills the window, so dragging the split rescales it); Fixed pins the footage to a comp-cover baseline so the split only re-crops it — set the punch-in with Zoom in Fixed mode (100% covers the whole comp), then drag the split freely.
  7. Retime a window from Top Time / Bottom Time: Speed is a percentage of real time and is integrated, so keyframing it 100 → 0 → 100 freezes the frame and then carries on from that frame instead of jumping. Time Offset is in frames and adds to that position — keyframe it while Speed is 0 to scrub the source by hand, frame by frame. Source Frames is a read-only readout of the source length, so the legal Time Offset range is visible in the same panel.
  8. Animate the picture-in-picture: pick which window floats with PiP Panel (Top | Bottom), drag the orange PIP Handle to where that window should sit (it's hidden until PiP Transition rises above 0), set its size with PiP Scale (percentage of comp width — height follows that card's own Aspect), then keyframe PiP Transition from 0 to 100. At 0 the layout is exactly the split build; at 100 the chosen window is a floating card on the PIP Handle and the other window fills the comp (minus its own Inset); everything in between interpolates the same window box, so mattes, footage, borders, shadows, and pan clamps all follow — including a real cast shadow over the background card, for either PiP Panel choice.
  9. Type your caption into Caption Text, and drag CAPTION Handle anywhere on the frame — both axes are free — to reposition the caption band. The handle hides itself if Show Captions is off.
  10. Add Caption — run this (with the rigged comp open and active) to drop a complete, independent caption: its own Caption NN Handle (Caption 02 Handle, then 03, …, next free index) carrying its own Verticalizer Caption control panel, plus its own Caption NN Text / Caption NN Box pair, spanning the full comp duration (1.6.1 — earlier builds trimmed each new caption to the current time indicator + 3 seconds, which meant every one needed its in/out points dragged back out before it could be timed; trimming is now entirely the editor's call). Requires a rig already built in the active comp, found by its [CTRL] Verticalizer layer and its base Caption Text / Caption Box pair. The new Caption NN Text layer stays unlocked and visible (so you can type into it right away); the new Caption NN Box joins the locked, shy stack like the original box. Select the new handle to reach that caption's own Effect Controls — resizing, recoloring, or hiding it never touches any other caption.
  11. To drive a caption from markers instead of typing into it, set that caption's Caption Source to Markers and add markers where the subtitles go — comp markers (no layer selected) for the built-in caption, layer markers on that caption's own Caption NN Handle for a standalone one. Give every marker a duration and a comment: the caption shows the active marker's comment for exactly that marker's duration and nothing in the gaps, so a zero-duration marker shows nothing at all. See "Marker-driven captions" above.
  12. Turn Caption Fade on and set Fade Frames to fade that caption in and out. In Markers mode the fade lives strictly inside the marker segment (the first and last Fade Frames of it), so the caption never draws outside its own marker; in Manual mode the fade instead runs against the text layer's own in/out points. Text and box (outline included) always fade together.
  13. Apply Rig FFX — applies the Verticalizer pseudo effect to every currently selected layer, one new instance per layer (it does not check for or skip a layer that already carries the effect). Useful for wiring a custom setup by hand against the same control panel, on any comp size thanks to the resolution-agnostic expressions.
  14. Turn Show Handles and Show Safe Zones off before final review — both live on guide layers, so they never render regardless, but hiding them cleans up the canvas while you look at the actual frame.
  15. Everything except [CTRL] Verticalizer, the handles, and each caption's own Caption NN Text is shy and locked, and the master comp opens with Hide Shy Layers on. Toggle Hide Shy Layers off in the timeline, or clear a specific layer's lock switch, if you need manual access to a matte, a card, the PiP Shadow pair, the divider, the background stack, a caption box, or the safe zones.
  16. To repair or upgrade a rig — expressions that stopped working, a rig built by an older script version, a layer that came back from a round trip with its wiring gone — open that rig's portrait comp (or select it in the Project panel) and press Create Portrait Rig again. A dialog now offers Re-apply / Upgrade Rig, Create New Rig, or Cancel. The repair rewires every expression on the rig's own layers (your other layers are never touched), adds any structural piece the current version expects and can't find, and upgrades an out-of-date control panel to the current one — carrying every value and every keyframe across by control name, including the PiP AmountPiP Transition rename. Anything the repair couldn't carry across (a control the new panel has no equivalent for, or a PAN handle whose Position is keyframed and so wasn't re-parented) is named in the completion report, so you know exactly what's left to set by hand. See "Repairing or upgrading a rig" above. A headless run can set $.global.VERTICALIZER_REPAIR = true to choose repair automatically instead of showing the dialog.
  17. Run as a floating window, Verticalizer closes itself after a successful action (Create Portrait Rig / Add Caption / Apply Rig FFX); docked as a panel it stays where you put it — there's no API for a docked panel to close itself. A run that fails, or has to warn about something, leaves the window open either way so the buttons are still reachable.

Create Portrait Rig, Add Caption, and Apply Rig FFX each run inside their own AE undo group (Verticalizer: Create Rig, Verticalizer: Add Caption, Verticalizer: Apply Rig FFX respectively).

Requirements

  • After Effects 2023 (23.0) or later — uses AVLayer.setTrackMatte() (per-layer track-matte assignment, with a legacy trackMatteType + layer-adjacency fallback when unavailable) and text style expressions (text.sourceText.style.setFontSize() / .setFillColor()).
  • "Allow Scripts to Write Files and Access Network" enabled in Preferences ▸ Scripting & Expressions — two embedded FFX binaries are written to disk to register their pseudo effects with After Effects, using the same CamBot/Rectangulator registration trick (apply to a throwaway shape layer in a throwaway 100×100 comp, then addProperty() the real controller): the rig controller, Verticalizer (match name Pseudo/IVGD Verticalizer9, 78,432 bytes, internal FFX format v1.9, 69 controls), and the standalone caption panel, Verticalizer Caption (match name Pseudo/IVGD VerticalizerCap2, 11,866 bytes, v2, 13 controls), applied once per added caption. Both registrations run before any other project mutation, so a missing preference fails cleanly with no partial rig left behind. As of 1.6.1, the temp .ffx is written to a stable IVGD folder under Folder.userData (falling back to Folder.temp then Folder.desktop, and only trusted once the write is verified to exist) rather than Folder.temp directly — on recent macOS builds Folder.temp resolves inside .../T/TemporaryItems, a folder the OS may empty between the write and After Effects reading the preset back, which surfaced as "File not found (3 :: 0)".
  • JavaScript expression engine — the script switches a legacy-engine project to javascript-1.0 automatically, since the caption styling expression needs it.
  • A footage item, composition, or AV layer to build the rig from.

Notes & limitations

  • Match name versioning (bumped a ninth identity in 1.8.0). AE registers one pseudo-effect parameter definition per match name per session — first loaded wins — so shipping a changed control set under an unchanged match name causes "effect control conversion required" resets when projects from different versions meet in one session. Adding Top Aspect Ease / Bottom Aspect Ease changes the definition, so the rig controller ships as Pseudo/IVGD Verticalizer9 (69 controls), a fresh identity that cannot collide with ...Verticalizer8 (67-control, 1.7.0), ...Verticalizer7 (64-control, 1.6.0), ...Verticalizer6 (64-control, 1.5.0), ...Verticalizer5 (61-control, 1.4.0), ...Verticalizer4 (59-control, 1.3.0), ...Verticalizer3 (55-control), ...Verticalizer2 (43-control), or ...Verticalizer (29-control) project. The effect display name stays Verticalizer, so every expression keeps working, and the registration probe now asks for Top Aspect Ease — a name that exists only in this binary. The standalone Verticalizer Caption effect is untouched by this bump (still Pseudo/IVGD VerticalizerCap2, 13 controls, probed on Caption Source) — 1.8.0 only touched the main rig controller. The rule going forward: never reuse a shipped match name for a different control set — any control-set change ships under a bumped match name.
  • A repair preserves keyframes by construction, not by care (1.9.0). Wiring a rig — fresh build or repair alike — only ever assigns expressions; the few static, non-expression writes a build makes go through a helper that refuses to touch a keyframed property, and the pseudo-effect migration re-lays every captured keyframe at its original time, value, and interpolation (hold stays hold). So a repair can't silently eat a keyframe the way a rebuild-from-scratch would. The one case it deliberately does not force through is a PAN handle whose Position already carries keyframes — re-parenting it would mean rewriting those keyframes' values — so that handle is left exactly as it is rather than silently reparented.
  • A repair names what it couldn't carry across, never guesses. The completion report lists any control the current panel has no equivalent for, and any PAN handle left un-reparented for the keyframe reason above, by name — so what's left to set by hand is always explicit rather than something you have to discover by comparing sliders. See "Repairing or upgrading a rig" above.
  • Everything but 7 layers is locked. PiP Shadow, PiP Shadow Matte, both mattes, both footage layers, both cards, the divider, the three background layers, each caption's box, Safe Zones, and both Z nulls are shy and locked at the end of the build (16 layers as of 1.6.0, up from 14 in 1.5.0 — the two new PiP Shadow layers). This is intentional hygiene (the timeline used to open with 21 layers of rig plumbing pre-1.5.1), but it means any manual tweak to those layers — reordering, retyping an expression, repositioning a matte — requires clearing that layer's lock switch first (and toggling Hide Shy Layers off in the timeline to even see it).
  • Clamp Pan assumes rotation ≈ 0. The clamp math approximates the effective scale as s / 100 and ignores the window's rotation; rotate a PAN handle significantly with Clamp Pan on and the window may still show empty space at a corner — framing at that point is an editorial call, not something the clamp fully solves.
  • Popup item lists and parameter order are frozen (Seam Mode, Audio Source, PiP Panel, Top Aspect, Bottom Aspect, Top Scale Mode, Bottom Scale Mode, BG Mode, Platform) — expressions read the numeric index, not the label, and the pseudo effect's parameter order is part of its binary contract. Don't reorder any of them when hand-editing the rig; new controls can only be appended.
  • The floating window auto-closes on success; a docked panel never does. After a successful Create Portrait Rig / Add Caption / Apply Rig FFX, a floating palette closes itself so it isn't left sitting on top of the rig it just built. A docked panel has no API to close itself, so it stays exactly where you docked it. Either way, a run that fails or has to warn about something (for example, Time Remapping refusing to enable on a layer) leaves the window open so the buttons are still there to retry — the close only happens on a clean, warning-free success.
  • The integrator only costs something when Speed is actually keyframed. A static (un-keyframed) Speed on either side takes a closed-form fast path ((time - inPoint) × Speed / 100), identical in cost to pre-1.3.0 builds. Once Speed carries keyframes, the Time Remap expression walks every frame from the layer's in point on each evaluation to accumulate the integrated position — this is what makes a freeze-then-resume land exactly on the held frame, but it means a long comp with a heavily keyframed Speed on one or both windows may preview more slowly than the same rig with a static Speed.
  • Fixed Scale Mode can show matte void. In Fixed mode the footage baseline is the comp, not the window, so a Zoom below that window's own fill percentage leaves the footage smaller than its window; the pan clamp centres it (Math.max(0, ...) collapses both limits to zero) and matte void shows at the edges. That's Fixed doing what it was asked to do — punch in with Zoom until it covers the window you want, or switch back to Fill Window.
  • The PiP floating card's drop shadow no longer clips (fixed in 1.6.0). Through 1.5.0, the occlusion trick punched a hole in the background card's matte sized exactly to the floating card's rect, so a shadow drawn by a floating BOT Card only showed through that hole with no room to spill outside it — PiP Panel = Top (the default) didn't have the problem, because TOP Card was already above everything it needed to be. 1.6.0 lifts the shadow off the card entirely onto a dedicated PiP Shadow / PiP Shadow Matte pair sitting above both card assemblies (see "Picture-in-picture morph" above), so either PiP Panel choice now casts a full, visible shadow.
  • Card edges interact with Edge Feather. Each card's border ring is sized directly from its own window box plus that side's Border Width; it does not itself get feathered, so a heavily feathered matte edge can visually separate from a sharp-edged border ring at high Edge Feather values — treat the two as independent styling choices rather than assuming the border will inherit the softened edge.
  • Renaming any rig layer or the effect breaks its expressions. Every wire — handles, mattes, cards, background, captions, safe zones, time remap, PiP occlusion — locates its counterpart by exact layer/effect name (thisComp.layer("..."), effect("Verticalizer")) at evaluation time. There's no fallback re-link.
  • Matte layers should stay directly above their footage layers. setTrackMatte() (AE 23+) references the matte layer directly and doesn't require adjacency, but the legacy trackMatteType fallback does — don't manually reorder TOP Matte / BOT Matte away from TOP <name> / BOT <name>. TOP Card / BOT Card, by contrast, sit below their footage layers by design and don't participate in that adjacency at all.
  • Z TOP / Z BOT are shy, disabled, locked nulls with no user-facing controls; they exist purely so the PAN handles inherit the correct window centre as the split, Aspect, or Inset settings move it. Don't rename, delete, or unlock them without reason.
  • Rig Size is a build-time choice, not a runtime constant (since 1.6.0). Create Portrait Rig defaults to 1080×1920 (9:16) but the panel's Rig Size presets/custom fields build any size from 4×4 up to 30,000×30,000 px, and the resolution-agnostic expressions mean Apply Rig FFX can also wire the same controls onto a differently-sized composition by hand. The SRC <name> precomp always keeps the source's own pixel aspect ratio regardless of the chosen Rig Size.
  • Stills get a 10-second rig (a footage item with no duration is treated as a still).
  • The source-coverage warning measures the default split window, not comp cover (fixed in 1.6.1) — see "Rig Size & source-coverage warning" above. It warns only when the footage would need a real (>110%) upscale to fill its window; there is no warning at all for an oversized source, and outside that one case a source resolution smaller than a window still pushes the effective zoom above the auto-fill baseline without stopping the build — it just starts from a punched-in default and will read soft.
  • Vertical or square sources are accepted — the fill-scale math (max(windowW / sw, windowH / sh)) is aspect-agnostic — though the two-window split is designed around landscape sources.
Get Verticalizer
v1.9.4 · PANEL · MIT