davinci-resolve-mcp 2.137.0 → 2.138.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 +18 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/docs/guides/native-drt-authoring.md +1 -0
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/author-interchange.mjs +6 -1
- package/resolve-advanced/server/tools/drt.mjs +1 -1
- package/resolve-advanced/vendor/drp-format/assemble-timeline.js +1 -0
- package/resolve-advanced/vendor/drp-format/place-transition.js +17 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
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.138.0 — wipes join the conform
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Wipe transitions authored offline** (`transitions[].type: 'wipe'`;
|
|
10
|
+
EDL `W`-codes route to it automatically through
|
|
11
|
+
`assemble_from_interchange`). Harvested from a live 19.1.3.7 EDL W-code
|
|
12
|
+
import (E61): Resolve stores a wipe as the same Cross Dissolve element
|
|
13
|
+
whose FieldsBlob zlib payload zeroes the style-id field the dissolve
|
|
14
|
+
fills — and its own importer maps EVERY W-code (W001/W002/W005 measured
|
|
15
|
+
identical) to one soft-edge wipe style, so a single style is full parity
|
|
16
|
+
with the host importer. Render-proven both directions: the live wipe's
|
|
17
|
+
midpoint splits spatially (157.2/206.3 left/right vs the dissolve's
|
|
18
|
+
uniform 181.6), the element survives the .drt round-trip bit-exact, and
|
|
19
|
+
the offline-authored wipe through the full EDL → assemble → import →
|
|
20
|
+
render route reproduces the split (158.1/205.0). Audio wipes refuse
|
|
21
|
+
(junctions cross-fade).
|
|
22
|
+
|
|
5
23
|
## What's New in v2.137.0 — the sweep reaches the database tier
|
|
6
24
|
|
|
7
25
|
### Fixed
|
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.138.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -42,6 +42,7 @@ window. `render.verify_output` covers the container-level checks.
|
|
|
42
42
|
| Media cuts, multi-source | `media: [{mediaFilePath, spec, cuts}]` | v2.106–2.107 |
|
|
43
43
|
| Multi-track video (V2+ stacking) | `cuts[].track` (video-only above V1) | v2.112 |
|
|
44
44
|
| Cross-dissolves | `transitions: [{track, atFrame, durationFrames}]` | v2.111 |
|
|
45
|
+
| Wipes (EDL `W`-codes) | `transitions[].type: 'wipe'` (single soft-edge style — host-importer parity) | v2.138 |
|
|
45
46
|
| Audio cross-fades | `transitions[].trackType: 'audio'` | v2.116 |
|
|
46
47
|
| Constant retimes, forward | `cuts[].speed` (e.g. `0.5`) | v2.113 |
|
|
47
48
|
| Constant retimes, reverse | `cuts[].reverse` | v2.114 |
|
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.138.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
|
@@ -425,7 +425,12 @@ export function eventsToAssembleSpec(events, opts = {}) {
|
|
|
425
425
|
});
|
|
426
426
|
continue;
|
|
427
427
|
}
|
|
428
|
-
|
|
428
|
+
// EDL W-codes author as a WIPE (Resolve's own EDL importer maps every
|
|
429
|
+
// W-code to its single soft-edge wipe style — W001/W002/W005 measured
|
|
430
|
+
// identical — so one style is host-importer parity; render-verified
|
|
431
|
+
// spatial split at the midpoint). Everything else is a dissolve.
|
|
432
|
+
const kind = /^W/i.test(String(c.type || '')) ? 'wipe' : 'dissolve';
|
|
433
|
+
transitions.push({ track: c.track, atFrame: c.atFrame, durationFrames: c.durationFrames, ...(kind === 'wipe' ? { type: 'wipe' } : {}) });
|
|
429
434
|
}
|
|
430
435
|
// Audio cross-fades, same geometry rules (render-verified on 19.1.3.7 via
|
|
431
436
|
// the harvested cross-fade template: the highpass RMS ramps, not steps).
|
|
@@ -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),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'
|
|
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),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' — the single soft-edge wipe style Resolve's own EDL importer maps every W-code to; all render-verified on 19)}], 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 AND depth-3 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()])
|
|
@@ -211,6 +211,7 @@ async function assembleTimeline(spec = {}) {
|
|
|
211
211
|
({ buffer } = await placeTransition(buffer, {
|
|
212
212
|
track: tr.track, atFrame: tr.atFrame, durationFrames: tr.durationFrames,
|
|
213
213
|
trackType: tr.trackType || 'video',
|
|
214
|
+
type: tr.type || 'dissolve',
|
|
214
215
|
}));
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -55,8 +55,21 @@ const clipDuration = (c) => { const m = c.match(/<Duration>(\d+)<\/Duration>/);
|
|
|
55
55
|
* @returns {Promise<{buffer:Buffer, entry:string, timelineUuid:string, track:number,
|
|
56
56
|
* atFrame:number, start:number, durationFrames:number, transitionDbId:string|null}>}
|
|
57
57
|
*/
|
|
58
|
+
// The WIPE variant (harvested from a live 19.1.3.7 EDL W-code import, E61):
|
|
59
|
+
// Resolve stores a wipe as the SAME Cross Dissolve transition element whose
|
|
60
|
+
// FieldsBlob zlib payload zeroes the style-id field the dissolve fills with
|
|
61
|
+
// 08 c79fc3e9. EffectFiltersBA, alignment, everything else is byte-identical.
|
|
62
|
+
// Render-proven: midpoint splits spatially (left 157.2 / right 206.3 on the
|
|
63
|
+
// standard probe media) vs the dissolve's uniform 181.6, and the element
|
|
64
|
+
// survives the .drt round-trip bit-exact. Resolve's own EDL importer maps
|
|
65
|
+
// EVERY W-code (W001/W002/W005 measured identical) to this single soft-edge
|
|
66
|
+
// wipe style, so one style is full parity with the host importer.
|
|
67
|
+
const WIPE_FIELDS_BLOB = '00000002000000158012120000002c789c636660642016000000da0005';
|
|
68
|
+
|
|
58
69
|
async function placeTransition(drpInput, opts = {}) {
|
|
59
|
-
const { track, atFrame, durationFrames = 24, trackType = 'video', timelineUuid } = opts;
|
|
70
|
+
const { track, atFrame, durationFrames = 24, trackType = 'video', type = 'dissolve', timelineUuid } = opts;
|
|
71
|
+
if (type !== 'dissolve' && type !== 'wipe') throw new Error("placeTransition: type must be 'dissolve' or 'wipe'");
|
|
72
|
+
if (type === 'wipe' && trackType === 'audio') throw new Error('placeTransition: wipes are video-only (audio junctions cross-fade)');
|
|
60
73
|
if (!Number.isInteger(track) || track < 1) throw new TypeError('placeTransition: track must be a positive integer');
|
|
61
74
|
if (!Number.isInteger(atFrame)) throw new TypeError('placeTransition: atFrame must be an integer');
|
|
62
75
|
if (!Number.isInteger(durationFrames) || durationFrames < 2) throw new TypeError('placeTransition: durationFrames must be an integer >= 2');
|
|
@@ -78,6 +91,9 @@ async function placeTransition(drpInput, opts = {}) {
|
|
|
78
91
|
|
|
79
92
|
let trans = fs.readFileSync(trackType === 'audio' ? AUDIO_TEMPLATE_PATH : TEMPLATE_PATH, 'utf8').trim();
|
|
80
93
|
trans = freshDbIds(trans);
|
|
94
|
+
if (type === 'wipe') {
|
|
95
|
+
trans = trans.replace(/<FieldsBlob>[0-9a-fA-F]*<\/FieldsBlob>/, `<FieldsBlob>${WIPE_FIELDS_BLOB}</FieldsBlob>`);
|
|
96
|
+
}
|
|
81
97
|
const start = atFrame - Math.floor(durationFrames / 2); // centered (AlignmentType 2)
|
|
82
98
|
trans = trans.replace(/<Start>\d+<\/Start>/, `<Start>${start}</Start>`);
|
|
83
99
|
trans = trans.replace(/<Duration>\d+<\/Duration>/, `<Duration>${durationFrames}</Duration>`);
|
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.138.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()}")
|