davinci-resolve-mcp 2.201.0 → 2.202.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 +17 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/editorial.mjs +25 -8
- package/resolve-advanced/server/tools/editorial.mjs +2 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
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.202.0 — E150: a rebase needs a real majority; retime rounding is the same window
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A timecode rebase needs a majority of ALL the source's cuts.** The rule
|
|
10
|
+
measured its majority over the cuts whose windows differed, so on a real
|
|
11
|
+
reel two shifted cuts out of fifty-four became that source's base and
|
|
12
|
+
turned fifty-two UNCHANGED cuts into false trims (a control with a free
|
|
13
|
+
floor). The majority is now over every paired cut of the source (at least
|
|
14
|
+
two, and more than half). On that reel: 187 → 237 of 335 cuts retained; the
|
|
15
|
+
reels where a rebase is real keep it.
|
|
16
|
+
- **One source frame of rounding on a retimed pair is the same window.**
|
|
17
|
+
Premiere's exact 80% and Resolve's keyframe slope (0.79999, frame-quantized
|
|
18
|
+
seconds) land one frame apart (41923 vs 41922). On a pair where either side
|
|
19
|
+
is retimed a source edge within `srcTolerance` (default 1 frame) compares
|
|
20
|
+
equal; a 100% pair keeps the exact compare; two frames is still a trim.
|
|
21
|
+
|
|
5
22
|
## What's New in v2.201.0 — E149: a rename is inferred from overlapping windows
|
|
6
23
|
|
|
7
24
|
### 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.202.0 版 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.
|
|
40
|
+
VERSION = "2.202.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
|
@@ -1031,28 +1031,45 @@ function diffChangelistOnce(oldEvents, newEvents, opts = {}) {
|
|
|
1031
1031
|
// camera TC) — not N trims. A shift witnessed on ≥2 cuts of a source is
|
|
1032
1032
|
// that source's offset; the compare below reads old through it, and a cut
|
|
1033
1033
|
// that still differs is a real trim on top of the rebase.
|
|
1034
|
-
const shiftTally = new Map(), differing = new Map();
|
|
1034
|
+
const shiftTally = new Map(), differing = new Map(), pairedCuts = new Map();
|
|
1035
1035
|
for (const { oe, ne } of P.pairs) {
|
|
1036
1036
|
if ([oe.srcIn, oe.srcOut, ne.srcIn, ne.srcOut].some((v) => v == null)) continue;
|
|
1037
|
+
const key = String(ne.source ?? '');
|
|
1038
|
+
pairedCuts.set(key, (pairedCuts.get(key) || 0) + 1);
|
|
1037
1039
|
const dIn = ne.srcIn - oe.srcIn, dOut = ne.srcOut - oe.srcOut;
|
|
1038
1040
|
if (dIn === 0 && dOut === 0) continue;
|
|
1039
|
-
const key = String(ne.source ?? '');
|
|
1040
1041
|
differing.set(key, (differing.get(key) || 0) + 1);
|
|
1041
1042
|
if (dIn === 0 || dIn !== dOut) continue;
|
|
1042
1043
|
if (!shiftTally.has(key)) shiftTally.set(key, new Map());
|
|
1043
1044
|
shiftTally.get(key).set(dIn, (shiftTally.get(key).get(dIn) || 0) + 1);
|
|
1044
1045
|
}
|
|
1045
1046
|
// A rebase is the source's DOMINANT story: the same shift on ≥2 cuts AND
|
|
1046
|
-
// on more than half of the
|
|
1047
|
-
// cuts
|
|
1048
|
-
//
|
|
1049
|
-
// trims
|
|
1047
|
+
// on more than half of ALL the source's paired cuts (E150b) — not merely
|
|
1048
|
+
// of the cuts whose windows differ: on a real reel two shifted cuts out of
|
|
1049
|
+
// fifty-four were adopted as the base and turned fifty-two UNCHANGED cuts
|
|
1050
|
+
// into false trims (a control with a free floor). A source whose cuts each
|
|
1051
|
+
// shift by a different amount (an eye-matched re-conform onto other media
|
|
1052
|
+
// — measured: two of seven cuts happened to share a shift) is trims, not
|
|
1053
|
+
// a base change.
|
|
1050
1054
|
const tcOffsets = new Map();
|
|
1051
1055
|
for (const [source, tally] of shiftTally) {
|
|
1052
1056
|
const [offset, cuts] = [...tally].sort((a, b) => b[1] - a[1])[0];
|
|
1053
|
-
if (cuts >= 2 && cuts > (
|
|
1057
|
+
if (cuts >= 2 && cuts > (pairedCuts.get(source) || 0) / 2) tcOffsets.set(source, { source, offset, cuts, rebased: 0 });
|
|
1054
1058
|
}
|
|
1055
1059
|
|
|
1060
|
+
// RETIME ROUNDING (E150): a retimed clip's source window is the speed map
|
|
1061
|
+
// evaluated and rounded — Premiere's exact 80% and Resolve's keyframe
|
|
1062
|
+
// slope (0.79999, frame-quantized seconds) land one frame apart on a real
|
|
1063
|
+
// reel (41923 vs 41922). On a pair where either side is retimed a source
|
|
1064
|
+
// edge within `srcTolerance` (default 1 frame) is the same window; a 100%
|
|
1065
|
+
// pair keeps the exact compare.
|
|
1066
|
+
const srcTol = opts.srcTolerance ?? 1;
|
|
1067
|
+
const srcDiffers = (a, b, oe, ne) => {
|
|
1068
|
+
if ((a ?? null) === (b ?? null)) return false;
|
|
1069
|
+
if (a == null || b == null) return true;
|
|
1070
|
+
const retimed = (oe.speed ?? 100) !== 100 || (ne.speed ?? 100) !== 100;
|
|
1071
|
+
return retimed ? Math.abs(a - b) > srcTol : true;
|
|
1072
|
+
};
|
|
1056
1073
|
const retained = [];
|
|
1057
1074
|
for (const { oe, ne } of P.pairs) {
|
|
1058
1075
|
const deltas = {};
|
|
@@ -1067,7 +1084,7 @@ function diffChangelistOnce(oldEvents, newEvents, opts = {}) {
|
|
|
1067
1084
|
} else if (Math.abs((oe.recIn ?? 0) - (ne.recIn ?? 0)) > recTol) {
|
|
1068
1085
|
kind = 'moved';
|
|
1069
1086
|
deltas.recIn = { old: oe.recIn, new: ne.recIn };
|
|
1070
|
-
} else if ((oIn
|
|
1087
|
+
} else if (srcDiffers(oIn, ne.srcIn, oe, ne) || srcDiffers(oOut, ne.srcOut, oe, ne)) {
|
|
1071
1088
|
kind = 'trimmed';
|
|
1072
1089
|
deltas.src = { old: [oIn, oOut], new: [ne.srcIn, ne.srcOut], ...(off ? { tcOffset: off, oldBeforeRebase: [oe.srcIn, oe.srcOut] } : {}) };
|
|
1073
1090
|
} else if (off && ((oe.srcIn ?? null) !== (ne.srcIn ?? null))) {
|
|
@@ -67,6 +67,7 @@ const changelistSchema = z.object({
|
|
|
67
67
|
old: eventArray.describe('Old (locked-cut) normalized events, OR provide oldFormat+oldContent'),
|
|
68
68
|
new: eventArray.describe('New (turnover) normalized events'),
|
|
69
69
|
recTolerance: z.number().optional(),
|
|
70
|
+
srcTolerance: z.number().optional().describe('Source-frame slack on RETIMED pairs only (default 1): a speed map evaluated and rounded lands a frame apart between apps (E150); 100% pairs compare exactly'),
|
|
70
71
|
timingGuards: z.boolean().optional().describe('Also run the timing silent-lie guards (default true)'),
|
|
71
72
|
sourceAliases: z.array(z.object({ from: z.string().optional(), to: z.string().optional(), pattern: z.string().optional(), replace: z.string().optional(), flags: z.string().optional() })).optional()
|
|
72
73
|
.describe('Rename OLD sources before pairing (E141): {from,to} exact or {pattern,replace} regex — e.g. the offline "4K-2K" proxies vs the online "4K" masters. A systematic rename is also inferred automatically (inferAliases) and reported in sourceAliases.'),
|
|
@@ -150,7 +151,7 @@ export const editorialTool = {
|
|
|
150
151
|
}
|
|
151
152
|
if (action === 'turnover_changelist') {
|
|
152
153
|
const p = changelistSchema.parse(args);
|
|
153
|
-
const result = diffChangelist(p.old, p.new, { recTolerance: p.recTolerance, sourceAliases: p.sourceAliases, inferAliases: p.inferAliases });
|
|
154
|
+
const result = diffChangelist(p.old, p.new, { recTolerance: p.recTolerance, srcTolerance: p.srcTolerance, sourceAliases: p.sourceAliases, inferAliases: p.inferAliases });
|
|
154
155
|
// The guards read the old cut through the SAME aliases the changelist adopted (E148).
|
|
155
156
|
if (p.timingGuards !== false) result.timing = timingGuards(p.old, p.new, { sourceAliases: result.sourceAliases });
|
|
156
157
|
return result;
|
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.202.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()}")
|