Bridge Protocol¶
How the desktop app talks to bridge_script.py running inside the free
version of Resolve. It is deliberately primitive: two JSON files and a poll
loop. No sockets (the sandboxed MAS build makes networking a permissions
question), no dependencies, nothing to install inside Resolve beyond one
stdlib script.
Files¶
All under the bridge directory: <Resolve HOME>/.autocut_bridge, which
on a sandboxed MAS install is inside the app container (see
Architecture). Tests override
it with the AUTOCUT_BRIDGE_DIR environment variable.
| File | Written by | Purpose |
|---|---|---|
request.json |
app | one pending request |
response.json |
bridge | response to the matching request |
owner |
bridge | instance takeover token |
gui.pid |
app | app liveness (bridge skips auto-launch if alive) |
launch_gui |
bridge | trigger file watched by the macOS LaunchAgent |
Wire format¶
Request:
{"id": "<uuid4>", "action": "clips", "params": {"track_type": "video", "index": 1}}
Response (written atomically: .tmp then rename):
{"id": "<same uuid>", "ok": true, "result": {"clips": [...]}}
{"id": "<same uuid>", "ok": false, "error": "message + short traceback"}
Mechanics:
- The bridge polls
request.jsonevery 0.25 s; the app pollsresponse.jsonevery 0.15 s until its deadline (per-action timeouts range from 4 s forpingto 120 s forimport_timeline). - The
idis how a response is matched to its request; the bridge remembers the last handled id, so a request file is executed once even though it stays on disk. - One request in flight at a time. There is no queue — the app is the only client and its calls are serialized.
ok: falsesurfaces in the app as aRuntimeError("Bridge error: …"); no response before the deadline raisesTimeoutErrorwith instructions to re-run the bridge script.
Lifecycle¶
- Newest instance wins. On start, the bridge writes a fresh UUID to
owner; any older instance still polling sees the change and exits. This prevents twofuscriptprocesses racing for requests. - Self-exit when Resolve dies. If
GetProjectManager()stops responding the bridge answers the pending request with an error and exits (ResolveGone) instead of squatting — Resolve runs menu scripts as separate processes that outlive it. - Hard runtime cap of 8 h; the GUI's
stopaction ends it cleanly.
Actions¶
ping- →
timeline_name,fps,start_frame,width,height. Also the connection health check;BridgeSession.refresh_timeline()is a re-ping. clips—{track_type, index}- →
{clips: [{name, start, end, duration, left_offset, right_offset, source_path}]}. Timeline items with the media-pool file path resolved. begin_edit—{video_track}- Caches the track's
MediaPoolItems in bridge memory (they survive across requests inSTATE). Must be called beforecreate_timelineswitches away from the source timeline. →{clips: n} create_timeline—{name}CreateEmptyTimeline+ make it current. Fails on duplicate names.append_segments—{segments: [{clip_index, start, end, [mediaType, trackIndex, recordFrame]}]}AppendToTimelineagainst the cached media-pool items. Frames are source frames.mediaType1 = video-only, 2 = audio-only;recordFrameplaces the clip explicitly. →{appended: n}marker—{frame, color, name, note}AddMarker. Takes an absolute timeline frame; the bridge subtracts the timeline start offset itself (see Frame Spaces).alternate_zoom—{video_track, zoom}- Sets
ZoomX/ZoomYon every second clip. →{zoomed: n} import_media—{paths}- Media-pool import. →
{imported: n} import_srt—{path}- Best-effort SRT placement: import,
AddTrack("subtitle")if none,AppendToTimeline. Returns diagnostic detail plusok= whether any subtitle items actually landed; the app falls back to plain media-pool import whenokis false. import_timeline—{path, options}ImportTimelineFromFile(FCPXML etc.). →{timeline_name}set_timeline—{name}- Switch the current timeline by name.
relink—{paths: {clip_name: file_path}}- Point offline media-pool entries back at real files via
ReplaceClip. Useless for FCPXML orphans, which have no pool item at all. →{relinked, no_mpi} methodsdir()introspection of the live timeline/project/mediapool objects — API archaeology for undocumented calls. → sorted method-name lists.stop- Ends the bridge loop. Handled before any timeline access so it works even when Resolve state is broken.
Adding an action¶
Three places, always:
bridge_script.py_handle()— the implementation running inside Resolve. Stdlib only; JSON-serializable results only (no Resolve objects across the wire).BridgeSession— a typed wrapper aroundself._request("your_action", …)with a realistic timeout.ResolveSession— the same capability via the direct API, so Studio users get it too.
Then re-run AutoCut Bridge inside Resolve — the running fuscript process is
still the old code — and extend the fake responder in tests/test_autocut.py
if the pipeline uses the new action.
Probing Resolve from a terminal¶
With the bridge running, Resolve's behavior can be probed directly — no GUI needed:
from resolve_bridge import BridgeSession
s = BridgeSession()
s._request("methods", timeout=10)
s._request("import_timeline", {"path": "/path/to.fcpxml",
"options": {"importSourceClips": False}})
This is how the FCPXML importer behavior documented in
Architecture was
established. methods plus a throwaway request is the fastest way to test
an undocumented API call before writing any code.