creative/resolve-beat-fx/CONTRACT.md

beat-fx build contract

Interface contract for the effect scripts and Fusion macros in this skill. Every script/macro author MUST follow this so the pieces compose.

Mission

Reusable effect and timing helpers for DaVinci Resolve. The authored edit chooses targets, timing, and treatment; helpers implement those choices. Defaults and macros are optional building blocks, not a genre specification.

Environment facts (verified 2026-08-02)

  • DaVinci Resolve Studio 21.0.3.7 at /Applications/DaVinci Resolve/DaVinci Resolve.app. External scripting WORKS (run scripts from Bash under system python3).
  • System python3 at /opt/homebrew/bin/python3. uv and ffmpeg on PATH.
  • The davinci-resolve-mcp server (repo: ~/dev/davinci-resolve-mcp) is connected. Its docs are the API reference of record: ~/dev/davinci-resolve-mcp/docs/SKILL.md, plus docs/, examples/.
  • More hard-won API recipes: ~/.claude/skills/davinci-resolve-scripting/SKILL.md. Probe the current edition and connection; the installed skill covers external and menu routes.

Two-process rule

  • Scripts that TOUCH RESOLVE run under system python3, stdlib only (fusionscript.so must load; no third-party deps, no uv-managed python).
  • Scripts with heavy deps (librosa etc.) run under uv with PEP 723 inline metadata (# /// script) and NEVER import DaVinciResolveScript. They exchange data with Resolve-side scripts via JSON files.

Layout

  • scripts/ — CLI entry points (both kinds).
  • lib/resolve_boot.py — shared connection bootstrap. Use it; don't re-derive. connect(), current_context(), timeline_fps().
  • macros/ — Fusion .setting macro sources, named BeatFX_<Name>.setting.

Script conventions

  • Pure ASCII source (Resolve's in-app loader chokes on curly quotes/em-dashes).
  • argparse CLI; every mutating script supports --dry-run (print plan, touch nothing). Default target: current project + current timeline.
  • Log every action to stdout AND append to /tmp/beatfx.log (in-app runs don't surface stdout). On error: traceback to both, exit nonzero.
  • Frame math: always via timeline_fps(); never hardcode fps. Timeline record frames start at the timeline start timecode (usually 01:00:00:00 -> frame 3600*fps) — check timeline.GetStartFrame().

Idempotency & ownership

  • Everything a script creates is tagged so re-runs clean up ONLY their own work:
    • markers: customData starting with "beatfx:"
    • media pool: bin named BeatFX
    • timelines the verify flow creates: name prefix beatfx-
  • Re-run behavior: delete/replace own artifacts, never touch anything else.
  • NEVER delete or modify user media, user markers, or user timelines.

Beat grid interchange format

beat_grid.py writes markers AND a JSON sidecar other scripts may consume: /tmp/beatfx_grid.json:

{"fps": 30.0, "timeline": "name", "start_frame": 108000,
 "beats":     [{"frame": 108012, "sec": 0.4, "downbeat": true}],
 "bpm": 120.0}
  • Beat markers: color Blue, customData beatfx:beat.
  • Downbeat markers: color Red, customData beatfx:downbeat.
  • Marker frameId values are relative to timeline start (GetStartFrame offset already subtracted) — that is what AddMarker expects.

Fusion macros

  • Plain-text .setting files, group/macro named BeatFX_<Name>, exposing a small number of published controls (e.g. Intensity, DurationFrames).
  • Install target (applier script copies if missing): ~/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Templates/Edit/Effects/BeatFX/ (appears in Edit page Effects Library under Effects > BeatFX).
  • Each macro ships with scripts/apply_<name>.py that applies it to timeline items at/around beat markers via the scripting API. Research the reliable application path in the MCP repo docs (fusion_comp / timeline_item_fusion tools, docs/, api_truth facts) — do not guess one API call and hope.

Verification

Test helper math and graph construction offline where possible, then verify the changed behavior in a short live Deliver render. File-only tests do not prove that a Fusion graph renders correctly. Serialize work against the one shared Resolve instance; if work is delegated, keep parallel workers offline and assign live mutation to a single operator.

Documentation duty

Each deliverable ends with a short header comment: what it does, args, example invocation. Anything you discovered the hard way (API quirk, wrong-page failure, property name) goes in your final report so it lands in SKILL.md.