This document describes how AutoSubs integrates with DaVinci Resolve: architecture, communication protocol, Lua server, Fusion macro, and development workflow.
What do you want to do?
- Set up development environment → Development Workflow
- Change Resolve integration logic → Lua Server
- Modify the animated caption → Fusion Macro
- Understand the architecture → Architecture
flowchart TD
ReactFE[React Frontend] <-->|Tauri IPC| RustBE[Rust Backend]
RustBE <-->|HTTP POST :56002| resolve_bridge[resolve_bridge.rs]
resolve_bridge <-->|HTTP| LuaServer[Lua Server on Port 56002]
LuaServer <-->|Resolve API| Resolve[DaVinci Resolve]
LuaServer <-->|Fusion API| Fusion[Fusion Page]
Fusion <-->|Macro| Macro[autosubs-macro.setting]
- React Frontend: UI for timeline selection, export settings, subtitle preview
- Rust Backend (
resolve_bridge.rs): HTTP client that posts requests to the Lua server - Lua Server (
autosubs_core.lua): HTTP server running inside Resolve on port 56002 - Fusion Macro (
autoSubs-macro.setting): Fusion template for animated captions with per-word highlighting
The frontend originally used @tauri-apps/plugin-http to POST directly to the Lua server, but the plugin's response-body stream hangs indefinitely against Resolve's Connection: close responses. Routing through Rust's reqwest via the resolve_bridge Tauri command fixes this.
React Frontend
→ invoke('resolve_bridge', { payload, timeoutSecs })
→ Rust resolve_bridge.rs
→ HTTP POST to http://127.0.0.1:56002/
→ Lua server (autosubs_core.lua)
→ Resolve/Fusion API
→ Response body → Frontend JSON.parse()
On failure the Lua server returns:
{ "error": "Short user-facing message", "detail": "Raw error", "func": "Failed function name" }The frontend throwIfError helper in resolve-api.ts checks for this and throws a ResolveApiError.
# In AutoSubs-App/
npm install
npm run setup-resolve # generates AutoSubs (Dev).lua in Resolve's Scripts folder
npm run dev # starts the app in dev mode- Open DaVinci Resolve
- Go to Workspace → Scripts → AutoSubs (Dev)
- The Lua server starts (no app window)
- Edit files in
src-tauri/resources/modules/and re-run the script to pick up changes - Re-run
npm run setup-resolveif you move the repository
| File | Purpose |
|---|---|
modules/autosubs_core.lua |
Main server and Resolve API functions |
modules/luaresolve.lua |
Helper functions for the Resolve API |
modules/font_fallback.lua |
Font fallback for non-Latin scripts |
modules/libavutil.lua |
Audio utilities |
- Lua: Use
print()— output appears in Resolve's Console (Script → Console) - HTTP: Check Rust backend logs for
resolve_bridgerequests - Fusion: Inspect tool inputs in the Fusion inspector or check node connections in the Flow view
| Symptom | Fix |
|---|---|
| Server not responding | Confirm Resolve is running and the dev script was launched; check port 56002 isn't in use |
| Macro not found | Verify autosubs-macro.setting is in the correct location and re-import if needed |
| Animation not working | Check KeyStretcherMod connection, verify animation length > 0 and the animation is enabled |
Both scripts are thin launchers — they set up Lua module paths and delegate immediately to autosubs_core.lua. All real logic lives in autosubs_core.lua; there is almost never a reason to edit the launchers.
- Production (
AutoSubs.lua): Setspackage.path, verifies thatautosubs_core.luaexists at the expected location, then callsAutoSubs:Init(). Launches the app window. - Development (
AutoSubs (Dev).lua): Same pattern, but points at your repo checkout and starts the server without launching the app window. Lua edits take effect on next script run.
AutoSubs.lua is generated during installation — do not hand-edit it:
- Windows: The NSIS installer (
src-tauri/windows/hooks.nsi) generatesAutoSubs.luaat install time with the chosen installation path baked in as a Lua long-bracket string. - macOS / Linux: The
AutoSubs.luachecked in to the repo is used directly; paths are pre-defined (/Applications/AutoSubs.appon macOS,/usr/bin/autosubson Linux).
AutoSubs (Dev).lua follows the same pattern: npm run setup-resolve reads the template from src-tauri/resources/AutoSubs (Dev).lua and writes a generated copy — with your repo's absolute path baked in — to Resolve's Scripts folder.
Every function must be defined in two places:
src/api/resolve-api.ts— TypeScript wrapper that callscallResolve({ func: 'FunctionName', ...params })and handles errorsmodules/autosubs_core.lua— Lua handler that runs inside Resolve and does the actual work
Adding a function in only one place will silently do nothing (the call reaches the Lua server but finds no matching handler, or the frontend has no way to invoke it).
A typical pair looks like:
// resolve-api.ts
export async function jumpToTime(seconds: number) {
return callResolve({ func: 'JumpToTime', seconds });
}-- autosubs_core.lua
handlers["JumpToTime"] = function(data)
local timeline = getCurrentTimeline()
timeline:SetCurrentTimecode(secondsToTimecode(data.seconds))
return { ok = true }
end| Function | Description |
|---|---|
ExportAudio |
Exports timeline audio. Non-blocking — poll with GetExportProgress. |
GetExportProgress |
Returns export progress { active, progress, status }. |
CancelExport |
Cancels the current audio export. |
GetTimelineInfo |
Returns current timeline metadata (frame rate, duration, tracks). |
GetTemplates |
Lists Fusion templates available in the media pool. |
CheckTrackConflicts |
Checks if subtitles would conflict with existing clips on a track. |
AddSubtitles |
Adds subtitle clips to the timeline using the Fusion macro. |
GeneratePreview |
Renders a single preview frame of a subtitle clip. |
StartPresetEdit |
Drops a test clip in Fusion for interactive preset editing. |
CapturePresetSettings |
Reads macro input values and cleans up the preset edit session. |
CancelPresetEdit |
Tears down the preset-edit clip/track without capturing. |
JumpToTime |
Moves the playhead to a given time in seconds. |
For parameters and return shapes, the Lua handlers in autosubs_core.lua are the authoritative reference.
The macro is a Fusion template stored as a .setting file. It renders animated captions with per-word highlighting using Text+, StyledTextFollower, KeyStretcherMod, BezierSpline, and XYPath tools.
Lua functions embedded in the macro's CustomData field handle preset get/set (GetInputValues, SetInputValues), animation logic (SetAnimations), and word-timing highlight updates (UpdateHighlight).
For editing .setting files, the Fusion Setting Highlighter extension is highly recommended. It provides syntax highlighting for Fusion .setting files with full embedded Lua support inside script blocks.
Installation:
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/tmoroney/fusion-setting-highlighter/master/scripts/install.sh | shWindows (PowerShell):
irm https://raw.githubusercontent.com/tmoroney/fusion-setting-highlighter/master/scripts/install.ps1 | iexYou need a Fusion text clip on the timeline to open in the Fusion page. The easiest starting point is the "AutoSubs Caption" clip in the AutoSubs bin in your media pool:
- If the bin isn't in your media pool, drag
AutoSubs-App/src-tauri/resources/AutoSubs/caption-bin.drbinto the media pool to import it. - Drag the AutoSubs Caption clip from the bin onto the timeline.
- Double-click the clip to open it in the Fusion page.
- Delete the existing macro node.
- Drag
autosubs-macro.settinginto the Fusion page — it appears as a node and is ready to edit.
Animation Architecture
All animation logic lives in CustomData inside autosubs-macro.setting as Lua long-bracket strings ([[ ... ]]) that are executed at runtime via loadstring. There are three parts:
Animations table — named strings, one ApplyX and one ResetX per animation. Each function receives a single ctx table:
ctx = {
follower -- StyledTextFollower tool
animStretcher -- AnimationKeyframeStretcher tool
animSpline -- BezierSpline connected to the stretcher
animInEnd -- frame where the in-animation ends (0–100 range)
animOutStart -- frame where the out-animation starts (0–100 range)
mode -- 0 = in only, 1 = out only, 2 = both
level -- 0 = line, 1 = word
}AnimationRegistry — an ordered list of descriptors. SetAnimations loops over this; it never hardcodes individual animation names.
{ controlKey = "PopInEnabled", usesFade = true, applyKey = "ApplyPopIn", resetKey = "ResetPopIn" }controlKey— theUserControlcheckbox that enables this animationusesFade— iftrue, fade is automatically applied as a base layer when this animation is enabled (even ifFadeEnabledis off)applyKey/resetKey— keys into theAnimationstable
SetAnimations — the orchestrator. On each call it: resets all registered animations, checks which are enabled and whether fade is needed, applies fade once (or flat opacity), then applies each enabled animation. It does not need to change when new animations are added.
Full detail on the
ctxtable, theAnimationRegistry/SetAnimationspattern, the per-animation recipes (Fade, PopIn, SlideUp), and the order/timing spline is indocs/animation-system.md.
Every animation needs its own enable/disable toggle, so adding one always involves both the logic and the Fusion node graph:
- Add
ApplyXandResetXstrings to theAnimationstable inCustomData. - Add a descriptor entry to
AnimationRegistry. - Add the control key to
InputKeysinCustomData(so presets capture its value). - Add a
UserControlcheckbox entry in theUserControls = ordered()block (around line 986), following the same pattern asSlideUpEnabled:
BounceEnabled = {
LINKS_Name = "Bounce",
LINKID_DataType = "Number",
INPID_InputControl = "CheckboxControl",
INP_Integer = true,
INP_Default = 0,
INP_Passive = true,
INP_External = false,
CBC_TriState = false,
},Steps 1–2 are pure text edits in autosubs-macro.setting. Steps 3–4 require opening the macro in the Fusion page.
For maintainers only — do not include
caption-bin.drbin your PR.
When the macro changes, the caption bin must be regenerated before release. This is handled by the maintainer, not contributors, since the binary is opaque in code review and the app must be codesigned before shipping.
To regenerate the bin:
- In Resolve, drag the Fusion Text with the updated macro loaded into your media pool.
- Name the clip exactly "AutoSubs Caption" — this name is hardcoded in
autosubs_core.lua. - Replace the current clip in the AutoSubs bin, then export the bin.
- Replace
AutoSubs-App/src-tauri/resources/AutoSubs/caption-bin.drbwith the exported file.
- Uses LuaJIT FFI (
MultiByteToWideChar,_wfopen) for UTF-16 path handling — standardio.openfails on paths with special characters - App:
%LOCALAPPDATA%\AutoSubs\AutoSubs.exe - Resources:
%LOCALAPPDATA%\AutoSubs\resources - Scripts:
%APPDATA%\Blackmagic Design\DaVinci Resolve\Support\Fusion\Scripts\Utility\
- App:
/Applications/AutoSubs.app - Resources:
/Applications/AutoSubs.app/Contents/Resources/resources - Scripts:
~/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Utility/
- App:
/usr/bin/autosubs - Resources:
/usr/lib/autosubs/resources - Scripts:
/opt/resolve/Fusion/Scripts/Utility/or$HOME/.local/share/DaVinciResolve/Fusion/Scripts/Utility/
App-specific docs live here; all general Resolve/Fusion reference lives in the skill (single source of truth):
docs/animation-system.md— how the AutoSubs macro implements its animations (app-specific)davinci-resolve-fusion/— the general Resolve/Fusion skill (distributed separately; also the reference for this project):references/resolve-api.txt— Resolve scripting API (re-sync withdavinci-resolve-fusion/scripts/update-resolve-api.sh)references/fusion-manual/00-index.md— Fusion manual, split one-file-per-class for easy searchingreferences/animation.md,references/macro-authoring.md,references/fusion-templates.md— general guidesexamples/— runnable Resolve API examples
Blackmagic's documentation is limited and sometimes outdated. autosubs_core.lua is the most reliable reference for working Resolve API usage.