davinci-resolve-mcp 2.82.0 → 2.86.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 +270 -1
- package/README.md +16 -3
- package/README.zh-CN.md +261 -0
- package/docs/SKILL.md +6 -3
- package/docs/process/release-process.md +6 -0
- package/docs/reference/api-coverage.md +17 -10
- package/docs/reference/api-limitations.md +23 -5
- package/install.py +3 -3
- package/package.json +1 -1
- package/resolve-advanced/server/db-patch.mjs +43 -4
- package/resolve-advanced/vendor/drp-format/subtitle-style.js +11 -5
- package/scripts/doctor.py +4 -2
- package/scripts/resolve_bridge_launcher.py +2 -1
- package/src/analysis_dashboard.py +2 -2
- package/src/granular/common.py +1 -1
- package/src/granular/timeline_item.py +27 -1
- package/src/server.py +221 -21
- package/src/utils/api_truth.py +86 -7
- package/src/utils/clip_colors.py +94 -0
- package/src/utils/edit_engine.py +6 -0
- package/src/utils/resolve_bridge_client.py +6 -3
- package/src/utils/resolve_connection.py +47 -12
- package/src/utils/resolve_versions.py +338 -0
- package/src/utils/swallowed_retakes.py +139 -0
- package/src/utils/take_ranking.py +18 -1
|
@@ -8,12 +8,12 @@ Complete Resolve scripting API coverage, live-test status, and method-by-method
|
|
|
8
8
|
|--------|-------|
|
|
9
9
|
| MCP Tools | **34** compound (default) / **341** granular |
|
|
10
10
|
| Kernel Actions | **136** guarded MCP workflow actions across 9 compound tools |
|
|
11
|
-
| API Methods Covered | **
|
|
12
|
-
| Methods Live Tested | **338/
|
|
11
|
+
| API Methods Covered | **351/351** (100%) |
|
|
12
|
+
| Methods Live Tested | **338/351** (96.3%) |
|
|
13
13
|
| Live Test Pass Rate | **338/338** (100%) |
|
|
14
14
|
| API Object Classes | 13 |
|
|
15
|
-
| Tested Against | DaVinci Resolve 19.1.3 Studio + Resolve 20.3.2 Studio + Resolve 21.0.2 Studio |
|
|
16
|
-
| Compatibility Note | Resolve 19.1.3 remains the compatibility baseline; Resolve 20.x scripting calls are additive, version-guarded, and live-tested on 20.3.2; Resolve 21.0 additions are version-guarded and live-tested on 21.0.2 (see the Resolve 21 delta row below — five wrappers need AI Extras packs and stay untested without one, and one is deliberately not executed) |
|
|
15
|
+
| Tested Against | DaVinci Resolve 19.1.3 Studio + Resolve 20.3.2 Studio + Resolve 21.0.2 Studio + Resolve 21.0.3 **free** (via the in-app bridge) |
|
|
16
|
+
| Compatibility Note | Resolve 19.1.3 remains the compatibility baseline; Resolve 20.x scripting calls are additive, version-guarded, and live-tested on 20.3.2; Resolve 21.0 additions are version-guarded and live-tested on 21.0.2 (see the Resolve 21 delta row below — five wrappers need AI Extras packs and stay untested without one, and one is deliberately not executed). The Resolve 21.0.4 additions are version-guarded and unit-tested, but not live-tested here — the validation machine carries Studio 19.1.3 and free 21.0.3, neither of which is 21.0.4. Note the free build is reachable only through the in-app bridge, since the free edition refuses external scripting |
|
|
17
17
|
|
|
18
18
|
## API Coverage
|
|
19
19
|
|
|
@@ -109,7 +109,7 @@ source, these summaries are downstream of it:
|
|
|
109
109
|
| Phase 5 | 6/6 | 100% | Scene cuts, subtitles from audio, graph node cache/tools/enable |
|
|
110
110
|
| Resolve 20 delta | 12/12 | 100% | Resolve 20.0-20.2.2 scripting additions live-tested on 20.3.2 |
|
|
111
111
|
| Resolve 21 delta | 7/7 | 100% | Resolve 21.0 additions that could be executed, live-tested on Studio 21.0.2.4 (`tests/live_resolve21_validation.py`). The other 6 need an AI Extras pack or are unsafe to run — counted as untested, not as passes |
|
|
112
|
-
| **Total** | **338/338** | **100%** | **96.
|
|
112
|
+
| **Total** | **338/338** | **100%** | **96.3% of the 351 covered methods tested live** |
|
|
113
113
|
|
|
114
114
|
#### Resolve 21 delta detail
|
|
115
115
|
|
|
@@ -130,7 +130,7 @@ marked 🔬 rather than ⚠️.
|
|
|
130
130
|
| `Project.GenerateSpeech` | 🔬 | Requires AI Speech Generator; returned an error **string**, not a MediaPoolItem |
|
|
131
131
|
| `Resolve.DisableBackgroundTasksForCurrentResolveSession` | 🔬 | Present in `dir()`; **not executed** — session-wide, returns None, and has no `Enable...` counterpart, so there is no undo short of restarting Resolve |
|
|
132
132
|
|
|
133
|
-
### Untested Methods (
|
|
133
|
+
### Untested Methods (13 of 351)
|
|
134
134
|
|
|
135
135
|
Every ☁️ and 🔬 row from the reference tables, listed here so the count is
|
|
136
136
|
checkable rather than asserted.
|
|
@@ -148,10 +148,15 @@ checkable rather than asserted.
|
|
|
148
148
|
| `MPI.AnalyzeForSlate` | Requires the AI Slate ID Extra | Yes |
|
|
149
149
|
| `Project.GenerateSpeech` | Requires the AI Speech Generator Extra | Yes |
|
|
150
150
|
| `Resolve.DisableBackgroundTasksForCurrentResolveSession` | Deliberately not executed: session-wide, returns `None`, and has no `Enable...` counterpart, so there is no undo short of restarting Resolve | No |
|
|
151
|
+
| `MPI.GetTimeline` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
|
|
152
|
+
| `TL.GetSelectedClips` | Requires a Resolve 21.0.4 build; the validation machine runs 19.1.3 | Yes |
|
|
151
153
|
|
|
152
154
|
The five AI Extras rows are untested for want of a downloadable pack, not because
|
|
153
155
|
the wrappers are suspect — a report from anyone who has the packs installed would
|
|
154
|
-
close them. The
|
|
156
|
+
close them. The two 21.0.4 rows are untested for want of the build: this machine
|
|
157
|
+
runs 19.1.3, so they are guarded and unit-tested against stubs and carry a
|
|
158
|
+
contributor's report rather than a result of ours. The remaining one is a
|
|
159
|
+
decision rather than a gap: it is reachable, and
|
|
155
160
|
running it during a validation sweep would disable background tasks for every
|
|
156
161
|
project open in that Resolve instance.
|
|
157
162
|
|
|
@@ -192,7 +197,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
192
197
|
| 19 | `ExportBurnInPreset(presetName, exportPath)` | ⚠️ | API accepts; needs valid preset name |
|
|
193
198
|
| 20 | `GetKeyframeMode()` | ✅ | Returns keyframe mode |
|
|
194
199
|
| 21 | `SetKeyframeMode(keyframeMode)` | ⚠️ | API accepts; mode must match valid enum |
|
|
195
|
-
| 22 | `GetFairlightPresets()` | ✅ |
|
|
200
|
+
| 22 | `GetFairlightPresets()` | ✅ | Returns a preset map. Live on 20.3.2 Studio and 21.0.3 free; **absent below 20.2.2** — confirmed missing on 19.1.3, so the version floor is verified from both sides |
|
|
196
201
|
| 23 | `DisableBackgroundTasksForCurrentResolveSession()` | 🔬 | Resolve 21.0. Present in `dir()`; **not executed** — session-wide, returns `None`, no `Enable...` counterpart, so no undo short of restarting Resolve |
|
|
197
202
|
|
|
198
203
|
### ProjectManager
|
|
@@ -250,7 +255,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
250
255
|
| 19 | `LoadRenderPreset(presetName)` | ✅ | Loads render preset |
|
|
251
256
|
| 20 | `SaveAsNewRenderPreset(presetName)` | ✅ | Creates render preset |
|
|
252
257
|
| 21 | `DeleteRenderPreset(presetName)` | ✅ | Deletes render preset |
|
|
253
|
-
| 22 | `SetRenderSettings({settings})` | ✅ | Applies render settings; Resolve 20.2 adds `ExportSubtitle` and `SubtitleFormat` keys |
|
|
258
|
+
| 22 | `SetRenderSettings({settings})` | ✅ | Applies render settings; Resolve 20.2 adds `ExportSubtitle` and `SubtitleFormat` keys; Resolve 21.0.4 adds `UseFullExtents`, `AddFrameHandles` and `DataBurnIn` — `AddFrameHandles` is accepted and then ignored while `UseFullExtents` is true, so the server returns it as a warning |
|
|
254
259
|
| 23 | `GetRenderJobStatus(jobId)` | ✅ | Returns `{JobStatus, CompletionPercentage}` |
|
|
255
260
|
| 24 | `GetQuickExportRenderPresets()` | ✅ | Returns preset names |
|
|
256
261
|
| 25 | `RenderWithQuickExport(preset, {params})` | ✅ | Initiates quick export |
|
|
@@ -271,7 +276,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
271
276
|
| 40 | `GetColorGroupsList()` | ✅ | Returns color group list |
|
|
272
277
|
| 41 | `AddColorGroup(groupName)` | ✅ | Returns ColorGroup object |
|
|
273
278
|
| 42 | `DeleteColorGroup(colorGroup)` | ✅ | Deletes color group |
|
|
274
|
-
| 43 | `ApplyFairlightPresetToCurrentTimeline(presetName)` | ⚠️ |
|
|
279
|
+
| 43 | `ApplyFairlightPresetToCurrentTimeline(presetName)` | ⚠️ | Accepts the call and returns `False` when handed a name that does not exist. **Never exercised against a genuinely saved preset**, so a `True` path has not been observed — the API has no way to create one, and `GetFairlightPresets` reported an empty map on both test machines. To clear this: save a mix as a Fairlight preset in the UI, then apply it by that name. Requires 20.2.2+ |
|
|
275
280
|
| 44 | `GenerateSpeech({speechGenerationSettings}, timecode)` | 🔬 | Resolve 21.0. Requires the AI Speech Generator Extra; without it returns an error **string**, not a MediaPoolItem |
|
|
276
281
|
| 45 | `ResetIntellisearchAnalysis()` | ✅ | Resolve 21.0.2 live test returns `True` |
|
|
277
282
|
|
|
@@ -382,6 +387,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
382
387
|
| 39 | `AnalyzeForIntellisearch(identifyFaces, isBetterMode)` | 🔬 | Resolve 21.0. Requires the AI IntelliSearch Extra; without it returns an error **string**, not `False` |
|
|
383
388
|
| 40 | `AnalyzeForSlate(markerColor)` | 🔬 | Resolve 21.0. Requires the AI Slate ID Extra. Documented `resolve.MARKER_*` constants do not exist on the handle |
|
|
384
389
|
| 41 | `RemoveMotionBlur({deblurOption})` | ✅ | Resolve 21.0.2 live test returns the new MediaPoolItem; source media path unchanged. Requires the AI Motion Deblur Extra |
|
|
390
|
+
| 42 | `GetTimeline()` | 🔬 | Resolve 21.0.4. Not executed here — no 21.0.4 build on hand; reported working on Studio 21.0.4.5 in issue #131 |
|
|
385
391
|
|
|
386
392
|
### Timeline
|
|
387
393
|
|
|
@@ -445,6 +451,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
445
451
|
| 56 | `ClearMarkInOut(type)` | ✅ | Clears mark in/out |
|
|
446
452
|
| 57 | `GetVoiceIsolationState(trackIndex)` | ✅ | Resolve 20.3.2 live test returns voice isolation state |
|
|
447
453
|
| 58 | `SetVoiceIsolationState(trackIndex, {state})` | ✅ | Resolve 20.3.2 live test sets voice isolation state |
|
|
454
|
+
| 59 | `GetSelectedClips()` | 🔬 | Resolve 21.0.4. First name tried by the selection probe, ahead of the two legacy speculative names. Not executed here — no 21.0.4 build on hand; reported working on Studio 21.0.4.5 in issue #131 |
|
|
448
455
|
|
|
449
456
|
### TimelineItem
|
|
450
457
|
|
|
@@ -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:** 27 missing capabilities, 30 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
|
|
@@ -131,7 +131,7 @@ equivalent, blocking full automation.
|
|
|
131
131
|
### Fairlight audio levels / pan / EQ / automation / FairlightFX
|
|
132
132
|
|
|
133
133
|
- **Object:** `TimelineItem / Timeline`
|
|
134
|
-
- **Behavior:** There is no API to set clip or track volume, pan, EQ, audio automation, or to add/configure FairlightFX. SetProperty covers video transform only; the audio surface is read-only (GetSourceAudioChannelMapping, GetAudioMapping, voice isolation). Verified via dir() + SetProperty docs AND by live mutating attempt on 21.0.0: SetProperty('Volume'|'Level'|'Gain'|'AudioVolume', 0) all return False (note 'Pan' is the VIDEO transform key, not audio pan, so it misleadingly succeeds). The gap is PER-PARAMETER control specifically: a whole saved mix CAN be applied wholesale via Project.ApplyFairlightPresetToCurrentTimeline(name), with the available names from Resolve.GetFairlightPresets() — so 'no Fairlight write path exists' would be too strong.
|
|
134
|
+
- **Behavior:** There is no API to set clip or track volume, pan, EQ, audio automation, or to add/configure FairlightFX. SetProperty covers video transform only; the audio surface is read-only (GetSourceAudioChannelMapping, GetAudioMapping, voice isolation). Verified via dir() + SetProperty docs AND by live mutating attempt on 21.0.0: SetProperty('Volume'|'Level'|'Gain'|'AudioVolume', 0) all return False (note 'Pan' is the VIDEO transform key, not audio pan, so it misleadingly succeeds). The gap is PER-PARAMETER control specifically: a whole saved mix CAN be applied wholesale via Project.ApplyFairlightPresetToCurrentTimeline(name), with the available names from Resolve.GetFairlightPresets() — so 'no Fairlight write path exists' would be too strong. Both methods require Resolve 20.2.2+; on 19.1.3 they are absent (confirmed live 2026-08-06), so on older builds the per-parameter gap really is the whole story.
|
|
135
135
|
- **Workaround / current handling:** To reapply a known mix, save it once as a Fairlight preset in the UI and apply it per-timeline with ApplyFairlightPresetToCurrentTimeline (exposed as resolve_control get_fairlight_presets + project_settings apply_fairlight_preset). Dial individual levels/pan/EQ/automation/FairlightFX in the Fairlight UI; beyond presets, only voice-isolation state and channel-mapping reads are scriptable.
|
|
136
136
|
- **Tags:** missing-method, audio, fairlight
|
|
137
137
|
|
|
@@ -182,7 +182,7 @@ equivalent, blocking full automation.
|
|
|
182
182
|
|
|
183
183
|
- **Object:** `TimelineItem / Timeline / Project`
|
|
184
184
|
- **Behavior:** There is no API method to set or query subtitle font family, font size, text color, background color, outline, shadow, position, alignment, or to apply/query subtitle style presets. TimelineItem.GetProperty() on subtitle items returns only transform/composite keys. Timeline.GetSetting() and Project.GetSetting() return None for all probed subtitle-style keys (e.g. 'subtitleFontName', 'subtitleFontSize', 'subtitleTextColor', 'subtitleBackgroundColor', 'subtitlePosition', 'subtitleAlignment', 'subtitlePreset', 'subtitleStyle'). Verified via dir(), GetProperty(), and GetSetting() on Resolve 21.0.0.48.
|
|
185
|
-
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on
|
|
185
|
+
- **Workaround / current handling:** No API workaround exists, but the style IS reachable below the API: it lives in Sm2TiTrack.FieldsBlob for Type=2 tracks, as an EffectFiltersBA payload whose effect 136 carries a Qt QFont descriptor (param 18) and a normalised position vector (param 17). Exposed as project_db list_subtitle_styles / set_subtitle_style (font family/size/weight/italic + position). Confirmed live on BOTH editions 2026-08-06 — Studio 19.1.3 and free 21.0.3, identical behaviour: Resolve opens a patched track and re-serialises it back to its own zstd form with the patched values intact, so it genuinely parses the write. Caveats: whole-TRACK style not per-caption, project must be CLOSED, Resolve must be fully quit and relaunched afterwards, and the track must already carry a style blob (a freshly added subtitle track has none until it is styled once in the UI). Burn-in overlays via Fusion titles remain a visual alternative but do not produce subtitle tracks.
|
|
186
186
|
- **Tags:** missing-method, subtitle, style, preset
|
|
187
187
|
|
|
188
188
|
### Speech recognition engine selection and SRT import
|
|
@@ -222,6 +222,15 @@ equivalent, blocking full automation.
|
|
|
222
222
|
- **Workaround / current handling:** SetCurrentFolder to the target bin first (media_pool set_current_folder), import, then restore the previous current folder if it matters.
|
|
223
223
|
- **Tags:** media-pool, import
|
|
224
224
|
|
|
225
|
+
### SetClipColor (undocumented value space; marker constants are a decoy)
|
|
226
|
+
|
|
227
|
+
- **Object:** `TimelineItem / MediaPoolItem`
|
|
228
|
+
- **Signature:** `(colorName) -> bool`
|
|
229
|
+
- **Behavior:** SetClipColor accepts exactly 16 names — the Edit-page clip-colour palette — and refuses everything else with a bare False, no exception and no other signal. Enumerated live on Studio 19.1.3.7 (2026-08-06) against both objects; the accepted set is IDENTICAL on TimelineItem and MediaPoolItem: Orange, Apricot, Yellow, Lime, Olive, Green, Teal, Navy, Blue, Purple, Violet, Pink, Tan, Beige, Brown, Chocolate. The empty string is refused too. The trap is that the scripting reference documents colorName as a bare string with no enumerated values, while exporting the MARKER palette as constants (resolve.MARKER_ROSE, resolve.MARKER_FUCHSIA, ...) — so the only colour vocabulary reachable from the API surface is the wrong one. Cyan, Red, Fuchsia, Rose, Lavender, Sky, Mint, Lemon, Sand, Cocoa and Cream are all marker-only and all refused. Five names overlap both palettes (Blue, Green, Yellow, Pink, Purple), which is why the decoy survives: reasoning from the marker constants scores 5 of 16 and looks like the right vocabulary with a few gaps. The reporter's field experience sharpens this — the trap is not that a decoy vocabulary exists, it is that the decoy HALF-WORKS. Intermittent successes read as an unreliable API rather than as a wrong vocabulary, so the wrong conclusion is the natural one; a clean 0-for-8 would have exposed the mechanism immediately.
|
|
230
|
+
- **Workaround / current handling:** Pass only the 16 clip-colour names; on False, treat it as an invalid name rather than an item/lock/page problem. The set is pinned in utils/clip_colors.py and named in the refusal remediation, but deliberately NOT enforced — it was measured on one build and a later Resolve could extend it.
|
|
231
|
+
- **Reference:** [issue #124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124)
|
|
232
|
+
- **Tags:** timeline-item, media-pool, silent-failure, undocumented-enum
|
|
233
|
+
|
|
225
234
|
### Project.SetCurrentRenderFormatAndCodec
|
|
226
235
|
|
|
227
236
|
- **Object:** `Project`
|
|
@@ -376,8 +385,8 @@ values, or automation-hostile modal prompts.
|
|
|
376
385
|
### hasattr() / getattr() on Resolve API objects (attribute fabrication)
|
|
377
386
|
|
|
378
387
|
- **Object:** `(all Resolve scripting objects)`
|
|
379
|
-
- **Behavior:** UNRESOLVED — the two measurements do not test the same thing. On 21.0.0 the bridge was recorded as returning a callable for ANY attribute name, making capability detection by hasattr impossible; the evidence was REAL API method names borrowed from other object types (SetStart, Razor, AddNode, GenerateProxy, AddSmartBin reported present on objects that do not have them). A 21.0.2.4 control probe of the invented name 'TotallyMadeUpMethod_xyz123' returned getattr-callable False on all eight object types, matching dir() in every case. That does NOT refute the 21.0.0 record: if the bridge resolves any name known to the RemoteObject method table rather than literally any string, an invented name is correctly rejected on both builds and the probe never exercised the failing case.
|
|
380
|
-
- **Workaround / current handling:** Use dir(obj) membership for capability probes. It is correct on every build measured, and it is the only form not affected by whichever way this resolves. server._has_method uses
|
|
388
|
+
- **Behavior:** UNRESOLVED — the two measurements do not test the same thing. On 21.0.0 the bridge was recorded as returning a callable for ANY attribute name, making capability detection by hasattr impossible; the evidence was REAL API method names borrowed from other object types (SetStart, Razor, AddNode, GenerateProxy, AddSmartBin reported present on objects that do not have them). A 21.0.2.4 control probe of the invented name 'TotallyMadeUpMethod_xyz123' returned getattr-callable False on all eight object types, matching dir() in every case. That does NOT refute the 21.0.0 record: if the bridge resolves any name known to the RemoteObject method table rather than literally any string, an invented name is correctly rejected on both builds and the probe never exercised the failing case. PARTLY SETTLED 2026-08-06 on Studio 19.1.3.7, DIRECT connection (not the bridge): the five real borrowed names were re-run as the entry asked, across Resolve, ProjectManager, Project, MediaPool, Timeline, Folder and TimelineItem — 42 checks. Every one returned getattr-callable False and dir()-absent, agreeing in all 42 cases, exactly like the invented control. So NO fabrication on the direct path on this build, and the borrowed-name case the 21.0.2.4 probe missed is now covered there. What the same run DID establish is narrower and worse than expected: bare hasattr() returns True for EVERY name on every object — real, borrowed or invented — while getattr returns None. hasattr alone is therefore not a weak probe, it is a constant True and carries no information at all. STILL OPEN: the 21.0.0 record was against the BRIDGE, which this run did not exercise, so bridge-side fabrication remains untested and the recommendation below stands unchanged.
|
|
389
|
+
- **Workaround / current handling:** Use dir(obj) membership for capability probes. It is correct on every build measured, and it is the only form not affected by whichever way this resolves. NEVER use bare hasattr(): it is a constant True on Resolve objects and cannot distinguish anything. server._has_method uses callable(getattr(...)), which agreed with dir() in all 42 direct-path checks on 19.1.3.7 but may still over-report on a bridge build where fabrication is live — that is the case _requires_method gates guard, so it matters most exactly where it is least tested. Calling a fabricated method typically returns None/False with no error.
|
|
381
390
|
- **Tags:** bridge, introspection, silent-failure
|
|
382
391
|
|
|
383
392
|
### Resolve 21 AI methods (AnalyzeForIntellisearch, GenerateSpeech, AnalyzeForSlate) — inconsistent failure return type
|
|
@@ -443,6 +452,15 @@ values, or automation-hostile modal prompts.
|
|
|
443
452
|
- **Reference:** [issue #59](https://github.com/samuelgursky/davinci-resolve-mcp/issues/59)
|
|
444
453
|
- **Tags:** render, deliver, silent-failure, id-vs-label
|
|
445
454
|
|
|
455
|
+
### TimelineItem.SetClipColor on generator/title items
|
|
456
|
+
|
|
457
|
+
- **Object:** `TimelineItem`
|
|
458
|
+
- **Signature:** `(colorName) -> bool`
|
|
459
|
+
- **Behavior:** On a generator or title item SetClipColor returns True with a VALID colour name and the colour does not persist — GetClipColor still reads '' immediately afterwards, and ClearClipColor changes nothing because there is nothing to clear. Measured on Studio 19.1.3.7 (2026-08-06) on a Solid Color generator. A media-backed item on the SAME timeline in the same session persists correctly (SetClipColor('Teal') -> True, GetClipColor -> 'Teal'), so the bool is honest for some items and a lie for others, with nothing in the return value to tell them apart. A timeline item's colour is also independent of its backing MediaPoolItem's: colouring the item leaves the pool clip at ''.
|
|
460
|
+
- **Workaround / current handling:** Never trust the bool alone — read GetClipColor back and compare. To mark a generator or title, use a timeline marker at its start instead; markers read back reliably.
|
|
461
|
+
- **Reference:** [issue #124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124)
|
|
462
|
+
- **Tags:** timeline-item, silent-failure, generator, readback-lies
|
|
463
|
+
|
|
446
464
|
### Project.SetRenderSettings (inherits the loaded preset)
|
|
447
465
|
|
|
448
466
|
- **Object:** `Project`
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.
|
|
39
|
+
VERSION = "2.86.1"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
|
@@ -1748,8 +1748,8 @@ def main():
|
|
|
1748
1748
|
" python scripts/install_resolve_bridge.py"
|
|
1749
1749
|
)
|
|
1750
1750
|
print(
|
|
1751
|
-
" then Workspace > Scripts > resolve_bridge
|
|
1752
|
-
"
|
|
1751
|
+
" then Workspace > Scripts > resolve_bridge "
|
|
1752
|
+
"(used automatically once running)"
|
|
1753
1753
|
)
|
|
1754
1754
|
else:
|
|
1755
1755
|
print(f" Resolve: {yellow('Not running')} — start Resolve to use MCP tools")
|
package/package.json
CHANGED
|
@@ -18,6 +18,29 @@ const require = createRequire(import.meta.url);
|
|
|
18
18
|
|
|
19
19
|
export const DISK_DB_ROOT = path.join(os.homedir(), 'Library/Application Support/Blackmagic Design/DaVinci Resolve/Resolve Disk Database/Resolve Projects');
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The FREE edition ships from the App Store and runs SANDBOXED, so its project
|
|
23
|
+
* library is not under Application Support at all — it lives inside the app's
|
|
24
|
+
* container, under a differently-named root ("Resolve Project Library", not
|
|
25
|
+
* "Resolve Disk Database"). Confirmed on free 21.0.3.7, macOS.
|
|
26
|
+
*
|
|
27
|
+
* Without this, `project_db` resolved by projectName could never find a
|
|
28
|
+
* free-edition project: it searched only the Studio root and reported "no
|
|
29
|
+
* Project.db found", sending exactly the users the in-app bridge exists to serve
|
|
30
|
+
* hunting for a path they have no reason to know.
|
|
31
|
+
*
|
|
32
|
+
* macOS-only: the sandbox container is an Apple construct. Where the free
|
|
33
|
+
* edition keeps its library on Windows and Linux is NOT verified here, so
|
|
34
|
+
* nothing is guessed for those platforms — pass `projectDb` explicitly there.
|
|
35
|
+
*/
|
|
36
|
+
export const LITE_DB_ROOT = path.join(
|
|
37
|
+
os.homedir(),
|
|
38
|
+
'Library/Containers/com.blackmagic-design.DaVinciResolveLite/Data/Library/Application Support/Resolve Project Library/Resolve Projects',
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
/** Every root searched when resolving a project by name, Studio first. */
|
|
42
|
+
export const DB_ROOTS = [DISK_DB_ROOT, LITE_DB_ROOT];
|
|
43
|
+
|
|
21
44
|
export function loadSqlite() {
|
|
22
45
|
try {
|
|
23
46
|
return require('better-sqlite3');
|
|
@@ -26,7 +49,10 @@ export function loadSqlite() {
|
|
|
26
49
|
}
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Recursively locate <projectName>/Project.db under a project-library root.
|
|
54
|
+
* Called once per root by resolveDbPath; pass `root` to search just one.
|
|
55
|
+
*/
|
|
30
56
|
export function findProjectDb(projectName, root = DISK_DB_ROOT) {
|
|
31
57
|
const hits = [];
|
|
32
58
|
const walk = (dir, depth) => {
|
|
@@ -51,9 +77,22 @@ export function findProjectDb(projectName, root = DISK_DB_ROOT) {
|
|
|
51
77
|
export function resolveDbPath({ projectDb, projectName }) {
|
|
52
78
|
if (projectDb) return projectDb;
|
|
53
79
|
if (!projectName) throw new Error('provide projectDb (path) or projectName');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
// Deduplicate: a project present under both roots must not read as ambiguous
|
|
81
|
+
// just because the same file was found twice.
|
|
82
|
+
const hits = [...new Set(DB_ROOTS.flatMap((root) => findProjectDb(projectName, root)))];
|
|
83
|
+
if (!hits.length) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`no Project.db found for project "${projectName}". Searched the Studio library ` +
|
|
86
|
+
`(${DISK_DB_ROOT}) and the sandboxed free-edition library (${LITE_DB_ROOT}). ` +
|
|
87
|
+
'If Resolve keeps its projects elsewhere — a relocated library, a network/Postgres ' +
|
|
88
|
+
'database, or the free edition on Windows/Linux — pass projectDb with the full path.',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (hits.length > 1) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`multiple Project.db match "${projectName}" (${hits.join(', ')}): pass projectDb explicitly`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
57
96
|
return hits[0];
|
|
58
97
|
}
|
|
59
98
|
|
|
@@ -167,11 +167,17 @@ function unwrapEffectFilters(raw) {
|
|
|
167
167
|
* *compressor* (the bundled fzstd is decompress-only); the cost is a larger
|
|
168
168
|
* blob, which does not matter here.
|
|
169
169
|
*
|
|
170
|
-
* Confirmed live
|
|
171
|
-
* a 0x80 payload opens without error, and once
|
|
172
|
-
* track it writes the style back out as 0x81
|
|
173
|
-
*
|
|
174
|
-
* its in-memory model rather than passing
|
|
170
|
+
* Confirmed live on BOTH editions (2026-08-06) — Studio 19.1.3 and free 21.0.3:
|
|
171
|
+
* a subtitle track patched with a 0x80 payload opens without error, and once
|
|
172
|
+
* Resolve next re-serialises that track it writes the style back out as 0x81
|
|
173
|
+
* zstd with the font descriptor and position preserved exactly. Resolve
|
|
174
|
+
* genuinely parses the 0x80 form into its in-memory model rather than passing
|
|
175
|
+
* the bytes through untouched. Both versions behaved identically, down to
|
|
176
|
+
* dropping the same neighbouring key on rewrite.
|
|
177
|
+
*
|
|
178
|
+
* Also identical across both: a freshly added subtitle track carries NO style
|
|
179
|
+
* blob (a ~111-byte NumLayers stub), so there is nothing to patch until the
|
|
180
|
+
* track has been styled once in the UI.
|
|
175
181
|
*/
|
|
176
182
|
function wrapEffectFilters(protobuf, version = 2) {
|
|
177
183
|
const head = Buffer.alloc(9);
|
package/scripts/doctor.py
CHANGED
|
@@ -338,8 +338,10 @@ def bridge_checks(probe: dict[str, Any]) -> list[dict[str, str]]:
|
|
|
338
338
|
elif installed_at:
|
|
339
339
|
check(
|
|
340
340
|
results, "INFO", "DAVINCI_RESOLVE_BRIDGE",
|
|
341
|
-
"not set — the bridge is
|
|
342
|
-
"
|
|
341
|
+
"not set — the bridge is still used automatically if external scripting "
|
|
342
|
+
"is unavailable and the in-Resolve script is running. Set it to 1 to make "
|
|
343
|
+
"the bridge the only transport tried, so its faults surface directly "
|
|
344
|
+
"instead of falling back.",
|
|
343
345
|
)
|
|
344
346
|
return results
|
|
345
347
|
|
|
@@ -168,7 +168,8 @@ def main():
|
|
|
168
168
|
print(" operations : %d" % len(resolve_bridge_ops.ResolveOperations.OPERATIONS))
|
|
169
169
|
print(" generation : %d" % generation)
|
|
170
170
|
print()
|
|
171
|
-
print(" The MCP server
|
|
171
|
+
print(" The MCP server uses this bridge automatically when external")
|
|
172
|
+
print(" scripting is unavailable. DAVINCI_RESOLVE_BRIDGE=1 forces it.")
|
|
172
173
|
if model["blocking_required"]:
|
|
173
174
|
print(" This script now HOLDS the listener open and will look busy until")
|
|
174
175
|
print(" Resolve exits. That is expected — closing it stops the bridge.")
|
|
@@ -12023,8 +12023,8 @@ def _not_connected_message(bridge_on: bool) -> str:
|
|
|
12023
12023
|
"Workspace > Scripts > resolve_bridge; launching Resolve cannot start it.")
|
|
12024
12024
|
return ("DaVinci Resolve is not connected. On Studio, enable Preferences > General > "
|
|
12025
12025
|
"'External scripting using' = Local. On the free edition, install the in-app "
|
|
12026
|
-
"bridge
|
|
12027
|
-
"
|
|
12026
|
+
"bridge and run Workspace > Scripts > resolve_bridge — once running it is used "
|
|
12027
|
+
"automatically, no environment variable needed.")
|
|
12028
12028
|
|
|
12029
12029
|
|
|
12030
12030
|
@_serialize_resolve
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.
|
|
88
|
+
VERSION = "2.86.1"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Timeline item property, keyframe, and clip-level tools."""
|
|
2
2
|
|
|
3
3
|
from src.granular.common import * # noqa: F401,F403
|
|
4
|
+
from src.utils.clip_colors import clip_color_refusal
|
|
4
5
|
|
|
5
6
|
resolve = ResolveProxy()
|
|
6
7
|
|
|
@@ -1528,7 +1529,32 @@ def ti_set_clip_color(color: str, item_index: int = 0, track_type: str = "video"
|
|
|
1528
1529
|
item, err = _get_timeline_item(track_type, track_index, item_index)
|
|
1529
1530
|
if err:
|
|
1530
1531
|
return err
|
|
1531
|
-
|
|
1532
|
+
returned = bool(item.SetClipColor(color))
|
|
1533
|
+
readback = item.GetClipColor() or ""
|
|
1534
|
+
if not returned:
|
|
1535
|
+
refusal = clip_color_refusal(color)
|
|
1536
|
+
return {
|
|
1537
|
+
"success": False,
|
|
1538
|
+
"color": color,
|
|
1539
|
+
"error": f"SetClipColor refused '{color}'",
|
|
1540
|
+
"reason": refusal["reason"],
|
|
1541
|
+
"remediation": refusal["remediation"],
|
|
1542
|
+
"valid_colors": refusal["state"]["valid_colors"],
|
|
1543
|
+
}
|
|
1544
|
+
if readback != color:
|
|
1545
|
+
# Generator and title items take the call, return True, and drop the
|
|
1546
|
+
# colour (issue #124, Studio 19.1.3.7). Media-backed items persist.
|
|
1547
|
+
return {
|
|
1548
|
+
"success": False,
|
|
1549
|
+
"color": color,
|
|
1550
|
+
"readback": readback,
|
|
1551
|
+
"error": "SetClipColor returned True but the colour did not persist",
|
|
1552
|
+
"remediation": (
|
|
1553
|
+
"Generator and title items accept and discard clip colours; colour "
|
|
1554
|
+
"a media-backed item, or use a timeline marker instead."
|
|
1555
|
+
),
|
|
1556
|
+
}
|
|
1557
|
+
return {"success": True, "color": color, "readback": readback}
|
|
1532
1558
|
|
|
1533
1559
|
|
|
1534
1560
|
@mcp.tool()
|