davinci-resolve-mcp 2.104.2 → 2.104.4

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,52 @@
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.104.4
6
+
7
+ Hardening pass over the classes the v2.104.2 batch exposed, live-verified on
8
+ Studio 19.1.3.7.
9
+
10
+ **set_title_text works on builds where SetProperty cannot.** On Studio 19.1.3
11
+ a Text+ item rejects every title property key, so set_title_text failed while
12
+ the item's Fusion comp accepted the same text all along. The setter now falls
13
+ back to writing StyledText on the TextPlus tool — deliberately UNLOCKED, per
14
+ the comp-lock render bug — and reports success only after reading the input
15
+ back. Live-verified end to end: set via fallback, read via get_title_text,
16
+ and a rendered frame confirms the text reaches the output (mean luma above
17
+ black). bulk_set_title_text inherits the fallback. PR #166's discarded-return
18
+ guard caught the fallback's bare SetInput during development — the allowlist
19
+ entry records that the write is verified by readback, which is stronger than
20
+ the bool Fusion doesn't return.
21
+
22
+ **verify_output no longer flags deliberate short renders.** A single-frame
23
+ capture tripped the mark-range-collapse warning, because the checker cannot
24
+ distinguish a caller-chosen short range from a Resolve-rewritten one. Passing
25
+ expected_frames / expected_duration_seconds matching the mark range now
26
+ suppresses the collapse warning; an unstated short range still warns.
27
+
28
+ **#171's scope measured: the internal-name override is FCP7-specific.** An
29
+ OTIO export re-imported under a new timelineName creates a new timeline
30
+ (measured 19.1.3.7), so the api_truth entry now says the override is an FCP7
31
+ XML behavior, not a general import rule.
32
+
33
+ **One more #167-class constant found and removed.** effect-encoder's exported
34
+ "common double values" hex table — consumed by nothing — carried a '0.9'
35
+ entry that decoded to 0.8. Deleted; the sweep found the remaining converters
36
+ (editorial, media-inventory, the Python timecode helpers) already round
37
+ correctly.
38
+
39
+ ## What's New in v2.104.3
40
+
41
+ Documentation follow-through on the v2.104.2 batch.
42
+
43
+ - The FCP7 internal-sequence-name-overrides-timelineName behavior (#171) is now
44
+ a submit-tagged api_truth entry, so it feeds the Blackmagic-facing
45
+ limitations report alongside the fix that works around it.
46
+ - `project_db.list_subtitle_styles`'s styled:false note now states that the
47
+ "must be styled once in the UI" precondition covers the scripted
48
+ `ImportMedia(srt)` + `AppendToTimeline` route too (confirmed by the #169
49
+ reporter on Studio 21.0.4.5), not only tracks added empty in the UI.
50
+
5
51
  ## What's New in v2.104.2
6
52
 
7
53
  A contributor batch: two merged PRs, one PR converted into its fix, and four
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.104.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.104.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.104.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.104.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.104.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.104.4 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -12,7 +12,7 @@ that none exists).
12
12
 
13
13
  **Verified on:** DaVinci Resolve Studio 21.0.2
14
14
 
15
- **Totals:** 33 missing capabilities, 39 bugs / unreliable behaviors.
15
+ **Totals:** 33 missing capabilities, 40 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
@@ -517,6 +517,15 @@ values, or automation-hostile modal prompts.
517
517
  - **Reference:** [issue #77](https://github.com/samuelgursky/davinci-resolve-mcp/issues/77)
518
518
  - **Tags:** unreliable-return, silent-failure, metadata, reel-name
519
519
 
520
+ ### MediaPool.ImportTimelineFromFile (internal sequence name overrides timelineName)
521
+
522
+ - **Object:** `MediaPool`
523
+ - **Signature:** `(filePath, {timelineName, importSourceClips, ...}) -> Timeline`
524
+ - **Behavior:** For FCP7 XML, the sequence name INSIDE the file wins over the timelineName import option. When the internal name matches an existing timeline, the call returns that EXISTING timeline — no error, no new timeline — so an export→edit→re-import loop keying uniqueness on the option 'succeeds' while operating on one timeline forever (issue #171, Studio 21.0.4.5; wrapper behavior verified on 19.1.3.7). Distinct from the documented repeated-timelineName None return: here the option is fresh and the file's name is stale.
525
+ - **Workaround / current handling:** Rewrite the <sequence><name> inside the file to the intended name before importing — timeline.import_timeline_checked does this automatically for FCP7 XML and errors when a non-rewritable format still returns an existing timeline. Never treat a truthy return as proof of creation; check the returned timeline's id against the pre-import set.
526
+ - **Reference:** [issue #171](https://github.com/samuelgursky/davinci-resolve-mcp/issues/171)
527
+ - **Tags:** timeline, import, silent-failure, unreliable-return
528
+
520
529
  ### Timeline.DeleteClips (requires the Edit page; flaky first attempt)
521
530
 
522
531
  - **Object:** `Timeline`
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.104.2"
40
+ VERSION = "2.104.4"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.104.2",
3
+ "version": "2.104.4",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -259,7 +259,7 @@ export const projectDbTool = {
259
259
  return {
260
260
  tracks,
261
261
  note: tracks.some((t) => t.styled === false)
262
- ? 'Tracks with styled:false carry no style blob (Resolve writes a NumLayers-only stub until the track is styled once in the UI); set_subtitle_style cannot patch those.'
262
+ ? 'Tracks with styled:false carry no style blob (Resolve writes a NumLayers-only stub until the track is styled once in the UI); set_subtitle_style cannot patch those. This covers scripted routes too: a track built by MediaPool.ImportMedia(srt) + AppendToTimeline([srtClip]) reports styled:false (confirmed on Studio 21.0.4.5, issue #169), so style once in the UI regardless of how the track was created.'
263
263
  : undefined,
264
264
  };
265
265
  } finally {
@@ -180,16 +180,10 @@ function encodeFloat(value) {
180
180
  return buffer.toString('hex');
181
181
  }
182
182
 
183
- /**
184
- * Common double values in hex for quick reference
185
- */
186
- const DOUBLE_VALUES = {
187
- '1.0': '000000000000f03f',
188
- '1.1': '9a9999999999f13f',
189
- '0.9': '9a9999999999e93f',
190
- '2.0': '0000000000000040',
191
- '0.5': '000000000000e03f',
192
- };
183
+ // A "common double values in hex" reference table used to sit here. It was
184
+ // consumed by nothing, and its '0.9' entry actually decoded to 0.8 — the same
185
+ // hand-typed-hex rot that put 30000/1001 in drt.author's 23.976 slot
186
+ // (issue #167). Encode with encodeDouble()/writeDoubleLE; never copy hex.
193
187
 
194
188
  /**
195
189
  * Build a protobuf field with double value
@@ -636,5 +630,4 @@ module.exports = {
636
630
  // Constants
637
631
  EFFECT_PARAMS,
638
632
  WIRE_TYPES,
639
- DOUBLE_VALUES,
640
633
  };
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.104.2"
90
+ VERSION = "2.104.4"
91
91
  logger = logging.getLogger("davinci-resolve-mcp")
92
92
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
93
93
  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 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.104.2"
14
+ VERSION = "2.104.4"
15
15
 
16
16
  import base64
17
17
  import os
@@ -5745,9 +5745,41 @@ def _timeline_set_title_text(tl, p: Dict[str, Any]) -> Dict[str, Any]:
5745
5745
  "attempts": attempts,
5746
5746
  }
5747
5747
 
5748
+ # SetProperty exposes no title keys on every build (Text+ on Studio 19.1.3
5749
+ # rejects all of them), while the item's Fusion comp carries the same text
5750
+ # as the TextPlus tool's StyledText input and accepts writes there — the
5751
+ # route get_title_text already reads. Deliberately a bare, UNLOCKED
5752
+ # SetInput: the comp-lock render bug (api_truth / v2.98.5) eats writes
5753
+ # wrapped in Comp.Lock(), and unlocked writes are the safe ones.
5754
+ if not bool(p.get("as_styled_xml", p.get("styled", False))):
5755
+ try:
5756
+ if int(item.GetFusionCompCount() or 0) > 0:
5757
+ comp = item.GetFusionCompByIndex(1)
5758
+ tools = comp.GetToolList(False, "TextPlus") if comp else None
5759
+ for tool_key in (tools or {}):
5760
+ tool = tools[tool_key]
5761
+ tool.SetInput("StyledText", text)
5762
+ confirmed = tool.GetInput("StyledText")
5763
+ rec = {"mode": "fusion_comp", "property_key": "StyledText",
5764
+ "success": confirmed == text}
5765
+ if confirmed != text:
5766
+ rec["readback"] = _ser(confirmed)
5767
+ attempts.append(rec)
5768
+ if confirmed == text:
5769
+ return {
5770
+ "success": True,
5771
+ "timeline_item_id": _safe_timeline_item_id(item),
5772
+ "property_key": "StyledText",
5773
+ "mode": "fusion_comp",
5774
+ "attempts": attempts,
5775
+ }
5776
+ except Exception as exc:
5777
+ attempts.append({"mode": "fusion_comp", "success": False, "error": str(exc)})
5778
+
5748
5779
  return {
5749
5780
  "success": False,
5750
- "error": "SetProperty did not succeed; run title_property_scan, copy a real key from `properties`, "
5781
+ "error": "SetProperty did not succeed and the Fusion-comp StyledText fallback found no "
5782
+ "TextPlus tool to write; run title_property_scan, copy a real key from `properties`, "
5751
5783
  "and pass `property_key` (see `attempts` for diagnostics).",
5752
5784
  "attempts": attempts,
5753
5785
  }
@@ -18833,7 +18865,9 @@ def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
18833
18865
  cross-checks the job's timeline items (which read back their real
18834
18866
  positions): items before the timeline start and a mark range far
18835
18867
  smaller than the item extent both warn. Pass expected_frames or
18836
- expected_duration_seconds when you know what the render should hold.
18868
+ expected_duration_seconds when you know what the render should hold
18869
+ a DELIBERATE short render (single-frame capture, excerpt) reads as a
18870
+ collapse warning unless the stated expectation matches the mark range.
18837
18871
  Verify BEFORE deleting the job; deleted jobs carry no TargetDir.
18838
18872
  start(job_ids?, interactive?) -> {success}
18839
18873
  stop() -> {success}
@@ -18971,7 +19005,28 @@ def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
18971
19005
  "zero, issue #164). The items read back as placed; the "
18972
19006
  "render is a stub."
18973
19007
  )
19008
+ caller_expected_frames = None
19009
+ if p.get("expected_frames") is not None:
19010
+ try:
19011
+ caller_expected_frames = int(p["expected_frames"])
19012
+ except (TypeError, ValueError):
19013
+ caller_expected_frames = None
19014
+ elif p.get("expected_duration_seconds") is not None and fps:
19015
+ try:
19016
+ caller_expected_frames = int(round(float(p["expected_duration_seconds"]) * fps))
19017
+ except (TypeError, ValueError):
19018
+ caller_expected_frames = None
19019
+ # A caller who states the render SHOULD be this short (a
19020
+ # deliberate single-frame or excerpt render) is not a collapse
19021
+ # victim — the warning is for ranges Resolve rewrote, which the
19022
+ # caller by definition did not expect.
19023
+ intentional_short_range = (
19024
+ caller_expected_frames is not None
19025
+ and mark_frames is not None
19026
+ and abs(caller_expected_frames - mark_frames) <= 1
19027
+ )
18974
19028
  if (extent_lo is not None and mark_frames is not None
19029
+ and not intentional_short_range
18975
19030
  and mark_frames < (extent_hi - extent_lo) * 0.5):
18976
19031
  warnings.append(
18977
19032
  f"The job's mark range ({mark_frames} frame(s)) is under "
@@ -1853,6 +1853,32 @@ API_TRUTH: List[Dict[str, Any]] = [
1853
1853
  "when mirroring keep-ranges into clipInfos.",
1854
1854
  "tags": ["timeline", "edit", "off-by-one", "readback"],
1855
1855
  },
1856
+ {
1857
+ "symbol": "MediaPool.ImportTimelineFromFile (internal sequence name overrides timelineName)",
1858
+ "object": "MediaPool",
1859
+ "signature": "(filePath, {timelineName, importSourceClips, ...}) -> Timeline",
1860
+ "reality": "For FCP7 XML, the sequence name INSIDE the file wins over "
1861
+ "the timelineName import option. When the internal name "
1862
+ "matches an existing timeline, the call returns that "
1863
+ "EXISTING timeline — no error, no new timeline — so an "
1864
+ "export→edit→re-import loop keying uniqueness on the option "
1865
+ "'succeeds' while operating on one timeline forever "
1866
+ "(issue #171, Studio 21.0.4.5; wrapper behavior verified on "
1867
+ "19.1.3.7). Distinct from the documented repeated-"
1868
+ "timelineName None return: here the option is fresh and the "
1869
+ "file's name is stale.",
1870
+ "recommended": "Rewrite the <sequence><name> inside the file to the "
1871
+ "intended name before importing — "
1872
+ "timeline.import_timeline_checked does this "
1873
+ "automatically for FCP7 XML and errors when a "
1874
+ "non-rewritable format still returns an existing "
1875
+ "timeline. Never treat a truthy return as proof of "
1876
+ "creation; check the returned timeline's id against "
1877
+ "the pre-import set.",
1878
+ "tags": ["timeline", "import", "silent-failure", "unreliable-return"],
1879
+ "submit": "bug",
1880
+ "issue": 171,
1881
+ },
1856
1882
  {
1857
1883
  "symbol": "MediaPool.AppendToTimeline clipInfo recordFrame (timeline-absolute origin)",
1858
1884
  "object": "MediaPool",