davinci-resolve-mcp 4.14.4 → 4.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -2
- package/docs/SKILL.md +58 -13
- package/docs/install.md +26 -0
- package/docs/reference/api-limitations.md +32 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/scripts/doctor.py +34 -1
- package/scripts/doctor_python_env.py +659 -0
- package/src/granular/common.py +1 -1
- package/src/granular/timeline_item.py +31 -0
- package/src/server.py +1201 -49
- package/src/utils/api_truth.py +161 -0
- package/src/utils/destructive_hook.py +5 -0
- package/src/utils/execution_lifecycle.py +7 -0
- package/src/utils/playback_guard.py +79 -0
- package/src/utils/resolve_runtime.py +65 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,152 @@
|
|
|
2
2
|
|
|
3
3
|
Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
|
|
4
4
|
|
|
5
|
+
## What's New in v4.15.1 — Frame captures arrive as images on Python 3.10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`timeline_frame` and `timeline_markers get_thumbnail_image` return their
|
|
10
|
+
image again on Python 3.10** (#314). Both tools are declared `-> Any`. On
|
|
11
|
+
Python 3.10, FastMCP inferred a `{"result": ...}` output schema from that
|
|
12
|
+
annotation and then failed to serialize the image, so every capture
|
|
13
|
+
succeeded in Resolve and reached the client as "Unable to serialize unknown
|
|
14
|
+
type". Python 3.11 and later infer no schema, which hid the bug from CI and
|
|
15
|
+
the direct-call unit tests. Both tools now opt out of structured output;
|
|
16
|
+
their dict results still arrive as JSON text. Reported by @jo-nike.
|
|
17
|
+
|
|
18
|
+
### Validation
|
|
19
|
+
|
|
20
|
+
- New `tests/test_image_tool_structured_output.py` calls both tools through
|
|
21
|
+
`mcp.call_tool`. One case imports the server with Python 3.10's schema
|
|
22
|
+
inference forced, so it fails without the fix on any Python.
|
|
23
|
+
- Full offline suite and release static checks. No Resolve scripting
|
|
24
|
+
behaviour changed; live Resolve test not required.
|
|
25
|
+
|
|
26
|
+
## What's New in v4.15.0 — Conform-session fixes, Python doctor, restart hint
|
|
27
|
+
|
|
28
|
+
Six gaps a real conform session hit on Resolve Studio 19.1.3.7, each measured
|
|
29
|
+
live with rendered frames; `scripts/doctor.py` now checks the Python the
|
|
30
|
+
server runs on; and a refused scripting connection with the preference already
|
|
31
|
+
Local now says to restart Resolve instead of changing a setting that is
|
|
32
|
+
already correct.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **`timeline_frame capture` works on projects where rendering a frame fails.**
|
|
37
|
+
The default `quality="frame"` now exports the viewer's frame with
|
|
38
|
+
`Project.ExportCurrentFrameAsStill`: seek, read the playhead back by frame,
|
|
39
|
+
export, read the playhead again, restore it. It switches no page and writes
|
|
40
|
+
no render settings. On the project that surfaced this, the render route
|
|
41
|
+
failed every call with "AddRenderJob returned nothing"; the export worked
|
|
42
|
+
every call (3840×2160 in about 0.2 s). The single-frame render is still
|
|
43
|
+
there as `quality="render"`, used only when asked for by name. `preview` and
|
|
44
|
+
`full` now mean the export.
|
|
45
|
+
- **Keyframe actions no longer crash on 19.x.** `timeline_item`
|
|
46
|
+
`get_keyframes`, `add_keyframe`, `modify_keyframe`, `delete_keyframe` and
|
|
47
|
+
`set_keyframe_interpolation` died with "'NoneType' object is not callable":
|
|
48
|
+
a live 19.1 TimelineItem exposes no keyframe methods. They now return
|
|
49
|
+
`KEYFRAME_API_UNAVAILABLE` with the live version and what to do instead.
|
|
50
|
+
- **Locked tracks are named, not answered with a bare False.**
|
|
51
|
+
`timeline delete_clips` refuses with `TRACK_LOCKED` listing the locked
|
|
52
|
+
tracks before calling `DeleteClips`. A failed `media_pool
|
|
53
|
+
append_to_timeline` names locked target tracks in its error.
|
|
54
|
+
- **Page switches refuse during playback.** A Color-page visit while playback
|
|
55
|
+
ran, followed by `restore_state`, hung Resolve's main thread, and every
|
|
56
|
+
later scripting call hung with it. `resolve_control open_page` and
|
|
57
|
+
`restore_state`, `gallery_stills grab_and_export`, and the page-switching
|
|
58
|
+
capture routes detect a moving playhead and refuse with `PLAYBACK_ACTIVE`.
|
|
59
|
+
`allow_during_playback=true` overrides.
|
|
60
|
+
- **A refused connection with scripting already set to Local now says to
|
|
61
|
+
restart Resolve.** When Resolve was running but refused scripting
|
|
62
|
+
connections, `SCRIPTING_UNAVAILABLE` always said to set Preferences >
|
|
63
|
+
General > External scripting = Local, and implied the free edition was the
|
|
64
|
+
cause. A running Studio can refuse every connection with the preference
|
|
65
|
+
already Local until it is relaunched. The server now reads
|
|
66
|
+
`System.Scripting.Mode` from Resolve's `config.dat`, and when it is Local
|
|
67
|
+
the error leads with "Quit and relaunch DaVinci Resolve", then the existing
|
|
68
|
+
guidance. When the preference is not Local or cannot be read, the message is
|
|
69
|
+
unchanged. The error `state` has a new `scripting_preference_local` field
|
|
70
|
+
(true, false or null). The macOS `config.dat` path was checked on a real
|
|
71
|
+
Mac; the Windows and Linux paths are untested, and a wrong path reads as
|
|
72
|
+
unknown, which keeps the old message.
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- **`timeline replace_source_range`: slip a clip to a new source range.**
|
|
77
|
+
19.x has no slip or trim API. The action rebuilds the item at the same
|
|
78
|
+
record frame through a temporary track: append the new range there,
|
|
79
|
+
CopyGrades and carry transform, crop, composite and scaling onto it, delete
|
|
80
|
+
the original, append the final copy on the original track, copy grade and
|
|
81
|
+
properties again, remove the temporary copy and track, restore the track
|
|
82
|
+
lock. Start, end and duration are verified exactly. A length that lands a
|
|
83
|
+
frame off (mixed source and timeline rates) is corrected and retried. If the
|
|
84
|
+
final placement fails, the temporary copy stays as the recovery and the
|
|
85
|
+
error says where. Confirm-token gated, archived first, `dry_run` supported;
|
|
86
|
+
a locked track needs `unlock_track=true`.
|
|
87
|
+
- **`timeline_item probe_keyframe_override`.** Tells whether a property write
|
|
88
|
+
reaches the render. On a clip with Inspector keyframes, `SetProperty`
|
|
89
|
+
returns True and reads back while the render ignores it. The probe exports
|
|
90
|
+
the frame twice as a null control, nudges the property, exports again,
|
|
91
|
+
restores the property and compares. Verdict: `override`, `writes_render` or
|
|
92
|
+
`inconclusive`.
|
|
93
|
+
- **`set_transform` takes `pan_px` / `tilt_px`.** Pan and Tilt scale with the
|
|
94
|
+
fitted image: Pan moves `Pan × fittedW / timelineW` px. For scale-to-fit the
|
|
95
|
+
action computes the fitted size from the clip and timeline resolutions and
|
|
96
|
+
reports the factors it used. Other scaling modes need `fitted_width` /
|
|
97
|
+
`fitted_height`.
|
|
98
|
+
- **`scripts/doctor.py` checks the interpreter the server runs on.** The checks
|
|
99
|
+
live in `scripts/doctor_python_env.py`. Each problem is reported as WARN or
|
|
100
|
+
FAIL with a `Fix:` line (a `fix` key under `--json`) that gives the exact
|
|
101
|
+
command.
|
|
102
|
+
- *Python bitness.* A 32-bit Python fails, because Resolve's scripting
|
|
103
|
+
library is 64-bit only. So does a Python whose architecture the
|
|
104
|
+
`fusionscript` library was not built for. The library header (ELF, thin
|
|
105
|
+
or universal Mach-O, PE) is read directly. An Intel Python under Rosetta
|
|
106
|
+
against an arm64-only library is named as such.
|
|
107
|
+
- *Python version for Resolve.* Below 3.10 fails. 3.13+ warns unless Resolve
|
|
108
|
+
answered on this interpreter or the build is 20.3.2 or later. The build is
|
|
109
|
+
read from the live probe or, on macOS, the app bundle. 3.13+ fails when
|
|
110
|
+
the scripting module is present but will not import.
|
|
111
|
+
- *Which Python.* It reads the Claude Desktop and Codex configs. It warns
|
|
112
|
+
when a config starts the server as a bare `python3`, which a GUI app
|
|
113
|
+
resolves through a PATH that is not your shell's. It also warns when a
|
|
114
|
+
config runs a different interpreter from the one doctor checked. It lists
|
|
115
|
+
every Python on PATH and marks the server's. A venv and its base
|
|
116
|
+
interpreter count as different Pythons, because they share a binary but
|
|
117
|
+
not `sys.prefix`. It also states where `fusionscript` loads: external
|
|
118
|
+
scripting loads it into the server's interpreter, and Resolve's own
|
|
119
|
+
Scripts menu uses the Python it finds itself.
|
|
120
|
+
- The fix for a wrong interpreter moves the venv aside before re-running the
|
|
121
|
+
installer, because `install.py` reuses an existing venv.
|
|
122
|
+
- **The `SCRIPTING_UNAVAILABLE` remediation points at doctor** for the case
|
|
123
|
+
where Studio is already set to Local and the cause is the server's Python.
|
|
124
|
+
|
|
125
|
+
### Documentation
|
|
126
|
+
|
|
127
|
+
- New api_truth entries, carried into `docs/reference/api-limitations.md`:
|
|
128
|
+
ExportCurrentFrameAsStill as the frame-capture route, the silent
|
|
129
|
+
keyframe override on SetProperty, DeleteClips on a locked track, Edit
|
|
130
|
+
transform units (reconciling the "Pan = timeline pixels" rule of thumb,
|
|
131
|
+
which holds only when the fitted width equals the timeline's), OpenPage
|
|
132
|
+
during playback, and the missing slip/trim API.
|
|
133
|
+
- `docs/SKILL.md`: capture routes rewritten for the new default; Known Gotchas
|
|
134
|
+
cover playback, keyframe overrides, locked tracks, transform units and the
|
|
135
|
+
slip action.
|
|
136
|
+
|
|
137
|
+
### Validation
|
|
138
|
+
|
|
139
|
+
- Full Python suite: 4,221 tests, zero failures, 5 skipped. New
|
|
140
|
+
`tests/test_conform_session_gaps.py` (45 tests), an updated
|
|
141
|
+
`tests/test_playhead_frame_capture.py`, `tests/test_doctor_python_env.py`
|
|
142
|
+
(every check against fakes, with a clean 64-bit 3.12 setup as the null
|
|
143
|
+
control), and `ScriptingPreferenceTests` in `tests/test_resolve_bridge.py`.
|
|
144
|
+
No advanced-server (Node) code changed.
|
|
145
|
+
- Live: the conform behaviour these fixes encode was measured in the conform
|
|
146
|
+
session itself. A live pass of the new code paths on a scratch project has
|
|
147
|
+
not run, because Resolve was in use by that session. The restart hint was
|
|
148
|
+
checked by reading this Mac's real `config.dat` (Local) and producing the
|
|
149
|
+
restart message.
|
|
150
|
+
|
|
5
151
|
## What's New in v4.14.4 — Compatible dependency updates
|
|
6
152
|
|
|
7
153
|
Lands #303, #304, #305, #306, and #308. Resolves #307, #309, and #310.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
|
@@ -449,6 +449,7 @@ Extension authoring references live in [docs/authoring](docs/authoring/). Resolv
|
|
|
449
449
|
|
|
450
450
|
- DaVinci Resolve 18.5+ on macOS, Windows, or Linux. **Studio** supports external scripting directly. The **free edition** does not — Blackmagic gates external scripting to Studio — but it is still reachable through the [in-app bridge](#free-edition-in-app-bridge), which runs inside Resolve from the ungated **Workspace ▸ Scripts** menu.
|
|
451
451
|
- Python 3.10+ (3.10-3.12 is the lowest-risk range). Python 3.13/3.14 also work on recent Resolve builds (verified on Studio 20.3.2); older builds may fail to connect on 3.13+, in which case use 3.10-3.12.
|
|
452
|
+
- Not connecting? `python3 scripts/doctor.py` checks the Python the server runs on and prints the exact fix for each problem: 32-bit or wrong-architecture Python, a Python too new for your Resolve build, or several Pythons where the MCP client starts the wrong one. See [Checking the Python environment](docs/install.md#checking-the-python-environment).
|
|
452
453
|
- Resolve external scripting set to **Local** (Studio). On the free edition this
|
|
453
454
|
preference has no effect — use the [in-app bridge](#free-edition-in-app-bridge)
|
|
454
455
|
instead.
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v4.
|
|
15
|
+
> 本翻译对应 v4.15.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/docs/SKILL.md
CHANGED
|
@@ -1890,9 +1890,16 @@ rendering — grade, Fusion, titles, transitions — rather than inferring it fr
|
|
|
1890
1890
|
metadata. (For the raw camera file instead, use
|
|
1891
1891
|
`media_analysis(action="extract_frames")`.)
|
|
1892
1892
|
|
|
1893
|
-
- `quality="frame"` (default)
|
|
1894
|
-
frame-accurate
|
|
1895
|
-
|
|
1893
|
+
- `quality="frame"` (default) exports exactly that frame with
|
|
1894
|
+
`Project.ExportCurrentFrameAsStill`: frame-accurate, full resolution, about
|
|
1895
|
+
0.2 s, **no page switch and no render settings**. The seek is read back by
|
|
1896
|
+
frame before the export and the playhead is read again after it; a playhead
|
|
1897
|
+
that moved means playback is running and the capture refuses
|
|
1898
|
+
(`PLAYBACK_ACTIVE`). `preview` is the same export bounded to 1280px.
|
|
1899
|
+
- `quality="render"` renders the frame as a one-frame render job. Use it only
|
|
1900
|
+
when you want that route by name: on a real project on Studio 19.1.3.7 it
|
|
1901
|
+
failed every call with "AddRenderJob returned nothing" while the export
|
|
1902
|
+
worked every call.
|
|
1896
1903
|
- `quality="thumbnail"` is instant and touches nothing, but returns the **clip's**
|
|
1897
1904
|
thumbnail — identical for every frame of that clip. Use it to see which clip is
|
|
1898
1905
|
under the playhead, never to judge a specific frame. Needs the Color page *and*
|
|
@@ -1903,22 +1910,23 @@ metadata. (For the raw camera file instead, use
|
|
|
1903
1910
|
(default), `png`, or `tif`.
|
|
1904
1911
|
- `timecode` accepts absolute (`01:00:15:12`) or elapsed (`00:00:15:12`) time;
|
|
1905
1912
|
`frame` is the absolute timeline frame. Omit both to capture the playhead.
|
|
1913
|
+
- The page-switching routes (`render`, `thumbnail`, `still`) refuse while
|
|
1914
|
+
playback is running; `allow_during_playback=true` overrides.
|
|
1906
1915
|
|
|
1907
|
-
The playhead, page, current timeline and Gallery are restored. The render route
|
|
1916
|
+
The playhead, page, current timeline and Gallery are restored. The `render` route
|
|
1908
1917
|
additionally touches project render settings and puts them back: render mode,
|
|
1909
1918
|
format, codec, mark range and output folder (`TargetDir`, read off a throwaway
|
|
1910
1919
|
render job because there is no `GetRenderSettings`). The file name
|
|
1911
1920
|
(`CustomName`) is never written, and the render job is deleted. One gap: a
|
|
1912
1921
|
project that has never had an output folder has none to put back, and Resolve
|
|
1913
1922
|
cannot clear one, so it is left on the capture's temporary folder
|
|
1914
|
-
(`capabilities` repeats this as `render_settings_caveat`).
|
|
1915
|
-
`quality="thumbnail"` when zero side effects matter more than accuracy.
|
|
1923
|
+
(`capabilities` repeats this as `render_settings_caveat`).
|
|
1916
1924
|
|
|
1917
|
-
The render calls pull Resolve onto the Deliver page. The
|
|
1918
|
-
and reads the page to confirm it. If a restore does not take (page,
|
|
1919
|
-
timeline, render mode, format, range or output folder), the image is
|
|
1920
|
-
`{"warnings": [...]}` block naming what was left changed and the
|
|
1921
|
-
it back. No warnings block means every restore was confirmed.
|
|
1925
|
+
The render calls pull Resolve onto the Deliver page. The `render` route switches
|
|
1926
|
+
back and reads the page to confirm it. If a restore does not take (page,
|
|
1927
|
+
playhead, timeline, render mode, format, range or output folder), the image is
|
|
1928
|
+
followed by a `{"warnings": [...]}` block naming what was left changed and the
|
|
1929
|
+
call that puts it back. No warnings block means every restore was confirmed.
|
|
1922
1930
|
|
|
1923
1931
|
```
|
|
1924
1932
|
timeline_frame(action="capture", params={"timecode": "01:00:15:12", "max_width": 1280})
|
|
@@ -2493,6 +2501,42 @@ Other recorded traps ride along on results as `known_limitation` without blockin
|
|
|
2493
2501
|
`RESOLVE_MCP_DISABLE_TRAP_GUARD=1` disables both behaviours.
|
|
2494
2502
|
|
|
2495
2503
|
|
|
2504
|
+
**Stop playback before switching pages** — On Studio 19.1.3.7 a Color-page visit
|
|
2505
|
+
while playback was running, followed by `restore_state`, timed out, and after
|
|
2506
|
+
that every scripting call hung, even a fresh connection: Resolve's main thread
|
|
2507
|
+
was stuck in a viewer render loop. 19.x has no transport API, so the server
|
|
2508
|
+
cannot stop playback. `resolve_control open_page` / `restore_state`,
|
|
2509
|
+
`gallery_stills grab_and_export` and the page-switching `timeline_frame` routes
|
|
2510
|
+
refuse with `PLAYBACK_ACTIVE` when the playhead is moving; ask the user to stop
|
|
2511
|
+
playback (Space or K). `allow_during_playback=true` overrides.
|
|
2512
|
+
|
|
2513
|
+
**A transform write that reads back can still not render** — On a clip with
|
|
2514
|
+
Inspector keyframes (an AAF import can bring them), `SetProperty('ZoomX', v)`
|
|
2515
|
+
returns True and reads back `v`, and the render does not change. 19.x has no
|
|
2516
|
+
TimelineItem keyframe methods at all, so the `timeline_item` keyframe actions
|
|
2517
|
+
return `KEYFRAME_API_UNAVAILABLE` there. `timeline_item(action=
|
|
2518
|
+
"probe_keyframe_override")` exports the frame twice (null control), nudges the
|
|
2519
|
+
property, exports again and restores it; `verdict: "override"` means keyframes
|
|
2520
|
+
are winning. Prove sizing with frames, never with the readback.
|
|
2521
|
+
|
|
2522
|
+
**Locked tracks fail deletes silently** — `DeleteClips` on a locked track
|
|
2523
|
+
returns a bare False, while `SetProperty` on the same items still works.
|
|
2524
|
+
`timeline delete_clips` refuses with `TRACK_LOCKED` naming the tracks.
|
|
2525
|
+
|
|
2526
|
+
**Edit transform units** — Zoom is relative to the fitted image. Pan moves
|
|
2527
|
+
`Pan × fittedW / timelineW` px and Tilt `Tilt × fittedH / timelineH` px (+up),
|
|
2528
|
+
so Pan equals timeline pixels only when the fitted width equals the timeline's.
|
|
2529
|
+
A 720×1280 clip in a 4096×1716 timeline fits at 965×1716, and Pan −500 moved it
|
|
2530
|
+
117 px. `set_transform` takes `pan_px` / `tilt_px` and converts for
|
|
2531
|
+
scale-to-fit.
|
|
2532
|
+
|
|
2533
|
+
**Slipping a clip** — there is no slip or trim API.
|
|
2534
|
+
`timeline(action="replace_source_range", params={"timeline_item_id": ...,
|
|
2535
|
+
"source_start": N})` rebuilds the item at the same record frame through a
|
|
2536
|
+
temporary track, carrying grade, transform, crop, composite and scaling, and
|
|
2537
|
+
verifies start, end and duration exactly. It is confirm-token gated, archives
|
|
2538
|
+
the timeline first, and refuses a locked track unless `unlock_track=true`.
|
|
2539
|
+
|
|
2496
2540
|
**Resolve API object lifetimes** — Objects like timelines, clips, and color groups
|
|
2497
2541
|
returned by the API are live references that can become stale if the project state
|
|
2498
2542
|
changes (e.g., the user deletes a timeline). Always re-fetch IDs after any
|
|
@@ -2572,8 +2616,9 @@ extracted from a delivered render.
|
|
|
2572
2616
|
|
|
2573
2617
|
**Start here: `timeline_frame(action="capture")`** — Returns the frame at the
|
|
2574
2618
|
playhead (or at any `timecode`/`frame` you name) as MCP image content, so a
|
|
2575
|
-
multimodal assistant can simply look at it. It
|
|
2576
|
-
|
|
2619
|
+
multimodal assistant can simply look at it. It exports that one frame from the
|
|
2620
|
+
viewer with `ExportCurrentFrameAsStill`, which is frame-accurate and switches no
|
|
2621
|
+
page; `max_width` bounds the context cost.
|
|
2577
2622
|
|
|
2578
2623
|
```
|
|
2579
2624
|
timeline_frame(action="capture", params={"timecode": "01:00:15:12", "max_width": 1280})
|
package/docs/install.md
CHANGED
|
@@ -53,6 +53,32 @@ Model weights carry their own licences, separate from the code that loads them.
|
|
|
53
53
|
> (`brew install python@3.12`, `pyenv install 3.12`, or python.org on Windows) and
|
|
54
54
|
> point the launcher at it with `DAVINCI_RESOLVE_MCP_PYTHON=/path/to/python3.12`.
|
|
55
55
|
|
|
56
|
+
### Checking the Python environment
|
|
57
|
+
|
|
58
|
+
Most connection failures that are not Resolve's preferences are Python. Run the
|
|
59
|
+
read-only doctor from the checkout, with any Python:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 scripts/doctor.py # add --json for machine-readable output
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Besides paths and client configs, it checks the interpreter the server runs on.
|
|
66
|
+
Each problem is reported as `WARN` or `FAIL` with a `Fix:` line giving the exact
|
|
67
|
+
command:
|
|
68
|
+
|
|
69
|
+
| Check | Fails or warns when |
|
|
70
|
+
|---|---|
|
|
71
|
+
| **Python bitness** | The server's Python is 32-bit (Resolve's scripting library is 64-bit only), or its architecture is not one the `fusionscript` library is built for. The common case is an Intel Python running under Rosetta on an Apple silicon Mac. The library header is read directly, so a universal build counts as both. |
|
|
72
|
+
| **Python version for Resolve** | Below 3.10 (`FAIL`, the SDK floor). On 3.13+ it warns unless Resolve answered on this interpreter or the Resolve build is 20.3.2 or later. It fails if the scripting module is present but will not import. |
|
|
73
|
+
| **Client interpreter** | An MCP client config starts the server as a bare `python3` (resolved through the PATH the GUI app sees, not your shell's), or runs a different interpreter from the one doctor checked. |
|
|
74
|
+
| **Pythons on PATH** | Several are installed and the first is not the server's. This is `INFO` unless a bare client command or a 32-bit or too-old Python first on PATH makes it matter. |
|
|
75
|
+
| **Where fusionscript loads** | Never; this is `INFO`. External scripting loads `fusionscript` into the *server's* interpreter. Resolve's own Scripts menu, which the free-edition bridge runs from, uses a Python it finds itself. On macOS that is `PYTHON3HOME`, then `/usr/local/bin/python3`. The two need not match. |
|
|
76
|
+
|
|
77
|
+
`install.py` reuses an existing venv, so the fix for a wrong interpreter moves
|
|
78
|
+
the venv aside before re-running the installer with a 64-bit Python 3.12.
|
|
79
|
+
`npx davinci-resolve-mcp doctor` is the installer's dry run. It does not run
|
|
80
|
+
these checks.
|
|
81
|
+
|
|
56
82
|
Validated live coverage is based on **DaVinci Resolve 19.1.3 Studio** for the original API surface, plus **DaVinci Resolve 20.3.2 Studio** for the Resolve 20.0-20.2.2 scripting additions. Resolve 21 beta APIs are intentionally deferred until a stable release.
|
|
57
83
|
|
|
58
84
|
## Quick Start
|
|
@@ -12,7 +12,7 @@ that none exists).
|
|
|
12
12
|
|
|
13
13
|
**Verified on:** DaVinci Resolve Studio 21.0.2
|
|
14
14
|
|
|
15
|
-
**Totals:**
|
|
15
|
+
**Totals:** 43 missing capabilities, 61 bugs / unreliable behaviors.
|
|
16
16
|
|
|
17
17
|
The authoritative source is the runtime-queryable `api_truth` ledger
|
|
18
18
|
(`resolve_control api_truth "<query>"`); this document is generated from
|
|
@@ -361,6 +361,13 @@ equivalent, blocking full automation.
|
|
|
361
361
|
- **Workaround / current handling:** Read <in> as the literal source frame (Resolve reads and writes it that way), record-align it by the junction-minus-span-start offset on a -1 start, and walk -1/-1 clips with a record-order cursor. conform.snapshot ingest_xml and editorial.parse_interchange both do; the frame QC then samples each cut CLEAR of its transition windows (inside one the reference is a blend, or black for a fade).
|
|
362
362
|
- **Tags:** timeline, export, xml, fcp7, transitions, silent-failure
|
|
363
363
|
|
|
364
|
+
### Timeline slip / trim of an existing item (no API on 19.x)
|
|
365
|
+
|
|
366
|
+
- **Object:** `Timeline`
|
|
367
|
+
- **Behavior:** There is no call to slip an item's source range or trim its edges in place: no SetProperty key moves the source in-point and no Timeline method takes a new range. The only route is to delete the item and append the new range at the same record frame, which loses its grade, transform, crop and composite unless they are carried over by hand. With a source rate different from the timeline's, the appended length can come out a frame off (the AppendToTimeline mixed-fps floor) and needed a corrected endFrame and a second append.
|
|
368
|
+
- **Workaround / current handling:** timeline replace_source_range: append the new range on a temporary track at the same record frame, CopyGrades onto it, carry transform/crop/composite/scaling, delete the original, append the final copy on the original track, CopyGrades from the temporary copy, re-apply the properties, delete the temporary copy, restore the track lock. Start, end and duration are verified exactly; an endFrame that lands a frame off is corrected and retried.
|
|
369
|
+
- **Tags:** missing-method, timeline, edit, mixed-fps
|
|
370
|
+
|
|
364
371
|
## Bugs / Unreliable Behavior (please fix)
|
|
365
372
|
|
|
366
373
|
Methods that exist but misbehave — silent failures, unreliable return
|
|
@@ -830,3 +837,27 @@ values, or automation-hostile modal prompts.
|
|
|
830
837
|
- **Behavior:** No scriptable call produces an archive. With source media and proxies off it returns False instantly and writes nothing, for an open or a closed project and with render cache on or off. With isArchiveSrcMedia or isArchiveProxyMedia on it creates an empty directory at the target and Resolve crashes (SIGSEGV) in the same second; the call comes back through the bridge as None and every later handle is dead. Four crashes, one of them in a Blackmagic Cloud library, share identical top stack frames in Fusion script-symbol teardown on the UI thread; a separate crash in the same session during DeleteProject/LoadProject had a different stack, so the signature belongs to the archive calls. Media-flag calls crashed 4 of 4, flags-off calls 0 of 5. A file already at the target survived every case byte for byte, including a crash, so the destination is never overwritten; unsaved work in the open project is what is lost. The native defaults turn source media on, so a default call crashes Resolve. Resolve logs nothing about the False returns.
|
|
831
838
|
- **Workaround / current handling:** Keep isArchiveSrcMedia and isArchiveProxyMedia off unless you have verified the build, and save every open project first. Treat False as 'nothing archived', not as a path problem: a .dra and a folder-style path fail identically. Archive from the Project Manager UI when you need a real archive.
|
|
832
839
|
- **Tags:** crash, unreliable-return, silent-failure, project, reported
|
|
840
|
+
|
|
841
|
+
### TimelineItem.SetProperty (Inspector-keyframed property)
|
|
842
|
+
|
|
843
|
+
- **Object:** `TimelineItem`
|
|
844
|
+
- **Signature:** `(propertyKey, propertyValue) -> Bool`
|
|
845
|
+
- **Behavior:** On a clip that carries Edit-page (Inspector) keyframes for a transform property, as clips from an AAF import can, SetProperty('ZoomX', v) returns True and GetProperty reads v back, but the rendered frame never changes: the keyframes win at render time. Nothing in the return or the readback says so. On 19.x it cannot be detected or fixed from the API either: dir() of a live TimelineItem lists no keyframe methods (GetKeyframeCount, AddKeyframe and the rest are absent, and not in the scripting README), so the attribute is None and a call dies as "'NoneType' object is not callable".
|
|
846
|
+
- **Workaround / current handling:** Prove a transform with frames, never with the return or the readback. timeline_item probe_keyframe_override exports the frame twice (null control), nudges the property, exports again and restores it; verdict 'override' means keyframes are winning. Then remove them in the Inspector, or rebuild the item from the Media Pool (timeline replace_source_range re-appends it; a fresh append should carry no Inspector keyframes, which is not yet verified by render). The timeline_item keyframe actions return KEYFRAME_API_UNAVAILABLE on builds without the methods.
|
|
847
|
+
- **Tags:** silent-failure, unreliable-return, keyframes, transform, timeline, missing-method
|
|
848
|
+
|
|
849
|
+
### Timeline.DeleteClips (locked track)
|
|
850
|
+
|
|
851
|
+
- **Object:** `Timeline`
|
|
852
|
+
- **Signature:** `([TimelineItem], ripple) -> bool`
|
|
853
|
+
- **Behavior:** On a locked track DeleteClips returns a bare False and deletes nothing, with nothing to say the lock is why. The lock does not stop SetProperty: transform and other property writes on items of the same locked track still succeed, so a property edit that worked is no evidence the track is unlocked. Separate from the Edit-page gate in 'Timeline.DeleteClips (requires the Edit page; flaky first attempt)'.
|
|
854
|
+
- **Workaround / current handling:** Read Timeline.GetIsTrackLocked for every track the targets sit on before deleting. timeline delete_clips refuses with TRACK_LOCKED naming the tracks; replace_source_range unlocks only with unlock_track=true and locks the track again afterwards; media_pool append_to_timeline names locked target tracks in its failure.
|
|
855
|
+
- **Tags:** silent-failure, timeline, edit, track-lock
|
|
856
|
+
|
|
857
|
+
### Resolve.OpenPage (during playback)
|
|
858
|
+
|
|
859
|
+
- **Object:** `Resolve`
|
|
860
|
+
- **Signature:** `(pageName) -> Bool`
|
|
861
|
+
- **Behavior:** With playback engaged, a visit to the Color page followed by a page/playhead restore (resolve_control restore_state) timed out, and from then on EVERY scripting call hung, including a brand-new scriptapp() connection: Resolve's main thread was stuck in a viewer render loop until playback was dealt with in the GUI. The 19.x scripting API has no transport control (no Play, Stop or IsPlaying), so a script can neither stop playback first nor ask whether it is running.
|
|
862
|
+
- **Workaround / current handling:** Detect playback by reading Timeline.GetCurrentTimecode twice ~0.2 s apart; a playhead that moved means the transport is running. resolve_control open_page / restore_state, gallery_stills grab_and_export and the page-switching timeline_frame routes refuse with PLAYBACK_ACTIVE (allow_during_playback=true overrides). Stop playback in the GUI (Space or K) before retrying.
|
|
863
|
+
- **Tags:** hang, page-dependent, playback, missing-method
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "4.
|
|
40
|
+
VERSION = "4.15.1"
|
|
41
41
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
42
42
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
43
43
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/scripts/doctor.py
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
Checks the checkout, Python interpreter, Resolve app and scripting-API paths,
|
|
5
5
|
MCP client configs, and probes the DaVinciResolveScript bridge without
|
|
6
|
-
mutating anything.
|
|
6
|
+
mutating anything. The Python-environment checks (bitness against the
|
|
7
|
+
fusionscript library, a Python too new for the Resolve build, several Pythons
|
|
8
|
+
on PATH) live in doctor_python_env.py. Prints OK/WARN/FAIL lines, each problem
|
|
9
|
+
with a `Fix:` line (or JSON with --json, where it is a `fix` key), and exits
|
|
7
10
|
nonzero only when a FAIL is present.
|
|
8
11
|
|
|
9
12
|
Run: `python3 scripts/doctor.py`
|
|
@@ -506,6 +509,32 @@ def extras_checks() -> list[dict[str, str]]:
|
|
|
506
509
|
return results
|
|
507
510
|
|
|
508
511
|
|
|
512
|
+
def python_env_checks(probe: dict[str, Any]) -> list[dict[str, str]]:
|
|
513
|
+
"""Bitness, version-for-this-Resolve, and which-Python checks (C56).
|
|
514
|
+
|
|
515
|
+
Kept in their own module because they are the ones worth testing against
|
|
516
|
+
fakes: every input is a probe result or a file header, never the machine.
|
|
517
|
+
"""
|
|
518
|
+
try:
|
|
519
|
+
sys.path.insert(0, str(REPO / "scripts"))
|
|
520
|
+
import doctor_python_env # type: ignore
|
|
521
|
+
except Exception as exc:
|
|
522
|
+
return [{"status": "WARN", "name": "Python environment", "detail": f"checks unavailable: {exc!r}"}]
|
|
523
|
+
try:
|
|
524
|
+
return doctor_python_env.collect(
|
|
525
|
+
server_python=PYTHON,
|
|
526
|
+
lib_path=RESOLVE_LIB,
|
|
527
|
+
app_path=RESOLVE_APP,
|
|
528
|
+
repo=REPO,
|
|
529
|
+
probe=probe,
|
|
530
|
+
claude_config=CLAUDE_CONFIG,
|
|
531
|
+
codex_config=CODEX_CONFIG,
|
|
532
|
+
)
|
|
533
|
+
except Exception as exc:
|
|
534
|
+
# Never let a diagnostic tool be the thing that crashes.
|
|
535
|
+
return [{"status": "WARN", "name": "Python environment", "detail": f"checks failed: {exc!r}"}]
|
|
536
|
+
|
|
537
|
+
|
|
509
538
|
def collect(
|
|
510
539
|
resolve_host: str | None = None,
|
|
511
540
|
resolve_timeout: float | None = None,
|
|
@@ -571,6 +600,8 @@ def collect(
|
|
|
571
600
|
"(see 'Free-edition bridge' below).",
|
|
572
601
|
)
|
|
573
602
|
|
|
603
|
+
results.extend(python_env_checks(probe))
|
|
604
|
+
|
|
574
605
|
results.extend(bridge_checks(probe))
|
|
575
606
|
|
|
576
607
|
results.extend(extras_checks())
|
|
@@ -601,6 +632,8 @@ def main() -> int:
|
|
|
601
632
|
else:
|
|
602
633
|
for item in results:
|
|
603
634
|
print(f"[{item['status']}] {item['name']}: {item['detail']}")
|
|
635
|
+
if item.get("fix"):
|
|
636
|
+
print(f" Fix: {item['fix']}")
|
|
604
637
|
|
|
605
638
|
return 1 if any(item["status"] == "FAIL" for item in results) else 0
|
|
606
639
|
|