creative/davinci-resolve-scripting/SKILL.md

name: davinci-resolve-scripting description: >- Build and revise DaVinci Resolve projects and timelines through raw Python scripting. Use when an edit needs direct Scripting API access beyond the installed MCP tools. For general Resolve operation start with davinci-resolve; for creative direction use music-edit. Covers connection, source/timeline frame placement, owned rebuilds, and playback diagnostics.

davinci-resolve-scripting

Assemble and revise an authored edit on a real Resolve timeline. Hard-won facts below — they cost real round-trips; don't relearn them.

Connect through the available surface

Probe the running edition and connection (resolve_control / get_version through the installed MCP surface, or the product string from a connected script). Studio 21.0.3.7 supported external system-Python scripting in the 2026-08 local verification. Prefer the installed MCP tools when they cover the operation; use raw Python when it adds a needed capability.

If external acquisition returns None, inspect the current edition, scripting preferences, and bridge status before concluding the operation needs a user click. The server's in-app resolve_bridge is another connection path. For a menu-launched script, Workspace > Scripts > Utility supplies the injected resolve global; preserve it. Use the manual menu route only when the available agent-controlled routes do not connect, and read a file log after execution.

Setup

  1. Resolve at /Applications/DaVinci Resolve/DaVinci Resolve.app (download from blackmagicdesign.com or Mac App Store; verify the installed path).
  2. Put agent scripts here so they appear in the menu: ~/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Utility/<name>.py They show under Workspace > Scripts > Utility. Resolve scans on launch and re-scans when the menu opens; restart Resolve only if a new script doesn't appear.
  3. Studio + external use only — export:
    RESOLVE_SCRIPT_API="/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting"
    RESOLVE_SCRIPT_LIB="/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so"
    PYTHONPATH="$PYTHONPATH:$RESOLVE_SCRIPT_API/Modules"
    

Non-obvious gotchas (each cost a round-trip)

  • Output doesn't surface. print() from a menu/Console run is not reliably shown. Always log to a file (/tmp/resolve_build.log) and read it with Bash — that's the agent's only reliable feedback channel.
  • ASCII only. The loader reads files as ASCII; em-dashes/curly quotes/... -> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2. Keep scripts pure ASCII (hyphens, straight quotes) or open(path, encoding='utf-8'). ASCII is safest.
  • Don't clobber the injected global. In the Console, resolve = scriptapp(...) overwrites the good injected object with None. Acquire into a local (R), never reassign resolve.
  • Robust acquisition (works menu / Console / external):
    R = None
    try: R = bmd.scriptapp("Resolve")          # noqa: F821
    except Exception: pass
    if R is None: R = globals().get("resolve")  # <- the free MENU-launch winner
    if R is None:
        import DaVinciResolveScript as dvr
        R = dvr.scriptapp("Resolve")            # external (Studio)
    
  • Idempotency. Re-running naively duplicates media + timelines. Dedup media against root.GetClipList() before ImportMedia; mp.DeleteTimelines([...]) the timelines the script OWNS (by name) before recreating — never delete the user's hand-named cuts.

Core API recipe

pm   = R.GetProjectManager()
proj = pm.CreateProject("name") or pm.LoadProject("name") or pm.GetCurrentProject()
for k, v in {"timelineResolutionWidth":"1080","timelineResolutionHeight":"1920",
             "timelineFrameRate":"30"}.items():
    proj.SetSetting(k, v)        # frame rate locks after first timeline/media — set early
mp    = proj.GetMediaPool()
clips = mp.ImportMedia(["/abs/a.mov", "/abs/b.mov"])   # -> MediaPoolItems
tl    = mp.CreateEmptyTimeline("My Cut")
proj.SetCurrentTimeline(tl)
mp.AppendToTimeline([                                  # one dict per clip/beat
    {"mediaPoolItem": clips[0], "startFrame": 99,   "endFrame": 387},   # SOURCE frames, OUT inclusive
    {"mediaPoolItem": clips[1], "startFrame": 1504, "endFrame": 1754},
])
pm.SaveProject()
  • Frames are SOURCE frames at the clip's exact rate. Probe rate and cadence; use the corpus export contract when footage-index supplied the selects.
  • Precise placement works (verified 21.0.3.7 external): dicts also take "trackIndex", "recordFrame" (timeline frames), and "mediaType" (1 = video only, 2 = audio only). Music bed = one mediaType: 2 entry at recordFrame: tl.GetStartFrame(); beat chunks = mediaType: 1 with explicit recordFrames — no append-order drift, and game/source audio stays off the timeline.
  • recordFrame is ABSOLUTE timeline frames including the start timecode. A fresh timeline starts at 01:00:00:00 = GetStartFrame() (432000 @120 fps); passing recordFrame: 0 places clips an HOUR BEFORE the visible timeline — tracks report items, the GUI looks empty, and GetEndFrame()-GetStartFrame() reads 0. Always add base = int(tl.GetStartFrame()) to every recordFrame. That zero-duration-but-items-exist signature IS this bug.
  • Lay each authored source range as its own timeline item for trim handles and take-swapping. A range may span several musical beats; split where the edit calls for a new shot or independent treatment.

Playback frame-rate mismatch

If timeline playback is slow with popping audio while the source viewer plays fine, compare playback and timeline rates before changing media. In the Studio 21.0.3.7 local verification:

  • pm.CreateProject() makes the project at Resolve's default fps (often 24).
  • proj.SetSetting("timelineFrameRate","30") works, BUT proj.SetSetting("timelinePlaybackFrameRate","30") returned False, leaving the Playback frame rate at the project's birth default.
  • Result: a 30 fps timeline played at 24 fps = 30/24 = 1.25x slow motion + stretched audio.

Diagnose: proj.GetSetting("timelineFrameRate") vs proj.GetSetting("timelinePlaybackFrameRate") (the template logs both and WARNs on mismatch; compare as floats -- one reads "120", the other "120.0"). Check the current setting surface and read back any attempted correction. If the setter still fails as it did on 21.0.3.7, two tested alternatives are:

  • GUI: Project Settings (gear, bottom-right) > Master Settings > "Playback frame rate" -> match the timeline -> Save (saved with the project; idempotent rebuilds reuse it).
  • Scripted (no GUI): clone a project whose playback rate is already right -- pm.ExportProject(shell, "/tmp/shell.drp"); pm.ImportProject("/tmp/shell.drp", new_name); load it, mp.DeleteTimelines(...) + mp.DeleteClips(root.GetClipList()) to gut the copy (the original is untouched); then SetSetting("timelineFrameRate", ...) DOES work again on the emptied project, playback stays as inherited. Build into that. Keep one known-good shell per target rate; find candidates by loading projects and reading their playback setting.

Playback performance (iPhone HEVC stutters/pops on the timeline)

Separate issue: long-GOP 10-bit HEVC beats each start mid-GOP, so the TIMELINE viewer drops frames (stutter) and glitches audio at cuts even when the SOURCE viewer plays one clip fine. That's decode cost. Fixes (non-destructive, don't affect export):

  • Playback > Render Cache > Smart, set Project Settings > Master Settings > "Render cache format" = ProRes 422. Wait for the bar above the ruler to turn blue.
  • Playback > Timeline Proxy Resolution > Half for an instant preview.
  • Generate Optimized Media (right-click clips) for permanent smoothness. If cache/proxy changes do not help, check rate settings, CPU contention, and rendered output separately instead of assuming the source is corrupt. HDR note: Resolve handles iPhone 10-bit HLG/BT.2020 natively; no flags needed to lay clips.

Template

resolve_template.py here is a ready-to-adapt builder: robust acquisition, file logging, media dedup, idempotent owned-timeline rebuild, beat catalog + timeline definitions.

# Studio (this machine): adapt a copy, export the Setup env vars, run it from Bash
python3 my_build.py && cat /tmp/resolve_build.log
# Menu fallback when agent-controlled connections are unavailable: install under
# Fusion/Scripts/Utility/<name>.py, run it from Workspace > Scripts > Utility,
# then read the same log.

Verify the assembly

Log the acquired connection, imported sources, timeline item bounds, and any exceptions. Read back source ranges, track placement, and output duration after mutation. Render a short passage with the music to check timing, source audio, and framing; successful API calls or populated tracks alone do not verify an edit.