davinci-resolve-mcp 4.15.3 → 4.15.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 +30 -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 +42 -2
- package/src/utils/api_truth.py +62 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
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 v4.15.4 — add_keyframe no longer leaves a stray key
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **`fusion_comp(action="add_keyframe")` removes the key Fusion adds when an
|
|
10
|
+
input is first animated** (#317, thanks @assassin007-hub). Attaching an
|
|
11
|
+
empty BezierSpline keys the comp's current time with the input's old value,
|
|
12
|
+
so keying frames 10 and 20 left keys `[0, 10, 20]` and the curve ramped from
|
|
13
|
+
the old value instead of holding until the first intended key. The first
|
|
14
|
+
call on an input now deletes that key after the real one is written, reads
|
|
15
|
+
the keys back, and returns `removed_stray_keys`, or `stray_keys_remaining`
|
|
16
|
+
with a `warning` pointing to `delete_keyframe` if a key survives. A key
|
|
17
|
+
written at the current time simply overwrites it. Text Follower range
|
|
18
|
+
bounds (±1e9) are not treated as keys.
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- Two measured `api_truth` entries: BezierSpline LH/RH handles are offsets
|
|
23
|
+
from their key, not absolute points; and the stray key an empty spline
|
|
24
|
+
attach leaves, now attached to `fusion_comp add_keyframe`.
|
|
25
|
+
|
|
26
|
+
### Validation
|
|
27
|
+
|
|
28
|
+
- CI passed on Python 3.10 and 3.11 and on the advanced Node suite under
|
|
29
|
+
Node 20 and 22. Full offline suite run against main: 4,230 tests, with only
|
|
30
|
+
the 4 `source_media_guard` failures that always occur in a scratch worktree.
|
|
31
|
+
- Live behavior was measured by the contributor on Studio 21.0.2.4 (keys
|
|
32
|
+
`[0, 10, 20]` before, `[10, 20]` after `DeleteKeyFrames`). It was not
|
|
33
|
+
re-run on 19.1.3.7 for this release.
|
|
34
|
+
|
|
5
35
|
## What's New in v4.15.3 — CI covers the version floors
|
|
6
36
|
|
|
7
37
|
No server behavior changed. Pull requests are now tested on the oldest Python
|
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
|
-
> 本翻译对应 v4.15.
|
|
15
|
+
> 本翻译对应 v4.15.4 版 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 = "4.15.
|
|
40
|
+
VERSION = "4.15.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
package/src/granular/common.py
CHANGED
|
@@ -91,7 +91,7 @@ if not logging.getLogger().handlers:
|
|
|
91
91
|
handlers=[logging.StreamHandler()],
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
VERSION = "4.15.
|
|
94
|
+
VERSION = "4.15.4"
|
|
95
95
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
96
96
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
97
97
|
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 377-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "4.15.
|
|
14
|
+
VERSION = "4.15.4"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -32535,6 +32535,32 @@ def _fusion_input_spline(inp):
|
|
|
32535
32535
|
return None
|
|
32536
32536
|
|
|
32537
32537
|
|
|
32538
|
+
def _fusion_remove_stray_keys(inp, stray: List[float]) -> Dict[str, Any]:
|
|
32539
|
+
"""Delete the keys Fusion added when add_keyframe attached a spline.
|
|
32540
|
+
|
|
32541
|
+
The result is read back rather than trusted: a key that is still there is
|
|
32542
|
+
reported, so a caller is never told the curve holds only what it wrote.
|
|
32543
|
+
"""
|
|
32544
|
+
spline = _fusion_input_spline(inp)
|
|
32545
|
+
if spline is not None and _has_method(spline, "DeleteKeyFrames"):
|
|
32546
|
+
for frame in stray:
|
|
32547
|
+
try:
|
|
32548
|
+
spline.DeleteKeyFrames(frame)
|
|
32549
|
+
except Exception:
|
|
32550
|
+
pass
|
|
32551
|
+
remaining = [f for f in _fusion_keyframe_frames(inp)
|
|
32552
|
+
if any(abs(f - s) < 1e-6 for s in stray)]
|
|
32553
|
+
removed = [s for s in stray if not any(abs(s - f) < 1e-6 for f in remaining)]
|
|
32554
|
+
if not remaining:
|
|
32555
|
+
return _ok(removed_stray_keys=removed)
|
|
32556
|
+
return _ok(
|
|
32557
|
+
removed_stray_keys=removed,
|
|
32558
|
+
stray_keys_remaining=remaining,
|
|
32559
|
+
warning=(f"Attaching the spline also keyed frame(s) {remaining} with the old value "
|
|
32560
|
+
"and they could not be removed; remove them with delete_keyframe."),
|
|
32561
|
+
)
|
|
32562
|
+
|
|
32563
|
+
|
|
32538
32564
|
def _fusion_delete_keyframe(tool, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
32539
32565
|
"""Remove one keyframe from an animated Fusion input. (issue #155)
|
|
32540
32566
|
|
|
@@ -32953,6 +32979,7 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
32953
32979
|
_already_animated = inp.GetConnectedOutput() is not None
|
|
32954
32980
|
except Exception:
|
|
32955
32981
|
_already_animated = False
|
|
32982
|
+
_stray = []
|
|
32956
32983
|
if not _already_animated:
|
|
32957
32984
|
_is_nest, _members = _fusion_nest_members(tool, p["input_name"])
|
|
32958
32985
|
if _is_nest:
|
|
@@ -32986,8 +33013,21 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
32986
33013
|
"attach it with add_modifier, then drive the "
|
|
32987
33014
|
"modifier tool it returns.",
|
|
32988
33015
|
)
|
|
33016
|
+
# Attaching an empty spline keys the comp's CURRENT time with the
|
|
33017
|
+
# input's old value (measured on Studio 21.0.2.4: keying 10 and 20
|
|
33018
|
+
# left [0, 10, 20]). Note it now; it is removed after the real key
|
|
33019
|
+
# lands, since DeleteKeyFrames will not remove a spline's only key.
|
|
33020
|
+
# Only a spline holds keys; a text Follower answers GetKeyFrames
|
|
33021
|
+
# with its +/-1e9 render-range bounds, which are not keys.
|
|
33022
|
+
_spline = _fusion_input_spline(tool[p["input_name"]])
|
|
33023
|
+
if _spline is not None and _has_method(_spline, "DeleteKeyFrames"):
|
|
33024
|
+
_stray = [f for f in _fusion_keyframe_frames(tool[p["input_name"]])
|
|
33025
|
+
if abs(f) < 1e8]
|
|
32989
33026
|
tool[p["input_name"]][p["time"]] = p["value"]
|
|
32990
|
-
|
|
33027
|
+
_stray = [f for f in _stray if abs(f - float(p["time"])) >= 1e-6]
|
|
33028
|
+
if not _stray:
|
|
33029
|
+
return _ok()
|
|
33030
|
+
return _fusion_remove_stray_keys(tool[p["input_name"]], _stray)
|
|
32991
33031
|
finally:
|
|
32992
33032
|
comp.EndUndo(True)
|
|
32993
33033
|
|
package/src/utils/api_truth.py
CHANGED
|
@@ -3915,6 +3915,64 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
3915
3915
|
"verified_on": "DaVinci Resolve Studio 19.1.3.7",
|
|
3916
3916
|
"mitigation": ["_timeline_replace_source_range_impl"],
|
|
3917
3917
|
},
|
|
3918
|
+
{
|
|
3919
|
+
"symbol": "BezierSpline.SetKeyFrames (handle coordinates)",
|
|
3920
|
+
"object": "Fusion BezierSpline",
|
|
3921
|
+
"signature": "(keyframes, replace) -> None",
|
|
3922
|
+
"reality": "A key's LH/RH handles are OFFSETS from that key, {1: dTime, 2: dValue}, "
|
|
3923
|
+
"not absolute (time, value) points. GetKeyFrames() reads them back the "
|
|
3924
|
+
"same way: Fusion's own auto-created first key returns RH {1: 2.0, "
|
|
3925
|
+
"2: -0.333} for a key at frame 0 easing toward the next one. Absolute "
|
|
3926
|
+
"coordinates are accepted without complaint and read back verbatim, so "
|
|
3927
|
+
"nothing flags the mistake; the curve is simply wrong. Measured with keys "
|
|
3928
|
+
"0 -> 1 at frames 10 -> 20: flat handles written relative ({3, 0} / "
|
|
3929
|
+
"{-3, 0}) sample 0, 0.113, 0.5, 0.887, 1 at frames 10/12/15/18/20, a clean "
|
|
3930
|
+
"ease-in-out; the same intent written absolute ({13, 0} / {17, 1}) "
|
|
3931
|
+
"samples 0, 0.015, 0.089, 0.217, 1. A whip authored with absolute handles "
|
|
3932
|
+
"moved the wrong way (0.5 -> 0.56) before snapping to its end value.",
|
|
3933
|
+
"recommended": "Write handles relative to their key: RH = {segment * k, 0} and "
|
|
3934
|
+
"LH = {-segment * k, 0} gives a flat-tangent ease, where k is the handle "
|
|
3935
|
+
"length as a fraction of the segment (0.33 is a standard ease, 0.6 to "
|
|
3936
|
+
"0.75 an expo-like snap). Prove a curve by sampling "
|
|
3937
|
+
"tool.GetInput(name, frame) at a few frames, not by reading the keys "
|
|
3938
|
+
"back.",
|
|
3939
|
+
"tags": ["fusion", "keyframe", "silent-failure", "units"],
|
|
3940
|
+
"verified_on": "DaVinci Resolve Studio 21.0.2.4",
|
|
3941
|
+
"measured": "2026-10-26 on a Fusion composition clip: BezierSpline keyed 10 -> 20 "
|
|
3942
|
+
"with relative vs absolute flat handles, connected to Blur YBlurSize, "
|
|
3943
|
+
"sampled with GetInput at frames 10/12/15/18/20; whip Transform Center "
|
|
3944
|
+
"via XYPath confirmed in rendered frames.",
|
|
3945
|
+
},
|
|
3946
|
+
{
|
|
3947
|
+
"symbol": "BezierSpline attach (stray key at the current time)",
|
|
3948
|
+
"object": "Fusion Tool input / BezierSpline",
|
|
3949
|
+
"signature": "tool.AddModifier(input, 'BezierSpline') | tool[input] = comp.BezierSpline()",
|
|
3950
|
+
"reality": "Connecting an EMPTY spline to an input, by either route, adds a key at "
|
|
3951
|
+
"the comp's current time holding the input's old value. Keys set "
|
|
3952
|
+
"afterwards are added next to it, and SetKeyFrames(keys, True) does not "
|
|
3953
|
+
"remove it despite the replace flag. Measured with CurrentTime 0: "
|
|
3954
|
+
"AddModifier then input[10] = 5, input[20] = 9 left keys [0, 10, 20] with "
|
|
3955
|
+
"1.0 (the old value) at frame 0, so the value drifts from 1.0 before the "
|
|
3956
|
+
"first intended key instead of holding. The stray key can be deleted with "
|
|
3957
|
+
"spline.DeleteKeyFrames(time) once other keys exist; deleting it while it "
|
|
3958
|
+
"is the only key does nothing. A spline that already has keys when it is "
|
|
3959
|
+
"connected gets no extra key.",
|
|
3960
|
+
"recommended": "Build the spline first and connect it last: s = comp.BezierSpline(); "
|
|
3961
|
+
"s.SetKeyFrames(keys, True); tool[input] = s. When the spline is "
|
|
3962
|
+
"already attached, delete the stray key (DeleteKeyFrames(time)) after "
|
|
3963
|
+
"the intended keys are written, then confirm with GetKeyFrames(). "
|
|
3964
|
+
"fusion_comp add_keyframe does this itself: its first call on an "
|
|
3965
|
+
"input deletes the stray key, reports it as removed_stray_keys, and "
|
|
3966
|
+
"returns stray_keys_remaining plus a warning if it survives.",
|
|
3967
|
+
"tags": ["fusion", "keyframe", "silent-failure"],
|
|
3968
|
+
"mitigation": ["fusion_comp.add_keyframe", "_fusion_remove_stray_keys"],
|
|
3969
|
+
"verified_on": "DaVinci Resolve Studio 21.0.2.4",
|
|
3970
|
+
"measured": "2026-10-26 on a Fusion composition clip (CurrentTime 0), on a Blur: "
|
|
3971
|
+
"AddModifier + two indexed writes -> keys [0, 10, 20]; empty spline "
|
|
3972
|
+
"connected then SetKeyFrames(replace=True) -> [0, 10, 20]; "
|
|
3973
|
+
"DeleteKeyFrames(0.0) then -> [10, 20]; spline keyed before connect -> "
|
|
3974
|
+
"[10, 20].",
|
|
3975
|
+
},
|
|
3918
3976
|
|
|
3919
3977
|
]
|
|
3920
3978
|
|
|
@@ -3976,7 +4034,10 @@ ACTION_SYMBOLS: Dict[Tuple[str, str], List[str]] = {
|
|
|
3976
4034
|
("timeline", "duplicate"): ["Timeline.DuplicateTimeline"],
|
|
3977
4035
|
("project_manager", "archive"): ["ProjectManager.ArchiveProject"],
|
|
3978
4036
|
("fusion_comp", "add_modifier"): ["Tool.AddModifier", "Tool.AddModifier (NestControl inputs)"],
|
|
3979
|
-
("fusion_comp", "add_keyframe"): [
|
|
4037
|
+
("fusion_comp", "add_keyframe"): [
|
|
4038
|
+
"Tool.AddModifier (NestControl inputs)",
|
|
4039
|
+
"BezierSpline attach (stray key at the current time)",
|
|
4040
|
+
],
|
|
3980
4041
|
("project_manager", "safe_project_archive"): ["ProjectManager.ArchiveProject"],
|
|
3981
4042
|
("render", "start"): ["Project.StartRendering"],
|
|
3982
4043
|
("resolve_control", "set_high_priority"): [
|