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 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
- [![Version](https://img.shields.io/badge/version-2.201.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.202.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.201.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.202.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.201.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
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.201.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.201.0",
3
+ "version": "2.202.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -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 cuts whose windows differ at all. A source whose
1047
- // cuts each shift by a different amount (an eye-matched re-conform onto
1048
- // other media measured: two of seven cuts happened to share a shift) is
1049
- // trims, not a base change.
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 > (differing.get(source) || 0) / 2) tcOffsets.set(source, { source, offset, cuts, rebased: 0 });
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 ?? null) !== (ne.srcIn ?? null) || (oOut ?? null) !== (ne.srcOut ?? null)) {
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;
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.201.0"
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()}")
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.201.0"
14
+ VERSION = "2.202.0"
15
15
 
16
16
  import base64
17
17
  import os