davinci-resolve-mcp 2.104.8 → 2.104.10
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 +53 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/reference/api-limitations.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +38 -3
- package/src/utils/api_truth.py +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
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.10
|
|
6
|
+
|
|
7
|
+
Stones turned on the live-validation backlog, on Studio 19.1.3.7.
|
|
8
|
+
|
|
9
|
+
**AAF live import, validated at last** (marked "NOT live-validated" since
|
|
10
|
+
2026-07-06): an EXPORT_AAF/EXPORT_AAF_NEW round trip imports cleanly with
|
|
11
|
+
importSourceClips=false, lands fully offline (the documented turnover shape),
|
|
12
|
+
and preserves the start timecode. The naming matrix across import formats is
|
|
13
|
+
now complete and in api_truth — FCP7 XML ignores timelineName (internal name
|
|
14
|
+
wins, the #171 trap); AAF honours timelineName when given, else its internal
|
|
15
|
+
name; OTIO honours timelineName; .drt names the timeline after the file. Only
|
|
16
|
+
FCP7 exhibits the returned-existing trap. The AAF post-import relink leg was
|
|
17
|
+
exercised too: under importSourceClips=false it correctly reports "no Media
|
|
18
|
+
Pool Items to relink" — the API relinks pool items, and none exist on that
|
|
19
|
+
path — so its precondition is now stated instead of assumed.
|
|
20
|
+
|
|
21
|
+
**safe_quick_export verifies its output.** RenderWithQuickExport's status
|
|
22
|
+
dict was the last render surface trusted without a file check: a success
|
|
23
|
+
status that wrote nothing read as an export. The files that actually landed
|
|
24
|
+
in TargetDir are now listed with size and ffprobe duration, and a success
|
|
25
|
+
status with no new file flips to an error.
|
|
26
|
+
|
|
27
|
+
**Housekeeping:** the delete-locked scratch project from the v2.104.7 session
|
|
28
|
+
is gone — the pre-restart DeleteProject had returned False while actually
|
|
29
|
+
succeeding, the documented DeleteProject lie caught in the wild. A clean
|
|
30
|
+
Resolve quit/relaunch verified the wedge entry's other half: Quit() works
|
|
31
|
+
when no orphaned render holds the pipeline.
|
|
32
|
+
|
|
33
|
+
## What's New in v2.104.9
|
|
34
|
+
|
|
35
|
+
**The NTSC coverage gap, closed.** The conform fixtures were integer-rate
|
|
36
|
+
only — which is how parseEDL ran exact-rate timecode math against a
|
|
37
|
+
nominal-rate writer for years (fixed in v2.104.6, convention measured against
|
|
38
|
+
Resolve's own GetStartFrame). New fixtures now exercise the pipeline at
|
|
39
|
+
29.97: a broadcast-start EDL parses to nominal frames (an NDF minute is 1800
|
|
40
|
+
frames, butt cuts stay gapless), the EDL write→parse round trip is
|
|
41
|
+
frame-identical, media-inventory's tc↔frames round trip is the identity at
|
|
42
|
+
all three NTSC rates, and drop-frame pins to the canonical values
|
|
43
|
+
(01:00:00;00 → 107892 — the number that haunted the #168 saga, now living
|
|
44
|
+
where it belongs). Cross-language pin tests assert the Python converters
|
|
45
|
+
(_timecode_to_frame_id, multicam) and the Node converters agree on the same
|
|
46
|
+
canonical values, so a change that moves one side fails the other side's
|
|
47
|
+
suite.
|
|
48
|
+
|
|
49
|
+
**Python dependency stack: audited to zero.** pip-audit over the dev venv
|
|
50
|
+
found and cleared advisories in urllib3, requests, python-multipart,
|
|
51
|
+
setuptools, starlette (0.52 → 1.6 — the MCP SDK tolerated the major, full
|
|
52
|
+
suite green), pyjwt, pydantic-settings, pygments, pillow, idna, msgpack,
|
|
53
|
+
cryptography, pip itself, and torch 2.13 (with the matching torchvision).
|
|
54
|
+
Also found: the venv's mcp SDK was at 1.27.0, BELOW the repo's own >=1.29
|
|
55
|
+
floor, and carrying a CVE — now 1.29.1. Both stacks (npm and pip) report
|
|
56
|
+
zero known vulnerabilities, with clean resolver constraints.
|
|
57
|
+
|
|
5
58
|
## What's New in v2.104.8
|
|
6
59
|
|
|
7
60
|
More laps: the aggregation class swept to completion, and the dependency
|
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.104.
|
|
15
|
+
> 本翻译对应 v2.104.10 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -521,7 +521,7 @@ values, or automation-hostile modal prompts.
|
|
|
521
521
|
|
|
522
522
|
- **Object:** `MediaPool`
|
|
523
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.
|
|
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. THE NAMING AUTHORITY DIFFERS PER FORMAT (all measured on 19.1.3.7): FCP7 XML ignores timelineName entirely (internal <name> wins); AAF honours timelineName when given and falls back to its internal name; OTIO honours timelineName; and .drt names the timeline after the FILE (see the .drt entry below). Only FCP7 exhibits the returned-existing trap.
|
|
525
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
526
|
- **Reference:** [issue #171](https://github.com/samuelgursky/davinci-resolve-mcp/issues/171)
|
|
527
527
|
- **Tags:** timeline, import, silent-failure, unreliable-return
|
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.
|
|
40
|
+
VERSION = "2.104.10"
|
|
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.104.
|
|
90
|
+
VERSION = "2.104.10"
|
|
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.
|
|
14
|
+
VERSION = "2.104.10"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -18975,8 +18975,40 @@ def _safe_quick_export(proj, p: Dict[str, Any]):
|
|
|
18975
18975
|
return {"success": False, "validation": validation}
|
|
18976
18976
|
if p.get("dry_run") or not p.get("allow_render", False):
|
|
18977
18977
|
return _ok(would_render=False, preset=preset, params=params, validation=validation)
|
|
18978
|
+
before = set()
|
|
18979
|
+
if target_dir and os.path.isdir(target_dir):
|
|
18980
|
+
before = set(os.listdir(target_dir))
|
|
18978
18981
|
status = _ser(proj.RenderWithQuickExport(preset, params))
|
|
18979
|
-
|
|
18982
|
+
out: Dict[str, Any] = {
|
|
18983
|
+
"success": not (isinstance(status, dict) and status.get("error")),
|
|
18984
|
+
"status": status,
|
|
18985
|
+
"params": params,
|
|
18986
|
+
}
|
|
18987
|
+
# RenderWithQuickExport's status dict is trusted nowhere else in this
|
|
18988
|
+
# repo's render surface anymore (issue #164 taught that lesson) — check
|
|
18989
|
+
# what actually landed on disk. Best-effort: TargetDir is known, so a
|
|
18990
|
+
# successful export must have produced at least one new file there.
|
|
18991
|
+
if out["success"] and target_dir and os.path.isdir(target_dir):
|
|
18992
|
+
new_files = sorted(set(os.listdir(target_dir)) - before)
|
|
18993
|
+
outputs = []
|
|
18994
|
+
for name in new_files:
|
|
18995
|
+
path = os.path.join(target_dir, name)
|
|
18996
|
+
if not os.path.isfile(path):
|
|
18997
|
+
continue
|
|
18998
|
+
rec: Dict[str, Any] = {"path": path, "size_bytes": os.path.getsize(path)}
|
|
18999
|
+
probe = _ffprobe_media_summary(path)
|
|
19000
|
+
if probe:
|
|
19001
|
+
rec["duration_seconds"] = probe.get("duration_seconds")
|
|
19002
|
+
outputs.append(rec)
|
|
19003
|
+
out["outputs"] = outputs
|
|
19004
|
+
if not outputs:
|
|
19005
|
+
out["success"] = False
|
|
19006
|
+
out["error"] = (
|
|
19007
|
+
"RenderWithQuickExport reported success but wrote no file to "
|
|
19008
|
+
f"{target_dir} — treat the status dict as unreliable and check "
|
|
19009
|
+
"the Deliver page's render queue."
|
|
19010
|
+
)
|
|
19011
|
+
return out
|
|
18980
19012
|
|
|
18981
19013
|
|
|
18982
19014
|
def _export_render_boundary_report(proj, p: Dict[str, Any]):
|
|
@@ -19049,7 +19081,10 @@ def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
|
|
|
19049
19081
|
way to read back — see the SetRenderSettings api_truth entry.
|
|
19050
19082
|
render_job_lifecycle_probe(target_dir, settings?, format?, codec?, custom_name?) -> {success, job_id, status_before_delete}
|
|
19051
19083
|
quick_export_capabilities() -> {presets, safe_params, guards}
|
|
19052
|
-
safe_quick_export(preset, target_dir?|params?, custom_name?, dry_run?, allow_render?) -> {success, status}
|
|
19084
|
+
safe_quick_export(preset, target_dir?|params?, custom_name?, dry_run?, allow_render?) -> {success, status, outputs}
|
|
19085
|
+
After a live export, the files that actually landed in TargetDir are
|
|
19086
|
+
listed with size and ffprobe duration; a success status that wrote no
|
|
19087
|
+
file flips success to false (the issue #164 trust lesson).
|
|
19053
19088
|
export_render_boundary_report(include_matrix?, max_pairs?, include_quick_export?) -> {capabilities, settings, matrix?}
|
|
19054
19089
|
list_delivery_targets(tier?, check_availability?) -> {targets, tiers, schema_version}
|
|
19055
19090
|
resolve_delivery_target(target, overrides?) -> {format_id, codec_id, settings, qc_spec}
|
package/src/utils/api_truth.py
CHANGED
|
@@ -1866,7 +1866,13 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
1866
1866
|
"(issue #171, Studio 21.0.4.5; wrapper behavior verified on "
|
|
1867
1867
|
"19.1.3.7). Distinct from the documented repeated-"
|
|
1868
1868
|
"timelineName None return: here the option is fresh and the "
|
|
1869
|
-
"file's name is stale."
|
|
1869
|
+
"file's name is stale. THE NAMING AUTHORITY DIFFERS PER "
|
|
1870
|
+
"FORMAT (all measured on 19.1.3.7): FCP7 XML ignores "
|
|
1871
|
+
"timelineName entirely (internal <name> wins); AAF honours "
|
|
1872
|
+
"timelineName when given and falls back to its internal "
|
|
1873
|
+
"name; OTIO honours timelineName; and .drt names the "
|
|
1874
|
+
"timeline after the FILE (see the .drt entry below). Only "
|
|
1875
|
+
"FCP7 exhibits the returned-existing trap.",
|
|
1870
1876
|
"recommended": "Rewrite the <sequence><name> inside the file to the "
|
|
1871
1877
|
"intended name before importing — "
|
|
1872
1878
|
"timeline.import_timeline_checked does this "
|