davinci-resolve-mcp 2.57.3 → 2.57.5

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 CHANGED
@@ -2,6 +2,58 @@
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 v2.57.5
6
+
7
+ Fixes a silent data-loss bug on Reel Name writes (issue #77) and bundles the
8
+ community-contributed API-limitation entries from PR #76 (issue #75).
9
+
10
+ - **Fixed** (#77) `set_clip_property` / `set_metadata` reporting `success: true`
11
+ when writing the `Reel Name` clip property even though Resolve silently
12
+ dropped the value. Resolve gates `Reel Name` behind the project setting
13
+ *General Options > Assist using reel names from the:* — when that derives reel
14
+ names automatically, scripted writes are ignored but still return `True`. On a
15
+ batch ingest this meant hundreds of clips believed to have reel names assigned
16
+ when none actually stuck. The server now reads the value back after writing
17
+ known-unreliable keys and refuses to report success on mismatch, surfacing the
18
+ project-setting gate as a `hint`. Wired into `set_clip_property`,
19
+ `set_metadata` (both forms), and the bulk `normalize_metadata` path. Adds an
20
+ `api_truth` bug entry (report now lists 18 missing + 11 bugs) and 10 unit
21
+ tests.
22
+ - **Added** (PR #76, thanks @swayll) four `submit: missing` API-limitation
23
+ entries verified on Resolve 21.0.0: per-subtitle text/timing editing, subtitle
24
+ track styling/presets, speech-recognition engine selection + SRT import, and
25
+ Media Pool folder rename. These document the API ceiling behind the subtitle
26
+ feature request in #75 — direct subtitle text editing and SRT round-trip are
27
+ not exposed by the Blackmagic scripting API.
28
+
29
+ ### Validation
30
+
31
+ - Offline: full unit suite green; new `tests/test_reel_name_writeback.py` (10
32
+ tests) plus the api_truth/limitations drift guards. Live Resolve validation of
33
+ the Reel Name read-back is recommended but not performed here — forcing the
34
+ gate would require writing reel names into a live project's media metadata.
35
+
36
+ ## What's New in v2.57.4
37
+
38
+ Live mutating verification of the catalogued API gaps.
39
+
40
+ - **Added** `tests/live_api_gap_verification.py` — attempts each catalogued
41
+ "missing capability" against a disposable project built from synthetic ffmpeg
42
+ media, recording the failing call alongside a positive control that succeeds.
43
+ All 8 surface-audited gaps confirmed missing on Resolve Studio 21.0.0:
44
+ `SetProperty('Speed', …)` / audio-level keys return False while
45
+ `RetimeProcess` / video-transform keys succeed; trim/move/split/proxy/node-
46
+ graph/Smart-Bin methods are absent (by `dir()`), while append/CDL/AddSubFolder
47
+ controls work.
48
+ - **Added** a new bug entry: `hasattr()`/`getattr()` are unusable on Resolve
49
+ objects — the Python bridge fabricates a callable for ANY attribute name, so
50
+ capability detection must use `dir()`. (Discovered when the first harness pass
51
+ reported nonexistent methods as present.) Report now lists 14 missing + 10 bugs.
52
+ - **Changed** the report's Scope note to record the live mutating-harness
53
+ methodology and the `hasattr` caveat. Strengthened the clip-speed and
54
+ Fairlight-audio entries with the live `SetProperty` rejection evidence (and
55
+ noted that `'Pan'` is the video-transform key, not audio pan).
56
+
5
57
  ## What's New in v2.57.3
6
58
 
7
59
  Expanded the API-limitations catalogue with a live surface audit.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DaVinci Resolve MCP Server
2
2
 
3
- [![Version](https://img.shields.io/badge/version-2.57.3-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.57.5-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
4
4
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
5
5
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
6
6
  [![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(341%20full)-blue.svg)](#server-modes)
@@ -12,7 +12,7 @@ that none exists).
12
12
 
13
13
  **Verified on:** DaVinci Resolve Studio 21.0.0
14
14
 
15
- **Totals:** 14 missing capabilities, 9 bugs / unreliable behaviors.
15
+ **Totals:** 18 missing capabilities, 11 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
@@ -21,14 +21,21 @@ it and stays in sync via a drift guard.
21
21
  ### Scope & completeness
22
22
 
23
23
  This list is **not guaranteed exhaustive.** It combines (a) issues hit
24
- while building this MCP server and (b) a `dir()` surface audit of the live
24
+ while building this MCP server, (b) a `dir()` surface audit of the live
25
25
  Resolve API objects (ProjectManager, Project, MediaPool, MediaPoolItem,
26
- Timeline, TimelineItem, Graph) diffed against Resolve's UI feature set.
26
+ Timeline, TimelineItem, Graph) diffed against Resolve's UI feature set,
27
+ and (c) a live mutating harness (`tests/live_api_gap_verification.py`)
28
+ that attempts each operation against a disposable project built from
29
+ synthetic media and confirms it fails while a related control succeeds.
27
30
  That catches absent methods and documented constraints, but not subtler
28
31
  issues: parameters that exist yet misbehave, version-specific regressions,
29
32
  or capabilities we simply never exercised. New findings are added as
30
33
  `submit`-tagged `api_truth` entries and this document is regenerated.
31
34
 
35
+ Note: `hasattr()`/`getattr()` cannot be used to probe this API — the
36
+ Python bridge fabricates a callable for any attribute name (see the
37
+ `hasattr` bug below). Method existence here was checked with `dir()`.
38
+
32
39
  ## Missing Capabilities (please add)
33
40
 
34
41
  Functionality that exists in the Resolve UI but has no scripting API
@@ -95,7 +102,7 @@ equivalent, blocking full automation.
95
102
  ### Clip speed / retime ratio and speed ramps
96
103
 
97
104
  - **Object:** `TimelineItem`
98
- - **Behavior:** SetProperty exposes only retime *quality* (RetimeProcess, MotionEstimation) and transform/crop/composite/opacity keys — not the speed value itself. There is no way to set a clip to a given % speed, reverse it, or author a speed ramp. Verified against the documented SetProperty key list (21.0.0).
105
+ - **Behavior:** SetProperty exposes only retime *quality* (RetimeProcess, MotionEstimation) and transform/crop/composite/opacity keys — not the speed value itself. There is no way to set a clip to a given % speed, reverse it, or author a speed ramp. Verified against the documented SetProperty key list AND by live mutating attempt on 21.0.0: SetProperty('Speed'|'PlaybackSpeed'|'RetimeSpeed'|'ClipSpeed', 50) all return False, while SetProperty('RetimeProcess', 1) returns True.
99
106
  - **Workaround / current handling:** Set clip speed/retime in the Resolve UI; no scripted equivalent exists.
100
107
  - **Tags:** missing-method, timeline, retime, speed
101
108
 
@@ -109,7 +116,7 @@ equivalent, blocking full automation.
109
116
  ### Fairlight audio levels / pan / EQ / automation / FairlightFX
110
117
 
111
118
  - **Object:** `TimelineItem / Timeline`
112
- - **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 (21.0.0).
119
+ - **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).
113
120
  - **Workaround / current handling:** Mix in the Fairlight UI; only voice-isolation state and channel-mapping reads are scriptable.
114
121
  - **Tags:** missing-method, audio, fairlight
115
122
 
@@ -134,6 +141,34 @@ equivalent, blocking full automation.
134
141
  - **Workaround / current handling:** Create Smart/Power Bins in the Resolve UI; only regular bins are scriptable.
135
142
  - **Tags:** missing-method, media-pool, bins
136
143
 
144
+ ### Per-subtitle text content and timing editing
145
+
146
+ - **Object:** `TimelineItem (subtitle track)`
147
+ - **Behavior:** TimelineItem on a subtitle track exposes only 21 standard transform/composite properties (Pan, Tilt, ZoomX, Opacity, Crop, etc.). There are no methods to get or set subtitle text (GetText/SetText), start time, end time, or duration for individual subtitle items. Subtitles created via CreateSubtitlesFromAudio or imported via the Resolve UI cannot have their content or timing read or modified programmatically. Verified via dir() and GetProperty() on Resolve 21.0.0.48.
148
+ - **Workaround / current handling:** No workaround exists — subtitle text and timing are completely inaccessible from the scripting API. Must be edited in the Resolve UI.
149
+ - **Tags:** missing-method, subtitle, text, timing
150
+
151
+ ### Subtitle track styling and presets
152
+
153
+ - **Object:** `TimelineItem / Timeline / Project`
154
+ - **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.
155
+ - **Workaround / current handling:** No workaround exists — subtitle styling is UI-only. Burn-in overlays via Fusion titles are a visual alternative but do not produce proper subtitle tracks.
156
+ - **Tags:** missing-method, subtitle, style, preset
157
+
158
+ ### Speech recognition engine selection and SRT import
159
+
160
+ - **Object:** `Timeline`
161
+ - **Behavior:** Timeline.CreateSubtitlesFromAudio(autoCaptionSettings) always uses the built-in Resolve speech recognition engine. There is no API parameter to select an alternative provider (e.g. whisper-cli, Google Speech, AWS Transcribe). The language selection via resolve.AUTO_CAPTION_LANGUAGE_* is the only customization; the engine itself cannot be changed. Furthermore, there is no API method to import an SRT file into a subtitle track programmatically — File -> Import -> Subtitle is UI-only.
162
+ - **Workaround / current handling:** No workaround exists for provider selection or SRT import. External transcripts must be converted to SRT and imported through the Resolve UI.
163
+ - **Tags:** missing-method, subtitle, transcription, speech-recognition, asr
164
+
165
+ ### Media Pool folder rename
166
+
167
+ - **Object:** `MediaPool`
168
+ - **Behavior:** MediaPool exposes AddSubFolder(name), DeleteSubFolders([names]), and MoveFolders([names], targetFolder) but no RenameSubFolder(oldName, newName) method. Folders can be created, deleted, and moved, but their names cannot be changed through the API. Verified via dir() on Resolve 21.0.0.
169
+ - **Workaround / current handling:** Delete and recreate the folder with the desired name, or rename in the Resolve UI.
170
+ - **Tags:** missing-method, media-pool, folder
171
+
137
172
  ## Bugs / Unreliable Behavior (please fix)
138
173
 
139
174
  Methods that exist but misbehave — silent failures, unreliable return
@@ -206,3 +241,19 @@ values, or automation-hostile modal prompts.
206
241
  - **Behavior:** Returns None and pops a modal 'Save Current Project' dialog when the current unsaved/Untitled project blocks the switch. SaveProject() on an Untitled project re-triggers the same modal.
207
242
  - **Workaround / current handling:** CloseProject(current) to discard the untitled project without a prompt, then CreateProject; restore with LoadProject afterward.
208
243
  - **Tags:** project, modal, silent-failure
244
+
245
+ ### hasattr() / getattr() on Resolve API objects (attribute fabrication)
246
+
247
+ - **Object:** `(all Resolve scripting objects)`
248
+ - **Behavior:** The Python bridge returns a callable for ANY attribute name, so hasattr(obj, 'TotallyMadeUpMethod') is always True and getattr never raises. This makes capability detection by hasattr impossible — verified on 21.0.0 (hasattr reported SetStart, Razor, AddNode, GenerateProxy, AddSmartBin etc. as present though none exist). Only dir() lists the real methods.
249
+ - **Workaround / current handling:** Never probe method existence with hasattr/getattr; test membership against dir(obj) instead. Calling a fabricated method typically returns None/False with no error.
250
+ - **Tags:** bridge, introspection, silent-failure
251
+
252
+ ### MediaPoolItem.SetClipProperty('Reel Name', ...)
253
+
254
+ - **Object:** `MediaPoolItem`
255
+ - **Signature:** `(propertyName, propertyValue) -> bool`
256
+ - **Behavior:** Setting the 'Reel Name' clip property returns True but the value is silently dropped on read-back when the project is configured to derive reel names automatically (General Options > 'Assist using reel names from the:' set to source clip file / embedding / filename pattern). The same True-but-unpersisted behavior occurs via SetMetadata('Reel Name', ...). Other clip properties on the same clip (e.g. 'Comments') write and persist normally, so this is field-specific, not a bridge/permission failure. Verified on Resolve 21.0.0; reported as issue #77.
257
+ - **Workaround / current handling:** After writing 'Reel Name', read it back with GetClipProperty('Reel Name') and refuse to report success on mismatch; surface the project-setting gate to the caller (server._verify_clip_property_writeback).
258
+ - **Reference:** [issue #77](https://github.com/samuelgursky/davinci-resolve-mcp/issues/77)
259
+ - **Tags:** unreliable-return, silent-failure, metadata, reel-name
package/install.py CHANGED
@@ -35,7 +35,7 @@ from src.utils.update_check import (
35
35
 
36
36
  # ─── Version ──────────────────────────────────────────────────────────────────
37
37
 
38
- VERSION = "2.57.3"
38
+ VERSION = "2.57.5"
39
39
  # Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
40
40
  # Resolve's scripting bridge loads into newer interpreters on recent builds
41
41
  # (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.57.3",
3
+ "version": "2.57.5",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -85,14 +85,21 @@ def render() -> str:
85
85
  "### Scope & completeness",
86
86
  "",
87
87
  "This list is **not guaranteed exhaustive.** It combines (a) issues hit",
88
- "while building this MCP server and (b) a `dir()` surface audit of the live",
88
+ "while building this MCP server, (b) a `dir()` surface audit of the live",
89
89
  "Resolve API objects (ProjectManager, Project, MediaPool, MediaPoolItem,",
90
- "Timeline, TimelineItem, Graph) diffed against Resolve's UI feature set.",
90
+ "Timeline, TimelineItem, Graph) diffed against Resolve's UI feature set,",
91
+ "and (c) a live mutating harness (`tests/live_api_gap_verification.py`)",
92
+ "that attempts each operation against a disposable project built from",
93
+ "synthetic media and confirms it fails while a related control succeeds.",
91
94
  "That catches absent methods and documented constraints, but not subtler",
92
95
  "issues: parameters that exist yet misbehave, version-specific regressions,",
93
96
  "or capabilities we simply never exercised. New findings are added as",
94
97
  "`submit`-tagged `api_truth` entries and this document is regenerated.",
95
98
  "",
99
+ "Note: `hasattr()`/`getattr()` cannot be used to probe this API — the",
100
+ "Python bridge fabricates a callable for any attribute name (see the",
101
+ "`hasattr` bug below). Method existence here was checked with `dir()`.",
102
+ "",
96
103
  "## Missing Capabilities (please add)",
97
104
  "",
98
105
  "Functionality that exists in the Resolve UI but has no scripting API",
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.57.3"
83
+ VERSION = "2.57.5"
84
84
  logger = logging.getLogger("davinci-resolve-mcp")
85
85
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
86
86
  logger.info(f"Detected platform: {get_platform()}")
package/src/server.py CHANGED
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 341-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.57.3"
14
+ VERSION = "2.57.5"
15
15
 
16
16
  import base64
17
17
  import os
@@ -6559,6 +6559,78 @@ def _safe_clip_call(clip, method_name: str, *args):
6559
6559
  return None, str(exc)
6560
6560
 
6561
6561
 
6562
+ # Clip-property keys whose SetClipProperty/SetMetadata write returns True but is
6563
+ # silently dropped by Resolve under common project configurations (issue #77).
6564
+ # For these we read the value back and refuse to report success unless it stuck.
6565
+ _WRITEBACK_VERIFY_KEYS = frozenset({"Reel Name"})
6566
+
6567
+ # Per-key hint explaining the most common cause of a non-persisting write.
6568
+ _WRITEBACK_VERIFY_HINTS = {
6569
+ "Reel Name": (
6570
+ "Reel Name is commonly gated by the project setting "
6571
+ "'General Options > Assist using reel names from the:'. When that is "
6572
+ "set to derive reel names from the source clip / embedding / filename "
6573
+ "pattern, Resolve ignores scripted writes to 'Reel Name' (the call "
6574
+ "still returns True). Set that option to blank/manual, or assign reel "
6575
+ "names in the Resolve UI; the scripting API cannot override the gate."
6576
+ ),
6577
+ }
6578
+
6579
+
6580
+ def _normalize_clip_property_value(value):
6581
+ """Coerce a clip-property value to a comparable, trimmed string."""
6582
+ if value is None:
6583
+ return ""
6584
+ return str(value).strip()
6585
+
6586
+
6587
+ def _verify_clip_property_writeback(clip, key, value):
6588
+ """Confirm a just-written, known-unreliable clip property actually persisted.
6589
+
6590
+ Returns a structured failure dict when Resolve accepted the write (returned
6591
+ True) but the value did not stick on read-back; returns ``None`` when the
6592
+ write verified, the key is not in the watch list, or we cannot read it back
6593
+ (in which case we don't override the original success). See issue #77.
6594
+ """
6595
+ if key not in _WRITEBACK_VERIFY_KEYS:
6596
+ return None
6597
+ actual, err = _safe_clip_call(clip, "GetClipProperty", key)
6598
+ if err is not None:
6599
+ return None # can't read back — don't contradict the reported result
6600
+ if isinstance(actual, dict): # GetClipProperty('') style dict, just in case
6601
+ actual = actual.get(key)
6602
+ if _normalize_clip_property_value(actual) == _normalize_clip_property_value(value):
6603
+ return None
6604
+ return {
6605
+ "success": False,
6606
+ "verified": False,
6607
+ "key": key,
6608
+ "requested": value,
6609
+ "actual": actual,
6610
+ "error": (
6611
+ f"Resolve reported success writing {key!r} but the value did not "
6612
+ f"persist (read-back returned {actual!r})."
6613
+ ),
6614
+ "hint": _WRITEBACK_VERIFY_HINTS.get(key),
6615
+ }
6616
+
6617
+
6618
+ def _verify_writeback(clip, written):
6619
+ """Verify a batch of just-written {key: value} pairs.
6620
+
6621
+ Returns the first silent-revert failure dict, else ``None``. Only keys in
6622
+ ``_WRITEBACK_VERIFY_KEYS`` are checked, so ordinary writes pay nothing.
6623
+ """
6624
+ if not isinstance(written, dict):
6625
+ return None
6626
+ for key, value in written.items():
6627
+ if key in _WRITEBACK_VERIFY_KEYS:
6628
+ fail = _verify_clip_property_writeback(clip, key, value)
6629
+ if fail:
6630
+ return fail
6631
+ return None
6632
+
6633
+
6562
6634
  def _media_pool_item_summary(clip):
6563
6635
  if not clip:
6564
6636
  return None
@@ -10213,7 +10285,17 @@ def _normalize_metadata(root, mp, p: Dict[str, Any]):
10213
10285
  third_party_ok = True
10214
10286
  for key, value in third_party.items():
10215
10287
  third_party_ok = bool(clip.SetThirdPartyMetadata(key, value)) and third_party_ok
10216
- results.append({"clip_id": clip_id, "success": ok and third_party_ok})
10288
+ row = {"clip_id": clip_id, "success": ok and third_party_ok}
10289
+ if ok and metadata:
10290
+ silent = _verify_writeback(clip, metadata)
10291
+ if silent:
10292
+ row["success"] = False
10293
+ row["verified"] = False
10294
+ row["unpersisted"] = {silent["key"]: silent["actual"]}
10295
+ row["error"] = silent["error"]
10296
+ if silent.get("hint"):
10297
+ row["hint"] = silent["hint"]
10298
+ results.append(row)
10217
10299
  return {"success": all(row.get("success") for row in results), "count": len(results), "missing": missing, "results": results}
10218
10300
 
10219
10301
 
@@ -15696,8 +15778,18 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15696
15778
  return {"metadata": _ser(clip.GetMetadata(p.get("key", "")))}
15697
15779
  elif action == "set_metadata":
15698
15780
  if "metadata" in p:
15699
- return {"success": bool(clip.SetMetadata(p["metadata"]))}
15700
- return {"success": bool(clip.SetMetadata(p["key"], p["value"]))}
15781
+ ok = bool(clip.SetMetadata(p["metadata"]))
15782
+ if ok:
15783
+ silent = _verify_writeback(clip, p["metadata"])
15784
+ if silent:
15785
+ return silent
15786
+ return {"success": ok}
15787
+ ok = bool(clip.SetMetadata(p["key"], p["value"]))
15788
+ if ok:
15789
+ silent = _verify_clip_property_writeback(clip, p["key"], p["value"])
15790
+ if silent:
15791
+ return silent
15792
+ return {"success": ok}
15701
15793
  elif action == "get_third_party_metadata":
15702
15794
  return {"metadata": _ser(clip.GetThirdPartyMetadata(p.get("key", "")))}
15703
15795
  elif action == "set_third_party_metadata":
@@ -15707,7 +15799,12 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15707
15799
  elif action == "get_clip_property":
15708
15800
  return {"properties": _ser(clip.GetClipProperty(p.get("key", "")))}
15709
15801
  elif action == "set_clip_property":
15710
- return {"success": bool(clip.SetClipProperty(p["key"], p["value"]))}
15802
+ ok = bool(clip.SetClipProperty(p["key"], p["value"]))
15803
+ if ok:
15804
+ silent = _verify_clip_property_writeback(clip, p["key"], p["value"])
15805
+ if silent:
15806
+ return silent
15807
+ return {"success": ok}
15711
15808
  elif action == "get_clip_color":
15712
15809
  return {"color": clip.GetClipColor()}
15713
15810
  elif action == "set_clip_color":
@@ -290,7 +290,10 @@ API_TRUTH: List[Dict[str, Any]] = [
290
290
  "MotionEstimation) and transform/crop/composite/opacity keys — "
291
291
  "not the speed value itself. There is no way to set a clip to a "
292
292
  "given % speed, reverse it, or author a speed ramp. Verified "
293
- "against the documented SetProperty key list (21.0.0).",
293
+ "against the documented SetProperty key list AND by live "
294
+ "mutating attempt on 21.0.0: SetProperty('Speed'|'PlaybackSpeed'"
295
+ "|'RetimeSpeed'|'ClipSpeed', 50) all return False, while "
296
+ "SetProperty('RetimeProcess', 1) returns True.",
294
297
  "recommended": "Set clip speed/retime in the Resolve UI; no scripted "
295
298
  "equivalent exists.",
296
299
  "tags": ["missing-method", "timeline", "retime", "speed"],
@@ -319,7 +322,10 @@ API_TRUTH: List[Dict[str, Any]] = [
319
322
  "automation, or to add/configure FairlightFX. SetProperty covers "
320
323
  "video transform only; the audio surface is read-only "
321
324
  "(GetSourceAudioChannelMapping, GetAudioMapping, voice "
322
- "isolation). Verified via dir() + SetProperty docs (21.0.0).",
325
+ "isolation). Verified via dir() + SetProperty docs AND by live "
326
+ "mutating attempt on 21.0.0: SetProperty('Volume'|'Level'|'Gain'"
327
+ "|'AudioVolume', 0) all return False (note 'Pan' is the VIDEO "
328
+ "transform key, not audio pan, so it misleadingly succeeds).",
323
329
  "recommended": "Mix in the Fairlight UI; only voice-isolation state and "
324
330
  "channel-mapping reads are scriptable.",
325
331
  "tags": ["missing-method", "audio", "fairlight"],
@@ -360,6 +366,94 @@ API_TRUTH: List[Dict[str, Any]] = [
360
366
  "tags": ["missing-method", "media-pool", "bins"],
361
367
  "submit": "missing",
362
368
  },
369
+ {
370
+ "symbol": "Per-subtitle text content and timing editing",
371
+ "object": "TimelineItem (subtitle track)",
372
+ "reality": "TimelineItem on a subtitle track exposes only 21 standard "
373
+ "transform/composite properties (Pan, Tilt, ZoomX, Opacity, "
374
+ "Crop, etc.). There are no methods to get or set subtitle "
375
+ "text (GetText/SetText), start time, end time, or duration "
376
+ "for individual subtitle items. Subtitles created via "
377
+ "CreateSubtitlesFromAudio or imported via the Resolve UI "
378
+ "cannot have their content or timing read or modified "
379
+ "programmatically. Verified via dir() and GetProperty() on "
380
+ "Resolve 21.0.0.48.",
381
+ "recommended": "No workaround exists — subtitle text and timing are "
382
+ "completely inaccessible from the scripting API. Must "
383
+ "be edited in the Resolve UI.",
384
+ "tags": ["missing-method", "subtitle", "text", "timing"],
385
+ "submit": "missing",
386
+ },
387
+ {
388
+ "symbol": "Subtitle track styling and presets",
389
+ "object": "TimelineItem / Timeline / Project",
390
+ "reality": "There is no API method to set or query subtitle font "
391
+ "family, font size, text color, background color, outline, "
392
+ "shadow, position, alignment, or to apply/query subtitle "
393
+ "style presets. TimelineItem.GetProperty() on subtitle "
394
+ "items returns only transform/composite keys. "
395
+ "Timeline.GetSetting() and Project.GetSetting() return "
396
+ "None for all probed subtitle-style keys (e.g. "
397
+ "'subtitleFontName', 'subtitleFontSize', "
398
+ "'subtitleTextColor', 'subtitleBackgroundColor', "
399
+ "'subtitlePosition', 'subtitleAlignment', "
400
+ "'subtitlePreset', 'subtitleStyle'). Verified via dir(), "
401
+ "GetProperty(), and GetSetting() on Resolve 21.0.0.48.",
402
+ "recommended": "No workaround exists — subtitle styling is UI-only. "
403
+ "Burn-in overlays via Fusion titles are a visual "
404
+ "alternative but do not produce proper subtitle tracks.",
405
+ "tags": ["missing-method", "subtitle", "style", "preset"],
406
+ "submit": "missing",
407
+ },
408
+ {
409
+ "symbol": "Speech recognition engine selection and SRT import",
410
+ "object": "Timeline",
411
+ "reality": "Timeline.CreateSubtitlesFromAudio(autoCaptionSettings) "
412
+ "always uses the built-in Resolve speech recognition "
413
+ "engine. There is no API parameter to select an alternative "
414
+ "provider (e.g. whisper-cli, Google Speech, AWS Transcribe). "
415
+ "The language selection via resolve.AUTO_CAPTION_LANGUAGE_* "
416
+ "is the only customization; the engine itself cannot be "
417
+ "changed. Furthermore, there is no API method to import an "
418
+ "SRT file into a subtitle track programmatically — "
419
+ "File -> Import -> Subtitle is UI-only.",
420
+ "recommended": "No workaround exists for provider selection or SRT "
421
+ "import. External transcripts must be converted to SRT "
422
+ "and imported through the Resolve UI.",
423
+ "tags": ["missing-method", "subtitle", "transcription",
424
+ "speech-recognition", "asr"],
425
+ "submit": "missing",
426
+ },
427
+ {
428
+ "symbol": "Media Pool folder rename",
429
+ "object": "MediaPool",
430
+ "reality": "MediaPool exposes AddSubFolder(name), "
431
+ "DeleteSubFolders([names]), and "
432
+ "MoveFolders([names], targetFolder) but no "
433
+ "RenameSubFolder(oldName, newName) method. Folders can be "
434
+ "created, deleted, and moved, but their names cannot be "
435
+ "changed through the API. Verified via dir() on Resolve "
436
+ "21.0.0.",
437
+ "recommended": "Delete and recreate the folder with the desired name, "
438
+ "or rename in the Resolve UI.",
439
+ "tags": ["missing-method", "media-pool", "folder"],
440
+ "submit": "missing",
441
+ },
442
+ {
443
+ "symbol": "hasattr() / getattr() on Resolve API objects (attribute fabrication)",
444
+ "object": "(all Resolve scripting objects)",
445
+ "reality": "The Python bridge returns a callable for ANY attribute name, so "
446
+ "hasattr(obj, 'TotallyMadeUpMethod') is always True and getattr "
447
+ "never raises. This makes capability detection by hasattr "
448
+ "impossible — verified on 21.0.0 (hasattr reported SetStart, "
449
+ "Razor, AddNode, GenerateProxy, AddSmartBin etc. as present "
450
+ "though none exist). Only dir() lists the real methods.",
451
+ "recommended": "Never probe method existence with hasattr/getattr; test "
452
+ "membership against dir(obj) instead. Calling a fabricated "
453
+ "method typically returns None/False with no error.",
454
+ "tags": ["bridge", "introspection", "silent-failure"],
455
+ "submit": "bug",
456
+ },
363
457
  {
364
458
  "symbol": "subprocess inheriting stdin under the MCP stdio server",
365
459
  "object": "(server runtime)",
@@ -370,6 +464,29 @@ API_TRUTH: List[Dict[str, Any]] = [
370
464
  "run while serving over stdio.",
371
465
  "tags": ["runtime", "stdio", "subprocess"],
372
466
  },
467
+ {
468
+ "symbol": "MediaPoolItem.SetClipProperty('Reel Name', ...)",
469
+ "object": "MediaPoolItem",
470
+ "signature": "(propertyName, propertyValue) -> bool",
471
+ "reality": "Setting the 'Reel Name' clip property returns True but the "
472
+ "value is silently dropped on read-back when the project is "
473
+ "configured to derive reel names automatically "
474
+ "(General Options > 'Assist using reel names from the:' set "
475
+ "to source clip file / embedding / filename pattern). The "
476
+ "same True-but-unpersisted behavior occurs via "
477
+ "SetMetadata('Reel Name', ...). Other clip properties on the "
478
+ "same clip (e.g. 'Comments') write and persist normally, so "
479
+ "this is field-specific, not a bridge/permission failure. "
480
+ "Verified on Resolve 21.0.0; reported as issue #77.",
481
+ "recommended": "After writing 'Reel Name', read it back with "
482
+ "GetClipProperty('Reel Name') and refuse to report "
483
+ "success on mismatch; surface the project-setting gate to "
484
+ "the caller (server._verify_clip_property_writeback).",
485
+ "tags": ["unreliable-return", "silent-failure", "metadata", "reel-name"],
486
+ "submit": "bug",
487
+ "issue": 77,
488
+ "mitigation": ["_verify_clip_property_writeback", "_verify_writeback"],
489
+ },
373
490
  ]
374
491
 
375
492