Skip to the content.

Script Sequencer

Script-to-animatic scaffolding for CG virtual production and cinematics in Unreal Engine. Script Sequencer builds Level Sequences from a Fountain or Final Draft (.fdx) screenplay and keeps them in sync as the script changes, without overwriting anyone’s work.

Version: 0.4.0 for Unreal Engine 5.6, 5.7, and 5.8

Platforms: Windows, Mac

Download on Fab: https://fab.com/s/d64f0cb6cf3e

Forum Post: https://forums.unrealengine.com/t/studio22-dev-script-sequencer-early-access/2843536

Support: studio22.development@gmail.com

Contents

Quick Start

Requires Unreal Engine 5.8, 5.7 or 5.6 (Windows or Mac). On older engine versions, the plug-in still loads but logs a warning, and some features may not work.

Installed from Fab, the plug-in lives in your engine’s Engine/Plugins/Marketplace/ScriptSequencer/ folder. This guide, the data contract and three sample scripts (Sample.fountain, Sample_Shots.fountain, Sample_FDX.fdx) are in its Docs folder. Keep scripts inside your project, for example in <Project>/Scripts/. It is best to make a backup of your script before importing. The plug-in records each script by its path in the project, so the same path works on every machine.

  1. Enable Script Sequencer in Edit > Plugins. It also enables Python Editor Script Plugin, Editor Scripting Utilities and Sequencer Scripting. Restart the editor.
  2. Choose how much you want automated: Tools > Script Sequencer > Automation Settings… (or Project Settings > Plugins > Script Sequencer). The default preset is Blocking, which also adds empty cine cameras. Choose Script timing if you don’t want cameras to be created.
  3. Choose Tools > Script Sequencer > Import > Script (Fountain or FDX)… and pick a .fountain or .fdx file (try Sample.fountain).
  4. For a Fountain script, decide whether ID notes such as [[id:L-4d5e6f]] may be added. Yes keeps rewritten lines linked more reliably (a .bak copy is saved first). Final Draft files are never changed: Final Draft 13 keeps its own IDs (see Final Draft below).
  5. Open /Game/Script/<Episode>/<ScriptName>_Master (<Episode> is the title page’s Episode: value, or the script name if there isn’t one). Its Shots track holds one section per scene (sc010, sc020…), each its own Level Sequence with an empty camera (CAM_sc010) and its camera cut. Takes, EDL export/import, and Movie Render Queue work like any Unreal master sequence.
  6. Keep editing your script. Depending on the sync mode, each save updates Sequencer straight away, or waits until you use Sync > All Scripts, or choose the script you’d like to Sync individually under the Sync menu. The report window then lists what changed.

Everything is under Tools > Script Sequencer:

Automation Presets

For teams: commit your project’s Config/DefaultEditor.ini so everyone shares settings.

Preset Result Timing
Structure only Shot sections and scene heading markers are created per scene Existing timing is held
Script timing A marker per line and silent scratch audio per dialogue line is also created Estimated from the script, ripples
Blocking (default) Empty cine cameras and camera cuts are also created Estimated, ripples
Custom Any combination of the settings and more Your choice

Sync decides when changes reach the Sequencer: Apply on every save (default) sends changes to the Sequencer automatically every time your script is saved, and Only when I choose Sync waits until you use Tools > Sync > All Scripts or select an individual script to Sync. Either way, the report window shows what each sync changed (see* The report window). A project or script still set to the old *Show changes and ask before applying option now applies on every save.

By using Python from the Unreal Output Log console, you can change the same settings (replacing <ScriptName>): For example: import script_sequencer.editor_entry as ss; ss.configure("<ScriptName>", preset="blocking") or ss.configure("<ScriptName>", timing="hold", sync="manual").

When an editor takes over scene timing, switch to Timing: Hold existing timing in the plug-in Automation settings, or use timing="hold" in the Python console. The script then only updates labels and adds new material, never retiming what exists.

Placeholder timing lengths come from word count: 2.5 words a second for dialogue (about 150 words a minute), 3.5 for action, with gaps, lead-ins, and minimum lengths, all of which you can change under Placeholder Timing on the settings page.

Shots and Cameras

Casting

Speaker cameras

  1. Choose Tools > Script Sequencer > Automation Settings…. Select the Custom automation preset, then Cameras: One placeholder camera per speaker and a wide.
  2. Sync the script. Each scene or shot sequence gets a wide camera and a camera for each on-screen speaker. V.O., O.S. and O.C. lines hold the current camera. Dual dialogue uses the wide. Lines marked (V.O.), (O.S.) or (O.C.) never get a speaker camera or a cut. A character literally named VO, OS or OC is treated the same way.
  3. Under Speaker Cameras, choose Action Cut (Wide or Hold), Speaker Cut Min Seconds (1.5 by default) and Speaker Shot Size (MCU by default). A line shorter than Speaker Cut Min Seconds never gets a cut of its own: the camera before it holds through it. Shot notes and dual dialogue always cut.
  4. A shot note controls its whole segment, until the next note or unit end. Use a note such as [[SHOT: CU Ben reacts]] for a reaction shot. The script cannot infer who should be listening.
  5. To aim cameras, bind the cast, place the characters through their sequence bindings, enable Aim Cameras, then choose Tools > Script Sequencer > Re-aim Placeholder Cameras. A newly created camera can also be initially aimed when its cast is already placed.
  6. Re-aim writes one static template transform, without animation keys. Saving the screenplay does not re-aim existing cameras. After changing character layout, select Re-aim explicitly.
  7. Moving a camera, changing its lens/filmback, or adding authored camera/component tracks gives its placement to you. Editing any cut gives the sequence’s cuts to you. Deleted cameras stay deleted; changing modes keeps all camera bindings and changes only unowned generated cuts.
  8. Cameras from earlier versions without a verified baseline are kept as they are. A failed partial aim is protected too. Frame those manually or use a fresh generated placeholder. Aiming needs the editor UI for evaluated cast samples, and does not perform collision checks, camera moves, performance tracking or automatic reaction shots.

Takes and captures

Removing a script

Capture lists

Recapture flags

Mocap Manager

Series and episodes

For a series with one script per episode. Each script gets its own content folder through {episode} in the Root Template (the title page’s Episode: value, or the script’s file name when it has none)

Naming tokens

Needs Epic’s Naming Tokens plug-in (UE 5.6 and later, enabled by default). Script Sequencer adds these tokens wherever Unreal accepts naming tokens, such as Movie Render Queue output paths and Cine Assembly names:

Token Example
{ss:unit} sc010, or sc010_sh0020 for a shot
{ss:scene} sc010
{ss:shot} sh0020 (empty for a scene)
{ss:scene_num} / {ss:shot_num} 010 / 0020
{ss:episode} Ep01
{ss:script} the script’s name

For Movie Render Queue, set Output > File Name Format to {ss:episode}/{ss:unit}/{ss:unit}.{frame_number}. Tokens read the sequence being named, so they’re empty for a sequence Script Sequencer didn’t generate.

Final Draft (.fdx)

Naming and Folders

Names are chosen on the settings page under Structure and Naming and are fixed per script when it’s imported, so a later change to project settings never renames existing work

Naming preset Sequences Shots Cameras
Scene / shot (default) sc010 sc010_sh0020 CAM_sc010, CAM_sc010_sh0020
Sequence / shot sq010 sq010_sh0010 CAM_sq010, CAM_sq010_sh0010
Custom Your own prefixes and templates, below    

With Custom, names come from templates with these tokens: {episode} (the title page’s Episode: value, else the script name), {script} (the file name), {scene_num} and {shot_num} (zero-padded numbers such as 012A, 0020), {scene} and {shot} (prefix plus number, such as sc012A, sh0020) and, for cameras, {unit} (the sequence or shot the camera belongs to). The Root Template (default /Game/Script/{episode}) is the folder for each script, so each episode gets its own folder. Templates with unknown tokens, or that could give two things the same name, are refused when you save the settings.

To import with a different scheme without touching project settings: ss.import_script("<path>/Ep02.fountain", naming_preset="sequence_shot"). Two scripts can’t share a folder: give each its own Episode: value, or put {script} in the Root Template.

Each script in a project also needs its own file name, ignoring the extension: Ep01.fountain and Ep01.fdx can’t both be imported, because the plug-in tells scripts apart by name. Importing the second one is refused with a message, rename one of them first.

To change the structure or naming of a script that’s already imported, import it again into a new folder, existing sequences are never renamed.

The Report Window

It’s on by default: Project Settings > Plugins > Script Sequencer > Report > Report window, or for one script ss.configure("Episode01", report_window=False) to turn it off. After every save or Sync All Scripts Now that changes Sequencer, Script Sequencer lists what changed, grouped by scene. A change that needs a decision has a dropdown:

Change Choices
A scene’s dialogue no longer fits its shot (timing on hold) Ripple later shots · Extend the shot · Ignore
A scene’s dialogue now needs less time than its shot (timing on hold) Shorten and Ripple · Keep
A line you edited by hand was cut from the script Remove anyway · Keep
The script changed under audio or a marker you edited Reset to script · Keep
A line you edited by hand is in a scene that was merged into another Remove anyway · Keep

Markers

Script Sequencer adds a marker at each scene heading (on the master) and at each line (in the scene’s sequence). These settings are in Project Settings > Plugins > Script Sequencer > Markers, and each can be set for one script too, for example ss.configure("<ScriptName>", marker_labels="full").

Changing Marker labels or Show IDs in markers updates the markers straight away, without a sync, in every script your editor syncs. Markers on lines and shots you own are updated too. A marker whose label you edited yourself keeps it. To update one script from the Python console, run import script_sequencer.editor_entry as ss; ss.relabel_markers("<ScriptName>").

Sequencer draws each label to the right of its marker, but when the label would run past the right edge of the view, it draws it to the left of the marker instead, over the labels before it. That’s why a label can jump as you scroll. Shorter labels make this rarer, and zooming in avoids it.

What The Plug-in Edits

Working With The Edit (EDL and OTIO)

Three things shape the master sequence, and each has one job:

In practice:

Working In A Team

Several people can open the same project through source control (Perforce, Git LFS 2, or any provider set up under Revision Control in the editor’s toolbar). A single Multi-User session can also be joined. One editor per script turns its saves into Sequencer changes:

Tips

Data Contract, Tags and Your Own Tools

Script Sequencer writes what it generates in a documented file format, the data contract (Docs/DataContract.md, a technical spec), so your own pipeline scripts and tools can read it.

Content Browser Filters

The plug-in registers the suite.* keys with the Asset Registry when the editor starts, so you can type, for example, suite.line=L-4d5e6f in the Content Browser’s search box. If a search finds nothing, add the keys yourself: open Edit > Project Settings > Game > Asset Manager, find Asset Registry > Metadata Tags For Asset Registry, add suite.contract, suite.generator, suite.script, suite.unit, suite.scene, suite.line, suite.character, suite.slot and suite.take, and restart the editor.

Files

Known Limitations

Support

Email studio22.development@gmail.com with your engine version, the Output Log from the sync, its .sync.json, and if you can, the relevant section of the script itself.