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 +26 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +43 -1
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
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v2.215.
|
|
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.
|
|
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
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.215.
|
|
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.
|
|
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.
|