davinci-resolve-mcp 2.215.0 → 2.215.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 CHANGED
@@ -2,6 +2,32 @@
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.215.1 — single-frame capture survives per-clip render mode and a vanished stills folder
6
+
7
+ ### Fixed
8
+
9
+ - **`timeline_frame capture` forces Single-clip render mode and restores it.**
10
+ Measured 2026-09-09 on a project whose delivery preset was "Individual
11
+ clips": every single-frame capture reported success, wrote no file, and took
12
+ 30+ seconds, because in that mode Resolve ignores `CustomName`, renders the
13
+ whole clip under its own naming, and the frame this helper waits for never
14
+ appears. The helper now reads `GetCurrentRenderMode()`, switches to single
15
+ clip (1) for the render, puts the mode back afterwards, and refuses with
16
+ `RENDER_MODE_REFUSED` before adding a job if the switch fails.
17
+ - **The shared stills folder is recreated before every directory listing.**
18
+ Every sandbox path redirects to one `~/Documents/resolve-stills`, and the
19
+ helper's own cleanup removes it once it empties, so a concurrent capture (or
20
+ anything else) can take it away between the makedirs at the top and the
21
+ `os.listdir` that follows. Frame 81 of a 214-frame QC batch died on exactly
22
+ that. Recreate, do not assume.
23
+
24
+ ### Measured (not code)
25
+
26
+ - A per-clip `.drx` carries the clip node graph losslessly (applying a clip's
27
+ own emitted grade back onto it re-renders bit-identically, PSNR 99 on 5/5),
28
+ but NOT colour-group pre/post grades, NOT Colour-page input sizing, and with
29
+ `grade_mode` 0 NOT keyframes. A traced conform whose source used groups needs
30
+ the group grades carried separately.
5
31
  ## What's New in v2.215.0 — mutating operations write a structured operation log; the free-edition bridge is version-qualified for Resolve 21.1
6
32
 
7
33
  ### Added
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.215.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.215.1-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.215.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.215.1-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.215.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.215.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.215.0"
40
+ VERSION = "2.215.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.215.0",
3
+ "version": "2.215.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.215.0"
90
+ VERSION = "2.215.1"
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.215.0"
14
+ VERSION = "2.215.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -14963,6 +14963,18 @@ def _playhead_frame_render(proj, tl, p: Dict[str, Any]):
14963
14963
  original_fc = proj.GetCurrentRenderFormatAndCodec()
14964
14964
  except Exception:
14965
14965
  pass
14966
+ # Render MODE is project state too, and it decides whether the capture can
14967
+ # work at all. In "Individual clips" mode (0) Resolve ignores CustomName,
14968
+ # renders the WHOLE clip under the frame's own file naming, and the
14969
+ # single-frame file this helper waits for never appears — measured
14970
+ # 2026-09-09 on a project whose delivery preset was per-clip: every capture
14971
+ # reported success, wrote no file, and took 30+ s rendering the clip.
14972
+ # Force single clip (1) for the capture and put the mode back afterwards.
14973
+ original_mode = None
14974
+ try:
14975
+ original_mode = proj.GetCurrentRenderMode()
14976
+ except Exception:
14977
+ original_mode = None
14966
14978
  # Rendering pulls Resolve onto the Deliver page and moves the playhead;
14967
14979
  # measured leaving the user on Deliver at a different frame. Both are ours
14968
14980
  # to put back.
@@ -15014,6 +15026,16 @@ def _playhead_frame_render(proj, tl, p: Dict[str, Any]):
15014
15026
 
15015
15027
  job = None
15016
15028
  try:
15029
+ if original_mode is not None and original_mode != 1:
15030
+ if not proj.SetCurrentRenderMode(1):
15031
+ return _err(
15032
+ "Resolve refused to switch the render mode to single clip; in "
15033
+ "individual-clips mode a single-frame capture renders the whole clip "
15034
+ "under Resolve's own naming and the captured file never appears",
15035
+ code="RENDER_MODE_REFUSED", category="api_error",
15036
+ remediation="render(action='set_mode', params={'mode': 1}) then retry.",
15037
+ state={"render_mode": original_mode},
15038
+ )
15017
15039
  codecs = proj.GetRenderCodecs("JPEG" if fmt == "jpg" else fmt.upper()) or {}
15018
15040
  codec = list(codecs.values())[0] if codecs else fmt
15019
15041
  if not proj.SetCurrentRenderFormatAndCodec(fmt, codec):
@@ -15040,6 +15062,13 @@ def _playhead_frame_render(proj, tl, p: Dict[str, Any]):
15040
15062
  job = proj.AddRenderJob()
15041
15063
  if not job:
15042
15064
  return _err("AddRenderJob returned nothing", code="RENDER_JOB_FAILED", category="api_error")
15065
+ # The folder is shared (every sandbox path redirects to one
15066
+ # ~/Documents/resolve-stills) and the cleanup below removes it when it
15067
+ # empties, so another capture — or anything else — can take it away
15068
+ # between the makedirs above and here. Measured 2026-09-09: frame 81 of a
15069
+ # 214-frame QC batch died in os.listdir on the missing folder. Recreate,
15070
+ # don't assume.
15071
+ os.makedirs(folder, exist_ok=True)
15043
15072
  before = set(os.listdir(folder))
15044
15073
  # Positional on purpose: the free-edition bridge proxies method calls
15045
15074
  # positionally, and a keyword argument dies inside _BoundMethod with
@@ -15087,6 +15116,19 @@ def _playhead_frame_render(proj, tl, p: Dict[str, Any]):
15087
15116
  proj.DeleteRenderJob(job)
15088
15117
  except Exception:
15089
15118
  pass
15119
+ if original_mode is not None and original_mode != 1:
15120
+ # Same teardown contract as the format/codec restore below.
15121
+ try:
15122
+ restored_mode = bool(proj.SetCurrentRenderMode(original_mode))
15123
+ except Exception as exc:
15124
+ restored_mode, mode_exc = False, exc
15125
+ else:
15126
+ mode_exc = None
15127
+ if not restored_mode:
15128
+ logger.warning(
15129
+ "frame capture could not restore render mode %r: %s",
15130
+ original_mode, mode_exc or "SetCurrentRenderMode returned False",
15131
+ )
15090
15132
  if original_fc:
15091
15133
  # A failed restore leaves the Deliver page on the capture's format
15092
15134
  # and codec, which the user's next render would silently inherit.