davinci-resolve-mcp 2.140.0 → 2.141.0
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 +16 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/guides/native-drt-authoring.md +5 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/editorial.mjs +15 -0
- package/resolve-advanced/server/tools/drt.mjs +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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.141.0 — OTIO transitions, depth-4, and honest gaps
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **OTIO `Transition` children parse and author** — they occupy no record
|
|
10
|
+
time (the AAF overlap convention), attach to the incoming clip, and route
|
|
11
|
+
through the same style table as XMEML effectids (`SMPTE_Dissolve` → plain
|
|
12
|
+
dissolve; wipe-named types → the wipe). A Gap breaks the junction.
|
|
13
|
+
|
|
14
|
+
### Measured
|
|
15
|
+
|
|
16
|
+
- **Depth-4 nested compounds render** (234 through four levels via the spec
|
|
17
|
+
route) — nesting depth is effectively unbounded on 19.1.3.
|
|
18
|
+
- **Record gaps render clean black** (cut 122.9 / gap 16.0 / white 234) —
|
|
19
|
+
sparse turnovers assemble without synthetic filler.
|
|
20
|
+
|
|
5
21
|
## What's New in v2.140.0 — the transition style registry
|
|
6
22
|
|
|
7
23
|
### 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.
|
|
15
|
+
> 本翻译对应 v2.141.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -58,7 +58,7 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
58
58
|
| Subtitles | `spec.subtitles` / `spec.subtitlesSrt` (raw SRT) | v2.128 |
|
|
59
59
|
| Compound clips | survive `extract_from_drp` → `.drt` (inner containers kept, recursive) | v2.130 |
|
|
60
60
|
| Compound authoring | `spec.compounds` (multiple compose; nested edits render) | v2.131–2.132 |
|
|
61
|
-
| Nested compounds | `compounds[].compounds` (depth-2
|
|
61
|
+
| Nested compounds | `compounds[].compounds` (depth-2 through depth-4 playback render-verified) | v2.134–2.141 |
|
|
62
62
|
| Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
|
|
63
63
|
|
|
64
64
|
`assemble_from_interchange` drives the same engine from an EDL / OTIO /
|
|
@@ -115,6 +115,10 @@ runs in the "working" direction. The real freeze (harvested from a live EDL
|
|
|
115
115
|
left **empty**. `cuts[].freeze: true` authors exactly that; EDL `M2 000.0`
|
|
116
116
|
and zero-speed warps route to it through `assemble_from_interchange`.
|
|
117
117
|
|
|
118
|
+
**Gaps are legitimate.** Cuts need not abut: record gaps between placements
|
|
119
|
+
render as clean black (measured: cut 122.9 / gap 16 / white 234), so sparse
|
|
120
|
+
turnovers assemble without synthetic filler.
|
|
121
|
+
|
|
118
122
|
**Timeline origin.** Template timelines start at frame 86400
|
|
119
123
|
(01:00:00:00 @ 24fps). Clips placed before the origin are silently dropped
|
|
120
124
|
by Resolve on import — `startFrame` is timeline-absolute.
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.141.0"
|
|
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
|
@@ -152,12 +152,25 @@ export function parseOTIO(otio, opts = {}) {
|
|
|
152
152
|
}));
|
|
153
153
|
}
|
|
154
154
|
let rec = 0;
|
|
155
|
+
// An OTIO Transition occupies NO record time (like an AAF transition —
|
|
156
|
+
// it overlaps its neighbours); it describes the junction between the
|
|
157
|
+
// previous and NEXT clip. Carry it to the next clip event's transition
|
|
158
|
+
// field so the bridge can author it (type strings like SMPTE_Dissolve
|
|
159
|
+
// map through the same style table as XMEML effectids).
|
|
160
|
+
let pendingTransition = null;
|
|
155
161
|
for (const child of track.children || []) {
|
|
156
162
|
const schema = child.OTIO_SCHEMA || '';
|
|
157
163
|
const dur = (child.source_range && child.source_range.duration && child.source_range.duration.value) || 0;
|
|
158
164
|
const rate = (child.source_range && child.source_range.duration && child.source_range.duration.rate) || opts.fps || 24;
|
|
159
165
|
if (schema.startsWith('Gap')) {
|
|
160
166
|
rec += dur;
|
|
167
|
+
pendingTransition = null; // a gap breaks the junction
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (schema.startsWith('Transition')) {
|
|
171
|
+
const inOff = (child.in_offset && child.in_offset.value) || 0;
|
|
172
|
+
const outOff = (child.out_offset && child.out_offset.value) || 0;
|
|
173
|
+
pendingTransition = { type: String(child.transition_type || 'SMPTE_Dissolve'), duration: inOff + outOff };
|
|
161
174
|
continue;
|
|
162
175
|
}
|
|
163
176
|
if (schema.startsWith('Clip')) {
|
|
@@ -191,9 +204,11 @@ export function parseOTIO(otio, opts = {}) {
|
|
|
191
204
|
recOut: rec + dur,
|
|
192
205
|
speed: Math.abs(speed),
|
|
193
206
|
reverse,
|
|
207
|
+
transition: pendingTransition,
|
|
194
208
|
fps: rate,
|
|
195
209
|
}),
|
|
196
210
|
);
|
|
211
|
+
pendingTransition = null;
|
|
197
212
|
rec += dur;
|
|
198
213
|
}
|
|
199
214
|
}
|
|
@@ -51,7 +51,7 @@ const assembleSchema = z.object({
|
|
|
51
51
|
spec: z
|
|
52
52
|
.object({})
|
|
53
53
|
.passthrough()
|
|
54
|
-
.describe("assembleTimeline spec: { timelineName?, startFrame? (timeline start frame @24, default 86400=01:00:00:00 — sets the start TIMECODE, render-verified on 19), media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?,track? (1-based video track; >1 = video-only, render-verified stacking),speed?/reverse? (constant retime, e.g. 0.5, forward or backwards; video-only; readback+render-verified on 19),freeze? (true = hold source frame srcIn for the whole cut; video-only; render-proven frozen on 19 via freezedetect),ramp? ([{durationFrames,speed},...] >=2 LINEAR segments from the cut head, srcIn honored; video-only; render-proven cadence on 19 — eased/curved ramps are NOT authorable, an interp!=0 keyframe crashes Resolve on import (measured)),audioOnly?+track? (explicit AUDIO placement on audio track 1-8; presence suppresses the A1 mirror; render-verified on 19)}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), transitions?: [{track, atFrame, durationFrames?, trackType? ('video' | 'audio' cross-fade), type? ('dissolve' default | 'wipe' | 'dip' | 'additive' | 'fade-to-color' | 'smooth-cut' | 'non-additive' — all render-verified on 19 w/ midpoint fingerprints; XMEML/EDL transition codes route automatically, and NOTE Resolve's own XMEML importer writes transitions that render INERT, so this route beats it)}], markers?: [{frame (timeline-absolute), color? (16 names), name?, note?, duration?, customData?}] (readback-verified on 19), compounds?: [{name, startFrame (parent, absolute), durationFrames, track?, cuts:[{mediaFilePath, startFrame (INNER, 0-based), durationFrames, srcIn?}], compounds?: [same, nested — frames inner-relative]}] (multiple PARALLEL compounds compose AND compounds NEST recursively — depth-2
|
|
54
|
+
.describe("assembleTimeline spec: { timelineName?, startFrame? (timeline start frame @24, default 86400=01:00:00:00 — sets the start TIMECODE, render-verified on 19), media?: {mediaFilePath, spec:{width,height,frameCount,fps}, cuts:[{startFrame,durationFrames,srcIn?,track? (1-based video track; >1 = video-only, render-verified stacking),speed?/reverse? (constant retime, e.g. 0.5, forward or backwards; video-only; readback+render-verified on 19),freeze? (true = hold source frame srcIn for the whole cut; video-only; render-proven frozen on 19 via freezedetect),ramp? ([{durationFrames,speed},...] >=2 LINEAR segments from the cut head, srcIn honored; video-only; render-proven cadence on 19 — eased/curved ramps are NOT authorable, an interp!=0 keyframe crashes Resolve on import (measured)),audioOnly?+track? (explicit AUDIO placement on audio track 1-8; presence suppresses the A1 mirror; render-verified on 19)}]} | [same, ...] (multi-source needs media_pool.capture_media_template run once per file), transitions?: [{track, atFrame, durationFrames?, trackType? ('video' | 'audio' cross-fade), type? ('dissolve' default | 'wipe' | 'dip' | 'additive' | 'fade-to-color' | 'smooth-cut' | 'non-additive' — all render-verified on 19 w/ midpoint fingerprints; XMEML/EDL transition codes route automatically, and NOTE Resolve's own XMEML importer writes transitions that render INERT, so this route beats it)}], markers?: [{frame (timeline-absolute), color? (16 names), name?, note?, duration?, customData?}] (readback-verified on 19), compounds?: [{name, startFrame (parent, absolute), durationFrames, track?, cuts:[{mediaFilePath, startFrame (INNER, 0-based), durationFrames, srcIn?}], compounds?: [same, nested — frames inner-relative]}] (multiple PARALLEL compounds compose AND compounds NEST recursively — depth-2 through depth-4 playback render-verified on 19 (the old depth-2 black was a missing SequenceSetup key, fixed); inner cuts need captured templates w/ native clips), subtitles?: [{startFrame (timeline-absolute), durationFrames, text}] + subtitlesSrt? (raw SRT, cues anchor at the origin; readback-verified on 19; angle-bracket runs read as SRT markup), elements?: [{type:'title'|'generator', track, startFrame, durationFrames?, text?, generatorName? ('Solid Color'|'SMPTE Color Bar'|'Grey Scale' render-verified on 19), ...}] }. startFrame is timeline-absolute (origin 86400)."),
|
|
55
55
|
outputPath: z.string().describe('Absolute path where the importable .drt will be written'),
|
|
56
56
|
targetAppVersion: z
|
|
57
57
|
.union([z.string(), z.number()])
|
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.
|
|
90
|
+
VERSION = "2.141.0"
|
|
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()}")
|